Command reference#

Setup and UI#

./scripts/bootstrap
./scripts/serverctl doctor [--json]
./scripts/serverctl web [--port PORT] [--no-open --url-file PATH]
./scripts/serverctl web --credential ALIAS [--port PORT] [--no-open --url-file PATH]
./scripts/serverctl web --status [--json]
./scripts/serverctl web --stop [--json]
./scripts/serverctl ui [--port PORT] [--no-open --url-file PATH]
./scripts/serverctl ui --credential ALIAS [--port PORT] [--no-open --url-file PATH]
./scripts/serverctl ui --status [--json]
./scripts/serverctl ui --stop [--json]

web is the browser-shell entry point; ui is a compatibility alias with identical options. Both open the bundled modern React UI and share one managed runtime record. A second launch fails while a managed runtime is active; use --status and the existing window rather than stopping someone else's active session.

The default --port 0 selects an available loopback port and opens the browser. When --no-open is used, --url-file is required; the file is created with mode 600 and contains the one-time tokenized URL. The token is never printed. If an explicitly requested port is occupied, retry with --port 0 or choose a different port.

ui --credential ALIAS opens the browser directly on that server's edit dialog with the new-credential form expanded, so entering a password is one field and one click. Use it right after server add when guiding a user through first-time setup.

ui --status reports the managed UI process (pid, port, start time) and exits 0 while it runs. ui --stop terminates it and deletes its --url-file; the command is idempotent, so use it to clean up when the user is done with the UI instead of tracking pids by hand.

Agent post-install handoff#

doctor is a local health check, not the completion condition for an agent-driven installation. After it succeeds, an installing agent should run serverctl server list --json once, summarize the actual managed hosts, and teach the user what they can now ask for in natural language using those real aliases. Do not add remote server test calls only for onboarding, do not lead with database/config paths or raw JSON, and do not require the user to learn CLI syntax. Make clear that stored secrets remain in the local OS vault and that a successful doctor does not prove a host login has been tested.

Servers#

./scripts/serverctl server list [--json]
./scripts/serverctl server show ALIAS [--json]
./scripts/serverctl server add ALIAS --hostname HOST --username USER [--port PORT]
  [--credential CREDENTIAL | --password-credential LABEL] [--proxy-jump ALIAS ...] [--notes TEXT]
./scripts/serverctl server edit ALIAS [field options]
./scripts/serverctl server remove ALIAS [--yes]
./scripts/serverctl server import [--config PATH] [--apply] [--overwrite] [--json]
./scripts/serverctl server test ALIAS [--timeout SECONDS] [--json]
./scripts/serverctl server diagnose ALIAS [--timeout SECONDS] [--json]
./scripts/serverctl server note ALIAS (--text TEXT [--append] | --clear) [--json]

An import without --apply is always a preview. Literal aliases are resolved with ssh -G; wildcard-only patterns are reported but not imported.

Import warnings identify authentication directives that are not converted into managed credentials, configured IdentityFile paths and the selected source config. Applied hosts retain that config as an OpenSSH fallback, including a custom --config path. Import reads no private-key contents and does not verify login. --overwrite updates imported connection fields while preserving the latest credential binding, notes, tags, directories and Skill bindings. An empty credential binding may still authenticate through the source config; confirm with an explicit server test.

server add --password-credential LABEL creates a password credential and attaches it in one step, using the same hidden local prompt as credential add-password. It needs a real terminal; without a TTY it fails fast and points to serverctl ui --credential ALIAS instead.

diagnose identifies the host's system: its remote check reports os, os_id, os_version, os_family, and package_manager (Linux distros via os-release, macOS via sw_vers, BSDs, and Windows OpenSSH hosts) plus kernel, arch, CPU, memory, and disk. Use recent cached facts, or run it when those facts are missing or stale, before choosing install/admin commands; it is read-only and heavier than test, so run it on demand, not as a routine sweep. A successful remote summary is saved as sanitized facts, including observed_at, os_like, and a closed os_logo key, for subsequent offline inventory reads.

