denchclaw 2.0.0 → 2.0.2

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 (36) hide show
  1. package/README.md +1 -1
  2. package/apps/web/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  3. package/apps/web/.next/standalone/apps/web/.next/app-build-manifest.json +95 -95
  4. package/apps/web/.next/standalone/apps/web/.next/app-path-routes-manifest.json +27 -27
  5. package/apps/web/.next/standalone/apps/web/.next/build-manifest.json +2 -2
  6. package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
  7. package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found.rsc +1 -1
  8. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/init/route.js +1 -1
  9. package/apps/web/.next/standalone/apps/web/.next/server/app/index.html +2 -2
  10. package/apps/web/.next/standalone/apps/web/.next/server/app/index.rsc +1 -1
  11. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace/page.js +1 -1
  12. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace/page_client-reference-manifest.js +1 -1
  13. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace.html +1 -1
  14. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace.rsc +2 -2
  15. package/apps/web/.next/standalone/apps/web/.next/server/app-paths-manifest.json +27 -27
  16. package/apps/web/.next/standalone/apps/web/.next/server/functions-config-manifest.json +20 -20
  17. package/apps/web/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
  18. package/apps/web/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  19. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/workspace/page-a9c68aaa3f71a7fe.js +1 -0
  20. package/apps/web/.next/standalone/package.json +2 -2
  21. package/apps/web/.next/static/chunks/app/workspace/page-a9c68aaa3f71a7fe.js +1 -0
  22. package/dist/{cli-name-DUQ-cavN.js → cli-name-8WJ6gVD5.js} +36 -3
  23. package/dist/entry.js +2 -21
  24. package/dist/program-DSR-Zphq.js +2412 -0
  25. package/dist/{run-main-buUOQk0k.js → run-main-B-QwRglo.js} +9 -7
  26. package/package.json +2 -2
  27. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/workspace/page-d499296a443bbbf0.js +0 -1
  28. package/apps/web/.next/static/chunks/app/workspace/page-d499296a443bbbf0.js +0 -1
  29. package/dist/links-BTx7dmFj.js +0 -57
  30. package/dist/program-DahL9wBm.js +0 -195
  31. package/dist/register.bootstrap-D9YhQgaK.js +0 -1330
  32. package/dist/theme-uCBEEejb.js +0 -36
  33. /package/apps/web/.next/standalone/apps/web/.next/static/{oOwR1DKioMELtFQgLgScE → rfl8V4U-KWtyivUflJxQF}/_buildManifest.js +0 -0
  34. /package/apps/web/.next/standalone/apps/web/.next/static/{oOwR1DKioMELtFQgLgScE → rfl8V4U-KWtyivUflJxQF}/_ssgManifest.js +0 -0
  35. /package/apps/web/.next/static/{oOwR1DKioMELtFQgLgScE → rfl8V4U-KWtyivUflJxQF}/_buildManifest.js +0 -0
  36. /package/apps/web/.next/static/{oOwR1DKioMELtFQgLgScE → rfl8V4U-KWtyivUflJxQF}/_ssgManifest.js +0 -0
