CommunityCoding & Developmentgithub.com

Carrot404/agent-ssh-cli

Securely operate remote servers via an SSH-based CLI and manage local connection configurations. Supports listing connections, executing remote commands, uploading/downloading files, launching/stopping the visual configuration editor, and confirming parameters, return values, config paths, and environment verification steps.

What is agent-ssh-cli?

This AI agent skill, `agent-ssh-cli`, provides a command-line tool installed via npm with a Rust native executor for SSH operations, designed for AI or users to securely manage remote servers through local configurations. It simplifies tasks like listing SSH connections, executing commands remotely with sudo/su support, uploading and downloading files with resume capability, and managing host key trust via SHA256 fingerprint confirmation. The tool enforces security by requiring user confirmation for dangerous operations (e.g., file deletion, service restarts) and supports command whitelist/blacklist regex. It operates across macOS, Linux, and Windows, and its output can be structured as JSON for easy parsing by AI agents. The skill also includes a visual configuration editor for managing connections and secrets, with automatic caching via a Rust daemon to reduce connection overhead during repeated operations. Notably, it avoids outputting plaintext passwords or sensitive credentials, using encrypted storage instead.

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Carrot404/skills/tree/HEAD/skills/agent-ssh-cli

Installed? Explore more Coding & Development skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

agent-ssh-cli Usage Guide

agentsshcli is a CLI tool installed via npm with a Rust native executor for SSH operations. It allows AI or users to securely operate remote servers through local configurations.

What it can do:

  • List SSH server connections in local configurations
  • Execute commands on specified remote servers
  • Register SSH host public keys into the local known_hosts using SHA256 fingerprints explicitly confirmed by the user
  • Upload local files to remote servers
  • Download files from remote servers to local machine
  • Restrict executable commands via command whitelists and blacklists
  • Cache SSH connections for a short duration via a Rust daemon, reducing repeated connection overhead during consecutive operations
  • Manage connections, credential references, and security policies through a visual editor that only listens on the local loopback address
  • npm installation automatically downloads the appropriate optional precompiled binary for the current system, currently supporting macOS arm64/x64, Linux x64/arm64, and Windows x64

What it does NOT do:

  • Does not output plaintext passwords, private keys, or sensitive authentication information; passwords can be encrypted and saved locally in secrets.json and only sent via SSH channel stdin
  • Does not scan networks or discover servers; it uses only configured connections
  • Does not bypass command restrictions in the configuration

Command whitelist/blacklist uses JavaScript RegExp syntax, not POSIX regular expressions. Whitespace characters must be written as \s, not [:space:]. For example:

{
  "commandBlacklist": [
    "(^|[;&|()\\s])rm(\\s|$)",
    "(^|[;&|()\\s])shutdown(\\s|$)",
    "(^|[;&|()\\s])reboot(\\s|$)"
  ]
}

Security Confirmation

User confirmation must be obtained before performing dangerous operations; they cannot be executed directly.

Dangerous operations include:

  • Deleting, clearing, or overwriting files or directories (e.g., rm, truncate, redirect overwrites, batch deletions)
  • Clearing caches, logs, temporary directories, or business data
  • Rebooting, shutting down, stopping services, or killing processes (e.g., reboot, shutdown, systemctl stop, kill)
  • Modifying permissions, ownership, system configurations, or startup items (e.g., chmod, chown, editing files under /etc)
  • Uploading files that overwrite existing files on the remote server
  • Downloading files that overwrite existing files on the local machine
  • Any irreversible operation affecting online services or data integrity

During confirmation, the target connection name, command or file path, and potential impact must be specified, and execution must wait for the user's explicit consent.

Environment Verification

Before invoking, prioritize checking if the CLI itself is available:

agentsshcli --help

If the above command fails, then check the underlying environment:

node --version
npm --version

If node or npm are not present, prompt the user to install Node.js >= 18 and npm >= 8.

Once the CLI is available, check if the configuration file exists:

test -f "${AGENT_SSH_CONFIG:-$HOME/.agent-ssh-cli/config.json}"

If the configuration file does not exist, prompt the user to create it before proceeding with SSH commands:

