@clawops/cli 1.7.1 → 1.7.3

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 (41) hide show
  1. package/README.md +37 -92
  2. package/dist/{apply-BTZFFGAD.js → apply-PZ5IIU7Z.js} +5 -4
  3. package/dist/{aws-D7Y6LCGK.js → aws-L4RR2LSE.js} +1 -1
  4. package/dist/{azure-JQAMNVHN.js → azure-7OXQENQL.js} +1 -1
  5. package/dist/{bootstrap-RY4VOSDJ.js → bootstrap-HL4DNUNG.js} +13 -4
  6. package/dist/bootstrap.sh.tmpl +204 -0
  7. package/dist/chunk-2YOP6N7X.js +126 -0
  8. package/dist/{chunk-GJEF6UQA.js → chunk-3XK66DKK.js} +100 -16
  9. package/dist/{chunk-ISFHLA4G.js → chunk-5SPESD5N.js} +8 -6
  10. package/dist/{chunk-YTH4L2GN.js → chunk-6X45DDGH.js} +5 -4
  11. package/dist/{chunk-XKT42M34.js → chunk-C5X375RO.js} +14 -1
  12. package/dist/{chunk-LCKD7L7X.js → chunk-DQDAWNGB.js} +1 -1
  13. package/dist/{chunk-CG3Y7Y2R.js → chunk-FSUWPL7Q.js} +9 -9
  14. package/dist/{chunk-FQ4JXUCR.js → chunk-NESXDU5F.js} +7 -4
  15. package/dist/{chunk-ZFNPM2WG.js → chunk-O3F7OGH7.js} +40 -3
  16. package/dist/{chunk-3MFZ7E74.js → chunk-P5LKWRPL.js} +3 -3
  17. package/dist/chunk-PVUVI35Y.js +29 -0
  18. package/dist/cli.js +122 -68
  19. package/dist/{context-ALSJMTHE.js → context-5LCUG2QR.js} +1 -1
  20. package/dist/{gcp-6FT2A45S.js → gcp-UG6AWPIZ.js} +1 -1
  21. package/dist/{generate-SPT7PJKR.js → generate-6E7J6BTM.js} +4 -3
  22. package/dist/{harden-BDVYW567.js → harden-URPXLSVF.js} +2 -2
  23. package/dist/{mcp-wire-ZWIYMLEW.js → mcp-wire-SLL4TLSC.js} +1 -1
  24. package/dist/{package-OMPI6RMV.js → package-QE7V2JAT.js} +3 -2
  25. package/dist/{pool-JZGKPP6K.js → pool-QGFQRMGU.js} +2 -2
  26. package/dist/{remote-config-QF5TT7GU.js → remote-config-PFB66RPY.js} +3 -1
  27. package/dist/{server-RGLKKCCU.js → server-L22AJQZO.js} +19 -18
  28. package/dist/{ssh-E2AMBPZY.js → ssh-CNJNLM3E.js} +1 -1
  29. package/dist/{validate-T5M5EHSJ.js → validate-CFQ4FH2T.js} +2 -1
  30. package/dist/version-guard-MZAO4KHS.js +25 -0
  31. package/dist/versions-OEPAS2EL.js +24 -0
  32. package/package.json +3 -2
  33. package/spec/_gen-schemas.md +136 -0
  34. package/spec/deploy-plan.schema.json +193 -0
  35. package/spec/errors.yaml +175 -0
  36. package/spec/integrations.yaml +97 -0
  37. package/spec/invariants.yaml +81 -0
  38. package/spec/mcp-tools.yaml +532 -0
  39. package/spec/models.yaml +113 -0
  40. package/spec/openclaw-versions.yaml +67 -0
  41. package/spec/providers.schema.json +120 -0
@@ -8,6 +8,98 @@ import { readFileSync, appendFileSync, mkdirSync } from "fs";
8
8
  import { createServer } from "net";
9
9
  import path from "path";
10
10
  import { Client } from "ssh2";