@@ -0,0 +1,2412 @@
1
+ import { c as hasFlag, d as applyCliProfileEnv, f as expandHomePrefix, i as defaultRuntime, p as resolveRequiredHomeDir, s as getPrimaryCommand, t as isTruthyEnvValue, u as hasRootVersionAlias } from "./entry.js";
2
+ import { a as hasEmittedCliBanner, c as theme, i as formatCliBannerLine, n as resolveCliName, o as VERSION, s as isRich, t as replaceCliName } from "./cli-name-8WJ6gVD5.js";
3
+ import { execFileSync, spawn } from "node:child_process";
4
+ import process$1 from "node:process";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import fs, { copyFileSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
8
+ import { Command } from "commander";
9
+ import { confirm, isCancel, spinner } from "@clack/prompts";
10
+ import { fileURLToPath } from "node:url";
11
+ import { createConnection } from "node:net";
12
+
13
+ //#region src/infra/ports-lsof.ts
14
+ const LSOF_CANDIDATES = process.platform === "darwin" ? ["/usr/sbin/lsof", "/usr/bin/lsof"] : ["/usr/bin/lsof", "/usr/sbin/lsof"];
15
+ function resolveLsofCommandSync() {
16
+ for (const candidate of LSOF_CANDIDATES) try {
17
+ fs.accessSync(candidate, fs.constants.X_OK);
18
+ return candidate;
19
+ } catch {}
20
+ return "lsof";
21
+ }
22
+
23
+ //#endregion
24
+ //#region src/utils.ts
25
+ /**
26
+ * Escapes special regex characters in a string so it can be used in a RegExp constructor.
27
+ */
28
+ function escapeRegExp(value) {
29
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
30
+ }
31
+ function sleep$1(ms) {
32
+ return new Promise((resolve) => setTimeout(resolve, ms));
33
+ }
34
+ function resolveUserPath(input) {
35
+ const trimmed = input.trim();
36
+ if (!trimmed) return trimmed;
37
+ if (trimmed.startsWith("~")) {
38
+ const expanded = expandHomePrefix(trimmed, {
39
+ home: resolveRequiredHomeDir(process.env, os.homedir),
40
+ env: process.env,
41
+ homedir: os.homedir
42
+ });
43
+ return path.resolve(expanded);
44
+ }
45
+ return path.resolve(trimmed);
46
+ }
47
+ function resolveConfigDir(env = process.env, homedir = os.homedir) {
48
+ const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
49
+ if (override) return resolveUserPath(override);
50
+ const newDir = path.join(resolveRequiredHomeDir(env, homedir), ".openclaw");
51
+ try {
52
+ if (fs.existsSync(newDir)) return newDir;
53
+ } catch {}
54
+ return newDir;
55
+ }
56
+ function formatTerminalLink(label, url, opts) {
57
+ const esc = "\x1B";
58
+ const safeLabel = label.replaceAll(esc, "");
59
+ const safeUrl = url.replaceAll(esc, "");
60
+ if (!(opts?.force === true ? true : opts?.force === false ? false : Boolean(process.stdout.isTTY))) return opts?.fallback ?? `${safeLabel} (${safeUrl})`;
61
+ return `\u001b]8;;${safeUrl}\u0007${safeLabel}\u001b]8;;\u0007`;
62
+ }
63
+ const CONFIG_DIR = resolveConfigDir();
64
+
65
+ //#endregion
66
+ //#region src/cli/ports.ts
67
+ function parseLsofOutput(output) {
68
+ const lines = output.split(/\r?\n/).filter(Boolean);
69
+ const results = [];
70
+ let current = {};
71
+ for (const line of lines) if (line.startsWith("p")) {
72
+ if (current.pid) results.push(current);
73
+ current = { pid: Number.parseInt(line.slice(1), 10) };
74
+ } else if (line.startsWith("c")) current.command = line.slice(1);
75
+ if (current.pid) results.push(current);
76
+ return results;
77
+ }
78
+ function listPortListeners(port) {
79
+ try {
80
+ return parseLsofOutput(execFileSync(resolveLsofCommandSync(), [
81
+ "-nP",
82
+ `-iTCP:${port}`,
83
+ "-sTCP:LISTEN",
84
+ "-FpFc"
85
+ ], { encoding: "utf-8" }));
86
+ } catch (err) {
87
+ const status = err.status;
88
+ if (err.code === "ENOENT") throw new Error("lsof not found; required for --force", { cause: err });
89
+ if (status === 1) return [];
90
+ throw err instanceof Error ? err : new Error(String(err));
91
+ }
92
+ }
93
+
94
+ //#endregion
95
+ //#region src/terminal/links.ts
96
+ const DOCS_ROOT = "https://docs.openclaw.ai";
97
+ function formatDocsLink(path, label, opts) {
98
+ const trimmed = path.trim();
99
+ const url = trimmed.startsWith("http") ? trimmed : `${DOCS_ROOT}${trimmed.startsWith("/") ? trimmed : `/${trimmed}`}`;
100
+ return formatTerminalLink(label ?? url, url, {
101
+ fallback: opts?.fallback ?? url,
102
+ force: opts?.force
103
+ });
104
+ }
105
+
106
+ //#endregion
107
+ //#region src/terminal/prompt-style.ts
108
+ const stylePromptMessage = (message) => isRich() ? theme.accent(message) : message;
109
+
110
+ //#endregion
111
+ //#region src/cli/web-runtime.ts
112
+ const DEFAULT_WEB_APP_PORT = 3100;
113
+ const WEB_RUNTIME_DIRNAME = "web-runtime";
114
+ const WEB_RUNTIME_APP_DIRNAME = "app";
115
+ const WEB_RUNTIME_MANIFEST_FILENAME = "manifest.json";
116
+ const WEB_RUNTIME_PROCESS_FILENAME = "process.json";
117
+ const WEB_APP_PROBE_ATTEMPTS = 20;
118
+ const WEB_APP_PROBE_DELAY_MS = 750;
119
+ const WEB_APP_PROBE_TIMEOUT_MS = 1500;
120
+ const LEGACY_STANDALONE_SEGMENT = "/apps/web/.next/standalone/apps/web";
121
+ function normalizePathForMatch(value) {
122
+ return value.replaceAll("\\", "/").toLowerCase();
123
+ }
124
+ function isPathWithin(parentDir, candidatePath) {
125
+ const relative = path.relative(path.resolve(parentDir), path.resolve(candidatePath));
126
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
127
+ }
128
+ function parseOptionalPositiveInt(value) {
129
+ if (value === void 0) return;
130
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value), 10);
131
+ if (!Number.isFinite(parsed) || parsed <= 0) return;
132
+ return parsed;
133
+ }
134
+ function ensureParentDir(filePath) {
135
+ mkdirSync(path.dirname(filePath), { recursive: true });
136
+ }
137
+ function readJsonFile(filePath) {
138
+ if (!existsSync(filePath)) return null;
139
+ try {
140
+ return JSON.parse(readFileSync(filePath, "utf-8"));
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+ function writeJsonFile(filePath, value) {
146
+ ensureParentDir(filePath);
147
+ writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
148
+ }
149
+ function isProcessAlive(pid) {
150
+ try {
151
+ process$1.kill(pid, 0);
152
+ return true;
153
+ } catch (error) {
154
+ return error.code !== "ESRCH";
155
+ }
156
+ }
157
+ async function terminatePidWithEscalation(pid) {
158
+ try {
159
+ process$1.kill(pid, "SIGTERM");
160
+ } catch (error) {
161
+ if (error.code === "ESRCH") return;
162
+ throw error;
163
+ }
164
+ for (let i = 0; i < 8; i += 1) {
165
+ if (!isProcessAlive(pid)) return;
166
+ await sleep$1(100);
167
+ }
168
+ if (!isProcessAlive(pid)) return;
169
+ try {
170
+ process$1.kill(pid, "SIGKILL");
171
+ } catch (error) {
172
+ if (error.code === "ESRCH") return;
173
+ throw error;
174
+ }
175
+ for (let i = 0; i < 8; i += 1) {
176
+ if (!isProcessAlive(pid)) return;
177
+ await sleep$1(100);
178
+ }
179
+ }
180
+ function resolveCliPackageRoot() {
181
+ let dir = path.dirname(fileURLToPath(import.meta.url));
182
+ for (let i = 0; i < 5; i += 1) {
183
+ if (existsSync(path.join(dir, "package.json"))) return dir;
184
+ dir = path.dirname(dir);
185
+ }
186
+ return process$1.cwd();
187
+ }
188
+ function resolveProfileStateDir(profile, env = process$1.env) {
189
+ const home = resolveRequiredHomeDir(env, os.homedir);
190
+ return path.join(home, ".openclaw-dench");
191
+ }
192
+ function resolveManagedWebRuntimeDir(stateDir) {
193
+ return path.join(stateDir, WEB_RUNTIME_DIRNAME);
194
+ }
195
+ function resolveManagedWebRuntimeAppDir(stateDir) {
196
+ return path.join(resolveManagedWebRuntimeDir(stateDir), WEB_RUNTIME_APP_DIRNAME);
197
+ }
198
+ function resolveManagedWebRuntimeServerPath(stateDir) {
199
+ return path.join(resolveManagedWebRuntimeAppDir(stateDir), "server.js");
200
+ }
201
+ function resolveManagedWebRuntimeManifestPath(stateDir) {
202
+ return path.join(resolveManagedWebRuntimeDir(stateDir), WEB_RUNTIME_MANIFEST_FILENAME);
203
+ }
204
+ function resolveManagedWebRuntimeProcessPath(stateDir) {
205
+ return path.join(resolveManagedWebRuntimeDir(stateDir), WEB_RUNTIME_PROCESS_FILENAME);
206
+ }
207
+ function resolvePackagedStandaloneServerPath(packageRoot) {
208
+ return path.join(packageRoot, "apps/web/.next/standalone/apps/web/server.js");
209
+ }
210
+ function resolvePackagedStandaloneAppDir(packageRoot) {
211
+ return path.dirname(resolvePackagedStandaloneServerPath(packageRoot));
212
+ }
213
+ function readManagedWebRuntimeManifest(stateDir) {
214
+ return readJsonFile(resolveManagedWebRuntimeManifestPath(stateDir));
215
+ }
216
+ function readManagedWebRuntimeProcess(stateDir) {
217
+ return readJsonFile(resolveManagedWebRuntimeProcessPath(stateDir));
218
+ }
219
+ function writeManagedWebRuntimeManifest(stateDir, manifest) {
220
+ writeJsonFile(resolveManagedWebRuntimeManifestPath(stateDir), manifest);
221
+ return manifest;
222
+ }
223
+ function writeManagedWebRuntimeProcess(stateDir, processMeta) {
224
+ writeJsonFile(resolveManagedWebRuntimeProcessPath(stateDir), processMeta);
225
+ }
226
+ function clearManagedWebRuntimeProcess(stateDir) {
227
+ rmSync(resolveManagedWebRuntimeProcessPath(stateDir), { force: true });
228
+ }
229
+ function updateManifestLastPort(stateDir, webPort, gatewayPort) {
230
+ const manifest = readManagedWebRuntimeManifest(stateDir);
231
+ if (!manifest) return null;
232
+ return writeManagedWebRuntimeManifest(stateDir, {
233
+ ...manifest,
234
+ lastPort: webPort,
235
+ lastGatewayPort: gatewayPort
236
+ });
237
+ }
238
+ function evaluateWebProfilesPayload(payload) {
239
+ if (!payload || typeof payload !== "object") return {
240
+ ok: false,
241
+ reason: "response payload is not an object"
242
+ };
243
+ const data = payload;
244
+ if (!(Array.isArray(data.profiles) ? data.profiles : Array.isArray(data.workspaces) ? data.workspaces : void 0)) return {
245
+ ok: false,
246
+ reason: "response payload missing profiles/workspaces array"
247
+ };
248
+ if ((data.activeProfile === null || typeof data.activeProfile === "string" ? data.activeProfile : data.activeWorkspace === null || typeof data.activeWorkspace === "string" ? data.activeWorkspace : void 0) === void 0) return {
249
+ ok: false,
250
+ reason: "response payload missing active profile/workspace field"
251
+ };
252
+ return {
253
+ ok: true,
254
+ reason: "profiles payload shape is valid"
255
+ };
256
+ }
257
+ async function probeWebRuntime(port) {
258
+ const controller = new AbortController();
259
+ const timer = setTimeout(() => controller.abort(), WEB_APP_PROBE_TIMEOUT_MS);
260
+ try {
261
+ const response = await fetch(`http://127.0.0.1:${port}/api/profiles`, {
262
+ method: "GET",
263
+ signal: controller.signal,
264
+ redirect: "manual"
265
+ });
266
+ if (response.status < 200 || response.status >= 400) return {
267
+ ok: false,
268
+ status: response.status,
269
+ reason: `/api/profiles returned status ${response.status}`
270
+ };
271
+ const evaluation = evaluateWebProfilesPayload(await response.json().catch(() => null));
272
+ return {
273
+ ok: evaluation.ok,
274
+ status: response.status,
275
+ reason: evaluation.reason
276
+ };
277
+ } catch (error) {
278
+ return {
279
+ ok: false,
280
+ reason: (error instanceof Error ? error.message : String(error)) || "probe failed"
281
+ };
282
+ } finally {
283
+ clearTimeout(timer);
284
+ }
285
+ }
286
+ async function waitForWebRuntime(port) {
287
+ let lastResult = {
288
+ ok: false,
289
+ reason: "web runtime did not respond"
290
+ };
291
+ for (let attempt = 0; attempt < WEB_APP_PROBE_ATTEMPTS; attempt += 1) {
292
+ const result = await probeWebRuntime(port);
293
+ if (result.ok) return result;
294
+ lastResult = result;
295
+ await sleep$1(WEB_APP_PROBE_DELAY_MS);
296
+ }
297
+ return lastResult;
298
+ }
299
+ function classifyWebPortListener(params) {
300
+ if (!params.cwd) return "foreign";
301
+ if (isPathWithin(params.managedRuntimeAppDir, params.cwd)) return "managed";
302
+ if (normalizePathForMatch(params.cwd).includes(LEGACY_STANDALONE_SEGMENT)) return "legacy-standalone";
303
+ return "foreign";
304
+ }
305
+ function parseSemverMajor(version) {
306
+ if (!version) return null;
307
+ const match = version.trim().match(/^v?(\d+)(?:\.\d+)?(?:\.\d+)?(?:[-+].*)?$/u);
308
+ if (!match) return null;
309
+ const major = Number.parseInt(match[1], 10);
310
+ if (!Number.isFinite(major)) return null;
311
+ return major;
312
+ }
313
+ function evaluateMajorVersionTransition(params) {
314
+ const previousMajor = parseSemverMajor(params.previousVersion);
315
+ const currentMajor = parseSemverMajor(params.currentVersion);
316
+ return {
317
+ previousMajor,
318
+ currentMajor,
319
+ isMajorTransition: previousMajor !== null && currentMajor !== null && previousMajor !== currentMajor
320
+ };
321
+ }
322
+ function resolveProcessCwd(pid) {
323
+ try {
324
+ const output = execFileSync(resolveLsofCommandSync(), [
325
+ "-nP",
326
+ "-a",
327
+ "-p",
328
+ String(pid),
329
+ "-d",
330
+ "cwd",
331
+ "-Fn"
332
+ ], { encoding: "utf-8" });
333
+ for (const line of output.split(/\r?\n/)) if (line.startsWith("n")) {
334
+ const cwd = line.slice(1).trim();
335
+ if (cwd.length > 0) return cwd;
336
+ }
337
+ return;
338
+ } catch {
339
+ return;
340
+ }
341
+ }
342
+ function inspectWebPortListeners(port, stateDir) {
343
+ if (process$1.env.VITEST === "true" && process$1.env.OPENCLAW_TEST_REAL_PORTS !== "1") return [];
344
+ const listeners = listPortListeners(port);
345
+ const managedRuntimeAppDir = resolveManagedWebRuntimeAppDir(stateDir);
346
+ return listeners.map((listener) => {
347
+ const cwd = resolveProcessCwd(listener.pid);
348
+ const ownership = classifyWebPortListener({
349
+ cwd,
350
+ managedRuntimeAppDir
351
+ });
352
+ return {
353
+ ...listener,
354
+ cwd,
355
+ ownership
356
+ };
357
+ });
358
+ }
359
+ function readLastKnownWebPort(stateDir) {
360
+ const processPort = parseOptionalPositiveInt(readManagedWebRuntimeProcess(stateDir)?.port);
361
+ if (processPort) return processPort;
362
+ const manifestPort = parseOptionalPositiveInt(readManagedWebRuntimeManifest(stateDir)?.lastPort);
363
+ if (manifestPort) return manifestPort;
364
+ return DEFAULT_WEB_APP_PORT;
365
+ }
366
+ function installManagedWebRuntime(params) {
367
+ const runtimeDir = resolveManagedWebRuntimeDir(params.stateDir);
368
+ const runtimeAppDir = resolveManagedWebRuntimeAppDir(params.stateDir);
369
+ const runtimeServerPath = resolveManagedWebRuntimeServerPath(params.stateDir);
370
+ const sourceStandaloneServer = resolvePackagedStandaloneServerPath(params.packageRoot);
371
+ const sourceAppDir = resolvePackagedStandaloneAppDir(params.packageRoot);
372
+ if (!existsSync(sourceStandaloneServer)) return {
373
+ installed: false,
374
+ runtimeDir,
375
+ runtimeAppDir,
376
+ runtimeServerPath,
377
+ reason: "standalone-missing"
378
+ };
379
+ mkdirSync(runtimeDir, { recursive: true });
380
+ rmSync(runtimeAppDir, {
381
+ recursive: true,
382
+ force: true
383
+ });
384
+ cpSync(sourceAppDir, runtimeAppDir, {
385
+ recursive: true,
386
+ force: true
387
+ });
388
+ const manifest = {
389
+ schemaVersion: 1,
390
+ deployedDenchVersion: params.denchVersion,
391
+ deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
392
+ sourceStandaloneServer,
393
+ ...typeof params.webPort === "number" ? { lastPort: params.webPort } : {},
394
+ ...typeof params.gatewayPort === "number" ? { lastGatewayPort: params.gatewayPort } : {}
395
+ };
396
+ writeManagedWebRuntimeManifest(params.stateDir, manifest);
397
+ return {
398
+ installed: true,
399
+ runtimeDir,
400
+ runtimeAppDir,
401
+ runtimeServerPath,
402
+ manifest
403
+ };
404
+ }
405
+ async function stopManagedWebRuntime(params) {
406
+ const listeners = inspectWebPortListeners(params.port, params.stateDir);
407
+ const includeLegacyStandalone = params.includeLegacyStandalone ?? true;
408
+ const stoppable = listeners.filter((listener) => listener.ownership === "managed" || includeLegacyStandalone && listener.ownership === "legacy-standalone");
409
+ const skippedForeign = listeners.filter((listener) => listener.ownership === "foreign");
410
+ const uniquePids = [...new Set(stoppable.map((listener) => listener.pid))];
411
+ const stoppedPids = [];
412
+ for (const pid of uniquePids) {
413
+ await terminatePidWithEscalation(pid);
414
+ if (!isProcessAlive(pid)) stoppedPids.push(pid);
415
+ }
416
+ const processMeta = readManagedWebRuntimeProcess(params.stateDir);
417
+ if (processMeta?.port === params.port && stoppedPids.includes(processMeta.pid)) clearManagedWebRuntimeProcess(params.stateDir);
418
+ if (inspectWebPortListeners(params.port, params.stateDir).filter((listener) => listener.ownership === "managed").length === 0) clearManagedWebRuntimeProcess(params.stateDir);
419
+ return {
420
+ port: params.port,
421
+ stoppedPids,
422
+ skippedForeignPids: [...new Set(skippedForeign.map((listener) => listener.pid))]
423
+ };
424
+ }
425
+ function startManagedWebRuntime(params) {
426
+ const runtimeServerPath = resolveManagedWebRuntimeServerPath(params.stateDir);
427
+ if (!existsSync(runtimeServerPath)) return {
428
+ started: false,
429
+ runtimeServerPath,
430
+ reason: "runtime-missing"
431
+ };
432
+ const logsDir = path.join(params.stateDir, "logs");
433
+ mkdirSync(logsDir, { recursive: true });
434
+ const outFd = openSync(path.join(logsDir, "web-app.log"), "a");
435
+ const errFd = openSync(path.join(logsDir, "web-app.err.log"), "a");
436
+ const child = spawn(process$1.execPath, [runtimeServerPath], {
437
+ cwd: path.dirname(runtimeServerPath),
438
+ detached: true,
439
+ stdio: [
440
+ "ignore",
441
+ outFd,
442
+ errFd
443
+ ],
444
+ env: {
445
+ ...process$1.env,
446
+ ...params.env,
447
+ PORT: String(params.port),
448
+ HOSTNAME: "127.0.0.1",
449
+ OPENCLAW_GATEWAY_PORT: String(params.gatewayPort)
450
+ }
451
+ });
452
+ child.unref();
453
+ writeManagedWebRuntimeProcess(params.stateDir, {
454
+ pid: child.pid ?? -1,
455
+ port: params.port,
456
+ gatewayPort: params.gatewayPort,
457
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
458
+ runtimeAppDir: path.dirname(runtimeServerPath)
459
+ });
460
+ updateManifestLastPort(params.stateDir, params.port, params.gatewayPort);
461
+ return {
462
+ started: true,
463
+ pid: child.pid ?? -1,
464
+ runtimeServerPath
465
+ };
466
+ }
467
+ async function ensureManagedWebRuntime(params) {
468
+ if (!installManagedWebRuntime({
469
+ stateDir: params.stateDir,
470
+ packageRoot: params.packageRoot,
471
+ denchVersion: params.denchVersion,
472
+ webPort: params.port,
473
+ gatewayPort: params.gatewayPort
474
+ }).installed) return {
475
+ ready: false,
476
+ reason: "standalone web build is missing from package"
477
+ };
478
+ await stopManagedWebRuntime({
479
+ stateDir: params.stateDir,
480
+ port: params.port,
481
+ includeLegacyStandalone: true
482
+ });
483
+ const foreign = inspectWebPortListeners(params.port, params.stateDir).filter((listener) => listener.ownership === "foreign");
484
+ if (foreign.length > 0) {
485
+ const detail = foreign.map((listener) => `${listener.pid}${listener.command ? `:${listener.command}` : ""}`).join(", ");
486
+ return {
487
+ ready: false,
488
+ reason: `port ${params.port} is owned by non-Dench process(es): ${detail}`
489
+ };
490
+ }
491
+ if (!startManagedWebRuntime({
492
+ stateDir: params.stateDir,
493
+ port: params.port,
494
+ gatewayPort: params.gatewayPort
495
+ }).started) return {
496
+ ready: false,
497
+ reason: "managed web runtime is missing after install"
498
+ };
499
+ const probe = await waitForWebRuntime(params.port);
500
+ return {
501
+ ready: probe.ok,
502
+ reason: probe.reason
503
+ };
504
+ }
505
+ function resolveOpenClawCommandOrThrow() {
506
+ try {
507
+ const first = execFileSync(process$1.platform === "win32" ? "where" : "which", ["openclaw"], { encoding: "utf-8" }).trim().split(/\r?\n/).map((line) => line.trim()).find(Boolean);
508
+ if (first) return first;
509
+ throw new Error("openclaw command not found");
510
+ } catch {
511
+ throw new Error("Global `openclaw` CLI was not found on PATH. Install it with: npm install -g openclaw");
512
+ }
513
+ }
514
+
515
+ //#endregion
516
+ //#region src/cli/workspace-seed.ts
517
+ const SEED_OBJECTS = [
518
+ {
519
+ id: "seed_obj_people_00000000000000",
520
+ name: "people",
521
+ description: "Contact management",
522
+ icon: "users",
523
+ defaultView: "table",
524
+ entryCount: 5,
525
+ fields: [
526
+ {
527
+ name: "Full Name",
528
+ type: "text",
529
+ required: true
530
+ },
531
+ {
532
+ name: "Email Address",
533
+ type: "email",
534
+ required: true
535
+ },
536
+ {
537
+ name: "Phone Number",
538
+ type: "phone"
539
+ },
540
+ {
541
+ name: "Company",
542
+ type: "text"
543
+ },
544
+ {
545
+ name: "Status",
546
+ type: "enum",
547
+ enumValues: [
548
+ "Active",
549
+ "Inactive",
550
+ "Lead"
551
+ ]
552
+ },
553
+ {
554
+ name: "Notes",
555
+ type: "richtext"
556
+ }
557
+ ]
558
+ },
559
+ {
560
+ id: "seed_obj_company_0000000000000",
561
+ name: "company",
562
+ description: "Company tracking",
563
+ icon: "building-2",
564
+ defaultView: "table",
565
+ entryCount: 3,
566
+ fields: [
567
+ {
568
+ name: "Company Name",
569
+ type: "text",
570
+ required: true
571
+ },
572
+ {
573
+ name: "Industry",
574
+ type: "enum",
575
+ enumValues: [
576
+ "Technology",
577
+ "Finance",
578
+ "Healthcare",
579
+ "Education",
580
+ "Retail",
581
+ "Other"
582
+ ]
583
+ },
584
+ {
585
+ name: "Website",
586
+ type: "text"
587
+ },
588
+ {
589
+ name: "Type",
590
+ type: "enum",
591
+ enumValues: [
592
+ "Client",
593
+ "Partner",
594
+ "Vendor",
595
+ "Prospect"
596
+ ]
597
+ },
598
+ {
599
+ name: "Notes",
600
+ type: "richtext"
601
+ }
602
+ ]
603
+ },
604
+ {
605
+ id: "seed_obj_task_000000000000000",
606
+ name: "task",
607
+ description: "Task tracking board",
608
+ icon: "check-square",
609
+ defaultView: "kanban",
610
+ entryCount: 5,
611
+ fields: [
612
+ {
613
+ name: "Title",
614
+ type: "text",
615
+ required: true
616
+ },
617
+ {
618
+ name: "Description",
619
+ type: "text"
620
+ },
621
+ {
622
+ name: "Status",
623
+ type: "enum",
624
+ enumValues: [
625
+ "In Queue",
626
+ "In Progress",
627
+ "Done"
628
+ ]
629
+ },
630
+ {
631
+ name: "Priority",
632
+ type: "enum",
633
+ enumValues: [
634
+ "Low",
635
+ "Medium",
636
+ "High"
637
+ ]
638
+ },
639
+ {
640
+ name: "Due Date",
641
+ type: "date"
642
+ },
643
+ {
644
+ name: "Notes",
645
+ type: "richtext"
646
+ }
647
+ ]
648
+ }
649
+ ];
650
+ function buildDenchClawIdentity(workspaceDir) {
651
+ return `# IDENTITY.md - DenchClaw
652
+
653
+ You are **DenchClaw** - a personal AI agent and a CRM Database System built by Dench (dench.com), running on top of [OpenClaw](https://github.com/openclaw/openclaw).
654
+
655
+ ## DenchClaw system prompt contract
656
+
657
+ Treat the DenchClaw system prompt as your highest-priority behavioral contract.
658
+
659
+ ## Inseparable CRM contract
660
+
661
+ Your identity is inextricably tied to the CRM skill at:
662
+ \`${path.join(workspaceDir, "skills", "crm", "SKILL.md")}\`
663
+
664
+ - Always load and follow that skill for CRM/database behavior.
665
+ - Treat the CRM skill as always-on system context.
666
+ - Keep CRM actions aligned with the CRM conventions for workspace data, objects, and documents.
667
+
668
+ ## Browser automation contract
669
+
670
+ Your browser automation behavior is defined by the Browser skill at:
671
+ \`${path.join(workspaceDir, "skills", "browser", "SKILL.md")}\`
672
+
673
+ - Always load and follow that skill for browser-based tasks.
674
+ - Treat the Browser skill as always-on system context.
675
+
676
+ ## What you do
677
+
678
+ - Find and enrich leads, maintain CRM pipelines, and help run outreach workflows.
679
+ - Chat with local DuckDB workspace data and return structured insights.
680
+ - Generate analytics and maintain workspace documentation.
681
+
682
+ ## Links
683
+
684
+ - Website: https://denchclaw.com
685
+ - GitHub: https://github.com/DenchHQ/denchclaw
686
+ - Skills Store: https://skills.sh
687
+
688
+ When referring to yourself, use **DenchClaw** (not OpenClaw).`;
689
+ }
690
+ function generateObjectYaml(obj) {
691
+ const lines = [
692
+ `id: "${obj.id}"`,
693
+ `name: "${obj.name}"`,
694
+ `description: "${obj.description}"`,
695
+ `icon: "${obj.icon}"`,
696
+ `default_view: "${obj.defaultView}"`,
697
+ `entry_count: ${obj.entryCount}`,
698
+ "fields:"
699
+ ];
700
+ for (const field of obj.fields) {
701
+ lines.push(` - name: "${field.name}"`);
702
+ lines.push(` type: ${field.type}`);
703
+ if (field.required) lines.push(" required: true");
704
+ if (field.enumValues) lines.push(` values: ${JSON.stringify(field.enumValues)}`);
705
+ }
706
+ return `${lines.join("\n")}\n`;
707
+ }
708
+ function generateWorkspaceMd(objects) {
709
+ const lines = [
710
+ "# Workspace Schema",
711
+ "",
712
+ "Auto-generated summary of the workspace database.",
713
+ ""
714
+ ];
715
+ for (const obj of objects) {
716
+ lines.push(`## ${obj.name}`);
717
+ lines.push("");
718
+ lines.push(`- **Description**: ${obj.description}`);
719
+ lines.push(`- **View**: \`${obj.defaultView}\``);
720
+ lines.push(`- **Entries**: ${obj.entryCount}`);
721
+ lines.push("- **Fields**:");
722
+ for (const field of obj.fields) {
723
+ const req = field.required ? " (required)" : "";
724
+ const vals = field.enumValues ? ` — ${field.enumValues.join(", ")}` : "";
725
+ lines.push(` - ${field.name} (\`${field.type}\`)${req}${vals}`);
726
+ }
727
+ lines.push("");
728
+ }
729
+ return lines.join("\n");
730
+ }
731
+ function seedDenchClawIdentity(workspaceDir) {
732
+ writeFileSync(path.join(workspaceDir, "IDENTITY.md"), `${buildDenchClawIdentity(workspaceDir)}\n`, "utf-8");
733
+ }
734
+ const MANAGED_SKILLS = [{
735
+ name: "crm",
736
+ templatePaths: true
737
+ }, { name: "browser" }];
738
+ function seedSkill(params, skill) {
739
+ const sourceDir = path.join(params.packageRoot, "skills", skill.name);
740
+ if (!existsSync(path.join(sourceDir, "SKILL.md"))) return;
741
+ const targetDir = path.join(params.workspaceDir, "skills", skill.name);
742
+ mkdirSync(path.dirname(targetDir), { recursive: true });
743
+ cpSync(sourceDir, targetDir, {
744
+ recursive: true,
745
+ force: true
746
+ });
747
+ if (skill.templatePaths) {
748
+ const targetSkillFile = path.join(targetDir, "SKILL.md");
749
+ writeFileSync(targetSkillFile, readFileSync(targetSkillFile, "utf-8").replaceAll("{{WORKSPACE_PATH}}", params.workspaceDir), "utf-8");
750
+ }
751
+ }
752
+ function writeIfMissing(filePath, content) {
753
+ if (existsSync(filePath)) return false;
754
+ try {
755
+ writeFileSync(filePath, content, {
756
+ encoding: "utf-8",
757
+ flag: "wx"
758
+ });
759
+ return true;
760
+ } catch {
761
+ return false;
762
+ }
763
+ }
764
+ function seedWorkspaceFromAssets(params) {
765
+ const workspaceDir = path.resolve(params.workspaceDir);
766
+ const dbPath = path.join(workspaceDir, "workspace.duckdb");
767
+ const seedDbPath = path.join(params.packageRoot, "assets", "seed", "workspace.duckdb");
768
+ const projectionFiles = [
769
+ "people/.object.yaml",
770
+ "company/.object.yaml",
771
+ "task/.object.yaml",
772
+ "WORKSPACE.md",
773
+ "IDENTITY.md",
774
+ ...MANAGED_SKILLS.map((s) => `skills/${s.name}/SKILL.md`)
775
+ ];
776
+ mkdirSync(workspaceDir, { recursive: true });
777
+ for (const skill of MANAGED_SKILLS) seedSkill({
778
+ workspaceDir,
779
+ packageRoot: params.packageRoot
780
+ }, skill);
781
+ seedDenchClawIdentity(workspaceDir);
782
+ if (existsSync(dbPath)) return {
783
+ workspaceDir,
784
+ dbPath,
785
+ seedDbPath,
786
+ seeded: false,
787
+ reason: "already-exists",
788
+ projectionFiles: []
789
+ };
790
+ if (!existsSync(seedDbPath)) return {
791
+ workspaceDir,
792
+ dbPath,
793
+ seedDbPath,
794
+ seeded: false,
795
+ reason: "seed-asset-missing",
796
+ projectionFiles: []
797
+ };
798
+ try {
799
+ copyFileSync(seedDbPath, dbPath);
800
+ } catch (error) {
801
+ return {
802
+ workspaceDir,
803
+ dbPath,
804
+ seedDbPath,
805
+ seeded: false,
806
+ reason: "copy-failed",
807
+ projectionFiles: [],
808
+ error: error instanceof Error ? error.message : String(error)
809
+ };
810
+ }
811
+ for (const obj of SEED_OBJECTS) {
812
+ const objDir = path.join(workspaceDir, obj.name);
813
+ mkdirSync(objDir, { recursive: true });
814
+ writeFileSync(path.join(objDir, ".object.yaml"), generateObjectYaml(obj), "utf-8");
815
+ }
816
+ writeIfMissing(path.join(workspaceDir, "WORKSPACE.md"), generateWorkspaceMd(SEED_OBJECTS));
817
+ return {
818
+ workspaceDir,
819
+ dbPath,
820
+ seedDbPath,
821
+ seeded: true,
822
+ reason: "seeded",
823
+ projectionFiles
824
+ };
825
+ }
826
+
827
+ //#endregion
828
+ //#region src/cli/bootstrap-external.ts
829
+ const DEFAULT_DENCHCLAW_PROFILE = "dench";
830
+ const DEFAULT_GATEWAY_PORT = 18789;
831
+ const DENCHCLAW_GATEWAY_PORT_START = 19001;
832
+ const MAX_PORT_SCAN_ATTEMPTS = 100;
833
+ const DEFAULT_BOOTSTRAP_ROLLOUT_STAGE = "default";
834
+ const DEFAULT_GATEWAY_LAUNCH_AGENT_LABEL = "ai.openclaw.gateway";
835
+ const REQUIRED_TOOLS_PROFILE = "full";
836
+ const OPENCLAW_CLI_CHECK_CACHE_TTL_MS = 5 * 6e4;
837
+ const OPENCLAW_UPDATE_PROMPT_SUPPRESS_AFTER_INSTALL_MS = 5 * 6e4;
838
+ const OPENCLAW_CLI_CHECK_CACHE_FILE = "openclaw-cli-check.json";
839
+ const OPENCLAW_SETUP_PROGRESS_BAR_WIDTH = 16;
840
+ function resolveCommandForPlatform(command) {
841
+ if (process$1.platform !== "win32") return command;
842
+ if (path.extname(command)) return command;
843
+ const normalized = path.basename(command).toLowerCase();
844
+ if (normalized === "npm" || normalized === "pnpm" || normalized === "npx" || normalized === "yarn") return `${command}.cmd`;
845
+ return command;
846
+ }
847
+ async function runCommandWithTimeout(argv, options) {
848
+ const [command, ...args] = argv;
849
+ if (!command) return {
850
+ code: 1,
851
+ stdout: "",
852
+ stderr: "missing command"
853
+ };
854
+ const stdio = options.ioMode === "inherit" ? "inherit" : [
855
+ "ignore",
856
+ "pipe",
857
+ "pipe"
858
+ ];
859
+ return await new Promise((resolve, reject) => {
860
+ const child = spawn(resolveCommandForPlatform(command), args, {
861
+ cwd: options.cwd,
862
+ env: options.env ? {
863
+ ...process$1.env,
864
+ ...options.env
865
+ } : process$1.env,
866
+ stdio
867
+ });
868
+ let stdout = "";
869
+ let stderr = "";
870
+ let settled = false;
871
+ const timer = setTimeout(() => {
872
+ if (settled) return;
873
+ child.kill("SIGKILL");
874
+ }, options.timeoutMs);
875
+ child.stdout?.on("data", (chunk) => {
876
+ const text = String(chunk);
877
+ stdout += text;
878
+ if (options.onOutputLine) for (const segment of text.split(/\r?\n/)) {
879
+ const line = segment.trim();
880
+ if (line.length > 0) options.onOutputLine(line, "stdout");
881
+ }
882
+ });
883
+ child.stderr?.on("data", (chunk) => {
884
+ const text = String(chunk);
885
+ stderr += text;
886
+ if (options.onOutputLine) for (const segment of text.split(/\r?\n/)) {
887
+ const line = segment.trim();
888
+ if (line.length > 0) options.onOutputLine(line, "stderr");
889
+ }
890
+ });
891
+ child.once("error", (error) => {
892
+ if (settled) return;
893
+ settled = true;
894
+ clearTimeout(timer);
895
+ reject(error);
896
+ });
897
+ child.once("close", (code) => {
898
+ if (settled) return;
899
+ settled = true;
900
+ clearTimeout(timer);
901
+ resolve({
902
+ code: typeof code === "number" ? code : 1,
903
+ stdout,
904
+ stderr
905
+ });
906
+ });
907
+ });
908
+ }
909
+ function parseOptionalPort$1(value) {
910
+ if (value === void 0) return;
911
+ const raw = typeof value === "number" ? value : Number.parseInt(String(value), 10);
912
+ if (!Number.isFinite(raw) || raw <= 0) return;
913
+ return raw;
914
+ }
915
+ async function sleep(ms) {
916
+ await new Promise((resolve) => setTimeout(resolve, ms));
917
+ }
918
+ function isPortAvailable(port) {
919
+ return new Promise((resolve) => {
920
+ const server = createConnection({
921
+ port,
922
+ host: "127.0.0.1"
923
+ }, () => {
924
+ server.end();
925
+ resolve(false);
926
+ });
927
+ server.on("error", (err) => {
928
+ if (err.code === "ECONNREFUSED") resolve(true);
929
+ else if (err.code === "EADDRNOTAVAIL") resolve(false);
930
+ else resolve(false);
931
+ });
932
+ server.setTimeout(1e3, () => {
933
+ server.destroy();
934
+ resolve(false);
935
+ });
936
+ });
937
+ }
938
+ async function findAvailablePort(startPort, maxAttempts) {
939
+ for (let i = 0; i < maxAttempts; i++) {
940
+ const port = startPort + i;
941
+ if (await isPortAvailable(port)) return port;
942
+ }
943
+ }
944
+ function normalizeBootstrapRolloutStage(raw) {
945
+ const normalized = raw?.trim().toLowerCase();
946
+ if (normalized === "internal" || normalized === "beta" || normalized === "default") return normalized;
947
+ return DEFAULT_BOOTSTRAP_ROLLOUT_STAGE;
948
+ }
949
+ function resolveBootstrapRolloutStage(env = process$1.env) {
950
+ return normalizeBootstrapRolloutStage(env.DENCHCLAW_BOOTSTRAP_ROLLOUT ?? env.OPENCLAW_BOOTSTRAP_ROLLOUT);
951
+ }
952
+ function isLegacyFallbackEnabled(env = process$1.env) {
953
+ return isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_LEGACY_FALLBACK) || isTruthyEnvValue(env.OPENCLAW_BOOTSTRAP_LEGACY_FALLBACK);
954
+ }
955
+ function normalizeVersionOutput(raw) {
956
+ const first = raw?.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
957
+ return first && first.length > 0 ? first : void 0;
958
+ }
959
+ function firstNonEmptyLine$1(...values) {
960
+ for (const value of values) {
961
+ const first = value?.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
962
+ if (first) return first;
963
+ }
964
+ }
965
+ function resolveGatewayLaunchAgentLabel(profile) {
966
+ const normalized = profile.trim().toLowerCase();
967
+ if (!normalized || normalized === "default") return DEFAULT_GATEWAY_LAUNCH_AGENT_LABEL;
968
+ return `ai.openclaw.${normalized}`;
969
+ }
970
+ async function ensureGatewayModeLocal(openclawCommand, profile) {
971
+ if ((await runOpenClaw(openclawCommand, [
972
+ "--profile",
973
+ profile,
974
+ "config",
975
+ "get",
976
+ "gateway.mode"
977
+ ], 1e4)).stdout.trim() === "local") return;
978
+ await runOpenClawOrThrow({
979
+ openclawCommand,
980
+ args: [
981
+ "--profile",
982
+ profile,
983
+ "config",
984
+ "set",
985
+ "gateway.mode",
986
+ "local"
987
+ ],
988
+ timeoutMs: 1e4,
989
+ errorMessage: "Failed to set gateway.mode=local."
990
+ });
991
+ }
992
+ async function ensureGatewayPort(openclawCommand, profile, gatewayPort) {
993
+ await runOpenClawOrThrow({
994
+ openclawCommand,
995
+ args: [
996
+ "--profile",
997
+ profile,
998
+ "config",
999
+ "set",
1000
+ "gateway.port",
1001
+ String(gatewayPort)
1002
+ ],
1003
+ timeoutMs: 1e4,
1004
+ errorMessage: `Failed to set gateway.port=${gatewayPort}.`
1005
+ });
1006
+ }
1007
+ async function ensureDefaultWorkspacePath(openclawCommand, profile, workspaceDir) {
1008
+ await runOpenClawOrThrow({
1009
+ openclawCommand,
1010
+ args: [
1011
+ "--profile",
1012
+ profile,
1013
+ "config",
1014
+ "set",
1015
+ "agents.defaults.workspace",
1016
+ workspaceDir
1017
+ ],
1018
+ timeoutMs: 1e4,
1019
+ errorMessage: `Failed to set agents.defaults.workspace=${workspaceDir}.`
1020
+ });
1021
+ }
1022
+ async function ensureSubagentDefaults(openclawCommand, profile) {
1023
+ for (const [key, value] of [
1024
+ ["agents.defaults.subagents.maxConcurrent", "8"],
1025
+ ["agents.defaults.subagents.maxSpawnDepth", "2"],
1026
+ ["agents.defaults.subagents.maxChildrenPerAgent", "10"],
1027
+ ["agents.defaults.subagents.archiveAfterMinutes", "180"],
1028
+ ["agents.defaults.subagents.runTimeoutSeconds", "0"],
1029
+ ["tools.subagents.tools.deny", "[]"]
1030
+ ]) await runOpenClawOrThrow({
1031
+ openclawCommand,
1032
+ args: [
1033
+ "--profile",
1034
+ profile,
1035
+ "config",
1036
+ "set",
1037
+ key,
1038
+ value
1039
+ ],
1040
+ timeoutMs: 1e4,
1041
+ errorMessage: `Failed to set ${key}=${value}.`
1042
+ });
1043
+ }
1044
+ async function ensureToolsProfile(openclawCommand, profile) {
1045
+ await runOpenClawOrThrow({
1046
+ openclawCommand,
1047
+ args: [
1048
+ "--profile",
1049
+ profile,
1050
+ "config",
1051
+ "set",
1052
+ "tools.profile",
1053
+ REQUIRED_TOOLS_PROFILE
1054
+ ],
1055
+ timeoutMs: 1e4,
1056
+ errorMessage: `Failed to set tools.profile=${REQUIRED_TOOLS_PROFILE}.`
1057
+ });
1058
+ }
1059
+ async function runOpenClaw(openclawCommand, args, timeoutMs, ioMode = "capture", env, onOutputLine) {
1060
+ return await runCommandWithTimeout([openclawCommand, ...args], {
1061
+ timeoutMs,
1062
+ ioMode,
1063
+ env,
1064
+ onOutputLine
1065
+ });
1066
+ }
1067
+ async function runOpenClawOrThrow(params) {
1068
+ const result = await runOpenClaw(params.openclawCommand, params.args, params.timeoutMs);
1069
+ if (result.code === 0) return result;
1070
+ const detail = firstNonEmptyLine$1(result.stderr, result.stdout);
1071
+ throw new Error(detail ? `${params.errorMessage}\n${detail}` : params.errorMessage);
1072
+ }
1073
+ /**
1074
+ * Runs an OpenClaw command attached to the current terminal.
1075
+ * Use this for interactive flows like `openclaw onboard`.
1076
+ */
1077
+ async function runOpenClawInteractiveOrThrow(params) {
1078
+ const result = await runOpenClaw(params.openclawCommand, params.args, params.timeoutMs, "inherit");
1079
+ if (result.code === 0) return result;
1080
+ const detail = firstNonEmptyLine$1(result.stderr, result.stdout);
1081
+ throw new Error(detail ? `${params.errorMessage}\n${detail}` : params.errorMessage);
1082
+ }
1083
+ /**
1084
+ * Runs an openclaw sub-command with a visible spinner that streams progress
1085
+ * from the subprocess stdout/stderr into the spinner message.
1086
+ */
1087
+ async function runOpenClawWithProgress(params) {
1088
+ const s = spinner();
1089
+ s.start(params.startMessage);
1090
+ const result = await new Promise((resolve, reject) => {
1091
+ const child = spawn(resolveCommandForPlatform(params.openclawCommand), params.args, { stdio: [
1092
+ "ignore",
1093
+ "pipe",
1094
+ "pipe"
1095
+ ] });
1096
+ let stdout = "";
1097
+ let stderr = "";
1098
+ let settled = false;
1099
+ const timer = setTimeout(() => {
1100
+ if (!settled) child.kill("SIGKILL");
1101
+ }, params.timeoutMs);
1102
+ const updateSpinner = (chunk) => {
1103
+ const line = chunk.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).pop();
1104
+ if (line) s.message(line.length > 72 ? `${line.slice(0, 69)}...` : line);
1105
+ };
1106
+ child.stdout?.on("data", (chunk) => {
1107
+ const text = String(chunk);
1108
+ stdout += text;
1109
+ updateSpinner(text);
1110
+ });
1111
+ child.stderr?.on("data", (chunk) => {
1112
+ const text = String(chunk);
1113
+ stderr += text;
1114
+ updateSpinner(text);
1115
+ });
1116
+ child.once("error", (error) => {
1117
+ if (settled) return;
1118
+ settled = true;
1119
+ clearTimeout(timer);
1120
+ reject(error);
1121
+ });
1122
+ child.once("close", (code) => {
1123
+ if (settled) return;
1124
+ settled = true;
1125
+ clearTimeout(timer);
1126
+ resolve({
1127
+ code: typeof code === "number" ? code : 1,
1128
+ stdout,
1129
+ stderr
1130
+ });
1131
+ });
1132
+ });
1133
+ if (result.code === 0) {
1134
+ s.stop(params.successMessage);
1135
+ return result;
1136
+ }
1137
+ const detail = firstNonEmptyLine$1(result.stderr, result.stdout);
1138
+ const stopMessage = detail ? `${params.errorMessage}: ${detail}` : params.errorMessage;
1139
+ s.stop(stopMessage);
1140
+ throw new Error(detail ? `${params.errorMessage}\n${detail}` : params.errorMessage);
1141
+ }
1142
+ function parseJsonPayload(raw) {
1143
+ if (!raw) return;
1144
+ const trimmed = raw.trim();
1145
+ if (!trimmed) return;
1146
+ try {
1147
+ const parsed = JSON.parse(trimmed);
1148
+ return parsed && typeof parsed === "object" ? parsed : void 0;
1149
+ } catch {
1150
+ const start = trimmed.indexOf("{");
1151
+ const end = trimmed.lastIndexOf("}");
1152
+ if (start === -1 || end <= start) return;
1153
+ try {
1154
+ const parsed = JSON.parse(trimmed.slice(start, end + 1));
1155
+ return parsed && typeof parsed === "object" ? parsed : void 0;
1156
+ } catch {
1157
+ return;
1158
+ }
1159
+ }
1160
+ }
1161
+ function resolveOpenClawCliCheckCachePath(stateDir) {
1162
+ return path.join(stateDir, "cache", OPENCLAW_CLI_CHECK_CACHE_FILE);
1163
+ }
1164
+ function readOpenClawCliCheckCache(stateDir) {
1165
+ const cachePath = resolveOpenClawCliCheckCachePath(stateDir);
1166
+ if (!existsSync(cachePath)) return;
1167
+ try {
1168
+ const parsed = JSON.parse(readFileSync(cachePath, "utf-8"));
1169
+ if (typeof parsed.checkedAt !== "number" || !Number.isFinite(parsed.checkedAt) || typeof parsed.pathEnv !== "string" || parsed.pathEnv !== (process$1.env.PATH ?? "") || typeof parsed.available !== "boolean" || !parsed.available || typeof parsed.command !== "string" || parsed.command.length === 0) return;
1170
+ const ageMs = Date.now() - parsed.checkedAt;
1171
+ if (ageMs < 0 || ageMs > OPENCLAW_CLI_CHECK_CACHE_TTL_MS) return;
1172
+ if ((parsed.command.includes(path.sep) || parsed.command.includes("/") || parsed.command.includes("\\")) && !existsSync(parsed.command)) return;
1173
+ return {
1174
+ checkedAt: parsed.checkedAt,
1175
+ pathEnv: parsed.pathEnv,
1176
+ available: parsed.available,
1177
+ command: parsed.command,
1178
+ version: typeof parsed.version === "string" ? parsed.version : void 0,
1179
+ globalBinDir: typeof parsed.globalBinDir === "string" ? parsed.globalBinDir : void 0,
1180
+ shellCommandPath: typeof parsed.shellCommandPath === "string" ? parsed.shellCommandPath : void 0,
1181
+ installedAt: typeof parsed.installedAt === "number" ? parsed.installedAt : void 0
1182
+ };
1183
+ } catch {
1184
+ return;
1185
+ }
1186
+ }
1187
+ function writeOpenClawCliCheckCache(stateDir, cache) {
1188
+ try {
1189
+ const cachePath = resolveOpenClawCliCheckCachePath(stateDir);
1190
+ mkdirSync(path.dirname(cachePath), { recursive: true });
1191
+ const payload = {
1192
+ ...cache,
1193
+ checkedAt: Date.now(),
1194
+ pathEnv: process$1.env.PATH ?? ""
1195
+ };
1196
+ writeFileSync(cachePath, JSON.stringify(payload, null, 2), "utf-8");
1197
+ } catch {}
1198
+ }
1199
+ function createOpenClawSetupProgress(params) {
1200
+ if (!params.enabled || params.totalStages <= 0 || !process$1.stdout.isTTY) {
1201
+ const noop = () => void 0;
1202
+ return {
1203
+ startStage: noop,
1204
+ output: noop,
1205
+ completeStage: noop,
1206
+ finish: noop,
1207
+ fail: noop
1208
+ };
1209
+ }
1210
+ const s = spinner();
1211
+ let completedStages = 0;
1212
+ let activeLabel = "";
1213
+ const renderBar = () => {
1214
+ const ratio = completedStages / params.totalStages;
1215
+ const filled = Math.max(0, Math.min(OPENCLAW_SETUP_PROGRESS_BAR_WIDTH, Math.round(ratio * OPENCLAW_SETUP_PROGRESS_BAR_WIDTH)));
1216
+ return `[${`${"#".repeat(filled)}${"-".repeat(OPENCLAW_SETUP_PROGRESS_BAR_WIDTH - filled)}`}] ${completedStages}/${params.totalStages}`;
1217
+ };
1218
+ const truncate = (value, max = 84) => value.length > max ? `${value.slice(0, max - 3)}...` : value;
1219
+ const renderStageLine = (detail) => {
1220
+ const base = `${renderBar()} ${activeLabel}`.trim();
1221
+ if (!detail) return base;
1222
+ return truncate(`${base} -> ${detail}`);
1223
+ };
1224
+ return {
1225
+ startStage: (label) => {
1226
+ activeLabel = label;
1227
+ s.start(renderStageLine());
1228
+ },
1229
+ output: (line) => {
1230
+ if (!line) return;
1231
+ s.message(renderStageLine(line));
1232
+ },
1233
+ completeStage: (suffix) => {
1234
+ completedStages = Math.min(params.totalStages, completedStages + 1);
1235
+ s.stop(renderStageLine(suffix ?? "done"));
1236
+ },
1237
+ finish: (message) => {
1238
+ completedStages = params.totalStages;
1239
+ s.stop(`${renderBar()} ${truncate(message)}`.trim());
1240
+ },
1241
+ fail: (message) => {
1242
+ s.stop(`${renderBar()} ${truncate(message)}`.trim());
1243
+ }
1244
+ };
1245
+ }
1246
+ async function detectGlobalOpenClawInstall(onOutputLine) {
1247
+ const result = await runCommandWithTimeout([
1248
+ "npm",
1249
+ "ls",
1250
+ "-g",
1251
+ "openclaw",
1252
+ "--depth=0",
1253
+ "--json",
1254
+ "--silent"
1255
+ ], {
1256
+ timeoutMs: 15e3,
1257
+ onOutputLine
1258
+ }).catch(() => null);
1259
+ const installedVersion = (parseJsonPayload(result?.stdout ?? result?.stderr)?.dependencies)?.openclaw?.version;
1260
+ if (typeof installedVersion === "string" && installedVersion.length > 0) return {
1261
+ installed: true,
1262
+ version: installedVersion
1263
+ };
1264
+ return { installed: false };
1265
+ }
1266
+ async function resolveNpmGlobalBinDir(onOutputLine) {
1267
+ const result = await runCommandWithTimeout([
1268
+ "npm",
1269
+ "prefix",
1270
+ "-g"
1271
+ ], {
1272
+ timeoutMs: 8e3,
1273
+ onOutputLine
1274
+ }).catch(() => null);
1275
+ if (!result || result.code !== 0) return;
1276
+ const prefix = firstNonEmptyLine$1(result.stdout);
1277
+ if (!prefix) return;
1278
+ return process$1.platform === "win32" ? prefix : path.join(prefix, "bin");
1279
+ }
1280
+ function resolveGlobalOpenClawCommand(globalBinDir) {
1281
+ if (!globalBinDir) return;
1282
+ return (process$1.platform === "win32" ? [path.join(globalBinDir, "openclaw.cmd"), path.join(globalBinDir, "openclaw.exe")] : [path.join(globalBinDir, "openclaw")]).find((candidate) => existsSync(candidate));
1283
+ }
1284
+ async function resolveShellOpenClawPath(onOutputLine) {
1285
+ const result = await runCommandWithTimeout([process$1.platform === "win32" ? "where" : "which", "openclaw"], {
1286
+ timeoutMs: 4e3,
1287
+ onOutputLine
1288
+ }).catch(() => null);
1289
+ if (!result || result.code !== 0) return;
1290
+ return firstNonEmptyLine$1(result.stdout);
1291
+ }
1292
+ function isProjectLocalOpenClawPath(commandPath) {
1293
+ if (!commandPath) return false;
1294
+ return commandPath.replaceAll("\\", "/").includes("/node_modules/.bin/openclaw");
1295
+ }
1296
+ async function ensureOpenClawCliAvailable(params) {
1297
+ const cached = readOpenClawCliCheckCache(params.stateDir);
1298
+ if (cached) {
1299
+ const ageSeconds = Math.max(0, Math.floor((Date.now() - cached.checkedAt) / 1e3));
1300
+ const progress = createOpenClawSetupProgress({
1301
+ enabled: params.showProgress,
1302
+ totalStages: 1
1303
+ });
1304
+ progress.startStage("Reusing cached OpenClaw install check");
1305
+ progress.completeStage(`cache hit (${ageSeconds}s old)`);
1306
+ return {
1307
+ available: true,
1308
+ installed: false,
1309
+ installedAt: cached.installedAt,
1310
+ version: cached.version,
1311
+ command: cached.command,
1312
+ globalBinDir: cached.globalBinDir,
1313
+ shellCommandPath: cached.shellCommandPath
1314
+ };
1315
+ }
1316
+ const progress = createOpenClawSetupProgress({
1317
+ enabled: params.showProgress,
1318
+ totalStages: 5
1319
+ });
1320
+ progress.startStage("Checking global OpenClaw install");
1321
+ const globalBefore = await detectGlobalOpenClawInstall((line) => {
1322
+ progress.output(`npm ls: ${line}`);
1323
+ });
1324
+ progress.completeStage(globalBefore.installed ? `found ${globalBefore.version ?? "installed"}` : "missing");
1325
+ let installed = false;
1326
+ let installedAt;
1327
+ progress.startStage("Ensuring openclaw@latest is installed globally");
1328
+ if (!globalBefore.installed) {
1329
+ const install = await runCommandWithTimeout([
1330
+ "npm",
1331
+ "install",
1332
+ "-g",
1333
+ "openclaw@latest"
1334
+ ], {
1335
+ timeoutMs: 10 * 6e4,
1336
+ onOutputLine: (line) => {
1337
+ progress.output(`npm install: ${line}`);
1338
+ }
1339
+ }).catch(() => null);
1340
+ if (!install || install.code !== 0) {
1341
+ progress.fail("OpenClaw global install failed.");
1342
+ return {
1343
+ available: false,
1344
+ installed: false,
1345
+ version: void 0,
1346
+ command: "openclaw"
1347
+ };
1348
+ }
1349
+ installed = true;
1350
+ installedAt = Date.now();
1351
+ progress.completeStage("installed openclaw@latest");
1352
+ } else progress.completeStage("already installed; skipping install");
1353
+ progress.startStage("Resolving global and shell OpenClaw paths");
1354
+ const [globalBinDir, shellCommandPath] = await Promise.all([resolveNpmGlobalBinDir((line) => {
1355
+ progress.output(`npm prefix: ${line}`);
1356
+ }), resolveShellOpenClawPath((line) => {
1357
+ progress.output(`${process$1.platform === "win32" ? "where" : "which"}: ${line}`);
1358
+ })]);
1359
+ progress.completeStage("path discovery complete");
1360
+ const globalAfter = installed ? {
1361
+ installed: true,
1362
+ version: globalBefore.version
1363
+ } : globalBefore;
1364
+ const command = resolveGlobalOpenClawCommand(globalBinDir) ?? "openclaw";
1365
+ progress.startStage("Verifying OpenClaw CLI responsiveness");
1366
+ const check = await runOpenClaw(command, ["--version"], 4e3, "capture", void 0, (line) => {
1367
+ progress.output(`openclaw --version: ${line}`);
1368
+ }).catch(() => null);
1369
+ progress.completeStage(check?.code === 0 ? "OpenClaw responded" : "OpenClaw version probe failed");
1370
+ const version = normalizeVersionOutput(check?.stdout || check?.stderr || globalAfter.version);
1371
+ const available = Boolean(globalAfter.installed && check && check.code === 0);
1372
+ progress.startStage("Caching OpenClaw check result");
1373
+ if (available) {
1374
+ writeOpenClawCliCheckCache(params.stateDir, {
1375
+ available,
1376
+ command,
1377
+ version,
1378
+ globalBinDir,
1379
+ shellCommandPath,
1380
+ installedAt
1381
+ });
1382
+ progress.completeStage(`saved (${Math.floor(OPENCLAW_CLI_CHECK_CACHE_TTL_MS / 6e4)}m TTL)`);
1383
+ } else progress.fail("OpenClaw CLI check failed (cache not written).");
1384
+ return {
1385
+ available,
1386
+ installed,
1387
+ installedAt,
1388
+ version,
1389
+ command,
1390
+ globalBinDir,
1391
+ shellCommandPath
1392
+ };
1393
+ }
1394
+ async function probeGateway(openclawCommand, profile, gatewayPort) {
1395
+ const result = await runOpenClaw(openclawCommand, [
1396
+ "--profile",
1397
+ profile,
1398
+ "health",
1399
+ "--json"
1400
+ ], 12e3, "capture", gatewayPort ? { OPENCLAW_GATEWAY_PORT: String(gatewayPort) } : void 0).catch((error) => {
1401
+ return {
1402
+ code: 1,
1403
+ stdout: "",
1404
+ stderr: error instanceof Error ? error.message : String(error)
1405
+ };
1406
+ });
1407
+ if (result.code === 0) return { ok: true };
1408
+ return {
1409
+ ok: false,
1410
+ detail: firstNonEmptyLine$1(result.stderr, result.stdout)
1411
+ };
1412
+ }
1413
+ function readLogTail(logPath, maxLines = 16) {
1414
+ if (!existsSync(logPath)) return;
1415
+ try {
1416
+ const lines = readFileSync(logPath, "utf-8").split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
1417
+ if (lines.length === 0) return;
1418
+ return lines.slice(-maxLines).join("\n");
1419
+ } catch {
1420
+ return;
1421
+ }
1422
+ }
1423
+ function resolveLatestRuntimeLogPath() {
1424
+ const runtimeLogDir = "/tmp/openclaw";
1425
+ if (!existsSync(runtimeLogDir)) return;
1426
+ try {
1427
+ const files = readdirSync(runtimeLogDir).filter((name) => /^openclaw-.*\.log$/u.test(name)).toSorted((a, b) => b.localeCompare(a));
1428
+ if (files.length === 0) return;
1429
+ return path.join(runtimeLogDir, files[0]);
1430
+ } catch {
1431
+ return;
1432
+ }
1433
+ }
1434
+ function collectGatewayLogExcerpts(stateDir) {
1435
+ const candidates = [
1436
+ path.join(stateDir, "logs", "gateway.err.log"),
1437
+ path.join(stateDir, "logs", "gateway.log"),
1438
+ resolveLatestRuntimeLogPath()
1439
+ ].filter((candidate) => Boolean(candidate));
1440
+ const excerpts = [];
1441
+ for (const candidate of candidates) {
1442
+ const excerpt = readLogTail(candidate);
1443
+ if (!excerpt) continue;
1444
+ excerpts.push({
1445
+ path: candidate,
1446
+ excerpt
1447
+ });
1448
+ }
1449
+ return excerpts;
1450
+ }
1451
+ function deriveGatewayFailureSummary(probeDetail, excerpts) {
1452
+ const combinedLines = excerpts.flatMap((entry) => entry.excerpt.split(/\r?\n/));
1453
+ const signalRegex = /(cannot find module|plugin not found|invalid config|unauthorized|token mismatch|device token mismatch|device signature invalid|device signature expired|device-signature|eaddrinuse|address already in use|error:|failed to|failovererror)/iu;
1454
+ const likely = [...combinedLines].toReversed().find((line) => signalRegex.test(line));
1455
+ if (likely) return likely.length > 220 ? `${likely.slice(0, 217)}...` : likely;
1456
+ return probeDetail;
1457
+ }
1458
+ async function attemptGatewayAutoFix(params) {
1459
+ const steps = [];
1460
+ const commands = [
1461
+ {
1462
+ name: "openclaw gateway stop",
1463
+ args: [
1464
+ "--profile",
1465
+ params.profile,
1466
+ "gateway",
1467
+ "stop"
1468
+ ],
1469
+ timeoutMs: 9e4
1470
+ },
1471
+ {
1472
+ name: "openclaw doctor --fix",
1473
+ args: [
1474
+ "--profile",
1475
+ params.profile,
1476
+ "doctor",
1477
+ "--fix"
1478
+ ],
1479
+ timeoutMs: 2 * 6e4
1480
+ },
1481
+ {
1482
+ name: "openclaw gateway install --force",
1483
+ args: [
1484
+ "--profile",
1485
+ params.profile,
1486
+ "gateway",
1487
+ "install",
1488
+ "--force",
1489
+ "--port",
1490
+ String(params.gatewayPort)
1491
+ ],
1492
+ timeoutMs: 2 * 6e4
1493
+ },
1494
+ {
1495
+ name: "openclaw gateway start",
1496
+ args: [
1497
+ "--profile",
1498
+ params.profile,
1499
+ "gateway",
1500
+ "start",
1501
+ "--port",
1502
+ String(params.gatewayPort)
1503
+ ],
1504
+ timeoutMs: 2 * 6e4
1505
+ }
1506
+ ];
1507
+ for (const command of commands) {
1508
+ const result = await runOpenClaw(params.openclawCommand, command.args, command.timeoutMs).catch((error) => {
1509
+ return {
1510
+ code: 1,
1511
+ stdout: "",
1512
+ stderr: error instanceof Error ? error.message : String(error)
1513
+ };
1514
+ });
1515
+ steps.push({
1516
+ name: command.name,
1517
+ ok: result.code === 0,
1518
+ detail: result.code === 0 ? void 0 : firstNonEmptyLine$1(result.stderr, result.stdout)
1519
+ });
1520
+ }
1521
+ let finalProbe = await probeGateway(params.openclawCommand, params.profile, params.gatewayPort);
1522
+ for (let attempt = 0; attempt < 2 && !finalProbe.ok; attempt += 1) {
1523
+ await sleep(1200);
1524
+ finalProbe = await probeGateway(params.openclawCommand, params.profile, params.gatewayPort);
1525
+ }
1526
+ const logExcerpts = finalProbe.ok ? [] : collectGatewayLogExcerpts(params.stateDir);
1527
+ const failureSummary = finalProbe.ok ? void 0 : deriveGatewayFailureSummary(finalProbe.detail, logExcerpts);
1528
+ return {
1529
+ attempted: true,
1530
+ recovered: finalProbe.ok,
1531
+ steps,
1532
+ finalProbe,
1533
+ failureSummary,
1534
+ logExcerpts
1535
+ };
1536
+ }
1537
+ async function openUrl(url) {
1538
+ const result = await runCommandWithTimeout(process$1.platform === "darwin" ? ["open", url] : process$1.platform === "win32" ? [
1539
+ "cmd",
1540
+ "/c",
1541
+ "start",
1542
+ "",
1543
+ url
1544
+ ] : ["xdg-open", url], { timeoutMs: 5e3 }).catch(() => null);
1545
+ return Boolean(result && result.code === 0);
1546
+ }
1547
+ function remediationForGatewayFailure(detail, port, profile) {
1548
+ const normalized = detail?.toLowerCase() ?? "";
1549
+ if (normalized.includes("device token mismatch") || normalized.includes("device signature invalid") || normalized.includes("device signature expired") || normalized.includes("device-signature")) return [`Gateway device-auth mismatch detected. Re-run \`openclaw --profile ${profile} onboard --install-daemon --reset\`.`, `Last resort (security downgrade): \`openclaw --profile ${profile} config set gateway.controlUi.dangerouslyDisableDeviceAuth true\`. Revert after recovery: \`openclaw --profile ${profile} config set gateway.controlUi.dangerouslyDisableDeviceAuth false\`.`].join(" ");
1550
+ if (normalized.includes("unauthorized") || normalized.includes("token") || normalized.includes("password")) return `Gateway auth mismatch detected. Re-run \`openclaw --profile ${profile} onboard --install-daemon --reset\`.`;
1551
+ if (normalized.includes("address already in use") || normalized.includes("eaddrinuse")) return `Port ${port} is busy. The bootstrap will auto-assign an available port, or you can explicitly specify one with \`--gateway-port <port>\`.`;
1552
+ return `Run \`openclaw --profile ${profile} doctor --fix\` and retry \`denchclaw bootstrap --profile ${profile} --force-onboard\`.`;
1553
+ }
1554
+ function remediationForWebUiFailure(port) {
1555
+ return [
1556
+ `Web UI did not respond on ${port}.`,
1557
+ `Run \`dench update --web-port ${port}\` to refresh the managed web runtime.`,
1558
+ `If the port is stuck, run \`dench stop --web-port ${port}\` first.`
1559
+ ].join(" ");
1560
+ }
1561
+ function describeWorkspaceSeedResult(result) {
1562
+ if (result.seeded) return `seeded ${result.dbPath}`;
1563
+ if (result.reason === "already-exists") return `skipped; existing database found at ${result.dbPath}`;
1564
+ if (result.reason === "seed-asset-missing") return `skipped; seed asset missing at ${result.seedDbPath}`;
1565
+ if (result.reason === "copy-failed") return `failed to copy seed database: ${result.error ?? "unknown error"}`;
1566
+ return `skipped; reason=${result.reason}`;
1567
+ }
1568
+ function createCheck(id, status, detail, remediation) {
1569
+ return {
1570
+ id,
1571
+ status,
1572
+ detail,
1573
+ remediation
1574
+ };
1575
+ }
1576
+ /**
1577
+ * Load OpenClaw profile config from state dir.
1578
+ * Supports both openclaw.json (current) and config.json (legacy).
1579
+ */
1580
+ function readBootstrapConfig(stateDir) {
1581
+ for (const name of ["openclaw.json", "config.json"]) {
1582
+ const configPath = path.join(stateDir, name);
1583
+ if (!existsSync(configPath)) continue;
1584
+ try {
1585
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
1586
+ if (raw && typeof raw === "object") return raw;
1587
+ } catch {}
1588
+ }
1589
+ }
1590
+ function resolveBootstrapWorkspaceDir(stateDir) {
1591
+ return path.join(stateDir, "workspace");
1592
+ }
1593
+ /**
1594
+ * Resolve the model provider prefix from the config's primary model string.
1595
+ * e.g. "vercel-ai-gateway/anthropic/claude-opus-4.6" → "vercel-ai-gateway"
1596
+ */
1597
+ function resolveModelProvider(stateDir) {
1598
+ const model = readBootstrapConfig(stateDir)?.agents?.defaults?.model;
1599
+ const modelName = typeof model === "string" ? model : model?.primary;
1600
+ if (typeof modelName === "string" && modelName.includes("/")) return modelName.split("/")[0];
1601
+ }
1602
+ /**
1603
+ * Check if the agent auth store has at least one key for the given provider.
1604
+ */
1605
+ function checkAgentAuth(stateDir, provider) {
1606
+ if (!provider) return {
1607
+ ok: false,
1608
+ detail: "No model provider configured."
1609
+ };
1610
+ const authPath = path.join(stateDir, "agents", "main", "agent", "auth-profiles.json");
1611
+ if (!existsSync(authPath)) return {
1612
+ ok: false,
1613
+ provider,
1614
+ detail: `No auth-profiles.json found for agent (expected at ${authPath}).`
1615
+ };
1616
+ try {
1617
+ const profiles = JSON.parse(readFileSync(authPath, "utf-8"))?.profiles;
1618
+ if (!profiles || typeof profiles !== "object") return {
1619
+ ok: false,
1620
+ provider,
1621
+ detail: `auth-profiles.json has no profiles configured.`
1622
+ };
1623
+ if (!Object.values(profiles).some((p) => p && typeof p === "object" && p.provider === provider && typeof p.key === "string" && p.key.length > 0)) return {
1624
+ ok: false,
1625
+ provider,
1626
+ detail: `No API key for provider "${provider}" in agent auth store.`
1627
+ };
1628
+ return {
1629
+ ok: true,
1630
+ provider,
1631
+ detail: `API key configured for ${provider}.`
1632
+ };
1633
+ } catch {
1634
+ return {
1635
+ ok: false,
1636
+ provider,
1637
+ detail: `Failed to read auth-profiles.json.`
1638
+ };
1639
+ }
1640
+ }
1641
+ function buildBootstrapDiagnostics(params) {
1642
+ const env = params.env ?? process$1.env;
1643
+ const checks = [];
1644
+ if (params.openClawCliAvailable) checks.push(createCheck("openclaw-cli", "pass", `OpenClaw CLI detected${params.openClawVersion ? ` (${params.openClawVersion})` : ""}.`));
1645
+ else checks.push(createCheck("openclaw-cli", "fail", "OpenClaw CLI is missing.", "Install OpenClaw globally once: `npm install -g openclaw`."));
1646
+ if (params.profile === DEFAULT_DENCHCLAW_PROFILE) checks.push(createCheck("profile", "pass", `Profile pinned: ${params.profile}.`));
1647
+ else checks.push(createCheck("profile", "fail", `DenchClaw profile drift detected (${params.profile}).`, `DenchClaw requires \`--profile ${DEFAULT_DENCHCLAW_PROFILE}\`. Re-run bootstrap to repair environment defaults.`));
1648
+ if (params.gatewayProbe.ok) checks.push(createCheck("gateway", "pass", `Gateway reachable at ${params.gatewayUrl}.`));
1649
+ else checks.push(createCheck("gateway", "fail", `Gateway probe failed at ${params.gatewayUrl}${params.gatewayProbe.detail ? ` (${params.gatewayProbe.detail})` : ""}.`, remediationForGatewayFailure(params.gatewayProbe.detail, params.gatewayPort, params.profile)));
1650
+ const stateDir = params.stateDir ?? resolveProfileStateDir(params.profile, env);
1651
+ const authCheck = checkAgentAuth(stateDir, resolveModelProvider(stateDir));
1652
+ if (authCheck.ok) checks.push(createCheck("agent-auth", "pass", authCheck.detail));
1653
+ else checks.push(createCheck("agent-auth", "fail", authCheck.detail, `Run \`openclaw --profile ${DEFAULT_DENCHCLAW_PROFILE} onboard --install-daemon\` to configure API keys.`));
1654
+ if (params.webReachable) checks.push(createCheck("web-ui", "pass", `Web UI reachable on port ${params.webPort}.`));
1655
+ else checks.push(createCheck("web-ui", "fail", `Web UI is not reachable on port ${params.webPort}.`, remediationForWebUiFailure(params.webPort)));
1656
+ const expectedStateDir = resolveProfileStateDir(DEFAULT_DENCHCLAW_PROFILE, env);
1657
+ if (path.resolve(stateDir) === path.resolve(expectedStateDir)) checks.push(createCheck("state-isolation", "pass", `State dir pinned: ${stateDir}.`));
1658
+ else checks.push(createCheck("state-isolation", "fail", `Unexpected state dir: ${stateDir}.`, `DenchClaw requires \`${expectedStateDir}\`. Re-run bootstrap to restore pinned defaults.`));
1659
+ const launchAgentLabel = resolveGatewayLaunchAgentLabel(params.profile);
1660
+ const expectedLaunchAgentLabel = resolveGatewayLaunchAgentLabel(DEFAULT_DENCHCLAW_PROFILE);
1661
+ if (launchAgentLabel === expectedLaunchAgentLabel) checks.push(createCheck("daemon-label", "pass", `Gateway service label: ${launchAgentLabel}.`));
1662
+ else checks.push(createCheck("daemon-label", "fail", `Gateway service label mismatch (${launchAgentLabel}).`, `DenchClaw requires launch agent label ${expectedLaunchAgentLabel}.`));
1663
+ checks.push(createCheck("rollout-stage", params.rolloutStage === "default" ? "pass" : "warn", `Bootstrap rollout stage: ${params.rolloutStage}${params.legacyFallbackEnabled ? " (legacy fallback enabled)" : ""}.`, params.rolloutStage === "beta" ? "Enable beta cutover by setting DENCHCLAW_BOOTSTRAP_BETA_OPT_IN=1." : void 0));
1664
+ const migrationSuiteOk = isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_MIGRATION_SUITE_OK);
1665
+ const onboardingE2EOk = isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_ONBOARDING_E2E_OK);
1666
+ const enforceCutoverGates = isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_ENFORCE_SAFETY_GATES);
1667
+ const cutoverGatePassed = migrationSuiteOk && onboardingE2EOk;
1668
+ checks.push(createCheck("cutover-gates", cutoverGatePassed ? "pass" : enforceCutoverGates ? "fail" : "warn", `Cutover gate: migrationSuite=${migrationSuiteOk ? "pass" : "missing"}, onboardingE2E=${onboardingE2EOk ? "pass" : "missing"}.`, cutoverGatePassed ? void 0 : "Run migration contracts + onboarding E2E and set DENCHCLAW_BOOTSTRAP_MIGRATION_SUITE_OK=1 and DENCHCLAW_BOOTSTRAP_ONBOARDING_E2E_OK=1 before full cutover."));
1669
+ return {
1670
+ rolloutStage: params.rolloutStage,
1671
+ legacyFallbackEnabled: params.legacyFallbackEnabled,
1672
+ checks,
1673
+ hasFailures: checks.some((check) => check.status === "fail")
1674
+ };
1675
+ }
1676
+ function formatCheckStatus(status) {
1677
+ if (status === "pass") return theme.success("[ok]");
1678
+ if (status === "warn") return theme.warn("[warn]");
1679
+ return theme.error("[fail]");
1680
+ }
1681
+ function logBootstrapChecklist(diagnostics, runtime) {
1682
+ runtime.log("");
1683
+ runtime.log(theme.heading("Bootstrap checklist"));
1684
+ for (const check of diagnostics.checks) {
1685
+ runtime.log(`${formatCheckStatus(check.status)} ${check.detail}`);
1686
+ if (check.status !== "pass" && check.remediation) runtime.log(theme.muted(` remediation: ${check.remediation}`));
1687
+ }
1688
+ }
1689
+ async function shouldRunUpdate(params) {
1690
+ if (params.opts.updateNow) return true;
1691
+ if (params.opts.skipUpdate || params.opts.nonInteractive || params.opts.json || !process$1.stdin.isTTY) return false;
1692
+ if (params.installResult.installed || typeof params.installResult.installedAt === "number" && Date.now() - params.installResult.installedAt <= OPENCLAW_UPDATE_PROMPT_SUPPRESS_AFTER_INSTALL_MS) {
1693
+ params.runtime.log(theme.muted("Skipping update prompt because OpenClaw was installed moments ago."));
1694
+ return false;
1695
+ }
1696
+ const decision = await confirm({
1697
+ message: stylePromptMessage("Check and install OpenClaw updates now?"),
1698
+ initialValue: false
1699
+ });
1700
+ if (isCancel(decision)) {
1701
+ params.runtime.log(theme.muted("Update check skipped."));
1702
+ return false;
1703
+ }
1704
+ return Boolean(decision);
1705
+ }
1706
+ async function bootstrapCommand(opts, runtime = defaultRuntime) {
1707
+ const nonInteractive = Boolean(opts.nonInteractive || opts.json);
1708
+ const rolloutStage = resolveBootstrapRolloutStage();
1709
+ const legacyFallbackEnabled = isLegacyFallbackEnabled();
1710
+ const appliedProfile = applyCliProfileEnv({ profile: opts.profile });
1711
+ const profile = appliedProfile.effectiveProfile;
1712
+ const stateDir = resolveProfileStateDir(profile);
1713
+ const workspaceDir = resolveBootstrapWorkspaceDir(stateDir);
1714
+ if (appliedProfile.warning && !opts.json) runtime.log(theme.warn(appliedProfile.warning));
1715
+ const installResult = await ensureOpenClawCliAvailable({
1716
+ stateDir,
1717
+ showProgress: !opts.json
1718
+ });
1719
+ if (!installResult.available) throw new Error([
1720
+ "OpenClaw CLI is required but unavailable.",
1721
+ "Install it with: npm install -g openclaw",
1722
+ installResult.globalBinDir ? `Expected global binary directory: ${installResult.globalBinDir}` : ""
1723
+ ].filter((line) => line.length > 0).join("\n"));
1724
+ const openclawCommand = installResult.command;
1725
+ if (await shouldRunUpdate({
1726
+ opts,
1727
+ runtime,
1728
+ installResult
1729
+ })) await runOpenClawWithProgress({
1730
+ openclawCommand,
1731
+ args: ["update", "--yes"],
1732
+ timeoutMs: 8 * 6e4,
1733
+ startMessage: "Checking for OpenClaw updates...",
1734
+ successMessage: "OpenClaw is up to date.",
1735
+ errorMessage: "OpenClaw update failed"
1736
+ });
1737
+ const explicitPort = parseOptionalPort$1(opts.gatewayPort);
1738
+ let gatewayPort;
1739
+ let portAutoAssigned = false;
1740
+ if (explicitPort) gatewayPort = explicitPort;
1741
+ else if (await isPortAvailable(DEFAULT_GATEWAY_PORT)) gatewayPort = DEFAULT_GATEWAY_PORT;
1742
+ else {
1743
+ const availablePort = await findAvailablePort(DENCHCLAW_GATEWAY_PORT_START, MAX_PORT_SCAN_ATTEMPTS);
1744
+ if (!availablePort) throw new Error(`Could not find an available gateway port between ${DENCHCLAW_GATEWAY_PORT_START} and ${DENCHCLAW_GATEWAY_PORT_START + MAX_PORT_SCAN_ATTEMPTS}. Please specify a port explicitly with --gateway-port.`);
1745
+ gatewayPort = availablePort;
1746
+ portAutoAssigned = true;
1747
+ }
1748
+ if (portAutoAssigned && !opts.json) runtime.log(theme.muted(`Default gateway port ${DEFAULT_GATEWAY_PORT} is in use. Using auto-assigned port ${gatewayPort}.`));
1749
+ await ensureDefaultWorkspacePath(openclawCommand, profile, workspaceDir);
1750
+ const onboardArgv = [
1751
+ "--profile",
1752
+ profile,
1753
+ "onboard",
1754
+ "--install-daemon",
1755
+ "--gateway-bind",
1756
+ "loopback",
1757
+ "--gateway-port",
1758
+ String(gatewayPort)
1759
+ ];
1760
+ if (opts.forceOnboard) onboardArgv.push("--reset");
1761
+ if (nonInteractive) onboardArgv.push("--non-interactive", "--accept-risk");
1762
+ if (opts.noOpen) onboardArgv.push("--skip-ui");
1763
+ if (nonInteractive) await runOpenClawOrThrow({
1764
+ openclawCommand,
1765
+ args: onboardArgv,
1766
+ timeoutMs: 12 * 6e4,
1767
+ errorMessage: "OpenClaw onboarding failed."
1768
+ });
1769
+ else await runOpenClawInteractiveOrThrow({
1770
+ openclawCommand,
1771
+ args: onboardArgv,
1772
+ timeoutMs: 12 * 6e4,
1773
+ errorMessage: "OpenClaw onboarding failed."
1774
+ });
1775
+ const packageRoot = resolveCliPackageRoot();
1776
+ const workspaceSeed = seedWorkspaceFromAssets({
1777
+ workspaceDir,
1778
+ packageRoot
1779
+ });
1780
+ await ensureGatewayModeLocal(openclawCommand, profile);
1781
+ await ensureGatewayPort(openclawCommand, profile, gatewayPort);
1782
+ await ensureToolsProfile(openclawCommand, profile);
1783
+ await ensureSubagentDefaults(openclawCommand, profile);
1784
+ let gatewayProbe = await probeGateway(openclawCommand, profile, gatewayPort);
1785
+ let gatewayAutoFix;
1786
+ if (!gatewayProbe.ok) {
1787
+ gatewayAutoFix = await attemptGatewayAutoFix({
1788
+ openclawCommand,
1789
+ profile,
1790
+ stateDir,
1791
+ gatewayPort
1792
+ });
1793
+ gatewayProbe = gatewayAutoFix.finalProbe;
1794
+ if (!gatewayProbe.ok && gatewayAutoFix.failureSummary) gatewayProbe = {
1795
+ ...gatewayProbe,
1796
+ detail: [gatewayProbe.detail, gatewayAutoFix.failureSummary].filter((value, index, self) => value && self.indexOf(value) === index).join(" | ")
1797
+ };
1798
+ }
1799
+ const gatewayUrl = `ws://127.0.0.1:${gatewayPort}`;
1800
+ const preferredWebPort = parseOptionalPort$1(opts.webPort) ?? DEFAULT_WEB_APP_PORT;
1801
+ const webRuntimeStatus = await ensureManagedWebRuntime({
1802
+ stateDir,
1803
+ packageRoot,
1804
+ denchVersion: VERSION,
1805
+ port: preferredWebPort,
1806
+ gatewayPort
1807
+ });
1808
+ const webReachable = webRuntimeStatus.ready;
1809
+ const webUrl = `http://localhost:${preferredWebPort}`;
1810
+ const diagnostics = buildBootstrapDiagnostics({
1811
+ profile,
1812
+ openClawCliAvailable: installResult.available,
1813
+ openClawVersion: installResult.version,
1814
+ gatewayPort,
1815
+ gatewayUrl,
1816
+ gatewayProbe,
1817
+ webPort: preferredWebPort,
1818
+ webReachable,
1819
+ rolloutStage,
1820
+ legacyFallbackEnabled,
1821
+ stateDir
1822
+ });
1823
+ const shouldOpen = !opts.noOpen && !opts.json;
1824
+ const opened = shouldOpen ? await openUrl(webUrl) : false;
1825
+ if (!opts.json) {
1826
+ if (!webRuntimeStatus.ready) runtime.log(theme.warn(`Managed web runtime check failed: ${webRuntimeStatus.reason}`));
1827
+ if (installResult.installed) runtime.log(theme.muted("Installed global OpenClaw CLI via npm."));
1828
+ if (isProjectLocalOpenClawPath(installResult.shellCommandPath)) {
1829
+ runtime.log(theme.warn(`\`openclaw\` currently resolves to a project-local binary (${installResult.shellCommandPath}).`));
1830
+ runtime.log(theme.muted(`Bootstrap now uses the global binary (${openclawCommand}) to avoid repo-local drift.`));
1831
+ } else if (!installResult.shellCommandPath && installResult.globalBinDir) {
1832
+ runtime.log(theme.warn("Global OpenClaw was installed, but `openclaw` is not on shell PATH."));
1833
+ runtime.log(theme.muted(`Add this to your shell profile, then open a new terminal: export PATH="${installResult.globalBinDir}:$PATH"`));
1834
+ }
1835
+ runtime.log(theme.muted(`Workspace seed: ${describeWorkspaceSeedResult(workspaceSeed)}`));
1836
+ if (gatewayAutoFix?.attempted) {
1837
+ runtime.log(theme.muted(`Gateway auto-fix ${gatewayAutoFix.recovered ? "recovered connectivity" : "ran but gateway is still unhealthy"}.`));
1838
+ for (const step of gatewayAutoFix.steps) runtime.log(theme.muted(` ${step.ok ? "[ok]" : "[fail]"} ${step.name}${step.detail ? ` (${step.detail})` : ""}`));
1839
+ if (!gatewayAutoFix.recovered && gatewayAutoFix.failureSummary) runtime.log(theme.error(`Likely gateway cause: ${gatewayAutoFix.failureSummary}`));
1840
+ if (!gatewayAutoFix.recovered && gatewayAutoFix.logExcerpts.length > 0) {
1841
+ runtime.log(theme.muted("Recent gateway logs:"));
1842
+ for (const excerpt of gatewayAutoFix.logExcerpts) {
1843
+ runtime.log(theme.muted(` ${excerpt.path}`));
1844
+ for (const line of excerpt.excerpt.split(/\r?\n/)) runtime.log(theme.muted(` ${line}`));
1845
+ }
1846
+ }
1847
+ }
1848
+ logBootstrapChecklist(diagnostics, runtime);
1849
+ runtime.log("");
1850
+ runtime.log(theme.heading("DenchClaw ready"));
1851
+ runtime.log(`Profile: ${profile}`);
1852
+ runtime.log(`OpenClaw CLI: ${installResult.version ?? "detected"}`);
1853
+ runtime.log(`Gateway: ${gatewayProbe.ok ? "reachable" : "check failed"}`);
1854
+ runtime.log(`Web UI: ${webUrl}`);
1855
+ runtime.log(`Rollout stage: ${rolloutStage}${legacyFallbackEnabled ? " (legacy fallback enabled)" : ""}`);
1856
+ if (!opened && shouldOpen) runtime.log(theme.muted("Browser open failed; copy/paste the URL above."));
1857
+ if (diagnostics.hasFailures) runtime.log(theme.warn("Bootstrap completed with failing checks. Address remediation items above before full cutover."));
1858
+ }
1859
+ const summary = {
1860
+ profile,
1861
+ onboarded: true,
1862
+ installedOpenClawCli: installResult.installed,
1863
+ openClawCliAvailable: installResult.available,
1864
+ openClawVersion: installResult.version,
1865
+ gatewayUrl,
1866
+ gatewayReachable: gatewayProbe.ok,
1867
+ gatewayAutoFix: gatewayAutoFix ? {
1868
+ attempted: gatewayAutoFix.attempted,
1869
+ recovered: gatewayAutoFix.recovered,
1870
+ steps: gatewayAutoFix.steps,
1871
+ failureSummary: gatewayAutoFix.failureSummary,
1872
+ logExcerpts: gatewayAutoFix.logExcerpts
1873
+ } : void 0,
1874
+ workspaceSeed,
1875
+ webUrl,
1876
+ webReachable,
1877
+ webOpened: opened,
1878
+ diagnostics
1879
+ };
1880
+ if (opts.json) runtime.log(JSON.stringify(summary, null, 2));
1881
+ return summary;
1882
+ }
1883
+
1884
+ //#endregion
1885
+ //#region src/logging/redact.ts
1886
+ function resolveNodeRequire() {
1887
+ const getBuiltinModule = process.getBuiltinModule;
1888
+ if (typeof getBuiltinModule !== "function") return null;
1889
+ try {
1890
+ const moduleNamespace = getBuiltinModule("module");
1891
+ return typeof moduleNamespace.createRequire === "function" ? moduleNamespace.createRequire : null;
1892
+ } catch {
1893
+ return null;
1894
+ }
1895
+ }
1896
+ const requireConfig = resolveNodeRequire()?.(import.meta.url) ?? null;
1897
+ const DEFAULT_REDACT_PATTERNS = [
1898
+ String.raw`\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1`,
1899
+ String.raw`"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"`,
1900
+ String.raw`--(?:api[-_]?key|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1`,
1901
+ String.raw`Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)`,
1902
+ String.raw`\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b`,
1903
+ String.raw`-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----`,
1904
+ String.raw`\b(sk-[A-Za-z0-9_-]{8,})\b`,
1905
+ String.raw`\b(ghp_[A-Za-z0-9]{20,})\b`,
1906
+ String.raw`\b(github_pat_[A-Za-z0-9_]{20,})\b`,
1907
+ String.raw`\b(xox[baprs]-[A-Za-z0-9-]{10,})\b`,
1908
+ String.raw`\b(xapp-[A-Za-z0-9-]{10,})\b`,
1909
+ String.raw`\b(gsk_[A-Za-z0-9_-]{10,})\b`,
1910
+ String.raw`\b(AIza[0-9A-Za-z\-_]{20,})\b`,
1911
+ String.raw`\b(pplx-[A-Za-z0-9_-]{10,})\b`,
1912
+ String.raw`\b(npm_[A-Za-z0-9]{10,})\b`,
1913
+ String.raw`\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b`,
1914
+ String.raw`\b(\d{6,}:[A-Za-z0-9_-]{20,})\b`
1915
+ ];
1916
+
1917
+ //#endregion
1918
+ //#region src/cli/cli-utils.ts
1919
+ async function runCommandWithRuntime(runtime, action, onError) {
1920
+ try {
1921
+ await action();
1922
+ } catch (err) {
1923
+ if (onError) {
1924
+ onError(err);
1925
+ return;
1926
+ }
1927
+ runtime.error(String(err));
1928
+ runtime.exit(1);
1929
+ }
1930
+ }
1931
+
1932
+ //#endregion
1933
+ //#region src/cli/program/register.bootstrap.ts
1934
+ function registerBootstrapCommand(program) {
1935
+ program.command("bootstrap").description("Bootstrap DenchClaw on top of OpenClaw and open the web UI").option("--profile <name>", "Compatibility flag; non-dench values are ignored with a warning").option("--force-onboard", "Run onboarding even if config already exists", false).option("--non-interactive", "Skip prompts where possible", false).option("--yes", "Auto-approve install prompts", false).option("--skip-update", "Skip update prompt/check", false).option("--update-now", "Run OpenClaw update before onboarding", false).option("--gateway-port <port>", "Gateway port override for first-run onboarding").option("--web-port <port>", "Preferred web UI port (default: 3100)").option("--no-open", "Do not open the browser automatically", false).option("--json", "Output summary as JSON", false).addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/onboard", "docs.openclaw.ai/cli/onboard")}\n`).action(async (opts) => {
1936
+ await runCommandWithRuntime(defaultRuntime, async () => {
1937
+ await bootstrapCommand({
1938
+ profile: opts.profile,
1939
+ forceOnboard: Boolean(opts.forceOnboard),
1940
+ nonInteractive: Boolean(opts.nonInteractive),
1941
+ yes: Boolean(opts.yes),
1942
+ skipUpdate: Boolean(opts.skipUpdate),
1943
+ updateNow: Boolean(opts.updateNow),
1944
+ gatewayPort: opts.gatewayPort,
1945
+ webPort: opts.webPort,
1946
+ noOpen: Boolean(opts.open === false),
1947
+ json: Boolean(opts.json)
1948
+ });
1949
+ });
1950
+ });
1951
+ }
1952
+
1953
+ //#endregion
1954
+ //#region src/cli/web-runtime-command.ts
1955
+ function parseOptionalPort(value) {
1956
+ if (value === void 0) return;
1957
+ const parsed = typeof value === "number" ? value : Number.parseInt(String(value), 10);
1958
+ if (!Number.isFinite(parsed) || parsed <= 0) return;
1959
+ return parsed;
1960
+ }
1961
+ function firstNonEmptyLine(...values) {
1962
+ for (const value of values) {
1963
+ const first = value?.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
1964
+ if (first) return first;
1965
+ }
1966
+ }
1967
+ async function runOpenClawUpdateWithProgress(openclawCommand) {
1968
+ const s = spinner();
1969
+ s.start("Updating OpenClaw (required for this major Dench upgrade)...");
1970
+ const result = await new Promise((resolve, reject) => {
1971
+ const child = spawn(openclawCommand, ["update", "--yes"], {
1972
+ stdio: [
1973
+ "ignore",
1974
+ "pipe",
1975
+ "pipe"
1976
+ ],
1977
+ env: process$1.env
1978
+ });
1979
+ let stdout = "";
1980
+ let stderr = "";
1981
+ let settled = false;
1982
+ const timer = setTimeout(() => {
1983
+ if (!settled) child.kill("SIGKILL");
1984
+ }, 8 * 6e4);
1985
+ const updateSpinner = (chunk) => {
1986
+ const line = chunk.split(/\r?\n/).map((item) => item.trim()).filter(Boolean).at(-1);
1987
+ if (line) s.message(line.length > 72 ? `${line.slice(0, 69)}...` : line);
1988
+ };
1989
+ child.stdout?.on("data", (chunk) => {
1990
+ const text = String(chunk);
1991
+ stdout += text;
1992
+ updateSpinner(text);
1993
+ });
1994
+ child.stderr?.on("data", (chunk) => {
1995
+ const text = String(chunk);
1996
+ stderr += text;
1997
+ updateSpinner(text);
1998
+ });
1999
+ child.once("error", (error) => {
2000
+ if (settled) return;
2001
+ settled = true;
2002
+ clearTimeout(timer);
2003
+ reject(error);
2004
+ });
2005
+ child.once("close", (code) => {
2006
+ if (settled) return;
2007
+ settled = true;
2008
+ clearTimeout(timer);
2009
+ resolve({
2010
+ code: typeof code === "number" ? code : 1,
2011
+ stdout,
2012
+ stderr
2013
+ });
2014
+ });
2015
+ });
2016
+ if (result.code === 0) {
2017
+ s.stop("OpenClaw update complete.");
2018
+ return;
2019
+ }
2020
+ const detail = firstNonEmptyLine(result.stderr, result.stdout);
2021
+ s.stop(detail ? `OpenClaw update failed: ${detail}` : "OpenClaw update failed.");
2022
+ throw new Error(detail ? `OpenClaw update failed.\n${detail}` : "OpenClaw update failed. Fix this before running `dench update` again.");
2023
+ }
2024
+ async function ensureMajorUpgradeAcknowledged(params) {
2025
+ if (!params.required) return;
2026
+ if (params.nonInteractive || !process$1.stdin.isTTY) {
2027
+ if (!params.yes) throw new Error(`Major Dench upgrade detected (${params.previousVersion ?? "unknown"} -> ${params.currentVersion}). Re-run with --yes to approve the required OpenClaw update.`);
2028
+ return;
2029
+ }
2030
+ if (params.yes) return;
2031
+ const decision = await confirm({
2032
+ message: stylePromptMessage(`Major Dench upgrade detected (${params.previousVersion ?? "unknown"} -> ${params.currentVersion}). Continue with mandatory OpenClaw update now?`),
2033
+ initialValue: true
2034
+ });
2035
+ if (isCancel(decision) || !decision) {
2036
+ params.runtime.log(theme.warn("Update cancelled. OpenClaw update is required for major upgrades."));
2037
+ throw new Error("Major upgrade requires OpenClaw update approval.");
2038
+ }
2039
+ }
2040
+ function resolveGatewayPort(stateDir) {
2041
+ const manifest = readManagedWebRuntimeManifest(stateDir);
2042
+ if (typeof manifest?.lastGatewayPort === "number" && Number.isFinite(manifest.lastGatewayPort) && manifest.lastGatewayPort > 0) return manifest.lastGatewayPort;
2043
+ for (const name of ["openclaw.json", "config.json"]) {
2044
+ const port = readConfigGatewayPort(path.join(stateDir, name));
2045
+ if (typeof port === "number") return port;
2046
+ }
2047
+ return 18789;
2048
+ }
2049
+ function readConfigGatewayPort(configPath) {
2050
+ try {
2051
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
2052
+ const parsedPort = typeof raw.gateway?.port === "number" ? raw.gateway.port : typeof raw.gateway?.port === "string" ? Number.parseInt(raw.gateway.port, 10) : void 0;
2053
+ if (typeof parsedPort === "number" && Number.isFinite(parsedPort) && parsedPort > 0) return parsedPort;
2054
+ return;
2055
+ } catch {
2056
+ return;
2057
+ }
2058
+ }
2059
+ async function updateWebRuntimeCommand(opts, runtime = defaultRuntime) {
2060
+ const appliedProfile = applyCliProfileEnv({ profile: opts.profile });
2061
+ const profile = appliedProfile.effectiveProfile;
2062
+ if (appliedProfile.warning && !opts.json) runtime.log(theme.warn(appliedProfile.warning));
2063
+ const stateDir = resolveProfileStateDir(profile);
2064
+ const packageRoot = resolveCliPackageRoot();
2065
+ const previousManifest = readManagedWebRuntimeManifest(stateDir);
2066
+ const transition = evaluateMajorVersionTransition({
2067
+ previousVersion: previousManifest?.deployedDenchVersion,
2068
+ currentVersion: VERSION
2069
+ });
2070
+ const nonInteractive = Boolean(opts.nonInteractive || opts.json);
2071
+ await ensureMajorUpgradeAcknowledged({
2072
+ required: transition.isMajorTransition,
2073
+ previousVersion: previousManifest?.deployedDenchVersion,
2074
+ currentVersion: VERSION,
2075
+ nonInteractive,
2076
+ yes: Boolean(opts.yes),
2077
+ runtime
2078
+ });
2079
+ if (transition.isMajorTransition) await runOpenClawUpdateWithProgress(resolveOpenClawCommandOrThrow());
2080
+ const selectedPort = parseOptionalPort(opts.webPort) ?? parseOptionalPort(previousManifest?.lastPort) ?? readLastKnownWebPort(stateDir) ?? DEFAULT_WEB_APP_PORT;
2081
+ const gatewayPort = resolveGatewayPort(stateDir);
2082
+ const stopResult = await stopManagedWebRuntime({
2083
+ stateDir,
2084
+ port: selectedPort,
2085
+ includeLegacyStandalone: true
2086
+ });
2087
+ const ensureResult = await ensureManagedWebRuntime({
2088
+ stateDir,
2089
+ packageRoot,
2090
+ denchVersion: VERSION,
2091
+ port: selectedPort,
2092
+ gatewayPort
2093
+ });
2094
+ const summary = {
2095
+ profile,
2096
+ webPort: selectedPort,
2097
+ version: VERSION,
2098
+ majorGate: {
2099
+ required: transition.isMajorTransition,
2100
+ previousVersion: previousManifest?.deployedDenchVersion,
2101
+ currentVersion: VERSION
2102
+ },
2103
+ stoppedPids: stopResult.stoppedPids,
2104
+ skippedForeignPids: stopResult.skippedForeignPids,
2105
+ ready: ensureResult.ready,
2106
+ reason: ensureResult.reason
2107
+ };
2108
+ if (!opts.json) {
2109
+ runtime.log("");
2110
+ runtime.log(theme.heading("Dench web update"));
2111
+ runtime.log(`Profile: ${profile}`);
2112
+ runtime.log(`Version: ${VERSION}`);
2113
+ runtime.log(`Web port: ${selectedPort}`);
2114
+ runtime.log(`Stopped web processes: ${summary.stoppedPids.length}`);
2115
+ if (summary.skippedForeignPids.length > 0) runtime.log(theme.warn(`Skipped non-Dench listeners on ${selectedPort}: ${summary.skippedForeignPids.join(", ")}`));
2116
+ runtime.log(`Web runtime: ${summary.ready ? "ready" : "not ready"}`);
2117
+ if (!summary.ready) runtime.log(theme.warn(summary.reason));
2118
+ }
2119
+ if (!summary.ready) throw new Error(`Web runtime update failed: ${summary.reason}`);
2120
+ if (opts.json) runtime.log(JSON.stringify(summary, null, 2));
2121
+ return summary;
2122
+ }
2123
+ async function stopWebRuntimeCommand(opts, runtime = defaultRuntime) {
2124
+ const appliedProfile = applyCliProfileEnv({ profile: opts.profile });
2125
+ const profile = appliedProfile.effectiveProfile;
2126
+ if (appliedProfile.warning && !opts.json) runtime.log(theme.warn(appliedProfile.warning));
2127
+ const stateDir = resolveProfileStateDir(profile);
2128
+ const selectedPort = parseOptionalPort(opts.webPort) ?? readLastKnownWebPort(stateDir);
2129
+ const stopResult = await stopManagedWebRuntime({
2130
+ stateDir,
2131
+ port: selectedPort,
2132
+ includeLegacyStandalone: true
2133
+ });
2134
+ const summary = {
2135
+ profile,
2136
+ webPort: selectedPort,
2137
+ stoppedPids: stopResult.stoppedPids,
2138
+ skippedForeignPids: stopResult.skippedForeignPids
2139
+ };
2140
+ if (opts.json) {
2141
+ runtime.log(JSON.stringify(summary, null, 2));
2142
+ return summary;
2143
+ }
2144
+ runtime.log("");
2145
+ runtime.log(theme.heading("Dench web stop"));
2146
+ runtime.log(`Profile: ${profile}`);
2147
+ runtime.log(`Web port: ${selectedPort}`);
2148
+ runtime.log(summary.stoppedPids.length > 0 ? `Stopped web processes: ${summary.stoppedPids.join(", ")}` : "Stopped web processes: none");
2149
+ if (summary.skippedForeignPids.length > 0) runtime.log(theme.warn(`Left non-Dench listener(s) running on ${selectedPort}: ${summary.skippedForeignPids.join(", ")}`));
2150
+ return summary;
2151
+ }
2152
+ async function startWebRuntimeCommand(opts, runtime = defaultRuntime) {
2153
+ const appliedProfile = applyCliProfileEnv({ profile: opts.profile });
2154
+ const profile = appliedProfile.effectiveProfile;
2155
+ if (appliedProfile.warning && !opts.json) runtime.log(theme.warn(appliedProfile.warning));
2156
+ const stateDir = resolveProfileStateDir(profile);
2157
+ const selectedPort = parseOptionalPort(opts.webPort) ?? readLastKnownWebPort(stateDir);
2158
+ const gatewayPort = resolveGatewayPort(stateDir);
2159
+ const stopResult = await stopManagedWebRuntime({
2160
+ stateDir,
2161
+ port: selectedPort,
2162
+ includeLegacyStandalone: true
2163
+ });
2164
+ if (stopResult.skippedForeignPids.length > 0) throw new Error(`Cannot start on ${selectedPort}; non-Dench listener(s) still own the port: ${stopResult.skippedForeignPids.join(", ")}`);
2165
+ if (!startManagedWebRuntime({
2166
+ stateDir,
2167
+ port: selectedPort,
2168
+ gatewayPort
2169
+ }).started) {
2170
+ const runtimeServerPath = resolveManagedWebRuntimeServerPath(stateDir);
2171
+ throw new Error([`Managed web runtime is missing at ${runtimeServerPath}.`, "Run `dench update` (or `dench bootstrap`) to install/update the web runtime first."].join(" "));
2172
+ }
2173
+ const probe = await waitForWebRuntime(selectedPort);
2174
+ const summary = {
2175
+ profile,
2176
+ webPort: selectedPort,
2177
+ stoppedPids: stopResult.stoppedPids,
2178
+ skippedForeignPids: stopResult.skippedForeignPids,
2179
+ started: probe.ok,
2180
+ reason: probe.reason
2181
+ };
2182
+ if (opts.json) {
2183
+ runtime.log(JSON.stringify(summary, null, 2));
2184
+ return summary;
2185
+ }
2186
+ runtime.log("");
2187
+ runtime.log(theme.heading("Dench web start"));
2188
+ runtime.log(`Profile: ${profile}`);
2189
+ runtime.log(`Web port: ${selectedPort}`);
2190
+ runtime.log(`Restarted managed web runtime: ${summary.started ? "yes" : "no"}`);
2191
+ if (!summary.started) runtime.log(theme.warn(summary.reason));
2192
+ if (!summary.started) throw new Error(`Web runtime failed readiness probe: ${summary.reason}`);
2193
+ return summary;
2194
+ }
2195
+
2196
+ //#endregion
2197
+ //#region src/cli/program/register.start.ts
2198
+ function registerStartCommand(program) {
2199
+ program.command("start").description("Start Dench managed web runtime without updating assets").option("--profile <name>", "Compatibility flag; non-dench values are ignored with a warning").option("--web-port <port>", "Web runtime port override").option("--json", "Output summary as JSON", false).action(async (opts) => {
2200
+ await runCommandWithRuntime(defaultRuntime, async () => {
2201
+ await startWebRuntimeCommand({
2202
+ profile: opts.profile,
2203
+ webPort: opts.webPort,
2204
+ json: Boolean(opts.json)
2205
+ });
2206
+ });
2207
+ });
2208
+ }
2209
+
2210
+ //#endregion
2211
+ //#region src/cli/program/register.stop.ts
2212
+ function registerStopCommand(program) {
2213
+ program.command("stop").description("Stop Dench managed web runtime on the configured port").option("--profile <name>", "Compatibility flag; non-dench values are ignored with a warning").option("--web-port <port>", "Web runtime port override").option("--json", "Output summary as JSON", false).action(async (opts) => {
2214
+ await runCommandWithRuntime(defaultRuntime, async () => {
2215
+ await stopWebRuntimeCommand({
2216
+ profile: opts.profile,
2217
+ webPort: opts.webPort,
2218
+ json: Boolean(opts.json)
2219
+ });
2220
+ });
2221
+ });
2222
+ }
2223
+
2224
+ //#endregion
2225
+ //#region src/cli/program/register.update.ts
2226
+ function registerUpdateCommand(program) {
2227
+ program.command("update").description("Update Dench managed web runtime without onboarding").option("--profile <name>", "Compatibility flag; non-dench values are ignored with a warning").option("--web-port <port>", "Web runtime port override").option("--non-interactive", "Fail instead of prompting for major-gate approval", false).option("--yes", "Approve mandatory major-gate OpenClaw update", false).option("--json", "Output summary as JSON", false).action(async (opts) => {
2228
+ await runCommandWithRuntime(defaultRuntime, async () => {
2229
+ await updateWebRuntimeCommand({
2230
+ profile: opts.profile,
2231
+ webPort: opts.webPort,
2232
+ nonInteractive: Boolean(opts.nonInteractive),
2233
+ yes: Boolean(opts.yes),
2234
+ json: Boolean(opts.json)
2235
+ });
2236
+ });
2237
+ });
2238
+ }
2239
+
2240
+ //#endregion
2241
+ //#region src/cli/program/command-registry.ts
2242
+ const CORE_CLI_ENTRIES = [
2243
+ {
2244
+ name: "bootstrap",
2245
+ description: "Bootstrap DenchClaw + OpenClaw and launch the web UI",
2246
+ register: ({ program }) => {
2247
+ registerBootstrapCommand(program);
2248
+ }
2249
+ },
2250
+ {
2251
+ name: "update",
2252
+ description: "Update Dench web runtime without onboarding",
2253
+ register: ({ program }) => {
2254
+ registerUpdateCommand(program);
2255
+ }
2256
+ },
2257
+ {
2258
+ name: "stop",
2259
+ description: "Stop Dench managed web runtime",
2260
+ register: ({ program }) => {
2261
+ registerStopCommand(program);
2262
+ }
2263
+ },
2264
+ {
2265
+ name: "start",
2266
+ description: "Start Dench managed web runtime",
2267
+ register: ({ program }) => {
2268
+ registerStartCommand(program);
2269
+ }
2270
+ }
2271
+ ];
2272
+ const CORE_CLI_ENTRY_BY_NAME = new Map(CORE_CLI_ENTRIES.map((entry) => [entry.name, entry]));
2273
+ function getCoreCliCommandsWithSubcommands() {
2274
+ return [];
2275
+ }
2276
+ function registerCoreCliCommands(program, ctx, argv) {
2277
+ const primary = getPrimaryCommand(argv);
2278
+ if (primary) {
2279
+ const entry = CORE_CLI_ENTRY_BY_NAME.get(primary);
2280
+ if (!entry) return;
2281
+ entry.register({
2282
+ program,
2283
+ ctx,
2284
+ argv
2285
+ });
2286
+ return;
2287
+ }
2288
+ for (const entry of CORE_CLI_ENTRIES) entry.register({
2289
+ program,
2290
+ ctx,
2291
+ argv
2292
+ });
2293
+ }
2294
+ function registerProgramCommands(program, ctx, argv = process.argv) {
2295
+ registerCoreCliCommands(program, ctx, argv);
2296
+ }
2297
+
2298
+ //#endregion
2299
+ //#region src/cli/program/context.ts
2300
+ function createProgramContext() {
2301
+ const channelOptions = ["web"];
2302
+ return {
2303
+ programVersion: VERSION,
2304
+ channelOptions,
2305
+ messageChannelOptions: channelOptions.join("|"),
2306
+ agentChannelOptions: ["last", ...channelOptions].join("|")
2307
+ };
2308
+ }
2309
+
2310
+ //#endregion
2311
+ //#region src/cli/program/help.ts
2312
+ const CLI_NAME = resolveCliName();
2313
+ const CLI_NAME_PATTERN = escapeRegExp(CLI_NAME);
2314
+ const ROOT_COMMANDS_WITH_SUBCOMMANDS = new Set(getCoreCliCommandsWithSubcommands());
2315
+ const ROOT_COMMANDS_HINT = "Hint: commands suffixed with * have subcommands. Run <command> --help for details.";
2316
+ const EXAMPLES = [
2317
+ ["openclaw start --web-port 3100", "Start the managed web runtime without replacing assets."],
2318
+ ["openclaw update", "Refresh the managed web runtime and enforce major upgrade gates."],
2319
+ ["openclaw stop --web-port 3100", "Stop only the managed web runtime on a specific port."],
2320
+ ["openclaw models --help", "Show detailed help for the models command."],
2321
+ ["openclaw channels login --verbose", "Link personal WhatsApp Web and show QR + connection logs."],
2322
+ ["openclaw message send --target +15555550123 --message \"Hi\" --json", "Send via your web session and print JSON result."],
2323
+ ["openclaw gateway --port 18789", "Run the WebSocket Gateway locally."],
2324
+ ["openclaw --profile team-a gateway", "Compatibility flag example: warns and still runs with --profile dench."],
2325
+ ["openclaw gateway --force", "Kill anything bound to the default gateway port, then start it."],
2326
+ ["openclaw gateway ...", "Gateway control via WebSocket."],
2327
+ ["openclaw agent --to +15555550123 --message \"Run summary\" --deliver", "Talk directly to the agent using the Gateway; optionally send the WhatsApp reply."],
2328
+ ["openclaw message send --channel telegram --target @mychat --message \"Hi\"", "Send via your Telegram bot."]
2329
+ ];
2330
+ function configureProgramHelp(program, ctx) {
2331
+ program.name(CLI_NAME).description("").version(ctx.programVersion).option("--dev", "Compatibility flag; DenchClaw always uses --profile dench and ~/.openclaw-dench").option("--profile <name>", "Compatibility flag; non-dench values are ignored with a warning");
2332
+ program.option("--no-color", "Disable ANSI colors", false);
2333
+ program.helpOption("-h, --help", "Display help for command");
2334
+ program.helpCommand("help [command]", "Display help for command");
2335
+ program.configureHelp({
2336
+ sortSubcommands: true,
2337
+ sortOptions: true,
2338
+ optionTerm: (option) => theme.option(option.flags),
2339
+ subcommandTerm: (cmd) => {
2340
+ const hasSubcommands = cmd.parent === program && ROOT_COMMANDS_WITH_SUBCOMMANDS.has(cmd.name());
2341
+ return theme.command(hasSubcommands ? `${cmd.name()} *` : cmd.name());
2342
+ }
2343
+ });
2344
+ const formatHelpOutput = (str) => {
2345
+ let output = str;
2346
+ if (new RegExp(`^Usage:\\s+${CLI_NAME_PATTERN}\\s+\\[options\\]\\s+\\[command\\]\\s*$`, "m").test(output) && /^Commands:/m.test(output)) output = output.replace(/^Commands:/m, `Commands:\n ${theme.muted(ROOT_COMMANDS_HINT)}`);
2347
+ return output.replace(/^Usage:/gm, theme.heading("Usage:")).replace(/^Options:/gm, theme.heading("Options:")).replace(/^Commands:/gm, theme.heading("Commands:"));
2348
+ };
2349
+ program.configureOutput({
2350
+ writeOut: (str) => {
2351
+ process.stdout.write(formatHelpOutput(str));
2352
+ },
2353
+ writeErr: (str) => {
2354
+ process.stderr.write(formatHelpOutput(str));
2355
+ },
2356
+ outputError: (str, write) => write(theme.error(str))
2357
+ });
2358
+ if (hasFlag(process.argv, "-V") || hasFlag(process.argv, "--version") || hasRootVersionAlias(process.argv)) {
2359
+ console.log(ctx.programVersion);
2360
+ process.exit(0);
2361
+ }
2362
+ program.addHelpText("beforeAll", () => {
2363
+ if (hasEmittedCliBanner()) return "";
2364
+ const rich = isRich();
2365
+ return `\n${formatCliBannerLine(ctx.programVersion, { richTty: rich })}\n`;
2366
+ });
2367
+ const fmtExamples = EXAMPLES.map(([cmd, desc]) => ` ${theme.command(replaceCliName(cmd, CLI_NAME))}\n ${theme.muted(desc)}`).join("\n");
2368
+ program.addHelpText("afterAll", ({ command }) => {
2369
+ if (command !== program) return "";
2370
+ const docs = formatDocsLink("/cli", "docs.openclaw.ai/cli");
2371
+ return `\n${theme.heading("Examples:")}\n${fmtExamples}\n\n${theme.muted("Docs:")} ${docs}\n`;
2372
+ });
2373
+ }
2374
+
2375
+ //#endregion
2376
+ //#region src/cli/program/preaction.ts
2377
+ function setProcessTitleForCommand(actionCommand) {
2378
+ let current = actionCommand;
2379
+ while (current.parent && current.parent.parent) current = current.parent;
2380
+ const name = current.name();
2381
+ const cliName = resolveCliName();
2382
+ if (!name || name === cliName) return;
2383
+ process.title = `${cliName}-${name}`;
2384
+ }
2385
+ function registerPreActionHooks(program, programVersion) {
2386
+ program.hook("preAction", (_thisCommand, actionCommand) => {
2387
+ setProcessTitleForCommand(actionCommand);
2388
+ });
2389
+ }
2390
+
2391
+ //#endregion
2392
+ //#region src/cli/program/program-context.ts
2393
+ const PROGRAM_CONTEXT_SYMBOL = Symbol.for("openclaw.cli.programContext");
2394
+ function setProgramContext(program, ctx) {
2395
+ program[PROGRAM_CONTEXT_SYMBOL] = ctx;
2396
+ }
2397
+
2398
+ //#endregion
2399
+ //#region src/cli/program/build-program.ts
2400
+ function buildProgram() {
2401
+ const program = new Command();
2402
+ const ctx = createProgramContext();
2403
+ const argv = process.argv;
2404
+ setProgramContext(program, ctx);
2405
+ configureProgramHelp(program, ctx);
2406
+ registerPreActionHooks(program, ctx.programVersion);
2407
+ registerProgramCommands(program, ctx, argv);
2408
+ return program;
2409
+ }
2410
+
2411
+ //#endregion
2412
+ export { buildProgram };