@clawops/cli 2.2.1 → 2.3.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 +48 -3
- package/dist/{apply-F5SQ2ZOS.js → apply-ODV3NOJJ.js} +9 -9
- package/dist/{automation-NBYQ6MFZ.js → automation-VDKLZMJI.js} +1 -1
- package/dist/{backup-flows-NDP5JTR3.js → backup-flows-THPX2D7S.js} +1 -1
- package/dist/{bootstrap-32TZXVDF.js → bootstrap-L3P5E4D7.js} +2 -2
- package/dist/{chunk-PASAVVNS.js → chunk-47CGHUVT.js} +1 -1
- package/dist/{chunk-E6BRQK67.js → chunk-4SEFKZRL.js} +1 -1
- package/dist/{chunk-PCSX6Y3U.js → chunk-66CUJFXK.js} +2 -2
- package/dist/{chunk-55UMO37O.js → chunk-CHPMOE4T.js} +1 -1
- package/dist/{chunk-Y3N63PDS.js → chunk-EGGXYZC6.js} +60 -59
- package/dist/{chunk-755X5OGS.js → chunk-FGY262WZ.js} +4 -1
- package/dist/{chunk-LV7UO5CG.js → chunk-G2ROJXYJ.js} +2 -2
- package/dist/{chunk-GFIR2IIJ.js → chunk-GYMAC4MW.js} +47 -28
- package/dist/{chunk-T6OCPTTR.js → chunk-K7OWMI63.js} +14 -8
- package/dist/{chunk-OUQVKIIA.js → chunk-PNRFFYOA.js} +14 -14
- package/dist/{chunk-AX65JMDZ.js → chunk-PRBDJSWN.js} +12 -9
- package/dist/{chunk-4MHDW2NB.js → chunk-QRF4OWG4.js} +1 -1
- package/dist/{chunk-AVERNLUY.js → chunk-SQPHNVCF.js} +6 -6
- package/dist/{chunk-RDUKRIRC.js → chunk-U3NRKBDW.js} +1 -1
- package/dist/cli.js +111 -72
- package/dist/{context-EHSLWKSH.js → context-4KTM4DKH.js} +5 -5
- package/dist/{flows-6B2I6TRW.js → flows-6YEJGHCK.js} +7 -7
- package/dist/{generate-4JWTMOLQ.js → generate-OJ5UWTLV.js} +9 -7
- package/dist/{harden-UHAOVQMZ.js → harden-7O553IOK.js} +5 -5
- package/dist/{init-HOPXDNWY.js → init-RVQ72GZ5.js} +2 -2
- package/dist/{mcp-wire-7MGEAMZH.js → mcp-wire-HSGZFOG5.js} +21 -21
- package/dist/{migrate-flow-24XA4GZL.js → migrate-flow-4XQUUFMH.js} +2 -2
- package/dist/{package-ESYPCIES.js → package-UOWSHBWP.js} +1 -1
- package/dist/policy-4FFK7REN.js +42 -0
- package/dist/{remote-config-OZYBMBSK.js → remote-config-V2WXIXIF.js} +1 -1
- package/dist/{server-Y2EDRUDL.js → server-YH2JWZ2Q.js} +21 -21
- package/dist/setup-policy-OK5PGWP7.js +90 -0
- package/dist/{stack-config-ICQ5YHUL.js → stack-config-3E7KWFPI.js} +2 -2
- package/dist/{stack-delete-NTIGKWUF.js → stack-delete-SNPMLWMD.js} +2 -2
- package/dist/{state-AKZP2S4T.js → state-KG6K5QZC.js} +2 -2
- package/dist/{store-YOLQYXMB.js → store-BTQOOIJH.js} +1 -1
- package/dist/{tailscale-cutover-V42HO5UM.js → tailscale-cutover-XTMGDM5H.js} +2 -2
- package/dist/{validate-DZE6LFEB.js → validate-CRXX7OYF.js} +1 -1
- package/package.json +1 -1
- package/spec/errors.yaml +5 -3
- package/spec/mcp-tools.yaml +4 -3
- package/dist/{chunk-UAJJEDCJ.js → chunk-AGSNTIYZ.js} +0 -0
- package/dist/{chunk-YPVWF7IA.js → chunk-HSGKK3RF.js} +3 -3
- package/dist/{ready-IJEUBFVX.js → ready-4H3NNEYQ.js} +3 -3
package/README.md
CHANGED
|
@@ -262,7 +262,7 @@ clawops down --yes # Destroy local-provider stack
|
|
|
262
262
|
|
|
263
263
|
| Command | Description |
|
|
264
264
|
|---|---|
|
|
265
|
-
| `setup` | First-run wizard: guided LLM, integrations, and deploy-plan generation |
|
|
265
|
+
| `setup` | First-run wizard: guided LLM, integrations, spending limits, and deploy-plan generation |
|
|
266
266
|
| `init` | Register a stack in `~/.clawops/config.json` without provisioning. Additive, existing stacks are kept; `--force` is needed only to overwrite one |
|
|
267
267
|
| `up` | Provision or update stack (`--dry-run` for preview, `--gateway-port` for a non-default port) |
|
|
268
268
|
| `down` | Destroy local-provider stack (requires `--yes`; `--dry-run` shows current outputs) |
|
|
@@ -397,7 +397,7 @@ Do not bind to a non-loopback address without additional authentication controls
|
|
|
397
397
|
| `clawops_harden` | cli | Apply hardening modules; join or leave a tailnet (elicits confirmation) |
|
|
398
398
|
| `clawops_init` | cli | Register a stack and write `~/.clawops/config.json` (no cloud resources) |
|
|
399
399
|
| `clawops_workflow_deploy_app` | workflow | End-to-end deploy: plan → confirm → apply → status |
|
|
400
|
-
| `clawops_workflow_recover` | workflow |
|
|
400
|
+
| `clawops_workflow_recover` | workflow | Diagnose an unhealthy stack: status, failed health checks, recent logs, and which tool fixes what |
|
|
401
401
|
| `clawops_task_status` | cli | Poll a long-running task |
|
|
402
402
|
|
|
403
403
|
Tools in the `read` toolset are also available in `--read-only` mode; the table's Toolset column shows the primary toolset. All other toolsets require full mode.
|
|
@@ -442,6 +442,24 @@ Config lives at `~/.clawops/config.json` (override with `$CLAWOPS_HOME`).
|
|
|
442
442
|
| Azure | `AZURE_CLIENT_ID` / `AZURE_CLIENT_SECRET` or `az login` |
|
|
443
443
|
| Local | SSH host + key configured in `stacks[name].localOpts` |
|
|
444
444
|
|
|
445
|
+
### Spending limits
|
|
446
|
+
|
|
447
|
+
An optional `policy` block limits what clawops will deploy from this machine, whichever tool asks:
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
"policy": {
|
|
451
|
+
"allowedInstanceTypes": { "aws": ["micro", "small"] },
|
|
452
|
+
"maxStacks": 3
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
`plan`, `apply` and `up` refuse a server size outside `allowedInstanceTypes` for that provider
|
|
457
|
+
(a clawops size or the provider's own type; `small` and `t3.small` are the same on AWS), and
|
|
458
|
+
`init` refuses a new stack beyond `maxStacks`. Every MCP tool that deploys is held to the same
|
|
459
|
+
limits, in the same words, and no tool can change them. `clawops setup` asks whether to set
|
|
460
|
+
them, then offers only the sizes you allowed. The limit lives here rather than in the plan
|
|
461
|
+
because a plan is what an agent writes.
|
|
462
|
+
|
|
445
463
|
---
|
|
446
464
|
|
|
447
465
|
## Known limitations
|
|
@@ -576,7 +594,7 @@ pnpm dev doctor # verify toolchain
|
|
|
576
594
|
```bash
|
|
577
595
|
pnpm dev # run CLI from src/ via tsx
|
|
578
596
|
pnpm build # tsup → dist/
|
|
579
|
-
pnpm test # vitest (
|
|
597
|
+
pnpm test # vitest (2115 tests, ~13s)
|
|
580
598
|
pnpm test:changed # vitest --changed (fast edit loop)
|
|
581
599
|
pnpm test:integration # Docker-based SSH integration tests
|
|
582
600
|
pnpm test:e2e:local # builds, then bootstrap, the host-touching MCP tools and a 1.x migration, in systemd containers
|
|
@@ -635,6 +653,33 @@ Use `pnpm changeset` to record a release note before merging a `feat` or `fix`.
|
|
|
635
653
|
|
|
636
654
|
---
|
|
637
655
|
|
|
656
|
+
## What's new in 2.3
|
|
657
|
+
|
|
658
|
+
Spending limits that an agent cannot lift, and a run of fixes from the backlog.
|
|
659
|
+
|
|
660
|
+
### Spending limits
|
|
661
|
+
|
|
662
|
+
- An optional `policy` block in `~/.clawops/config.json` limits the server sizes clawops may
|
|
663
|
+
deploy on each provider and the number of deployments it may manage. `plan`, `apply` and `up`
|
|
664
|
+
refuse a size outside the list; `init` refuses a stack beyond the cap.
|
|
665
|
+
- Every MCP tool that deploys is held to the same limits, in the same words, and no tool can
|
|
666
|
+
change them. Confirmation is something an agent can answer for itself; this is not.
|
|
667
|
+
- `clawops setup` asks whether to set them, then offers only the sizes you allowed. See
|
|
668
|
+
[Spending limits](#spending-limits).
|
|
669
|
+
|
|
670
|
+
### Fixes
|
|
671
|
+
|
|
672
|
+
- **The setup wizard's plan asked the cloud for a machine called `small`.** It wrote the clawops
|
|
673
|
+
size where the provider's own type belongs. It now maps it, as `clawops plan` does.
|
|
674
|
+
- **`clawops_workflow_recover` runs the stack's health checks, and recommends only real tools.** It
|
|
675
|
+
ran two of the four steps it described and finished by suggesting `clawops refresh`, which does
|
|
676
|
+
not exist.
|
|
677
|
+
- **The MCP audit log redacts credential arguments.** `GOOGLE_APPLICATION_CREDENTIALS` was named
|
|
678
|
+
sensitive in `spec/errors.yaml` and logged in the clear. A test now fails when the spec and the
|
|
679
|
+
code disagree on an error class, its exit code, or a redaction pattern (#185).
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
638
683
|
## What's new in 2.2
|
|
639
684
|
|
|
640
685
|
Recovery is a flag, agents can reach every command they need, and SSH failures say what failed.
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
applyPlan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PRBDJSWN.js";
|
|
5
5
|
import "./chunk-BOPSG2LI.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-PASAVVNS.js";
|
|
8
|
-
import "./chunk-55UMO37O.js";
|
|
6
|
+
import "./chunk-CHPMOE4T.js";
|
|
9
7
|
import "./chunk-AEEMRFZV.js";
|
|
10
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-AGSNTIYZ.js";
|
|
9
|
+
import "./chunk-47CGHUVT.js";
|
|
10
|
+
import "./chunk-SQPHNVCF.js";
|
|
11
11
|
import "./chunk-6ZFIFDBJ.js";
|
|
12
12
|
import "./chunk-WPZIJONN.js";
|
|
13
|
-
import "./chunk-YPVWF7IA.js";
|
|
14
13
|
import "./chunk-JW5A7E3X.js";
|
|
15
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-HSGKK3RF.js";
|
|
16
15
|
import "./chunk-N5ZYUTZS.js";
|
|
17
16
|
import "./chunk-VOEJYRIK.js";
|
|
18
17
|
import "./chunk-EI7CQQ7D.js";
|
|
19
18
|
import "./chunk-FEK7VJAY.js";
|
|
20
19
|
import "./chunk-RHOWYIWT.js";
|
|
21
20
|
import "./chunk-N4WZLWJ4.js";
|
|
22
|
-
import "./chunk-
|
|
23
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-U3NRKBDW.js";
|
|
22
|
+
import "./chunk-GYMAC4MW.js";
|
|
24
23
|
import "./chunk-KGXPLI7W.js";
|
|
24
|
+
import "./chunk-VUFMZEIM.js";
|
|
25
25
|
export {
|
|
26
26
|
applyPlan
|
|
27
27
|
};
|
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
import "./chunk-EI7CQQ7D.js";
|
|
15
15
|
import {
|
|
16
16
|
writeLocalState
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-U3NRKBDW.js";
|
|
18
|
+
import "./chunk-GYMAC4MW.js";
|
|
19
19
|
import {
|
|
20
20
|
ProviderError
|
|
21
21
|
} from "./chunk-KGXPLI7W.js";
|
|
@@ -44,7 +44,7 @@ async function validatePlanConfig(plan) {
|
|
|
44
44
|
]);
|
|
45
45
|
const { loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
|
|
46
46
|
const spec = loadVersionSpec(yaml);
|
|
47
|
-
const { deepMerge } = await import("./remote-config-
|
|
47
|
+
const { deepMerge } = await import("./remote-config-V2WXIXIF.js");
|
|
48
48
|
const merged = deepMerge(
|
|
49
49
|
{ gateway: { mode: "local" } },
|
|
50
50
|
overlay
|
|
@@ -136,7 +136,7 @@ var SYNTHESISED_CONFIG = JSON.stringify({
|
|
|
136
136
|
channels: {}
|
|
137
137
|
});
|
|
138
138
|
async function prepareMigration(args) {
|
|
139
|
-
const { buildContext } = await import("./context-
|
|
139
|
+
const { buildContext } = await import("./context-4KTM4DKH.js");
|
|
140
140
|
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
|
|
141
141
|
const version = args.openclawVersion ? await guardOpenclawVersion(args.openclawVersion) : await defaultOpenclawVersion();
|
|
142
142
|
const ctx = buildContext({ stack: args.stack });
|
|
@@ -230,8 +230,8 @@ async function stageRestore(session, opts) {
|
|
|
230
230
|
async function activateStaged(session, staged, opts = {}) {
|
|
231
231
|
const { signal } = opts;
|
|
232
232
|
const hostExec = (command) => execPrivileged(session, command, signal);
|
|
233
|
-
const { restartGateway } = await import("./remote-config-
|
|
234
|
-
const { waitForGateway } = await import("./ready-
|
|
233
|
+
const { restartGateway } = await import("./remote-config-V2WXIXIF.js");
|
|
234
|
+
const { waitForGateway } = await import("./ready-4H3NNEYQ.js");
|
|
235
235
|
opts.onPhase?.("activating");
|
|
236
236
|
const chown = await hostExec(`chown -R ${CONTAINER_UID}:${CONTAINER_UID} ${staged.statePath}`);
|
|
237
237
|
if (chown.code !== 0) {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_AGENT_LOG_LIMIT,
|
|
4
|
+
readAgentActivity
|
|
5
|
+
} from "./chunk-27335SIH.js";
|
|
2
6
|
import {
|
|
3
7
|
migrationQuestion,
|
|
4
8
|
prepareMigration,
|
|
5
9
|
runMigration
|
|
6
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-4SEFKZRL.js";
|
|
7
11
|
import {
|
|
8
12
|
ACTIVATED,
|
|
9
13
|
ARCHIVE_IS_A_CREDENTIAL,
|
|
@@ -15,43 +19,39 @@ import {
|
|
|
15
19
|
defaultBackupFilename,
|
|
16
20
|
nothingChanged,
|
|
17
21
|
stageRestore
|
|
18
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-66CUJFXK.js";
|
|
19
23
|
import {
|
|
20
24
|
checkStackDelete,
|
|
21
25
|
deleteStackFromConfig,
|
|
22
26
|
forgetNotice
|
|
23
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-G2ROJXYJ.js";
|
|
24
28
|
import {
|
|
25
29
|
applyPlan
|
|
26
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-PRBDJSWN.js";
|
|
27
31
|
import {
|
|
28
32
|
resolveConn
|
|
29
33
|
} from "./chunk-SX75L2T3.js";
|
|
30
34
|
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} from "./chunk-
|
|
34
|
-
import {
|
|
35
|
-
generatePlan
|
|
36
|
-
} from "./chunk-T6OCPTTR.js";
|
|
35
|
+
acquireSession,
|
|
36
|
+
drainPool
|
|
37
|
+
} from "./chunk-TYNMOYVM.js";
|
|
37
38
|
import {
|
|
38
39
|
extractBaseOutputs
|
|
39
40
|
} from "./chunk-3QJBNAHW.js";
|
|
41
|
+
import {
|
|
42
|
+
generatePlan
|
|
43
|
+
} from "./chunk-K7OWMI63.js";
|
|
40
44
|
import {
|
|
41
45
|
atomicWriteConfig,
|
|
42
46
|
readRemoteConfig,
|
|
43
47
|
restartGateway
|
|
44
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-AGSNTIYZ.js";
|
|
45
49
|
import {
|
|
46
50
|
validatePlan
|
|
47
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-47CGHUVT.js";
|
|
48
52
|
import {
|
|
49
53
|
buildContext
|
|
50
|
-
} from "./chunk-
|
|
51
|
-
import {
|
|
52
|
-
acquireSession,
|
|
53
|
-
drainPool
|
|
54
|
-
} from "./chunk-TYNMOYVM.js";
|
|
54
|
+
} from "./chunk-SQPHNVCF.js";
|
|
55
55
|
import {
|
|
56
56
|
AUDIT_CLEAN_MESSAGE,
|
|
57
57
|
AUDIT_FIX_HINT,
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
resolveUpdateVersion,
|
|
70
70
|
runDiagnostics,
|
|
71
71
|
updateGateway
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-PNRFFYOA.js";
|
|
73
73
|
import {
|
|
74
74
|
GATEWAY_LOGS_PROBE,
|
|
75
75
|
chooseLogSource,
|
|
@@ -82,7 +82,7 @@ import {
|
|
|
82
82
|
import {
|
|
83
83
|
getConfig,
|
|
84
84
|
getConfigDir
|
|
85
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-GYMAC4MW.js";
|
|
86
86
|
import {
|
|
87
87
|
StateError,
|
|
88
88
|
UsageError
|
|
@@ -105,7 +105,7 @@ function getSessionId() {
|
|
|
105
105
|
if (!_sessionId) _sessionId = randomUUID();
|
|
106
106
|
return _sessionId;
|
|
107
107
|
}
|
|
108
|
-
var SENSITIVE_KEYS = ["token", "secret", "key", "password", "connectionstring", "authorization"];
|
|
108
|
+
var SENSITIVE_KEYS = ["token", "secret", "key", "password", "connectionstring", "authorization", "credential"];
|
|
109
109
|
var EXEMPT_KEYS = ["keyname", "keypath", "privateKeyPath", "knownHostsPath"];
|
|
110
110
|
var ARN_RE = /arn:aws:[a-z0-9\-*]+:[a-z0-9\-]*:[0-9]*:[^\s,'"]+/gi;
|
|
111
111
|
function sanitize(args) {
|
|
@@ -565,9 +565,10 @@ var clawops_workflow_deploy_appAnnotations = {
|
|
|
565
565
|
var clawops_workflow_recoverSchema = z.object({
|
|
566
566
|
stackName: z.string().optional().describe("Which stack to diagnose. Omitted = the default stack in ~/.clawops/config.json")
|
|
567
567
|
});
|
|
568
|
-
var clawops_workflow_recoverDescription = `Diagnostic workflow for an unhealthy stack. Internally:
|
|
569
|
-
|
|
570
|
-
|
|
568
|
+
var clawops_workflow_recoverDescription = `Diagnostic workflow for an unhealthy stack. Internally: what is deployed
|
|
569
|
+
(clawops_status) \u2192 the stack's failed health checks (clawops_doctor) \u2192 the
|
|
570
|
+
gateway's last 50 log lines (clawops_logs_tail) \u2192 a report with next steps that
|
|
571
|
+
name the tool for each kind of failure. Changes nothing.
|
|
571
572
|
|
|
572
573
|
Use when: the user reports any "not working" symptom and you don't know
|
|
573
574
|
where to start. Best entry point for troubleshooting.
|
|
@@ -1155,7 +1156,7 @@ async function handleUp(input, server) {
|
|
|
1155
1156
|
);
|
|
1156
1157
|
}
|
|
1157
1158
|
const { localOpts } = stackConfig;
|
|
1158
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
1159
|
+
const { localBootstrap } = await import("./bootstrap-L3P5E4D7.js");
|
|
1159
1160
|
const ac = new AbortController();
|
|
1160
1161
|
const state = await localBootstrap({
|
|
1161
1162
|
host: localOpts.host,
|
|
@@ -1178,8 +1179,8 @@ async function handleUp(input, server) {
|
|
|
1178
1179
|
const progressToken = void 0;
|
|
1179
1180
|
const emit = makeProgressEmitter(server, progressToken);
|
|
1180
1181
|
startTask(taskId, `clawops_up stack=${ctx.stackName}`);
|
|
1181
|
-
const { generatePlan: generatePlan2 } = await import("./generate-
|
|
1182
|
-
const { applyPlan: applyPlan2 } = await import("./apply-
|
|
1182
|
+
const { generatePlan: generatePlan2 } = await import("./generate-OJ5UWTLV.js");
|
|
1183
|
+
const { applyPlan: applyPlan2 } = await import("./apply-ODV3NOJJ.js");
|
|
1183
1184
|
const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
|
|
1184
1185
|
const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
|
|
1185
1186
|
const network = await resolveNetworkFlags(
|
|
@@ -1294,7 +1295,7 @@ async function handlePlan(input, _server) {
|
|
|
1294
1295
|
let network = resolved;
|
|
1295
1296
|
if (input.privateOnly) {
|
|
1296
1297
|
const { privateOnlyNetwork, assertTailnetReachable } = await import("./private-only-DIA26I2E.js");
|
|
1297
|
-
const { probeSsh } = await import("./tailscale-cutover-
|
|
1298
|
+
const { probeSsh } = await import("./tailscale-cutover-XTMGDM5H.js");
|
|
1298
1299
|
network = privateOnlyNetwork(flags, resolved, ctx.config.stacks[stackName]?.tailscale, stackName);
|
|
1299
1300
|
await assertTailnetReachable(ctx, (conn) => probeSsh(conn));
|
|
1300
1301
|
}
|
|
@@ -1331,7 +1332,7 @@ async function handleHarden(input, server) {
|
|
|
1331
1332
|
"tailscale and tailscaleRevert do the opposite of each other; pass one, not both."
|
|
1332
1333
|
);
|
|
1333
1334
|
}
|
|
1334
|
-
const { getConfig: getConfig2 } = await import("./store-
|
|
1335
|
+
const { getConfig: getConfig2 } = await import("./store-BTQOOIJH.js");
|
|
1335
1336
|
const config = getConfig2();
|
|
1336
1337
|
if (!config) {
|
|
1337
1338
|
return errText(
|
|
@@ -1350,12 +1351,12 @@ async function handleHarden(input, server) {
|
|
|
1350
1351
|
return okText(confirmation.reason);
|
|
1351
1352
|
}
|
|
1352
1353
|
}
|
|
1353
|
-
const { revertTailnet, cutOverToTailnet, connectionFor } = await import("./flows-
|
|
1354
|
+
const { revertTailnet, cutOverToTailnet, connectionFor } = await import("./flows-6YEJGHCK.js");
|
|
1354
1355
|
if (input.tailscaleRevert) {
|
|
1355
1356
|
const outcome = await revertTailnet(ctx, config);
|
|
1356
1357
|
return outcome.ok ? okText(outcome.message) : errText(outcome.reason);
|
|
1357
1358
|
}
|
|
1358
|
-
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary, makeTailscaleModule } = await import("./harden-
|
|
1359
|
+
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary, makeTailscaleModule } = await import("./harden-7O553IOK.js");
|
|
1359
1360
|
const forStack = makeTailscaleModule(ctx.stackName);
|
|
1360
1361
|
const resolved = resolveModules(MODULE_CATALOG, input.options, ctx.adapter.name).map(
|
|
1361
1362
|
(m) => m.id === "tailscale" ? forStack : m
|
|
@@ -1399,7 +1400,7 @@ async function handleHarden(input, server) {
|
|
|
1399
1400
|
|
|
1400
1401
|
// src/mcp/tools/cli/init.ts
|
|
1401
1402
|
async function handleInit(input, _server) {
|
|
1402
|
-
const { initStack } = await import("./init-
|
|
1403
|
+
const { initStack } = await import("./init-RVQ72GZ5.js");
|
|
1403
1404
|
const result = await initStack({
|
|
1404
1405
|
provider: input.provider,
|
|
1405
1406
|
stackName: input.stackName,
|
|
@@ -1865,39 +1866,39 @@ Changes: ${diffLine}. This will provision cloud resources and may incur costs.`,
|
|
|
1865
1866
|
}
|
|
1866
1867
|
|
|
1867
1868
|
// src/mcp/tools/workflow/recover.ts
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
const statusResult = await handleStatus({ stackName: input.stackName }, server);
|
|
1873
|
-
const statusText = statusResult.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
1874
|
-
parts.push(`### Status
|
|
1875
|
-
\`\`\`json
|
|
1876
|
-
${statusText}
|
|
1877
|
-
\`\`\``);
|
|
1878
|
-
} catch (err) {
|
|
1879
|
-
parts.push(`### Status
|
|
1880
|
-
Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1881
|
-
}
|
|
1869
|
+
function textOf(result) {
|
|
1870
|
+
return result.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
1871
|
+
}
|
|
1872
|
+
async function section(title, fence, run) {
|
|
1882
1873
|
try {
|
|
1883
|
-
const
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
${
|
|
1888
|
-
|
|
1874
|
+
const result = await run();
|
|
1875
|
+
return result.isError ? `### ${title}
|
|
1876
|
+
Error: ${textOf(result)}` : `### ${title}
|
|
1877
|
+
\`\`\`${fence}
|
|
1878
|
+
${textOf(result)}
|
|
1879
|
+
\`\`\``;
|
|
1889
1880
|
} catch (err) {
|
|
1890
|
-
|
|
1891
|
-
Error: ${err instanceof Error ? err.message : String(err)}
|
|
1881
|
+
return `### ${title}
|
|
1882
|
+
Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
1892
1883
|
}
|
|
1884
|
+
}
|
|
1885
|
+
async function handleWorkflowRecover(input, server) {
|
|
1886
|
+
const stack = input.stackName ?? "default";
|
|
1887
|
+
const parts = [`## Diagnostic Report \u2014 stack: ${stack}
|
|
1888
|
+
`];
|
|
1889
|
+
parts.push(await section("Status", "json", () => handleStatus({ stackName: input.stackName }, server)));
|
|
1890
|
+
parts.push(await section("Failed health checks", "json", () => handleDoctor({ stackName: input.stackName, failuresOnly: true }, server)));
|
|
1891
|
+
parts.push(await section("Recent Logs (last 50 lines)", "", () => handleLogsTail({ stackName: input.stackName, tailLines: 50 }, server)));
|
|
1893
1892
|
parts.push(
|
|
1894
1893
|
`### Next Steps
|
|
1895
|
-
-
|
|
1896
|
-
-
|
|
1897
|
-
-
|
|
1898
|
-
-
|
|
1894
|
+
- Gateway not running, or not answering: \`clawops_gateway_restart\`
|
|
1895
|
+
- Gateway fails on its current version: \`clawops_gateway_update\` to a version that worked, or \`clawops_backup_restore\` with \`activate\` if the state is at fault
|
|
1896
|
+
- Host not set up, or set up only partly: \`clawops_up\` again (it is idempotent)
|
|
1897
|
+
- Infrastructure differs from what was deployed: \`clawops_plan\`, review, then \`clawops_apply\`
|
|
1898
|
+
- Nothing deployed yet: \`clawops_workflow_deploy_app\``
|
|
1899
1899
|
);
|
|
1900
|
-
|
|
1900
|
+
const { content } = trimForMcp(parts.join("\n\n"), stack);
|
|
1901
|
+
return { content: [{ type: "text", text: content }] };
|
|
1901
1902
|
}
|
|
1902
1903
|
|
|
1903
1904
|
// src/mcp/tools/registry.ts
|
|
@@ -2232,7 +2233,7 @@ function bearerFrom(header) {
|
|
|
2232
2233
|
return match?.[1]?.trim();
|
|
2233
2234
|
}
|
|
2234
2235
|
async function serveMcp(opts) {
|
|
2235
|
-
const { version } = await import("./package-
|
|
2236
|
+
const { version } = await import("./package-UOWSHBWP.js");
|
|
2236
2237
|
const makeServer = () => {
|
|
2237
2238
|
const server = new McpServer({ name: "clawops", version });
|
|
2238
2239
|
registerTools(server, opts);
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
getConfig,
|
|
4
4
|
getConfigDir,
|
|
5
5
|
setConfig
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GYMAC4MW.js";
|
|
7
7
|
|
|
8
8
|
// src/config/init.ts
|
|
9
9
|
import { spawnSync } from "child_process";
|
|
@@ -109,6 +109,9 @@ async function initStack(opts) {
|
|
|
109
109
|
reason: `Stack "${stackName}" already exists in ${path.join(getConfigDir(), "config.json")} (${existing.stacks[stackName].stateUrl}). Pass force to overwrite its settings, or use a different stack name to add another one.`
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
+
const { stackCapRefusal } = await import("./policy-4FFK7REN.js");
|
|
113
|
+
const overCap = stackCapRefusal(existing, stackName);
|
|
114
|
+
if (overCap) return { ok: false, reason: overCap };
|
|
112
115
|
const configDir = getConfigDir();
|
|
113
116
|
mkdirSync(configDir, { recursive: true });
|
|
114
117
|
const keyPath = opts.keyPath ?? path.join(configDir, "id_ed25519");
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
requireConfig,
|
|
4
4
|
setConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GYMAC4MW.js";
|
|
6
6
|
|
|
7
7
|
// src/config/stack-delete.ts
|
|
8
8
|
function forgetNotice(name) {
|
|
@@ -47,7 +47,7 @@ async function checkStackDelete(name, opts) {
|
|
|
47
47
|
return { ok: true, name, isDefault, warnings };
|
|
48
48
|
}
|
|
49
49
|
async function isStackDeployed(name) {
|
|
50
|
-
const { buildContext } = await import("./context-
|
|
50
|
+
const { buildContext } = await import("./context-4KTM4DKH.js");
|
|
51
51
|
const ctx = buildContext({ stack: name });
|
|
52
52
|
if (ctx.adapter.name === "local") return !!ctx.localState;
|
|
53
53
|
const stack = await ctx.getStack();
|
|
@@ -10,46 +10,64 @@ import { randomUUID } from "crypto";
|
|
|
10
10
|
import os from "os";
|
|
11
11
|
import path from "path";
|
|
12
12
|
import process from "process";
|
|
13
|
+
import { z as z2 } from "zod";
|
|
14
|
+
|
|
15
|
+
// src/config/policy-schema.ts
|
|
13
16
|
import { z } from "zod";
|
|
14
|
-
var
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
var PolicySchema = z.object({
|
|
18
|
+
/**
|
|
19
|
+
* Per provider, the instance types clawops may deploy. A clawops size (`micro`, `small`,
|
|
20
|
+
* `medium`, `large`) or a provider-native type; sizes are compared after the provider maps
|
|
21
|
+
* them, so `small` and `t3.small` are the same entry on AWS. A provider with no entry is
|
|
22
|
+
* unrestricted.
|
|
23
|
+
*/
|
|
24
|
+
allowedInstanceTypes: z.record(z.array(z.string()).min(1)).optional(),
|
|
25
|
+
/** How many stacks this config may hold. Absent = no cap. */
|
|
26
|
+
maxStacks: z.number().int().positive().optional()
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// src/config/store.ts
|
|
30
|
+
var CredentialsRefSchema = z2.object({
|
|
31
|
+
source: z2.enum(["env", "cli-profile", "file", "instance-metadata"]),
|
|
32
|
+
envVars: z2.array(z2.string()).optional(),
|
|
33
|
+
profileName: z2.string().optional()
|
|
18
34
|
});
|
|
19
|
-
var LocalOptsSchema =
|
|
20
|
-
host:
|
|
21
|
-
sshUser:
|
|
22
|
-
sshPort:
|
|
23
|
-
sshKeyPath:
|
|
35
|
+
var LocalOptsSchema = z2.object({
|
|
36
|
+
host: z2.string(),
|
|
37
|
+
sshUser: z2.string(),
|
|
38
|
+
sshPort: z2.number(),
|
|
39
|
+
sshKeyPath: z2.string()
|
|
24
40
|
});
|
|
25
|
-
var TailscaleOverrideSchema =
|
|
26
|
-
ip:
|
|
27
|
-
hostname:
|
|
41
|
+
var TailscaleOverrideSchema = z2.object({
|
|
42
|
+
ip: z2.string(),
|
|
43
|
+
hostname: z2.string().optional(),
|
|
28
44
|
/** When the address was verified, ISO 8601. */
|
|
29
|
-
verifiedAt:
|
|
45
|
+
verifiedAt: z2.string(),
|
|
30
46
|
/** Set once --private-only has closed the public ports, so revert knows to reopen them. */
|
|
31
|
-
privateOnly:
|
|
47
|
+
privateOnly: z2.boolean().optional()
|
|
32
48
|
});
|
|
33
|
-
var StackConfigSchema =
|
|
34
|
-
provider:
|
|
35
|
-
stateUrl:
|
|
36
|
-
region:
|
|
49
|
+
var StackConfigSchema = z2.object({
|
|
50
|
+
provider: z2.string(),
|
|
51
|
+
stateUrl: z2.string(),
|
|
52
|
+
region: z2.string().optional(),
|
|
37
53
|
credentialsRef: CredentialsRefSchema,
|
|
38
54
|
localOpts: LocalOptsSchema.optional(),
|
|
39
55
|
tailscale: TailscaleOverrideSchema.optional()
|
|
40
56
|
});
|
|
41
|
-
var ClawopsConfigSchema =
|
|
42
|
-
version:
|
|
43
|
-
defaults:
|
|
44
|
-
stack:
|
|
45
|
-
provider:
|
|
57
|
+
var ClawopsConfigSchema = z2.object({
|
|
58
|
+
version: z2.literal(1),
|
|
59
|
+
defaults: z2.object({
|
|
60
|
+
stack: z2.string(),
|
|
61
|
+
provider: z2.string()
|
|
46
62
|
}),
|
|
47
|
-
stacks:
|
|
48
|
-
ssh:
|
|
49
|
-
keyPath:
|
|
50
|
-
knownHostsPath:
|
|
63
|
+
stacks: z2.record(StackConfigSchema),
|
|
64
|
+
ssh: z2.object({
|
|
65
|
+
keyPath: z2.string(),
|
|
66
|
+
knownHostsPath: z2.string()
|
|
51
67
|
}),
|
|
52
|
-
mcp:
|
|
68
|
+
mcp: z2.object({ auditLogPath: z2.string() }).optional(),
|
|
69
|
+
/** The operator's spending policy; see ./policy.ts. */
|
|
70
|
+
policy: PolicySchema.optional()
|
|
53
71
|
});
|
|
54
72
|
function getConfigDir() {
|
|
55
73
|
return process.env["CLAWOPS_HOME"] ?? path.join(os.homedir(), ".clawops");
|
|
@@ -92,6 +110,7 @@ function setConfig(config) {
|
|
|
92
110
|
}
|
|
93
111
|
|
|
94
112
|
export {
|
|
113
|
+
PolicySchema,
|
|
95
114
|
getConfigDir,
|
|
96
115
|
getConfigPath,
|
|
97
116
|
getConfig,
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
} from "./chunk-POYS3EVQ.js";
|
|
5
5
|
import {
|
|
6
6
|
validatePlan
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-47CGHUVT.js";
|
|
8
8
|
import {
|
|
9
9
|
buildContext,
|
|
10
10
|
loadAdapterModule
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-SQPHNVCF.js";
|
|
12
12
|
import {
|
|
13
13
|
getConfig
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-GYMAC4MW.js";
|
|
15
15
|
import {
|
|
16
16
|
StateError,
|
|
17
17
|
UsageError
|
|
@@ -62,9 +62,12 @@ async function resolveInstanceType(intent) {
|
|
|
62
62
|
`[clawops] note: "${requested}" is not a clawops size (${INSTANCE_ALIASES.join(", ")}), so it is passed to ${intent.provider} as written.
|
|
63
63
|
`
|
|
64
64
|
);
|
|
65
|
-
return requested;
|
|
66
65
|
}
|
|
67
|
-
|
|
66
|
+
return nativeInstanceType(intent.provider, requested);
|
|
67
|
+
}
|
|
68
|
+
async function nativeInstanceType(provider, requested) {
|
|
69
|
+
if (!isInstanceAlias(requested)) return requested;
|
|
70
|
+
const adapter = await loadAdapterModule(provider);
|
|
68
71
|
return adapter.normalizeInstanceType(requested);
|
|
69
72
|
}
|
|
70
73
|
var parseDiffForTest = parseDiff;
|
|
@@ -74,9 +77,11 @@ async function generatePlan(intent, _opts) {
|
|
|
74
77
|
"plan/apply is not supported for the local provider. Use `clawops up` directly."
|
|
75
78
|
);
|
|
76
79
|
}
|
|
77
|
-
const { version } = await import("./package-
|
|
80
|
+
const { version } = await import("./package-UOWSHBWP.js");
|
|
78
81
|
const config = getConfig();
|
|
79
82
|
const instanceType = await resolveInstanceType(intent);
|
|
83
|
+
const { enforceInstanceType } = await import("./policy-4FFK7REN.js");
|
|
84
|
+
await enforceInstanceType(config, intent.provider, instanceType);
|
|
80
85
|
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
|
|
81
86
|
const openclawVersion = await guardOpenclawVersion(
|
|
82
87
|
intent.openclawVersion ?? await defaultOpenclawVersion()
|
|
@@ -126,7 +131,7 @@ A plan is only as good as the state it was computed against, so this is not a pl
|
|
|
126
131
|
);
|
|
127
132
|
}
|
|
128
133
|
try {
|
|
129
|
-
const { writeStackConfig } = await import("./stack-config-
|
|
134
|
+
const { writeStackConfig } = await import("./stack-config-3E7KWFPI.js");
|
|
130
135
|
await writeStackConfig(stack, plan);
|
|
131
136
|
const outputLines = [];
|
|
132
137
|
const preview = await stack.preview({
|
|
@@ -154,7 +159,7 @@ A plan is only as good as the state it was computed against, so this is not a pl
|
|
|
154
159
|
throw new Error(`Generated plan failed schema validation:
|
|
155
160
|
${validation.errors.join("\n")}`);
|
|
156
161
|
}
|
|
157
|
-
const { validatePlanConfig, validatePlanNetwork } = await import("./validate-
|
|
162
|
+
const { validatePlanConfig, validatePlanNetwork } = await import("./validate-CRXX7OYF.js");
|
|
158
163
|
const cfgCheck = await validatePlanConfig(plan);
|
|
159
164
|
for (const w of cfgCheck.warnings) process.stderr.write(`[clawops] warning: ${w}
|
|
160
165
|
`);
|
|
@@ -186,6 +191,7 @@ function expandHome(p) {
|
|
|
186
191
|
export {
|
|
187
192
|
INSTANCE_ALIASES,
|
|
188
193
|
isInstanceAlias,
|
|
194
|
+
nativeInstanceType,
|
|
189
195
|
parseDiffForTest,
|
|
190
196
|
generatePlan,
|
|
191
197
|
planId
|