11
+
12
+ // src/transport/known-hosts.ts
13
+ import { createHmac, timingSafeEqual } from "crypto";
14
+ function hostEntryFor(host, port) {
15
+ return port === 22 ? host : `[${host}]:${port}`;
16
+ }
17
+ function parseKnownHosts(content) {
18
+ const entries = [];
19
+ for (const raw of content.split("\n")) {
20
+ const line = raw.trim();
21
+ if (!line || line.startsWith("#")) continue;
22
+ let parts = line.split(/\s+/);
23
+ let marker;
24
+ if (parts[0]?.startsWith("@")) {
25
+ marker = parts[0];
26
+ parts = parts.slice(1);
27
+ }
28
+ const hostField = parts[0];
29
+ if (!hostField) continue;
30
+ const hosts = hostField.split(",");
31
+ if (parts.length >= 3) {
32
+ entries.push({ hosts, keyType: parts[1], base64Key: parts[2], ...marker ? { marker } : {} });
33
+ } else if (parts.length === 2 && /^[0-9a-f]+$/i.test(parts[1] ?? "")) {
34
+ entries.push({ hosts, hexKey: parts[1], ...marker ? { marker } : {} });
35
+ }
36
+ }
37
+ return entries;
38
+ }
39
+ function hashedHostMatches(token, hostEntry) {
40
+ const parts = token.split("|");
41
+ if (parts.length !== 4 || parts[1] !== "1") return false;
42
+ const salt = parts[2];
43
+ const expected = parts[3];
44
+ if (!salt || !expected) return false;
45
+ try {
46
+ const actual = createHmac("sha1", Buffer.from(salt, "base64")).update(hostEntry).digest("base64");
47
+ const a = Buffer.from(actual);
48
+ const b = Buffer.from(expected);
49
+ return a.length === b.length && timingSafeEqual(a, b);
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+ function patternToRegExp(pattern) {
55
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
56
+ return new RegExp(`^${escaped}$`, "i");
57
+ }
58
+ var isPattern = (host) => host.includes("*") || host.includes("?");
59
+ function entryMatchesHost(entry, hostEntry) {
60
+ let matched = false;
61
+ for (const host of entry.hosts) {
62
+ if (host.startsWith("|1|")) {
63
+ if (hashedHostMatches(host, hostEntry)) matched = true;
64
+ continue;
65
+ }
66
+ const negated = host.startsWith("!");
67
+ const pattern = negated ? host.slice(1) : host;
68
+ const hit = isPattern(pattern) ? patternToRegExp(pattern).test(hostEntry) : pattern === hostEntry;
69
+ if (hit) {
70
+ if (negated) return false;
71
+ matched = true;
72
+ }
73
+ }
74
+ return matched;
75
+ }
76
+ function verifyAgainstKnownHosts(content, host, port, key) {
77
+ const hostEntry = hostEntryFor(host, port);
78
+ const base64Key = key.toString("base64");
79
+ const hexKey = key.toString("hex");
80
+ let sawHost = false;
81
+ for (const entry of parseKnownHosts(content)) {
82
+ if (!entryMatchesHost(entry, hostEntry)) continue;
83
+ if (entry.marker === "@cert-authority") continue;
84
+ sawHost = true;
85
+ const matches = entry.base64Key ? entry.base64Key === base64Key : entry.hexKey?.toLowerCase() === hexKey.toLowerCase();
86
+ if (matches) return entry.marker === "@revoked" ? "mismatch" : "match";
87
+ }
88
+ return sawHost ? "mismatch" : "unknown";
89
+ }
90
+ function formatKnownHostsLine(host, port, keyType, key) {
91
+ return `${hostEntryFor(host, port)} ${keyType} ${key.toString("base64")}
92
+ `;
93
+ }
94
+ function keyTypeFromBlob(key) {
95
+ if (key.length < 4) return void 0;
96
+ const len = key.readUInt32BE(0);
97
+ if (len === 0 || len > 64 || key.length < 4 + len) return void 0;
98
+ const type = key.subarray(4, 4 + len).toString("utf-8");
99
+ return /^[\x20-\x7e]+$/.test(type) ? type : void 0;
100
+ }
101
+
102
+ // src/transport/ssh.ts
11
103
  var Ssh2Session = class {
12
104
  constructor(client) {
13
105
  this.client = client;
@@ -147,27 +239,19 @@ async function connect(opts) {
147
239
  });
148
240
  }
149
241
  function verifyHostKey(host, port, keyHash, knownHostsPath) {
150
- const keyHex = keyHash.toString("hex");
151
- const hostEntry = port === 22 ? host : `[${host}]:${port}`;
152
- let existing = null;
242
+ let content = "";
153
243
  try {
154
- const content = readFileSync(knownHostsPath, "utf-8");
155
- for (const line of content.split("\n")) {
156
- const parts = line.trim().split(/\s+/);
157
- if (parts[0] === hostEntry && parts.length >= 2) {
158
- existing = parts[1] ?? null;
159
- break;
160
- }
161
- }
244
+ content = readFileSync(knownHostsPath, "utf-8");
162
245
  } catch {
163
246
  }
164
- if (existing !== null) {
165
- return existing === keyHex;
166
- }
247
+ const verdict = verifyAgainstKnownHosts(content, host, port, keyHash);
248
+ if (verdict === "match") return true;
249
+ if (verdict === "mismatch") return false;
250
+ const keyType = keyTypeFromBlob(keyHash);
251
+ if (!keyType) return false;
167
252
  try {
168
253
  mkdirSync(path.dirname(knownHostsPath), { recursive: true });
169
- appendFileSync(knownHostsPath, `${hostEntry} ${keyHex}
170
- `, "utf-8");
254
+ appendFileSync(knownHostsPath, formatKnownHostsLine(host, port, keyType, keyHash), "utf-8");
171
255
  } catch {
172
256
  }
173
257
  return true;
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- validatePlan
4
- } from "./chunk-YTH4L2GN.js";
5
2
  import {
6
3
  saveOverlay
7
4
  } from "./chunk-6ZFIFDBJ.js";
8
5
  import {
9
6
  resolveSecrets
10
7
  } from "./chunk-BOPSG2LI.js";
8
+ import {
9
+ validatePlan
10
+ } from "./chunk-6X45DDGH.js";
11
11
  import {
12
12
  atomicWriteConfig,
13
13
  deepMerge,
14
14
  readRemoteConfig,
15
15
  restartGateway
16
- } from "./chunk-ZFNPM2WG.js";
16
+ } from "./chunk-O3F7OGH7.js";
17
17
  import {
18
18
  buildContext
19
- } from "./chunk-3MFZ7E74.js";
19
+ } from "./chunk-P5LKWRPL.js";
20
20
  import {
21
21
  UsageError
22
22
  } from "./chunk-KGXPLI7W.js";
