Skip to content

CLI ​

Use sshc help for the full command list. Use sshc <command...> --help or sshc help <command...> for the exact arguments accepted by an individual command, for example sshc sync push --help or sshc help terminal send.

An option that takes a value accepts both --jobs 4 and --jobs=4. A short name such as -j accepts only -j 4. Giving the same option twice is an error.

The CLI uses the same OpenSSH configuration and the running engine's vault and sessions. For automation, use supported --json output instead of parsing human-readable text.

Codex and other AI agents can call the CLI directly. When the vault is unlocked and every host key and credential required by the route is already saved, non-interactive SSH uses the stored password, key passphrase, and assigned TOTP. The agent does not need the credential itself.

--json output ​

A command run with --json prints exactly one JSON value on stdout, whether it succeeds or fails:

json
{"schemaVersion":1,"success":true,"result":{}}
{"schemaVersion":1,"success":false,"failure":{"kind":"engine_not_running","retryable":true}}

When success is true, result holds the command's result. When it is false, failure holds the kind of failure (kind) and whether retrying the same operation later may succeed (retryable), and the exit code is non-zero. Decide on success and kind instead of parsing human-readable text. Only the report of a non-interactive Serial or Telnet run (--non-interactive) uses its own shape, which includes the transcript.

When sshc cannot parse the arguments, for example because an argument is missing or malformed or an option is given twice, the command does not start and prints no JSON even with --json. It writes the reason and the usage on stderr and exits with status 2. Stdout is empty in that case, so check the exit status before reading it as JSON.

Engine and vault ​

sh
sshc engine
sshc engine --port 60001
sshc engine --replace
sshc engine --background
sshc
sshc open
sshc open --no-browser
sshc open <alias>
sshc stop
sshc status --json
sshc version
sshc vault status
sshc vault create
sshc vault unlock
sshc vault lock
sshc vault change-password
sshc service install
sshc service status
sshc service restart
sshc service disable
sshc update

sshc without arguments and sshc open start the engine in the background if needed, wait until it is ready, print a one-time URL and open it in the browser. sshc open --no-browser performs the same startup but only prints the URL, for headless machines or use over SSH.

sshc open <alias> opens the sshc engine running on an SSH host in this machine's browser. This machine's engine connects to the host over SSH and receives the one-time URL printed by sshc open --no-browser there. It replaces the port with a relay listening on this machine's 127.0.0.1, prints the URL and opens it in the browser. Terminals and workspaces belong to the engine on the host, so they keep running there after the browser closes.

  • The relay port is remembered per alias and reused, because the settings a browser saves are kept per port.
  • The relay lasts until this machine's engine stops. After that, or after the engine on the host restarts, run sshc open <alias> again.
  • The SSH connection does not prompt. Accept a new host's key with sshc ssh <alias> first, and save any passphrase or password the host needs in Connections.
  • The SSH server on the host must allow TCP forwarding to its 127.0.0.1 (AllowTcpForwarding yes or local in sshd_config). If it does not, the command says so before printing a URL.
  • sshc must already be installed on the host. It is looked up on PATH and in ~/.local/bin, /home/linuxbrew/.linuxbrew/bin, /opt/homebrew/bin and /usr/local/bin. The host must run macOS or Linux.
  • Both machines listen on 127.0.0.1 only. On Linux, connections from another OS user on this machine are not relayed.
  • This machine's sshc UI and the host's UI can be open in the same browser at the same time.

sshc stop asks the running engine to stop and waits for it to exit. Open terminals and transfers close. If no engine is running, it exits successfully. On Linux, macOS, and Windows, an enabled service starts the engine again at the next login; use sshc service disable to keep it stopped.

Background output is appended to ~/.ssh/sshc/logs/engine.log (%USERPROFILE%\.ssh\sshc\logs\engine.log on Windows). If it exceeds 5 MiB before startup, it replaces engine.log.1. The directory and files are private to the user and excluded from encrypted sync. If the engine exits before it is ready, the CLI reports its exit code and the last 20 log lines.

