@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.
- package/README.md +122 -2
- package/dist/agent-activity-25FAWLAI.js +16 -0
- package/dist/{apply-HJ5GAKHW.js → apply-F5SQ2ZOS.js} +12 -12
- package/dist/{automation-MKCJ46I6.js → automation-NBYQ6MFZ.js} +1 -1
- package/dist/backup-flows-NDP5JTR3.js +29 -0
- package/dist/{bootstrap-NN647RCY.js → bootstrap-32TZXVDF.js} +5 -5
- package/dist/bootstrap.sh.tmpl +4 -0
- package/dist/chunk-27335SIH.js +51 -0
- package/dist/{chunk-X56CQ6GB.js → chunk-4MHDW2NB.js} +1 -1
- package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
- package/dist/{chunk-3Z5TDXCJ.js → chunk-55UMO37O.js} +1 -1
- package/dist/{chunk-IQQTB7ER.js → chunk-66R52R7K.js} +10 -14
- package/dist/chunk-755X5OGS.js +213 -0
- package/dist/{chunk-QDNFADJI.js → chunk-AVERNLUY.js} +6 -6
- package/dist/{chunk-R3DTA35B.js → chunk-AX65JMDZ.js} +20 -20
- package/dist/{chunk-T5XCMYAV.js → chunk-CQOYUWR7.js} +766 -210
- package/dist/chunk-E6BRQK67.js +289 -0
- package/dist/{chunk-64PONSA5.js → chunk-GFIR2IIJ.js} +3 -1
- package/dist/chunk-LV7UO5CG.js +82 -0
- package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
- package/dist/{chunk-GFUK6KDR.js → chunk-OUQVKIIA.js} +372 -34
- 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-ELU3XQEA.js → chunk-RDUKRIRC.js} +1 -1
- 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-BELTQGHT.js → chunk-YPVWF7IA.js} +4 -4
- package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
- package/dist/cli.js +383 -925
- package/dist/conn-M2V5HBPJ.js +9 -0
- package/dist/{context-7VI3R6EK.js → context-EHSLWKSH.js} +6 -6
- package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
- package/dist/{flows-RI7FGHAM.js → flows-6B2I6TRW.js} +7 -7
- package/dist/{generate-Q2BGVMH7.js → generate-4YYNW3IU.js} +9 -8
- package/dist/{harden-3KKCR7BL.js → harden-UHAOVQMZ.js} +5 -5
- package/dist/init-HOPXDNWY.js +13 -0
- package/dist/{mcp-wire-FQOMM7MK.js → mcp-wire-BXF4DJFT.js} +26 -19
- package/dist/migrate-flow-24XA4GZL.js +25 -0
- package/dist/{package-JFVGCNKU.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-E3YA32ND.js +53 -0
- package/dist/ssh-HOM7GF3M.js +13 -0
- package/dist/{stack-config-LUTS2XAD.js → stack-config-ICQ5YHUL.js} +2 -2
- package/dist/stack-delete-NTIGKWUF.js +13 -0
- package/dist/{state-AOAYLYY5.js → state-AKZP2S4T.js} +2 -2
- package/dist/{store-OVB3DAJR.js → store-YOLQYXMB.js} +1 -1
- package/dist/{tailscale-cutover-6YP2QKFM.js → tailscale-cutover-V42HO5UM.js} +3 -3
- 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 +282 -35
- package/dist/logs-5A7YYA53.js +0 -15
- package/dist/server-B7I6HBQH.js +0 -46
- 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
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@clawops/cli)
|
|
4
4
|
[](https://www.npmjs.com/package/@clawops/cli)
|
|
5
|
+
[](https://glama.ai/mcp/servers/dfridkin/clawops)
|
|
6
|
+
[](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 (
|
|
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-
|
|
4
|
+
} from "./chunk-AX65JMDZ.js";
|
|
5
5
|
import "./chunk-BOPSG2LI.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-UAJJEDCJ.js";
|
|
7
|
+
import "./chunk-PASAVVNS.js";
|
|
8
|
+
import "./chunk-55UMO37O.js";
|
|
9
9
|
import "./chunk-AEEMRFZV.js";
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
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-
|
|
15
|
-
import "./chunk-
|
|
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-
|
|
22
|
-
import "./chunk-
|
|
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
|
};
|
|
@@ -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-
|
|
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";
|
|
14
14
|
import "./chunk-EI7CQQ7D.js";
|
|
15
15
|
import {
|
|
16
16
|
writeLocalState
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-RDUKRIRC.js";
|
|
18
|
+
import "./chunk-GFIR2IIJ.js";
|
|
19
19
|
import {
|
|
20
20
|
ProviderError
|
|
21
21
|
} from "./chunk-KGXPLI7W.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,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-
|
|
7
|
+
} from "./chunk-PASAVVNS.js";
|
|
5
8
|
import {
|
|
6
9
|
buildContext,
|
|
7
10
|
loadAdapterModule
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AVERNLUY.js";
|
|
9
12
|
import {
|
|
10
13
|
getConfig
|
|
11
|
-
} from "./chunk-
|
|
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-
|
|
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}": ${
|
|
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-
|
|
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-
|
|
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,
|