@@ -35,6 +35,8 @@ ${validation.errors.join("\n")}`
35
35
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
36
36
  );
37
37
  }
38
+ const { guardOpenclawVersion } = await import("./version-guard-MZAO4KHS.js");
39
+ await guardOpenclawVersion(plan.spec.openclaw.version);
38
40
  const ctx = buildContext({
39
41
  stack: plan.spec.stackName,
40
42
  provider: plan.spec.provider
@@ -86,7 +88,7 @@ The diff you reviewed may no longer reflect what will be applied.
86
88
  };
87
89
  }
88
90
  async function applyConfigOverlay(plan, outputs, ctx, signal) {
89
- const { connect } = await import("./ssh-E2AMBPZY.js");
91
+ const { connect } = await import("./ssh-CNJNLM3E.js");
90
92
  const connInfo = ctx.adapter.getConnectionInfo(outputs);
91
93
  const session = await connect({
92
94
  host: connInfo.host,
@@ -1,19 +1,20 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ resolveSpecDir
4
+ } from "./chunk-PVUVI35Y.js";
2
5
 
3
6
  // src/plan/validate.ts
4
7
  import Ajv from "ajv/dist/2020";
5
8
  import addFormats from "ajv-formats";
6
9
  import { readFileSync } from "fs";
7
- import { fileURLToPath } from "url";
8
- import { join, dirname } from "path";
9
- var __dirname = dirname(fileURLToPath(import.meta.url));
10
+ import { join } from "path";
10
11
  var _validate;
11
12
  function getValidator() {
12
13
  if (_validate) return _validate;
13
14
  const ajv = new Ajv({ strict: false });
14
15
  addFormats(ajv);
15
16
  const schema = JSON.parse(
16
- readFileSync(join(__dirname, "../../spec/deploy-plan.schema.json"), "utf-8")
17
+ readFileSync(join(resolveSpecDir(), "deploy-plan.schema.json"), "utf-8")
17
18
  );
18
19
  _validate = ajv.compile(schema);
19
20
  return _validate;
@@ -42,6 +42,17 @@ usermod -aG docker clawops
42
42
  OPENCLAW_VERSION="${openclawVersion}"
43
43
  docker pull ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
44
44
 
45
+ # \u2500\u2500 Gateway auth token \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
46
+ # OpenClaw refuses a non-loopback bind without auth, and in a container it always
47
+ # binds 0.0.0.0. Without a token the gateway exits 78 and restart-loops.
48
+ OPENCLAW_ENV_FILE=/home/clawops/openclaw.env
49
+ if [ ! -s "\${OPENCLAW_ENV_FILE}" ]; then
50
+ OPENCLAW_TOKEN=$(openssl rand -hex 32 2>/dev/null || head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \\n')
51
+ printf 'OPENCLAW_GATEWAY_TOKEN=%s\\n' "\${OPENCLAW_TOKEN}" > "\${OPENCLAW_ENV_FILE}"
52
+ chmod 600 "\${OPENCLAW_ENV_FILE}"
53
+ chown clawops:clawops "\${OPENCLAW_ENV_FILE}"
54
+ fi
55
+
45
56
  # \u2500\u2500 Default config (apply.ts will overwrite with plan overlay) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
46
57
  OPENCLAW_CONFIG=/home/clawops/openclaw.json
47
58
  if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
@@ -58,9 +69,11 @@ docker run -d \\
58
69
  --name openclaw \\
59
70
  --restart unless-stopped \\
60
71
  -p 18789:18789 \\
72
+ -e OPENCLAW_CONFIG_PATH=/app/config.json --add-host=host.docker.internal:host-gateway \\
73
+ --env-file "\${OPENCLAW_ENV_FILE}" \\
61
74
  -v "\${OPENCLAW_CONFIG}":/app/config.json:ro \\
62
75
  ${bedrockEnvBlock} ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
63
- node openclaw.mjs gateway run --allow-unconfigured
76
+ node openclaw.mjs gateway run --allow-unconfigured --port 18789
64
77
  `;
