@clawops/cli 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +122 -2
  2. package/dist/agent-activity-25FAWLAI.js +16 -0
  3. package/dist/{apply-HJ5GAKHW.js → apply-F5SQ2ZOS.js} +12 -12
  4. package/dist/{automation-MKCJ46I6.js → automation-NBYQ6MFZ.js} +1 -1
  5. package/dist/backup-flows-NDP5JTR3.js +29 -0
  6. package/dist/{bootstrap-NN647RCY.js → bootstrap-32TZXVDF.js} +5 -5
  7. package/dist/bootstrap.sh.tmpl +4 -0
  8. package/dist/chunk-27335SIH.js +51 -0
  9. package/dist/{chunk-X56CQ6GB.js → chunk-4MHDW2NB.js} +1 -1
  10. package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
  11. package/dist/{chunk-3Z5TDXCJ.js → chunk-55UMO37O.js} +1 -1
  12. package/dist/{chunk-IQQTB7ER.js → chunk-66R52R7K.js} +10 -14
  13. package/dist/chunk-755X5OGS.js +213 -0
  14. package/dist/{chunk-QDNFADJI.js → chunk-AVERNLUY.js} +6 -6
  15. package/dist/{chunk-R3DTA35B.js → chunk-AX65JMDZ.js} +20 -20
  16. package/dist/{chunk-T5XCMYAV.js → chunk-CQOYUWR7.js} +766 -210
  17. package/dist/chunk-E6BRQK67.js +289 -0
  18. package/dist/{chunk-64PONSA5.js → chunk-GFIR2IIJ.js} +3 -1
  19. package/dist/chunk-LV7UO5CG.js +82 -0
  20. package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
  21. package/dist/{chunk-GFUK6KDR.js → chunk-OUQVKIIA.js} +372 -34
  22. package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
  23. package/dist/chunk-PCSX6Y3U.js +297 -0
  24. package/dist/chunk-POYS3EVQ.js +14 -0
  25. package/dist/{chunk-ELU3XQEA.js → chunk-RDUKRIRC.js} +1 -1
  26. package/dist/chunk-SX75L2T3.js +46 -0
  27. package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
  28. package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
  29. package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
  30. package/dist/{chunk-BELTQGHT.js → chunk-YPVWF7IA.js} +4 -4
  31. package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
  32. package/dist/cli.js +383 -925
  33. package/dist/conn-M2V5HBPJ.js +9 -0
  34. package/dist/{context-7VI3R6EK.js → context-EHSLWKSH.js} +6 -6
  35. package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
  36. package/dist/{flows-RI7FGHAM.js → flows-6B2I6TRW.js} +7 -7
  37. package/dist/{generate-Q2BGVMH7.js → generate-4YYNW3IU.js} +9 -8
  38. package/dist/{harden-3KKCR7BL.js → harden-UHAOVQMZ.js} +5 -5
  39. package/dist/init-HOPXDNWY.js +13 -0
  40. package/dist/{mcp-wire-FQOMM7MK.js → mcp-wire-BXF4DJFT.js} +26 -19
  41. package/dist/migrate-flow-24XA4GZL.js +25 -0
  42. package/dist/{package-JFVGCNKU.js → package-HYGHRX35.js} +3 -1
  43. package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
  44. package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
  45. package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
  46. package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
  47. package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
  48. package/dist/server-E3YA32ND.js +53 -0
  49. package/dist/ssh-HOM7GF3M.js +13 -0
  50. package/dist/{stack-config-LUTS2XAD.js → stack-config-ICQ5YHUL.js} +2 -2
  51. package/dist/stack-delete-NTIGKWUF.js +13 -0
  52. package/dist/{state-AOAYLYY5.js → state-AKZP2S4T.js} +2 -2
  53. package/dist/{store-OVB3DAJR.js → store-YOLQYXMB.js} +1 -1
  54. package/dist/{tailscale-cutover-6YP2QKFM.js → tailscale-cutover-V42HO5UM.js} +3 -3
  55. package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
  56. package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
  57. package/package.json +3 -1
  58. package/spec/mcp-tools.yaml +282 -35
  59. package/dist/logs-5A7YYA53.js +0 -15
  60. package/dist/server-B7I6HBQH.js +0 -46
  61. package/dist/ssh-VAI5DU5M.js +0 -11
  62. package/dist/upgrade-ULD4KFYC.js +0 -88
  63. /package/dist/{chunk-NUL5F6YV.js → chunk-SOZ62ZXW.js} +0 -0
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@clawops/cli)](https://www.npmjs.com/package/@clawops/cli)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@clawops/cli)](https://www.npmjs.com/package/@clawops/cli)
5
+ [![clawops MCP server – quality and maintenance score on Glama](https://glama.ai/mcp/servers/dfridkin/clawops/badges/score.svg)](https://glama.ai/mcp/servers/dfridkin/clawops)
6
+ [![Listed on mcpservers.org](https://mcpservers.org/badge.svg)](https://mcpservers.org/servers/dfridkin/clawops)
5
7
 
6
8
  MCP-native infrastructure ops for OpenClaw, with read-only mode, destructive-action confirmation, and audit logs built in.
7
9
 
@@ -192,6 +194,27 @@ Either way, pass the arguments. `mcp serve` is what speaks the protocol, and an
192
194
  is one that still reads clearly a year later. clawops does not strand a client that omits them:
193
195
  run with no command at all and a pipe on stdin — how every MCP client starts a server — and it
194
196
  starts `mcp serve`, saying so on stderr. Typed at a terminal, `clawops` still prints help.
197
+
198
+ **Pass cloud credentials in `env`.** A client starts the server with the environment its config
199
+ gives it, not your shell's, so an `AWS_PROFILE` exported in a terminal does not reach it, and
200
+ every tool that reads a stack fails with "Could not read the state of stack". Name the profile,
201
+ or the variables your state backend needs, in the server's `env` block:
202
+
203
+ ```json
204
+ {
205
+ "mcpServers": {
206
+ "clawops": {
207
+ "command": "npx",
208
+ "args": ["-y", "@clawops/cli", "mcp", "serve"],
209
+ "env": { "AWS_PROFILE": "my-profile" }
210
+ }
211
+ }
212
+ }
213
+ ```
214
+
215
+ It holds a profile name, not a key: the credentials themselves stay where the cloud CLI keeps
216
+ them (R6).
217
+
195
218
  Config file locations:
196
219
 
197
220
  | App | Path |
@@ -253,7 +276,7 @@ clawops down --yes # Destroy local-provider stack
253
276
  | `config` | Get/set remote OpenClaw config values (`--dry-run` shows would-write JSON) |
254
277
  | `agents` | List OpenClaw agents, or stream one agent's logs |
255
278
  | `gateway` | Restart the OpenClaw gateway service |
256
- | `backup` | Create and restore OpenClaw state backups (`restore` expands into a staging directory, never in place) |
279
+ | `backup` | Create and restore OpenClaw state backups (`restore` expands into a staging directory, never in place; `--activate` swaps it in and rolls back if the gateway does not come up) |
257
280
  | `stacks` | List named stacks and their state |
258
281
  | `doctor` | Check the local machine; with `--stack`, the deployment's health too; with `--provider`, one cloud's credentials and account setup whether or not a stack exists; with `--instance-type`, account checks ask about that size rather than the provider default. `--json` for the report. Exits 1 on any failure |
259
282
  | `secret` | Manage secrets: `list`, `set`, `delete`, `rotate`, `audit` |
@@ -353,6 +376,10 @@ Do not bind to a non-loopback address without additional authentication controls
353
376
  | `clawops_stacks_list` | admin | List all stacks and their state |
354
377
  | `clawops_config_get` | cli | Read a remote config value |
355
378
  | `clawops_agents_list` | cli | List running agents |
379
+ | `clawops_agents_logs` | cli | One agent's recent activity, paginated |
380
+ | `clawops_gateway_status` | cli | Whether the gateway container is running, and its image |
381
+ | `clawops_secret_list` | cli | List stored secrets by name and status (never values) |
382
+ | `clawops_secret_audit` | cli | Report missing secrets and unresolvable `$secret:` references |
356
383
  | `clawops_up` | cli | Provision or update a stack |
357
384
  | `clawops_destroy` | cli | Destroy a stack (elicits confirmation) |
358
385
  | `clawops_apply` | cli | Apply a plan file |
@@ -361,7 +388,14 @@ Do not bind to a non-loopback address without additional authentication controls
361
388
  | `clawops_config_unset` | cli | Remove a remote config key |
362
389
  | `clawops_config_validate` | cli | Validate the deployed config against the OpenClaw schema |
363
390
  | `clawops_gateway_restart` | cli | Restart the gateway (elicits confirmation) |
391
+ | `clawops_gateway_update` | cli | Move the gateway to a concrete OpenClaw version (elicits confirmation) |
392
+ | `clawops_backup_create` | cli | Back up gateway state to a local archive |
393
+ | `clawops_backup_restore` | cli | Restore a backup to staging, or with `activate` into service (elicits confirmation) |
394
+ | `clawops_migrate` | cli | Migrate a 1.x deployment onto the 2.0 runtime (elicits confirmation) |
395
+ | `clawops_stacks_delete` | admin | Forget a stack in config; does not destroy infrastructure (elicits confirmation) |
396
+ | `clawops_secret_delete` | cli | Delete a stored secret (elicits confirmation) |
364
397
  | `clawops_harden` | cli | Apply hardening modules; join or leave a tailnet (elicits confirmation) |
398
+ | `clawops_init` | cli | Register a stack and write `~/.clawops/config.json` (no cloud resources) |
365
399
  | `clawops_workflow_deploy_app` | workflow | End-to-end deploy: plan → confirm → apply → status |
366
400
  | `clawops_workflow_recover` | workflow | Diagnostic workflow for an unhealthy stack |
367
401
  | `clawops_task_status` | cli | Poll a long-running task |
@@ -542,9 +576,10 @@ pnpm dev doctor # verify toolchain
542
576
  ```bash
543
577
  pnpm dev # run CLI from src/ via tsx
544
578
  pnpm build # tsup → dist/
545
- pnpm test # vitest (1897 tests, ~13s)
579
+ pnpm test # vitest (2090 tests, ~13s)
546
580
  pnpm test:changed # vitest --changed (fast edit loop)
547
581
  pnpm test:integration # Docker-based SSH integration tests
582
+ pnpm test:e2e:local # builds, then bootstrap, the host-touching MCP tools and a 1.x migration, in systemd containers
548
583
  pnpm typecheck # tsc --noEmit
549
584
  pnpm lint # eslint src/ tests/ scripts/ (--max-warnings=0)
550
585
  pnpm gen:schemas # regenerate src/providers/types.ts + src/mcp/tools/_generated.ts
@@ -600,6 +635,80 @@ Use `pnpm changeset` to record a release note before merging a `feat` or `fix`.
600
635
 
601
636
  ---
602
637
 
638
+ ## What's new in 2.2
639
+
640
+ Recovery is a flag, agents can reach every command they need, and SSH failures say what failed.
641
+
642
+ ### Restore puts a backup into service
643
+
644
+ - `clawops backup restore --file <archive> --activate` swaps the restored state in, restarts
645
+ the gateway on it, and checks that it answers. Without `--activate`, a restore still
646
+ verifies and expands into a staging directory and touches nothing live.
647
+ - The state it replaces is kept, not deleted. If the gateway does not come up, clawops puts the
648
+ old state back and restarts again; the state that failed is kept as
649
+ `.failed-restore-<timestamp>`, as evidence.
650
+ - The restored state lands on the host, beside the directory it replaces. Before, it stayed in
651
+ the container's `/tmp`, where the next `gateway restart` destroyed it.
652
+ - Free space is checked before an archive is expanded.
653
+ - `clawops backup --help` no longer says restore is unavailable. It has worked since 2.0.
654
+
655
+ ### Every command an agent needs is an MCP tool
656
+
657
+ - Ten new tools bring the catalog to 30: backup create and restore (with `activate`), gateway
658
+ status and update, one agent's logs, migrate, stacks delete, and secret list, audit and
659
+ delete. Each calls the same code as its command, so both refuse the same things in the same
660
+ words.
661
+ - `clawops ssh` and `clawops secret set` / `rotate` stay CLI-only on purpose: a root shell or a
662
+ secret value does not belong in an agent's hands or its transcript. The secret tools never
663
+ return values.
664
+
665
+ ### SSH failures name the cause
666
+
667
+ - A timeout says nothing came back, which on a clawops stack usually means the security group no
668
+ longer admits your address. The message prints the re-plan and apply that fix it.
669
+ - A refused connection says the host is up and sshd is not. A rejected key names the key clawops
670
+ offered. A name that does not resolve, and a handshake with no algorithm in common, each say
671
+ what they are. ssh2's own words are kept in every message.
672
+ - `clawops doctor --stack <name>` prints the same advice on its own line.
673
+
674
+ ### Fixes found by running every tool against a real host
675
+
676
+ The new tools were driven end to end over MCP against a systemd host, a 1.x deployment made by
677
+ clawops 1.7.9, and AWS. That found defects the unit tests could not, several in the CLI too:
678
+
679
+ - **`clawops gateway update` works.** Every update since the upgrade gate shipped refused before
680
+ touching anything, because the state snapshot was given a host path inside a container that
681
+ cannot create it.
682
+ - **Local-provider hosts no longer race systemd.** The gateway runs there as the `openclaw` unit
683
+ with `Restart=always`, which undid a restart, an update, a migration or a restore about five
684
+ seconds after clawops made it. clawops now changes the gateway through the unit.
685
+ - **`clawops gateway`, `agents`, `config` and `logs` work on local stacks.** Each failed with "The
686
+ local provider does not use Pulumi stacks" while its MCP tool worked.
687
+ - **`clawops migrate` refuses a stack already on 2.x**, which it would have given a fresh minimal
688
+ config; **asks before it runs**, as `--yes` always claimed; and **pulls the 2.x image before
689
+ stopping 1.x**, so the downtime is a restart rather than a multi-gigabyte download.
690
+ - **A secret name can no longer reach outside the secrets directory.** `clawops secret delete
691
+ ../config.json` deleted the clawops config.
692
+ - **`clawops_gateway_restart` keeps a gateway published on every interface** where it was,
693
+ instead of moving it to loopback.
694
+ - **A stack whose state cannot be read says why**, instead of Pulumi's `code: -2`, and names the
695
+ usual cause for an MCP server: credentials missing from its client config's `env` block.
696
+
697
+ ### Other fixes
698
+
699
+ - Streaming Docker commands, such as a backup upload or a log follow, no longer reuse a sudo
700
+ decision learned from an unrelated command. On AWS, where the login user is not in the `docker`
701
+ group, a backup upload failed with "permission denied" after the backup had been taken.
702
+ - The backup and upgrade-rollback guides describe `--activate` instead of a manual procedure, and
703
+ CI follows every in-repo Markdown link and anchor (`pnpm verify:docs`).
704
+ - The local provider's bootstrap runs for real in its e2e suite, against a container with systemd
705
+ as PID 1, instead of being mocked. The same suite now drives the host-touching MCP tools and a
706
+ 1.x migration (`pnpm test:e2e:local`, nightly and on PRs labelled `e2e`), and
707
+ `E2E_MCP=1 pnpm test:cloud <cloud>` backs up and restores over MCP on a real cloud host.
708
+ - clawops.fyi carries structured data, an `/llms.txt`, a FAQ and a comparison page.
709
+
710
+ ---
711
+
603
712
  ## What's new in 2.1
604
713
 
605
714
  Private networking, hardening on every cloud, and two fixes to commands that could not start.
@@ -665,6 +774,17 @@ Private networking, hardening on every cloud, and two fixes to commands that cou
665
774
  does not create its destination. `pnpm verify:docker` builds it and speaks MCP to the running
666
775
  container, both through its entrypoint and as a bare binary, in CI.
667
776
 
777
+ ### 2.1.2 and 2.1.3
778
+
779
+ - Every MCP tool sends its description, and every one of its parameters documents itself. Clients
780
+ had been seeing 19 tools with a name, a schema and no description.
781
+ - `clawops_init` registers a stack over MCP, so a client can bootstrap from nothing. It writes the
782
+ config and an SSH key and provisions nothing.
783
+ - A client that cannot show a confirmation is told to ask the user and call again with
784
+ `yes: true`, instead of getting a `Client does not support form elicitation` crash.
785
+ - The Docker image carries `ssh-keygen` and a CA bundle; without them it could not make a key or
786
+ reach any HTTPS endpoint.
787
+
668
788
  ---
669
789
 
670
790
  ## What's new in 2.0.1
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_AGENT_LOG_LIMIT,
4
+ parseAgentActivity,
5
+ readAgentActivity,
6
+ readAgentActivityRaw
7
+ } from "./chunk-27335SIH.js";
8
+ import "./chunk-SOZ62ZXW.js";
9
+ import "./chunk-WPZIJONN.js";
10
+ import "./chunk-EI7CQQ7D.js";
11
+ export {
12
+ DEFAULT_AGENT_LOG_LIMIT,
13
+ parseAgentActivity,
14
+ readAgentActivity,
15
+ readAgentActivityRaw
16
+ };
@@ -1,27 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-R3DTA35B.js";
4
+ } from "./chunk-AX65JMDZ.js";
5
5
  import "./chunk-BOPSG2LI.js";
6
- import "./chunk-6ZFIFDBJ.js";
7
- import "./chunk-2I7KCMXZ.js";
8
- import "./chunk-3Z5TDXCJ.js";
6
+ import "./chunk-UAJJEDCJ.js";
7
+ import "./chunk-PASAVVNS.js";
8
+ import "./chunk-55UMO37O.js";
9
9
  import "./chunk-AEEMRFZV.js";
10
- import "./chunk-XFN3YDZE.js";
11
- import "./chunk-QDNFADJI.js";
12
- import "./chunk-NUDIHZEH.js";
10
+ import "./chunk-AVERNLUY.js";
11
+ import "./chunk-6ZFIFDBJ.js";
12
+ import "./chunk-WPZIJONN.js";
13
+ import "./chunk-YPVWF7IA.js";
13
14
  import "./chunk-JW5A7E3X.js";
14
- import "./chunk-BELTQGHT.js";
15
- import "./chunk-GIEVAGEK.js";
15
+ import "./chunk-VUFMZEIM.js";
16
+ import "./chunk-N5ZYUTZS.js";
16
17
  import "./chunk-VOEJYRIK.js";
17
18
  import "./chunk-EI7CQQ7D.js";
18
19
  import "./chunk-FEK7VJAY.js";
19
20
  import "./chunk-RHOWYIWT.js";
20
21
  import "./chunk-N4WZLWJ4.js";
21
- import "./chunk-ELU3XQEA.js";
22
- import "./chunk-64PONSA5.js";
22
+ import "./chunk-RDUKRIRC.js";
23
+ import "./chunk-GFIR2IIJ.js";
23
24
  import "./chunk-KGXPLI7W.js";
24
- import "./chunk-VUFMZEIM.js";
25
25
  export {
26
26
  applyPlan
27
27
  };
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-V25QRZRB.js";
8
8
  import {
9
9
  getConfigDir
10
- } from "./chunk-64PONSA5.js";
10
+ } from "./chunk-GFIR2IIJ.js";
11
11
  import "./chunk-KGXPLI7W.js";
12
12
 
13
13
  // src/pulumi/automation.ts
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ACTIVATED,
4
+ ARCHIVE_IS_A_CREDENTIAL,
5
+ PLUGINS_NOT_CARRIED,
6
+ activateStaged,
7
+ activatedLines,
8
+ activationFailureDetail,
9
+ createBackup,
10
+ defaultBackupFilename,
11
+ nothingChanged,
12
+ stageRestore
13
+ } from "./chunk-PCSX6Y3U.js";
14
+ import "./chunk-WPZIJONN.js";
15
+ import "./chunk-N5ZYUTZS.js";
16
+ import "./chunk-VOEJYRIK.js";
17
+ import "./chunk-EI7CQQ7D.js";
18
+ export {
19
+ ACTIVATED,
20
+ ARCHIVE_IS_A_CREDENTIAL,
21
+ PLUGINS_NOT_CARRIED,
22
+ activateStaged,
23
+ activatedLines,
24
+ activationFailureDetail,
25
+ createBackup,
26
+ defaultBackupFilename,
27
+ nothingChanged,
28
+ stageRestore
29
+ };
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  acquireSession
4
- } from "./chunk-2Z7J3ERQ.js";
5
- import "./chunk-E4NJSJ54.js";
4
+ } from "./chunk-TYNMOYVM.js";
5
+ import "./chunk-4PY6PN4G.js";
6
6
  import "./chunk-MC5ATCXS.js";
7
7
  import {
8
8
  gatewayRunArgs,
9
9
  gatewayRunCommand
10
- } from "./chunk-GIEVAGEK.js";
10
+ } from "./chunk-N5ZYUTZS.js";
11
11
  import {
12
12
  GATEWAY_PORT
13
13
  } from "./chunk-VOEJYRIK.js";
14
14
  import "./chunk-EI7CQQ7D.js";
15
15
  import {
16
16
  writeLocalState
17
- } from "./chunk-ELU3XQEA.js";
18
- import "./chunk-64PONSA5.js";
17
+ } from "./chunk-RDUKRIRC.js";
18
+ import "./chunk-GFIR2IIJ.js";
19
19
  import {
20
20
  ProviderError
21
21
  } from "./chunk-KGXPLI7W.js";
@@ -211,6 +211,10 @@ ExecStop=/usr/bin/docker stop openclaw
211
211
  [Install]
212
212
  WantedBy=multi-user.target
213
213
  UNIT
214
+ # A restart, update or migration since the last bootstrap pins its run command in a drop-in
215
+ # (see gatewayRunCommand). This unit is the new source of truth; a stale drop-in would
216
+ # override its ExecStart and quietly keep the old image.
217
+ rm -f /etc/systemd/system/openclaw.service.d/clawops-run.conf
214
218
  systemctl daemon-reload
215
219
  systemctl enable openclaw
216
220
  systemctl restart openclaw
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ agentAuditCommand
4
+ } from "./chunk-SOZ62ZXW.js";
5
+ import {
6
+ execPrivileged
7
+ } from "./chunk-WPZIJONN.js";
8
+ import {
9
+ err,
10
+ ok
11
+ } from "./chunk-EI7CQQ7D.js";
12
+
13
+ // src/openclaw/agent-activity.ts
14
+ var DEFAULT_AGENT_LOG_LIMIT = 50;
15
+ async function readAgentActivityRaw(session, opts, signal) {
16
+ const result = await execPrivileged(
17
+ session,
18
+ agentAuditCommand({
19
+ agentId: opts.agentId,
20
+ limit: opts.limit ?? DEFAULT_AGENT_LOG_LIMIT,
21
+ ...opts.cursor ? { cursor: opts.cursor } : {}
22
+ }),
23
+ signal
24
+ );
25
+ if (result.code !== 0) {
26
+ const why = result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`;
27
+ return err(`Cannot read activity for "${opts.agentId}": ${why}`);
28
+ }
29
+ return ok(result.stdout);
30
+ }
31
+ function parseAgentActivity(agentId, stdout) {
32
+ let page;
33
+ try {
34
+ page = JSON.parse(stdout.trim() || "{}");
35
+ } catch {
36
+ return err(`Cannot read activity for "${agentId}": unexpected output: ${stdout.trim().slice(0, 200)}`);
37
+ }
38
+ return ok({ records: page.records ?? [], ...page.cursor ? { cursor: page.cursor } : {} });
39
+ }
40
+ async function readAgentActivity(session, opts, signal) {
41
+ const raw = await readAgentActivityRaw(session, opts, signal);
42
+ if (!raw.ok) return raw;
43
+ return parseAgentActivity(opts.agentId, raw.value);
44
+ }
45
+
46
+ export {
47
+ DEFAULT_AGENT_LOG_LIMIT,
48
+ readAgentActivityRaw,
49
+ parseAgentActivity,
50
+ readAgentActivity
51
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getConfigDir
4
- } from "./chunk-64PONSA5.js";
4
+ } from "./chunk-GFIR2IIJ.js";
5
5
 
6
6
  // src/config/secrets.ts
7
7
  import { readFileSync, existsSync } from "fs";
@@ -153,14 +153,57 @@ var Ssh2Session = class {
153
153
  this.client.end();
154
154
  }
155
155
  };
156
- function describeConnectError(message, opts) {
157
- if (!/host.*(denied|verification)/i.test(message)) {
158
- return `SSH connection failed: ${message}`;
156
+ function diagnoseConnectError(message, opts) {
157
+ if (/host.*(denied|verification)/i.test(message)) {
158
+ const entry = opts.port === 22 ? opts.host : `[${opts.host}]:${opts.port}`;
159
+ return {
160
+ summary: `the host key for ${opts.host}:${opts.port} does not match the one recorded in ${opts.knownHostsPath}. If your cloud reassigned this address \u2014 the usual cause \u2014 drop the stale entry and retry:`,
161
+ remedy: ` ssh-keygen -R ${entry} -f ${opts.knownHostsPath}
162
+ If you did not expect this address to change hands, do not connect.`
163
+ };
164
+ }
165
+ if (opts.awaitingBoot) return { summary: `SSH connection failed: ${message}` };
166
+ if (message.includes("ECONNREFUSED")) {
167
+ return {
168
+ summary: `${opts.host}:${opts.port} refused the connection \u2014 the host is reachable and nothing is listening on that port (${message}).`,
169
+ remedy: "The instance is up and sshd is not. Check the instance state with `clawops status --stack <name>`, and that the port matches the one the stack was deployed with."
170
+ };
171
+ }
172
+ if (/ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|Timed out while waiting for handshake/.test(message)) {
173
+ return {
174
+ // ssh2's readyTimeout (30s) usually fires before the kernel gives up on the TCP connect,
175
+ // so a dropping firewall is reported as a handshake timeout rather than ETIMEDOUT. Both
176
+ // mean the same thing to the operator: nothing came back.
177
+ summary: `${opts.host}:${opts.port} never answered \u2014 nothing came back at all, which is what a firewall dropping the packets looks like (${message}).`,
178
+ remedy: "On a clawops stack the usual cause is that the security group no longer admits this machine: `--ssh-cidr auto` pins the address you planned from, and home, office and VPN addresses change. Re-plan from where you are now and apply:\n clawops plan --stack <name> --ssh-cidr auto --out <plan>.json\n clawops apply <plan>.json\nIf the address has not changed, the instance may be stopped or still booting."
179
+ };
180
+ }
181
+ if (message.includes("ENOTFOUND")) {
182
+ return {
183
+ summary: `${opts.host} does not resolve (${message}).`,
184
+ remedy: "Check the address recorded for this stack with `clawops stacks list`. An instance replaced outside clawops keeps the old address in `~/.clawops/config.json`."
185
+ };
186
+ }
187
+ if (message.includes("All configured authentication methods failed")) {
188
+ const key = opts.privateKeyPath;
189
+ const asUser = opts.user ? ` as ${opts.user}` : "";
190
+ return {
191
+ summary: `${opts.host}:${opts.port} rejected every authentication method clawops offered${asUser} (${message}).`,
192
+ remedy: key ? `clawops offered the key at ${key}. Its public half may not be installed on the host \u2014 an instance rebuilt outside clawops, or a key regenerated since this stack was created. Compare what clawops offers, \`ssh-keygen -y -f ${key}\`, against the authorized_keys on the host.` : "The key clawops offered may not be installed on the host \u2014 an instance rebuilt outside clawops, or a key regenerated since this stack was created."
193
+ };
159
194
  }
160
- const entry = opts.port === 22 ? opts.host : `[${opts.host}]:${opts.port}`;
161
- return `the host key for ${opts.host}:${opts.port} does not match the one recorded in ${opts.knownHostsPath}. If your cloud reassigned this address \u2014 the usual cause \u2014 drop the stale entry and retry:
162
- ssh-keygen -R ${entry} -f ${opts.knownHostsPath}
163
- If you did not expect this address to change hands, do not connect.`;
195
+ if (/no matching|Handshake failed/i.test(message)) {
196
+ return {
197
+ summary: `no algorithm the SSH handshake needs is accepted by both ends (${message}).`,
198
+ remedy: "The host is running an OpenSSH older than the algorithms ssh2 offers. clawops does not deploy hosts that old, so this is usually a pre-existing machine registered with the local provider; upgrading its OpenSSH is the fix."
199
+ };
200
+ }
201
+ return { summary: `SSH connection failed: ${message}` };
202
+ }
203
+ function describeConnectError(message, opts) {
204
+ const { summary, remedy } = diagnoseConnectError(message, opts);
205
+ return remedy ? `${summary}
206
+ ${remedy}` : summary;
164
207
  }
165
208
  async function connect(opts) {
166
209
  let privateKey;
@@ -221,6 +264,7 @@ function verifyHostKey(host, port, keyHash, knownHostsPath) {
221
264
  }
222
265
 
223
266
  export {
267
+ diagnoseConnectError,
224
268
  describeConnectError,
225
269
  connect
226
270
  };
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VOEJYRIK.js";
8
8
  import {
9
9
  getConfig
10
- } from "./chunk-64PONSA5.js";
10
+ } from "./chunk-GFIR2IIJ.js";
11
11
  import {
12
12
  UsageError
13
13
  } from "./chunk-KGXPLI7W.js";
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ pulumiCause
4
+ } from "./chunk-POYS3EVQ.js";
2
5
  import {
3
6
  validatePlan
4
- } from "./chunk-XFN3YDZE.js";
7
+ } from "./chunk-PASAVVNS.js";
5
8
  import {
6
9
  buildContext,
7
10
  loadAdapterModule
8
- } from "./chunk-QDNFADJI.js";
11
+ } from "./chunk-AVERNLUY.js";
9
12
  import {
10
13
  getConfig
11
- } from "./chunk-64PONSA5.js";
14
+ } from "./chunk-GFIR2IIJ.js";
12
15
  import {
13
16
  StateError,
14
17
  UsageError
@@ -71,7 +74,7 @@ async function generatePlan(intent, _opts) {
71
74
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
72
75
  );
73
76
  }
