sugarcraft / sugar-wishlist
SSH endpoint launcher — a TUI directory of `ssh user@host` shortcuts. Port of charmbracelet/wishlist. Loads endpoints from YAML/JSON, renders an interactive picker, then `exec`s into the chosen ssh client (with full host-key prompt / agent forwarding fidelity).
Requires
- php: >=8.3
- ext-pcntl: *
- sugarcraft/candy-core: dev-master
- sugarcraft/candy-fuzzy: dev-master
- sugarcraft/sugar-bits: dev-master
Requires (Dev)
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-07-10 03:51:48 UTC
README
SugarWishlist
PHP port of charmbracelet/wishlist — a TUI directory of SSH endpoints. Launch wishlist, pick a host, hit Enter, and the current process is replaced with ssh connecting to it.
── wishlist ──
filter:
▸ production ─ deploy@prod.example.com:2222
staging ─ stage.example.com
dev ─ dev.example.com
↑/↓ select · Enter connect · Esc quit · type to filter
Install
The wishlist binary lives at bin/wishlist. Composer adds it to your global vendor/bin/ when installed as a project dependency, or you can add the repo's bin/ to your $PATH.
composer require sugarcraft/sugar-wishlist
~/.composer/vendor/bin/wishlist
Configure
wishlist looks for, in order:
--config <path>(CLI flag)~/.config/wishlist.yml~/.config/wishlist.yaml~/.config/wishlist.jsonwishlist.{yml,yaml,json}in the current directory
YAML
- name: production host: prod.example.com port: 2222 user: deploy identity_file: ~/.ssh/prod-deploy - name: staging host: stage.example.com user: deploy - name: jumpbox host: bastion.example.com options: - ServerAliveInterval=30 - ProxyJump=gw.example.com
JSON
[
{ "name": "production", "host": "prod.example.com", "port": 2222, "user": "deploy" },
{ "name": "staging", "host": "stage.example.com" }
]
Keybindings
| Key | Action |
|---|---|
| ↑ / k | Move up |
| ↓ / j | Move down |
| Enter | Connect to highlighted endpoint |
| Esc / ^C | Quit without connecting |
| (typing) | Type-to-filter; Backspace clears |
Implementation
The picker is a tiny standalone widget — not a full SugarBits List. The lifecycle is
read config → render picker → read keys → choose → pcntl_exec(ssh, argv)
That last pcntl_exec is the critical line: it replaces the PHP process with ssh. File descriptors, environment, and the controlling tty all flow through unchanged, so the user sees a normal ssh session — host-key prompts, agent forwarding, MOTD, exit status, all native. We never proxy bytes; we get out of the way.
Programmatic use
use SugarCraft\Wishlist\Config; use SugarCraft\Wishlist\Picker; use SugarCraft\Wishlist\Launcher; $endpoints = Config::load('/etc/wishlist.yml'); $picked = (new Picker())->pick($endpoints); if ($picked !== null) { (new Launcher())->dispatch($picked); }
Import from SSH Config
wishlist can import endpoints directly from your OpenSSH config file (~/.ssh/config):
use SugarCraft\Wishlist\Config; $endpoints = Config::importFromSshConfig('/home/user/.ssh/config');
The parser handles:
| SSH Config Key | Endpoint Field |
|---|---|
Host <pattern> |
name |
HostName <value> |
host |
User <value> |
user |
Port <value> |
port |
IdentityFile <path> |
identityFiles[] |
ProxyJump <host> |
proxyJump |
Host * global defaults are inherited by all subsequent host blocks. Host patterns are used as the endpoint name (when no HostName is specified, the pattern itself becomes the host).
Programmatic Use
use SugarCraft\Wishlist\Config; use SugarCraft\Wishlist\Picker; use SugarCraft\Wishlist\Launcher; $endpoints = Config::load('/etc/wishlist.yml'); $picked = (new Picker())->pick($endpoints); if ($picked !== null) { (new Launcher())->dispatch($picked); } // Or import from SSH config: $sshEndpoints = Config::importFromSshConfig('/home/user/.ssh/config');
Shared foundations
sugar-wishlist uses candy-fuzzy — SmithWatermanMatcher::matchAll() replaces ad-hoc str_contains-style filtering. The picker now surfaces scored ranking and match-highlight indices (ANSI bold+cyan on matched grapheme clusters) for ranked, highlighted filter results.
Status
Phase 10.28 — SSH config import. 69 tests / 176 assertions. Endpoint, Config (JSON + flat-YAML + SSH config), Picker, Launcher, SshConfigParser are all covered.
