@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.
Files changed (38) hide show
  1. package/README.md +862 -807
  2. package/dist/{apply-KYFPWI5U.js → apply-HJ5GAKHW.js} +7 -7
  3. package/dist/{automation-RMAIBNVC.js → automation-MKCJ46I6.js} +3 -3
  4. package/dist/{bootstrap-HNGKY6WW.js → bootstrap-NN647RCY.js} +2 -2
  5. package/dist/{chunk-HSPHXG6W.js → chunk-3Z5TDXCJ.js} +2 -2
  6. package/dist/{chunk-CX5SL5HP.js → chunk-64PONSA5.js} +10 -1
  7. package/dist/{chunk-STLRHLLV.js → chunk-A2DS5SB7.js} +12 -9
  8. package/dist/{chunk-KC3GC3PE.js → chunk-AYUX4FOF.js} +127 -13
  9. package/dist/{chunk-LEVIW2PV.js → chunk-BELTQGHT.js} +16 -2
  10. package/dist/{preflight-MWWM5JT3.js → chunk-BTW7NHOH.js} +3 -2
  11. package/dist/{chunk-A2I76FTA.js → chunk-ELU3XQEA.js} +1 -1
  12. package/dist/{chunk-IVWP4QUJ.js → chunk-GFUK6KDR.js} +10 -10
  13. package/dist/{chunk-W3OLNNJW.js → chunk-PSOSBJVO.js} +1 -1
  14. package/dist/{chunk-5XT5IAJK.js → chunk-QDNFADJI.js} +12 -5
  15. package/dist/{chunk-LBE6COC4.js → chunk-R3DTA35B.js} +22 -3
  16. package/dist/{preflight-EF52S3WW.js → chunk-TC2VYPLH.js} +3 -2
  17. package/dist/chunk-X56CQ6GB.js +157 -0
  18. package/dist/{chunk-BSLIYB5Q.js → chunk-XFN3YDZE.js} +1 -1
  19. package/dist/{cli-WLD3EBEM.js → cli-NH7VUNOU.js} +1 -1
  20. package/dist/cli.js +156 -82
  21. package/dist/{context-7EVC6W45.js → context-7VI3R6EK.js} +4 -4
  22. package/dist/flows-RI7FGHAM.js +120 -0
  23. package/dist/{generate-I6YQ5V2T.js → generate-YEM73KVB.js} +6 -6
  24. package/dist/{harden-72JMRT4E.js → harden-3KKCR7BL.js} +541 -3
  25. package/dist/{mcp-wire-3SWBM7D6.js → mcp-wire-TE2ZL6HD.js} +10 -10
  26. package/dist/{package-5BCMEBTE.js → package-JWDMWPV3.js} +47 -10
  27. package/dist/preflight-7W23GP4R.js +19 -0
  28. package/dist/preflight-IE2YWBAC.js +13 -0
  29. package/dist/private-only-DIA26I2E.js +74 -0
  30. package/dist/{server-GBA7BJGK.js → server-ZJPXPRZT.js} +10 -10
  31. package/dist/{stack-config-R4SPSUQZ.js → stack-config-LUTS2XAD.js} +2 -2
  32. package/dist/{state-PRBIIN7I.js → state-AOAYLYY5.js} +2 -2
  33. package/dist/{store-SDUR52Z5.js → store-OVB3DAJR.js} +1 -1
  34. package/dist/tailscale-cutover-6YP2QKFM.js +113 -0
  35. package/dist/{validate-YVI3BJ55.js → validate-JXKN5WD6.js} +1 -1
  36. package/package.json +39 -8
  37. package/spec/deploy-plan.schema.json +16 -1
  38. 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-LBE6COC4.js";
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-HSPHXG6W.js";
8
+ import "./chunk-3Z5TDXCJ.js";
9
9
  import "./chunk-AEEMRFZV.js";
10
- import "./chunk-BSLIYB5Q.js";
11
- import "./chunk-5XT5IAJK.js";
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-LEVIW2PV.js";
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-A2I76FTA.js";
22
- import "./chunk-CX5SL5HP.js";
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-W3OLNNJW.js";
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-CX5SL5HP.js";
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-A2I76FTA.js";
18
- import "./chunk-CX5SL5HP.js";
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-CX5SL5HP.js";
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-EF52S3WW.js");
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-BSLIYB5Q.js";
4
+ } from "./chunk-XFN3YDZE.js";
5
5
  import {
6
6
  buildContext,
7
7
  loadAdapterModule
8
- } from "./chunk-5XT5IAJK.js";
8
+ } from "./chunk-QDNFADJI.js";
9
9
  import {
10
10
  getConfig
11
- } from "./chunk-CX5SL5HP.js";
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 === "+") create.push(ref);
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
- totalChanges: create.length + update.length + del.length
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-5BCMEBTE.js");
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-R4SPSUQZ.js");
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-YVI3BJ55.js");
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-LBE6COC4.js";
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-STLRHLLV.js";
15
+ } from "./chunk-A2DS5SB7.js";
16
16
  import {
17
17
  validatePlan
18
- } from "./chunk-BSLIYB5Q.js";
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-5XT5IAJK.js";
25
+ } from "./chunk-QDNFADJI.js";
26
26
  import {
27
27
  formatUptime,
28
28
  gatherSnapshot,
29
29
  runDiagnostics
30
- } from "./chunk-IVWP4QUJ.js";
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-CX5SL5HP.js";
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.enum(["micro", "small", "medium", "large", "gpu"]).optional(),
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-HNGKY6WW.js");
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-I6YQ5V2T.js");
855
- const { applyPlan: applyPlan2 } = await import("./apply-KYFPWI5U.js");
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-5BCMEBTE.js");
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-EF52S3WW.js");
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-MWWM5JT3.js");
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
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getConfigDir
4
- } from "./chunk-CX5SL5HP.js";
4
+ } from "./chunk-64PONSA5.js";
5
5
 
6
6
  // src/providers/local/state.ts
7
7
  import { readFileSync, writeFileSync, mkdirSync, renameSync } from "fs";
@@ -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-7EVC6W45.js");
150
- const { getConfig } = await import("./store-SDUR52Z5.js");
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-SDUR52Z5.js");
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-7EVC6W45.js");
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-SDUR52Z5.js");
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-WLD3EBEM.js");
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-72JMRT4E.js");
812
- const { buildContext } = await import("./context-7EVC6W45.js");
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-SDUR52Z5.js");
835
- const { buildContext } = await import("./context-7EVC6W45.js");
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-A2I76FTA.js";
4
+ } from "./chunk-ELU3XQEA.js";
5
5
  import {
6
6
  getConfigDir,
7
7
  requireConfig
8
- } from "./chunk-CX5SL5HP.js";
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 adapter = loadProvider(providerName);
22
- const localState = providerName === "local" ? readLocalState(stackName) : void 0;
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-RMAIBNVC.js");
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);