Repository navigation
Expand file tree
/
Copy pathllms-full.txt
More file actions
435 lines (388 loc) · 41.4 KB
/
Copy pathllms-full.txt
File metadata and controls
435 lines (388 loc) · 41.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# ssh-cli full LLM index
> One-shot multi-host SSH command-line interface (CLI) for AI agents with X Desktop Group (XDG) storage and no Node daemon
ssh-cli is a memory-safe Rust binary that lets large language model (LLM) agents operate remote servers over SSH through stdin and stdout. Each call is born, runs one operation and dies: it keeps no daemon, sends no telemetry, stores a multi-host registry under XDG with secrets encrypted at rest, and exposes 48 leaf commands with 25 JSON schemas in version 0.5.6. This file is the complete discovery map: it lists all 48 leaf commands with every flag, the 27 global flags, the 25 JSON schemas and the rules an agent needs.
- Portuguese discovery file: [llms.pt-BR.txt](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/llms.pt-BR.txt)
- Short index: [llms.txt](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/llms.txt)
## Primary Documentation
- [README](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/README.md): install, command table, exit codes and troubleshooting FAQ
- [HOW_TO_USE](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/HOW_TO_USE.md): first command in 60 seconds and the core command walk-through
- [COOKBOOK](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/COOKBOOK.md): executable recipes for hosts and remote operations
- [AGENTS](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/AGENTS.md): agent economics, command inventory and the JSON contract
- [MIGRATION](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/MIGRATION.md): upgrade path from 0.3.3 to 0.5.6
- [INTEGRATIONS](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/INTEGRATIONS.md): agent and shell catalog with flag aliases and new flags by version
- [CHANGELOG](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/CHANGELOG.md): release history
- [SECURITY](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/SECURITY.md): vulnerability disclosure and secrets at rest
- [CONTRIBUTING](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/CONTRIBUTING.md): contribution process
- [CODE_OF_CONDUCT](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/CODE_OF_CONDUCT.md): community standards
- [CROSS_PLATFORM](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/CROSS_PLATFORM.md): targets, binary sizes and Windows notes
- [TESTING](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/TESTING.md): catalog of the test suites
- [RELEASE_CHECKLIST](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/RELEASE_CHECKLIST.md): release gates and steps
- [schemas index](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/README.md): machine-readable JSON contracts, one schema per event
- [skill en](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/skills/ssh-cli-en/SKILL.md): imperative agent skill package in English
- [scripts/e2e_real_ssh.py](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/scripts/e2e_real_ssh.py): real SSH end-to-end run that never logs secrets
- [scripts/verify_install_resolve.py](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/scripts/verify_install_resolve.py): install resolution gate
- [scripts/check_all_gates.py](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/scripts/check_all_gates.py): mandatory gate battery that runs every gate in one invocation
## For AI Agents
- Call the binary, read one JSON document and let the process die: there is no session to keep or to close
- Register a host once, then address it by name in every call
- Use `--select`, `--filter` and `--limit` so the payload is small before it is written
- Read the exit code before the output, and read stderr for the failure envelope
- Load the skill package when the agent supports skills
## Quick Integration
- `cargo install ssh-cli --locked`: installs ssh-cli 0.5.6, which needs Rust 1.89 or newer
- `printf '%s' "$PASS" | ssh-cli vps add --name prod --host h.example --user deploy --password-stdin --json`: registers a host once without a password on argv
- `ssh-cli exec prod 'hostname' --json`: runs one remote command and prints one JSON object
- `ssh-cli doctor --json`: preflight for paths, encryption at rest and the config directory mode
- `ssh-cli commands --json`: full command tree with every argument of every command
- `ssh-cli schema`: catalog of the 25 embedded JSON schemas, and `ssh-cli schema <NAME>` prints one
- Agents with a shell tool, such as Claude Code, Cursor, Windsurf, Codex CLI, OpenCode, Aider, Continue, Gemini CLI and OpenHands, call the same argv: see [INTEGRATIONS](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/INTEGRATIONS.md)
- The only contract is argv, stdin, stdout, stderr and the exit code
## Install
- `cargo install ssh-cli --locked` installs the binary on the PATH of the user
- The minimum supported Rust version is 1.89
- `ssh-cli --version` prints `ssh-cli 0.5.6` followed by the commit in parentheses, or `(unknown)` when the build has no commit
- Shell completions come from `ssh-cli completions <SHELL>`
- The binary needs no daemon, no `.env` file and no product environment variable to run
## JSON Schemas
- [dry-run.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/dry-run.schema.json): `--dry-run --json` plan envelope, emitted instead of executing
- [error-envelope.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/error-envelope.schema.json): failure envelope written to stderr
- [exec-batch.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/exec-batch.schema.json): `exec|sudo-exec|su-exec --all|--hosts|--tags --json`
- [exec.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/exec.schema.json): `exec --json`, one NDJSON object
- [health-check-batch.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/health-check-batch.schema.json): `health-check --all|--hosts --json`, with `max_concurrency`
- [health-check.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/health-check.schema.json): `health-check --json`
- [scp-batch.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/scp-batch.schema.json): `scp upload|download --all|--hosts --json`
- [scp-transfer.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/scp-transfer.schema.json): `scp upload|download --json`, regular files only
- [secrets-init.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/secrets-init.schema.json): `secrets init --json`, `event: "secrets-init"`
- [secrets-reencrypt.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/secrets-reencrypt.schema.json): `secrets reencrypt --json`, `event: "secrets-reencrypt"`
- [sftp-batch.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/sftp-batch.schema.json): `sftp --all|--hosts --json`
- [sftp-fs-op.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/sftp-fs-op.schema.json): `sftp mkdir|rmdir|rm|stat|rename --json`
- [sftp-list.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/sftp-list.schema.json): `sftp ls --json`
- [sftp-transfer.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/sftp-transfer.schema.json): `sftp upload|download --json`, files and `--recursive` trees
- [su-exec.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/su-exec.schema.json): `su-exec --json`
- [sudo-exec.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/sudo-exec.schema.json): `sudo-exec --json`
- [tunnel-closed.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/tunnel-closed.schema.json): `tunnel --json` shutdown event with `reason`, `forwards_served` and `capacity_waits`
- [tunnel-listening.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/tunnel-listening.schema.json): `tunnel --json` event emitted after the bind
- [vps-doctor.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/vps-doctor.schema.json): `doctor --json` and `vps doctor --json`, with `local.config_dir_permissions`, `local.config_dir_exposed` and `local.invalid_host_names`
- [vps-edit.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/vps-edit.schema.json): `vps edit --json`, `event: "vps-edited"` with `updated`, or `vps-renamed` with `previous_name` and `active_rewritten` when `--name` renames
- [vps-export.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/vps-export.schema.json): `vps export --json`, `event: "vps-export"`
- [vps-import.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/vps-import.schema.json): `vps import --json`, `event: "vps-import"` with `imported` and the sorted, always present `replaced`
- [vps-list.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/vps-list.schema.json): `vps list --json`
- [vps-rename.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/vps-rename.schema.json): `vps rename --json`, `event: "vps-renamed"` with `previous_name` and `active_rewritten`
- [vps-show.schema.json](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/vps-show.schema.json): `vps show --json`
- [schemas index](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/docs/schemas/README.md): one schema per event, in English and Portuguese
- `ssh-cli schema` lists the same 25 schemas at runtime, and `ssh-cli schema <NAME>` prints one body
## Command reference
- 48 leaf commands from `ssh-cli commands`, written out in full because a retriever searching for `tls acme account create` must find that exact string
- `ssh-cli vps add`: Adds a new VPS to the registry
- Flags: `--name <NAME>` `--host <HOST>` `--port <PORT>` `--user <USER>` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>` `--max-command-chars <N>` `--max-output-chars <N>` `--max-chars <N>` `--sudo-password <SUDO_PASSWORD>` `--sudo-password-stdin` `--su-password <SU_PASSWORD>` `--su-password-stdin` `--disable-sudo` `--tag <TAG>` `--tls` `--tls-sni <NAME>` `--tls-client-cert <PATH>` `--tls-client-key <PATH>` `--check`
- `ssh-cli vps list`: Lists all VPS hosts (passwords masked)
- Flags: `--tag <TAG>`
- `ssh-cli vps remove`: Removes a VPS from the registry
- Arguments: `<NAME>`
- `ssh-cli vps rename`: Renames a registered VPS without touching credentials
- Arguments: `<NAME>` `<NEW_NAME>`
- `ssh-cli vps edit`: Edits fields of an existing VPS
- Arguments: `<NAME>`
- Flags: `--name <NEW>` `--host <HOST>` `--port <PORT>` `--user <USER>` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>` `--max-command-chars <N>` `--max-output-chars <N>` `--max-chars <N>` `--sudo-password <SUDO_PASSWORD>` `--sudo-password-stdin` `--su-password <SU_PASSWORD>` `--su-password-stdin` `--disable-sudo` `--enable-sudo` `--tls` `--no-tls` `--tls-sni <NAME>` `--tls-client-cert <PATH>` `--tls-client-key <PATH>` `--tag <TAG>` `--clear-tags` `--no-key` `--no-agent-socket` `--clear-sudo-password` `--clear-su-password` `--no-tls-sni` `--no-tls-client-cert` `--no-tls-client-key`
- `ssh-cli vps show`: Shows VPS details (passwords masked)
- Arguments: `<NAME>`
- `ssh-cli vps path`: Shows the configuration file path
- `ssh-cli vps doctor`: Diagnostics for XDG layers / path / schema
- Flags: `--probe-ssh` `--hosts <LIST>`
- `ssh-cli vps export`: Exports hosts (passwords redacted by default)
- Flags: `--include-secrets` `--output <PATH>` `--i-understand-secrets-on-stdout`
- `ssh-cli vps import`: Imports hosts from a TOML file or JSON `vps-export` envelope (EN + legacy PT keys)
- Flags: `--file <PATH>` `--allow-incomplete`
- `ssh-cli connect`: Sets the active VPS (writes sibling `active` file in the config directory)
- Arguments: `<NAME>`
- `ssh-cli exec`: Runs a command on the VPS over SSH (stdout/stderr captured)
- Flags: `--all` `--hosts <LIST>` `--tags <LIST>` `--use-active` `--step <CMD>` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>` `--description <DESCRIPTION>`
- `ssh-cli sudo-exec`: Runs a command with `sudo` (safe `sh -c` packing)
- Flags: `--all` `--hosts <LIST>` `--tags <LIST>` `--use-active` `--step <CMD>` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--sudo-password <SUDO_PASSWORD>` `--sudo-password-stdin` `--timeout <MS>` `--description <DESCRIPTION>`
- `ssh-cli su-exec`: Runs a command with one-shot `su -` elevation
- Flags: `--all` `--hosts <LIST>` `--tags <LIST>` `--use-active` `--step <CMD>` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--su-password <SU_PASSWORD>` `--su-password-stdin` `--timeout <MS>` `--description <DESCRIPTION>`
- `ssh-cli scp upload`: Uploads a local file to the remote host (regular files only)
- Arguments: `[<VPS>]`
- Flags: `--all` `--hosts <LIST>` `--src <PATH>` `--dest <DIR>` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli scp download`: Downloads a remote file to the local host (regular files only)
- Arguments: `[<VPS>]`
- Flags: `--all` `--hosts <LIST>` `--src <PATH>` `--dest <DIR>` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli sftp upload`: Uploads a local file or tree to the remote host via SFTP
- Arguments: `[<VPS>]`
- Flags: `--all` `--hosts <LIST>` `--src <PATH>` `--dest <DIR>` `--recursive` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli sftp download`: Downloads a remote file or tree to the local host via SFTP
- Arguments: `[<VPS>]`
- Flags: `--all` `--hosts <LIST>` `--src <PATH>` `--dest <DIR>` `--recursive` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli sftp ls`: Lists a remote directory
- Arguments: `<VPS_NAME>` `<REMOTE>`
- Flags: `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli sftp mkdir`: Creates a remote directory
- Arguments: `<VPS_NAME>` `<REMOTE>`
- Flags: `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli sftp rmdir`: Removes an empty remote directory
- Arguments: `<VPS_NAME>` `<REMOTE>`
- Flags: `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli sftp rm`: Removes a remote file
- Arguments: `<VPS_NAME>` `<REMOTE>`
- Flags: `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli sftp stat`: Shows metadata for a remote path
- Arguments: `<VPS_NAME>` `<REMOTE>`
- Flags: `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli sftp rename`: Renames a remote path
- Arguments: `<VPS_NAME>` `<FROM>` `<TO>`
- Flags: `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli tunnel`: SSH tunnel with mandatory deadline (bounded one-shot)
- Arguments: `<VPS_NAME>` `<LOCAL_PORT>` `[<REMOTE_HOST>]` `[<REMOTE_PORT>]`
- Flags: `--socks5` `--remote-socket <PATH>` `--reverse` `--timeout-ms <MS>` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--bind <ADDR>` `--i-accept-network-exposure`
- `ssh-cli health-check`: Checks SSH connectivity to a VPS (`--all` / `--hosts` / `--use-active`)
- Arguments: `[<VPS_NAME>]`
- Flags: `--all` `--hosts <LIST>` `--use-active` `--password <PASSWORD>` `--password-stdin` `--key <PATH>` `--key-passphrase <KEY_PASSPHRASE>` `--key-passphrase-stdin` `--use-agent` `--agent-socket <PATH>` `--timeout <MS>`
- `ssh-cli secrets status`: Shows encryption status (no sensitive material)
- `ssh-cli secrets init`: Generates and stores the primary key (`secrets.key` or keyring) and never prints the key
- Flags: `--keyring` `--force`
- `ssh-cli secrets reencrypt`: Rewrites `config.toml` re-encrypting secrets with the current key
- `ssh-cli completions`: Generates shell completions
- Arguments: `<SHELL>`
- `ssh-cli commands`: Emits the full command tree as JSON (agent discovery / rules `mycli commands`)
- `ssh-cli schema`: Emits embedded JSON Schema catalog or one schema body (G-E2E-02)
- Arguments: `[<NAME>]`
- `ssh-cli doctor`: Root alias for `vps doctor` (XDG / schema diagnostics, G-E2E-03)
- Flags: `--probe-ssh` `--hosts <LIST>`
- `ssh-cli locale show`: Shows resolved language, winning layer, and available locales (default)
- `ssh-cli locale set`: Persists preferred language under the config directory (`lang` file, 0o600)
- Arguments: `<LOCALE>`
- `ssh-cli locale clear`: Removes the persisted language preference
- `ssh-cli tls provider`: Shows rustls CryptoProvider status (`aws_lc_rs`)
- `ssh-cli tls paths`: Prints XDG TLS directory layout paths
- `ssh-cli tls mtls list`: Lists imported identity names
- `ssh-cli tls mtls import`: Imports PEM cert+key as a named identity
- Flags: `--name <NAME>` `--cert <PATH>` `--key <PATH>`
- `ssh-cli tls mtls show`: Shows paths for one identity
- Arguments: `<NAME>`
- `ssh-cli tls mtls remove`: Removes an identity directory
- Arguments: `<NAME>`
- `ssh-cli tls acme account create`: Creates an ACME account (credentials under XDG `tls/acme/account.json`, 0o600)
- Flags: `--staging` `--contact <URL>` `--force`
- `ssh-cli tls acme account show`: Shows whether an account exists and its path
- `ssh-cli tls acme issue`: Starts DNS-01 order and prints the TXT challenge (persists order URL under XDG)
- Flags: `--domain <DOMAIN>` `--staging` `--print-challenge`
- `ssh-cli tls acme complete`: Completes a pending order after DNS TXT is published
- Flags: `--domain <DOMAIN>`
- `ssh-cli tls acme status`: Shows certificate / pending status for one domain or all
- Flags: `--domain <DOMAIN>`
- `ssh-cli tls acme list`: Lists ACME domain directories under XDG
- Discover the same tree at runtime with `ssh-cli commands --json`, which publishes `id`, `long`, `short`, `value_name`, `required`, `repeatable`, `takes_value` and `hidden` for every argument
## Rename and edit
- `vps rename <NAME> <NEW_NAME>` moves the registry key and leaves every credential readable under the new name
- The record seals each secret without associated data, so no host name is needed to open a blob
- It emits `event: "vps-renamed"` with `previous_name` and `active_rewritten`, and repeating it after a success exits 66
- `vps edit <NAME>` changes fields in one locked transaction, and `--name <NEW>` renames in place with the same `vps-renamed` event
- Eight clear flags remove a field instead of overwriting it: `--no-key`, `--no-agent-socket`, `--clear-sudo-password`, `--clear-su-password`, `--no-tls-sni`, `--no-tls-client-cert`, `--no-tls-client-key` and `--clear-tags`
- Each clear flag conflicts with the value flag of the same field, and `--no-key` also clears the passphrase
- `vps-edited` is emitted only when nothing was renamed, and `updated` is always present with the fields whose stored value changed
- An edit that changes nothing exits 0 with `updated: []`, and a call with no field, rename or clear flag exits 64
- The old export, edit and import detour is obsolete, because the export carried no agent fields
## Tunnel modes
- `tunnel <VPS> <LOCAL_PORT> <REMOTE_HOST> <REMOTE_PORT> --timeout-ms <MS>` opens the default local forward
- `--reverse` asks the server to listen and deliver connections back, and `<REMOTE_PORT> 0` lets the server allocate the port
- `--socks5` serves a local SOCKS5 proxy (RFC 1928, no auth, `CONNECT`) that picks the destination per connection
- `--socks5` omits `<REMOTE_HOST>` and `<REMOTE_PORT>`, so one handshake reaches many destinations behind a bastion
- `--remote-socket <PATH>` forwards to a remote Unix socket through `direct-streamlocal@openssh.com`, and the path must be absolute or the call exits 64
- The JSON `mode` label is `local`, `reverse`, `socks5` or `streamlocal`
- `--bind` defaults to `127.0.0.1` and is validated as an IP at parse time, so a typo exits 2 before any handshake
- `tunnel_listening` carries the bind address and `local_port` after the bind
- `tunnel_closed` carries `reason`, `forwards_served` and `capacity_waits` on shutdown
- Prefer one `tunnel --socks5` over N `tunnel` processes when reaching N destinations behind one bastion
## Global flags
- `--lang <LOCALE>`: Forces the CLI language (`en` or `pt-BR`, and BCP47 tags negotiate to them)
- `-v, --verbose`: Raises log verbosity on stderr (`-v` info, `-vv` debug, `-vvv` trace), always scoped to this crate
- `-q, --quiet`: Suppresses non-JSON output
- `--config-dir <DIR>`: Overrides the configuration directory, and a path ending in `.toml` makes its parent the directory
- `--no-color`: Disables colored output
- `--output-format <text|json>`: Chooses `text` or `json`, and JSON is the default when stdout is not a TTY
- `--json`: Forces JSON output, alias of `--output-format json`
- `--select <PATHS>`: Keeps only these dotted paths in each record (CSV, alias `--fields`)
- `--filter <EXPR>`: Keeps records matching `key=value`, `key!=value` or `key~substring`, repeatable with AND
- `--limit <N>`: Emits at most N records
- `--sort <PATH>`: Sorts records ascending by dotted path, comparing numbers numerically
- `--dedupe-by <PATH>`: Drops later records that repeat the value of this dotted path
- `--count-only`: Replaces the record collection with `{"count": N}`, counted after filtering
- `--truncate-content <CHARS>`: Shortens strings longer than N characters, never bytes
- `--max-output-bytes <BYTES>`: Caps the envelope size by dropping trailing records, never by slicing the JSON text
- `--no-input`: Refuses to read stdin and fails fast instead of blocking
- `--dry-run`: Prints the plan and exits without executing, accepted only by eight operations and rejected with exit 64 elsewhere
- `--lock-wait-ms <MS>`: Bounds the wait for the config lock (default 5000, maximum 300000) before exit 69
- `--disable-sudo`: Disables `sudo-exec` and `su-exec` for this invocation (alias `--disableSudo`), and placed before `vps add` or `vps edit` it also writes `disable_sudo` to the host
- `--replace-host-key`: Replaces a diverging host key in the TOFU `known_hosts`
- `--allow-plaintext-secrets`: Allows plaintext secrets at rest, for tests only
- `--secrets-key-file <PATH>`: Reads the 64-hex primary key from this file for one invocation
- `--use-keyring`: Prefers the OS keyring for the primary key
- `--timeout <MS>`: Sets the global SSH timeout in milliseconds, and a local `--timeout` wins while `tunnel` still needs `--timeout-ms`
- `--max-concurrency <N>`: Caps concurrent multi-host sessions and tunnel forwards, from 1 to 64
- `--fail-fast`: Stops admitting new multi-host units after the first failure
- `--scp-file-concurrency <N>`: Sets concurrent SCP transfers on one SSH session (default 1)
## Agent rules
### Authentication
- Pass secrets with `--password-stdin`, `--key`, `--key-passphrase-stdin` or `--use-agent`, never as an argv value
- `vps edit --key`, `--password` or `--password-stdin`, and `--use-agent` switch the stored method in one call and clear the opposite fields
- `updated` in the `vps-edited` event names the new method and only the cleared fields that held a value
- Two primary auth flags in one call exit 2, and a passphrase without any key exits 64 and leaves the record untouched
- A different `--key` path drops the old passphrase unless the call brings a new one, and `--agent-socket` alone implies `--use-agent`
- `--no-key` exits 64 when the key was the only authentication, and `--no-agent-socket` exits 64 on Unix when it would leave the agent without an endpoint
- After an auth rejection (exit 77) try another method, and review a TOFU host-key error before using `--replace-host-key`
### Host names
- `vps add`, `vps rename`, `vps edit --name` and `vps import` accept only 1 to 255 ASCII letters, digits, dot, hyphen and underscore, starting with a letter or digit
- Any other name exits 64, and a name such as `bad name`, `x;y` or one with an ESC byte never reaches the registry
- `vps import` checks every item first, lists all invalid names at once, writes nothing and gives the same error under `--dry-run`
- Names already stored with a space or an accent still load, list, show and resolve
- A stored name with control or bidi characters loads too, `doctor` lists it escaped in `local.invalid_host_names`, and `vps rename '<legacy name>' <new>` repairs it
- Lookups that become a target (`exec`, `health-check`, `--hosts` and `connect`) refuse control and bidi characters with exit 64, and `connect` writes no marker
- `tls mtls import --name` refuses control, formatting and bidi characters with exit 64 and creates no directory, while `serviço-1` stays valid
- `vps rename` exits 64 with `vps_duplicate` when the destination exists, and exits 64 when the new name equals the current one
- The registry is keyed by the NFC record name, and two tables that normalize to the same name exit 65 with `config has two hosts named ...`
### Terminal safety
- Text mode writes control, formatting and bidi characters as escapes such as `\u{1b}` in messages, errors, `list`, `show`, `health-check`, `doctor` and batch output
- `sftp ls` and `sftp stat` also escape `\n` and `\t` inside remote names, so a hostile name cannot forge extra lines
- JSON output keeps the stored value unchanged because it is for machines
- The remote stdout and stderr of `exec` stay raw by design, so never print them to a terminal without your own filter
### Doctor and config directory
- `ssh-cli doctor --json` reports `local.config_dir_permissions`, `local.config_dir_exposed`, `local.invalid_host_names` and `local.active_vps`
- `--filter` and `--count-only` do not reach the `doctor` object and are named in `agent_shape.ignored`, while `--select` does, so `ssh-cli doctor --json --select local.active_vps` returns that field
- Read one field with `ssh-cli doctor --json --select local.config_dir_exposed`
- A config directory level created by the CLI gets mode 0700, and `config.toml`, `known_hosts.lock` and `lang` are created 0600
- An existing directory is tightened to 0700 only when it is the default XDG directory, not a symlink and owned by the current user
- Any other existing directory keeps its mode, so `config_dir_exposed: true` means the operator must run `chmod 700` on it
- `--config-dir <path>.toml` makes the parent the config directory for the registry, `secrets.key`, `known_hosts`, `active`, `lang` and the TLS files
- `vps export -o new/deep/e.json` creates the missing directories 0700 and never changes an existing one
### Preview with dry-run
- `--dry-run` is accepted only by `vps remove`, `vps import`, `vps edit`, `vps rename`, `sftp rm`, `sftp rmdir`, `secrets init` and `secrets reencrypt`
- Anywhere else `--dry-run` exits 64 instead of being accepted and ignored
- The plan is a `dry-run` event with `operation`, `executed: false`, `target_resolved`, `target_source` (`argv`), `host_resolved` and `host_source`, as in `docs/schemas/dry-run.schema.json`
- `vps edit`, `vps rename` and `vps remove`, real or dry, exit 66 on a missing registry or host and create no directory and no lock file
- Both previews of `vps edit` and `vps rename` run every validation of the real run and stop before the write
### Targets
- Always name the target: `exec <VPS> <COMMAND>`, or a selector (`--all`, `--hosts` or `--tags`) with one positional
- A lone positional on `exec`, `sudo-exec` or `su-exec` exits 64, because the host name would otherwise run as a remote binary
- `connect <NAME>` writes the active marker, and only `--use-active` with `<COMMAND>` reaches it, with exit 66 when no marker exists
- Read the target back from `target_resolved` and `target_source` (`argv`, `active_marker` or `selector`) and from `active_fallback`
- `host_resolved` and `host_source` are the 0.5.5 read aliases of the two canonical fields
### Output and errors
- Success JSON goes to stdout, and with `--json` or a non-TTY stdout a failure writes its envelope to stderr
- A failure before any remote work leaves stdout empty, while a remote command that ran and failed keeps its `exec` JSON on stdout
- A failed remote command exits 1: the `exec` JSON on stdout has the remote `exit_code`, and the stderr envelope `command_failed` carries `remote_exit_code`
- A partial fleet failure prints the batch JSON on stdout plus a `partial_failure` envelope on stderr, also with exit 1
- Usage errors are plain text on stderr with no JSON envelope: the exit 2 of the argument parser and the exit 64 of a malformed `--filter`
- Only runtime errors carry the JSON envelope, so parse the stderr of a non-zero exit as JSON only after the exit code is neither 2 nor a usage 64
- The failure envelope has `exit_code`, `error_code`, `message`, `error_class`, `retryable` and `suggestion`, as in `docs/schemas/error-envelope.schema.json`
- Read the exit code first, then the stream that matches it, and never parse tracing lines on stderr
- Retry only when `retryable` is true, which covers IO, timeouts and the busy config lock, and never retry auth, usage or data errors
- Error bodies stay in English by design, so the agent receives stable text
- Default tracing is `error`, ambient `RUST_LOG` is ignored, and `-v`, `-vv` and `-vvv` raise it, always scoped to this crate
### Payload shaping
- `--select` (alias `--fields`), `--filter`, `--limit`, `--sort`, `--dedupe-by`, `--count-only`, `--truncate-content` and `--max-output-bytes` are global and run before serialization
- The collection flags reduce the top-level array of `vps list`, the `results` array of batch envelopes and the `entries` array of `sftp ls`
- A single object such as `exec` on one host, `vps show` and `doctor` is reduced by `--select` and `--truncate-content`, so `ssh-cli doctor --json --select local.config_dir_exposed` returns one field
- The collection flags `--filter`, `--limit`, `--sort`, `--dedupe-by`, `--count-only` and `--max-output-bytes` cannot apply to one record, so they are named in `agent_shape.ignored`
- `--select` names the fields of each record, so on a batch envelope use `--select name,stdout` and not `--select results`
- A shaped envelope gains `agent_shape` with `input_count`, `output_count` and `dropped`, and a shaped single object gains `selected`, `content_truncated` and `ignored`
- A reduced bare array such as `vps list` has no room for `agent_shape`, so its report reaches stderr as one JSON line with `"event":"agent-shape"` and `--quiet` suppresses it
- `--filter` takes `key=value`, `key!=value` or `key~substring`, repeatable with AND, and a malformed predicate is rejected at parse time
- `--no-input` refuses to read stdin, so combine it with a flag value instead of `--password-stdin`
### Import and export
- `vps export` prints JSON on any non-TTY stdout, even into a `.toml` filename, and TOML only with `--output-format text`
- A redacted export has empty secrets and never an `sshcli-enc:` blob of empty, so importing it needs `--allow-incomplete`
- `--include-secrets` into a pipe needs `-o <PATH>` or `--i-understand-secrets-on-stdout`, and it exits 64 otherwise
- `vps import --file <PATH>` accepts TOML with EN keys such as `username` plus legacy PT keys, or a JSON `vps-export` envelope, and bad TOML exits 65
- The `vps-import` event carries `imported` and the sorted, always present `replaced` list
- Never use export, edit and import to rename a host, because `vps rename` keeps every credential and the detour dropped agent authentication
- A legacy host whose name fails the strict charset exports fine but restores only after `vps rename` gives it a strict name
### Fleet
- Prefer one process with `--all`, `--hosts a,b` or `--tags` over N single-host spawns
- `--tags` exists on `exec`, `sudo-exec` and `su-exec`, while `scp`, `sftp` and `health-check` take `--all` and `--hosts`
- Batch envelopes (`exec-batch`, `scp-batch`, `sftp-batch`, `health-check-batch`) carry `max_concurrency`, and on cancel the remainder is filled as cancelled so `results.len() == input.len()`
- `health-check --all` returns `results[].status` (`ok` or `error`) plus `error`, so list the failures with `--filter status!=ok`, while `exec-batch` results carry `ok`
- Partial multi-host failure exits 1, and `--fail-fast` stops admitting new units after the first failure
- `vps doctor --probe-ssh [--hosts a,b] --json` emits one `vps-doctor` document with `local` and `ssh_probe`
### Transfers and tunnels
- `scp upload|download` moves regular files only, with no `-r`, and `--json` emits `scp-transfer` as in `docs/schemas/scp-transfer.schema.json`
- mtime and mode preservation is best effort, so read `mtime_preserved` and `durable` for the real outcome
- A missing remote file exits 66 with `file not found: <path>`, and a download writes `.ssh-cli.partial`, runs `sync_data` and then renames
- Prefer 0.5.3 or newer for SFTP, where upload never truncates to 0 bytes and the SETSTAT call sends atime and mtime together
- SFTP upload masks modes with `SFTP_PERM_MASK` (0o7777) and download with `SFTP_PERM_MASK_UNTRUSTED` (0o0777), so server-sent setuid, setgid and sticky bits never reach a local file
- `tunnel` always needs `--timeout-ms`, and a deadline after the bind exits 0 while a timeout before the bind exits 74
- `tunnel` stopped by SIGINT exits 130 and by SIGTERM exits 143, and `tunnel_closed` carries `reason: signal`
- `tunnel` has four exclusive modes: the default local forward, `--socks5`, `--remote-socket <PATH>` and `--reverse`
- The JSON `mode` is `local`, `reverse`, `socks5` or `streamlocal`, and `tunnel_listening` and `tunnel_closed` both carry it
- A non-loopback `--bind` requires `--i-accept-network-exposure`, and under `--reverse` that flag guards the bind of the server
- Wait for `tunnel_listening` before using the local port, and read `tunnel_closed` (`reason`, `forwards_served`, `capacity_waits`) to learn why it ended
### Secrets and configuration
- Secrets in `config.toml` are encrypted by default with ChaCha20-Poly1305 and an auto-created `secrets.key`, and the first secret write reports `secrets_key_auto_created: true` in the same `vps-added` document
- Configure the product with CLI flags (`--allow-plaintext-secrets`, `--secrets-key-file`, `--use-keyring`) and XDG files, never with environment variables
- `SSH_CLI_SECRETS_*` variables fail closed when present, and there is no `.env` product store
- Never print or log host passwords, the master key or decrypted secrets, and `-vvv` does not dump russh password traffic
- `tls acme issue`, `tls acme complete` and `tls acme status` take the domain as `--domain <DOMAIN>`, and a positional domain exits 2
- A redacted `vps export` writes `password` as an empty string and the other secrets as `null`, while the three-asterisk mask appears only in `vps list` and `vps show`
- The config lock waits `--lock-wait-ms` (default 5000, maximum 300000) and then exits 69 instead of hanging
- Telemetry is always false, and the product never runs a daemon
## Defaults and limits
- Default SSH port is 22
- Default timeout is 60000 ms
- Default `max_command_chars` is 1000, and `--max-command-chars` is the main flag while `--max-chars` and `--maxChars` are legacy aliases
- Default `max_output_chars` is 100000
- Schema version of new hosts is 3, with dual read of EN keys and legacy PT aliases
- Empty password fields are JSON `null` on key-only hosts, and non-empty secrets are masked in `vps list` and `vps show`
- Transport is SSH-2 over russh with the aws-lc-rs backend and SSH compression `none`, with optional SSH-over-TLS through rustls and no OpenSSL or native-tls
- `--max-concurrency` accepts 1 to 64, and without it the limit is derived from the CPU count against the free RAM
- Install with `cargo install ssh-cli --locked`, and use 0.5.3 or newer for SFTP
## Troubleshooting
- Exit 66 on `exec` means the host is not in the registry, so run `ssh-cli vps list`
- Exit 64 with `expected exactly two positionals` means the target was not named, so pass `<VPS> <COMMAND>`
- Exit 77 means authentication was rejected or the host key changed, so review the TOFU error before `--replace-host-key`
- Exit 69 means the config lock stayed busy, so repeat the same argv or raise `--lock-wait-ms`
- Exit 141 means the reader closed the pipe, so the output is incomplete
- An empty stdout with a non-zero exit means the failure envelope is on stderr
- `vps import` exit 64 lists every invalid host name, so fix the names in the source file and run it again
- `local.config_dir_exposed: true` in `doctor` means the directory is open to other users, and only the operator can fix its mode
## Exit codes
- `0`: success
- `1`: runtime failure, remote command failure or partial multi-host failure
- `2`: clap usage error, such as conflicting flags or an invalid value
- `64`: usage error detected by the product
- `65`: data error in TOML, JSON, config or schema
- `66`: VPS, active marker or file not found
- `69`: service unavailable, such as the config lock still busy after `--lock-wait-ms` or a locked keyring
- `70`: internal software error
- `73`: cannot create config output
- `74`: IO error, SSH connection error or timeout
- `77`: authentication rejected, host key changed or sudo policy refused
- `130`: SIGINT
- `141`: broken pipe on stdout or stderr
- `143`: SIGTERM
- `ssh-cli --help` ends with an `Exit status:` section that lists these 14 codes
- All 48 leaf commands carry an `Examples:` block in their `--help`, and `tests/docs_conformance.rs` runs every published argv with `--help` appended to prove it parses
## Contributing
- Read [CONTRIBUTING](https://github.com/danilo-aguiar-br/ssh-cli/blob/main/CONTRIBUTING.md) before a pull request
- Run `python3 scripts/check_all_gates.py` to run the whole gate battery in one invocation
- Never reintroduce a long-lived daemon, telemetry or an environment variable as configuration
- Never print the master key or a live SSH password
## Release notes
- 0.5.6 reduces a single record with `--select` and `--truncate-content`, reports a bare-array reduction on stderr as `{"event":"agent-shape",...}`, names ignored collection flags under `agent_shape.ignored`, and shows an `Examples:` block on all 48 leaves
- 0.5.6 adds `vps rename <NAME> <NEW_NAME>` and `vps edit <NAME> --name <NEW>`, which rename natively and keep every credential readable under the new name
- 0.5.6 adds eight clear flags to `vps edit`: `--clear-tags`, `--clear-sudo-password`, `--clear-su-password`, `--no-tls-sni`, `--no-tls-client-cert`, `--no-tls-client-key`, `--no-key` and `--no-agent-socket`
- 0.5.6 extends `--dry-run` to `vps edit` and `vps rename`, bounds the config lock with `--lock-wait-ms` into exit 69, and makes `commands --json` publish every argument
- 0.5.6 closes the gaps found by the end-to-end audit and the independent reviews: strict host names, escaped terminal output, private config directories, auth switching and a clean package
- 0.5.6 is BREAKING for scripts, because `vps edit` now switches the auth method where it used to exit 64, and the release scripts became Python programs
- 0.5.6 is BREAKING for library users: the `Money` module and the `rust_decimal` dependency are gone and `SshCliError::Unavailable` carries `UnavailableService`
- 0.5.5 fixes GAP-SSH-EXEC-ARGC-001, where the exec family chose the target host by positional count, and it is BREAKING because a lone positional is now a usage error
- 0.5.5 adds `--use-active` with `<COMMAND>` plus `target_resolved`, `target_source` and `active_fallback` on every envelope, and `doctor` reports `local.active_vps`
- 0.5.4 fixes a remote pre-auth DoS in the SSH banner path (A1), stops server-sent setuid bits on downloads (A3) and closes the world-readable window on ACME and mTLS private keys (A2)
- 0.5.4 adds `tunnel --reverse`, `tunnel --socks5`, `tunnel --remote-socket`, the `tunnel_closed` event and the eight payload-shaping flags
- 0.5.4 is BREAKING because partial multi-host failure exits 1 (it was 65) and a non-loopback `--bind` requires `--i-accept-network-exposure`
## Optional
- Earlier releases are listed below
- 0.5.3 closed G1 to G19: SFTP integrity, graduated crate-scoped verbosity and batch cancel cardinality
- 0.5.2 closed the residual G-E2E items and added wire schema v3 with dual read
- 0.5.0 renamed the API to English and added the force-init reencrypt of secrets
- 0.4.2 closed TUN-003 and IO-010
- 0.4.1 closed AUD-POST: honest empty-secret export, tunnel deadline, auth parity and the `scp-transfer` event with `--password-stdin`
- Crates.io 0.3.9 shipped a broken SCP wire, so always install 0.5.3 or newer