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
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/wishlistwishlist looks for, in order:
--config <path>(CLI flag)~/.config/wishlist.yml~/.config/wishlist.yaml~/.config/wishlist.jsonwishlist.{yml,yaml,json}in the current directory
- 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[
{ "name": "production", "host": "prod.example.com", "port": 2222, "user": "deploy" },
{ "name": "staging", "host": "stage.example.com" }
]| Key | Action |
|---|---|
| β / k | Move up |
| β / j | Move down |
| Enter | Connect to highlighted endpoint |
| Esc / ^C | Quit without connecting |
| (typing) | Type-to-filter; Backspace clears |
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.
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);
}Phase 9+ β first cut. 26 tests / 67 assertions. Endpoint, Config (JSON + flat-YAML), Picker, Launcher are all covered.