mkdir -p ~/.agent-ssh-cli
# Then ask the user to edit ~/.agent-ssh-cli/config.json with actual server configurations

Default configuration file:

~/.agent-ssh-cli/config.json

All SSH connection establishments (including bastion hosts and editor "test connection") will verify ~/.ssh/known_hosts based on the configured hostname and port. On first connection when the host is not registered, inform the user about the target connection name, host and port, the received SHA256 fingerprint, and the risk of connecting to the wrong server or suffering a man-in-the-middle attack upon first trust. Once the user explicitly confirms, use trust-host to register (for non-22 ports, use [host]:port). It should not insist on independent verification from a console or operations record. When the user has explicitly expressed agreement such as "trust" or "direct trust" for the target, consider it as authorization and do not repeatedly ask for confirmation or refuse on grounds of lacking independent verification. Do not automatically trust without user confirmation; when an existing public key has changed, an investigation is still required, and old records should not be automatically overwritten.

It is recommended to use agentsshcli edit-config to replace passwords: when the editor saves, it encrypts the new password into secrets.json, leaving only a passwordRef in the configuration file. Old configurations still support passive migration: if a connection contains a non-empty plaintext password, the next execution of exec, upload, or download will generate secret.key, encrypt and save the password, and replace the plaintext field with a reference. Do not display plaintext passwords in conversations, logs, or command output.

The "Permission Boundary" page of the editor can set and view the sudo/su password: on first input, a unique reference is generated per connection name; after saving, only the ciphertext is written to secrets.json; if no separate sudo password is configured, the field prompts that executing --sudo will reuse the SSH password.

trust-host

Explicit user confirmation must be obtained for first-time registration; independent verification of the fingerprint is recommended but not a prerequisite for registration. The SHA256 fingerprint returned in the CLI's first connection error, or obtained via a probe without sending authentication credentials, can be used. Then confirm and register according to the rules above:

agentsshcli trust-host --fingerprint 'SHA256:<user-confirmed fingerprint>' '<connectionName>'
agentsshcli trust-host --config /path/to/config.json --fingerprint 'SHA256:<user-confirmed fingerprint>' '<connectionName>'
  • --fingerprint is required, accepts only the full SHA256 fingerprint, and must be placed before the connection name; --config is placed before it.
  • The command completes the handshake through the connection's original direct, SOCKS5, or bastion path without authenticating the target machine or executing commands. If the bastion host is not registered, first obtain user confirmation and register it.
  • If the fingerprint does not match or a different public key already exists, it refuses to write and does not overwrite old records. If the same public key is already registered, it does not add a duplicate. The host is written to ~/.ssh/known_hosts only if the provided fingerprint matches the handshake public key and no record exists for that host.
  • Fingerprints returned by ssh-keyscan or the first connection can be the object of user confirmation, but they cannot claim to have independently verified the server's identity. After user confirmation, registration can proceed; do not refuse due to lack of independent verification. On registration failure, check the specific error and do not use parameters that bypass checks.

edit-config / stop-editor

Use the local visual editor to manage SSH connections:

agentsshcli edit-config [--config <path>]
agentsshcli stop-editor [--config <path>]

Invocation rules:

  • When a user requests to open, edit, or visually manage SSH configurations, directly run edit-config; do not call the internal __editor subcommand or assemble a HTTP API manually.
  • The editor only listens on 127.0.0.1 and automatically opens the browser upon startup. Only one instance runs per configuration path; repeated executions will open the existing instance.
  • The local URL fragment output by the CLI contains a temporary token and should be treated as credentials; do not log it, include it in tickets, or external messages. Only provide it in the current local session when the user explicitly needs to access the address manually.
  • After 10 consecutive minutes without an authenticated valid API request or real pointerdown, keydown, or input events, the editor automatically exits and deletes its state file. If the page has expired, re-run edit-config.
  • stop-editor only stops the local editor for that configuration; it does not stop the SSH daemon, cached connections, or remote tasks.
  • Do not execute stop-editor when the user may have unsaved page modifications; only call it when the user explicitly requests to stop or has confirmed they can discard unsaved content.
  • edit-config / stop-editor support --config, --help, and --version. They do not accept --no-cache, --cache-ttl, or positional arguments.
  • The configuration file may be modified by other processes; if a save conflict occurs, reload first and do not bypass hash checks or directly overwrite.
  • The JSON panel supports "Global/Current Connection" and "Preview/Source". Backend validation is the final configuration contract; frontend prompts do not replace save results.
  • The page's "Test Connection" performs one SSH connection establishment and authentication with the current draft (including unsaved password drafts) and the relevant bastion host, then disconnects. It does not save configuration or execute commands. Only one test can run at a time.
  • Viewing a password causes the local backend to decrypt it. The plaintext is displayed briefly on the page and cleared after 15 seconds. Do not extract passwords via terminal, scripts, or HTTP debug interfaces.
  • To replace a password, use the page's replace operation. After saving, config.json retains only the passwordRef, and the ciphertext is written to the secrets.json in the same directory. Cloning a connection does not copy the password reference.
  • The editor does not display connection-level PTY controls but will pass through existing pty. Continue to use exec --pty / --no-pty for temporary overrides when executing commands.