sshc engine runs in the foreground. --background is used by services and background starts: it releases the console on Windows and appends stdout, stderr, and logs to the log file above. sshc engine --port <n> (1024–65535) pins the listening port. sshc version (-v, --version) prints this binary's version, and sshc vault status prints the same table as sshc status with the vault state.

Interactive secrets such as the Vault master password display one * per typed character instead of the value. Backspace and Ctrl+U update the mask, and the plaintext is never written to Terminal scrollback.

sshc service manages a systemd user service on Linux, a launchd user agent on macOS, or a logon task on Windows.

SubcommandWhat it does
installRegisters a stable Homebrew, install.sh, or Windows install.ps1 path
restartRestarts an active service whose definition matches this installation
disableRemoves only a definition created by sshc

install, restart, disable, and update show the planned changes and ask for confirmation. Use -y or --yes only when automation must skip the prompt.

sshc service restart refuses inactive, absent, unmanaged, outdated, or other-executable services with exit code 1. Restarting ends existing sessions and transfers. Success requires a verified engine handoff and status response. Linux and macOS also check the service PID; Windows checks that the handoff PID runs the registered executable. See Installation for recovery steps.

sshc vault change-password validates the current password as soon as you press Enter, before asking for a new password. Passwordless Vaults skip the current-password prompt. Leave both the new password and confirmation empty to remove password protection. sshc vault lock keeps a passwordless Vault unlocked; set a master password to enable locking. After upgrading the CLI, restart the engine to apply these changes.

OTP ​

sh
sshc otp list [--json]
sshc otp <name>
sshc otp show <name> --json
sshc otp add <name>
sshc otp edit <name>
sshc otp remove <name> [-y|--yes]

list prints saved names and assignments only. sshc otp <name> and show print the previous, current, and next code plus the remaining lifetime of the current code, which makes a clock boundary visible. The provisioning secret stays inside the engine and is absent from both human and JSON output.

add and edit read a Base32 setup key or otpauth:// URI from an interactive terminal, displaying * instead of the value. An assigned TOTP cannot be removed until its Connections assignments are cleared.

SSH ​

sh
sshc ssh
sshc ssh --list
sshc ssh <alias>
sshc ssh <alias> --non-interactive -- <command...>
sshc info <alias> --json

The Homebrew formula installs completions for bash, zsh, and fish. For other installation methods, add the matching command below to your shell startup file.

  • Completion covers subcommands, options, enumerated values, and connection aliases for sshc ssh, sshc info, sshc terminal create ssh, sshc sftp, and sshc vpn bind/unbind.
  • Alias candidates are read from the current ~/.ssh/config and reachable Include files whenever you press Tab.
  • An alias containing characters sshc refuses to launch or evaluate (shell metacharacters, whitespace, a leading -) is left out of both sshc ssh --list and completion, and the reason is reported on stderr.

Command parsing, per-command help, and bash, zsh, and fish completion are built from the same command definition. Names and choices offered by completion therefore match sshc help from the installed version.

sh
# bash
source <(sshc completion bash)

# zsh
source <(sshc completion zsh)

# fish
sshc completion fish | source

On Linux, the system Bash completion loader may not search Homebrew's completion directory. If only filenames are offered, run source <(sshc completion bash) in the current shell. Add that line to ~/.bashrc to load the current command definitions for each new shell. Reload it after updating sshc if the current shell still offers old commands.

sshc info resolves Include, Match, ProxyJump, encoding and the VPN route through the real connection path without an engine. It does not print saved credentials, SetEnv values or the ProxyCommand body.

sh
sshc ssh bastion --non-interactive -- uname -a

Non-interactive SSH fails when it would need a question, such as an unknown host key, 2FA, or an unsaved password.

Exit status ​

