@clawops/cli 2.0.1 → 2.1.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 +862 -807
- package/dist/{apply-KYFPWI5U.js → apply-HJ5GAKHW.js} +7 -7
- package/dist/{automation-RMAIBNVC.js → automation-MKCJ46I6.js} +3 -3
- package/dist/{bootstrap-HNGKY6WW.js → bootstrap-NN647RCY.js} +2 -2
- package/dist/{chunk-HSPHXG6W.js → chunk-3Z5TDXCJ.js} +2 -2
- package/dist/{chunk-CX5SL5HP.js → chunk-64PONSA5.js} +10 -1
- package/dist/{chunk-STLRHLLV.js → chunk-A2DS5SB7.js} +12 -9
- package/dist/{chunk-KC3GC3PE.js → chunk-AYUX4FOF.js} +127 -13
- package/dist/{chunk-LEVIW2PV.js → chunk-BELTQGHT.js} +16 -2
- package/dist/{preflight-MWWM5JT3.js → chunk-BTW7NHOH.js} +3 -2
- package/dist/{chunk-A2I76FTA.js → chunk-ELU3XQEA.js} +1 -1
- package/dist/{chunk-IVWP4QUJ.js → chunk-GFUK6KDR.js} +10 -10
- package/dist/{chunk-W3OLNNJW.js → chunk-PSOSBJVO.js} +1 -1
- package/dist/{chunk-5XT5IAJK.js → chunk-QDNFADJI.js} +12 -5
- package/dist/{chunk-LBE6COC4.js → chunk-R3DTA35B.js} +22 -3
- package/dist/{preflight-EF52S3WW.js → chunk-TC2VYPLH.js} +3 -2
- package/dist/chunk-X56CQ6GB.js +157 -0
- package/dist/{chunk-BSLIYB5Q.js → chunk-XFN3YDZE.js} +1 -1
- package/dist/{cli-WLD3EBEM.js → cli-NH7VUNOU.js} +1 -1
- package/dist/cli.js +156 -82
- package/dist/{context-7EVC6W45.js → context-7VI3R6EK.js} +4 -4
- package/dist/flows-RI7FGHAM.js +120 -0
- package/dist/{generate-I6YQ5V2T.js → generate-YEM73KVB.js} +6 -6
- package/dist/{harden-72JMRT4E.js → harden-3KKCR7BL.js} +541 -3
- package/dist/{mcp-wire-3SWBM7D6.js → mcp-wire-TE2ZL6HD.js} +10 -10
- package/dist/{package-5BCMEBTE.js → package-JWDMWPV3.js} +47 -10
- package/dist/preflight-7W23GP4R.js +19 -0
- package/dist/preflight-IE2YWBAC.js +13 -0
- package/dist/private-only-DIA26I2E.js +74 -0
- package/dist/{server-GBA7BJGK.js → server-ZJPXPRZT.js} +10 -10
- package/dist/{stack-config-R4SPSUQZ.js → stack-config-LUTS2XAD.js} +2 -2
- package/dist/{state-PRBIIN7I.js → state-AOAYLYY5.js} +2 -2
- package/dist/{store-SDUR52Z5.js → store-OVB3DAJR.js} +1 -1
- package/dist/tailscale-cutover-6YP2QKFM.js +113 -0
- package/dist/{validate-YVI3BJ55.js → validate-JXKN5WD6.js} +1 -1
- package/package.json +39 -8
- package/spec/deploy-plan.schema.json +16 -1
- package/spec/mcp-tools.yaml +67 -9
|
@@ -2,8 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
// package.json
|
|
4
4
|
var name = "@clawops/cli";
|
|
5
|
-
var version = "2.0
|
|
6
|
-
var description = "Deploy and
|
|
5
|
+
var version = "2.1.0";
|
|
6
|
+
var description = "Deploy and operate self-hosted OpenClaw across AWS, GCP, Azure and local VMs \u2014 a CLI and an MCP server in one.";
|
|
7
|
+
var keywords = [
|
|
8
|
+
"openclaw",
|
|
9
|
+
"mcp",
|
|
10
|
+
"mcp-server",
|
|
11
|
+
"model-context-protocol",
|
|
12
|
+
"pulumi",
|
|
13
|
+
"pulumi-automation-api",
|
|
14
|
+
"infrastructure-as-code",
|
|
15
|
+
"iac",
|
|
16
|
+
"devops",
|
|
17
|
+
"deployment",
|
|
18
|
+
"self-hosted",
|
|
19
|
+
"cli",
|
|
20
|
+
"aws",
|
|
21
|
+
"gcp",
|
|
22
|
+
"azure",
|
|
23
|
+
"ai-agent",
|
|
24
|
+
"agent-ops",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"typescript",
|
|
27
|
+
"nodejs"
|
|
28
|
+
];
|
|
29
|
+
var homepage = "https://clawops.fyi";
|
|
30
|
+
var bugs = {
|
|
31
|
+
url: "https://github.com/dfridkin/clawops/issues"
|
|
32
|
+
};
|
|
33
|
+
var repository = {
|
|
34
|
+
type: "git",
|
|
35
|
+
url: "git+https://github.com/dfridkin/clawops.git"
|
|
36
|
+
};
|
|
37
|
+
var license = "MPL-2.0";
|
|
7
38
|
var type = "module";
|
|
8
39
|
var bin = {
|
|
9
40
|
clawops: "./dist/cli.js"
|
|
@@ -15,11 +46,6 @@ var files = [
|
|
|
15
46
|
"dist",
|
|
16
47
|
"spec"
|
|
17
48
|
];
|
|
18
|
-
var repository = {
|
|
19
|
-
type: "git",
|
|
20
|
-
url: "git+https://github.com/dfridkin/clawops.git"
|
|
21
|
-
};
|
|
22
|
-
var homepage = "https://clawops.fyi";
|
|
23
49
|
var mcpName = "io.github.dfridkin/clawops";
|
|
24
50
|
var publishConfig = {
|
|
25
51
|
access: "public",
|
|
@@ -43,7 +69,12 @@ var scripts = {
|
|
|
43
69
|
changeset: "changeset",
|
|
44
70
|
release: "pnpm build && changeset publish",
|
|
45
71
|
"test:mutation": "node scripts/dev/mutation-check.mjs",
|
|
46
|
-
graph: "node scripts/dev/graph.mjs"
|
|
72
|
+
graph: "node scripts/dev/graph.mjs",
|
|
73
|
+
"verify:pack": "node scripts/dev/verify-pack.mjs",
|
|
74
|
+
"verify:mcp": "node scripts/dev/mcp-probe.mjs",
|
|
75
|
+
"verify:docker": "node scripts/dev/verify-docker.mjs",
|
|
76
|
+
"version:packages": "changeset version && node scripts/sync-server-json.mjs",
|
|
77
|
+
"sync:server-json": "node scripts/sync-server-json.mjs"
|
|
47
78
|
};
|
|
48
79
|
var dependencies = {
|
|
49
80
|
"@aws-sdk/client-bedrock": "^3.1063.0",
|
|
@@ -106,12 +137,15 @@ var package_default = {
|
|
|
106
137
|
name,
|
|
107
138
|
version,
|
|
108
139
|
description,
|
|
140
|
+
keywords,
|
|
141
|
+
homepage,
|
|
142
|
+
bugs,
|
|
143
|
+
repository,
|
|
144
|
+
license,
|
|
109
145
|
type,
|
|
110
146
|
bin,
|
|
111
147
|
exports,
|
|
112
148
|
files,
|
|
113
|
-
repository,
|
|
114
|
-
homepage,
|
|
115
149
|
mcpName,
|
|
116
150
|
publishConfig,
|
|
117
151
|
engines,
|
|
@@ -123,6 +157,7 @@ var package_default = {
|
|
|
123
157
|
};
|
|
124
158
|
export {
|
|
125
159
|
bin,
|
|
160
|
+
bugs,
|
|
126
161
|
package_default as default,
|
|
127
162
|
dependencies,
|
|
128
163
|
description,
|
|
@@ -131,6 +166,8 @@ export {
|
|
|
131
166
|
exports,
|
|
132
167
|
files,
|
|
133
168
|
homepage,
|
|
169
|
+
keywords,
|
|
170
|
+
license,
|
|
134
171
|
mcpName,
|
|
135
172
|
name,
|
|
136
173
|
packageManager,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
REQUIRED_PROVIDERS,
|
|
4
|
+
availableSizes,
|
|
5
|
+
azurePreflight,
|
|
6
|
+
managementToken,
|
|
7
|
+
sizeCheck,
|
|
8
|
+
stateBackendCheck
|
|
9
|
+
} from "./chunk-BTW7NHOH.js";
|
|
10
|
+
import "./chunk-FEK7VJAY.js";
|
|
11
|
+
import "./chunk-RHOWYIWT.js";
|
|
12
|
+
export {
|
|
13
|
+
REQUIRED_PROVIDERS,
|
|
14
|
+
availableSizes,
|
|
15
|
+
azurePreflight,
|
|
16
|
+
managementToken,
|
|
17
|
+
sizeCheck,
|
|
18
|
+
stateBackendCheck
|
|
19
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
UsageError
|
|
4
|
+
} from "./chunk-KGXPLI7W.js";
|
|
5
|
+
|
|
6
|
+
// src/plan/private-only.ts
|
|
7
|
+
function privateOnlyNetwork(flags, resolved, override, stackName) {
|
|
8
|
+
if (flags.sshCidr !== void 0 || flags.gatewayCidr !== void 0) {
|
|
9
|
+
throw new UsageError(
|
|
10
|
+
"--private-only closes public SSH and gateway access, so it cannot be combined with --ssh-cidr or --gateway-cidr. Drop those flags, or drop --private-only to keep a public rule."
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
const tailnet = requireOverride(override, stackName);
|
|
14
|
+
return {
|
|
15
|
+
...resolved,
|
|
16
|
+
allowedSshCidrs: [],
|
|
17
|
+
allowedGatewayCidrs: [],
|
|
18
|
+
tailscale: { enabled: true, privateOnly: true, ip: tailnet.ip }
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function assertTailnetReachable(ctx, probe) {
|
|
22
|
+
const tailnet = requireOverride(ctx.config.stacks[ctx.stackName]?.tailscale, ctx.stackName);
|
|
23
|
+
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
24
|
+
let conn;
|
|
25
|
+
try {
|
|
26
|
+
const outputMap = await (await ctx.getStack()).outputs();
|
|
27
|
+
const raw = Object.fromEntries(Object.entries(outputMap).map(([k, v]) => [k, v.value]));
|
|
28
|
+
conn = ctx.adapter.getConnectionInfo({
|
|
29
|
+
...extractBaseOutputs(raw),
|
|
30
|
+
privateKeyPath: ctx.config.ssh.keyPath,
|
|
31
|
+
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
32
|
+
});
|
|
33
|
+
} catch {
|
|
34
|
+
throw new UsageError(
|
|
35
|
+
`Stack "${ctx.stackName}" has no deployment to make private. Deploy it, then run \`clawops harden --tailscale\`, then plan with --private-only.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (!await probe(conn)) {
|
|
39
|
+
throw new UsageError(
|
|
40
|
+
`This machine cannot reach "${ctx.stackName}" at its tailnet address ${tailnet.ip}, so closing the public ports would leave no way in. Check that Tailscale is running here and signed in to the same tailnet, then try again. Nothing was changed.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return tailnet.ip;
|
|
44
|
+
}
|
|
45
|
+
async function guardPrivateOnlyApply(plan, ctx, probe) {
|
|
46
|
+
const { network } = plan.spec;
|
|
47
|
+
if (network.tailscale?.privateOnly !== true) return;
|
|
48
|
+
if (network.allowedSshCidrs.length > 0 || network.allowedGatewayCidrs.length > 0) {
|
|
49
|
+
throw new UsageError(
|
|
50
|
+
`This plan is marked private-only but still opens public ports (SSH: ${network.allowedSshCidrs.join(", ") || "none"}; gateway: ${network.allowedGatewayCidrs.join(", ") || "none"}). Regenerate it with \`clawops plan --private-only\`.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const override = ctx.config.stacks[ctx.stackName]?.tailscale;
|
|
54
|
+
const current = requireOverride(override, ctx.stackName);
|
|
55
|
+
if (network.tailscale.ip !== current.ip) {
|
|
56
|
+
throw new UsageError(
|
|
57
|
+
`This plan was made for the tailnet address ${network.tailscale.ip ?? "(none)"}, but "${ctx.stackName}" is now reached at ${current.ip}. Regenerate it with \`clawops plan --private-only\`.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
await assertTailnetReachable(ctx, probe);
|
|
61
|
+
}
|
|
62
|
+
function requireOverride(override, stackName) {
|
|
63
|
+
if (!override) {
|
|
64
|
+
throw new UsageError(
|
|
65
|
+
`Stack "${stackName}" has no verified tailnet address, so closing its public ports would leave no way in. Run \`clawops harden --tailscale\` first; it only records an address this machine has reached.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return override;
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
assertTailnetReachable,
|
|
72
|
+
guardPrivateOnlyApply,
|
|
73
|
+
privateOnlyNetwork
|
|
74
|
+
};
|
|
@@ -5,27 +5,27 @@ import {
|
|
|
5
5
|
bearerFrom,
|
|
6
6
|
isLoopbackBind,
|
|
7
7
|
serveMcp
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-AYUX4FOF.js";
|
|
9
|
+
import "./chunk-R3DTA35B.js";
|
|
10
10
|
import "./chunk-BOPSG2LI.js";
|
|
11
11
|
import "./chunk-6ZFIFDBJ.js";
|
|
12
12
|
import "./chunk-3QJBNAHW.js";
|
|
13
13
|
import "./chunk-2I7KCMXZ.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-3Z5TDXCJ.js";
|
|
15
15
|
import "./chunk-AEEMRFZV.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-A2DS5SB7.js";
|
|
17
|
+
import "./chunk-XFN3YDZE.js";
|
|
18
18
|
import "./chunk-2Z7J3ERQ.js";
|
|
19
19
|
import "./chunk-E4NJSJ54.js";
|
|
20
20
|
import "./chunk-MC5ATCXS.js";
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-QDNFADJI.js";
|
|
22
|
+
import "./chunk-GFUK6KDR.js";
|
|
23
23
|
import "./chunk-NUL5F6YV.js";
|
|
24
24
|
import "./chunk-3OLUJPCB.js";
|
|
25
25
|
import "./chunk-NUDIHZEH.js";
|
|
26
26
|
import "./chunk-JW5A7E3X.js";
|
|
27
27
|
import "./chunk-LT4XMO5D.js";
|
|
28
|
-
import "./chunk-
|
|
28
|
+
import "./chunk-BELTQGHT.js";
|
|
29
29
|
import "./chunk-GIEVAGEK.js";
|
|
30
30
|
import "./chunk-VOEJYRIK.js";
|
|
31
31
|
import "./chunk-EI7CQQ7D.js";
|
|
@@ -33,8 +33,8 @@ import "./chunk-FEK7VJAY.js";
|
|
|
33
33
|
import "./chunk-RHOWYIWT.js";
|
|
34
34
|
import "./chunk-Q7NQY5HV.js";
|
|
35
35
|
import "./chunk-N4WZLWJ4.js";
|
|
36
|
-
import "./chunk-
|
|
37
|
-
import "./chunk-
|
|
36
|
+
import "./chunk-ELU3XQEA.js";
|
|
37
|
+
import "./chunk-64PONSA5.js";
|
|
38
38
|
import "./chunk-KGXPLI7W.js";
|
|
39
39
|
import "./chunk-VUFMZEIM.js";
|
|
40
40
|
export {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
writeStackConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3Z5TDXCJ.js";
|
|
5
5
|
import "./chunk-AEEMRFZV.js";
|
|
6
6
|
import "./chunk-VOEJYRIK.js";
|
|
7
7
|
import "./chunk-EI7CQQ7D.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-64PONSA5.js";
|
|
9
9
|
import "./chunk-KGXPLI7W.js";
|
|
10
10
|
export {
|
|
11
11
|
writeStackConfig
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isTailscaleIpv4,
|
|
4
|
+
parseStatus,
|
|
5
|
+
rootPrefix
|
|
6
|
+
} from "./chunk-X56CQ6GB.js";
|
|
7
|
+
import {
|
|
8
|
+
formatKnownHostsLine,
|
|
9
|
+
keyTypeFromBlob,
|
|
10
|
+
verifyAgainstKnownHosts
|
|
11
|
+
} from "./chunk-MC5ATCXS.js";
|
|
12
|
+
import "./chunk-64PONSA5.js";
|
|
13
|
+
import "./chunk-KGXPLI7W.js";
|
|
14
|
+
|
|
15
|
+
// src/harden/tailscale-cutover.ts
|
|
16
|
+
import { readFileSync, appendFileSync, mkdirSync } from "fs";
|
|
17
|
+
import path from "path";
|
|
18
|
+
function parseHostKeys(text) {
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const line of text.split("\n")) {
|
|
21
|
+
const [declared, b64] = line.trim().split(/\s+/);
|
|
22
|
+
if (!declared || !b64) continue;
|
|
23
|
+
let blob;
|
|
24
|
+
try {
|
|
25
|
+
blob = Buffer.from(b64, "base64");
|
|
26
|
+
} catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const actual = keyTypeFromBlob(blob);
|
|
30
|
+
if (!actual || actual !== declared) continue;
|
|
31
|
+
out.push({ type: actual, blob });
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function pinKeys(knownHostsPath, host, port, keys) {
|
|
36
|
+
let content = "";
|
|
37
|
+
try {
|
|
38
|
+
content = readFileSync(knownHostsPath, "utf-8");
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
let added = 0;
|
|
42
|
+
mkdirSync(path.dirname(knownHostsPath), { recursive: true });
|
|
43
|
+
for (const k of keys) {
|
|
44
|
+
if (verifyAgainstKnownHosts(content, host, port, k.blob) === "match") continue;
|
|
45
|
+
const line = formatKnownHostsLine(host, port, k.type, k.blob);
|
|
46
|
+
appendFileSync(knownHostsPath, line, "utf-8");
|
|
47
|
+
content += line;
|
|
48
|
+
added += 1;
|
|
49
|
+
}
|
|
50
|
+
return added;
|
|
51
|
+
}
|
|
52
|
+
async function verifyTailnetAddress(publicConn, deps) {
|
|
53
|
+
const status = parseStatus((await deps.exec("tailscale status --json 2>/dev/null || true")).stdout);
|
|
54
|
+
if (status.state !== "Running" || !status.ipv4 || !isTailscaleIpv4(status.ipv4)) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "The host is not on a tailnet yet, so there is no address to move to. Run `clawops harden --options tailscale` first."
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const ip = status.ipv4;
|
|
61
|
+
const keys = parseHostKeys((await deps.exec("cat /etc/ssh/ssh_host_*_key.pub 2>/dev/null")).stdout);
|
|
62
|
+
if (keys.length === 0) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: "Could not read the host keys over the public connection, so the tailnet address cannot be tied to this host. Nothing was changed."
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const pinned = pinKeys(publicConn.knownHostsPath, ip, publicConn.port, keys);
|
|
69
|
+
const reached = await deps.probe({ ...publicConn, host: ip });
|
|
70
|
+
if (!reached) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
reason: `The host is on the tailnet as ${ip}, but this machine could not reach it there. The usual cause is that this machine is not on the same tailnet: install Tailscale here and sign in to the same account. clawops still uses the public address; nothing else was changed.`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return { ok: true, ip, ...status.hostname ? { hostname: status.hostname } : {}, pinned };
|
|
77
|
+
}
|
|
78
|
+
async function probeSsh(conn, signal) {
|
|
79
|
+
const { connect } = await import("./ssh-VAI5DU5M.js");
|
|
80
|
+
let session;
|
|
81
|
+
try {
|
|
82
|
+
session = await connect({
|
|
83
|
+
host: conn.host,
|
|
84
|
+
port: conn.port,
|
|
85
|
+
user: conn.user,
|
|
86
|
+
privateKeyPath: conn.privateKeyPath,
|
|
87
|
+
knownHostsPath: conn.knownHostsPath,
|
|
88
|
+
signal
|
|
89
|
+
});
|
|
90
|
+
return (await session.exec("true", signal)).code === 0;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
} finally {
|
|
94
|
+
session?.close();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function leaveTailnet(exec) {
|
|
98
|
+
const sudo = await rootPrefix(exec);
|
|
99
|
+
const r = await exec(`${sudo}tailscale logout 2>&1`);
|
|
100
|
+
if (r.code === 0) return { ok: true };
|
|
101
|
+
if (/not logged in|NeedsLogin/i.test(r.stdout)) return { ok: true };
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
reason: `tailscale logout failed on the host: ${r.stdout.trim().split("\n").slice(-1)[0] || `exit ${r.code}`}`
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
leaveTailnet,
|
|
109
|
+
parseHostKeys,
|
|
110
|
+
pinKeys,
|
|
111
|
+
probeSsh,
|
|
112
|
+
verifyTailnetAddress
|
|
113
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawops/cli",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "Deploy and
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Deploy and operate self-hosted OpenClaw across AWS, GCP, Azure and local VMs — a CLI and an MCP server in one.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openclaw",
|
|
7
|
+
"mcp",
|
|
8
|
+
"mcp-server",
|
|
9
|
+
"model-context-protocol",
|
|
10
|
+
"pulumi",
|
|
11
|
+
"pulumi-automation-api",
|
|
12
|
+
"infrastructure-as-code",
|
|
13
|
+
"iac",
|
|
14
|
+
"devops",
|
|
15
|
+
"deployment",
|
|
16
|
+
"self-hosted",
|
|
17
|
+
"cli",
|
|
18
|
+
"aws",
|
|
19
|
+
"gcp",
|
|
20
|
+
"azure",
|
|
21
|
+
"ai-agent",
|
|
22
|
+
"agent-ops",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"typescript",
|
|
25
|
+
"nodejs"
|
|
26
|
+
],
|
|
27
|
+
"homepage": "https://clawops.fyi",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/dfridkin/clawops/issues"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/dfridkin/clawops.git"
|
|
34
|
+
},
|
|
35
|
+
"license": "MPL-2.0",
|
|
5
36
|
"type": "module",
|
|
6
37
|
"bin": {
|
|
7
38
|
"clawops": "./dist/cli.js"
|
|
@@ -13,11 +44,6 @@
|
|
|
13
44
|
"dist",
|
|
14
45
|
"spec"
|
|
15
46
|
],
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/dfridkin/clawops.git"
|
|
19
|
-
},
|
|
20
|
-
"homepage": "https://clawops.fyi",
|
|
21
47
|
"mcpName": "io.github.dfridkin/clawops",
|
|
22
48
|
"publishConfig": {
|
|
23
49
|
"access": "public",
|
|
@@ -40,7 +66,12 @@
|
|
|
40
66
|
"changeset": "changeset",
|
|
41
67
|
"release": "pnpm build && changeset publish",
|
|
42
68
|
"test:mutation": "node scripts/dev/mutation-check.mjs",
|
|
43
|
-
"graph": "node scripts/dev/graph.mjs"
|
|
69
|
+
"graph": "node scripts/dev/graph.mjs",
|
|
70
|
+
"verify:pack": "node scripts/dev/verify-pack.mjs",
|
|
71
|
+
"verify:mcp": "node scripts/dev/mcp-probe.mjs",
|
|
72
|
+
"verify:docker": "node scripts/dev/verify-docker.mjs",
|
|
73
|
+
"version:packages": "changeset version && node scripts/sync-server-json.mjs",
|
|
74
|
+
"sync:server-json": "node scripts/sync-server-json.mjs"
|
|
44
75
|
},
|
|
45
76
|
"dependencies": {
|
|
46
77
|
"@aws-sdk/client-bedrock": "^3.1063.0",
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"type": "string",
|
|
163
163
|
"format": "cidr"
|
|
164
164
|
},
|
|
165
|
-
"description": "CIDRs allowed to reach the gateway port. Only meaningful with publishGateway \"all\"
|
|
165
|
+
"description": "CIDRs allowed to reach the gateway port. Only meaningful with publishGateway \"all\" \u2014 under \"loopback\" nothing listens on a routable interface, so these rules would admit traffic to a closed port. clawops refuses that combination rather than creating rules that do nothing."
|
|
166
166
|
},
|
|
167
167
|
"gatewayPort": {
|
|
168
168
|
"type": "integer",
|
|
@@ -181,6 +181,14 @@
|
|
|
181
181
|
"authKeyRef": {
|
|
182
182
|
"type": "string",
|
|
183
183
|
"description": "Reference to a secret containing the Tailscale auth key"
|
|
184
|
+
},
|
|
185
|
+
"privateOnly": {
|
|
186
|
+
"type": "boolean",
|
|
187
|
+
"description": "The plan closes public SSH and gateway ingress because the stack is reached over its tailnet (WO-34 step 6; ADR 0013). Apply refuses it unless the stack has a verified tailnet address, equal to `ip`, that answers SSH at apply time. Both CIDR lists must be empty."
|
|
188
|
+
},
|
|
189
|
+
"ip": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"description": "The tailnet address the plan relies on once public ingress is closed. Recorded so the reviewer sees which address remains, and so apply can refuse a plan made for an address the stack no longer has."
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
194
|
},
|
|
@@ -253,6 +261,13 @@
|
|
|
253
261
|
},
|
|
254
262
|
"totalChanges": {
|
|
255
263
|
"type": "integer"
|
|
264
|
+
},
|
|
265
|
+
"replace": {
|
|
266
|
+
"type": "array",
|
|
267
|
+
"description": "Resources Pulumi will destroy and recreate. A replaced instance loses its boot disk, and with it every byte of OpenClaw state on the host.",
|
|
268
|
+
"items": {
|
|
269
|
+
"$ref": "#/$defs/ResourceRef"
|
|
270
|
+
}
|
|
256
271
|
}
|
|
257
272
|
}
|
|
258
273
|
}
|
package/spec/mcp-tools.yaml
CHANGED
|
@@ -86,8 +86,9 @@ tools:
|
|
|
86
86
|
actually running.
|
|
87
87
|
|
|
88
88
|
Do NOT use when: you already know the problem and want to fix it. This tool only
|
|
89
|
-
reports — it changes nothing, and never runs `openclaw doctor --fix`.
|
|
90
|
-
|
|
89
|
+
reports — it changes nothing, and never runs `openclaw doctor --fix`. No tool
|
|
90
|
+
repairs a gateway: `clawops gateway update` is CLI-only, so tell the user to run
|
|
91
|
+
it themselves, after `clawops backup create`.
|
|
91
92
|
|
|
92
93
|
Every check carries a status: fail (something is wrong that clawops can name),
|
|
93
94
|
warn (worth knowing, not broken), info (did not apply). `ok` is false only when
|
|
@@ -125,8 +126,9 @@ tools:
|
|
|
125
126
|
|
|
126
127
|
Do NOT use when: the user wants real-time streaming logs (those are not
|
|
127
128
|
well-suited to tool calls; suggest the user run `clawops logs -f` directly
|
|
128
|
-
in their terminal). Do NOT use for instance-level system logs
|
|
129
|
-
|
|
129
|
+
in their terminal). Do NOT use for instance-level system logs: no tool runs
|
|
130
|
+
arbitrary remote commands, so tell the user to run `clawops ssh --command
|
|
131
|
+
'journalctl ...'` themselves.
|
|
130
132
|
annotations:
|
|
131
133
|
title: Tail Gateway Logs
|
|
132
134
|
readOnlyHint: true
|
|
@@ -238,7 +240,8 @@ tools:
|
|
|
238
240
|
Use when: the user wants to see which agents are running, debug agent
|
|
239
241
|
routing, or count active workspaces.
|
|
240
242
|
|
|
241
|
-
Do NOT use when: the user wants agent logs
|
|
243
|
+
Do NOT use when: the user wants one agent's logs — no tool exposes those; tell
|
|
244
|
+
the user to run `clawops agents logs <name>`.
|
|
242
245
|
annotations:
|
|
243
246
|
title: List OpenClaw Agents
|
|
244
247
|
readOnlyHint: true
|
|
@@ -332,9 +335,10 @@ tools:
|
|
|
332
335
|
Use when: the user explicitly asks to destroy, tear down, delete, or
|
|
333
336
|
remove a stack. Always confirm the stack name.
|
|
334
337
|
|
|
335
|
-
Do NOT use when: the user wants to stop the gateway temporarily —
|
|
336
|
-
|
|
337
|
-
list first with
|
|
338
|
+
Do NOT use when: the user wants to stop the gateway temporarily — that is not
|
|
339
|
+
a destroy, and no tool stops a gateway; clawops_gateway_restart is the only
|
|
340
|
+
gateway tool. Do NOT use when in doubt about which stack; list first with
|
|
341
|
+
clawops_stacks_list.
|
|
338
342
|
annotations:
|
|
339
343
|
title: Destroy Stack (DESTRUCTIVE)
|
|
340
344
|
readOnlyHint: false
|
|
@@ -393,7 +397,12 @@ tools:
|
|
|
393
397
|
stackName: { type: string, optional: true }
|
|
394
398
|
provider: { type: enum, values: [aws, gcp, azure, local], optional: true }
|
|
395
399
|
region: { type: string, optional: true }
|
|
396
|
-
instanceType: { type:
|
|
400
|
+
instanceType: { type: string, optional: true, description: "A clawops alias (micro|small|medium|large|gpu) or a machine type the cloud names itself, e.g. t3.small" }
|
|
401
|
+
openclawVersion: { type: string, optional: true, description: "semver, or 'stable'/'dev'" }
|
|
402
|
+
sshCidr: { type: string, optional: true, description: "CIDR(s) allowed to reach SSH, comma-separated, or 'auto' for this machine. Omitted = none, and nothing will be able to connect" }
|
|
403
|
+
gatewayCidr: { type: string, optional: true, description: "CIDR(s) allowed to reach the gateway port, or 'auto'. Requires publishGateway=all" }
|
|
404
|
+
publishGateway: { type: enum, values: [loopback, all], optional: true, description: "Which interface the gateway binds. loopback (default) keeps it off the network" }
|
|
405
|
+
privateOnly: { type: boolean, optional: true, description: "Close public SSH and gateway access; reach the stack over its tailnet. Requires a verified tailnet address (clawops_harden with tailscale), and refuses unless that address answers SSH now" }
|
|
397
406
|
outPath: { type: string, optional: true, description: "Absolute path to write plan; if omitted, plan returned inline" }
|
|
398
407
|
output:
|
|
399
408
|
planPath: { type: string, description: "Absolute path; only set if outPath provided" }
|
|
@@ -499,6 +508,55 @@ tools:
|
|
|
499
508
|
# COMPOSITE WORKFLOW TOOLS (R2: encode user intent, not API sequences)
|
|
500
509
|
# ────────────────────────────────────────────────────────────────────────────
|
|
501
510
|
|
|
511
|
+
- name: clawops_harden
|
|
512
|
+
toolset: [cli]
|
|
513
|
+
description: |
|
|
514
|
+
Apply security hardening to a deployed stack: SSH, UFW, fail2ban,
|
|
515
|
+
unattended-upgrades, the Docker socket, and per-cloud checks. Optionally join
|
|
516
|
+
the stack to a Tailscale network and reach it there instead of over the public
|
|
517
|
+
internet.
|
|
518
|
+
|
|
519
|
+
Use when: the user asks to harden, secure, or lock down a stack; asks what the
|
|
520
|
+
hardening report says (with dryRun: true, which changes nothing); or asks to put
|
|
521
|
+
a stack on their tailnet.
|
|
522
|
+
|
|
523
|
+
tailscale: true installs Tailscale, joins the tailnet as clawops-<stack>, and
|
|
524
|
+
then moves clawops onto that address — but only after opening an SSH session to
|
|
525
|
+
it, against host keys pinned over the connection already trusted. If that fails,
|
|
526
|
+
nothing is recorded and the public address stays in use. The auth key comes from
|
|
527
|
+
`clawops secret set TAILSCALE_AUTH_KEY` and is never passed through this tool
|
|
528
|
+
(R6).
|
|
529
|
+
|
|
530
|
+
tailscaleRevert: true takes the host back off the tailnet, over its public
|
|
531
|
+
address. On a private-only stack it refuses and names the plan/apply commands
|
|
532
|
+
that reopen SSH first — relay them rather than trying to work around it.
|
|
533
|
+
|
|
534
|
+
To close the public ports afterwards, plan with privateOnly: true and apply that
|
|
535
|
+
plan; this tool does not change firewall rules.
|
|
536
|
+
|
|
537
|
+
Do NOT use when: the user wants to know whether a stack is healthy — that is
|
|
538
|
+
clawops_doctor. Do NOT pass tailscale and tailscaleRevert together.
|
|
539
|
+
annotations:
|
|
540
|
+
title: Harden Stack
|
|
541
|
+
readOnlyHint: false
|
|
542
|
+
destructiveHint: true
|
|
543
|
+
idempotentHint: true
|
|
544
|
+
openWorldHint: true
|
|
545
|
+
input:
|
|
546
|
+
stackName: { type: string, optional: true }
|
|
547
|
+
options: { type: string, optional: true, description: "Comma-separated module IDs; default is every defaultOn module for the provider" }
|
|
548
|
+
dryRun: { type: boolean, optional: true, default: false, description: "Report the current state, change nothing" }
|
|
549
|
+
tailscale: { type: boolean, optional: true, default: false, description: "Join the tailnet, verify this machine reaches the host there, then use that address" }
|
|
550
|
+
tailscaleRevert: { type: boolean, optional: true, default: false, description: "Leave the tailnet and go back to the public address" }
|
|
551
|
+
yes: { type: boolean, optional: true, default: false, description: "Skip elicitation; CI/scripted use only" }
|
|
552
|
+
output:
|
|
553
|
+
modules:
|
|
554
|
+
type: array
|
|
555
|
+
description: "Per module: id, status, whether it changed anything, and detail"
|
|
556
|
+
tailnet:
|
|
557
|
+
type: object
|
|
558
|
+
description: "Set when tailscale or tailscaleRevert ran: the address now in use, or why it was refused"
|
|
559
|
+
|
|
502
560
|
- name: clawops_workflow_deploy_app
|
|
503
561
|
toolset: [workflow]
|
|
504
562
|
description: |
|