Return behavior:

  • On successful edit-config, stdout outputs Configuration editor opened: <local-url> with exit code 0. If the browser fails to open, the error message includes the local URL for manual access.
  • On successful stop-editor, stdout outputs Configuration editor stopped; if no corresponding service is running, it returns Configuration editor is not running with exit code 1.
  • If there is a save conflict, configuration validation failure, or incomplete credential reference in the editor, do not overwrite the original configuration. Handle based on the specific error in the page or stderr.

Example configuration with hidden password:

{
  "name": "server",
  "host": "192.0.2.10",
  "port": 22,
  "username": "root",
  "password": "",
  "passwordRef": "agentsshcli:server"
}

Complete configuration file fields (each item is a unique name for a server):

  • name: Connection name, must be unique
  • group / tags: Optional single-level group and tags array; the editor supports display by group or tag; tags are trimmed of whitespace and deduplicated
  • host / port / username: SSH host, port (default 22), username
  • password / passwordRef / privateKey: Authentication methods; only one of these three can be retained. passphrase is only used with privateKey.
  • jumpHost: Bastion host connection name, set to the name of another machine in the configuration file. When connecting, first establish an SSH connection to the bastion, then reach the target via a direct channel.
  • socksProxy: SOCKS5 proxy address, e.g., socks5://127.0.0.1:1080; the protocol can be omitted, e.g., 127.0.0.1:1080
  • pty: Whether to allocate a pseudo-terminal by default, exec --pty / --no-pty can temporarily override
  • privilegeEnabled: Whether exec --sudo/--su is allowed, default false
  • sudoUser: sudo target user, default root; sudoPassword/sudoPasswordRef saves the sudo password for the current SSH user; if missing, the SSH password can be reused
  • suUser: su target user, default root; suPassword/suPasswordRef must save the target user's password
  • commandWhitelist / commandBlacklist: Command allow/deny regex arrays

Bastion host example:

[
  {
    "name": "Target Machine",
    "host": "10.0.0.5",
    "username": "root",
    "passwordRef": "agentsshcli:Target Machine",
    "jumpHost": "Bastion"
  },
  {
    "name": "Bastion",
    "host": "203.0.113.10",
    "port": 22,
    "username": "ubuntu",
    "privateKey": "/path/to/jump_key"
  }
]

Using a different configuration file:

AGENT_SSH_CONFIG=/path/to/config.json agentsshcli list

If the CLI is unavailable but Node/npm is working, prompt the user to install:

npm install -g agent-ssh-cli
agentsshcli --help

For development from source or local debugging, first build the Rust native executor:

npm run build:native
npm test

Global Parameters

  • --config <path>: Specify the configuration file path, takes priority over the default
  • --help, -h: Output help
  • --version, -v: Output version

exec, upload, and download use the Rust daemon connection cache by default to reduce the overhead of repeated SSH handshakes and authentication during consecutive operations. Only when --no-cache is passed does it skip the cache and connect directly. Cache-related parameters:

  • --no-cache: Skip the Rust daemon connection cache; this command will independently establish and close the connection (direct mode)
  • --cache-ttl <ms>: Set the idle time in milliseconds for the Rust daemon connection cache, default 180000
  • --json: exec, upload, download output structured JSON (fields exitCode/stdout/stderr), suitable for scripts and AI parsing

