| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
$$$$$$\ $$$$$$\ $$\ $$\ $$\ $$\ $$ __$$\ $$ __$$\ $$ | $$ |$$ | $$ | $$ / \__|$$ / \__|$$ | $$ |\$$\ $$ | \$$$$$$\ \$$$$$$\ $$$$$$$$ | \$$$$ / \____$$\ \____$$\ $$ __$$ | $$ $$< $$\ $$ |$$\ $$ |$$ | $$ |$$ /\$$\ \$$$$$$ |\$$$$$$ |$$ | $$ |$$ / $$ | \______/ \______/ \__| \__|\__| \__| Agent-Native Remote Execution over SSH
SSH is the channel. X is execution.
sshx is an agent-native remote host execution tool. It uses SSH/SFTP to reach existing hosts and brings target resolution, execution preview, safety checks, command and file actions, structured results, and audit evidence into one CLI invocation.
Agents do not need another interactive SSH shell. They need a stable, composable remote execution contract with explicit side effects. sshx reduces argument assembly through named hosts, removes text guessing through JSON, exit codes, and error kinds, and lowers operational risk through dry-run plans, safety guardrails, the OS keyring or explicit local vault, host-key verification, and local auditing.
It remains a single binary with one-shot invocations and no resident component on remote hosts: efficient, secure, and auditable remote execution for agents over SSH. Human operators use the same command, preview, and audit semantics for supervision and troubleshooting.
If you have Go 1.21+ installed, you can use Go's built-in tools:
# Run the latest version
go run github.com/talkincode/sshx/cmd/sshx@latest --help
# Run specific version
go run github.com/talkincode/sshx/cmd/sshx@v0.0.6 -h=192.168.1.100 "uptime"# Install latest version to $GOPATH/bin
go install github.com/talkincode/sshx/cmd/sshx@latest
# Then use it anywhere
sshx --help
sshx -h=192.168.1.100 "uptime"
# Install the matching Agent skill from the binary
sshx skill installNote: Make sure $GOPATH/bin (typically ~/go/bin) is in your PATH.
brew install talkincode/tap/sshx
sshx skill installThis pulls prebuilt binaries from the talkincode/homebrew-tap repository, updated automatically on every tagged release. The binary embeds the matching Agent skill; the second command installs it to ~/.agents/skills/sshx/SKILL.md without another download.
curl -fsSL https://raw.githubusercontent.com/talkincode/sshx/main/install.sh | bashThe installer verifies the release checksum, installs the binary, and invokes sshx skill install --force to install the matching embedded Agent skill at ~/.agents/skills/sshx/SKILL.md.
Or download and run:
wget https://raw.githubusercontent.com/talkincode/sshx/main/install.sh
chmod +x install.sh
./install.shInstall specific version:
./install.sh v0.0.2Open PowerShell as Administrator and run:
irm https://raw.githubusercontent.com/talkincode/sshx/main/install.ps1 | iexOr download and run:
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/talkincode/sshx/main/install.ps1" -OutFile "install.ps1"
.\install.ps1Install specific version:
.\install.ps1 -Version v0.0.2Download pre-built binaries from Releases:
Linux / macOS:
# Download and extract (replace <platform>-<arch> with your system)
tar -xzf sshx-<platform>-<arch>.tar.gz
# Move to system path
sudo mv sshx /usr/local/bin/
# Make executable
sudo chmod +x /usr/local/bin/sshx
# Verify installation
sshx --helpWindows:
# Clone repository
git clone https://github.com/talkincode/sshx.git
cd sshx
# Build command-line tool
go build -o bin/sshx ./cmd/sshx
# Print the version (also exposed via the binary's --version flag)
make version
# Install to system (optional)
# Installs the binary to ~/.local/bin and the agent skill to ~/.agents/skills/sshx
make install
# Check the installed version
sshx --version# Execute remote command
sshx -h=192.168.1.100 -u=root "uptime"
# Save password for easier access (interactive input)
sshx --password-set=root
# Or set password for specific host
sshx --password-set=192.168.1.100-root
# Use the saved password for sudo auto-fill
sshx -h=192.168.1.100 -u=root "sudo df -h"
# Transfer a file directly from one server to another (streamed, no local copy)
sshx --transfer=192.168.1.100:/var/log/app.log --to=192.168.1.101:/backup/app.logsshx is designed to be driven by scripts and AI agents, not just humans. The command-execution path gives you a stable, machine-readable contract.
By default:
| Code | Meaning |
|---|---|
| 0 | Command succeeded |
| 1..254 | Remote command's exit status, propagated verbatim |
| 255 | sshx-level failure (connect / auth / host-key / timeout / blocked) |
A policy block reaches stderr as well as stdout, so a caller that only prints the streams sees the reason instead of a silent refusal:
$ sshx -h=prod-db --json "docker exec pg psql -U app -d app -c 'select 1'"
sshx: blocked by safety policy (phase=admission, error_kind=blocked, executed=false, exit_code=-1); no remote command ran
sshx: block reason: ⚠️ Dangerous command blocked | Command: docker exec pg psql … | Reason: Direct PostgreSQL client execution ("psql") bypasses the guarded SQL pipeline. Use: sshx sql -h=<host> --db=<name> [--docker=<container>] "<SQL>" (adds classification, backups, and audit) | If you are sure, use --force or -f flag
The block predicate is machine-readable, and all four fields appear together: exit_code=-1, error_kind=blocked, phase=admission, executed=false. A blocked command never reaches the network. Use the guarding path instead: sshx sql -h=<host> --db=<name> [--docker=<container>] "<SQL>". stdout still carries exactly one JSON document; the mirror lines are additional, not a replacement.
Add --json to get a single JSON object on stdout (diagnostics still go to stderr, so stdout stays pure). Human notices such as deprecation warnings and narration never touch stdout, and --quiet suppresses them on stderr, so a caller that merges the streams (2>&1) still reads one parseable document:
sshx -h=prod-web --json "systemctl is-active nginx"{
"host": "192.168.1.100",
"port": "22",
"user": "root",
"command": "systemctl is-active nginx",
"exit_code": 0,
"success": true,
"stdout": "active\n",
"stderr": "",
"duration_ms": 142,
"auth_method": "key"
}On an sshx-level failure the object has exit_code: -1 and a non-empty error_kind (one of timeout, auth, host_key, connect, blocked, exit_missing, config, error), so it is always distinguishable from a remote command that happens to exit 255.
Prefer sshx run for strict aliases, complex scripts, and bounded multi-host execution:
sshx run --target=prod-web --json -- "systemctl is-active nginx"
sshx run --group=prod-web --tag=env=prod --concurrency=4 --jsonl -- "uptime"
sshx run --target=prod-web --script-file=./check.sh --jsonMulti-target exit codes: 0 all succeeded, 1 partial failure/skip/uncertain, 255 request-level failure (invalid selectors, zero matches, bad input).
Add --dry-run to see how sshx would interpret an invocation before it opens an SSH connection, executes a command, performs an SFTP operation, reads keyring secrets, updates known_hosts, or writes settings. Combine it with --json for agent-readable output:
sshx -h=prod-web --dry-run --json "sudo systemctl restart nginx"Dry-run is a local plan preview. It reports host resolution, mode/action, sudo-key selection, safety-check result, and whether a real run would connect, execute, read a secret, or mutate state. It does not prove the remote command would succeed.
Remote previews add a nested sshx.plan.v1 plan, plan_hash, and scalar risk (read|mutation|privileged|destructive). After reviewing a bindable preview, repeat the same invocation with --expect-plan="$reviewed_hash". The hash must be sha256: plus 64 lowercase hex characters. This works for command/run, apply, SQL, SFTP, transfer and inspect; mismatch fails before secrets or network work, even with --force.
Binding needs explicit IP targets, usable public-key sidecars when key auth is enabled, and strict available host trust. DNS-only targets, missing pins, relaxed trust and remotely discovered SQL identities cannot be bound. The entire sorted trust-record snapshot is hashed, so unrelated known_hosts edits conservatively invalidate a plan. A plan does not freeze remote state.
Results add execution_id, parent_execution_id, execution_fingerprint, effects, change_state, nullable executed, verified, verification, and condition arrays. Unknown commands/scripts default to mutation with unknown effects; caller intent=read is not proof. Success is not verification, and unknown change state is not “unchanged.” Inspect partial/unknown outcomes before retrying. Raw output and secret values are not fingerprinted. See Plans, Outcomes, and Safe Retries.
Every non-dry-run invocation writes one JSONL audit event by default:
~/.sshx/audit/sshx-YYYY-MM-DD.jsonl
Use --audit-output=<dir> to place audit events next to a project, runbook, or incident record:
sshx -h=prod-web --audit-output=./.sshx-audit "systemctl reload nginx"Audit events record metadata and outcomes such as mode/action, host resolution, sudo/keyring decisions, safety status, auth method, exit code, and error kind. They do not record plaintext passwords, private key contents, or stdout/stderr. Command text is included for provenance but redacted for common password/token-style arguments. Use --no-audit or SSHX_NO_AUDIT=true to disable audit writing for a single invocation or environment.
Use sshx audit query --execution-id=<id> --json to correlate an execution. Corrupt-line diagnostics distinguish damaged records from an empty query. Audit persistence is best-effort and separate from execution success: a logging failure is not a reason to repeat a successful mutation.
# Limit the command wait to 30 seconds (also accepts 2m, etc.)
sshx -h=prod-web --timeout=30s "apt-get update"
# Opt back into a PTY for commands that insist on a terminal
# (note: a PTY merges stderr into stdout; it cannot be combined with --json)
sshx -h=prod-web --pty "top -b -n1"The timeout can also be set via the SSH_TIMEOUT environment variable. Optional --host-timeout covers an admitted target; --global-timeout also covers queue time. Existing --timeout semantics and defaults are unchanged. For fan-out, --fail-fast (alias of --failure-mode=fail_fast) and --max-failures=N stop new admission only; active targets finish and may add failures. Cancellation/deadlines can stop active local transports, but do not guarantee remote process termination or rollback.
MCP-capable agents can consume the same execution contract as native tools:
sshx mcp{
"mcpServers": {
"sshx": { "command": "sshx", "args": ["mcp"] }
}
}The server speaks MCP over stdio only, is spawned and owned by the client, and re-enters sshx as a one-shot child process per tool call — identical safety gates, keyring roles, and audit trail (events carry entry: "mcp"). Exposed tools: sshx_run, sshx_sql, sshx_apply, sshx_inspect, sshx_sftp, sshx_transfer, sshx_host_list. Password management is deliberately not exposed over MCP. See docs/mcp.md.
Use sshx sql instead of sending raw psql or sqlite3 commands through sshx run. It accepts exactly one statement, classifies it locally, blocks unbounded or unsupported forms, backs up affected data, and records a structured audit event. Direct psql/pgcli/sqlite3 invocations in run/command mode are blocked. The statement may be a positional argument, everything after --, a local file (--statement-file=PATH), or piped stdin; a statement that opens with a SQL comment is statement text, not an option. Reading stdin waits for EOF, so close stdin (or use --statement-file) when another process holds the pipe open.
For PostgreSQL, sshx runs EXPLAIN (FORMAT JSON) before DML. Psql backslash commands, data-modifying CTE bodies, EXPLAIN ANALYZE, SELECT INTO, CALL, and dblink delegated execution are blocked. Accepted reads run in a PostgreSQL read-only transaction to prevent writes through the current connection.
# Read-only query
sshx sql -h=prod-db --db=app --json "SELECT count(*) FROM users"
# Preview classification, gates, and backup plan without connecting
sshx sql -h=prod-db --db=app --dry-run --json \
"UPDATE users SET active=false WHERE id=42"
# Execute with a keyring-backed database password
sshx sql -h=prod-db --db=app --db-user=app \
--db-password-key=app-db --json \
"UPDATE users SET active=false WHERE id=42"
# Hand over a .sql file, or pipe the statement in
sshx sql -h=prod-db --db=app --json --statement-file=./query.sql
printf '%s' 'SELECT count(*) FROM users' | sshx sql -h=prod-db --db=app --jsonUPDATE/DELETE without a top-level WHERE requires --allow-full-table. Destructive DDL requires --force --no-backup; sshx does not claim an automatic restorable backup for schema destruction. Skipping a DML backup also requires both --no-backup and --force. Small changes receive a row CSV snapshot; complex or large changes receive a full-table CSV snapshot under ~/.sshx/sql-backups/. Backup and mutation run in one PostgreSQL transaction while holding a target-table write lock, closing the concurrency window between them. Catalog preflight blocks automatic execution when triggers, rewrite rules, partitions, or cascading referential actions can affect related tables; proceed only after an independent backup with --force --no-backup. UPSERTs are treated as overwrites and receive a table backup. Backups are created with owner-only permissions. Audit records and JSON results replace literal values with a redacted statement while retaining the exact statement's SHA-256 digest.
For PostgreSQL running in a production container, execute the database clients inside the container and resolve credentials from its environment:
# --docker alone reads the container environment for the role and database,
# so images whose POSTGRES_USER is not "postgres" work without --db-user.
sshx sql -h=prod --docker=pg-prod --json "SELECT count(*) FROM orders"
sshx sql -h=prod --docker=pg-prod \
--db-cred-from=docker:pg-prod --json \
"UPDATE users SET active=false WHERE id=42"
sshx sql -h=prod --docker=pg-prod \
--db-cred-from=env-file:/opt/app/.env \
--cred-cache=1h --json "SELECT count(*) FROM orders"Remotely resolved credentials are cached for 15 minutes by default. Secret values live only in the secret backend; local metadata records identity and expiry.
SQLite files live on the application host. Pass an absolute path; there is no database role or password:
sshx sql -h=app --engine=sqlite --db-file=/var/lib/app/app.db --json \
"SELECT count(*) FROM users"
sshx sql -h=app --engine=sqlite --db-file=/var/lib/app/app.db --json \
"UPDATE users SET active=0 WHERE id=42"SQLite reads open file:<path>?mode=ro. Bounded DML snapshots the table to CSV; overwrites and unbounded changes take a whole-file sqlite3 .backup under BEGIN IMMEDIATE. ATTACH, sqlite3 dot-commands, load_extension, and writable PRAGMA are blocked. Use --cred-cache=off to disable caching or --cred-refresh to discard and resolve the current value again.
sshx ros provides native support for MikroTik RouterOS devices strictly over the SSH protocol (no proprietary API or REST ports needed). It implements the agent-friendly contracts, introspection schemas, safety guardrails, and file workflows referenced from roswire.
# Introspection & Self-description (runs locally without network connection)
sshx ros commands --json
sshx ros help ip address add --json
sshx ros schema ip address add --json
sshx ros doctor --json
# Read-only inspection
sshx ros -h=router interface print --json
sshx ros -h=router ip address print --json
sshx ros -h=router system resource print --json
# Safe mutations & raw commands
sshx ros -h=router ip address add address=192.168.88.2/24 interface=ether1
sshx ros -h=router raw "/system/resource/print"
sshx ros -h=router raw "/ip/dns/set servers=1.1.1.1,8.8.8.8" --allow-write
# Preview with dry-run
sshx ros -h=router ip address add address=10.0.0.1/24 interface=ether2 --dry-run --json
# File, script, and backup workflows over SFTP
sshx ros -h=router file upload ./setup.rsc flash/setup.rsc
sshx ros -h=router file download flash/setup.rsc ./setup.rsc
sshx ros -h=router import ./setup.rsc --cleanup
sshx ros -h=router export download ./config.rsc --compact --cleanup
sshx ros -h=router backup download ./backup.backup --name=pre-change --cleanup
sshx ros -h=router script put bootstrap --source=@./setup.rscDestructive commands (reset-configuration, reboot, shutdown, disk format) are guarded and require --force.
Use one structured inspection instead of repeatedly probing an unfamiliar host:
sshx inspect -h=prod-web system.baseline --jsonStable system/network capabilities are built in. Docker, Nginx, and private application collectors are plugins stored under ~/.sshx/plugins/—never in an Agent skill and never installed persistently on the target.
sshx plugin create docker.environment \
--template=docker \
--privilege=optional \
--json
sshx plugin validate docker.environment --json
sshx plugin test docker.environment --fixture=complete --json
sshx plugin trust docker.environment --json
sshx inspect -h=prod-web docker.environment --jsonAn existing plugin directory is provisioned through the CLI instead of by hand: sshx plugin install <dir> stages the source with sshx's own modes, validates it through the same loader the executor uses, publishes it only when it is valid, and --trust records the digest in the same step (--replace keeps the previous plugin as a backup). sshx plugin list groups built-in capabilities and local plugins and always names the local plugin root, so "none installed" is visible, and a missing plugin names the directory that was searched.
New and edited plugins are untrusted until their current manifest/collector/schema digest is explicitly trusted. inspect checks that trust before opening SSH, streams the collector through stdin for that session only, validates one JSON result, and applies field redaction. Plugin trust is not a sandbox; review custom collectors before trusting them.
Remote observation reuse is opt-in:
sshx inspect -h=prod-web docker.environment \
--cache=remote-prefer \
--max-age=10m \
--jsonOnly normalized, redacted JSON is saved below the remote user's ~/.sshx/observations/v1/. Cache reuse is bound to plugin digest, host-key fingerprint, platform, boot ID, privilege, parameters, and TTL. Use --refresh to collect again or --allow-stale to explicitly accept a matching expired observation.
The local runtime root defaults to ~/.sshx; set SSHX_HOME to isolate settings, audit, plugins, and trust state for an Agent or CI run. See Inspection Capabilities and Local Plugins for the manifest, lifecycle, cache, and security contracts.
NEW! Manage your frequently used hosts in ~/.sshx/settings.json for quick access.
# Add hosts interactively
sshx --host-add
# Add host with command line options
sshx --host-add --host-name=prod-web -h=192.168.1.100 -u=root --host-desc="Production Web Server"
# Add a host that uses its own SSH private key
sshx --host-add --host-name=prod-db -h=192.168.1.200 -u=admin -i=~/.ssh/prod-db.pem
# List all configured hosts
sshx --host-list
# Test connection to a configured host
sshx --host-test=prod-web
# Use configured host (auto-resolves from settings)
sshx -h=prod-web "systemctl status nginx"
# Test every configured host and show auth methods
sshx --host-test-allLocation: ~/.sshx/settings.json
{
"key": "/Users/username/.ssh/id_rsa",
"hosts": [
{
"name": "prod-web",
"description": "Production Web Server",
"host": "192.168.1.100",
"port": "22",
"user": "root",
"password_key": "prod-web-password",
"type": "linux"
},
{
"name": "prod-db",
"description": "Production Database",
"host": "192.168.1.200",
"port": "22",
"user": "admin",
"key": "/Users/username/.ssh/prod-db.pem",
"type": "linux"
}
]
}The top-level key is the default SSH private key for all hosts. A per-host key overrides the default for that host only.
Benefits:
sshx stores secrets in the operating system's native credential manager by default. On headless servers without Secret Service / Keychain, set SSHX_SECRET_BACKEND=local-vault to use an encrypted local vault instead. The vault is write-only: Agents confirm keys with --password-check and never read values; sshx injects them over stdin during execution.
# Save default sudo password (interactive input, recommended)
sshx --password-set=master
# Save password for specific user
sshx --password-set=root
# Save password for specific host+user combination
sshx --password-set=192.168.1.100-root
# Set password inline (not recommended, insecure)
sshx --password-set=master:yourpasswordYou will be prompted to enter the password securely (input is hidden).
# Check if password exists
sshx --password-check=master
sshx --password-check=root
# Output example:
# ✓ Password exists for key: master# List common password keys
sshx --password-list
# Output example:
# Checking password keys in system keyring...
# Service: sshx
#
# Common keys:
# ✓ master (exists)
# ✓ root (exists)
# sudo (not set)# Read a stored password. On a terminal sshx only confirms the key exists; to
# obtain the value, pipe stdout — it is emitted raw, with no decoration.
PW=$(sshx --password-get=master) # capture into a variable
sshx --password-get=master | pbcopy # copy to clipboard (macOS)
# Interactive output example (the secret is NOT printed to the terminal):
# ✓ Password exists for key 'master' (service: sshx)
# Not printing the secret to a terminal. To use it, pipe stdout:
# sshx --password-get=master | pbcopy
# sshx --password-get=master | cat--password-get is refused when SSHX_SECRET_BACKEND=local-vault. Use --password-check and let sshx inject the secret.
export SSHX_SECRET_BACKEND=local-vault
export SSHX_VAULT_PASSPHRASE='a long passphrase'
# or: export SSHX_VAULT_KEY_FILE=/etc/sshx/vault.key # must be 0600
sshx --password-set=prod-web # prompt or stdin; value is never printed
sshx --password-check=prod-web
sshx -h=prod-web -pk=prod-web "sudo systemctl status nginx"There is no silent fallback: if the keyring is missing, sshx fails unless you explicitly select local-vault. Dry-run and audit report secret_backend and secret_unlock without secret values.
# Delete password
sshx --password-delete=master
sshx --password-delete=root
# Confirmation message:
# ✓ Password deleted from system keyring
# Service: sshx
# Key: masterOnce a password is saved, commands that start with sudo will automatically retrieve the password from system keyring:
# 1. First save sudo password
sshx --password-set=master
# 2. Execute sudo commands (automatically uses stored password)
sshx -h=192.168.1.100 -u=root "sudo systemctl status nginx"
sshx -h=192.168.1.100 -u=root "sudo reboot"
# 3. Multi-server scenario: save different passwords for different servers
sshx --password-set=server-A
sshx --password-set=server-B
sshx --password-set=server-C
# 4. Use -pk parameter to specify sudo password key temporarily
sshx -h=192.168.1.100 -pk=server-A "sudo systemctl restart nginx"
sshx -h=192.168.1.101 -pk=server-B "sudo systemctl restart nginx"
sshx -h=192.168.1.102 -pk=server-C "sudo systemctl restart nginx"Auto-fill only rewrites the first token. sshx detects a password prompt and feeds the stored password only when sudo is the command's leading token. A sudo that appears later runs without a password:
# NOT filled — sudo is not the leading token; the remote fails with
# "sudo: a password is required"
sshx -h=prod-web "cd /data/app && sudo docker compose up -d"
# Filled — wrap the whole privileged command
sshx -h=prod-web "sudo sh -c 'cd /data/app && docker compose up -d'"sshx warns on stderr when it sees the first form, both before connecting and again if the remote reports that sudo wanted a password. Auto-fill never changes the command text in ways the caller did not ask for.
sshx now enforces strict host key verification just like the OpenSSH client. Instead of silently trusting unknown hosts, the tool reads the trust store from ~/.ssh/known_hosts (or the path you provide) and aborts the connection if the host is missing or the key changes.
Ways to manage host keys:
If the host key ever changes, sshx clearly explains how to remove the old entry before re-connecting, protecting you from potential man-in-the-middle attacks.
If you manage multiple servers with the same username but different passwords, use this strategy:
# Scenario: Manage 3 servers, all with root user but different passwords
# 1. Save password for each server (use meaningful key names)
sshx --password-set=prod-web # Production web server
sshx --password-set=prod-db # Production database server
sshx --password-set=dev-server # Development server
# 2. Execute commands using -pk parameter to specify password key
sshx -h=192.168.1.10 -u=root -pk=prod-web "sudo systemctl status nginx"
sshx -h=192.168.1.20 -u=root -pk=prod-db "sudo systemctl status mysql"
sshx -h=192.168.1.30 -u=root -pk=dev-server "sudo docker ps"
# 3. You can also use aliases to simplify commands (add to ~/.zshrc or ~/.bashrc)
alias ssh-prod-web='sshx -h=192.168.1.10 -u=root -pk=prod-web'
alias ssh-prod-db='sshx -h=192.168.1.20 -u=root -pk=prod-db'
alias ssh-dev='sshx -h=192.168.1.30 -u=root -pk=dev-server'
# Then use simply:
ssh-prod-web "sudo systemctl restart nginx"
ssh-prod-db "sudo systemctl restart mysql"
ssh-dev "sudo docker-compose up -d"You can customize the sudo password key name via environment variable (but using -pk parameter is more flexible):
# Use environment variable (can only specify one at a time, needs constant modification)
export SSH_SUDO_KEY=my-sudo-password
sshx --password-set=my-sudo-password
sshx -h=192.168.1.100 "sudo ls -la /root"
# Recommended: Use -pk parameter, more flexible, no need to modify environment variables
sshx -h=192.168.1.100 -pk=server-A "sudo ls -la /root"
sshx -h=192.168.1.101 -pk=server-B "sudo ls -la /root"You can use environment variables to avoid typing credentials repeatedly:
# Set in .env file or export in shell
export SSH_KEY_PATH=~/.ssh/prod.pem
export SSH_SUDO_KEY=prod-web
export SSH_TIMEOUT=30s
# Optional: isolate all sshx runtime state for an Agent/CI run
export SSHX_HOME="$PWD/.sshx-runtime"
export SSHX_SECRET_BACKEND=local-vault # optional; headless hosts without a keyring
export SSHX_VAULT_PASSPHRASE='…' # required with local-vault unless SSHX_VAULT_KEY_FILE is set
# Then run with fewer repeated options
sshx -h=prod-web "sudo uptime"# Write audit events to a project-specific directory
export SSHX_AUDIT_OUTPUT=./.sshx-audit
# Disable audit writing
export SSHX_NO_AUDIT=trueYou can control the logging verbosity using the SSHX_LOG_LEVEL environment variable:
# Set log level to DEBUG (shows detailed debugging information)
export SSHX_LOG_LEVEL=debug
# Set log level to INFO (default)
export SSHX_LOG_LEVEL=info
# Set log level to WARNING
export SSHX_LOG_LEVEL=warning
# Set log level to ERROR
export SSHX_LOG_LEVEL=errorDebug level logs include:
# 1. Save sudo password (interactive input)
sshx --password-set=master
# Enter password for key 'master': ******
# 2. Verify it's saved
sshx --password-check=master
# ✓ Password exists for key: master
# 3. Use for SSH commands (sudo automatically uses stored password)
sshx -h=192.168.1.100 -u=root "sudo systemctl status docker"
sshx -h=192.168.1.100 -u=root "sudo df -h"
# 4. Use for SFTP operations
sshx -h=192.168.1.100 -u=root --upload=local.txt --to=/tmp/remote.txt
sshx -h=192.168.1.100 -u=root --download=/etc/hosts --to=./hosts.txt
# 5. List all saved password keys
sshx --password-list
# Common keys:
# ✓ master (exists)
# root (not set)
# 6. When done, optionally delete the password
sshx --password-delete=master
# ✓ Password deleted from system keyringSolution:
macOS may block the binary on first run:
sudo xattr -rd com.apple.quarantine /usr/local/bin/sshxOr go to System Preferences → Security & Privacy → Click "Allow Anyway"
Click "More info" and then "Run anyway" if Windows Defender SmartScreen shows a warning.
# Make sure the binary has execute permissions
sudo chmod +x /usr/local/bin/sshxThe project's target state, hard non-goals, and capability coverage matrix live in the Project Profile and Direction. The frozen v1 CLI/JSON/error_kind commitment is in Contract Freeze Policy. Every new top-level capability must add a Happy Path E2E and update the matrix; high-risk, permission-sensitive, and state-changing capabilities must also meet the corresponding failure, permission-state, and recovery coverage floors.
# Run fast unit/component tests
make test-short
# Run compiled-binary SSH/SFTP E2E tests
make test-e2e
# Format code
gofmt -w .
# Build for all platforms
make build-all
# Run linter
make lintThe lint target requires golangci-lint v2.6.1 or newer. Install it with go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.1.
The normal E2E run uses an isolated, test-only keyring provider. CI additionally checks the production binary against an ephemeral macOS Keychain.
Contributions are welcome. Read CONTRIBUTING.md for the development workflow, testing requirements (including the acceptance-matrix rule for new features), and PR expectations, and AGENT.md for the project's mission and scope boundaries.
The project currently has a single primary maintainer. Issues labeled good first issue are the intended on-ramp; if the maintainer is unavailable, those labeled issues plus CONTRIBUTING.md and AGENT.md are the succession record for continuing the contract without expanding scope.
This project is licensed under the MIT License - see the LICENSE file for details.
Documentation • Issues • Discussions • Releases
Made with ❤️ by talkincode
| Back | FazBrowse Home | New Git URL |