74
- const { version } = await import("./package-JFVGCNKU.js");
77
+ const { version } = await import("./package-HYGHRX35.js");
75
78
  const config = getConfig();
76
79
  const instanceType = await resolveInstanceType(intent);
77
80
  const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
@@ -118,12 +121,12 @@ async function generatePlan(intent, _opts) {
118
121
  } catch (err) {
119
122
  if (err instanceof UsageError) throw err;
120
123
  throw new StateError(
121
- `Cannot open the state backend for stack "${stackName}": ${messageOf(err)}
124
+ `Cannot open the state backend for stack "${stackName}": ${pulumiCause(err)}
122
125
  A plan is only as good as the state it was computed against, so this is not a plan clawops will write. Check the stateUrl in ~/.clawops/config.json, and that the bucket or container exists and your credentials can read it.`
123
126
  );
124
127
  }
125
128
  try {
126
- const { writeStackConfig } = await import("./stack-config-LUTS2XAD.js");
129
+ const { writeStackConfig } = await import("./stack-config-ICQ5YHUL.js");
127
130
  await writeStackConfig(stack, plan);
128
131
  const outputLines = [];
129
132
  const preview = await stack.preview({
@@ -151,7 +154,7 @@ A plan is only as good as the state it was computed against, so this is not a pl
151
154
  throw new Error(`Generated plan failed schema validation:
152
155
  ${validation.errors.join("\n")}`);
153
156
  }
154
- const { validatePlanConfig, validatePlanNetwork } = await import("./validate-JXKN5WD6.js");
157
+ const { validatePlanConfig, validatePlanNetwork } = await import("./validate-DZE6LFEB.js");
155
158
  const cfgCheck = await validatePlanConfig(plan);
156
159
  for (const w of cfgCheck.warnings) process.stderr.write(`[clawops] warning: ${w}
157
160
  `);
@@ -179,13 +182,6 @@ function planId() {
179
182
  function expandHome(p) {
180
183
  return p.replace(/^~/, process.env["HOME"] ?? "~");
181
184
  }
182
- function messageOf(err) {
183
- const raw = err instanceof Error ? err.message : String(err);
184
- const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l !== "");
185
- const explained = lines.find((l) => /(^|\s)error:/i.test(l));
186
- if (explained) return explained.replace(/^.*?error:\s*/i, "");
187
- return lines[0] ?? raw;
188
- }
189
185
 
190
186
  export {
191
187
  INSTANCE_ALIASES,