65
78
  }
66
79
  function makeBedrockEnvBlock() {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  connect
4
- } from "./chunk-GJEF6UQA.js";
4
+ } from "./chunk-3XK66DKK.js";
5
5
  import {
6
6
  NetworkError
7
7
  } from "./chunk-KGXPLI7W.js";
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ acquireSession,
4
+ drainPool
5
+ } from "./chunk-DQDAWNGB.js";
2
6
  import {
3
7
  OPENCLAW_CONFIG,
4
8
  atomicWriteConfig,
5
9
  restartGateway
6
- } from "./chunk-ZFNPM2WG.js";
10
+ } from "./chunk-O3F7OGH7.js";
7
11
  import {
8
12
  buildContext
9
- } from "./chunk-3MFZ7E74.js";
10
- import {
11
- acquireSession,
12
- drainPool
13
- } from "./chunk-LCKD7L7X.js";
13
+ } from "./chunk-P5LKWRPL.js";
14
14
  import {
15
15
  chalk,
16
16
  failure
@@ -131,7 +131,7 @@ function renderSnapshot(snap, opts) {
131
131
  return lines.join("\n");
132
132
  }
133
133
  async function probeEntries() {
134
- const { buildContext: buildContext2 } = await import("./context-ALSJMTHE.js");
134
+ const { buildContext: buildContext2 } = await import("./context-5LCUG2QR.js");
135
135
  const { getConfig } = await import("./store-SDUR52Z5.js");
136
136
  const config = getConfig();
137
137
  if (!config) return [];
@@ -358,8 +358,8 @@ var monitor_default = defineCommand({
358
358
  const tailLines = Math.max(1, parseInt(String(args.tail ?? "10"), 10) || 10);
359
359
  const isTTY = Boolean(process.stdout.isTTY);
360
360
  const noColor = Boolean(args["no-color"]) || !isTTY;
361
- const { buildContext: buildContext2 } = await import("./context-ALSJMTHE.js");
362
- const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-JZGKPP6K.js");
361
+ const { buildContext: buildContext2 } = await import("./context-5LCUG2QR.js");
362
+ const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-QGFQRMGU.js");
363
363
  const ac = new AbortController();
364
364
  process.on("SIGINT", () => ac.abort());
365
365
  process.on("SIGTERM", () => ac.abort());
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  validatePlan
4
- } from "./chunk-YTH4L2GN.js";
4
+ } from "./chunk-6X45DDGH.js";
5
5
  import {
6
6
  buildContext
7
- } from "./chunk-3MFZ7E74.js";
7
+ } from "./chunk-P5LKWRPL.js";
8
8
  import {
9
9
  getConfig
10
10
  } from "./chunk-CX5SL5HP.js";
@@ -45,10 +45,13 @@ async function generatePlan(intent, _opts) {
45
45
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
46
46
  );
47
47
  }
