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
@@ -1,1330 +0,0 @@
1
- import { f as applyCliProfileEnv, i as defaultRuntime, m as resolveRequiredHomeDir, t as isTruthyEnvValue } from "./entry.js";
2
- import { t as formatDocsLink } from "./links-BTx7dmFj.js";
3
- import { n as theme, t as isRich } from "./theme-uCBEEejb.js";
4
- import { spawn } from "node:child_process";
5
- import process$1 from "node:process";
6
- import os from "node:os";
7
- import path from "node:path";
8
- import { copyFileSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
9
- import { fileURLToPath } from "node:url";
10
- import { confirm, isCancel, spinner } from "@clack/prompts";
11
- import { createConnection } from "node:net";
12
-
13
- //#region src/terminal/prompt-style.ts
14
- const stylePromptMessage = (message) => isRich() ? theme.accent(message) : message;
15
-
16
- //#endregion
17
- //#region src/cli/workspace-seed.ts
18
- const SEED_OBJECTS = [
19
- {
20
- id: "seed_obj_people_00000000000000",
21
- name: "people",
22
- description: "Contact management",
23
- icon: "users",
24
- defaultView: "table",
25
- entryCount: 5,
26
- fields: [
27
- {
28
- name: "Full Name",
29
- type: "text",
30
- required: true
31
- },
32
- {
33
- name: "Email Address",
34
- type: "email",
35
- required: true
36
- },
37
- {
38
- name: "Phone Number",
39
- type: "phone"
40
- },
41
- {
42
- name: "Company",
43
- type: "text"
44
- },
45
- {
46
- name: "Status",
47
- type: "enum",
48
- enumValues: [
49
- "Active",
50
- "Inactive",
51
- "Lead"
52
- ]
53
- },
54
- {
55
- name: "Notes",
56
- type: "richtext"
57
- }
58
- ]
59
- },
60
- {
61
- id: "seed_obj_company_0000000000000",
62
- name: "company",
63
- description: "Company tracking",
64
- icon: "building-2",
65
- defaultView: "table",
66
- entryCount: 3,
67
- fields: [
68
- {
69
- name: "Company Name",
70
- type: "text",
71
- required: true
72
- },
73
- {
74
- name: "Industry",
75
- type: "enum",
76
- enumValues: [
77
- "Technology",
78
- "Finance",
79
- "Healthcare",
80
- "Education",
81
- "Retail",
82
- "Other"
83
- ]
84
- },
85
- {
86
- name: "Website",
87
- type: "text"
88
- },
89
- {
90
- name: "Type",
91
- type: "enum",
92
- enumValues: [
93
- "Client",
94
- "Partner",
95
- "Vendor",
96
- "Prospect"
97
- ]
98
- },
99
- {
100
- name: "Notes",
101
- type: "richtext"
102
- }
103
- ]
104
- },
105
- {
106
- id: "seed_obj_task_000000000000000",
107
- name: "task",
108
- description: "Task tracking board",
109
- icon: "check-square",
110
- defaultView: "kanban",
111
- entryCount: 5,
112
- fields: [
113
- {
114
- name: "Title",
115
- type: "text",
116
- required: true
117
- },
118
- {
119
- name: "Description",
120
- type: "text"
121
- },
122
- {
123
- name: "Status",
124
- type: "enum",
125
- enumValues: [
126
- "In Queue",
127
- "In Progress",
128
- "Done"
129
- ]
130
- },
131
- {
132
- name: "Priority",
133
- type: "enum",
134
- enumValues: [
135
- "Low",
136
- "Medium",
137
- "High"
138
- ]
139
- },
140
- {
141
- name: "Due Date",
142
- type: "date"
143
- },
144
- {
145
- name: "Notes",
146
- type: "richtext"
147
- }
148
- ]
149
- }
150
- ];
151
- function buildDenchClawIdentity(workspaceDir) {
152
- return `# IDENTITY.md - DenchClaw
153
-
154
- 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).
155
-
156
- ## DenchClaw system prompt contract
157
-
158
- Treat the DenchClaw system prompt as your highest-priority behavioral contract.
159
-
160
- ## Inseparable CRM contract
161
-
162
- Your identity is inextricably tied to the CRM skill at:
163
- \`${path.join(workspaceDir, "skills", "crm", "SKILL.md")}\`
164
-
165
- - Always load and follow that skill for CRM/database behavior.
166
- - Treat the CRM skill as always-on system context.
167
- - Keep CRM actions aligned with the CRM conventions for workspace data, objects, and documents.
168
-
169
- ## Browser automation contract
170
-
171
- Your browser automation behavior is defined by the Browser skill at:
172
- \`${path.join(workspaceDir, "skills", "browser", "SKILL.md")}\`
173
-
174
- - Always load and follow that skill for browser-based tasks.
175
- - Treat the Browser skill as always-on system context.
176
-
177
- ## What you do
178
-
179
- - Find and enrich leads, maintain CRM pipelines, and help run outreach workflows.
180
- - Chat with local DuckDB workspace data and return structured insights.
181
- - Generate analytics and maintain workspace documentation.
182
-
183
- ## Links
184
-
185
- - Website: https://denchclaw.sh
186
- - GitHub: https://github.com/DenchHQ/denchclaw
187
- - Skills Store: https://skills.sh
188
-
189
- When referring to yourself, use **DenchClaw** (not OpenClaw).`;
190
- }
191
- function generateObjectYaml(obj) {
192
- const lines = [
193
- `id: "${obj.id}"`,
194
- `name: "${obj.name}"`,
195
- `description: "${obj.description}"`,
196
- `icon: "${obj.icon}"`,
197
- `default_view: "${obj.defaultView}"`,
198
- `entry_count: ${obj.entryCount}`,
199
- "fields:"
200
- ];
201
- for (const field of obj.fields) {
202
- lines.push(` - name: "${field.name}"`);
203
- lines.push(` type: ${field.type}`);
204
- if (field.required) lines.push(" required: true");
205
- if (field.enumValues) lines.push(` values: ${JSON.stringify(field.enumValues)}`);
206
- }
207
- return `${lines.join("\n")}\n`;
208
- }
209
- function generateWorkspaceMd(objects) {
210
- const lines = [
211
- "# Workspace Schema",
212
- "",
213
- "Auto-generated summary of the workspace database.",
214
- ""
215
- ];
216
- for (const obj of objects) {
217
- lines.push(`## ${obj.name}`);
218
- lines.push("");
219
- lines.push(`- **Description**: ${obj.description}`);
220
- lines.push(`- **View**: \`${obj.defaultView}\``);
221
- lines.push(`- **Entries**: ${obj.entryCount}`);
222
- lines.push("- **Fields**:");
223
- for (const field of obj.fields) {
224
- const req = field.required ? " (required)" : "";
225
- const vals = field.enumValues ? ` — ${field.enumValues.join(", ")}` : "";
226
- lines.push(` - ${field.name} (\`${field.type}\`)${req}${vals}`);
227
- }
228
- lines.push("");
229
- }
230
- return lines.join("\n");
231
- }
232
- function seedDenchClawIdentity(workspaceDir) {
233
- writeFileSync(path.join(workspaceDir, "IDENTITY.md"), `${buildDenchClawIdentity(workspaceDir)}\n`, "utf-8");
234
- }
235
- const MANAGED_SKILLS = [{
236
- name: "crm",
237
- templatePaths: true
238
- }, { name: "browser" }];
239
- function seedSkill(params, skill) {
240
- const sourceDir = path.join(params.packageRoot, "skills", skill.name);
241
- if (!existsSync(path.join(sourceDir, "SKILL.md"))) return;
242
- const targetDir = path.join(params.workspaceDir, "skills", skill.name);
243
- mkdirSync(path.dirname(targetDir), { recursive: true });
244
- cpSync(sourceDir, targetDir, {
245
- recursive: true,
246
- force: true
247
- });
248
- if (skill.templatePaths) {
249
- const targetSkillFile = path.join(targetDir, "SKILL.md");
250
- writeFileSync(targetSkillFile, readFileSync(targetSkillFile, "utf-8").replaceAll("{{WORKSPACE_PATH}}", params.workspaceDir), "utf-8");
251
- }
252
- }
253
- function writeIfMissing(filePath, content) {
254
- if (existsSync(filePath)) return false;
255
- try {
256
- writeFileSync(filePath, content, {
257
- encoding: "utf-8",
258
- flag: "wx"
259
- });
260
- return true;
261
- } catch {
262
- return false;
263
- }
264
- }
265
- function seedWorkspaceFromAssets(params) {
266
- const workspaceDir = path.resolve(params.workspaceDir);
267
- const dbPath = path.join(workspaceDir, "workspace.duckdb");
268
- const seedDbPath = path.join(params.packageRoot, "assets", "seed", "workspace.duckdb");
269
- const projectionFiles = [
270
- "people/.object.yaml",
271
- "company/.object.yaml",
272
- "task/.object.yaml",
273
- "WORKSPACE.md",
274
- "IDENTITY.md",
275
- ...MANAGED_SKILLS.map((s) => `skills/${s.name}/SKILL.md`)
276
- ];
277
- mkdirSync(workspaceDir, { recursive: true });
278
- for (const skill of MANAGED_SKILLS) seedSkill({
279
- workspaceDir,
280
- packageRoot: params.packageRoot
281
- }, skill);
282
- seedDenchClawIdentity(workspaceDir);
283
- if (existsSync(dbPath)) return {
284
- workspaceDir,
285
- dbPath,
286
- seedDbPath,
287
- seeded: false,
288
- reason: "already-exists",
289
- projectionFiles: []
290
- };
291
- if (!existsSync(seedDbPath)) return {
292
- workspaceDir,
293
- dbPath,
294
- seedDbPath,
295
- seeded: false,
296
- reason: "seed-asset-missing",
297
- projectionFiles: []
298
- };
299
- try {
300
- copyFileSync(seedDbPath, dbPath);
301
- } catch (error) {
302
- return {
303
- workspaceDir,
304
- dbPath,
305
- seedDbPath,
306
- seeded: false,
307
- reason: "copy-failed",
308
- projectionFiles: [],
309
- error: error instanceof Error ? error.message : String(error)
310
- };
311
- }
312
- for (const obj of SEED_OBJECTS) {
313
- const objDir = path.join(workspaceDir, obj.name);
314
- mkdirSync(objDir, { recursive: true });
315
- writeFileSync(path.join(objDir, ".object.yaml"), generateObjectYaml(obj), "utf-8");
316
- }
317
- writeIfMissing(path.join(workspaceDir, "WORKSPACE.md"), generateWorkspaceMd(SEED_OBJECTS));
318
- return {
319
- workspaceDir,
320
- dbPath,
321
- seedDbPath,
322
- seeded: true,
323
- reason: "seeded",
324
- projectionFiles
325
- };
326
- }
327
-
328
- //#endregion
329
- //#region src/cli/bootstrap-external.ts
330
- const DEFAULT_DENCHCLAW_PROFILE = "dench";
331
- const DENCHCLAW_STATE_DIRNAME = ".openclaw-dench";
332
- const DEFAULT_GATEWAY_PORT = 18789;
333
- const DENCHCLAW_GATEWAY_PORT_START = 19001;
334
- const MAX_PORT_SCAN_ATTEMPTS = 100;
335
- const DEFAULT_WEB_APP_PORT = 3100;
336
- const WEB_APP_PROBE_ATTEMPTS = 20;
337
- const WEB_APP_PROBE_DELAY_MS = 750;
338
- const DEFAULT_BOOTSTRAP_ROLLOUT_STAGE = "default";
339
- const DEFAULT_GATEWAY_LAUNCH_AGENT_LABEL = "ai.openclaw.gateway";
340
- const REQUIRED_TOOLS_PROFILE = "full";
341
- function resolveCommandForPlatform(command) {
342
- if (process$1.platform !== "win32") return command;
343
- if (path.extname(command)) return command;
344
- const normalized = path.basename(command).toLowerCase();
345
- if (normalized === "npm" || normalized === "pnpm" || normalized === "npx" || normalized === "yarn") return `${command}.cmd`;
346
- return command;
347
- }
348
- async function runCommandWithTimeout(argv, options) {
349
- const [command, ...args] = argv;
350
- if (!command) return {
351
- code: 1,
352
- stdout: "",
353
- stderr: "missing command"
354
- };
355
- const stdio = options.ioMode === "inherit" ? "inherit" : [
356
- "ignore",
357
- "pipe",
358
- "pipe"
359
- ];
360
- return await new Promise((resolve, reject) => {
361
- const child = spawn(resolveCommandForPlatform(command), args, {
362
- cwd: options.cwd,
363
- env: options.env ? {
364
- ...process$1.env,
365
- ...options.env
366
- } : process$1.env,
367
- stdio
368
- });
369
- let stdout = "";
370
- let stderr = "";
371
- let settled = false;
372
- const timer = setTimeout(() => {
373
- if (settled) return;
374
- child.kill("SIGKILL");
375
- }, options.timeoutMs);
376
- child.stdout?.on("data", (chunk) => {
377
- stdout += String(chunk);
378
- });
379
- child.stderr?.on("data", (chunk) => {
380
- stderr += String(chunk);
381
- });
382
- child.once("error", (error) => {
383
- if (settled) return;
384
- settled = true;
385
- clearTimeout(timer);
386
- reject(error);
387
- });
388
- child.once("close", (code) => {
389
- if (settled) return;
390
- settled = true;
391
- clearTimeout(timer);
392
- resolve({
393
- code: typeof code === "number" ? code : 1,
394
- stdout,
395
- stderr
396
- });
397
- });
398
- });
399
- }
400
- function parseOptionalPort(value) {
401
- if (value === void 0) return;
402
- const raw = typeof value === "number" ? value : Number.parseInt(String(value), 10);
403
- if (!Number.isFinite(raw) || raw <= 0) return;
404
- return raw;
405
- }
406
- async function sleep(ms) {
407
- await new Promise((resolve) => setTimeout(resolve, ms));
408
- }
409
- function isPortAvailable(port) {
410
- return new Promise((resolve) => {
411
- const server = createConnection({
412
- port,
413
- host: "127.0.0.1"
414
- }, () => {
415
- server.end();
416
- resolve(false);
417
- });
418
- server.on("error", (err) => {
419
- if (err.code === "ECONNREFUSED") resolve(true);
420
- else if (err.code === "EADDRNOTAVAIL") resolve(false);
421
- else resolve(false);
422
- });
423
- server.setTimeout(1e3, () => {
424
- server.destroy();
425
- resolve(false);
426
- });
427
- });
428
- }
429
- async function findAvailablePort(startPort, maxAttempts) {
430
- for (let i = 0; i < maxAttempts; i++) {
431
- const port = startPort + i;
432
- if (await isPortAvailable(port)) return port;
433
- }
434
- }
435
- function normalizeBootstrapRolloutStage(raw) {
436
- const normalized = raw?.trim().toLowerCase();
437
- if (normalized === "internal" || normalized === "beta" || normalized === "default") return normalized;
438
- return DEFAULT_BOOTSTRAP_ROLLOUT_STAGE;
439
- }
440
- function resolveBootstrapRolloutStage(env = process$1.env) {
441
- return normalizeBootstrapRolloutStage(env.DENCHCLAW_BOOTSTRAP_ROLLOUT ?? env.OPENCLAW_BOOTSTRAP_ROLLOUT);
442
- }
443
- function isLegacyFallbackEnabled(env = process$1.env) {
444
- return isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_LEGACY_FALLBACK) || isTruthyEnvValue(env.OPENCLAW_BOOTSTRAP_LEGACY_FALLBACK);
445
- }
446
- function normalizeVersionOutput(raw) {
447
- const first = raw?.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
448
- return first && first.length > 0 ? first : void 0;
449
- }
450
- function firstNonEmptyLine(...values) {
451
- for (const value of values) {
452
- const first = value?.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
453
- if (first) return first;
454
- }
455
- }
456
- function resolveProfileStateDir(profile, env = process$1.env) {
457
- const home = resolveRequiredHomeDir(env, os.homedir);
458
- return path.join(home, DENCHCLAW_STATE_DIRNAME);
459
- }
460
- function resolveGatewayLaunchAgentLabel(profile) {
461
- const normalized = profile.trim().toLowerCase();
462
- if (!normalized || normalized === "default") return DEFAULT_GATEWAY_LAUNCH_AGENT_LABEL;
463
- return `ai.openclaw.${normalized}`;
464
- }
465
- async function ensureGatewayModeLocal(openclawCommand, profile) {
466
- if ((await runOpenClaw(openclawCommand, [
467
- "--profile",
468
- profile,
469
- "config",
470
- "get",
471
- "gateway.mode"
472
- ], 1e4)).stdout.trim() === "local") return;
473
- await runOpenClawOrThrow({
474
- openclawCommand,
475
- args: [
476
- "--profile",
477
- profile,
478
- "config",
479
- "set",
480
- "gateway.mode",
481
- "local"
482
- ],
483
- timeoutMs: 1e4,
484
- errorMessage: "Failed to set gateway.mode=local."
485
- });
486
- }
487
- async function ensureGatewayPort(openclawCommand, profile, gatewayPort) {
488
- await runOpenClawOrThrow({
489
- openclawCommand,
490
- args: [
491
- "--profile",
492
- profile,
493
- "config",
494
- "set",
495
- "gateway.port",
496
- String(gatewayPort)
497
- ],
498
- timeoutMs: 1e4,
499
- errorMessage: `Failed to set gateway.port=${gatewayPort}.`
500
- });
501
- }
502
- async function ensureDefaultWorkspacePath(openclawCommand, profile, workspaceDir) {
503
- await runOpenClawOrThrow({
504
- openclawCommand,
505
- args: [
506
- "--profile",
507
- profile,
508
- "config",
509
- "set",
510
- "agents.defaults.workspace",
511
- workspaceDir
512
- ],
513
- timeoutMs: 1e4,
514
- errorMessage: `Failed to set agents.defaults.workspace=${workspaceDir}.`
515
- });
516
- }
517
- async function ensureSubagentDefaults(openclawCommand, profile) {
518
- for (const [key, value] of [
519
- ["agents.defaults.subagents.maxConcurrent", "8"],
520
- ["agents.defaults.subagents.maxSpawnDepth", "2"],
521
- ["agents.defaults.subagents.maxChildrenPerAgent", "10"],
522
- ["agents.defaults.subagents.archiveAfterMinutes", "180"],
523
- ["agents.defaults.subagents.runTimeoutSeconds", "0"],
524
- ["tools.subagents.tools.deny", "[]"]
525
- ]) await runOpenClawOrThrow({
526
- openclawCommand,
527
- args: [
528
- "--profile",
529
- profile,
530
- "config",
531
- "set",
532
- key,
533
- value
534
- ],
535
- timeoutMs: 1e4,
536
- errorMessage: `Failed to set ${key}=${value}.`
537
- });
538
- }
539
- async function ensureToolsProfile(openclawCommand, profile) {
540
- await runOpenClawOrThrow({
541
- openclawCommand,
542
- args: [
543
- "--profile",
544
- profile,
545
- "config",
546
- "set",
547
- "tools.profile",
548
- REQUIRED_TOOLS_PROFILE
549
- ],
550
- timeoutMs: 1e4,
551
- errorMessage: `Failed to set tools.profile=${REQUIRED_TOOLS_PROFILE}.`
552
- });
553
- }
554
- async function probeForWebApp(port) {
555
- const controller = new AbortController();
556
- const timer = setTimeout(() => controller.abort(), 1500);
557
- try {
558
- const response = await fetch(`http://127.0.0.1:${port}/api/profiles`, {
559
- method: "GET",
560
- signal: controller.signal,
561
- redirect: "manual"
562
- });
563
- if (response.status < 200 || response.status >= 400) return false;
564
- const payload = await response.json().catch(() => null);
565
- return Boolean(payload && typeof payload === "object" && Array.isArray(payload.profiles) && typeof payload.activeProfile === "string");
566
- } catch {
567
- return false;
568
- } finally {
569
- clearTimeout(timer);
570
- }
571
- }
572
- async function waitForWebApp(preferredPort) {
573
- for (let attempt = 0; attempt < WEB_APP_PROBE_ATTEMPTS; attempt += 1) {
574
- if (await probeForWebApp(preferredPort)) return true;
575
- await sleep(WEB_APP_PROBE_DELAY_MS);
576
- }
577
- return false;
578
- }
579
- function resolveCliPackageRoot() {
580
- let dir = path.dirname(fileURLToPath(import.meta.url));
581
- for (let i = 0; i < 5; i++) {
582
- if (existsSync(path.join(dir, "package.json"))) return dir;
583
- dir = path.dirname(dir);
584
- }
585
- return process$1.cwd();
586
- }
587
- /**
588
- * Spawn the pre-built standalone Next.js server as a detached background
589
- * process if it isn't already running on the target port.
590
- */
591
- function startWebAppIfNeeded(port, stateDir, gatewayPort) {
592
- const pkgRoot = resolveCliPackageRoot();
593
- const standaloneServer = path.join(pkgRoot, "apps/web/.next/standalone/apps/web/server.js");
594
- if (!existsSync(standaloneServer)) return;
595
- const logDir = path.join(stateDir, "logs");
596
- mkdirSync(logDir, { recursive: true });
597
- const outFd = openSync(path.join(logDir, "web-app.log"), "a");
598
- const errFd = openSync(path.join(logDir, "web-app.err.log"), "a");
599
- spawn(process$1.execPath, [standaloneServer], {
600
- cwd: path.dirname(standaloneServer),
601
- detached: true,
602
- stdio: [
603
- "ignore",
604
- outFd,
605
- errFd
606
- ],
607
- env: {
608
- ...process$1.env,
609
- PORT: String(port),
610
- HOSTNAME: "127.0.0.1",
611
- OPENCLAW_GATEWAY_PORT: String(gatewayPort)
612
- }
613
- }).unref();
614
- }
615
- async function runOpenClaw(openclawCommand, args, timeoutMs, ioMode = "capture", env) {
616
- return await runCommandWithTimeout([openclawCommand, ...args], {
617
- timeoutMs,
618
- ioMode,
619
- env
620
- });
621
- }
622
- async function runOpenClawOrThrow(params) {
623
- const result = await runOpenClaw(params.openclawCommand, params.args, params.timeoutMs);
624
- if (result.code === 0) return result;
625
- const detail = firstNonEmptyLine(result.stderr, result.stdout);
626
- throw new Error(detail ? `${params.errorMessage}\n${detail}` : params.errorMessage);
627
- }
628
- /**
629
- * Runs an OpenClaw command attached to the current terminal.
630
- * Use this for interactive flows like `openclaw onboard`.
631
- */
632
- async function runOpenClawInteractiveOrThrow(params) {
633
- const result = await runOpenClaw(params.openclawCommand, params.args, params.timeoutMs, "inherit");
634
- if (result.code === 0) return result;
635
- const detail = firstNonEmptyLine(result.stderr, result.stdout);
636
- throw new Error(detail ? `${params.errorMessage}\n${detail}` : params.errorMessage);
637
- }
638
- /**
639
- * Runs an openclaw sub-command with a visible spinner that streams progress
640
- * from the subprocess stdout/stderr into the spinner message.
641
- */
642
- async function runOpenClawWithProgress(params) {
643
- const s = spinner();
644
- s.start(params.startMessage);
645
- const result = await new Promise((resolve, reject) => {
646
- const child = spawn(resolveCommandForPlatform(params.openclawCommand), params.args, { stdio: [
647
- "ignore",
648
- "pipe",
649
- "pipe"
650
- ] });
651
- let stdout = "";
652
- let stderr = "";
653
- let settled = false;
654
- const timer = setTimeout(() => {
655
- if (!settled) child.kill("SIGKILL");
656
- }, params.timeoutMs);
657
- const updateSpinner = (chunk) => {
658
- const line = chunk.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).pop();
659
- if (line) s.message(line.length > 72 ? `${line.slice(0, 69)}...` : line);
660
- };
661
- child.stdout?.on("data", (chunk) => {
662
- const text = String(chunk);
663
- stdout += text;
664
- updateSpinner(text);
665
- });
666
- child.stderr?.on("data", (chunk) => {
667
- const text = String(chunk);
668
- stderr += text;
669
- updateSpinner(text);
670
- });
671
- child.once("error", (error) => {
672
- if (settled) return;
673
- settled = true;
674
- clearTimeout(timer);
675
- reject(error);
676
- });
677
- child.once("close", (code) => {
678
- if (settled) return;
679
- settled = true;
680
- clearTimeout(timer);
681
- resolve({
682
- code: typeof code === "number" ? code : 1,
683
- stdout,
684
- stderr
685
- });
686
- });
687
- });
688
- if (result.code === 0) {
689
- s.stop(params.successMessage);
690
- return result;
691
- }
692
- const detail = firstNonEmptyLine(result.stderr, result.stdout);
693
- const stopMessage = detail ? `${params.errorMessage}: ${detail}` : params.errorMessage;
694
- s.stop(stopMessage);
695
- throw new Error(detail ? `${params.errorMessage}\n${detail}` : params.errorMessage);
696
- }
697
- function parseJsonPayload(raw) {
698
- if (!raw) return;
699
- const trimmed = raw.trim();
700
- if (!trimmed) return;
701
- try {
702
- const parsed = JSON.parse(trimmed);
703
- return parsed && typeof parsed === "object" ? parsed : void 0;
704
- } catch {
705
- const start = trimmed.indexOf("{");
706
- const end = trimmed.lastIndexOf("}");
707
- if (start === -1 || end <= start) return;
708
- try {
709
- const parsed = JSON.parse(trimmed.slice(start, end + 1));
710
- return parsed && typeof parsed === "object" ? parsed : void 0;
711
- } catch {
712
- return;
713
- }
714
- }
715
- }
716
- async function detectGlobalOpenClawInstall() {
717
- const result = await runCommandWithTimeout([
718
- "npm",
719
- "ls",
720
- "-g",
721
- "openclaw",
722
- "--depth=0",
723
- "--json",
724
- "--silent"
725
- ], { timeoutMs: 15e3 }).catch(() => null);
726
- const installedVersion = (parseJsonPayload(result?.stdout ?? result?.stderr)?.dependencies)?.openclaw?.version;
727
- if (typeof installedVersion === "string" && installedVersion.length > 0) return {
728
- installed: true,
729
- version: installedVersion
730
- };
731
- return { installed: false };
732
- }
733
- async function resolveNpmGlobalBinDir() {
734
- const result = await runCommandWithTimeout([
735
- "npm",
736
- "prefix",
737
- "-g"
738
- ], { timeoutMs: 8e3 }).catch(() => null);
739
- if (!result || result.code !== 0) return;
740
- const prefix = firstNonEmptyLine(result.stdout);
741
- if (!prefix) return;
742
- return process$1.platform === "win32" ? prefix : path.join(prefix, "bin");
743
- }
744
- function resolveGlobalOpenClawCommand(globalBinDir) {
745
- if (!globalBinDir) return;
746
- return (process$1.platform === "win32" ? [path.join(globalBinDir, "openclaw.cmd"), path.join(globalBinDir, "openclaw.exe")] : [path.join(globalBinDir, "openclaw")]).find((candidate) => existsSync(candidate));
747
- }
748
- async function resolveShellOpenClawPath() {
749
- const result = await runCommandWithTimeout([process$1.platform === "win32" ? "where" : "which", "openclaw"], { timeoutMs: 4e3 }).catch(() => null);
750
- if (!result || result.code !== 0) return;
751
- return firstNonEmptyLine(result.stdout);
752
- }
753
- function isProjectLocalOpenClawPath(commandPath) {
754
- if (!commandPath) return false;
755
- return commandPath.replaceAll("\\", "/").includes("/node_modules/.bin/openclaw");
756
- }
757
- async function ensureOpenClawCliAvailable() {
758
- const globalBefore = await detectGlobalOpenClawInstall();
759
- let installed = false;
760
- if (!globalBefore.installed) {
761
- const install = await runCommandWithTimeout([
762
- "npm",
763
- "install",
764
- "-g",
765
- "openclaw@latest"
766
- ], { timeoutMs: 10 * 6e4 }).catch(() => null);
767
- if (!install || install.code !== 0) return {
768
- available: false,
769
- installed: false,
770
- version: void 0,
771
- command: "openclaw"
772
- };
773
- installed = true;
774
- }
775
- const globalAfter = installed ? await detectGlobalOpenClawInstall() : globalBefore;
776
- const globalBinDir = await resolveNpmGlobalBinDir();
777
- const command = resolveGlobalOpenClawCommand(globalBinDir) ?? "openclaw";
778
- const check = await runOpenClaw(command, ["--version"], 4e3).catch(() => null);
779
- const shellCommandPath = await resolveShellOpenClawPath();
780
- const version = normalizeVersionOutput(check?.stdout || check?.stderr || globalAfter.version);
781
- return {
782
- available: Boolean(globalAfter.installed && check && check.code === 0),
783
- installed,
784
- version,
785
- command,
786
- globalBinDir,
787
- shellCommandPath
788
- };
789
- }
790
- async function probeGateway(openclawCommand, profile, gatewayPort) {
791
- const result = await runOpenClaw(openclawCommand, [
792
- "--profile",
793
- profile,
794
- "health",
795
- "--json"
796
- ], 12e3, "capture", gatewayPort ? { OPENCLAW_GATEWAY_PORT: String(gatewayPort) } : void 0).catch((error) => {
797
- return {
798
- code: 1,
799
- stdout: "",
800
- stderr: error instanceof Error ? error.message : String(error)
801
- };
802
- });
803
- if (result.code === 0) return { ok: true };
804
- return {
805
- ok: false,
806
- detail: firstNonEmptyLine(result.stderr, result.stdout)
807
- };
808
- }
809
- function readLogTail(logPath, maxLines = 16) {
810
- if (!existsSync(logPath)) return;
811
- try {
812
- const lines = readFileSync(logPath, "utf-8").split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
813
- if (lines.length === 0) return;
814
- return lines.slice(-maxLines).join("\n");
815
- } catch {
816
- return;
817
- }
818
- }
819
- function resolveLatestRuntimeLogPath() {
820
- const runtimeLogDir = "/tmp/openclaw";
821
- if (!existsSync(runtimeLogDir)) return;
822
- try {
823
- const files = readdirSync(runtimeLogDir).filter((name) => /^openclaw-.*\.log$/u.test(name)).toSorted((a, b) => b.localeCompare(a));
824
- if (files.length === 0) return;
825
- return path.join(runtimeLogDir, files[0]);
826
- } catch {
827
- return;
828
- }
829
- }
830
- function collectGatewayLogExcerpts(stateDir) {
831
- const candidates = [
832
- path.join(stateDir, "logs", "gateway.err.log"),
833
- path.join(stateDir, "logs", "gateway.log"),
834
- resolveLatestRuntimeLogPath()
835
- ].filter((candidate) => Boolean(candidate));
836
- const excerpts = [];
837
- for (const candidate of candidates) {
838
- const excerpt = readLogTail(candidate);
839
- if (!excerpt) continue;
840
- excerpts.push({
841
- path: candidate,
842
- excerpt
843
- });
844
- }
845
- return excerpts;
846
- }
847
- function deriveGatewayFailureSummary(probeDetail, excerpts) {
848
- const combinedLines = excerpts.flatMap((entry) => entry.excerpt.split(/\r?\n/));
849
- 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;
850
- const likely = [...combinedLines].toReversed().find((line) => signalRegex.test(line));
851
- if (likely) return likely.length > 220 ? `${likely.slice(0, 217)}...` : likely;
852
- return probeDetail;
853
- }
854
- async function attemptGatewayAutoFix(params) {
855
- const steps = [];
856
- const commands = [
857
- {
858
- name: "openclaw gateway stop",
859
- args: [
860
- "--profile",
861
- params.profile,
862
- "gateway",
863
- "stop"
864
- ],
865
- timeoutMs: 9e4
866
- },
867
- {
868
- name: "openclaw doctor --fix",
869
- args: [
870
- "--profile",
871
- params.profile,
872
- "doctor",
873
- "--fix"
874
- ],
875
- timeoutMs: 2 * 6e4
876
- },
877
- {
878
- name: "openclaw gateway install --force",
879
- args: [
880
- "--profile",
881
- params.profile,
882
- "gateway",
883
- "install",
884
- "--force",
885
- "--port",
886
- String(params.gatewayPort)
887
- ],
888
- timeoutMs: 2 * 6e4
889
- },
890
- {
891
- name: "openclaw gateway start",
892
- args: [
893
- "--profile",
894
- params.profile,
895
- "gateway",
896
- "start",
897
- "--port",
898
- String(params.gatewayPort)
899
- ],
900
- timeoutMs: 2 * 6e4
901
- }
902
- ];
903
- for (const command of commands) {
904
- const result = await runOpenClaw(params.openclawCommand, command.args, command.timeoutMs).catch((error) => {
905
- return {
906
- code: 1,
907
- stdout: "",
908
- stderr: error instanceof Error ? error.message : String(error)
909
- };
910
- });
911
- steps.push({
912
- name: command.name,
913
- ok: result.code === 0,
914
- detail: result.code === 0 ? void 0 : firstNonEmptyLine(result.stderr, result.stdout)
915
- });
916
- }
917
- let finalProbe = await probeGateway(params.openclawCommand, params.profile, params.gatewayPort);
918
- for (let attempt = 0; attempt < 2 && !finalProbe.ok; attempt += 1) {
919
- await sleep(1200);
920
- finalProbe = await probeGateway(params.openclawCommand, params.profile, params.gatewayPort);
921
- }
922
- const logExcerpts = finalProbe.ok ? [] : collectGatewayLogExcerpts(params.stateDir);
923
- const failureSummary = finalProbe.ok ? void 0 : deriveGatewayFailureSummary(finalProbe.detail, logExcerpts);
924
- return {
925
- attempted: true,
926
- recovered: finalProbe.ok,
927
- steps,
928
- finalProbe,
929
- failureSummary,
930
- logExcerpts
931
- };
932
- }
933
- async function openUrl(url) {
934
- const result = await runCommandWithTimeout(process$1.platform === "darwin" ? ["open", url] : process$1.platform === "win32" ? [
935
- "cmd",
936
- "/c",
937
- "start",
938
- "",
939
- url
940
- ] : ["xdg-open", url], { timeoutMs: 5e3 }).catch(() => null);
941
- return Boolean(result && result.code === 0);
942
- }
943
- function remediationForGatewayFailure(detail, port, profile) {
944
- const normalized = detail?.toLowerCase() ?? "";
945
- 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(" ");
946
- if (normalized.includes("unauthorized") || normalized.includes("token") || normalized.includes("password")) return `Gateway auth mismatch detected. Re-run \`openclaw --profile ${profile} onboard --install-daemon --reset\`.`;
947
- 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>\`.`;
948
- return `Run \`openclaw --profile ${profile} doctor --fix\` and retry \`denchclaw bootstrap --profile ${profile} --force-onboard\`.`;
949
- }
950
- function remediationForWebUiFailure(port) {
951
- return `Web UI did not respond on ${port}. Ensure the apps/web directory exists and rerun with \`denchclaw bootstrap --web-port <port>\` if needed.`;
952
- }
953
- function describeWorkspaceSeedResult(result) {
954
- if (result.seeded) return `seeded ${result.dbPath}`;
955
- if (result.reason === "already-exists") return `skipped; existing database found at ${result.dbPath}`;
956
- if (result.reason === "seed-asset-missing") return `skipped; seed asset missing at ${result.seedDbPath}`;
957
- if (result.reason === "copy-failed") return `failed to copy seed database: ${result.error ?? "unknown error"}`;
958
- return `skipped; reason=${result.reason}`;
959
- }
960
- function createCheck(id, status, detail, remediation) {
961
- return {
962
- id,
963
- status,
964
- detail,
965
- remediation
966
- };
967
- }
968
- /**
969
- * Load OpenClaw profile config from state dir.
970
- * Supports both openclaw.json (current) and config.json (legacy).
971
- */
972
- function readBootstrapConfig(stateDir) {
973
- for (const name of ["openclaw.json", "config.json"]) {
974
- const configPath = path.join(stateDir, name);
975
- if (!existsSync(configPath)) continue;
976
- try {
977
- const raw = JSON.parse(readFileSync(configPath, "utf-8"));
978
- if (raw && typeof raw === "object") return raw;
979
- } catch {}
980
- }
981
- }
982
- function resolveBootstrapWorkspaceDir(stateDir) {
983
- return path.join(stateDir, "workspace");
984
- }
985
- /**
986
- * Resolve the model provider prefix from the config's primary model string.
987
- * e.g. "vercel-ai-gateway/anthropic/claude-opus-4.6" → "vercel-ai-gateway"
988
- */
989
- function resolveModelProvider(stateDir) {
990
- const model = readBootstrapConfig(stateDir)?.agents?.defaults?.model;
991
- const modelName = typeof model === "string" ? model : model?.primary;
992
- if (typeof modelName === "string" && modelName.includes("/")) return modelName.split("/")[0];
993
- }
994
- /**
995
- * Check if the agent auth store has at least one key for the given provider.
996
- */
997
- function checkAgentAuth(stateDir, provider) {
998
- if (!provider) return {
999
- ok: false,
1000
- detail: "No model provider configured."
1001
- };
1002
- const authPath = path.join(stateDir, "agents", "main", "agent", "auth-profiles.json");
1003
- if (!existsSync(authPath)) return {
1004
- ok: false,
1005
- provider,
1006
- detail: `No auth-profiles.json found for agent (expected at ${authPath}).`
1007
- };
1008
- try {
1009
- const profiles = JSON.parse(readFileSync(authPath, "utf-8"))?.profiles;
1010
- if (!profiles || typeof profiles !== "object") return {
1011
- ok: false,
1012
- provider,
1013
- detail: `auth-profiles.json has no profiles configured.`
1014
- };
1015
- if (!Object.values(profiles).some((p) => p && typeof p === "object" && p.provider === provider && typeof p.key === "string" && p.key.length > 0)) return {
1016
- ok: false,
1017
- provider,
1018
- detail: `No API key for provider "${provider}" in agent auth store.`
1019
- };
1020
- return {
1021
- ok: true,
1022
- provider,
1023
- detail: `API key configured for ${provider}.`
1024
- };
1025
- } catch {
1026
- return {
1027
- ok: false,
1028
- provider,
1029
- detail: `Failed to read auth-profiles.json.`
1030
- };
1031
- }
1032
- }
1033
- function buildBootstrapDiagnostics(params) {
1034
- const env = params.env ?? process$1.env;
1035
- const checks = [];
1036
- if (params.openClawCliAvailable) checks.push(createCheck("openclaw-cli", "pass", `OpenClaw CLI detected${params.openClawVersion ? ` (${params.openClawVersion})` : ""}.`));
1037
- else checks.push(createCheck("openclaw-cli", "fail", "OpenClaw CLI is missing.", "Install OpenClaw globally once: `npm install -g openclaw`."));
1038
- if (params.profile === DEFAULT_DENCHCLAW_PROFILE) checks.push(createCheck("profile", "pass", `Profile pinned: ${params.profile}.`));
1039
- 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.`));
1040
- if (params.gatewayProbe.ok) checks.push(createCheck("gateway", "pass", `Gateway reachable at ${params.gatewayUrl}.`));
1041
- 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)));
1042
- const stateDir = params.stateDir ?? resolveProfileStateDir(params.profile, env);
1043
- const authCheck = checkAgentAuth(stateDir, resolveModelProvider(stateDir));
1044
- if (authCheck.ok) checks.push(createCheck("agent-auth", "pass", authCheck.detail));
1045
- else checks.push(createCheck("agent-auth", "fail", authCheck.detail, `Run \`openclaw --profile ${DEFAULT_DENCHCLAW_PROFILE} onboard --install-daemon\` to configure API keys.`));
1046
- if (params.webReachable) checks.push(createCheck("web-ui", "pass", `Web UI reachable on port ${params.webPort}.`));
1047
- else checks.push(createCheck("web-ui", "fail", `Web UI is not reachable on port ${params.webPort}.`, remediationForWebUiFailure(params.webPort)));
1048
- const expectedStateDir = resolveProfileStateDir(DEFAULT_DENCHCLAW_PROFILE, env);
1049
- if (path.resolve(stateDir) === path.resolve(expectedStateDir)) checks.push(createCheck("state-isolation", "pass", `State dir pinned: ${stateDir}.`));
1050
- else checks.push(createCheck("state-isolation", "fail", `Unexpected state dir: ${stateDir}.`, `DenchClaw requires \`${expectedStateDir}\`. Re-run bootstrap to restore pinned defaults.`));
1051
- const launchAgentLabel = resolveGatewayLaunchAgentLabel(params.profile);
1052
- const expectedLaunchAgentLabel = resolveGatewayLaunchAgentLabel(DEFAULT_DENCHCLAW_PROFILE);
1053
- if (launchAgentLabel === expectedLaunchAgentLabel) checks.push(createCheck("daemon-label", "pass", `Gateway service label: ${launchAgentLabel}.`));
1054
- else checks.push(createCheck("daemon-label", "fail", `Gateway service label mismatch (${launchAgentLabel}).`, `DenchClaw requires launch agent label ${expectedLaunchAgentLabel}.`));
1055
- 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));
1056
- const migrationSuiteOk = isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_MIGRATION_SUITE_OK);
1057
- const onboardingE2EOk = isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_ONBOARDING_E2E_OK);
1058
- const enforceCutoverGates = isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_ENFORCE_SAFETY_GATES);
1059
- const cutoverGatePassed = migrationSuiteOk && onboardingE2EOk;
1060
- 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."));
1061
- return {
1062
- rolloutStage: params.rolloutStage,
1063
- legacyFallbackEnabled: params.legacyFallbackEnabled,
1064
- checks,
1065
- hasFailures: checks.some((check) => check.status === "fail")
1066
- };
1067
- }
1068
- function formatCheckStatus(status) {
1069
- if (status === "pass") return theme.success("[ok]");
1070
- if (status === "warn") return theme.warn("[warn]");
1071
- return theme.error("[fail]");
1072
- }
1073
- function logBootstrapChecklist(diagnostics, runtime) {
1074
- runtime.log("");
1075
- runtime.log(theme.heading("Bootstrap checklist"));
1076
- for (const check of diagnostics.checks) {
1077
- runtime.log(`${formatCheckStatus(check.status)} ${check.detail}`);
1078
- if (check.status !== "pass" && check.remediation) runtime.log(theme.muted(` remediation: ${check.remediation}`));
1079
- }
1080
- }
1081
- async function shouldRunUpdate(params) {
1082
- if (params.opts.updateNow) return true;
1083
- if (params.opts.skipUpdate || params.opts.nonInteractive || params.opts.json || !process$1.stdin.isTTY) return false;
1084
- const decision = await confirm({
1085
- message: stylePromptMessage("Check and install OpenClaw updates now?"),
1086
- initialValue: false
1087
- });
1088
- if (isCancel(decision)) {
1089
- params.runtime.log(theme.muted("Update check skipped."));
1090
- return false;
1091
- }
1092
- return Boolean(decision);
1093
- }
1094
- async function bootstrapCommand(opts, runtime = defaultRuntime) {
1095
- const nonInteractive = Boolean(opts.nonInteractive || opts.json);
1096
- const rolloutStage = resolveBootstrapRolloutStage();
1097
- const legacyFallbackEnabled = isLegacyFallbackEnabled();
1098
- const appliedProfile = applyCliProfileEnv({ profile: opts.profile });
1099
- const profile = appliedProfile.effectiveProfile;
1100
- if (appliedProfile.warning && !opts.json) runtime.log(theme.warn(appliedProfile.warning));
1101
- const installResult = await ensureOpenClawCliAvailable();
1102
- if (!installResult.available) throw new Error([
1103
- "OpenClaw CLI is required but unavailable.",
1104
- "Install it with: npm install -g openclaw",
1105
- installResult.globalBinDir ? `Expected global binary directory: ${installResult.globalBinDir}` : ""
1106
- ].filter((line) => line.length > 0).join("\n"));
1107
- const openclawCommand = installResult.command;
1108
- if (await shouldRunUpdate({
1109
- opts,
1110
- runtime
1111
- })) await runOpenClawWithProgress({
1112
- openclawCommand,
1113
- args: ["update", "--yes"],
1114
- timeoutMs: 8 * 6e4,
1115
- startMessage: "Checking for OpenClaw updates...",
1116
- successMessage: "OpenClaw is up to date.",
1117
- errorMessage: "OpenClaw update failed"
1118
- });
1119
- const explicitPort = parseOptionalPort(opts.gatewayPort);
1120
- let gatewayPort;
1121
- let portAutoAssigned = false;
1122
- if (explicitPort) gatewayPort = explicitPort;
1123
- else if (await isPortAvailable(DEFAULT_GATEWAY_PORT)) gatewayPort = DEFAULT_GATEWAY_PORT;
1124
- else {
1125
- const availablePort = await findAvailablePort(DENCHCLAW_GATEWAY_PORT_START, MAX_PORT_SCAN_ATTEMPTS);
1126
- 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.`);
1127
- gatewayPort = availablePort;
1128
- portAutoAssigned = true;
1129
- }
1130
- const stateDir = resolveProfileStateDir(profile);
1131
- const workspaceDir = resolveBootstrapWorkspaceDir(stateDir);
1132
- if (portAutoAssigned && !opts.json) runtime.log(theme.muted(`Default gateway port ${DEFAULT_GATEWAY_PORT} is in use. Using auto-assigned port ${gatewayPort}.`));
1133
- await ensureDefaultWorkspacePath(openclawCommand, profile, workspaceDir);
1134
- const onboardArgv = [
1135
- "--profile",
1136
- profile,
1137
- "onboard",
1138
- "--install-daemon",
1139
- "--gateway-bind",
1140
- "loopback",
1141
- "--gateway-port",
1142
- String(gatewayPort)
1143
- ];
1144
- if (opts.forceOnboard) onboardArgv.push("--reset");
1145
- if (nonInteractive) onboardArgv.push("--non-interactive", "--accept-risk");
1146
- if (opts.noOpen) onboardArgv.push("--skip-ui");
1147
- if (nonInteractive) await runOpenClawOrThrow({
1148
- openclawCommand,
1149
- args: onboardArgv,
1150
- timeoutMs: 12 * 6e4,
1151
- errorMessage: "OpenClaw onboarding failed."
1152
- });
1153
- else await runOpenClawInteractiveOrThrow({
1154
- openclawCommand,
1155
- args: onboardArgv,
1156
- timeoutMs: 12 * 6e4,
1157
- errorMessage: "OpenClaw onboarding failed."
1158
- });
1159
- const workspaceSeed = seedWorkspaceFromAssets({
1160
- workspaceDir,
1161
- packageRoot: resolveCliPackageRoot()
1162
- });
1163
- await ensureGatewayModeLocal(openclawCommand, profile);
1164
- await ensureGatewayPort(openclawCommand, profile, gatewayPort);
1165
- await ensureToolsProfile(openclawCommand, profile);
1166
- await ensureSubagentDefaults(openclawCommand, profile);
1167
- let gatewayProbe = await probeGateway(openclawCommand, profile, gatewayPort);
1168
- let gatewayAutoFix;
1169
- if (!gatewayProbe.ok) {
1170
- gatewayAutoFix = await attemptGatewayAutoFix({
1171
- openclawCommand,
1172
- profile,
1173
- stateDir,
1174
- gatewayPort
1175
- });
1176
- gatewayProbe = gatewayAutoFix.finalProbe;
1177
- if (!gatewayProbe.ok && gatewayAutoFix.failureSummary) gatewayProbe = {
1178
- ...gatewayProbe,
1179
- detail: [gatewayProbe.detail, gatewayAutoFix.failureSummary].filter((value, index, self) => value && self.indexOf(value) === index).join(" | ")
1180
- };
1181
- }
1182
- const gatewayUrl = `ws://127.0.0.1:${gatewayPort}`;
1183
- const preferredWebPort = parseOptionalPort(opts.webPort) ?? DEFAULT_WEB_APP_PORT;
1184
- if (!await probeForWebApp(preferredWebPort)) startWebAppIfNeeded(preferredWebPort, stateDir, gatewayPort);
1185
- const webReachable = await waitForWebApp(preferredWebPort);
1186
- const webUrl = `http://localhost:${preferredWebPort}`;
1187
- const diagnostics = buildBootstrapDiagnostics({
1188
- profile,
1189
- openClawCliAvailable: installResult.available,
1190
- openClawVersion: installResult.version,
1191
- gatewayPort,
1192
- gatewayUrl,
1193
- gatewayProbe,
1194
- webPort: preferredWebPort,
1195
- webReachable,
1196
- rolloutStage,
1197
- legacyFallbackEnabled,
1198
- stateDir
1199
- });
1200
- const shouldOpen = !opts.noOpen && !opts.json;
1201
- const opened = shouldOpen ? await openUrl(webUrl) : false;
1202
- if (!opts.json) {
1203
- if (installResult.installed) runtime.log(theme.muted("Installed global OpenClaw CLI via npm."));
1204
- if (isProjectLocalOpenClawPath(installResult.shellCommandPath)) {
1205
- runtime.log(theme.warn(`\`openclaw\` currently resolves to a project-local binary (${installResult.shellCommandPath}).`));
1206
- runtime.log(theme.muted(`Bootstrap now uses the global binary (${openclawCommand}) to avoid repo-local drift.`));
1207
- } else if (!installResult.shellCommandPath && installResult.globalBinDir) {
1208
- runtime.log(theme.warn("Global OpenClaw was installed, but `openclaw` is not on shell PATH."));
1209
- runtime.log(theme.muted(`Add this to your shell profile, then open a new terminal: export PATH="${installResult.globalBinDir}:$PATH"`));
1210
- }
1211
- runtime.log(theme.muted(`Workspace seed: ${describeWorkspaceSeedResult(workspaceSeed)}`));
1212
- if (gatewayAutoFix?.attempted) {
1213
- runtime.log(theme.muted(`Gateway auto-fix ${gatewayAutoFix.recovered ? "recovered connectivity" : "ran but gateway is still unhealthy"}.`));
1214
- for (const step of gatewayAutoFix.steps) runtime.log(theme.muted(` ${step.ok ? "[ok]" : "[fail]"} ${step.name}${step.detail ? ` (${step.detail})` : ""}`));
1215
- if (!gatewayAutoFix.recovered && gatewayAutoFix.failureSummary) runtime.log(theme.error(`Likely gateway cause: ${gatewayAutoFix.failureSummary}`));
1216
- if (!gatewayAutoFix.recovered && gatewayAutoFix.logExcerpts.length > 0) {
1217
- runtime.log(theme.muted("Recent gateway logs:"));
1218
- for (const excerpt of gatewayAutoFix.logExcerpts) {
1219
- runtime.log(theme.muted(` ${excerpt.path}`));
1220
- for (const line of excerpt.excerpt.split(/\r?\n/)) runtime.log(theme.muted(` ${line}`));
1221
- }
1222
- }
1223
- }
1224
- logBootstrapChecklist(diagnostics, runtime);
1225
- runtime.log("");
1226
- runtime.log(theme.heading("DenchClaw ready"));
1227
- runtime.log(`Profile: ${profile}`);
1228
- runtime.log(`OpenClaw CLI: ${installResult.version ?? "detected"}`);
1229
- runtime.log(`Gateway: ${gatewayProbe.ok ? "reachable" : "check failed"}`);
1230
- runtime.log(`Web UI: ${webUrl}`);
1231
- runtime.log(`Rollout stage: ${rolloutStage}${legacyFallbackEnabled ? " (legacy fallback enabled)" : ""}`);
1232
- if (!opened && shouldOpen) runtime.log(theme.muted("Browser open failed; copy/paste the URL above."));
1233
- if (diagnostics.hasFailures) runtime.log(theme.warn("Bootstrap completed with failing checks. Address remediation items above before full cutover."));
1234
- }
1235
- const summary = {
1236
- profile,
1237
- onboarded: true,
1238
- installedOpenClawCli: installResult.installed,
1239
- openClawCliAvailable: installResult.available,
1240
- openClawVersion: installResult.version,
1241
- gatewayUrl,
1242
- gatewayReachable: gatewayProbe.ok,
1243
- gatewayAutoFix: gatewayAutoFix ? {
1244
- attempted: gatewayAutoFix.attempted,
1245
- recovered: gatewayAutoFix.recovered,
1246
- steps: gatewayAutoFix.steps,
1247
- failureSummary: gatewayAutoFix.failureSummary,
1248
- logExcerpts: gatewayAutoFix.logExcerpts
1249
- } : void 0,
1250
- workspaceSeed,
1251
- webUrl,
1252
- webReachable,
1253
- webOpened: opened,
1254
- diagnostics
1255
- };
1256
- if (opts.json) runtime.log(JSON.stringify(summary, null, 2));
1257
- return summary;
1258
- }
1259
-
1260
- //#endregion
1261
- //#region src/logging/redact.ts
1262
- function resolveNodeRequire() {
1263
- const getBuiltinModule = process.getBuiltinModule;
1264
- if (typeof getBuiltinModule !== "function") return null;
1265
- try {
1266
- const moduleNamespace = getBuiltinModule("module");
1267
- return typeof moduleNamespace.createRequire === "function" ? moduleNamespace.createRequire : null;
1268
- } catch {
1269
- return null;
1270
- }
1271
- }
1272
- const requireConfig = resolveNodeRequire()?.(import.meta.url) ?? null;
1273
- const DEFAULT_REDACT_PATTERNS = [
1274
- String.raw`\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1`,
1275
- String.raw`"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"`,
1276
- String.raw`--(?:api[-_]?key|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1`,
1277
- String.raw`Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)`,
1278
- String.raw`\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b`,
1279
- String.raw`-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----`,
1280
- String.raw`\b(sk-[A-Za-z0-9_-]{8,})\b`,
1281
- String.raw`\b(ghp_[A-Za-z0-9]{20,})\b`,
1282
- String.raw`\b(github_pat_[A-Za-z0-9_]{20,})\b`,
1283
- String.raw`\b(xox[baprs]-[A-Za-z0-9-]{10,})\b`,
1284
- String.raw`\b(xapp-[A-Za-z0-9-]{10,})\b`,
1285
- String.raw`\b(gsk_[A-Za-z0-9_-]{10,})\b`,
1286
- String.raw`\b(AIza[0-9A-Za-z\-_]{20,})\b`,
1287
- String.raw`\b(pplx-[A-Za-z0-9_-]{10,})\b`,
1288
- String.raw`\b(npm_[A-Za-z0-9]{10,})\b`,
1289
- String.raw`\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b`,
1290
- String.raw`\b(\d{6,}:[A-Za-z0-9_-]{20,})\b`
1291
- ];
1292
-
1293
- //#endregion
1294
- //#region src/cli/cli-utils.ts
1295
- async function runCommandWithRuntime(runtime, action, onError) {
1296
- try {
1297
- await action();
1298
- } catch (err) {
1299
- if (onError) {
1300
- onError(err);
1301
- return;
1302
- }
1303
- runtime.error(String(err));
1304
- runtime.exit(1);
1305
- }
1306
- }
1307
-
1308
- //#endregion
1309
- //#region src/cli/program/register.bootstrap.ts
1310
- function registerBootstrapCommand(program) {
1311
- 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) => {
1312
- await runCommandWithRuntime(defaultRuntime, async () => {
1313
- await bootstrapCommand({
1314
- profile: opts.profile,
1315
- forceOnboard: Boolean(opts.forceOnboard),
1316
- nonInteractive: Boolean(opts.nonInteractive),
1317
- yes: Boolean(opts.yes),
1318
- skipUpdate: Boolean(opts.skipUpdate),
1319
- updateNow: Boolean(opts.updateNow),
1320
- gatewayPort: opts.gatewayPort,
1321
- webPort: opts.webPort,
1322
- noOpen: Boolean(opts.open === false),
1323
- json: Boolean(opts.json)
1324
- });
1325
- });
1326
- });
1327
- }
1328
-
1329
- //#endregion
1330
- export { registerBootstrapCommand };