sshc ssh <alias>, and an interactive connection started from the sshc ssh picker, exit with:

  • the remote shell's exit status once connected
  • 2 for invalid arguments
  • 1 when sshc fails before it starts the SSH connection, for example because the engine is unreachable, the vault is not unlocked, the engine refused the request, or the SSH configuration cannot be read
  • 255, like OpenSSH's ssh, when the SSH connection fails (VPN route, name resolution, TCP connection, handshake, or authentication). Pressing Ctrl+C at a question during the connection (host key, password) also returns 255
  • 130 when Ctrl+C is pressed while sshc checks that the engine is running and the vault is unlocked, or in the picker. Once connected, Ctrl+C goes to the remote host
  • 0 when stopped by SIGTERM or SIGHUP (for example, when the terminal window is closed). On Windows, closing the window, signing out, and shutting down count as this
  • 0 when the picker is closed without choosing a host

Serial and Telnet interactive sessions and sshc engine follow the same rule for SIGTERM and SIGHUP. An interactive connection stopped by a signal was stopped by the person closing the terminal or by whatever supervises the process, so it is not reported as a failure.

Runs meant for automation never return 0 when a signal stops them. sshc ssh --non-interactive returns the remote command's exit status, 2 for invalid arguments, and 255, like OpenSSH's ssh, when it could not connect. A signal ends it directly, so the shell sees 130 (Ctrl+C), 143 (SIGTERM), or 129 (SIGHUP). Serial and Telnet non-interactive runs return 130 (see Serial and Telnet).

Sync ​

sh
sshc sync setup
sshc sync --json
sshc sync push [--force] [--json]
sshc sync pull [--force] [--json]
sshc sync now [--json]
sshc sync auto on|off [--json]

sshc sync push and sshc sync pull follow the transfer summary with one line per file, marked added, modified, or removed. When a plain pull stops because of a conflict or a removal, it prints the preview it did not apply in the same form on standard error. With --json, pull returns written, added, removed, and conflicts, and push returns added, modified, and removed as arrays of paths.

sshc sync setup shows the configured endpoint, bucket, path, region, and direction as defaults. Direction accepts both, push, or pull. The Access Key ID is shown as ***** followed by its final five characters; the Secret Access Key and the sync encryption key (the Sync key prompt) are shown only as configured. Press Enter on blank secret prompts to keep the values already held by the engine. While a new hidden value is typed, each character appears as *, and Backspace updates the mask without printing the plaintext.

VPN ​

Route the connections a VPN profile is attached to through a VPN of their own. A profile has no target: each connection reaches its own HostName and Port. Needs Docker on this machine. See Per-connection VPN.

sh
sshc vpn [--json]
sshc vpn add <name>
sshc vpn edit <name>
sshc vpn remove <name> [-y|--yes]
sshc vpn rename <old name> <new name> [--json]
sshc vpn up <name> [--json]
sshc vpn down <name> [--json]
sshc vpn logs <name> [--json]
sshc vpn proxy <name> <host> <port>
sshc vpn bind <alias> <name> [--json]
sshc vpn unbind <alias> [--json]

sshc vpn add creates a new profile. If the name is already taken it changes nothing and suggests editing it with sshc vpn edit, or renaming it with sshc vpn rename.

It asks for the settings in an interactive terminal, including the DNS servers that resolve targets written as names. By type:

TypeWhat it asks for
WireGuardThe path of the configuration file ([Interface] and [Peer]). The DNS servers come from its DNS. In sshc vpn edit a blank path keeps the stored file
OpenConnectHow to answer a device that asks one more question after the password (none, approve or totp)
OpenVPNThe path of the configuration file (.ovpn) and an optional username. The file is checked as it is read, and a directive that cannot be used is named with its line before anything is saved
IKEv2/IPsecHow to authenticate (eap-mschapv2 or psk) and where the CA certificate that verifies the server is kept (blank trusts the public certificate authorities)