list --json and show --json read local metadata without DNS, SSH, or vault access. They include nullable facts, last_success_at, last_resolved_ip, host_key, and geo, plus the literal address_scope. A failed test updates the latest test result but retains the last successful timestamp and facts. The resolved IP is a local DNS snapshot from an explicit check, not proof of the peer IP through ProxyJump. Host-key states are verified, unknown, changed, or revoked; saved state never bypasses OpenSSH verification. Changing hostname or port clears the old endpoint's observations.

note changes only local server metadata. Use --text to replace a note, --append to add an agent or user observation without losing existing text, or --clear to remove it. Never put passwords or key passphrases in notes.

Credentials#

./scripts/serverctl credential list [--json]
./scripts/serverctl credential add-password LABEL
./scripts/serverctl credential add-key LABEL --key-path PATH [--store-passphrase]
./scripts/serverctl credential add-agent LABEL
./scripts/serverctl credential edit LABEL [--label NEW_LABEL] [--key-path PATH]
  [--replace-secret] [--replace-passphrase] [--clear-passphrase]
./scripts/serverctl credential remove LABEL [--yes]

Secret input uses a local hidden prompt. There is deliberately no CLI command that reveals a saved secret.

Keys are locally parsed before saving, supporting OpenSSH/PEM RSA, ECDSA and Ed25519 identities. Unsupported formats fail explicitly. An encrypted key needs its passphrase through the hidden prompt or local UI; never provide one in chat, argv or environment variables. The private-key path and public metadata are saved, not its contents. Empty replacement input preserves the existing value; --clear-passphrase is the explicit removal action. Whitespace is significant.

remove still refuses a credential referenced by any host. The Web Credentials view provides a separate preview and explicit Detach and delete transaction, with durable pending vault/config cleanup and Retry cleanup when needed. Neither operation removes a private-key file. A committed deletion with pending cleanup must not be presented as completed secret removal.

The Web view also exposes local key Browse/Inspect/Validate, agent identities and Test on host. There is no new credential-test CLI command. The Web test authenticates the selected credential against any saved host without rebinding it, executing a remote command, or updating host health history. Password/key tests restrict authentication to the selected kind; agent tests use only agent keys without default private-key fallback, unlike ordinary connections that follow the managed OpenSSH configuration. A none authentication result is not credential verification. Progress/results never return secrets. See the local UI and API contracts.

Host-bound skills#

./scripts/serverctl skill discover [--json]
./scripts/serverctl skill list [--server ALIAS] [--json]
./scripts/serverctl skill show NAME|ID [--json]
./scripts/serverctl skill add PATH [--server ALIAS ...] [--json]
./scripts/serverctl skill refresh NAME|ID [--path PATH] [--json]
./scripts/serverctl skill attach NAME|ID SERVER [SERVER ...] [--json]
./scripts/serverctl skill detach NAME|ID SERVER [SERVER ...] [--json]
./scripts/serverctl skill remove NAME|ID [--yes] [--json]
./scripts/serverctl skill resolve ALIAS [ALIAS ...] [--json]

discover scans the standard local skill roots (~/.agents/skills, ~/.codex/skills, and ~/.claude/skills) plus SSM_SKILLS_DIRS. It is read-only and offline: candidates are not installed, registered, or bound. The base ssh-server-manager transport skill is omitted and cannot be registered as host-bound guidance. add accepts either a skill directory or its SKILL.md; each repeated --server creates a binding in the same transaction. Skill names are case-insensitively unique, so same-name candidates at different paths are an explicit conflict rather than an arbitrary choice.

discover --json returns candidates and conflicts. Candidate status is available, registered, conflict, or invalid; valid candidates include name, description, and path, registered matches include registered_id, and invalid candidates put the validation reason in error. Name conflicts contain name plus paths; path conflicts contain path plus names; registry conflicts also include registered_id.

refresh re-reads the registered SKILL.md, optionally from a replacement path. attach and detach update bindings for all positional server aliases. remove is blocked while any host is bound; detach those hosts first. remove --yes skips only the confirmation and never deletes local skill files.

Agents should call resolve after determining the target hosts and again whenever the target changes. The JSON contains both per-host skills and a deduplicated skill list whose applies_to aliases preserve multi-host scope. It returns metadata and paths, not skill bodies. missing, invalid, and name_mismatch registrations make ok false and the command exit 1; do not silently reuse a previous host's skill context. list and resolve add status_message to non-ready entries; show returns stored metadata and host bindings without calculating readiness.