48
- const { version } = await import("./package-OMPI6RMV.js");
48
+ const { version } = await import("./package-QE7V2JAT.js");
49
49
  const config = getConfig();
50
50
  const instanceType = intent.instanceType ?? "small";
51
- const openclawVersion = intent.openclawVersion ?? "latest";
51
+ const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-MZAO4KHS.js");
52
+ const openclawVersion = await guardOpenclawVersion(
53
+ intent.openclawVersion ?? await defaultOpenclawVersion()
54
+ );
52
55
  const network = intent.network ?? {
53
56
  allowedSshCidrs: [],
54
57
  allowedGatewayCidrs: []
@@ -1,5 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/openclaw/run-flags.ts
4
+ var CONFIG_MOUNT_PATH = "/app/config.json";
5
+ var GATEWAY_PORT = 18789;
6
+ var CONFIG_PATH_ENV = `-e OPENCLAW_CONFIG_PATH=${CONFIG_MOUNT_PATH}`;
7
+ var ADD_HOST_FLAG = "--add-host=host.docker.internal:host-gateway";
8
+ var PORT_PIN = `--port ${GATEWAY_PORT}`;
9
+ var COMMON_RUN_FLAGS = `${CONFIG_PATH_ENV} ${ADD_HOST_FLAG}`;
10
+
3
11
  // src/plan/remote-config.ts
4
12
  var OPENCLAW_CONFIG_LINUX = "/home/clawops/openclaw.json";
5
13
  var OPENCLAW_CONFIG_MACOS = "~/.config/openclaw/config.json";
@@ -30,6 +38,18 @@ async function readRemoteConfig(session, signal) {
30
38
  throw new Error(`Cannot parse ${configPath}: invalid JSON`);
31
39
  }
32
40
  }
