@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
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
applyPlan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-R3DTA35B.js";
|
|
5
5
|
import "./chunk-BOPSG2LI.js";
|
|
6
6
|
import "./chunk-6ZFIFDBJ.js";
|
|
7
7
|
import "./chunk-2I7KCMXZ.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-3Z5TDXCJ.js";
|
|
9
9
|
import "./chunk-AEEMRFZV.js";
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-XFN3YDZE.js";
|
|
11
|
+
import "./chunk-QDNFADJI.js";
|
|
12
12
|
import "./chunk-NUDIHZEH.js";
|
|
13
13
|
import "./chunk-JW5A7E3X.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-BELTQGHT.js";
|
|
15
15
|
import "./chunk-GIEVAGEK.js";
|
|
16
16
|
import "./chunk-VOEJYRIK.js";
|
|
17
17
|
import "./chunk-EI7CQQ7D.js";
|
|
18
18
|
import "./chunk-FEK7VJAY.js";
|
|
19
19
|
import "./chunk-RHOWYIWT.js";
|
|
20
20
|
import "./chunk-N4WZLWJ4.js";
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-ELU3XQEA.js";
|
|
22
|
+
import "./chunk-64PONSA5.js";
|
|
23
23
|
import "./chunk-KGXPLI7W.js";
|
|
24
24
|
import "./chunk-VUFMZEIM.js";
|
|
25
25
|
export {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ensurePulumiCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PSOSBJVO.js";
|
|
5
5
|
import {
|
|
6
6
|
ensurePassphrase
|
|
7
7
|
} from "./chunk-V25QRZRB.js";
|
|
8
8
|
import {
|
|
9
9
|
getConfigDir
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-64PONSA5.js";
|
|
11
11
|
import "./chunk-KGXPLI7W.js";
|
|
12
12
|
|
|
13
13
|
// src/pulumi/automation.ts
|
|
14
14
|
import path from "path";
|
|
15
|
-
import { LocalWorkspace } from "@pulumi/pulumi/automation";
|
|
15
|
+
import { LocalWorkspace } from "@pulumi/pulumi/automation/index.js";
|
|
16
16
|
async function getOrCreateStack(opts) {
|
|
17
17
|
const configDir = opts.configDir ?? getConfigDir();
|
|
18
18
|
const pulumiCommand = await ensurePulumiCli({ configDir, onInstall: opts.onInstall });
|
|
@@ -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-ELU3XQEA.js";
|
|
18
|
+
import "./chunk-64PONSA5.js";
|
|
19
19
|
import {
|
|
20
20
|
ProviderError
|
|
21
21
|
} from "./chunk-KGXPLI7W.js";
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-VOEJYRIK.js";
|
|
8
8
|
import {
|
|
9
9
|
getConfig
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-64PONSA5.js";
|
|
11
11
|
import {
|
|
12
12
|
UsageError
|
|
13
13
|
} from "./chunk-KGXPLI7W.js";
|
|
@@ -34,7 +34,7 @@ async function writeStackConfig(stack, plan) {
|
|
|
34
34
|
value: String(plan.spec.network?.gatewayPort ?? GATEWAY_PORT)
|
|
35
35
|
});
|
|
36
36
|
if (plan.spec.provider === "gcp") {
|
|
37
|
-
const { resolveProjectId } = await import("./preflight-
|
|
37
|
+
const { resolveProjectId } = await import("./preflight-IE2YWBAC.js");
|
|
38
38
|
const project = resolveProjectId();
|
|
39
39
|
if (project) await stack.setConfig("gcp:project", { value: project });
|
|
40
40
|
}
|
|
@@ -22,12 +22,21 @@ var LocalOptsSchema = z.object({
|
|
|
22
22
|
sshPort: z.number(),
|
|
23
23
|
sshKeyPath: z.string()
|
|
24
24
|
});
|
|
25
|
+
var TailscaleOverrideSchema = z.object({
|
|
26
|
+
ip: z.string(),
|
|
27
|
+
hostname: z.string().optional(),
|
|
28
|
+
/** When the address was verified, ISO 8601. */
|
|
29
|
+
verifiedAt: z.string(),
|
|
30
|
+
/** Set once --private-only has closed the public ports, so revert knows to reopen them. */
|
|
31
|
+
privateOnly: z.boolean().optional()
|
|
32
|
+
});
|
|
25
33
|
var StackConfigSchema = z.object({
|
|
26
34
|
provider: z.string(),
|
|
27
35
|
stateUrl: z.string(),
|
|
28
36
|
region: z.string().optional(),
|
|
29
37
|
credentialsRef: CredentialsRefSchema,
|
|
30
|
-
localOpts: LocalOptsSchema.optional()
|
|
38
|
+
localOpts: LocalOptsSchema.optional(),
|
|
39
|
+
tailscale: TailscaleOverrideSchema.optional()
|
|
31
40
|
});
|
|
32
41
|
var ClawopsConfigSchema = z.object({
|
|
33
42
|
version: z.literal(1),
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
validatePlan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XFN3YDZE.js";
|
|
5
5
|
import {
|
|
6
6
|
buildContext,
|
|
7
7
|
loadAdapterModule
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QDNFADJI.js";
|
|
9
9
|
import {
|
|
10
10
|
getConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-64PONSA5.js";
|
|
12
12
|
import {
|
|
13
13
|
StateError,
|
|
14
14
|
UsageError
|
|
@@ -16,11 +16,12 @@ import {
|
|
|
16
16
|
|
|
17
17
|
// src/plan/generate.ts
|
|
18
18
|
import { randomUUID } from "crypto";
|
|
19
|
-
var PREVIEW_LINE_RE = /^([+~-])\s+(\S+)\s+(\S+)?/;
|
|
19
|
+
var PREVIEW_LINE_RE = /^(\+-|-\+|[+~-])\s+(\S+)\s+(\S+)?/;
|
|
20
20
|
function parseDiff(lines) {
|
|
21
21
|
const create = [];
|
|
22
22
|
const update = [];
|
|
23
23
|
const del = [];
|
|
24
|
+
const replace = [];
|
|
24
25
|
const seen = /* @__PURE__ */ new Set();
|
|
25
26
|
for (const line of lines) {
|
|
26
27
|
const m = PREVIEW_LINE_RE.exec(line.trimStart());
|
|
@@ -34,7 +35,8 @@ function parseDiff(lines) {
|
|
|
34
35
|
type: resourceType,
|
|
35
36
|
...name ? { name } : {}
|
|
36
37
|
};
|
|
37
|
-
if (op === "
|
|
38
|
+
if (op === "+-" || op === "-+") replace.push(ref);
|
|
39
|
+
else if (op === "+") create.push(ref);
|
|
38
40
|
else if (op === "~") update.push({ resource: ref, before: null, after: null });
|
|
39
41
|
else if (op === "-") del.push(ref);
|
|
40
42
|
}
|
|
@@ -42,7 +44,8 @@ function parseDiff(lines) {
|
|
|
42
44
|
create,
|
|
43
45
|
update,
|
|
44
46
|
delete: del,
|
|
45
|
-
|
|
47
|
+
replace,
|
|
48
|
+
totalChanges: create.length + update.length + del.length + replace.length
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
51
|
var INSTANCE_ALIASES = ["micro", "small", "medium", "large", "gpu"];
|
|
@@ -68,7 +71,7 @@ async function generatePlan(intent, _opts) {
|
|
|
68
71
|
"plan/apply is not supported for the local provider. Use `clawops up` directly."
|
|
69
72
|
);
|
|
70
73
|
}
|
|
71
|
-
const { version } = await import("./package-
|
|
74
|
+
const { version } = await import("./package-JWDMWPV3.js");
|
|
72
75
|
const config = getConfig();
|
|
73
76
|
const instanceType = await resolveInstanceType(intent);
|
|
74
77
|
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
|
|
@@ -120,7 +123,7 @@ A plan is only as good as the state it was computed against, so this is not a pl
|
|
|
120
123
|
);
|
|
121
124
|
}
|
|
122
125
|
try {
|
|
123
|
-
const { writeStackConfig } = await import("./stack-config-
|
|
126
|
+
const { writeStackConfig } = await import("./stack-config-LUTS2XAD.js");
|
|
124
127
|
await writeStackConfig(stack, plan);
|
|
125
128
|
const outputLines = [];
|
|
126
129
|
const preview = await stack.preview({
|
|
@@ -148,7 +151,7 @@ A plan is only as good as the state it was computed against, so this is not a pl
|
|
|
148
151
|
throw new Error(`Generated plan failed schema validation:
|
|
149
152
|
${validation.errors.join("\n")}`);
|
|
150
153
|
}
|
|
151
|
-
const { validatePlanConfig, validatePlanNetwork } = await import("./validate-
|
|
154
|
+
const { validatePlanConfig, validatePlanNetwork } = await import("./validate-JXKN5WD6.js");
|
|
152
155
|
const cfgCheck = await validatePlanConfig(plan);
|
|
153
156
|
for (const w of cfgCheck.warnings) process.stderr.write(`[clawops] warning: ${w}
|
|
154
157
|
`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
applyPlan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-R3DTA35B.js";
|
|
5
5
|
import {
|
|
6
6
|
extractBaseOutputs
|
|
7
7
|
} from "./chunk-3QJBNAHW.js";
|
|
@@ -12,22 +12,22 @@ import {
|
|
|
12
12
|
} from "./chunk-2I7KCMXZ.js";
|
|
13
13
|
import {
|
|
14
14
|
generatePlan
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-A2DS5SB7.js";
|
|
16
16
|
import {
|
|
17
17
|
validatePlan
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-XFN3YDZE.js";
|
|
19
19
|
import {
|
|
20
20
|
acquireSession,
|
|
21
21
|
drainPool
|
|
22
22
|
} from "./chunk-2Z7J3ERQ.js";
|
|
23
23
|
import {
|
|
24
24
|
buildContext
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-QDNFADJI.js";
|
|
26
26
|
import {
|
|
27
27
|
formatUptime,
|
|
28
28
|
gatherSnapshot,
|
|
29
29
|
runDiagnostics
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-GFUK6KDR.js";
|
|
31
31
|
import {
|
|
32
32
|
GATEWAY_LOGS_PROBE,
|
|
33
33
|
chooseLogSource,
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
import {
|
|
49
49
|
getConfig,
|
|
50
50
|
getConfigDir
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-64PONSA5.js";
|
|
52
52
|
import {
|
|
53
53
|
StateError,
|
|
54
54
|
UsageError
|
|
@@ -121,7 +121,7 @@ function withAudit(toolName, handler) {
|
|
|
121
121
|
tool: toolName,
|
|
122
122
|
args: sanitize(input),
|
|
123
123
|
durationMs: Date.now() - start,
|
|
124
|
-
result: "ok"
|
|
124
|
+
result: result.isError === true ? "error" : "ok"
|
|
125
125
|
});
|
|
126
126
|
return result;
|
|
127
127
|
} catch (err) {
|
|
@@ -267,7 +267,12 @@ var clawops_planSchema = z.object({
|
|
|
267
267
|
stackName: z.string().optional(),
|
|
268
268
|
provider: z.enum(["aws", "gcp", "azure", "local"]).optional(),
|
|
269
269
|
region: z.string().optional(),
|
|
270
|
-
instanceType: z.
|
|
270
|
+
instanceType: z.string().optional(),
|
|
271
|
+
openclawVersion: z.string().optional(),
|
|
272
|
+
sshCidr: z.string().optional(),
|
|
273
|
+
gatewayCidr: z.string().optional(),
|
|
274
|
+
publishGateway: z.enum(["loopback", "all"]).optional(),
|
|
275
|
+
privateOnly: z.boolean().optional(),
|
|
271
276
|
outPath: z.string().optional()
|
|
272
277
|
});
|
|
273
278
|
var clawops_planAnnotations = {
|
|
@@ -327,6 +332,22 @@ var clawops_gateway_restartAnnotations = {
|
|
|
327
332
|
openWorldHint: true,
|
|
328
333
|
toolsets: ["cli"]
|
|
329
334
|
};
|
|
335
|
+
var clawops_hardenSchema = z.object({
|
|
336
|
+
stackName: z.string().optional(),
|
|
337
|
+
options: z.string().optional(),
|
|
338
|
+
dryRun: z.boolean().optional().default(false),
|
|
339
|
+
tailscale: z.boolean().optional().default(false),
|
|
340
|
+
tailscaleRevert: z.boolean().optional().default(false),
|
|
341
|
+
yes: z.boolean().optional().default(false)
|
|
342
|
+
});
|
|
343
|
+
var clawops_hardenAnnotations = {
|
|
344
|
+
title: "Harden Stack",
|
|
345
|
+
readOnlyHint: false,
|
|
346
|
+
destructiveHint: true,
|
|
347
|
+
idempotentHint: true,
|
|
348
|
+
openWorldHint: true,
|
|
349
|
+
toolsets: ["cli"]
|
|
350
|
+
};
|
|
330
351
|
var clawops_workflow_deploy_appSchema = z.object({
|
|
331
352
|
provider: z.enum(["aws", "gcp", "azure", "local"]),
|
|
332
353
|
region: z.string().optional(),
|
|
@@ -379,6 +400,7 @@ var TOOLSETS = {
|
|
|
379
400
|
"clawops_config_unset",
|
|
380
401
|
"clawops_config_validate",
|
|
381
402
|
"clawops_gateway_restart",
|
|
403
|
+
"clawops_harden",
|
|
382
404
|
"clawops_task_status"
|
|
383
405
|
],
|
|
384
406
|
workflow: [
|
|
@@ -828,7 +850,7 @@ async function handleUp(input, server) {
|
|
|
828
850
|
);
|
|
829
851
|
}
|
|
830
852
|
const { localOpts } = stackConfig;
|
|
831
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
853
|
+
const { localBootstrap } = await import("./bootstrap-NN647RCY.js");
|
|
832
854
|
const ac = new AbortController();
|
|
833
855
|
const state = await localBootstrap({
|
|
834
856
|
host: localOpts.host,
|
|
@@ -851,8 +873,8 @@ async function handleUp(input, server) {
|
|
|
851
873
|
const progressToken = void 0;
|
|
852
874
|
const emit = makeProgressEmitter(server, progressToken);
|
|
853
875
|
startTask(taskId, `clawops_up stack=${ctx.stackName}`);
|
|
854
|
-
const { generatePlan: generatePlan2 } = await import("./generate-
|
|
855
|
-
const { applyPlan: applyPlan2 } = await import("./apply-
|
|
876
|
+
const { generatePlan: generatePlan2 } = await import("./generate-YEM73KVB.js");
|
|
877
|
+
const { applyPlan: applyPlan2 } = await import("./apply-HJ5GAKHW.js");
|
|
856
878
|
const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
|
|
857
879
|
const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
|
|
858
880
|
const network = await resolveNetworkFlags(
|
|
@@ -955,13 +977,32 @@ async function handlePlan(input, _server) {
|
|
|
955
977
|
}
|
|
956
978
|
const provider = input.provider ?? ctx.adapter.name;
|
|
957
979
|
const stackName = ctx.stackName;
|
|
980
|
+
const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
|
|
981
|
+
const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
|
|
982
|
+
const flags = {
|
|
983
|
+
sshCidr: input.sshCidr,
|
|
984
|
+
gatewayCidr: input.gatewayCidr,
|
|
985
|
+
publishGateway: input.publishGateway
|
|
986
|
+
};
|
|
958
987
|
let plan;
|
|
959
988
|
try {
|
|
989
|
+
const resolved = await resolveNetworkFlags(flags, {
|
|
990
|
+
detectEgressIp: () => detectEgressIp("https://ifconfig.me/ip")
|
|
991
|
+
});
|
|
992
|
+
let network = resolved;
|
|
993
|
+
if (input.privateOnly) {
|
|
994
|
+
const { privateOnlyNetwork, assertTailnetReachable } = await import("./private-only-DIA26I2E.js");
|
|
995
|
+
const { probeSsh } = await import("./tailscale-cutover-6YP2QKFM.js");
|
|
996
|
+
network = privateOnlyNetwork(flags, resolved, ctx.config.stacks[stackName]?.tailscale, stackName);
|
|
997
|
+
await assertTailnetReachable(ctx, (conn) => probeSsh(conn));
|
|
998
|
+
}
|
|
960
999
|
plan = await generatePlan({
|
|
961
1000
|
stackName,
|
|
962
1001
|
provider,
|
|
963
1002
|
region: input.region,
|
|
964
|
-
instanceType: input.instanceType
|
|
1003
|
+
instanceType: input.instanceType,
|
|
1004
|
+
openclawVersion: input.openclawVersion,
|
|
1005
|
+
network
|
|
965
1006
|
});
|
|
966
1007
|
} catch (err) {
|
|
967
1008
|
return errText(`Plan generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -981,6 +1022,78 @@ async function handlePlan(input, _server) {
|
|
|
981
1022
|
};
|
|
982
1023
|
}
|
|
983
1024
|
|
|
1025
|
+
// src/mcp/tools/cli/harden.ts
|
|
1026
|
+
async function handleHarden(input, server) {
|
|
1027
|
+
if (input.tailscale && input.tailscaleRevert) {
|
|
1028
|
+
return errText(
|
|
1029
|
+
"tailscale and tailscaleRevert do the opposite of each other; pass one, not both."
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
const { getConfig: getConfig2 } = await import("./store-OVB3DAJR.js");
|
|
1033
|
+
const config = getConfig2();
|
|
1034
|
+
if (!config) return errText("No clawops config found. The user needs to run `clawops init` first.");
|
|
1035
|
+
const ctx = buildContext({ stack: input.stackName });
|
|
1036
|
+
if (!input.dryRun && !input.yes) {
|
|
1037
|
+
const what = input.tailscaleRevert ? `Take "${ctx.stackName}" off its tailnet and go back to its public address?` : input.tailscale ? `Harden "${ctx.stackName}" and join it to your tailnet? clawops will use the tailnet address afterwards.` : `Apply hardening modules to "${ctx.stackName}"? This changes the running host.`;
|
|
1038
|
+
const elicit = await server.server.elicitInput({
|
|
1039
|
+
message: what,
|
|
1040
|
+
requestedSchema: {
|
|
1041
|
+
type: "object",
|
|
1042
|
+
properties: { confirmed: { type: "boolean", title: "Confirm" } },
|
|
1043
|
+
required: ["confirmed"]
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
|
|
1047
|
+
return okText("Hardening cancelled. Nothing was changed.");
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const { revertTailnet, cutOverToTailnet, connectionFor } = await import("./flows-RI7FGHAM.js");
|
|
1051
|
+
if (input.tailscaleRevert) {
|
|
1052
|
+
const outcome = await revertTailnet(ctx, config);
|
|
1053
|
+
return outcome.ok ? okText(outcome.message) : errText(outcome.reason);
|
|
1054
|
+
}
|
|
1055
|
+
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary, makeTailscaleModule } = await import("./harden-3KKCR7BL.js");
|
|
1056
|
+
const forStack = makeTailscaleModule(ctx.stackName);
|
|
1057
|
+
const resolved = resolveModules(MODULE_CATALOG, input.options, ctx.adapter.name).map(
|
|
1058
|
+
(m) => m.id === "tailscale" ? forStack : m
|
|
1059
|
+
);
|
|
1060
|
+
const modules = input.tailscale && !resolved.some((m) => m.id === "tailscale") ? [...resolved, forStack] : resolved;
|
|
1061
|
+
if (modules.length === 0) {
|
|
1062
|
+
return okText(
|
|
1063
|
+
`No hardening modules apply to "${ctx.stackName}" (provider: ${ctx.adapter.name}) with those options.`
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
let conn;
|
|
1067
|
+
try {
|
|
1068
|
+
conn = await connectionFor(ctx, config);
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
const detail = (err instanceof Error ? err.message : String(err)).replace(/\s+/g, " ").trim();
|
|
1071
|
+
return errText(
|
|
1072
|
+
`Stack "${ctx.stackName}" has no deployment to harden, or clawops could not read its state. Deploy it first, and check the credentials for its state backend. (${detail.slice(0, 200)})`
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
const results = await runHardening(conn, { modules, dryRun: input.dryRun });
|
|
1076
|
+
const lines = [formatHardenSummary(results)];
|
|
1077
|
+
const failed = results.filter((r) => r.error);
|
|
1078
|
+
for (const r of failed) lines.push(`FAILED ${r.module.label}: ${r.error}`);
|
|
1079
|
+
if (input.tailscale && !input.dryRun && failed.length === 0) {
|
|
1080
|
+
const outcome = await cutOverToTailnet(ctx, conn, config);
|
|
1081
|
+
if (!outcome.ok) {
|
|
1082
|
+
lines.push(
|
|
1083
|
+
`The host is hardened, but clawops still uses its public address: ${outcome.reason}`
|
|
1084
|
+
);
|
|
1085
|
+
const { content: content2 } = trimForMcp(lines.join("\n"), ctx.stackName);
|
|
1086
|
+
return errText(content2);
|
|
1087
|
+
}
|
|
1088
|
+
lines.push(outcome.message);
|
|
1089
|
+
lines.push(
|
|
1090
|
+
"To close the public ports, plan with privateOnly: true and apply that plan. Nothing has been closed yet."
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
const { content } = trimForMcp(lines.join("\n"), ctx.stackName);
|
|
1094
|
+
return failed.length > 0 ? errText(content) : okText(content);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
984
1097
|
// src/mcp/tools/cli/apply.ts
|
|
985
1098
|
import { readFileSync } from "fs";
|
|
986
1099
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
@@ -1231,6 +1344,7 @@ var TOOL_REGISTRY = {
|
|
|
1231
1344
|
clawops_destroy: makeEntry(clawops_destroySchema, clawops_destroyAnnotations, handleDestroy),
|
|
1232
1345
|
clawops_apply: makeEntry(clawops_applySchema, clawops_applyAnnotations, handleApply),
|
|
1233
1346
|
clawops_plan: makeEntry(clawops_planSchema, clawops_planAnnotations, handlePlan),
|
|
1347
|
+
clawops_harden: makeEntry(clawops_hardenSchema, clawops_hardenAnnotations, handleHarden),
|
|
1234
1348
|
clawops_config_set: makeEntry(clawops_config_setSchema, clawops_config_setAnnotations, handleConfigSet),
|
|
1235
1349
|
clawops_config_unset: makeEntry(clawops_config_unsetSchema, clawops_config_unsetAnnotations, handleConfigUnset),
|
|
1236
1350
|
clawops_config_validate: makeEntry(clawops_config_validateSchema, clawops_config_validateAnnotations, handleConfigValidate),
|
|
@@ -1532,7 +1646,7 @@ function bearerFrom(header) {
|
|
|
1532
1646
|
return match?.[1]?.trim();
|
|
1533
1647
|
}
|
|
1534
1648
|
async function serveMcp(opts) {
|
|
1535
|
-
const { version } = await import("./package-
|
|
1649
|
+
const { version } = await import("./package-JWDMWPV3.js");
|
|
1536
1650
|
const makeServer = () => {
|
|
1537
1651
|
const server = new McpServer({ name: "clawops", version });
|
|
1538
1652
|
registerTools(server, opts);
|
|
@@ -461,6 +461,20 @@ var gcpProgram = async () => {
|
|
|
461
461
|
machineType: instanceType,
|
|
462
462
|
zone,
|
|
463
463
|
tags: ["clawops"],
|
|
464
|
+
// debian-12 advertises UEFI_COMPATIBLE, so all three protections are available. Left
|
|
465
|
+
// unset, GCP turns on vTPM and integrity monitoring and leaves Secure Boot off, which is
|
|
466
|
+
// what `clawops harden` was reporting on every GCP stack.
|
|
467
|
+
//
|
|
468
|
+
// Changing this on an existing instance is an update, not a replacement: Pulumi stops the
|
|
469
|
+
// machine, applies it and starts it again, and the boot disk is untouched. Verified against
|
|
470
|
+
// a live stack — the preview reads "0 to create, 1 to update, 0 to delete". Without
|
|
471
|
+
// allowStoppingForUpdate the provider refuses the update rather than replacing anything.
|
|
472
|
+
allowStoppingForUpdate: true,
|
|
473
|
+
shieldedInstanceConfig: {
|
|
474
|
+
enableSecureBoot: true,
|
|
475
|
+
enableVtpm: true,
|
|
476
|
+
enableIntegrityMonitoring: true
|
|
477
|
+
},
|
|
464
478
|
bootDisk: {
|
|
465
479
|
initializeParams: {
|
|
466
480
|
image: "debian-cloud/debian-12",
|
|
@@ -555,7 +569,7 @@ var gcpAdapter = {
|
|
|
555
569
|
return { ok: errors.length === 0, errors };
|
|
556
570
|
},
|
|
557
571
|
async preflight(opts) {
|
|
558
|
-
const { gcpPreflight } = await import("./preflight-
|
|
572
|
+
const { gcpPreflight } = await import("./preflight-IE2YWBAC.js");
|
|
559
573
|
return gcpPreflight(opts);
|
|
560
574
|
}
|
|
561
575
|
};
|
|
@@ -807,7 +821,7 @@ var azureAdapter = {
|
|
|
807
821
|
return `azblob://${bucket}`;
|
|
808
822
|
},
|
|
809
823
|
async preflight(opts) {
|
|
810
|
-
const { azurePreflight } = await import("./preflight-
|
|
824
|
+
const { azurePreflight } = await import("./preflight-7W23GP4R.js");
|
|
811
825
|
return azurePreflight(opts);
|
|
812
826
|
},
|
|
813
827
|
async validateConfig() {
|
|
@@ -171,11 +171,12 @@ function stateBackendCheck(container) {
|
|
|
171
171
|
detail: ok ? `${where} on storage account ${account}` : `${where} needs AZURE_STORAGE_ACCOUNT and one of AZURE_STORAGE_KEY or AZURE_STORAGE_SAS_TOKEN. Pulumi authenticates to blob storage with these and not with your \`az login\`, so every other check here can pass and the deploy still fail to open its own state.`
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
|
+
|
|
174
175
|
export {
|
|
175
176
|
REQUIRED_PROVIDERS,
|
|
176
|
-
availableSizes,
|
|
177
|
-
azurePreflight,
|
|
178
177
|
managementToken,
|
|
178
|
+
availableSizes,
|
|
179
179
|
sizeCheck,
|
|
180
|
+
azurePreflight,
|
|
180
181
|
stateBackendCheck
|
|
181
182
|
};
|
|
@@ -146,8 +146,8 @@ function renderSnapshot(snap, opts) {
|
|
|
146
146
|
return lines.join("\n");
|
|
147
147
|
}
|
|
148
148
|
async function probeEntries() {
|
|
149
|
-
const { buildContext } = await import("./context-
|
|
150
|
-
const { getConfig } = await import("./store-
|
|
149
|
+
const { buildContext } = await import("./context-7VI3R6EK.js");
|
|
150
|
+
const { getConfig } = await import("./store-OVB3DAJR.js");
|
|
151
151
|
const config = getConfig();
|
|
152
152
|
if (!config) return [];
|
|
153
153
|
return Promise.all(
|
|
@@ -215,7 +215,7 @@ function renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete) {
|
|
|
215
215
|
return lines.join("\n");
|
|
216
216
|
}
|
|
217
217
|
async function deleteFromRegistry(name) {
|
|
218
|
-
const { requireConfig, setConfig } = await import("./store-
|
|
218
|
+
const { requireConfig, setConfig } = await import("./store-OVB3DAJR.js");
|
|
219
219
|
const config = requireConfig();
|
|
220
220
|
const newStacks = { ...config.stacks };
|
|
221
221
|
delete newStacks[name];
|
|
@@ -373,7 +373,7 @@ var monitor_default = defineCommand({
|
|
|
373
373
|
const tailLines = Math.max(1, parseInt(String(args.tail ?? "10"), 10) || 10);
|
|
374
374
|
const isTTY = Boolean(process.stdout.isTTY);
|
|
375
375
|
const noColor = Boolean(args["no-color"]) || !isTTY;
|
|
376
|
-
const { buildContext } = await import("./context-
|
|
376
|
+
const { buildContext } = await import("./context-7VI3R6EK.js");
|
|
377
377
|
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
378
378
|
const ac = new AbortController();
|
|
379
379
|
process.on("SIGINT", () => ac.abort());
|
|
@@ -508,7 +508,7 @@ import { accessSync, mkdirSync, readFileSync, constants } from "fs";
|
|
|
508
508
|
import ssh2 from "ssh2";
|
|
509
509
|
import path from "path";
|
|
510
510
|
async function runDiagnostics(opts = {}, deps = {}) {
|
|
511
|
-
const { getConfig, getConfigDir } = await import("./store-
|
|
511
|
+
const { getConfig, getConfigDir } = await import("./store-OVB3DAJR.js");
|
|
512
512
|
const config = getConfig();
|
|
513
513
|
const sections = [];
|
|
514
514
|
sections.push({ title: "Runtime", checks: await runtimeChecks(getConfigDir()) });
|
|
@@ -576,7 +576,7 @@ async function runtimeChecks(configDir) {
|
|
|
576
576
|
} catch {
|
|
577
577
|
checks.push({ name: "Pulumi home", status: "fail", detail: `${pulumiHome} (not writable)` });
|
|
578
578
|
}
|
|
579
|
-
const { pulumiCliStatus } = await import("./cli-
|
|
579
|
+
const { pulumiCliStatus } = await import("./cli-NH7VUNOU.js");
|
|
580
580
|
const cli = await pulumiCliStatus(configDir);
|
|
581
581
|
checks.push(
|
|
582
582
|
cli.kind === "managed" ? { name: "Pulumi CLI", status: "pass", detail: `${cli.version} (${cli.root})` } : cli.kind === "path" ? { name: "Pulumi CLI", status: "pass", detail: `${cli.version} (on PATH)` } : {
|
|
@@ -808,8 +808,8 @@ async function deployedVersionCheck(image, deployedVersion) {
|
|
|
808
808
|
}
|
|
809
809
|
async function hardeningChecks(conn, stack, signal) {
|
|
810
810
|
try {
|
|
811
|
-
const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-
|
|
812
|
-
const { buildContext } = await import("./context-
|
|
811
|
+
const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-3KKCR7BL.js");
|
|
812
|
+
const { buildContext } = await import("./context-7VI3R6EK.js");
|
|
813
813
|
const provider = buildContext({ stack }).adapter.name;
|
|
814
814
|
const modules = resolveModules(MODULE_CATALOG, void 0, provider);
|
|
815
815
|
const checks = [];
|
|
@@ -831,8 +831,8 @@ async function hardeningChecks(conn, stack, signal) {
|
|
|
831
831
|
}
|
|
832
832
|
}
|
|
833
833
|
async function defaultOpenSession(stack, signal) {
|
|
834
|
-
const { getConfig } = await import("./store-
|
|
835
|
-
const { buildContext } = await import("./context-
|
|
834
|
+
const { getConfig } = await import("./store-OVB3DAJR.js");
|
|
835
|
+
const { buildContext } = await import("./context-7VI3R6EK.js");
|
|
836
836
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
837
837
|
const { acquireSession } = await import("./pool-PIIKJ4L2.js");
|
|
838
838
|
const config = getConfig();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/pulumi/cli.ts
|
|
4
4
|
import path from "path";
|
|
5
5
|
import process from "process";
|
|
6
|
-
import { PulumiCommand } from "@pulumi/pulumi/automation";
|
|
6
|
+
import { PulumiCommand } from "@pulumi/pulumi/automation/index.js";
|
|
7
7
|
function pulumiCliRoot(configDir) {
|
|
8
8
|
return path.join(configDir, ".pulumi-cli");
|
|
9
9
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
readLocalState
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ELU3XQEA.js";
|
|
5
5
|
import {
|
|
6
6
|
getConfigDir,
|
|
7
7
|
requireConfig
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-64PONSA5.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError
|
|
11
11
|
} from "./chunk-KGXPLI7W.js";
|
|
@@ -18,8 +18,10 @@ function buildContext(args) {
|
|
|
18
18
|
const config = requireConfig();
|
|
19
19
|
const stackName = typeof args.stack === "string" ? args.stack : config.defaults.stack;
|
|
20
20
|
const providerName = typeof args.provider === "string" ? args.provider : config.stacks[stackName]?.provider ?? config.defaults.provider;
|
|
21
|
-
const
|
|
22
|
-
const
|
|
21
|
+
const tailscale = args.ignoreTailnet === true ? void 0 : config.stacks[stackName]?.tailscale;
|
|
22
|
+
const adapter = tailscale ? viaTailscale(loadProvider(providerName), tailscale.ip) : loadProvider(providerName);
|
|
23
|
+
const persisted = providerName === "local" ? readLocalState(stackName) : void 0;
|
|
24
|
+
const localState = persisted && tailscale ? { ...persisted, sshHost: tailscale.ip } : persisted;
|
|
23
25
|
let stackCache = null;
|
|
24
26
|
return {
|
|
25
27
|
config,
|
|
@@ -39,7 +41,7 @@ function buildContext(args) {
|
|
|
39
41
|
`Stack "${stackName}" not found in config. Run \`clawops init\` or use \`--stack\` to specify a different stack.`
|
|
40
42
|
);
|
|
41
43
|
}
|
|
42
|
-
const { getOrCreateStack } = await import("./automation-
|
|
44
|
+
const { getOrCreateStack } = await import("./automation-MKCJ46I6.js");
|
|
43
45
|
stackCache = await getOrCreateStack({
|
|
44
46
|
stack: stackName,
|
|
45
47
|
stateUrl: stackConfig.stateUrl,
|
|
@@ -50,6 +52,11 @@ function buildContext(args) {
|
|
|
50
52
|
}
|
|
51
53
|
};
|
|
52
54
|
}
|
|
55
|
+
function viaTailscale(adapter, ip) {
|
|
56
|
+
const wrapped = Object.create(adapter);
|
|
57
|
+
wrapped.getConnectionInfo = (outputs) => ({ ...adapter.getConnectionInfo(outputs), host: ip });
|
|
58
|
+
return wrapped;
|
|
59
|
+
}
|
|
53
60
|
function loadProvider(name) {
|
|
54
61
|
try {
|
|
55
62
|
return getProvider(name);
|