All parameters (--no-cache, --cache-ttl, --json, --timeout, --pty, etc.) must be placed before the connection name (the first positional argument). They can be mixed in any order. If placed after the connection name, they will be treated as part of the command content and result in an "unsupported parameter" error. Commands should be wrapped in quotes.

init-config

Generate a default configuration file at ~/.agent-ssh-cli/config.json (does not overwrite if it already exists):

agentsshcli init-config

list

List servers in the configuration.

agentsshcli list
agentsshcli list --json
agentsshcli list --group Production --tag app

Parameters:

  • --json: Output JSON format. The current default output is also JSON.
  • --config <path>: Specify the configuration file
  • --group <group> / --tag <tag>: Case-sensitive exact filter; when used together, it returns the intersection

Return values:

  • On success, stdout outputs a server array containing name, host, port, username, group, tags; if a bastion host is configured, also includes jumpHost. If no matches are found, outputs []
  • Does not output passwords, private keys, passphrases, whitelists/blacklists, or other sensitive or control fields
  • Exit code is 0

Example output:

[
  {
    "name": "Server",
    "group": null,
    "tags": [],
    "host": "192.0.2.10",
    "port": 22,
    "username": "root"
  }
]

exec

Execute commands on the remote server.

Positional argument forms:

agentsshcli exec "<connectionName>" "<command>"
agentsshcli exec --no-cache "<connectionName>" "<command>"
agentsshcli exec --cache-ttl 60000 "<connectionName>" "<command>"
agentsshcli exec --pty "<connectionName>" "<command>"
agentsshcli exec --no-pty "<connectionName>" "<command>"
agentsshcli exec --sudo "<connectionName>" "systemctl status app"
agentsshcli exec --su "<connectionName>" "id && pwd"

sudo/su Privilege Escalation Rules

When a user requests to use sudo, su, root identity, or switch target users via an existing non-root connection, the CLI top-level parameters --sudo or --su must be used. Do not use interactive remote commands directly.

  • --sudo/--su must be placed before the connection name or --connection.
  • Do not concatenate sudo, sudo -S, sudo -n, su, or su -c into <command>; doing so bypasses the CLI's privilege escalation password channel and may wait for interactive input or incorrectly indicate that the tool does not support passwords.
  • Do not conclude that agentsshcli cannot handle password prompts just because a regular su -c command times out. agentsshcli 0.5.0+ automatically sends the configured encrypted credentials via the SSH channel stdin.
  • When --su --json succeeds, stderr may still contain the remote Password: prompt text. This prompt does not contain actual passwords and does not indicate authentication failure. The result must be determined by combining the JSON exitCode and stdout with commands like whoami/id -u.
  • Before use, ensure the connection configuration has privilegeEnabled: true and the target user is configured. su always requires a separate password; sudo can use the remote NOPASSWD policy if no password is available.

Correct usage:

agentsshcli exec --json --sudo "<connectionName>" "whoami; id -u"
agentsshcli exec --json --su "<connectionName>" "whoami; id -u"
agentsshcli exec --no-cache --json --su "<connectionName>" "id"

Incorrect usage:

agentsshcli exec "<connectionName>" "su -c 'id'"
agentsshcli exec "<connectionName>" "sudo -S id"
agentsshcli exec "<connectionName>" "sudo -n id"

Named argument forms:

agentsshcli exec --connection "<connectionName>" --command "<command>" --directory "/root" --timeout 5000
agentsshcli exec --connection "<connectionName>" --command-file "./script.sh" --timeout 5000
agentsshcli exec --no-cache --connection "<connectionName>" --command "<command>"

