Repository navigation
Release v0.18.0
SSHX - Agent-native remote host execution over SSH
📋 What's Changed
Added
sshx textnarrates scan progress on stderr after a short grace period
(bytes, percentage, lines, elapsed, matches) and warns when a scan stops at
its--max-scan-bytesbudget, naming--offset/--tail/--pattern/
--max-scan-bytesas the ways to narrow it. A slow scan closes with the same
advice. stdout stays exactly one JSON document; progress never enters it.sshx.text.v1stats.expected_scan_bytes(additive): the byte budget of the
scanned window, so a caller can size a scan and spot a partial one before
trustingtotal_hits_exact. Omitted when the source size is unknown (journal).- A policy block now mirrors its reason to stderr in
--jsonmode
(sshx: blocked by safety policy (phase=admission, error_kind=blocked, executed=false, exit_code=-1)) plus the flattened reason with the guarded
alternative, so a caller that only prints stdout/stderr no longer sees a
silent refusal. The block predicate and thesshx sql --docker=alternative
are documented in README/README_CN/usage, with unit and E2E assertions. sshxwarns when a command contains a non-leadingsudo(for example
cd /data/app && sudo docker compose up -d): password auto-fill only rewrites
a leadingsudo, so it announces the boundary before connecting and explains
the refusal afterwards, suggestingsudo sh -c "<command>". The auto-fill
scope is unchanged.- Per-verb help: every subcommand now answers
sshx <verb> --helpwith its own
usage document instead of rejecting--helpas an unknown option.--help --jsonemits the same blocks as ansshx.help.v1document (sshx text --help --jsonkeeps its structuredsshx.text.help.v1document), and the
help text is defined once and shared with the globalsshx --helpsurface. --quiet(alias--no-notices): suppresses human notices on stderr
(deprecation warnings, policy-block mirrors, sudo-boundary hints, scan
progress, narration) so a caller that merges the streams
(2>&1) under--jsonstill reads exactly one parseable document. stdout,
the exit code, and the JSON result are unchanged. Like--help, it is
recognized in option position in any order, so it works before or after other
sshx options and never reaches the remote command.sshx sql --statement-file=PATHand statement input on stdin: a query no
longer has to be assembled as a shell string.sshx sqlalso accepts a
statement that opens with a SQL comment (-- header) as statement text
instead of rejecting it as an unknown option. Reading stdin waits for EOF
(likepsql), so a caller whose stdin pipe stays open should pass
--statement-fileinstead.sshx plugin install <dir> [--replace] [--trust]: provision an existing local
plugin directory through the audited CLI instead of hand-placing files under
the runtime plugin root. The source is staged with sshx's own modes, validated
through the executor's loader before publishing, and--trustrecords the
published digest in the same step.--replacepreserves the previous plugin
as a backup, symlinks and non-regular entries are refused, and the copy is
bounded (8MiB, 128 files).
Changed
-
sshx textreads remote files through a pipelined SFTP path: read-ahead
aperture plusUseConcurrentReads, so the SFTP layer keeps multiple requests
in flight for one file instead of one round trip per read. On the reporting
host the same 8 MiB window went from a median 83.0 s to 26.5 s (88.0/77.9 s →
23.8/29.1 s, alternating runs, identical bytes and lines scanned).--max-scan-bytes
still bounds both the scan and the read-ahead, and the truncation probe reads
exactly one byte past the budget. -
sshx plugin listgroups built-in capabilities and local plugins and always
names the local plugin root, so "no local plugins installed" is visible
instead of inferred; local entries report provenance, trust, validity, and
digest, andplugin show/trust/installreport the same state line. A
staging directory left by an interrupted install is skipped instead of being
reported as an invalid plugin, and a publication that cannot be renamed swaps
the previous plugin back in (or reports where the recovery copy was kept). -
Compatibility mode (
sshx -h=<host> ...) now accepts the documented
--ssh-password-key=KEYoption instead of forwarding it as part of the remote
command, and rejects an unrecognized option instead of forwarding it, naming
the offending token and suggesting the intended option when one is close. The guessed
--local/--remotetransfer options name the real surface
(--upload=<local> --to=<remote>), and a missing upload/download destination
names--toexplicitly. Remote command arguments after the first command
token, and after--, are unchanged. -
A missing plugin,
--list=,--mkdir=,--rm=,--upload=or
--download=now names the searched directory or the option that supplies the
missing value (remote path is required/local path is requireddiagnosed
the wrong cause).
Fixed
-
sshx textno longer looks like a hang: a 60+ second SFTP window used to emit
nothing at all, and a budget-limited scan returned partial results without
saying so. -
sshx run --target=<name>resolves the sudo keyring reference per host, the
same way single-target verbs do: an explicit-pkstill wins, but the
built-in default (master) no longer shadows a host's configured
sudo_password_key. The plan, the SSH client, the keyring lookup, and the
audit trail all resolve the same reference, so the plan reports the key that
will be used and each target's audit event names the credential it used (the
run summary records only a caller-level choice). -
sshx applyremoves its staging temp, publication temp, and unverified backup
with an absolute remover or POSIXunlinkbefore falling back toPATH rm, so
a host whosermis a trash-move wrapper no longer collects leaked copies of
the applied payload. Cleanup success now means the path is gone, and an
artifact that cannot be removed still reportscleanup_pendingwith exit 4. -
TestApplySudoScriptEvidenceAndCleanupno longer pipes the generated
privileged script through the child's stdin or captures its output through
os/execcopier goroutines; the fixture runs the script from a file with
file-backed streams and reports a truncated report readably instead of
panicking on a nil pointer. -
The CI
Lintgate is pinned to golangci-lintv2.13.2and.golangci.yml
uses the v2linters.exclusions.rulesschema, so an upstream release or the
v1-era key can no longer fail the job before any Go file is analysed.
🚀 Quick Install
Linux / macOS:
curl -fsSL https://raw.githubusercontent.com/talkincode/sshx/main/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/talkincode/sshx/main/install.ps1 | iex📦 Downloads
| Platform | Architecture | File |
|---|---|---|
| Linux | x86_64 | sshx-linux-amd64.tar.gz |
| Linux | ARM64 | sshx-linux-arm64.tar.gz |
| macOS | Intel | sshx-darwin-amd64.tar.gz |
| macOS | Apple Silicon | sshx-darwin-arm64.tar.gz |
| Windows | x86_64 | sshx-windows-amd64.zip |
| Windows | ARM64 | sshx-windows-arm64.zip |
Verify downloads: Use checksums.txt for integrity verification