@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
package/dist/cli.js CHANGED
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ MCP_APPS,
4
+ buildMcpEntry,
5
+ writeAppConfigs
6
+ } from "./chunk-OIGTOLB3.js";
2
7
  import {
3
8
  monitor_default,
4
9
  runDiagnostics
5
- } from "./chunk-IVWP4QUJ.js";
10
+ } from "./chunk-GFUK6KDR.js";
6
11
  import {
7
12
  GATEWAY_LOGS_PROBE,
8
13
  chooseLogSource,
9
14
  containerLogsCommand,
10
15
  gatewayLogsCommand
11
16
  } from "./chunk-NUL5F6YV.js";
12
- import {
13
- MCP_APPS,
14
- buildMcpEntry,
15
- writeAppConfigs
16
- } from "./chunk-OIGTOLB3.js";
17
17
  import "./chunk-3OLUJPCB.js";
18
18
  import {
19
19
  execPrivileged,
@@ -27,7 +27,7 @@ import {
27
27
  interpretProbe,
28
28
  probeCommand
29
29
  } from "./chunk-LT4XMO5D.js";
30
- import "./chunk-LEVIW2PV.js";
30
+ import "./chunk-BELTQGHT.js";
31
31
  import {
32
32
  CONFIG_FILENAME,
33
33
  CONTAINER_UID,
@@ -62,7 +62,7 @@ import {
62
62
  getConfigDir,
63
63
  requireConfig,
64
64
  setConfig
65
- } from "./chunk-CX5SL5HP.js";
65
+ } from "./chunk-64PONSA5.js";
66
66
  import {
67
67
  ClawopsError,
68
68
  UsageError
@@ -148,7 +148,7 @@ async function resolveScopeAccount(provider, signal) {
148
148
  const { callerAccount } = await import("./preflight-TFD4N5ZU.js");
149
149
  return await callerAccount(signal);
150
150
  }
151
- const { resolveProjectId } = await import("./preflight-EF52S3WW.js");
151
+ const { resolveProjectId } = await import("./preflight-IE2YWBAC.js");
152
152
  return resolveProjectId();
153
153
  } catch {
154
154
  return void 0;
@@ -339,7 +339,7 @@ var up_default = defineCommand2({
339
339
  "publish-gateway": { type: "string", description: 'loopback (default) or all. "all" serves plaintext HTTP \u2014 put TLS in front of it' }
340
340
  },
341
341
  async run({ args }) {
342
- const { buildContext } = await import("./context-7EVC6W45.js");
342
+ const { buildContext } = await import("./context-7VI3R6EK.js");
343
343
  const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
344
344
  const ctx = buildContext(args);
345
345
  const requestedVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : await defaultOpenclawVersion();
@@ -352,7 +352,7 @@ var up_default = defineCommand2({
352
352
  );
353
353
  }
354
354
  const { localOpts } = stackConfig;
355
- const { localBootstrap } = await import("./bootstrap-HNGKY6WW.js");
355
+ const { localBootstrap } = await import("./bootstrap-NN647RCY.js");
356
356
  const gatewayPort = parseGatewayPort(args["gateway-port"]);
357
357
  const abortController2 = new AbortController();
358
358
  process3.on("SIGINT", () => abortController2.abort());
@@ -392,8 +392,8 @@ var up_default = defineCommand2({
392
392
  }
393
393
  return;
394
394
  }
395
- const { generatePlan } = await import("./generate-I6YQ5V2T.js");
396
- const { applyPlan } = await import("./apply-KYFPWI5U.js");
395
+ const { generatePlan } = await import("./generate-YEM73KVB.js");
396
+ const { applyPlan } = await import("./apply-HJ5GAKHW.js");
397
397
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
398
398
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
399
399
  const isDryRun = Boolean(args["dry-run"]);
@@ -523,7 +523,7 @@ var down_default = defineCommand3({
523
523
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
524
524
  },
525
525
  async run({ args }) {
526
- const { buildContext } = await import("./context-7EVC6W45.js");
526
+ const { buildContext } = await import("./context-7VI3R6EK.js");
527
527
  const ctx = buildContext(args);
528
528
  if (args["dry-run"]) {
529
529
  info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
@@ -587,7 +587,7 @@ var status_default = defineCommand4({
587
587
  json: { type: "boolean", description: "Emit JSON" }
588
588
  },
589
589
  async run({ args }) {
590
- const { buildContext } = await import("./context-7EVC6W45.js");
590
+ const { buildContext } = await import("./context-7VI3R6EK.js");
591
591
  const ctx = buildContext(args);
592
592
  if (ctx.adapter.name === "local") {
593
593
  const state = ctx.localState;
@@ -645,6 +645,30 @@ import { defineCommand as defineCommand5 } from "citty";
645
645
  import process7 from "process";
646
646
  import { writeFileSync as writeFileSync2 } from "fs";
647
647
  import { isAbsolute } from "path";
648
+ function disruptionWarnings(diff) {
649
+ const out = [];
650
+ const isInstance = (type) => /:(Instance|instance)/.test(type);
651
+ const replaced = diff.replace ?? [];
652
+ if (replaced.length > 0) {
653
+ const instance = replaced.some((r) => isInstance(r.type));
654
+ out.push(
655
+ `This plan REPLACES ${replaced.length} existing resource${replaced.length === 1 ? "" : "s"}: ` + replaced.map((r) => r.name ?? r.type).join(", ") + "." + (instance ? " Replacing the instance destroys its boot disk, and with it the OpenClaw config, database, sessions and transcripts on the host. Take a backup first: `clawops backup create`." : "")
656
+ );
657
+ }
658
+ if (diff.update.length > 0) {
659
+ const instance = diff.update.some((u) => isInstance(u.resource.type));
660
+ out.push(
661
+ `This plan modifies ${diff.update.length} existing resource${diff.update.length === 1 ? "" : "s"}.` + (instance ? " Some instance changes require the machine to be stopped and started, so the gateway goes down for the duration and in-flight work is lost. State on disk is kept." : "")
662
+ );
663
+ }
664
+ if (diff.delete.length > 0) {
665
+ out.push(`This plan DELETES ${diff.delete.length} existing resource${diff.delete.length === 1 ? "" : "s"}.`);
666
+ }
667
+ return out;
668
+ }
669
+ function warnAboutDisruption(diff) {
670
+ for (const line2 of disruptionWarnings(diff)) warn(line2);
671
+ }
648
672
  var plan_default = defineCommand5({
649
673
  meta: {
650
674
  name: "plan",
@@ -659,11 +683,12 @@ var plan_default = defineCommand5({
659
683
  "ssh-cidr": { type: "string", description: "CIDR(s) allowed to reach SSH, comma-separated, or 'auto' for this machine's IP. Omitted = none, and nothing will be able to connect" },
660
684
  "gateway-cidr": { type: "string", description: "CIDR(s) allowed to reach the gateway port, comma-separated, or 'auto'. Requires --publish-gateway all" },
661
685
  "publish-gateway": { type: "string", description: 'loopback (default) or all. "all" serves plaintext HTTP \u2014 put TLS in front of it' },
662
- out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" }
686
+ out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" },
687
+ "private-only": { type: "boolean", description: "Close public SSH and gateway access; reach the stack over its tailnet. Needs `clawops harden --tailscale` first, and checks the tailnet answers before planning" }
663
688
  },
664
689
  async run({ args }) {
665
- const { buildContext } = await import("./context-7EVC6W45.js");
666
- const { generatePlan } = await import("./generate-I6YQ5V2T.js");
690
+ const { buildContext } = await import("./context-7VI3R6EK.js");
691
+ const { generatePlan } = await import("./generate-YEM73KVB.js");
667
692
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
668
693
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
669
694
  const ctx = buildContext(args);
@@ -677,14 +702,21 @@ var plan_default = defineCommand5({
677
702
  if (outPath && !isAbsolute(outPath)) {
678
703
  throw new UsageError("--out path must be absolute (R7). Use an absolute path like /tmp/plan.json.");
679
704
  }
680
- const network = await resolveNetworkFlags(
681
- {
682
- sshCidr: strArg2(args["ssh-cidr"]),
683
- gatewayCidr: strArg2(args["gateway-cidr"]),
684
- publishGateway: strArg2(args["publish-gateway"])
685
- },
686
- { detectEgressIp: () => detectEgressIp("https://ifconfig.me/ip") }
687
- );
705
+ const flags = {
706
+ sshCidr: strArg2(args["ssh-cidr"]),
707
+ gatewayCidr: strArg2(args["gateway-cidr"]),
708
+ publishGateway: strArg2(args["publish-gateway"])
709
+ };
710
+ const resolved = await resolveNetworkFlags(flags, {
711
+ detectEgressIp: () => detectEgressIp("https://ifconfig.me/ip")
712
+ });
713
+ let network = resolved;
714
+ if (args["private-only"]) {
715
+ const { privateOnlyNetwork, assertTailnetReachable } = await import("./private-only-DIA26I2E.js");
716
+ const { probeSsh } = await import("./tailscale-cutover-6YP2QKFM.js");
717
+ network = privateOnlyNetwork(flags, resolved, ctx.config.stacks[ctx.stackName]?.tailscale, ctx.stackName);
718
+ await assertTailnetReachable(ctx, (conn) => probeSsh(conn));
719
+ }
688
720
  const abortController = new AbortController();
689
721
  process7.on("SIGINT", () => abortController.abort());
690
722
  process7.on("SIGTERM", () => abortController.abort());
@@ -715,7 +747,8 @@ var plan_default = defineCommand5({
715
747
  process7.stdout.write(planJson + "\n");
716
748
  }
717
749
  const { spec, metadata } = plan;
718
- const sshCidrs = spec.network.allowedSshCidrs.join(", ") || "(none)";
750
+ const tailnetIp = spec.network.tailscale?.privateOnly ? spec.network.tailscale.ip : void 0;
751
+ const sshCidrs = spec.network.allowedSshCidrs.join(", ") || (tailnetIp ? `(none) \u2014 tailnet only, at ${tailnetIp}` : "(none)");
719
752
  const gatewayCidrs = spec.network.allowedGatewayCidrs.join(", ") || "(none)";
720
753
  const publish = spec.network.publishGateway ?? "loopback";
721
754
  process7.stderr.write(
@@ -729,20 +762,22 @@ Plan: ${metadata.name} (${spec.provider}${spec.region ? ` / ${spec.region}` : "
729
762
  `
730
763
  );
731
764
  if (plan.diff) {
732
- const { create, update, delete: del, totalChanges } = plan.diff;
765
+ const { create, update, delete: del, replace = [], totalChanges } = plan.diff;
733
766
  process7.stderr.write(
734
767
  `
735
- Changes: ${create.length} to create, ${update.length} to update, ${del.length} to delete (${totalChanges} total)
768
+ Changes: ${create.length} to create, ${update.length} to update, ${replace.length} to replace, ${del.length} to delete (${totalChanges} total)
736
769
  `
737
770
  );
738
771
  const rows = [
739
772
  ...create.map((r) => ["+", r.type, r.name ?? ""]),
740
773
  ...update.map((r) => ["~", r.resource.type, r.resource.name ?? ""]),
774
+ ...replace.map((r) => ["+-", r.type, r.name ?? ""]),
741
775
  ...del.map((r) => ["-", r.type, r.name ?? ""])
742
776
  ];
743
777
  if (rows.length > 0) {
744
778
  process7.stderr.write(renderTable(["Op", "Resource Type", "Name"], rows) + "\n");
745
779
  }
780
+ warnAboutDisruption(plan.diff);
746
781
  } else {
747
782
  process7.stderr.write("\n(diff unavailable \u2014 preview could not run against this stack)\n");
748
783
  }
@@ -768,8 +803,8 @@ var apply_default = defineCommand6({
768
803
  "dry-run": { type: "boolean", description: "Validate plan and show diff without applying" }
769
804
  },
770
805
  async run({ args }) {
771
- const { validatePlan } = await import("./validate-YVI3BJ55.js");
772
- const { applyPlan } = await import("./apply-KYFPWI5U.js");
806
+ const { validatePlan } = await import("./validate-JXKN5WD6.js");
807
+ const { applyPlan } = await import("./apply-HJ5GAKHW.js");
773
808
  const planPath = args._?.[0];
774
809
  if (!planPath) {
775
810
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -895,7 +930,7 @@ var destroy_default = defineCommand7({
895
930
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
896
931
  },
897
932
  async run({ args }) {
898
- const { buildContext } = await import("./context-7EVC6W45.js");
933
+ const { buildContext } = await import("./context-7VI3R6EK.js");
899
934
  const ctx = buildContext(args);
900
935
  if (ctx.adapter.name === "local") {
901
936
  throw new UsageError(
@@ -933,7 +968,7 @@ Pass --yes to proceed with destruction.
933
968
  const abortController = new AbortController();
934
969
  process9.on("SIGINT", () => abortController.abort());
935
970
  process9.on("SIGTERM", () => abortController.abort());
936
- const doomedHost = await hostOf(stack, ctx);
971
+ const doomedHosts = await hostsOf(stack, ctx);
937
972
  const spin = spinner(`Destroying stack "${ctx.stackName}"\u2026`);
938
973
  try {
939
974
  await stack.destroy({
@@ -947,31 +982,29 @@ Pass --yes to proceed with destruction.
947
982
  spin.fail("Destroy failed");
948
983
  throw err;
949
984
  }
950
- if (doomedHost) {
951
- const { forgetHost } = await import("./known-hosts-file-I3Q3NBYQ.js");
952
- const removed = forgetHost(
953
- expandHome(ctx.config.ssh.knownHostsPath),
954
- doomedHost.host,
955
- doomedHost.port
956
- );
957
- if (removed) info(`Forgot the host key for ${doomedHost.host} \u2014 its instance is gone.`);
985
+ const { forgetHost } = await import("./known-hosts-file-I3Q3NBYQ.js");
986
+ for (const doomed of doomedHosts) {
987
+ const removed = forgetHost(expandHome(ctx.config.ssh.knownHostsPath), doomed.host, doomed.port);
988
+ if (removed) info(`Forgot the host key for ${doomed.host} \u2014 its instance is gone.`);
958
989
  }
959
990
  }
960
991
  });
961
- async function hostOf(stack, ctx) {
992
+ async function hostsOf(stack, ctx) {
993
+ const hosts = [];
962
994
  try {
963
995
  const outputMap = await stack.outputs();
964
996
  const raw = Object.fromEntries(Object.entries(outputMap).map(([k, v]) => [k, v.value]));
965
997
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
966
- const conn = ctx.adapter.getConnectionInfo({
967
- ...extractBaseOutputs(raw),
968
- privateKeyPath: ctx.config.ssh.keyPath,
969
- knownHostsPath: ctx.config.ssh.knownHostsPath
970
- });
971
- return conn.host ? { host: conn.host, port: conn.port } : void 0;
998
+ const base = extractBaseOutputs(raw);
999
+ if (base.sshHost) hosts.push({ host: base.sshHost, port: base.sshPort });
972
1000
  } catch {
973
- return void 0;
974
1001
  }
1002
+ const tailnet = ctx.config.stacks?.[ctx.stackName]?.tailscale;
1003
+ if (tailnet?.ip) {
1004
+ const port = hosts[0]?.port ?? 22;
1005
+ if (!hosts.some((h) => h.host === tailnet.ip)) hosts.push({ host: tailnet.ip, port });
1006
+ }
1007
+ return hosts;
975
1008
  }
976
1009
  function expandHome(p) {
977
1010
  return p.replace(/^~/, process9.env["HOME"] ?? "~");
@@ -990,7 +1023,7 @@ var ssh_default = defineCommand8({
990
1023
  command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
991
1024
  },
992
1025
  async run({ args }) {
993
- const { buildContext } = await import("./context-7EVC6W45.js");
1026
+ const { buildContext } = await import("./context-7VI3R6EK.js");
994
1027
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
995
1028
  const ctx = buildContext(args);
996
1029
  let conn;
@@ -1077,7 +1110,7 @@ var tunnel_default = defineCommand9({
1077
1110
  "no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
1078
1111
  },
1079
1112
  async run({ args }) {
1080
- const { buildContext } = await import("./context-7EVC6W45.js");
1113
+ const { buildContext } = await import("./context-7VI3R6EK.js");
1081
1114
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1082
1115
  const { acquireSession } = await import("./pool-PIIKJ4L2.js");
1083
1116
  const ctx = buildContext(args);
@@ -1146,7 +1179,7 @@ var logs_default = defineCommand10({
1146
1179
  json: { type: "boolean", description: "Emit the gateway's structured JSON log lines" }
1147
1180
  },
1148
1181
  async run({ args }) {
1149
- const { buildContext } = await import("./context-7EVC6W45.js");
1182
+ const { buildContext } = await import("./context-7VI3R6EK.js");
1150
1183
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1151
1184
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1152
1185
  const ctx = buildContext(args);
@@ -1252,7 +1285,7 @@ var config_default = defineCommand11({
1252
1285
  "dry-run": { type: "boolean", description: "Show what would change without writing" }
1253
1286
  },
1254
1287
  async run({ args }) {
1255
- const { buildContext } = await import("./context-7EVC6W45.js");
1288
+ const { buildContext } = await import("./context-7VI3R6EK.js");
1256
1289
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1257
1290
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1258
1291
  const [action, key, value] = args._ ?? [];
@@ -1387,7 +1420,7 @@ var agents_default = defineCommand12({
1387
1420
  cursor: { type: "string", description: "Continue from a previous `logs` result cursor" }
1388
1421
  },
1389
1422
  async run({ args }) {
1390
- const { buildContext } = await import("./context-7EVC6W45.js");
1423
+ const { buildContext } = await import("./context-7VI3R6EK.js");
1391
1424
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1392
1425
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1393
1426
  const [action, name] = args._ ?? [];
@@ -1619,7 +1652,7 @@ var migrate_default = defineCommand13({
1619
1652
  yes: { type: "boolean", description: "Skip the confirmation prompt" }
1620
1653
  },
1621
1654
  async run({ args }) {
1622
- const { buildContext } = await import("./context-7EVC6W45.js");
1655
+ const { buildContext } = await import("./context-7VI3R6EK.js");
1623
1656
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1624
1657
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1625
1658
  const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
@@ -1758,7 +1791,7 @@ var gateway_default = defineCommand14({
1758
1791
  json: { type: "boolean", description: "Emit JSON (for status)" }
1759
1792
  },
1760
1793
  async run({ args }) {
1761
- const { buildContext } = await import("./context-7EVC6W45.js");
1794
+ const { buildContext } = await import("./context-7VI3R6EK.js");
1762
1795
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1763
1796
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1764
1797
  const [action, versionArg] = args._ ?? [];
@@ -1990,7 +2023,7 @@ var backup_default = defineCommand15({
1990
2023
  yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
1991
2024
  },
1992
2025
  async run({ args }) {
1993
- const { buildContext } = await import("./context-7EVC6W45.js");
2026
+ const { buildContext } = await import("./context-7VI3R6EK.js");
1994
2027
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1995
2028
  const action = args.action;
1996
2029
  if (action !== "create" && action !== "restore") {
@@ -2201,7 +2234,7 @@ var stacks_default = defineCommand16({
2201
2234
  let isDeployed = false;
2202
2235
  if (!args.force) {
2203
2236
  try {
2204
- const { buildContext } = await import("./context-7EVC6W45.js");
2237
+ const { buildContext } = await import("./context-7VI3R6EK.js");
2205
2238
  const ctx = buildContext({ stack: name });
2206
2239
  if (ctx.adapter.name === "local") {
2207
2240
  isDeployed = !!ctx.localState;
@@ -2342,7 +2375,7 @@ var serveCmd = defineCommand18({
2342
2375
  inspector: { type: "boolean", description: "Enable MCP inspector" }
2343
2376
  },
2344
2377
  async run({ args }) {
2345
- const { serveMcp } = await import("./server-GBA7BJGK.js");
2378
+ const { serveMcp } = await import("./server-ZJPXPRZT.js");
2346
2379
  await serveMcp({
2347
2380
  port: args.http ? Number(args.http) : void 0,
2348
2381
  bind: args.bind,
@@ -2420,11 +2453,11 @@ var wireCmd = defineCommand18({
2420
2453
  rewire: { type: "boolean", description: "Replace an existing clawops entry" }
2421
2454
  },
2422
2455
  async run({ args }) {
2423
- const { buildContext } = await import("./context-7EVC6W45.js");
2456
+ const { buildContext } = await import("./context-7VI3R6EK.js");
2424
2457
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
2425
2458
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
2426
- const { wireGatewayMcp } = await import("./mcp-wire-3SWBM7D6.js");
2427
- const { MCP_HTTP_PORT } = await import("./server-GBA7BJGK.js");
2459
+ const { wireGatewayMcp } = await import("./mcp-wire-TE2ZL6HD.js");
2460
+ const { MCP_HTTP_PORT } = await import("./server-ZJPXPRZT.js");
2428
2461
  const ac = new AbortController();
2429
2462
  process20.on("SIGINT", () => {
2430
2463
  ac.abort();
@@ -3007,7 +3040,7 @@ var setup_default = defineCommand19({
3007
3040
  inquirer
3008
3041
  });
3009
3042
  } else {
3010
- const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
3043
+ const { getConfigDir: getConfigDir2 } = await import("./store-OVB3DAJR.js");
3011
3044
  const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
3012
3045
  process21.stdout.write("\n");
3013
3046
  info("To deploy later, run these two commands:");
@@ -3024,7 +3057,7 @@ var setup_default = defineCommand19({
3024
3057
  default: false
3025
3058
  }]);
3026
3059
  if (applyNow) {
3027
- const { applyPlan } = await import("./apply-KYFPWI5U.js");
3060
+ const { applyPlan } = await import("./apply-HJ5GAKHW.js");
3028
3061
  const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
3029
3062
  try {
3030
3063
  await applyPlan(plan, {
@@ -3052,8 +3085,8 @@ To deploy later: clawops apply ${outputPath}`);
3052
3085
  }
3053
3086
  });
3054
3087
  async function runLocalDeploy(opts) {
3055
- const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
3056
- const { localBootstrap } = await import("./bootstrap-HNGKY6WW.js");
3088
+ const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-OVB3DAJR.js");
3089
+ const { localBootstrap } = await import("./bootstrap-NN647RCY.js");
3057
3090
  const { connect } = await import("./ssh-VAI5DU5M.js");
3058
3091
  const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
3059
3092
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
@@ -3527,8 +3560,8 @@ async function maybeWireGatewayMcp(opts) {
3527
3560
  const { wireGateway } = await inquirerPromptWire(opts.inquirer);
3528
3561
  if (!wireGateway) return;
3529
3562
  const { connect } = await import("./ssh-VAI5DU5M.js");
3530
- const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
3531
- const { wireGatewayMcp } = await import("./mcp-wire-3SWBM7D6.js");
3563
+ const { getConfigDir: getConfigDir2 } = await import("./store-OVB3DAJR.js");
3564
+ const { wireGatewayMcp } = await import("./mcp-wire-TE2ZL6HD.js");
3532
3565
  const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
3533
3566
  const spin = spinner("Wiring gateway MCP client...");
3534
3567
  const session = await connect({
@@ -3647,8 +3680,8 @@ function instanceChoices(provider) {
3647
3680
  return table[provider] ?? [];
3648
3681
  }
3649
3682
  async function maybeHarden(opts) {
3650
- const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-72JMRT4E.js");
3651
- const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
3683
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-3KKCR7BL.js");
3684
+ const { getConfig: getConfig2 } = await import("./store-OVB3DAJR.js");
3652
3685
  const config = getConfig2();
3653
3686
  if (!config) return;
3654
3687
  const availableModules = resolveModules(MODULE_CATALOG, void 0, opts.provider);
@@ -3671,7 +3704,7 @@ async function maybeHarden(opts) {
3671
3704
  const modules = availableModules.filter((m) => selectedIds.includes(m.id));
3672
3705
  process21.stdout.write("\nApplying hardening modules...\n\n");
3673
3706
  try {
3674
- const { buildContext } = await import("./context-7EVC6W45.js");
3707
+ const { buildContext } = await import("./context-7VI3R6EK.js");
3675
3708
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
3676
3709
  const ctx = buildContext({ stack: opts.stackName });
3677
3710
  const stackObj = await ctx.getStack();
@@ -3946,7 +3979,7 @@ var rotateCmd = defineCommand20({
3946
3979
  spawnSync3("chmod", ["700", secretsDir()], { stdio: "ignore" });
3947
3980
  writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
3948
3981
  success(`Secret "${name}" updated.`);
3949
- const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
3982
+ const { getConfig: getConfig2 } = await import("./store-OVB3DAJR.js");
3950
3983
  const cfg = getConfig2();
3951
3984
  const targetStack = args.stack ?? cfg?.defaults?.stack;
3952
3985
  if (!targetStack) {
@@ -3963,11 +3996,11 @@ var rotateCmd = defineCommand20({
3963
3996
  }
3964
3997
  info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
3965
3998
  try {
3966
- const { buildContext } = await import("./context-7EVC6W45.js");
3999
+ const { buildContext } = await import("./context-7VI3R6EK.js");
3967
4000
  const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
3968
4001
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
3969
4002
  const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
3970
- const { localStateToConnectionInfo } = await import("./state-PRBIIN7I.js");
4003
+ const { localStateToConnectionInfo } = await import("./state-AOAYLYY5.js");
3971
4004
  const ctx = buildContext({ stack: targetStack });
3972
4005
  let conn;
3973
4006
  if (ctx.adapter.name === "local") {
@@ -4150,7 +4183,7 @@ import { accessSync, constants } from "fs";
4150
4183
  import { execSync as execSync2 } from "child_process";
4151
4184
  var ISSUES_URL = `${REPO_URL}/issues/new`;
4152
4185
  var BODY_CAP = 2e3;
4153
- var VERSION = true ? "2.0.1" : "dev";
4186
+ var VERSION = true ? "2.1.0" : "dev";
4154
4187
  async function collectContext() {
4155
4188
  const nodeVersion = process23.version;
4156
4189
  const os3 = `${process23.platform} ${process23.arch}`;
@@ -4158,7 +4191,7 @@ async function collectContext() {
4158
4191
  let stackCount = 0;
4159
4192
  let sshKey = "unknown";
4160
4193
  try {
4161
- const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
4194
+ const { getConfig: getConfig2 } = await import("./store-OVB3DAJR.js");
4162
4195
  const config = getConfig2();
4163
4196
  if (config) {
4164
4197
  const stacks = Object.values(config.stacks);
@@ -4264,12 +4297,14 @@ var harden_default = defineCommand23({
4264
4297
  list: { type: "boolean", description: "List available modules and exit" },
4265
4298
  "dry-run": { type: "boolean", description: "Check current state without applying changes" },
4266
4299
  yes: { type: "boolean", description: "Skip confirmation prompt" },
4267
- json: { type: "boolean", description: "Emit structured JSON output" }
4300
+ json: { type: "boolean", description: "Emit structured JSON output" },
4301
+ tailscale: { type: "boolean", description: "Join the tailnet, verify this machine can reach the host there, then use that address" },
4302
+ "tailscale-revert": { type: "boolean", description: "Take the host off its tailnet and go back to its public address. A private-only stack must be reopened with a plan first" }
4268
4303
  },
4269
4304
  async run({ args }) {
4270
- const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-72JMRT4E.js");
4271
- const { buildContext } = await import("./context-7EVC6W45.js");
4272
- const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
4305
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-3KKCR7BL.js");
4306
+ const { buildContext } = await import("./context-7VI3R6EK.js");
4307
+ const { getConfig: getConfig2 } = await import("./store-OVB3DAJR.js");
4273
4308
  if (args.list) {
4274
4309
  process24.stdout.write("\nAvailable hardening modules:\n\n");
4275
4310
  for (const mod of MODULE_CATALOG) {
@@ -4290,7 +4325,36 @@ var harden_default = defineCommand23({
4290
4325
  }
4291
4326
  const ctx = buildContext({ stack: args.stack });
4292
4327
  const provider = ctx.adapter.name;
4293
- const modules = resolveModules(MODULE_CATALOG, args.options, provider);
4328
+ if (args["tailscale-revert"]) {
4329
+ const { revertTailnet } = await import("./flows-RI7FGHAM.js");
4330
+ const override = config.stacks[ctx.stackName]?.tailscale;
4331
+ if (override && args.yes !== true) {
4332
+ const { createInterface: createInterface3 } = await import("readline/promises");
4333
+ const rl = createInterface3({ input: process24.stdin, output: process24.stdout });
4334
+ const answer = await rl.question(
4335
+ `Take "${ctx.stackName}" off the tailnet (${override.ip}) and use its public address again? [y/N] `
4336
+ );
4337
+ rl.close();
4338
+ if (!answer.toLowerCase().startsWith("y")) {
4339
+ info("Aborted.");
4340
+ return;
4341
+ }
4342
+ }
4343
+ const outcome = await revertTailnet(ctx, config);
4344
+ if (!outcome.ok) {
4345
+ failure(outcome.reason);
4346
+ process24.exit(1);
4347
+ }
4348
+ if ("noop" in outcome) info(outcome.message);
4349
+ else success(outcome.message);
4350
+ return;
4351
+ }
4352
+ const { makeTailscaleModule } = await import("./harden-3KKCR7BL.js");
4353
+ const forStack = makeTailscaleModule(ctx.stackName);
4354
+ const resolved = resolveModules(MODULE_CATALOG, args.options, provider).map(
4355
+ (m) => m.id === "tailscale" ? forStack : m
4356
+ );
4357
+ const modules = args.tailscale && !resolved.some((m) => m.id === "tailscale") ? [...resolved, forStack] : resolved;
4294
4358
  if (modules.length === 0) {
4295
4359
  warn("No modules selected for this provider. Use --options to specify modules or --list to see all.");
4296
4360
  return;
@@ -4368,6 +4432,16 @@ var harden_default = defineCommand23({
4368
4432
  if (!args["dry-run"]) {
4369
4433
  success("Hardening complete.");
4370
4434
  }
4435
+ if (args.tailscale && !args["dry-run"]) {
4436
+ const { cutOverToTailnet } = await import("./flows-RI7FGHAM.js");
4437
+ process24.stdout.write("\nVerifying the tailnet address before using it...\n");
4438
+ const outcome = await cutOverToTailnet(ctx, conn, config);
4439
+ if (!outcome.ok) {
4440
+ failure(outcome.reason);
4441
+ process24.exit(1);
4442
+ }
4443
+ success(outcome.message);
4444
+ }
4371
4445
  }
4372
4446
  });
4373
4447
 
@@ -4390,7 +4464,7 @@ function handleError(err) {
4390
4464
  }
4391
4465
 
4392
4466
  // src/cli/index.ts
4393
- var CLI_VERSION = true ? "2.0.1" : "dev";
4467
+ var CLI_VERSION = true ? "2.1.0" : "dev";
4394
4468
  var main = defineCommand24({
4395
4469
  meta: {
4396
4470
  name: "clawops",
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  buildContext,
4
4
  loadAdapterModule
5
- } from "./chunk-5XT5IAJK.js";
6
- import "./chunk-LEVIW2PV.js";
5
+ } from "./chunk-QDNFADJI.js";
6
+ import "./chunk-BELTQGHT.js";
7
7
  import "./chunk-GIEVAGEK.js";
8
8
  import "./chunk-VOEJYRIK.js";
9
9
  import "./chunk-EI7CQQ7D.js";
10
10
  import "./chunk-FEK7VJAY.js";
11
11
  import "./chunk-RHOWYIWT.js";
12
12
  import "./chunk-N4WZLWJ4.js";
13
- import "./chunk-A2I76FTA.js";
14
- import "./chunk-CX5SL5HP.js";
13
+ import "./chunk-ELU3XQEA.js";
14
+ import "./chunk-64PONSA5.js";
15
15
  import "./chunk-KGXPLI7W.js";
16
16
  import "./chunk-VUFMZEIM.js";
17
17
  export {