Parameters:

  • <connectionName>: Connection name
  • <command>: Remote command
  • --connection <name>, -c <name>: Connection name
  • --command <command>: Remote command
  • --command-file <path>: Read content from a local UTF-8 file to execute as a remote command (not uploaded first and then executed). Suitable for multi-line scripts. The file must use LF line endings, not Windows CRLF. Cannot be used simultaneously with --command or the positional <command>.
  • --directory <dir>, -d <dir>: Remote working directory
  • --timeout <ms>, -t <ms>: Timeout in milliseconds, default 30000
  • --pty: Allocate a pseudo-terminal for this command, takes priority over the configuration file
  • --no-pty: Do not allocate a pseudo-terminal for this command, takes priority over the configuration file
  • --sudo: Execute with sudo privilege escalation, requires privilegeEnabled: true in the connection configuration
  • --su: Execute with su user switch, requires privilegeEnabled: true in the connection configuration. Mutually exclusive with --sudo.
  • --json: Output structured JSON (exitCode/stdout/stderr)
  • --no-cache: Do not reuse the connection; must be placed before the connection name or --connection
  • --cache-ttl <ms>: Idle time in milliseconds for the connection cache; must be placed before the connection name or --connection

Privilege escalation modes only support non-interactive commands: the CLI sends the password via SSH channel stdin and then closes stdin. The target command cannot continue reading input. If sudo's independent credentials are missing, the SSH password can be reused. If no password is available, non-interactive sudo -n is used, which supports remote NOPASSWD or valid authentication cache; it will fail immediately if a password is required. When using private key authentication and the remote server requires a password, sudoPassword/sudoPasswordRef must be configured. su always requires an independent target user password.

When using --command-file, ensure the script file uses LF line endings. A CRLF file will transmit \r to the remote bash, potentially causing errors like $'xxx\r': command not found.

Recommended approach for macOS/Linux:

cat > /tmp/remote-command.sh <<'EOF'
pwd
EOF
agentsshcli exec --connection "<connectionName>" --command-file /tmp/remote-command.sh

Recommended approach for Windows PowerShell (explicit LF):

[System.IO.File]::WriteAllText("$env:TEMP\remote-command.sh", "pwd`n", [System.Text.UTF8Encoding]::new($false))
agentsshcli exec --connection "<connectionName>" --command-file "$env:TEMP\remote-command.sh"

Return values:

  • On success with stdout, stdout outputs the remote command result
  • On success without stdout, no content is output
  • Exit code is 0
  • If the remote command exits with non-zero, times out, hits a blacklist entry, misses a whitelist entry, or fails to connect, stderr outputs an error message and exit code is 1
  • In --json mode, output is JSON. exitCode is the actual exit code of the remote command (non-zero indicates command failure); stdout/stderr faithfully return the remote output. The process exit code remains 1 when the command fails.

Important notes:

  • When using pkill -f / pgrep -f in commands, the match string may match the remote shell executing the command itself (the command line contains the string), causing the shell to be killed, the command to be interrupted, and no output. Use regex tricks to exclude the process itself, e.g., pkill -f 'name[.]log'.
  • If the remote session is abnormally terminated (e.g., shell killed), a message [remote] Session terminated abnormally (no exit status) is reported; it does not silently return success.
  • If a command starts a background process at the end without redirecting stdout, the remote side will not close the channel, and the command will wait until the total timeout. For background operations, redirect stdout/stderr to a file (e.g., > /tmp/x.log 2>&1 < /dev/null &).
  • Wrap commands in quotes (e.g., --command "<command>" or positional "<command>"): tokens like --json, --timeout within the command content will be parsed as CLI arguments if not quoted, potentially being swallowed or causing errors.

upload

Upload local files to the remote server.

Positional argument forms:

agentsshcli upload "<connectionName>" "<localPath>" "<remotePath>"
agentsshcli upload --no-cache "<connectionName>" "<localPath>" "<remotePath>"

Named argument forms:

agentsshcli upload --connection "<connectionName>" --local "./tmp/upload.txt" --remote "/usr/local/test/upload.txt"
agentsshcli upload --no-cache --connection "<connectionName>" --local "./tmp/upload.txt" --remote "/usr/local/test/upload.txt"