41
+ function normaliseGatewayPort(cfg) {
42
+ const gateway = cfg["gateway"];
43
+ if (!gateway || typeof gateway !== "object" || Array.isArray(gateway)) return void 0;
44
+ const g = gateway;
45
+ const current = g["port"];
46
+ if (typeof current === "number" && current !== GATEWAY_PORT) {
47
+ g["port"] = GATEWAY_PORT;
48
+ return current;
49
+ }
50
+ if (current === void 0) g["port"] = GATEWAY_PORT;
51
+ return void 0;
52
+ }
33
53
  async function atomicWriteConfig(session, cfg, signal) {
34
54
  const os = await detectOS(session, signal);
35
55
  const configPath = configPathForOS(os);
@@ -50,25 +70,41 @@ async function restartGateway(session, signal) {
50
70
  const imgResult = os === "Darwin" ? await session.exec(imgCmd, signal) : await execWithFallbackSudo(session, imgCmd, signal);
51
71
  const image = imgResult.stdout.trim();
52
72
  const cfgResult = await session.exec(`cat ${configPath}`, signal);
53
- let gatewayCmd = "node openclaw.mjs gateway run --allow-unconfigured";
73
+ let gatewayCmd = `node openclaw.mjs gateway run --allow-unconfigured ${PORT_PIN}`;
54
74
  try {
55
75
  const cfg = JSON.parse(cfgResult.stdout);
56
76
  const token = cfg?.["gateway"]?.["auth"];
57
77
  const tokenVal = token?.["token"];
58
78
  if (tokenVal) {
59
- gatewayCmd = `node openclaw.mjs gateway run --allow-unconfigured --token '${tokenVal}'`;
79
+ gatewayCmd = `node openclaw.mjs gateway run --allow-unconfigured ${PORT_PIN} --token '${tokenVal}'`;
60
80
  }
61
81
  } catch {
62
82
  }
63
83
  const restartCmd = pathPrefix + [
64
84
  "docker stop openclaw 2>/dev/null || true",
65
85
  "docker rm openclaw 2>/dev/null || true",
66
- `docker run -d --name openclaw --restart unless-stopped -p 18789:18789 -v ${configPath}:/app/config.json:ro ${image} ${gatewayCmd}`
86
+ `docker run -d --name openclaw --restart unless-stopped -p ${GATEWAY_PORT}:${GATEWAY_PORT} ${COMMON_RUN_FLAGS} -v ${configPath}:/app/config.json:ro ${image} ${gatewayCmd}`
67
87
  ].join(" && ");
68
88
  const result = os === "Darwin" ? await session.exec(restartCmd, signal) : await execWithFallbackSudo(session, restartCmd, signal);
69
89
  if (result.code !== 0) {
70
90
  throw new Error(`Gateway restart failed: ${result.stderr}`);
71
91
  }
92
+ const healthy = await waitForGateway(session, pathPrefix, signal);
93
+ if (!healthy) {
94
+ throw new Error(
95
+ `Gateway restarted but did not become healthy on port ${GATEWAY_PORT}. The previous container has already been replaced; inspect it with \`docker logs openclaw\`. If the newly-applied config is at fault, revert it and restart \u2014 before v1.7.2 this config was never applied, so a value that has sat unused may now be taking effect.`
96
+ );
97
+ }
98
+ }
99
+ async function waitForGateway(session, pathPrefix, signal, attempts = 15) {
100
+ const probe = `${pathPrefix}curl -fsS -m 3 http://127.0.0.1:${GATEWAY_PORT}/healthz >/dev/null 2>&1 && echo ok || echo waiting`;
101
+ for (let i = 0; i < attempts; i++) {
102
+ if (signal?.aborted) return false;
103
+ const r = await session.exec(probe, signal);
104
+ if (r.stdout.trim().endsWith("ok")) return true;
105
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
106
+ }
107
+ return false;
72
108
  }
73
109
  function deepMerge(base, overlay) {
74
110
  const result = { ...base };
@@ -91,6 +127,7 @@ export {
91
127
  OPENCLAW_CONFIG,
92
128
  OPENCLAW_TMP,
93
129
  readRemoteConfig,
130
+ normaliseGatewayPort,
94
131
  atomicWriteConfig,
95
132
  restartGateway,
96
133
  deepMerge
@@ -56,17 +56,17 @@ function makeProviderProxy(name) {
56
56
  if (resolved) return resolved;
57
57
  switch (name) {
58
58
  case "gcp": {
59
- const mod = await import("./gcp-6FT2A45S.js");
59
+ const mod = await import("./gcp-UG6AWPIZ.js");
60
60
  resolved = mod.default;
61
61
  return resolved;
62
62
  }
63
63
  case "aws": {
64
- const mod = await import("./aws-D7Y6LCGK.js");
64
+ const mod = await import("./aws-L4RR2LSE.js");
65
65
  resolved = mod.default;
66
66
  return resolved;
67
67
  }
68
68
  case "azure": {
69
- const mod = await import("./azure-JQAMNVHN.js");
69
+ const mod = await import("./azure-7OXQENQL.js");
70
70
  resolved = mod.default;
71
71
  return resolved;
72
72
  }
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/spec-path.ts
4
+ import { existsSync } from "fs";
5
+ import { fileURLToPath } from "url";
6
+ import { dirname, join, parse } from "path";
7
+ var MARKER = "openclaw-versions.yaml";
8
+ var _cached;
9
+ function resolveSpecDir(startUrl = import.meta.url) {
10
+ if (_cached) return _cached;
11
+ let dir = dirname(fileURLToPath(startUrl));
12
+ const { root } = parse(dir);
13
+ while (true) {
14
+ const candidate = join(dir, "spec");
15
+ if (existsSync(join(candidate, MARKER))) {
16
+ _cached = candidate;
17
+ return candidate;
18
+ }
19
+ if (dir === root) break;
20
+ dir = dirname(dir);
21
+ }
22
+ throw new Error(
23
+ `Cannot locate the spec/ directory (searched upward from ${dirname(fileURLToPath(startUrl))}). If this is an installed package, spec/ may be missing from the published files.`
24
+ );
25
+ }
26
+
27
+ export {
28
+ resolveSpecDir
29
+ };