How secrets are handled:

  • The private key, the VPN password and the IPsec pre-shared key are read without echo and are never taken from command arguments or environment variables. They are stored in the vault.
  • OpenVPN and WireGuard configuration files hold keys, so the whole file is stored in the vault as a secret.
  • sshc vpn edit never shows a stored secret; one left blank keeps its stored value.

sshc vpn edit changes a saved profile. Every prompt starts from the saved value; a blank secret keeps the saved one, and - clears an optional setting. New secrets are asked for only when the type changes.

sshc vpn also prints the tunnel's interface, its address inside the VPN and when the route opened, for every route that is up.

sshc vpn down disconnects the route. The connections that use it are closed as well, and Terminal does not reconnect them automatically.

sshc vpn remove and sshc vpn rename need an unlocked vault. sshc vpn rename moves the settings, the stored secrets and the connections that use the profile to the new name together. A running route is taken down first, unless the new name is refused.

sshc vpn logs prints what the sshc engine recorded while preparing that profile's route, followed by the recent output of its container, with the stored secrets replaced by [REDACTED]. It is the first place to look when a route will not come up.

sshc vpn proxy pipes standard input and output through the route, so the host's own ssh, scp and git can use it from ~/.ssh/config:

text
Host lab
  HostName 10.9.9.1
  ProxyCommand sshc vpn proxy tundra %h %p

The target is passed as %h %p and is required. When the target cannot be reached it refuses rather than falling back to the ordinary uplink, and says why on standard error. The SSH handshake and the keys stay with ssh; sshc only carries the bytes.

A connection with a profile attached takes the same route from the terminal, from SFTP and from sshc ssh <alias>. When the route is not available the connection is refused rather than quietly sent over the ordinary uplink.

Attaching, detaching or replacing a profile with bind and unbind stops the saved password, one-time password and startup snippet assigned to that connection. Save the connection's Basic settings again under Connections in the Web UI, and assign the startup snippet again under Snippets. Renaming a profile with sshc vpn rename or editing it with sshc vpn edit keeps the assignments working.

SFTP transfers ​

Transfers use the running engine and the same OpenSSH configuration, host-key checks, and Vault credentials as the Web UI. Specify remote paths as absolute POSIX paths such as /var/log/app.log, or start them with ~/ to count from the directory the SFTP server logs you into (usually your home). Quote ~, as in '~/logs/app.log': unquoted, your shell replaces it with the local home directory before sshc sees it. In local paths, a quoted ~ is read as the local home directory.

sh
sshc sftp get bastion /var/log/app.log ./app.log
sshc sftp put bastion ./release.tar.gz /tmp/release.tar.gz
sshc sftp get bastion /srv/data ./data --recursive
sshc sftp put bastion ./public /var/www/public --recursive
sshc sftp get bastion /srv/archive ./archive --recursive --jobs 4
sshc sftp get bastion /srv/archive ./archive --recursive --max-total-size 8192
sshc sftp settings
sshc sftp settings --split-size 73 --split-jobs 6 --chunk-size 41
sshc sftp get bastion /backup/disk.img ./disk.img --split-size 100 --split-jobs 4 --chunk-size 512
sshc sftp put bastion ./disk.img /backup/disk.img --split-size 100 --split-jobs 4 --chunk-size 512

sshc sftp settings displays the split threshold, connections per file, and chunk size shared by Web and CLI. Add --split-size (16–1024 MiB), --split-jobs (1–128), or --chunk-size (8–4096 MiB) to persist only the supplied defaults. --json returns the saved values for automation.

-j or --jobs sets the number of files transferred concurrently from 1 to 8; the default is 1. Regular file uploads and downloads support up to 512 GiB.

