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).

Maintainers

Package info

github.com/sugarcraft/sugar-wishlist

Documentation

pkg:composer/sugarcraft/sugar-wishlist

Transparency log

Statistics

Installs: 0

Dependents: 1

Suggesters: 1

Stars: 1

Open Issues: 0

dev-master 2026-06-29 23:44 UTC

This package is auto-updated.

Last update: 2026-07-10 04:40:35 UTC


README

sugar-wishlist

SugarWishlist

CI codecov Packagist Version License PHP

demo

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:

  1. --config <path> (CLI flag)
  2. ~/.config/wishlist.yml
  3. ~/.config/wishlist.yaml
  4. ~/.config/wishlist.json
  5. wishlist.{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-fuzzySmithWatermanMatcher::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.