Parameters:

  • <connectionName>: Connection name
  • <localPath>: Local file path
  • <remotePath>: Remote target file path
  • --connection <name>, -c <name>: Connection name
  • --local <path>, -l <path>: Local file path
  • --remote <path>, -r <path>: Remote target file path
  • --timeout <ms>: Total timeout in milliseconds, no limit by default (allows long-running large files), different from exec's default 30s
  • --recursive: Recursively upload directories, preserving relative paths; symbolic links are not followed – those pointing to directories are skipped (to prevent loops), those pointing to files upload their content
  • --json: Output structured JSON
  • --no-cache: Do not reuse the connection; must be placed before the connection name or --connection
  • --cache-ttl <ms>: Idle time in milliseconds for the connection cache; must be placed before the connection name or --connection

Upload stability:

  • Uploads first write to a temporary file at <remotePath>.part, and after validation, rename to the final target file.
  • A metadata file <remotePath>.part.meta is also written for resumption. If the local file size, modification time, or chunk size changes, the old temporary file is deleted and the file is re-uploaded to avoid incorrect concatenation.
  • If an upload is interrupted, the next upload of the same local file to the same remote path resumes from the existing .part size.
  • --no-cache mode can stop the current upload with Ctrl+C. Daemon mode can use stop-daemon to abruptly stop the connection pool, but this affects other tasks within the same daemon and is not a precise cancellation of a single upload.

Return values:

  • On success, stdout outputs File uploaded successfully
  • Exit code is 0
  • Single file upload does not create remote directories. If the target directory does not exist (or a file exists at the same path), it directly reports Remote directory does not exist or is not accessible: <dir>. The directory must be created first, or use --recursive to upload an entire directory (recursive mode automatically creates directories).
  • If the local file does not exist, remote write fails, or connection fails, stderr outputs an error message and exit code is 1
  • In --json mode, success outputs {"exitCode":0,"stdout":"File uploaded successfully","stderr":""}; failure outputs with exitCode as 1

download

Download remote files to the local machine.

Positional argument forms:

agentsshcli download "<connectionName>" "<remotePath>" "<localPath>"
agentsshcli download --no-cache "<connectionName>" "<remotePath>" "<localPath>"

Named argument forms:

agentsshcli download --connection "<connectionName>" --remote "/usr/local/test/upload.txt" --local "./tmp/download.txt"
agentsshcli download --no-cache --connection "<connectionName>" --remote "/usr/local/test/upload.txt" --local "./tmp/download.txt"
  • <connectionName>: Connection name
  • <remotePath>: Remote file path
  • <localPath>: Local target file path
  • --connection <name>, -c <name>: Connection name
  • --remote <path>, -r <path>: Remote file path
  • --local <path>, -l <path>: Local target file path
  • --timeout <ms>: Total timeout in milliseconds, no limit by default (allows long-running large files), different from exec's default 30s
  • --recursive: Recursively download directories, preserving relative paths; remote symbolic links are skipped
  • --json: Output structured JSON
  • --no-cache: Do not reuse the connection; must be placed before the connection name or --connection
  • --cache-ttl <ms>: Idle time in milliseconds for the connection cache; must be placed before the connection name or --connection

Download stability:

  • Downloads first write to a local temporary file at <localPath>.part, and write metadata to <localPath>.part.meta for resumption. After completion, verify size and rename to the final target file.
  • If interrupted, the next download of the same remote file to the same local path resumes from the existing .part size. If the remote file characteristics change, the old .part is automatically deleted and the download restarts.

Return values:

  • On success, stdout outputs File downloaded successfully
  • Exit code is 0
  • If local write fails, remote read fails, or connection fails, stderr outputs an error message and exit code is 1
  • In --json mode, success outputs {"exitCode":0,"stdout":"File downloaded successfully","stderr":""}; failure outputs with exitCode as 1

stop-daemon

Stop the SSH cache process (connection pool) corresponding to the current configuration file. This is a connection pool maintenance command, not a precise cancellation of a single upload task, and affects other tasks within the same daemon:

agentsshcli stop-daemon [--config <path>]

help/version

agentsshcli --help
agentsshcli help list
agentsshcli help exec
agentsshcli help upload
agentsshcli help download
agentsshcli help edit-config
agentsshcli help stop-editor
agentsshcli --version

Return values:

  • On successful help, stdout outputs help text with exit code 0
  • On successful version, stdout outputs the version number with exit code 0

Error Rules

...

[truncated for translation]

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Related Skills