@clawops/cli 2.1.3 → 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.
- package/README.md +120 -2
- package/dist/agent-activity-25FAWLAI.js +16 -0
- package/dist/{apply-JARPVA5Y.js → apply-F5SQ2ZOS.js} +7 -7
- package/dist/backup-flows-NDP5JTR3.js +29 -0
- package/dist/{bootstrap-3PBVY7MA.js → bootstrap-32TZXVDF.js} +3 -3
- package/dist/bootstrap.sh.tmpl +4 -0
- package/dist/chunk-27335SIH.js +51 -0
- package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
- package/dist/{chunk-PTCVFLZ3.js → chunk-66R52R7K.js} +7 -11
- package/dist/{chunk-7T4CIT7C.js → chunk-AX65JMDZ.js} +15 -15
- package/dist/{chunk-TYHTISI5.js → chunk-CQOYUWR7.js} +610 -104
- package/dist/chunk-E6BRQK67.js +289 -0
- package/dist/chunk-LV7UO5CG.js +82 -0
- package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
- package/dist/{chunk-CPCO6UJK.js → chunk-OUQVKIIA.js} +368 -30
- package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
- package/dist/chunk-PCSX6Y3U.js +297 -0
- package/dist/chunk-POYS3EVQ.js +14 -0
- package/dist/chunk-SX75L2T3.js +46 -0
- package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
- package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
- package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
- package/dist/{chunk-CHNSL3TC.js → chunk-YPVWF7IA.js} +1 -1
- package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
- package/dist/cli.js +303 -698
- package/dist/conn-M2V5HBPJ.js +9 -0
- package/dist/{context-WOKSKAIA.js → context-EHSLWKSH.js} +2 -2
- package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
- package/dist/{flows-HGJIA6EL.js → flows-6B2I6TRW.js} +5 -5
- package/dist/{generate-NE54BATS.js → generate-4YYNW3IU.js} +5 -4
- package/dist/{harden-RUOUA5BV.js → harden-UHAOVQMZ.js} +3 -3
- package/dist/{mcp-wire-FIO5DFBY.js → mcp-wire-BXF4DJFT.js} +21 -14
- package/dist/migrate-flow-24XA4GZL.js +25 -0
- package/dist/{package-MF5LPITL.js → package-HYGHRX35.js} +3 -1
- package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
- package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
- package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
- package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
- package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
- package/dist/{server-OXWHBTXK.js → server-E3YA32ND.js} +21 -14
- package/dist/ssh-HOM7GF3M.js +13 -0
- package/dist/stack-delete-NTIGKWUF.js +13 -0
- package/dist/{tailscale-cutover-EKAQSDXP.js → tailscale-cutover-V42HO5UM.js} +1 -1
- package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
- package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
- package/package.json +3 -1
- package/spec/mcp-tools.yaml +213 -7
- package/dist/logs-5A7YYA53.js +0 -15
- package/dist/ssh-VAI5DU5M.js +0 -11
- package/dist/upgrade-ULD4KFYC.js +0 -88
- /package/dist/{chunk-NUL5F6YV.js → chunk-SOZ62ZXW.js} +0 -0
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@clawops/cli)
|
|
4
4
|
[](https://www.npmjs.com/package/@clawops/cli)
|
|
5
5
|
[](https://glama.ai/mcp/servers/dfridkin/clawops)
|
|
6
|
+
[](https://mcpservers.org/servers/dfridkin/clawops)
|
|
6
7
|
|
|
7
8
|
MCP-native infrastructure ops for OpenClaw, with read-only mode, destructive-action confirmation, and audit logs built in.
|
|
8
9
|
|
|
@@ -193,6 +194,27 @@ Either way, pass the arguments. `mcp serve` is what speaks the protocol, and an
|
|
|
193
194
|
is one that still reads clearly a year later. clawops does not strand a client that omits them:
|
|
194
195
|
run with no command at all and a pipe on stdin — how every MCP client starts a server — and it
|
|
195
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
|
+
|
|
196
218
|
Config file locations:
|
|
197
219
|
|
|
198
220
|
| App | Path |
|
|
@@ -254,7 +276,7 @@ clawops down --yes # Destroy local-provider stack
|
|
|
254
276
|
| `config` | Get/set remote OpenClaw config values (`--dry-run` shows would-write JSON) |
|
|
255
277
|
| `agents` | List OpenClaw agents, or stream one agent's logs |
|
|
256
278
|
| `gateway` | Restart the OpenClaw gateway service |
|
|
257
|
-
| `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) |
|
|
258
280
|
| `stacks` | List named stacks and their state |
|
|
259
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 |
|
|
260
282
|
| `secret` | Manage secrets: `list`, `set`, `delete`, `rotate`, `audit` |
|
|
@@ -354,6 +376,10 @@ Do not bind to a non-loopback address without additional authentication controls
|
|
|
354
376
|
| `clawops_stacks_list` | admin | List all stacks and their state |
|
|
355
377
|
| `clawops_config_get` | cli | Read a remote config value |
|
|
356
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 |
|
|
357
383
|
| `clawops_up` | cli | Provision or update a stack |
|
|
358
384
|
| `clawops_destroy` | cli | Destroy a stack (elicits confirmation) |
|
|
359
385
|
| `clawops_apply` | cli | Apply a plan file |
|
|
@@ -362,6 +388,12 @@ Do not bind to a non-loopback address without additional authentication controls
|
|
|
362
388
|
| `clawops_config_unset` | cli | Remove a remote config key |
|
|
363
389
|
| `clawops_config_validate` | cli | Validate the deployed config against the OpenClaw schema |
|
|
364
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) |
|
|
365
397
|
| `clawops_harden` | cli | Apply hardening modules; join or leave a tailnet (elicits confirmation) |
|
|
366
398
|
| `clawops_init` | cli | Register a stack and write `~/.clawops/config.json` (no cloud resources) |
|
|
367
399
|
| `clawops_workflow_deploy_app` | workflow | End-to-end deploy: plan → confirm → apply → status |
|
|
@@ -544,9 +576,10 @@ pnpm dev doctor # verify toolchain
|
|
|
544
576
|
```bash
|
|
545
577
|
pnpm dev # run CLI from src/ via tsx
|
|
546
578
|
pnpm build # tsup → dist/
|
|
547
|
-
pnpm test # vitest (
|
|
579
|
+
pnpm test # vitest (2090 tests, ~13s)
|
|
548
580
|
pnpm test:changed # vitest --changed (fast edit loop)
|
|
549
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
|
|
550
583
|
pnpm typecheck # tsc --noEmit
|
|
551
584
|
pnpm lint # eslint src/ tests/ scripts/ (--max-warnings=0)
|
|
552
585
|
pnpm gen:schemas # regenerate src/providers/types.ts + src/mcp/tools/_generated.ts
|
|
@@ -602,6 +635,80 @@ Use `pnpm changeset` to record a release note before merging a `feat` or `fix`.
|
|
|
602
635
|
|
|
603
636
|
---
|
|
604
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
|
+
|
|
605
712
|
## What's new in 2.1
|
|
606
713
|
|
|
607
714
|
Private networking, hardening on every cloud, and two fixes to commands that could not start.
|
|
@@ -667,6 +774,17 @@ Private networking, hardening on every cloud, and two fixes to commands that cou
|
|
|
667
774
|
does not create its destination. `pnpm verify:docker` builds it and speaks MCP to the running
|
|
668
775
|
container, both through its entrypoint and as a bare binary, in CI.
|
|
669
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
|
+
|
|
670
788
|
---
|
|
671
789
|
|
|
672
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,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
applyPlan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-AX65JMDZ.js";
|
|
5
5
|
import "./chunk-BOPSG2LI.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-XFN3YDZE.js";
|
|
6
|
+
import "./chunk-UAJJEDCJ.js";
|
|
7
|
+
import "./chunk-PASAVVNS.js";
|
|
9
8
|
import "./chunk-55UMO37O.js";
|
|
10
9
|
import "./chunk-AEEMRFZV.js";
|
|
11
10
|
import "./chunk-AVERNLUY.js";
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-6ZFIFDBJ.js";
|
|
12
|
+
import "./chunk-WPZIJONN.js";
|
|
13
|
+
import "./chunk-YPVWF7IA.js";
|
|
14
14
|
import "./chunk-JW5A7E3X.js";
|
|
15
15
|
import "./chunk-VUFMZEIM.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-N5ZYUTZS.js";
|
|
17
17
|
import "./chunk-VOEJYRIK.js";
|
|
18
18
|
import "./chunk-EI7CQQ7D.js";
|
|
19
19
|
import "./chunk-FEK7VJAY.js";
|
|
@@ -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,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
acquireSession
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
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-
|
|
10
|
+
} from "./chunk-N5ZYUTZS.js";
|
|
11
11
|
import {
|
|
12
12
|
GATEWAY_PORT
|
|
13
13
|
} from "./chunk-VOEJYRIK.js";
|
package/dist/bootstrap.sh.tmpl
CHANGED
|
@@ -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
|
+
};
|
|
@@ -153,14 +153,57 @@ var Ssh2Session = class {
|
|
|
153
153
|
this.client.end();
|
|
154
154
|
}
|
|
155
155
|
};
|
|
156
|
-
function
|
|
157
|
-
if (
|
|
158
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
pulumiCause
|
|
4
|
+
} from "./chunk-POYS3EVQ.js";
|
|
2
5
|
import {
|
|
3
6
|
validatePlan
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PASAVVNS.js";
|
|
5
8
|
import {
|
|
6
9
|
buildContext,
|
|
7
10
|
loadAdapterModule
|
|
@@ -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-
|
|
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,7 +121,7 @@ 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}": ${
|
|
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
|
}
|
|
@@ -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-
|
|
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,
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
import {
|
|
3
3
|
resolveSecrets
|
|
4
4
|
} from "./chunk-BOPSG2LI.js";
|
|
5
|
-
import {
|
|
6
|
-
saveOverlay
|
|
7
|
-
} from "./chunk-6ZFIFDBJ.js";
|
|
8
5
|
import {
|
|
9
6
|
atomicWriteConfig,
|
|
10
7
|
deepMerge,
|
|
11
8
|
readRemoteConfig,
|
|
12
9
|
restartGateway
|
|
13
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-UAJJEDCJ.js";
|
|
14
11
|
import {
|
|
15
12
|
validatePlan
|
|
16
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PASAVVNS.js";
|
|
17
14
|
import {
|
|
18
15
|
writeStackConfig
|
|
19
16
|
} from "./chunk-55UMO37O.js";
|
|
20
17
|
import {
|
|
21
18
|
buildContext
|
|
22
19
|
} from "./chunk-AVERNLUY.js";
|
|
20
|
+
import {
|
|
21
|
+
saveOverlay
|
|
22
|
+
} from "./chunk-6ZFIFDBJ.js";
|
|
23
23
|
import {
|
|
24
24
|
GATEWAY_PORT
|
|
25
25
|
} from "./chunk-VOEJYRIK.js";
|
|
@@ -54,7 +54,7 @@ ${validation.errors.join("\n")}`
|
|
|
54
54
|
});
|
|
55
55
|
const stack = await ctx.getStack();
|
|
56
56
|
const { guardPrivateOnlyApply } = await import("./private-only-DIA26I2E.js");
|
|
57
|
-
const { probeSsh } = await import("./tailscale-cutover-
|
|
57
|
+
const { probeSsh } = await import("./tailscale-cutover-V42HO5UM.js");
|
|
58
58
|
await guardPrivateOnlyApply(plan, ctx, opts?.probeTailnet ?? ((conn2) => probeSsh(conn2, opts?.signal)));
|
|
59
59
|
await writeStackConfig(stack, plan);
|
|
60
60
|
if (plan.metadata.stackVersion !== void 0) {
|
|
@@ -84,7 +84,7 @@ The diff you reviewed may no longer reflect what will be applied.
|
|
|
84
84
|
changeSummary[op] = count;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
const { waitForSsh } = await import("./wait-
|
|
87
|
+
const { waitForSsh } = await import("./wait-DOUNNJNN.js");
|
|
88
88
|
const conn = await connectionInfoFor(ctx, outputs);
|
|
89
89
|
if (opts?.skipReadiness) {
|
|
90
90
|
return { outputs, changeSummary, durationMs: Date.now() - start };
|
|
@@ -93,7 +93,7 @@ The diff you reviewed may no longer reflect what will be applied.
|
|
|
93
93
|
signal: opts?.signal,
|
|
94
94
|
onProgress: (line) => reportProgress(opts, line)
|
|
95
95
|
});
|
|
96
|
-
const { waitForGateway } = await import("./ready-
|
|
96
|
+
const { waitForGateway } = await import("./ready-IJEUBFVX.js");
|
|
97
97
|
try {
|
|
98
98
|
await waitForGateway(readySession, {
|
|
99
99
|
signal: opts?.signal,
|
|
@@ -129,7 +129,7 @@ function expandHome(p) {
|
|
|
129
129
|
return p.replace(/^~/, process.env["HOME"] ?? "~");
|
|
130
130
|
}
|
|
131
131
|
async function applyConfigOverlay(plan, outputs, ctx, signal) {
|
|
132
|
-
const { connect } = await import("./ssh-
|
|
132
|
+
const { connect } = await import("./ssh-HOM7GF3M.js");
|
|
133
133
|
const session = await connect({ ...await connectionInfoFor(ctx, outputs), signal });
|
|
134
134
|
try {
|
|
135
135
|
const remote = await readRemoteConfig(session, signal);
|
|
@@ -158,7 +158,7 @@ async function applyConfigOverlay(plan, outputs, ctx, signal) {
|
|
|
158
158
|
async function installProviderPlugins(session, cfg, openclawVersion, signal) {
|
|
159
159
|
const [{ requiredPlugins, installCommand }, { STATE_DIR_HOST_LINUX }, yaml, { readFileSync }, { join }, { resolveSpecDir }] = await Promise.all([
|
|
160
160
|
import("./plugins-DTST3OER.js"),
|
|
161
|
-
import("./runtime-
|
|
161
|
+
import("./runtime-VW53TU2W.js"),
|
|
162
162
|
import("./js-yaml-PTEEG4FO.js"),
|
|
163
163
|
import("fs"),
|
|
164
164
|
import("path"),
|
|
@@ -169,7 +169,7 @@ async function installProviderPlugins(session, cfg, openclawVersion, signal) {
|
|
|
169
169
|
);
|
|
170
170
|
const needed = requiredPlugins(cfg, catalog);
|
|
171
171
|
if (needed.length === 0) return;
|
|
172
|
-
const { execPrivileged } = await import("./privileged-
|
|
172
|
+
const { execPrivileged } = await import("./privileged-5Y6O4H7Z.js");
|
|
173
173
|
const image = `ghcr.io/openclaw/openclaw:${openclawVersion}`;
|
|
174
174
|
for (const plugin of needed) {
|
|
175
175
|
const result = await execPrivileged(
|
|
@@ -187,7 +187,7 @@ async function installProviderPlugins(session, cfg, openclawVersion, signal) {
|
|
|
187
187
|
}
|
|
188
188
|
async function verifyProviders(session, cfg, signal) {
|
|
189
189
|
const { missingProviders } = await import("./plugins-DTST3OER.js");
|
|
190
|
-
const { execPrivileged } = await import("./privileged-
|
|
190
|
+
const { execPrivileged } = await import("./privileged-5Y6O4H7Z.js");
|
|
191
191
|
const result = await execPrivileged(
|
|
192
192
|
session,
|
|
193
193
|
"docker exec openclaw openclaw plugins list --json",
|
|
@@ -213,10 +213,10 @@ async function loadChannelCatalog() {
|
|
|
213
213
|
return yaml.load(readFileSync(join(resolveSpecDir(), "integrations.yaml"), "utf-8"));
|
|
214
214
|
}
|
|
215
215
|
async function installChannelPlugins(session, cfg, openclawVersion, signal) {
|
|
216
|
-
const [{ requiredChannelPlugins, channelInstallCommand }, { STATE_DIR_HOST_LINUX }] = await Promise.all([import("./channels-KBUR42IU.js"), import("./runtime-
|
|
216
|
+
const [{ requiredChannelPlugins, channelInstallCommand }, { STATE_DIR_HOST_LINUX }] = await Promise.all([import("./channels-KBUR42IU.js"), import("./runtime-VW53TU2W.js")]);
|
|
217
217
|
const needed = requiredChannelPlugins(cfg, await loadChannelCatalog());
|
|
218
218
|
if (needed.length === 0) return;
|
|
219
|
-
const { execPrivileged } = await import("./privileged-
|
|
219
|
+
const { execPrivileged } = await import("./privileged-5Y6O4H7Z.js");
|
|
220
220
|
const image = `ghcr.io/openclaw/openclaw:${openclawVersion}`;
|
|
221
221
|
for (const plugin of needed) {
|
|
222
222
|
const result = await execPrivileged(
|
|
@@ -234,7 +234,7 @@ async function installChannelPlugins(session, cfg, openclawVersion, signal) {
|
|
|
234
234
|
}
|
|
235
235
|
async function verifyChannels(session, cfg, signal) {
|
|
236
236
|
const { missingChannels, CHANNELS_LIST_CMD } = await import("./channels-KBUR42IU.js");
|
|
237
|
-
const { execPrivileged } = await import("./privileged-
|
|
237
|
+
const { execPrivileged } = await import("./privileged-5Y6O4H7Z.js");
|
|
238
238
|
const result = await execPrivileged(session, CHANNELS_LIST_CMD, signal);
|
|
239
239
|
if (result.code !== 0) return;
|
|
240
240
|
const missing = missingChannels(cfg, result.stdout, await loadChannelCatalog());
|