Split options on get or put override the saved defaults for that invocation. Initial defaults are 100 MiB, one connection (no splitting), and 32 MiB chunks.

  • --split-jobs 1 disables splitting.
  • To transfer in parallel, give --split-jobs for that run or change the saved default with sshc sftp settings --split-jobs or the Transfer Manager.
  • --split-jobs accepts up to 128, while the actual connection count is limited by the number of unfinished chunks.
  • A host that authenticates with a one-time code transfers over one connection whatever the setting.
  • Concurrent split transfers multiply the total connection count, so choose a value that fits both the server and the device.

put and get behave like this:

  • A transferred file is given the modification time of its source.
  • Symlinks are transferred as what they point to, as WinSCP and scp do, and a link to a directory brings that directory's contents.
  • A link whose target is missing, or an entry that is neither a file nor a directory, is reported on standard error as skip and counted in the result's skipped without stopping the rest.
  • A directory sent with --recursive creates the remote directories it needs; a single-file put stops with remote_directory_missing when the destination directory does not exist.
  • A newly created file gets the server's default permissions (its umask); a replaced file keeps its own.

Recursive downloads default to a safety budget of 64 levels below the selected root, 10,000 files and directories in total, and 1,024 MiB of file data. If a limit is reached, sshc stops before transferring anything. For an intentionally larger tree, raise the per-run limit with --max-depth (up to 256), --max-entries (up to 1,000,000), or --max-total-size (MiB, up to 8 TiB). These options apply only to get --recursive.

In an interactive terminal, sshc sftp get displays one progress bar for each SFTP connection while the engine prepares the remote file. A split transfer therefore shows one line per connection, while a non-split transfer shows one. Only files still transferring are shown, and a file's lines disappear when it finishes. When several files are transferred, the first line counts the finished files. Progress is suppressed for --json and non-interactive output so automation remains clean.

Existing files are never overwritten implicitly. --overwrite shows one confirmation before replacing them; add --yes only when automation must skip that confirmation. Use --skip-existing to preserve existing files or --dry-run to inspect the plan without changing anything. With --json, stdout contains one JSON result while progress remains on stderr. Pressing Ctrl+C also cancels the remote temporary upload.

Serial / Telnet ​

sh
sshc serial
sshc serial /dev/ttyUSB0 --baud 9600
sshc telnet console.example:23

Press Ctrl+] to leave an interactive Serial or Telnet connection. Telnet neither encrypts the connection nor authenticates the server.

Terminal control ​

Inspect, create, and control terminals owned by the running engine.

sh
sshc terminal list --json
sshc terminal create ssh bastion --json
sshc terminal create shell --json
sshc terminal create shell --workspace "sshc dev"
sshc terminal show <session-id> --json
sshc terminal read <session-id> --cursor 0 --limit 4096 --json
sshc terminal send <session-id> --text 'uptime' --json
sshc terminal send <session-id> --text 'partial input' --no-enter
sshc terminal wait <session-id> --for connected --timeout 30s --json
sshc terminal rename <session-id> deploy
sshc terminal rename <session-id> --auto
sshc terminal close <session-id>

create shell opens a local shell on the engine host. send appends Enter (CR) by default; --no-enter sends the text as is.

A session opened with create goes into the hidden Agents workspace by default, so connections opened by LLMs and scripts stay out of the sidebar. --workspace puts it in another workspace, named by ID or name. Until they are renamed, the built-in Main and Agents workspaces go by the names the web UI shows (メイン and エージェント in Japanese) or by their IDs, default and agents. list also shows the workspace ID of each session.

read returns a scrollback cursor and warns when an older position has already been discarded. send checks the current process generation to avoid sending to a replacement process.

A name set with rename is pinned, so titles that programs set no longer change it.

  • --auto unpins it and, like Use automatic name in the app, shows the title the program set, or else the connection alias or shell name. A name and --auto cannot be given together.
  • Put a name that starts with - after --, as in sshc terminal rename <session-id> --json -- -dev. Nothing after -- is read as an option.
  • In the --json result, presentation.titlePinned tells whether the name is pinned.