Saved working directories#

./scripts/serverctl path list ALIAS [--json]
./scripts/serverctl path add ALIAS PATH --label LABEL [--note TEXT] [--json]
./scripts/serverctl path edit ALIAS LABEL|ID [--label NEW] [--path NEW] [--note TEXT] [--json]
./scripts/serverctl path remove ALIAS LABEL|ID [--yes] [--json]
./scripts/serverctl path resolve ALIAS [ALIAS ...] [--json]

Call path resolve after choosing the target hosts and before browsing or transferring anything. It returns, in one read and without connecting, the directories the user has marked as theirs on those hosts — which is the difference between working in /srv/app and guessing at /var/www. The paths array collapses identical paths across hosts and lists every host that has one in applies_to, so a fleet-wide action can be scoped in a single step.

Saved directories are one-to-many, unlike skills: each belongs to one host, and labels are unique per host case-insensitively. They are metadata the user wrote, not a directory listing — never present them as proof a path exists.

File transfer#

./scripts/serverctl cp SOURCE DEST [--force] [--timeout SECONDS] [--json]
./scripts/serverctl get ALIAS:REMOTE [LOCAL] [--force] [--timeout SECONDS] [--json]
./scripts/serverctl put LOCAL ALIAS:REMOTE [--force] [--timeout SECONDS] [--json]

Use these instead of scp, rsync, or sftp. They run the same SFTP path as the rest of the tool, so a vault password or key passphrase is injected without prompting; the raw tools cannot see the vault and will prompt or fail.

Exactly one side is ALIAS:PATH. One file per invocation. get refuses to overwrite an existing local file without --force — treat that refusal as a question for the user, not an obstacle to route around.

The browser's separate HTTP transfer interface is capped at 100 MiB per file (advertised as max_web_transfer_bytes by /api/v1/runtime). Its uploads default to refusing an existing remote destination with HTTP 409; an authorized overwrite uses the API's explicit overwrite=true form field. Default web uploads require the server's SFTP hard-link operation for safe no-replace publication; unsupported servers return an error. Web downloads refuse symlinks when the target size cannot be verified. These HTTP options add no CLI commands or flags: CLI transfers have no web size cap, and CLI put retains its existing remote-replacement behavior.

Mounting#

./scripts/serverctl mount ALIAS[:REMOTE] [MOUNTPOINT] [--read-only] [--json]
./scripts/serverctl mount --list [--json]
./scripts/serverctl unmount ALIAS|MOUNTPOINT [--json]

Mounting needs a FUSE stack that may not be installed: macFUSE, libfuse, or SSHFS-Win with WinFsp. Check the mount row of serverctl doctor before offering it, and report the missing component instead of retrying. Prefer --read-only unless the user asked to write.

Connections#

./scripts/serverctl connect ALIAS
./scripts/serverctl exec ALIAS [--stdin|--stdin-binary] [--reuse SECONDS] [--shell] [--json] -- COMMAND [ARG ...]
./scripts/serverctl config render [--json]

connect opens an interactive shell and is meant for a human terminal, not for agent tool calls (see SKILL.md). Without a TTY it fails fast with exit code 2 and a pointer to exec.

With the normal OpenSSH ask policy, an unknown host key is shown in the human's controlling terminal even when AskPass supplies a saved password or key passphrase. Compare its fingerprint with a trusted source before typing yes or the matching fingerprint; no, EOF, or an unavailable terminal rejects confirmation. Explicit stricter SSH policies remain in force. This does not authorize agent calls, checks, transfers, or credential tests to accept keys, and it does not replace a changed or revoked key.

exec quotes command arguments for a POSIX remote shell. --shell accepts exactly one command string and runs it through remote sh -lc; use it for pipelines, redirects, command substitutions, or multiple commands. Without --shell, pass the executable and each argument separately. For a non-POSIX remote shell, pass arguments appropriate for that server and omit --shell.

--stdin is UTF-8 text. --stdin-binary streams raw bytes without a text decode, which is suitable for artifacts piped to a remote command. Do not put secrets in command arguments, environment variables, or temporary files.

Edit this page on GitHub