openclaw-service 0.5.0 → 0.6.1

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 (72) hide show
  1. package/dist/index.js +1737 -151
  2. package/dist/index.pkg.cjs +7385 -0
  3. package/package.json +1 -1
  4. package/README.aiclaw.md +0 -25
  5. package/README.aliclaw.md +0 -25
  6. package/README.alpha-claw.md +0 -25
  7. package/README.alphaclaw.md +0 -25
  8. package/README.amazonclaw.md +0 -25
  9. package/README.amzclaw.md +0 -25
  10. package/README.anthropicclaw.md +0 -25
  11. package/README.appleclaw.md +0 -25
  12. package/README.autoopenclaw.md +0 -25
  13. package/README.awsclaw.md +0 -25
  14. package/README.bdclaw.md +0 -25
  15. package/README.blclaw.md +0 -25
  16. package/README.bytclaw.md +0 -25
  17. package/README.claw-open.md +0 -25
  18. package/README.clawjs.md +0 -25
  19. package/README.coclaw.md +0 -25
  20. package/README.copaw.md +0 -25
  21. package/README.ddclaw.md +0 -25
  22. package/README.duclaw.md +0 -25
  23. package/README.dyclaw.md +0 -25
  24. package/README.easyclaw.md +0 -25
  25. package/README.fastclaw.md +0 -25
  26. package/README.fbclaw.md +0 -25
  27. package/README.googleclaw.md +0 -25
  28. package/README.hello-claw.md +0 -25
  29. package/README.hwclaw.md +0 -25
  30. package/README.jdclaw.md +0 -25
  31. package/README.kimiclaw.md +0 -25
  32. package/README.ksclaw.md +0 -25
  33. package/README.maxclaw.md +0 -25
  34. package/README.md.bak +0 -75
  35. package/README.megaclaw.md +0 -25
  36. package/README.metaclaw.md +0 -25
  37. package/README.miclaw.md +0 -25
  38. package/README.msclaw.md +0 -25
  39. package/README.mtclaw.md +0 -25
  40. package/README.nflxclaw.md +0 -25
  41. package/README.nvdaclaw.md +0 -25
  42. package/README.open-claw.md +0 -25
  43. package/README.openaiclaw.md +0 -25
  44. package/README.openclaw-cli.md +0 -25
  45. package/README.openclaw-daemon.md +0 -25
  46. package/README.openclaw-gateway.md +0 -25
  47. package/README.openclaw-health.md +0 -25
  48. package/README.openclaw-helper.md +0 -25
  49. package/README.openclaw-install.md +0 -25
  50. package/README.openclaw-manage.md +0 -25
  51. package/README.openclaw-monitor.md +0 -25
  52. package/README.openclaw-run.md +0 -25
  53. package/README.openclaw-service.md +0 -25
  54. package/README.openclaw-setup.md +0 -25
  55. package/README.openclaw-start.md +0 -25
  56. package/README.openclaw-tools.md +0 -25
  57. package/README.openclaw-upgrade.md +0 -25
  58. package/README.openclaw-utils.md +0 -25
  59. package/README.openclaw-watch.md +0 -25
  60. package/README.pddclaw.md +0 -25
  61. package/README.qclaw-cli.md +0 -25
  62. package/README.qclaw.md +0 -25
  63. package/README.smartclaw.md +0 -25
  64. package/README.ttclaw.md +0 -25
  65. package/README.txclaw.md +0 -25
  66. package/README.uberclaw.md +0 -25
  67. package/README.volclaw.md +0 -25
  68. package/README.wxclaw.md +0 -25
  69. package/README.xclaw.md +0 -25
  70. package/README.zhclaw.md +0 -25
  71. package/dist/chunk-XSRH4RHR.js +0 -1152
  72. package/dist/server-3JMOADVR.js +0 -7
package/dist/index.js CHANGED
@@ -1,49 +1,1379 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- APP_HOME,
4
- BINARY_NAME,
5
- DISPLAY_NAME,
6
- DOCTOR_LOG_DIR,
7
- PID_FILE,
8
- RestartThrottle,
9
- STOP_FLAG_FILE,
10
- __require,
11
- checkHealth,
12
- detectOpenClaw,
13
- ensureDoctorHome,
14
- initLogger,
15
- loadConfig,
16
- log,
17
- restartGateway,
18
- runOpenClawCmd,
19
- startDashboard,
20
- startGateway,
21
- stopGateway
22
- } from "./chunk-XSRH4RHR.js";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
10
+ var __esm = (fn, res) => function __init() {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+
18
+ // src/brand.ts
19
+ import { basename, join } from "path";
20
+ import { homedir } from "os";
21
+ function toDisplayName(bin) {
22
+ if (bin === "openclaw-cli") return "OpenClaw CLI";
23
+ if (bin === "openclaw-doctor") return "OpenClaw Doctor";
24
+ return bin.split("-").map((s) => s ? s[0].toUpperCase() + s.slice(1) : s).join(" ");
25
+ }
26
+ var detectedBin, BINARY_NAME, APP_HOME, DISPLAY_NAME;
27
+ var init_brand = __esm({
28
+ "src/brand.ts"() {
29
+ "use strict";
30
+ detectedBin = basename(process.argv[1] ?? "openclaw-cli").replace(/\.[cm]?js$/, "");
31
+ BINARY_NAME = detectedBin || "openclaw-cli";
32
+ APP_HOME = join(homedir(), ".openclaw-doctor");
33
+ DISPLAY_NAME = toDisplayName(BINARY_NAME);
34
+ }
35
+ });
36
+
37
+ // src/config.ts
38
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
39
+ import { join as join2, resolve } from "path";
40
+ function ensureDoctorHome() {
41
+ if (!existsSync(DOCTOR_HOME)) {
42
+ mkdirSync(DOCTOR_HOME, { recursive: true });
43
+ }
44
+ if (!existsSync(DOCTOR_LOG_DIR)) {
45
+ mkdirSync(DOCTOR_LOG_DIR, { recursive: true });
46
+ }
47
+ }
48
+ function resolveConfigPath(configPath) {
49
+ if (configPath) return configPath;
50
+ if (existsSync(LOCAL_CONFIG)) return LOCAL_CONFIG;
51
+ return CONFIG_PATH;
52
+ }
53
+ function loadConfig(configPath) {
54
+ const file = resolveConfigPath(configPath);
55
+ if (existsSync(file)) {
56
+ const raw = JSON.parse(readFileSync(file, "utf-8"));
57
+ return {
58
+ ...defaults,
59
+ ...raw,
60
+ notify: {
61
+ webhook: { ...defaults.notify.webhook, ...raw.notify?.webhook ?? {} },
62
+ system: { ...defaults.notify.system, ...raw.notify?.system ?? {} }
63
+ }
64
+ };
65
+ }
66
+ ensureDoctorHome();
67
+ writeFileSync(CONFIG_PATH, JSON.stringify(defaults, null, 2) + "\n");
68
+ return { ...defaults };
69
+ }
70
+ var DOCTOR_HOME, CONFIG_PATH, DOCTOR_LOG_DIR, PID_FILE, STOP_FLAG_FILE, defaults, LOCAL_CONFIG;
71
+ var init_config = __esm({
72
+ "src/config.ts"() {
73
+ "use strict";
74
+ init_brand();
75
+ DOCTOR_HOME = APP_HOME;
76
+ CONFIG_PATH = join2(APP_HOME, "config.json");
77
+ DOCTOR_LOG_DIR = join2(APP_HOME, "logs");
78
+ PID_FILE = join2(APP_HOME, "daemon.pid");
79
+ STOP_FLAG_FILE = join2(APP_HOME, "gateway.stopped");
80
+ defaults = {
81
+ checkInterval: 30,
82
+ failThreshold: 5,
83
+ dashboardPort: 9090,
84
+ maxRestartsPerHour: 5,
85
+ openclawProfile: "default",
86
+ notify: {
87
+ webhook: {
88
+ enabled: false,
89
+ url: "",
90
+ bodyTemplate: '{"msgtype":"text","text":{"content":"{{message}}"}}'
91
+ },
92
+ system: {
93
+ enabled: true
94
+ }
95
+ }
96
+ };
97
+ LOCAL_CONFIG = resolve(process.cwd(), "doctor.config.json");
98
+ }
99
+ });
100
+
101
+ // src/core/logger.ts
102
+ import { appendFileSync } from "fs";
103
+ import { join as join3 } from "path";
104
+ import chalk from "chalk";
105
+ function initLogger(dir) {
106
+ logDir = dir ?? DOCTOR_LOG_DIR;
107
+ ensureDoctorHome();
108
+ }
109
+ function getLogFile() {
110
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
111
+ return join3(logDir, `${date}.log`);
112
+ }
113
+ function log(level, message) {
114
+ const time = (/* @__PURE__ */ new Date()).toISOString();
115
+ const line = `[${time}] [${level.toUpperCase()}] ${message}`;
116
+ const colorFn = level === "error" ? chalk.red : level === "warn" ? chalk.yellow : level === "success" ? chalk.green : chalk.blue;
117
+ console.log(colorFn(line));
118
+ try {
119
+ appendFileSync(getLogFile(), line + "\n");
120
+ } catch {
121
+ }
122
+ }
123
+ function addCheckRecord(record) {
124
+ checkHistory.push(record);
125
+ if (checkHistory.length > MAX_HISTORY) checkHistory.shift();
126
+ }
127
+ function addRestartRecord(record) {
128
+ restartHistory.push(record);
129
+ if (restartHistory.length > MAX_HISTORY) restartHistory.shift();
130
+ }
131
+ function getCheckHistory() {
132
+ return [...checkHistory];
133
+ }
134
+ function getRestartHistory() {
135
+ return [...restartHistory];
136
+ }
137
+ var logDir, checkHistory, restartHistory, MAX_HISTORY;
138
+ var init_logger = __esm({
139
+ "src/core/logger.ts"() {
140
+ "use strict";
141
+ init_config();
142
+ logDir = DOCTOR_LOG_DIR;
143
+ checkHistory = [];
144
+ restartHistory = [];
145
+ MAX_HISTORY = 100;
146
+ }
147
+ });
148
+
149
+ // src/core/openclaw.ts
150
+ import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync } from "fs";
151
+ import { join as join4 } from "path";
152
+ import { homedir as homedir2 } from "os";
153
+ import { execSync } from "child_process";
154
+ import { exec } from "child_process";
155
+ import { promisify } from "util";
156
+ import * as http from "http";
157
+ function getOpenClawHome(profile) {
158
+ if (profile === "dev") return join4(homedir2(), ".openclaw-dev");
159
+ if (profile !== "default") return join4(homedir2(), `.openclaw-${profile}`);
160
+ return join4(homedir2(), ".openclaw");
161
+ }
162
+ function findOpenClawBin() {
163
+ const plistDir = join4(homedir2(), "Library", "LaunchAgents");
164
+ if (existsSync3(plistDir)) {
165
+ const plists = readdirSync(plistDir).filter(
166
+ (f) => f.includes("openclaw") && f.endsWith(".plist")
167
+ );
168
+ for (const plist of plists) {
169
+ const content = readFileSync2(join4(plistDir, plist), "utf-8");
170
+ const nodeMatch = content.match(
171
+ /<string>(\/[^<]*\/bin\/node)<\/string>/
172
+ );
173
+ const cliMatch = content.match(
174
+ /<string>(\/[^<]*openclaw[^<]*\.(?:js|mjs))<\/string>/
175
+ );
176
+ if (nodeMatch && cliMatch) {
177
+ return { nodePath: nodeMatch[1], cliBinPath: cliMatch[1] };
178
+ }
179
+ }
180
+ }
181
+ try {
182
+ const bin = execSync("which openclaw", { encoding: "utf-8" }).trim();
183
+ if (bin) return { nodePath: process.execPath, cliBinPath: bin };
184
+ } catch {
185
+ }
186
+ return null;
187
+ }
188
+ function findLaunchdLabel() {
189
+ const plistDir = join4(homedir2(), "Library", "LaunchAgents");
190
+ if (!existsSync3(plistDir)) return "ai.openclaw.gateway";
191
+ const plists = readdirSync(plistDir).filter(
192
+ (f) => f.includes("openclaw") && f.endsWith(".plist")
193
+ );
194
+ if (plists.length > 0) {
195
+ return plists[0].replace(".plist", "");
196
+ }
197
+ return "ai.openclaw.gateway";
198
+ }
199
+ function detectOpenClaw(profile = "default") {
200
+ const home = getOpenClawHome(profile);
201
+ const configPath = join4(home, "openclaw.json");
202
+ const logDir2 = join4(home, "logs");
203
+ const defaults2 = {
204
+ configPath,
205
+ gatewayPort: 18789,
206
+ gatewayToken: "",
207
+ launchdLabel: findLaunchdLabel(),
208
+ nodePath: process.execPath,
209
+ cliBinPath: "",
210
+ logDir: logDir2,
211
+ profile,
212
+ channels: [],
213
+ agents: [],
214
+ version: null
215
+ };
216
+ const binInfo = findOpenClawBin();
217
+ if (binInfo) {
218
+ defaults2.nodePath = binInfo.nodePath;
219
+ defaults2.cliBinPath = binInfo.cliBinPath;
220
+ }
221
+ if (!existsSync3(configPath)) {
222
+ return defaults2;
223
+ }
224
+ try {
225
+ const raw = JSON.parse(readFileSync2(configPath, "utf-8"));
226
+ defaults2.gatewayPort = raw.gateway?.port ?? defaults2.gatewayPort;
227
+ defaults2.gatewayToken = raw.gateway?.auth?.token ?? "";
228
+ defaults2.version = raw.meta?.lastTouchedVersion ?? null;
229
+ if (raw.channels) {
230
+ defaults2.channels = Object.entries(raw.channels).filter(([, v]) => v.enabled !== false).map(([k]) => k);
231
+ }
232
+ if (raw.agents?.list) {
233
+ defaults2.agents = raw.agents.list.map(
234
+ (a) => ({
235
+ id: a.id,
236
+ name: a.name ?? a.id,
237
+ isDefault: a.default ?? false,
238
+ model: typeof a.model === "string" ? a.model : a.model?.primary ?? raw.agents?.defaults?.model?.primary ?? void 0,
239
+ workspace: a.workspace
240
+ })
241
+ );
242
+ }
243
+ } catch {
244
+ }
245
+ return defaults2;
246
+ }
247
+ async function runOpenClawCmd(info, args) {
248
+ if (!info.cliBinPath) return null;
249
+ try {
250
+ const { stdout } = await execAsync(`"${info.nodePath}" "${info.cliBinPath}" ${args}`, {
251
+ timeout: 3e4,
252
+ env: { ...process.env, NODE_NO_WARNINGS: "1" }
253
+ });
254
+ return stdout.trim();
255
+ } catch {
256
+ return null;
257
+ }
258
+ }
259
+ async function getGatewayHealthHttp(info) {
260
+ return new Promise((resolve3) => {
261
+ const url = `http://127.0.0.1:${info.gatewayPort}/health`;
262
+ const req = http.get(url, { timeout: 5e3 }, (res) => {
263
+ let data = "";
264
+ res.on("data", (chunk) => {
265
+ data += chunk;
266
+ });
267
+ res.on("end", () => {
268
+ try {
269
+ resolve3(JSON.parse(data));
270
+ } catch {
271
+ resolve3(null);
272
+ }
273
+ });
274
+ });
275
+ req.on("error", () => resolve3(null));
276
+ req.on("timeout", () => {
277
+ req.destroy();
278
+ resolve3(null);
279
+ });
280
+ });
281
+ }
282
+ async function getGatewayHealth(info) {
283
+ const raw = await runOpenClawCmd(info, "health --json");
284
+ if (!raw) return null;
285
+ try {
286
+ const jsonStart = raw.indexOf("{");
287
+ if (jsonStart === -1) return null;
288
+ return JSON.parse(raw.slice(jsonStart));
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
293
+ function getRestartCommand(info) {
294
+ const uid = process.getuid?.() ?? 501;
295
+ return `launchctl kickstart -k gui/${uid}/${info.launchdLabel}`;
296
+ }
297
+ function getStopCommand(info) {
298
+ const uid = process.getuid?.() ?? 501;
299
+ return `launchctl bootout gui/${uid}/${info.launchdLabel} 2>/dev/null || launchctl kill SIGTERM gui/${uid}/${info.launchdLabel} 2>/dev/null || true`;
300
+ }
301
+ function getStartCommand(info) {
302
+ const uid = process.getuid?.() ?? 501;
303
+ const plistDir = `${process.env.HOME}/Library/LaunchAgents`;
304
+ return `(launchctl bootstrap gui/${uid} ${plistDir}/${info.launchdLabel}.plist 2>/dev/null || true) && launchctl kickstart gui/${uid}/${info.launchdLabel}`;
305
+ }
306
+ var execAsync;
307
+ var init_openclaw = __esm({
308
+ "src/core/openclaw.ts"() {
309
+ "use strict";
310
+ execAsync = promisify(exec);
311
+ }
312
+ });
313
+
314
+ // src/core/health-checker.ts
315
+ async function checkHealth(info) {
316
+ const start = Date.now();
317
+ let health = await getGatewayHealthHttp(info);
318
+ if (!health) {
319
+ log("warn", "HTTP probe failed, falling back to CLI health check");
320
+ health = await getGatewayHealth(info);
321
+ }
322
+ const durationMs = Date.now() - start;
323
+ if (!health) {
324
+ const error = "Gateway unreachable (openclaw health failed)";
325
+ addCheckRecord({
326
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
327
+ healthy: false,
328
+ error,
329
+ responseTime: durationMs
330
+ });
331
+ log("error", `Health check failed: ${error} (${durationMs}ms)`);
332
+ return { healthy: false, gateway: false, channels: [], agentRuntimes: [], durationMs, error };
333
+ }
334
+ const channels = health.channels ? Object.entries(health.channels).map(([name, ch]) => ({
335
+ name,
336
+ ok: ch.probe?.ok ?? false
337
+ })) : [];
338
+ const healthy = health.ok;
339
+ const agentRuntimes = health.agents ?? [];
340
+ addCheckRecord({
341
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
342
+ healthy,
343
+ responseTime: durationMs
344
+ });
345
+ if (healthy) {
346
+ log("success", `Health OK \u2014 gateway up, ${channels.length} channels (${durationMs}ms)`);
347
+ } else {
348
+ log("warn", `Health degraded \u2014 gateway responded but ok=false (${durationMs}ms)`);
349
+ }
350
+ return { healthy, gateway: true, channels, agentRuntimes, durationMs, raw: health };
351
+ }
352
+ var init_health_checker = __esm({
353
+ "src/core/health-checker.ts"() {
354
+ "use strict";
355
+ init_logger();
356
+ init_openclaw();
357
+ }
358
+ });
359
+
360
+ // src/core/process-manager.ts
361
+ import { exec as exec2 } from "child_process";
362
+ import { promisify as promisify2 } from "util";
363
+ async function runShell(command) {
364
+ try {
365
+ const { stdout } = await execAsync2(command, { timeout: 12e4 });
366
+ return { success: true, output: stdout.trim() };
367
+ } catch (err) {
368
+ const error = err instanceof Error ? err.message : String(err);
369
+ return { success: false, error };
370
+ }
371
+ }
372
+ async function restartGateway(info) {
373
+ const cmd = getRestartCommand(info);
374
+ log("warn", `Restarting gateway: ${cmd}`);
375
+ const result = await runShell(cmd);
376
+ if (result.success) {
377
+ log("success", "Gateway restarted");
378
+ } else {
379
+ log("error", `Gateway restart failed: ${result.error}`);
380
+ }
381
+ addRestartRecord({
382
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
383
+ reason: "health check failed",
384
+ success: result.success
385
+ });
386
+ return result;
387
+ }
388
+ async function startGateway(info) {
389
+ const cmd = getStartCommand(info);
390
+ log("info", `Starting gateway: ${cmd}`);
391
+ const result = await runShell(cmd);
392
+ if (result.success) log("success", "Gateway started");
393
+ else log("error", `Gateway start failed: ${result.error}`);
394
+ return result;
395
+ }
396
+ async function stopGateway(info) {
397
+ const cmd = getStopCommand(info);
398
+ log("info", `Stopping gateway: ${cmd}`);
399
+ const result = await runShell(cmd);
400
+ if (result.success) log("success", "Gateway stopped");
401
+ else log("error", `Gateway stop failed: ${result.error}`);
402
+ return result;
403
+ }
404
+ var execAsync2, RestartThrottle;
405
+ var init_process_manager = __esm({
406
+ "src/core/process-manager.ts"() {
407
+ "use strict";
408
+ init_logger();
409
+ init_openclaw();
410
+ execAsync2 = promisify2(exec2);
411
+ RestartThrottle = class {
412
+ constructor(maxPerHour) {
413
+ this.maxPerHour = maxPerHour;
414
+ }
415
+ timestamps = [];
416
+ canRestart() {
417
+ const oneHourAgo = Date.now() - 36e5;
418
+ this.timestamps = this.timestamps.filter((t) => t > oneHourAgo);
419
+ return this.timestamps.length < this.maxPerHour;
420
+ }
421
+ record() {
422
+ this.timestamps.push(Date.now());
423
+ }
424
+ recentCount() {
425
+ const oneHourAgo = Date.now() - 36e5;
426
+ return this.timestamps.filter((t) => t > oneHourAgo).length;
427
+ }
428
+ };
429
+ }
430
+ });
431
+
432
+ // src/core/workspace-scanner.ts
433
+ import { existsSync as existsSync5, statSync, readdirSync as readdirSync2 } from "fs";
434
+ import { join as join6 } from "path";
435
+ import { homedir as homedir3 } from "os";
436
+ function expandHome(p) {
437
+ return p.startsWith("~/") ? join6(homedir3(), p.slice(2)) : p;
438
+ }
439
+ function dirSizeKB(dir, depth = 0) {
440
+ if (depth > 4 || !existsSync5(dir)) return 0;
441
+ let total = 0;
442
+ try {
443
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
444
+ if (["node_modules", ".git", ".DS_Store"].includes(entry.name)) continue;
445
+ const full = join6(dir, entry.name);
446
+ if (entry.isDirectory()) {
447
+ total += dirSizeKB(full, depth + 1);
448
+ } else {
449
+ try {
450
+ total += statSync(full).size;
451
+ } catch {
452
+ }
453
+ }
454
+ }
455
+ } catch {
456
+ }
457
+ return Math.round(total / 1024);
458
+ }
459
+ function scanWorkspaces(info) {
460
+ const results = [];
461
+ for (const agent of info.agents) {
462
+ const workspaceRaw = agent.workspace;
463
+ if (!workspaceRaw) continue;
464
+ const workspacePath = expandHome(workspaceRaw);
465
+ let memoryFileSizeKB = 0;
466
+ try {
467
+ const memPath = join6(workspacePath, "MEMORY.md");
468
+ if (existsSync5(memPath)) {
469
+ memoryFileSizeKB = Math.round(statSync(memPath).size / 1024);
470
+ }
471
+ } catch {
472
+ }
473
+ let totalWorkspaceSizeKB = 0;
474
+ try {
475
+ totalWorkspaceSizeKB = dirSizeKB(workspacePath);
476
+ } catch {
477
+ }
478
+ let sessionCount = 0;
479
+ try {
480
+ const sessDir = join6(homedir3(), ".openclaw", "agents", agent.id, "sessions");
481
+ if (existsSync5(sessDir)) {
482
+ sessionCount = readdirSync2(sessDir).filter((f) => f.endsWith(".jsonl") || f.endsWith(".json")).length;
483
+ }
484
+ } catch {
485
+ }
486
+ results.push({
487
+ agentId: agent.id,
488
+ agentName: agent.name,
489
+ workspacePath,
490
+ memoryFileSizeKB,
491
+ memoryWarning: memoryFileSizeKB > 50,
492
+ totalWorkspaceSizeKB,
493
+ sessionCount,
494
+ model: agent.model
495
+ });
496
+ }
497
+ return results;
498
+ }
499
+ var init_workspace_scanner = __esm({
500
+ "src/core/workspace-scanner.ts"() {
501
+ "use strict";
502
+ }
503
+ });
504
+
505
+ // src/core/cost-scanner.ts
506
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
507
+ import { join as join7 } from "path";
508
+ import { homedir as homedir4 } from "os";
509
+ function parseSessionCosts(filePath, sinceMs) {
510
+ let cost = 0;
511
+ let tokens = 0;
512
+ try {
513
+ const lines = readFileSync4(filePath, "utf-8").split("\n").filter(Boolean);
514
+ for (const line of lines) {
515
+ try {
516
+ const msg = JSON.parse(line);
517
+ if (msg.type !== "message" || !msg.message?.usage?.cost) continue;
518
+ const ts = msg.timestamp ? new Date(msg.timestamp).getTime() : msg.message?.timestamp ?? 0;
519
+ if (ts < sinceMs) continue;
520
+ cost += msg.message.usage.cost.total ?? 0;
521
+ tokens += msg.message.usage.totalTokens ?? 0;
522
+ } catch {
523
+ }
524
+ }
525
+ } catch {
526
+ }
527
+ return { cost, tokens };
528
+ }
529
+ function scanCosts(agents) {
530
+ const now = Date.now();
531
+ const todayStart = /* @__PURE__ */ new Date();
532
+ todayStart.setHours(0, 0, 0, 0);
533
+ const weekStart = now - 7 * 24 * 3600 * 1e3;
534
+ const result = [];
535
+ for (const agent of agents) {
536
+ const sessDir = join7(homedir4(), ".openclaw", "agents", agent.id, "sessions");
537
+ if (!existsSync6(sessDir)) continue;
538
+ let todayCost = 0, weekCost = 0, totalTokens = 0, sessionCount = 0;
539
+ const files = readdirSync3(sessDir).filter((f) => f.endsWith(".jsonl"));
540
+ sessionCount = files.length;
541
+ for (const file of files) {
542
+ const fpath = join7(sessDir, file);
543
+ try {
544
+ const mtime = statSync2(fpath).mtimeMs;
545
+ if (mtime < weekStart) continue;
546
+ } catch {
547
+ continue;
548
+ }
549
+ const week = parseSessionCosts(fpath, weekStart);
550
+ weekCost += week.cost;
551
+ totalTokens += week.tokens;
552
+ const today = parseSessionCosts(fpath, todayStart.getTime());
553
+ todayCost += today.cost;
554
+ }
555
+ result.push({ agentId: agent.id, agentName: agent.name, todayCost, weekCost, totalTokens, sessionCount });
556
+ }
557
+ return {
558
+ agents: result,
559
+ todayTotal: result.reduce((s, a) => s + a.todayCost, 0),
560
+ weekTotal: result.reduce((s, a) => s + a.weekCost, 0),
561
+ currency: "USD"
562
+ };
563
+ }
564
+ var init_cost_scanner = __esm({
565
+ "src/core/cost-scanner.ts"() {
566
+ "use strict";
567
+ }
568
+ });
569
+
570
+ // src/dashboard/server.ts
571
+ var server_exports = {};
572
+ __export(server_exports, {
573
+ startDashboard: () => startDashboard
574
+ });
575
+ import { createServer } from "http";
576
+ import { readFileSync as readFileSync5, existsSync as existsSync7, readdirSync as readdirSync4 } from "fs";
577
+ import { join as join8 } from "path";
578
+ import chalk2 from "chalk";
579
+ import { hostname as osHostname } from "os";
580
+ function readDoctorLogs(maxLines = 50) {
581
+ if (!existsSync7(DOCTOR_LOG_DIR)) return [];
582
+ const files = readdirSync4(DOCTOR_LOG_DIR).filter((f) => f.endsWith(".log")).sort().reverse();
583
+ if (files.length === 0) return [];
584
+ const content = readFileSync5(join8(DOCTOR_LOG_DIR, files[0]), "utf-8");
585
+ const lines = content.trim().split("\n");
586
+ return lines.slice(-maxLines);
587
+ }
588
+ function renderShell() {
589
+ return `<!DOCTYPE html>
590
+ <html lang="en">
591
+ <head>
592
+ <meta charset="utf-8">
593
+ <meta name="viewport" content="width=device-width, initial-scale=1">
594
+ <title>OpenClaw Doctor</title>
595
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
596
+ <style>
597
+ * { margin:0; padding:0; box-sizing:border-box; }
598
+ body { font-family:system-ui,-apple-system,sans-serif; background:#050810; color:#f0f4ff; min-height:100vh; }
599
+
600
+ /* Navbar */
601
+ .navbar { display:flex; align-items:center; justify-content:space-between; padding:0.75rem 1.5rem; background:#0d1424; border-bottom:1px solid #1a2744; flex-wrap:wrap; gap:0.5rem; }
602
+ .nav-left { display:flex; align-items:center; gap:0.5rem; font-weight:700; font-size:1rem; white-space:nowrap; }
603
+ .nav-left .ver { font-weight:400; color:#6b7fa3; font-size:0.8rem; }
604
+ .nav-center { display:flex; align-items:center; gap:0.5rem; }
605
+ .status-dot { width:10px; height:10px; border-radius:50%; display:inline-block; }
606
+ .status-label { font-weight:600; font-size:0.9rem; }
607
+ .nav-right { color:#6b7fa3; font-size:0.75rem; white-space:nowrap; }
608
+
609
+ /* Tabs */
610
+ .tabs { display:flex; border-bottom:1px solid #1a2744; background:#0d1424; overflow-x:auto; }
611
+ .tab { padding:0.65rem 1.25rem; cursor:pointer; color:#6b7fa3; font-size:0.85rem; border-bottom:2px solid transparent; white-space:nowrap; transition:color 0.15s; }
612
+ .tab:hover { color:#f0f4ff; }
613
+ .tab.active { color:#f0f4ff; border-bottom-color:#007AFF; }
614
+
615
+ /* Content */
616
+ .content { padding:1.5rem; max-width:1200px; margin:0 auto; }
617
+
618
+ /* Cards */
619
+ .card { background:#0d1424; border:1px solid #1a2744; border-radius:12px; padding:1.25rem; margin-bottom:1rem; }
620
+ .card-title { color:#6b7fa3; font-size:0.75rem; text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.5rem; }
621
+
622
+ .big-status { font-size:2rem; font-weight:700; }
623
+ .meta-row { color:#6b7fa3; font-size:0.8rem; margin-top:0.25rem; }
624
+
625
+ .grid2 { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }
626
+ @media (max-width:768px) { .grid2 { grid-template-columns:1fr; } }
627
+
628
+ /* Tables */
629
+ table { width:100%; border-collapse:collapse; }
630
+ th, td { text-align:left; padding:0.4rem 0.75rem; border-bottom:1px solid #1a2744; font-size:0.8rem; }
631
+ th { color:#6b7fa3; font-size:0.7rem; text-transform:uppercase; letter-spacing:0.05em; }
632
+
633
+ /* Tags */
634
+ .tag { display:inline-block; padding:0.15rem 0.5rem; border-radius:9999px; font-size:0.7rem; font-weight:600; }
635
+ .tag-ok { background:rgba(0,166,126,0.15); color:#00A67E; }
636
+ .tag-fail { background:#ef444422; color:#ef4444; }
637
+ .tag-default { background:rgba(0,122,255,0.15); color:#007AFF; font-size:0.65rem; margin-left:0.35rem; }
638
+
639
+ /* Buttons */
640
+ .btn { padding:0.5rem 1rem; border:none; border-radius:0.375rem; cursor:pointer; font-size:0.8rem; font-weight:600; transition:opacity 0.15s; }
641
+ .btn:hover { opacity:0.85; }
642
+ .btn:disabled { opacity:0.5; cursor:not-allowed; }
643
+ .btn-blue { background:#007AFF; color:#fff; }
644
+ .btn-amber { background:#f59e0b; color:#fff; }
645
+ .btn-group { display:flex; gap:0.5rem; flex-wrap:wrap; }
646
+
647
+ /* Result box */
648
+ .result-box { margin-top:0.75rem; padding:0.75rem; background:#030609; border-radius:8px; font-size:0.75rem; font-family:ui-monospace,monospace; white-space:pre-wrap; word-break:break-all; max-height:200px; overflow-y:auto; }
649
+
650
+ /* Logs */
651
+ .log-line { font-family:ui-monospace,monospace; font-size:0.75rem; padding:0.2rem 0; line-height:1.4; word-break:break-all; }
652
+ .log-info { color:#6b7fa3; }
653
+ .log-warn { color:#eab308; }
654
+ .log-error { color:#ef4444; }
655
+ .log-success { color:#00A67E; }
656
+
657
+ /* Config */
658
+ .cfg-row { display:flex; justify-content:space-between; padding:0.5rem 0; border-bottom:1px solid #1a2744; font-size:0.85rem; }
659
+ .cfg-key { color:#6b7fa3; }
660
+ .cfg-val { color:#f0f4ff; font-family:ui-monospace,monospace; }
661
+
662
+ /* Loading */
663
+ .loading { color:#6b7fa3; text-align:center; padding:3rem 0; }
664
+ </style>
665
+ </head>
666
+ <body x-data="dashboard()" x-init="init()">
667
+
668
+ <!-- Navbar -->
669
+ <div class="navbar">
670
+ <div class="nav-left">
671
+ <span>&#129438; OpenClaw Doctor</span>
672
+ <span class="ver">v${pkgVersion}</span>
673
+ </div>
674
+ <div class="nav-center">
675
+ <span class="status-dot" :style="'background:' + statusColor"></span>
676
+ <span class="status-label" :style="'color:' + statusColor" x-text="statusText"></span>
677
+ </div>
678
+ <div class="nav-actions" style="display:flex;gap:0.5rem;align-items:center;">
679
+ <button class="btn" style="background:#00A67E;color:#fff;padding:0.35rem 0.75rem;font-size:0.75rem;" :disabled="actionLoading" @click="doStart()">\u25B6 Start</button>
680
+ <button class="btn btn-blue" style="padding:0.35rem 0.75rem;font-size:0.75rem;" :disabled="actionLoading" @click="doRestart()">\u21BA Restart</button>
681
+ <button class="btn" style="background:#ef4444;color:#fff;padding:0.35rem 0.75rem;font-size:0.75rem;" :disabled="actionLoading" @click="doStop()">\u25A0 Stop</button>
682
+ </div>
683
+ <div class="nav-right" x-text="lastCheck ? 'Updated ' + lastCheck : 'Loading...'"></div>
684
+ </div>
685
+
686
+ <!-- Tabs -->
687
+ <div class="tabs">
688
+ <template x-for="t in ['Overview','Cost','Restarts','Logs','Config']">
689
+ <div class="tab" :class="{ active: tab === t }" @click="tab = t" x-text="t"></div>
690
+ </template>
691
+ </div>
692
+
693
+ <!-- Content -->
694
+ <div class="content">
695
+
696
+ <!-- Loading state -->
697
+ <template x-if="!loaded">
698
+ <div class="loading">Loading...</div>
699
+ </template>
700
+
701
+ <!-- Overview Tab -->
702
+ <template x-if="loaded && tab === 'Overview'">
703
+ <div>
704
+ <div class="card">
705
+ <div class="big-status" :style="'color:' + statusColor" x-text="statusText"></div>
706
+ <div class="meta-row">
707
+ Gateway :<span x-text="data.info?.gatewayPort ?? '?'"></span>
708
+ &nbsp;|&nbsp; Latency: <span x-text="(data.durationMs ?? '-') + 'ms'"></span>
709
+ &nbsp;|&nbsp; Profile: <span x-text="data.info?.profile ?? '?'"></span>
710
+ &nbsp;|&nbsp; OpenClaw <span x-text="data.info?.version ?? '?'"></span>
711
+ </div>
712
+ </div>
713
+
714
+ <div class="grid2">
715
+ <!-- Channels -->
716
+ <div class="card">
717
+ <div class="card-title">Channels</div>
718
+ <template x-if="data.channels && data.channels.length > 0">
719
+ <table>
720
+ <thead><tr><th>Name</th><th>Status</th></tr></thead>
721
+ <tbody>
722
+ <template x-for="c in data.channels" :key="c.name">
723
+ <tr>
724
+ <td x-text="c.name"></td>
725
+ <td><span class="tag" :class="c.ok ? 'tag-ok' : 'tag-fail'" x-text="c.ok ? 'OK' : 'FAIL'"></span></td>
726
+ </tr>
727
+ </template>
728
+ </tbody>
729
+ </table>
730
+ </template>
731
+ <template x-if="!data.channels || data.channels.length === 0">
732
+ <div style="color:#6b7fa3;font-size:0.8rem;">No channels</div>
733
+ </template>
734
+ </div>
735
+
736
+ <!-- Agents -->
737
+ <div class="card">
738
+ <div class="card-title">Agents</div>
739
+ <template x-if="data.agents && data.agents.length > 0">
740
+ <table>
741
+ <thead><tr><th>Name</th><th>Model</th><th>Sessions</th><th>Last Active</th><th></th></tr></thead>
742
+ <tbody>
743
+ <template x-for="a in data.agents" :key="a.id">
744
+ <tr x-data="{ rt() { return (data.agentRuntimes||[]).find(r=>r.agentId===a.id) } }">
745
+ <td x-text="a.name"></td>
746
+ <td style="color:#6b7fa3;font-size:0.78rem;" x-text="a.model ? a.model.replace('anthropic/','').replace('openai/','') : '\u2014'"></td>
747
+ <td style="color:#6b7fa3;font-size:0.78rem;" x-text="rt()?.sessions?.count ?? '\u2014'"></td>
748
+ <td style="font-size:0.78rem;" x-text="rt()?.sessions?.recent?.[0]?.age != null ? (rt().sessions.recent[0].age < 60000 ? 'just now' : rt().sessions.recent[0].age < 3600000 ? Math.floor(rt().sessions.recent[0].age/60000)+'m ago' : Math.floor(rt().sessions.recent[0].age/3600000)+'h ago') : '\u2014'"></td>
749
+ <td><template x-if="a.isDefault"><span class="tag tag-default">default</span></template></td>
750
+ </tr>
751
+ </template>
752
+ </tbody>
753
+ </table>
754
+ </template>
755
+ <template x-if="!data.agents || data.agents.length === 0">
756
+ <div style="color:#6b7fa3;font-size:0.8rem;">No agents</div>
757
+ </template>
758
+ </div>
759
+ </div>
760
+
761
+ <!-- Recent checks -->
762
+ <div class="card">
763
+ <div class="card-title">Recent Health Checks</div>
764
+ <template x-if="data.checks && data.checks.length > 0">
765
+ <div style="overflow-x:auto;">
766
+ <table>
767
+ <thead><tr><th>Time</th><th>Status</th><th>Latency</th><th>Error</th></tr></thead>
768
+ <tbody>
769
+ <template x-for="c in data.checks.slice().reverse().slice(0, 10)" :key="c.timestamp">
770
+ <tr>
771
+ <td x-text="fmtTime(c.timestamp)"></td>
772
+ <td><span class="tag" :class="c.healthy ? 'tag-ok' : 'tag-fail'" x-text="c.healthy ? 'OK' : 'FAIL'"></span></td>
773
+ <td x-text="(c.responseTime ?? '-') + 'ms'"></td>
774
+ <td style="color:#ef4444;" x-text="c.error ?? ''"></td>
775
+ </tr>
776
+ </template>
777
+ </tbody>
778
+ </table>
779
+ </div>
780
+ </template>
781
+ <template x-if="!data.checks || data.checks.length === 0">
782
+ <div style="color:#6b7fa3;font-size:0.8rem;">No checks yet</div>
783
+ </template>
784
+ </div>
785
+ </div>
786
+ </template>
787
+
788
+
789
+ <!-- Workspace Health -->
790
+ <div class="card">
791
+ <div class="card-title">Workspace Health</div>
792
+ <template x-if="data.workspaces && data.workspaces.length > 0">
793
+ <table>
794
+ <thead><tr><th>Agent</th><th>MEMORY.md</th><th>Sessions</th><th>Workspace Size</th><th></th></tr></thead>
795
+ <tbody>
796
+ <template x-for="w in data.workspaces" :key="w.agentId">
797
+ <tr>
798
+ <td x-text="w.agentName"></td>
799
+ <td x-text="w.memoryFileSizeKB + ' KB'"></td>
800
+ <td x-text="w.sessionCount"></td>
801
+ <td x-text="w.totalWorkspaceSizeKB + ' KB'"></td>
802
+ <td>
803
+ <template x-if="w.memoryWarning">
804
+ <span class="tag" style="background:rgba(245,158,11,0.15);color:#f59e0b;">\u26A0 Large</span>
805
+ </template>
806
+ </td>
807
+ </tr>
808
+ </template>
809
+ </tbody>
810
+ </table>
811
+ </template>
812
+ <template x-if="!data.workspaces || data.workspaces.length === 0">
813
+ <div style="color:#6b7fa3;font-size:0.8rem;">No workspace data</div>
814
+ </template>
815
+ </div>
816
+
817
+ <!-- Restarts Tab -->
818
+ <template x-if="loaded && tab === 'Restarts'">
819
+ <div>
820
+ <div class="card">
821
+ <div class="card-title">Actions</div>
822
+ <div class="btn-group">
823
+ <button class="btn btn-blue" :disabled="actionLoading" @click="doRestart()">Restart Gateway</button>
824
+ <button class="btn btn-amber" :disabled="actionLoading" @click="doDoctor()">Run Doctor Fix</button>
825
+ </div>
826
+ <template x-if="actionResult">
827
+ <div class="result-box" x-text="actionResult"></div>
828
+ </template>
829
+ </div>
830
+
831
+ <div class="card">
832
+ <div class="card-title">Restart History</div>
833
+ <template x-if="data.restarts && data.restarts.length > 0">
834
+ <div style="overflow-x:auto;">
835
+ <table>
836
+ <thead><tr><th>Time</th><th>Reason</th><th>Result</th></tr></thead>
837
+ <tbody>
838
+ <template x-for="r in data.restarts.slice().reverse()" :key="r.timestamp">
839
+ <tr>
840
+ <td x-text="fmtTime(r.timestamp)"></td>
841
+ <td x-text="r.reason"></td>
842
+ <td><span class="tag" :class="r.success ? 'tag-ok' : 'tag-fail'" x-text="r.success ? 'OK' : 'FAIL'"></span></td>
843
+ </tr>
844
+ </template>
845
+ </tbody>
846
+ </table>
847
+ </div>
848
+ </template>
849
+ <template x-if="!data.restarts || data.restarts.length === 0">
850
+ <div style="color:#6b7fa3;font-size:0.8rem;">No restarts</div>
851
+ </template>
852
+ </div>
853
+ </div>
854
+ </template>
855
+
856
+ <!-- Logs Tab -->
857
+ <template x-if="loaded && tab === 'Logs'">
858
+ <div>
859
+ <div class="card">
860
+ <div class="card-title">Doctor Logs (latest 50)</div>
861
+ <template x-if="logs.length > 0">
862
+ <div style="max-height:600px;overflow-y:auto;">
863
+ <template x-for="(line, i) in logs" :key="i">
864
+ <div class="log-line" :class="logClass(line)" x-text="line"></div>
865
+ </template>
866
+ </div>
867
+ </template>
868
+ <template x-if="logs.length === 0">
869
+ <div style="color:#6b7fa3;font-size:0.8rem;">No logs yet</div>
870
+ </template>
871
+ </div>
872
+ </div>
873
+ </template>
874
+
875
+ <!-- Cost Tab -->
876
+ <template x-if="loaded && tab === 'Cost'">
877
+ <div>
878
+ <template x-if="costData">
879
+ <div>
880
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1rem;">
881
+ <div class="card" style="text-align:center;">
882
+ <div class="card-title">Today</div>
883
+ <div style="font-size:2rem;font-weight:700;color:#f0f4ff;" x-text="'$' + (costData.todayTotal||0).toFixed(4)"></div>
884
+ </div>
885
+ <div class="card" style="text-align:center;">
886
+ <div class="card-title">This Week</div>
887
+ <div style="font-size:2rem;font-weight:700;color:#f0f4ff;" x-text="'$' + (costData.weekTotal||0).toFixed(4)"></div>
888
+ </div>
889
+ </div>
890
+ <div class="card">
891
+ <div class="card-title">By Agent</div>
892
+ <table>
893
+ <thead><tr><th>Agent</th><th>Today</th><th>This Week</th><th>Sessions</th></tr></thead>
894
+ <tbody>
895
+ <template x-for="a in costData.agents" :key="a.agentId">
896
+ <tr>
897
+ <td x-text="a.agentName"></td>
898
+ <td x-text="'$' + (a.todayCost||0).toFixed(4)"></td>
899
+ <td x-text="'$' + (a.weekCost||0).toFixed(4)"></td>
900
+ <td style="color:#6b7fa3;" x-text="a.sessionCount"></td>
901
+ </tr>
902
+ </template>
903
+ </tbody>
904
+ </table>
905
+ </div>
906
+ </div>
907
+ </template>
908
+ <template x-if="!costData">
909
+ <div class="loading">Loading cost data...</div>
910
+ </template>
911
+ </div>
912
+ </template>
913
+
914
+ <!-- Config Tab -->
915
+ <template x-if="loaded && tab === 'Config'">
916
+ <div>
917
+ <div class="card">
918
+ <div class="card-title">Current Configuration</div>
919
+ <template x-if="data.config">
920
+ <div>
921
+ <div class="cfg-row"><span class="cfg-key">checkInterval</span><span class="cfg-val" x-text="data.config.checkInterval + 's'"></span></div>
922
+ <div class="cfg-row"><span class="cfg-key">failThreshold</span><span class="cfg-val" x-text="data.config.failThreshold"></span></div>
923
+ <div class="cfg-row"><span class="cfg-key">dashboardPort</span><span class="cfg-val" x-text="data.config.dashboardPort"></span></div>
924
+ <div class="cfg-row"><span class="cfg-key">maxRestartsPerHour</span><span class="cfg-val" x-text="data.config.maxRestartsPerHour"></span></div>
925
+ <div class="cfg-row"><span class="cfg-key">openclawProfile</span><span class="cfg-val" x-text="data.config.openclawProfile"></span></div>
926
+ <div class="cfg-row"><span class="cfg-key">notify.webhook.enabled</span><span class="cfg-val" x-text="data.config.notify?.webhook?.enabled ?? false"></span></div>
927
+ <div class="cfg-row"><span class="cfg-key">notify.system.enabled</span><span class="cfg-val" x-text="data.config.notify?.system?.enabled ?? false"></span></div>
928
+ </div>
929
+ </template>
930
+ </div>
931
+ </div>
932
+ </template>
933
+
934
+ </div>
935
+
936
+ <script>
937
+ function dashboard() {
938
+ return {
939
+ tab: 'Overview',
940
+ costData: null,
941
+ loaded: false,
942
+ data: {},
943
+ logs: [],
944
+ lastCheck: '',
945
+ actionLoading: false,
946
+ actionResult: '',
947
+
948
+ get statusText() {
949
+ if (!this.data || !this.loaded) return 'LOADING';
950
+ if (this.data.healthy) return 'HEALTHY';
951
+ if (this.data.gateway) return 'DEGRADED';
952
+ return 'UNREACHABLE';
953
+ },
954
+
955
+ get statusColor() {
956
+ if (!this.data || !this.loaded) return '#64748b';
957
+ if (this.data.healthy) return '#00A67E';
958
+ if (this.data.gateway) return '#f59e0b';
959
+ return '#ef4444';
960
+ },
961
+
962
+ async init() {
963
+ await this.refresh();
964
+ await this.refreshLogs();
965
+ setInterval(() => this.refresh(), 10000);
966
+ setInterval(() => this.refreshLogs(), 10000);
967
+ },
968
+
969
+ async refresh() {
970
+ try {
971
+ const res = await fetch('/api/status');
972
+ this.data = await res.json();
973
+ this.lastCheck = new Date().toLocaleTimeString();
974
+ this.loaded = true;
975
+ } catch (e) {
976
+ console.error('Failed to fetch status', e);
977
+ }
978
+ },
979
+
980
+ async refreshCost() {
981
+ try {
982
+ const r = await fetch('/api/cost');
983
+ this.costData = await r.json();
984
+ } catch {}
985
+ },
986
+ async refreshLogs() {
987
+ try {
988
+ const res = await fetch('/api/logs');
989
+ const d = await res.json();
990
+ this.logs = d.lines ?? [];
991
+ } catch (e) {
992
+ console.error('Failed to fetch logs', e);
993
+ }
994
+ },
995
+
996
+ async doRestart() {
997
+ this.actionLoading = true;
998
+ this.actionResult = '';
999
+ try {
1000
+ const res = await fetch('/api/restart', { method: 'POST' });
1001
+ const d = await res.json();
1002
+ this.actionResult = d.success ? 'Gateway restarted successfully.' : ('Restart failed: ' + (d.error ?? 'unknown'));
1003
+ await this.refresh();
1004
+ } catch (e) {
1005
+ this.actionResult = 'Request failed: ' + e.message;
1006
+ }
1007
+ this.actionLoading = false;
1008
+ },
1009
+
1010
+ async doStart() {
1011
+ this.actionLoading = true;
1012
+ this.actionResult = '';
1013
+ try {
1014
+ const res = await fetch('/api/gateway/start', { method: 'POST' });
1015
+ const d = await res.json();
1016
+ this.actionResult = d.success ? 'Gateway started.' : ('Start failed: ' + (d.message ?? 'unknown'));
1017
+ await this.refresh();
1018
+ } catch (e) {
1019
+ this.actionResult = 'Request failed: ' + e.message;
1020
+ }
1021
+ this.actionLoading = false;
1022
+ },
1023
+
1024
+ async doStop() {
1025
+ this.actionLoading = true;
1026
+ this.actionResult = '';
1027
+ try {
1028
+ const res = await fetch('/api/gateway/stop', { method: 'POST' });
1029
+ const d = await res.json();
1030
+ this.actionResult = d.success ? 'Gateway stopped.' : ('Stop failed: ' + (d.message ?? 'unknown'));
1031
+ await this.refresh();
1032
+ } catch (e) {
1033
+ this.actionResult = 'Request failed: ' + e.message;
1034
+ }
1035
+ this.actionLoading = false;
1036
+ },
1037
+
1038
+ async doDoctor() {
1039
+ this.actionLoading = true;
1040
+ this.actionResult = '';
1041
+ try {
1042
+ const res = await fetch('/api/doctor', { method: 'POST' });
1043
+ const d = await res.json();
1044
+ this.actionResult = d.output ?? 'No output';
1045
+ } catch (e) {
1046
+ this.actionResult = 'Request failed: ' + e.message;
1047
+ }
1048
+ this.actionLoading = false;
1049
+ },
1050
+
1051
+ fmtTime(iso) {
1052
+ if (!iso) return '';
1053
+ try { return new Date(iso).toLocaleString(); } catch { return iso; }
1054
+ },
1055
+
1056
+ logClass(line) {
1057
+ if (line.includes('[ERROR]')) return 'log-error';
1058
+ if (line.includes('[WARN]')) return 'log-warn';
1059
+ if (line.includes('[SUCCESS]')) return 'log-success';
1060
+ return 'log-info';
1061
+ }
1062
+ };
1063
+ }
1064
+ </script>
1065
+ </body>
1066
+ </html>`;
1067
+ }
1068
+ async function handleApiStatus(info, configPath, res) {
1069
+ try {
1070
+ const live = await checkHealth(info);
1071
+ const config = loadConfig(configPath);
1072
+ const workspaces = scanWorkspaces(info);
1073
+ const agentRuntimes = live.agentRuntimes ?? [];
1074
+ const payload = {
1075
+ healthy: live.healthy,
1076
+ gateway: live.gateway,
1077
+ channels: live.channels,
1078
+ agents: info.agents,
1079
+ agentRuntimes,
1080
+ durationMs: live.durationMs,
1081
+ checks: getCheckHistory(),
1082
+ restarts: getRestartHistory(),
1083
+ config,
1084
+ workspaces,
1085
+ info: {
1086
+ version: info.version,
1087
+ gatewayPort: info.gatewayPort,
1088
+ profile: info.profile
1089
+ }
1090
+ };
1091
+ res.writeHead(200, { "Content-Type": "application/json" });
1092
+ res.end(JSON.stringify(payload));
1093
+ } catch (err) {
1094
+ res.writeHead(500, { "Content-Type": "application/json" });
1095
+ res.end(JSON.stringify({ error: String(err) }));
1096
+ }
1097
+ }
1098
+ async function handleApiRestart(info, res) {
1099
+ try {
1100
+ const result = await restartGateway(info);
1101
+ res.writeHead(200, { "Content-Type": "application/json" });
1102
+ res.end(JSON.stringify({ success: result.success, message: result.output ?? result.error ?? "" }));
1103
+ } catch (err) {
1104
+ res.writeHead(500, { "Content-Type": "application/json" });
1105
+ res.end(JSON.stringify({ success: false, message: String(err) }));
1106
+ }
1107
+ }
1108
+ async function handleApiGatewayStart(info, res) {
1109
+ try {
1110
+ const result = await startGateway(info);
1111
+ res.writeHead(200, { "Content-Type": "application/json" });
1112
+ res.end(JSON.stringify({ success: result.success, message: result.output ?? result.error ?? "" }));
1113
+ } catch (err) {
1114
+ res.writeHead(500, { "Content-Type": "application/json" });
1115
+ res.end(JSON.stringify({ success: false, message: String(err) }));
1116
+ }
1117
+ }
1118
+ async function handleApiGatewayStop(info, res) {
1119
+ try {
1120
+ const result = await stopGateway(info);
1121
+ res.writeHead(200, { "Content-Type": "application/json" });
1122
+ res.end(JSON.stringify({ success: result.success, message: result.output ?? result.error ?? "" }));
1123
+ } catch (err) {
1124
+ res.writeHead(500, { "Content-Type": "application/json" });
1125
+ res.end(JSON.stringify({ success: false, message: String(err) }));
1126
+ }
1127
+ }
1128
+ async function handleApiDoctor(info, res) {
1129
+ try {
1130
+ const output = await runOpenClawCmd(info, "doctor --non-interactive");
1131
+ res.writeHead(200, { "Content-Type": "application/json" });
1132
+ res.end(JSON.stringify({ output: output ?? "No output" }));
1133
+ } catch (err) {
1134
+ res.writeHead(500, { "Content-Type": "application/json" });
1135
+ res.end(JSON.stringify({ output: "Error: " + String(err) }));
1136
+ }
1137
+ }
1138
+ async function handleApiCost(info, res) {
1139
+ try {
1140
+ const costs = scanCosts(info.agents);
1141
+ res.writeHead(200, { "Content-Type": "application/json" });
1142
+ res.end(JSON.stringify(costs));
1143
+ } catch (err) {
1144
+ res.writeHead(500, { "Content-Type": "application/json" });
1145
+ res.end(JSON.stringify({ error: String(err) }));
1146
+ }
1147
+ }
1148
+ function handleApiLogs(res) {
1149
+ const lines = readDoctorLogs(50);
1150
+ res.writeHead(200, { "Content-Type": "application/json" });
1151
+ res.end(JSON.stringify({ lines }));
1152
+ }
1153
+ function startDashboard(options) {
1154
+ const config = loadConfig(options.config);
1155
+ const info = detectOpenClaw(options.profile ?? config.openclawProfile);
1156
+ const port = config.dashboardPort;
1157
+ const shell = renderShell();
1158
+ const server = createServer(async (req, res) => {
1159
+ const url = req.url ?? "/";
1160
+ const method = req.method ?? "GET";
1161
+ if (method === "GET" && url === "/") {
1162
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1163
+ res.end(shell);
1164
+ } else if (method === "GET" && url === "/api/status") {
1165
+ await handleApiStatus(info, options.config, res);
1166
+ } else if (method === "GET" && url === "/api/cost") {
1167
+ await handleApiCost(info, res);
1168
+ } else if (method === "GET" && url === "/api/logs") {
1169
+ handleApiLogs(res);
1170
+ } else if (method === "POST" && url === "/api/restart") {
1171
+ await handleApiRestart(info, res);
1172
+ } else if (method === "POST" && url === "/api/gateway/start") {
1173
+ await handleApiGatewayStart(info, res);
1174
+ } else if (method === "POST" && url === "/api/gateway/stop") {
1175
+ await handleApiGatewayStop(info, res);
1176
+ } else if (method === "POST" && url === "/api/doctor") {
1177
+ await handleApiDoctor(info, res);
1178
+ } else {
1179
+ res.writeHead(404, { "Content-Type": "application/json" });
1180
+ res.end(JSON.stringify({ error: "Not found" }));
1181
+ }
1182
+ });
1183
+ server.listen(port, () => {
1184
+ log("info", `Dashboard running at http://localhost:${port}`);
1185
+ console.log(chalk2.green.bold(`
1186
+ Dashboard: http://localhost:${port}
1187
+ `));
1188
+ });
1189
+ const REMOTE_CONFIG_PATH = join8(DOCTOR_HOME, "remote.json");
1190
+ const REMOTE_API_URL2 = "https://api.openclaw-cli.app";
1191
+ let gatewayStartTime = null;
1192
+ const proxyFetch2 = async (url, opts) => {
1193
+ const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.ALL_PROXY || process.env.all_proxy;
1194
+ if (proxyUrl) {
1195
+ try {
1196
+ const { ProxyAgent, fetch: uf } = await import("undici");
1197
+ return uf(url, { ...opts, dispatcher: new ProxyAgent(proxyUrl) });
1198
+ } catch {
1199
+ }
1200
+ }
1201
+ return fetch(url, opts);
1202
+ };
1203
+ const HEARTBEAT_INTERVAL_MS = 10 * 60 * 1e3;
1204
+ const POLL_INTERVAL_MS = 3e4;
1205
+ let lastReportedHealthy = null;
1206
+ let lastReportedChannels = "";
1207
+ let lastReportedAt = 0;
1208
+ const doReport = async (reason) => {
1209
+ try {
1210
+ if (!existsSync7(REMOTE_CONFIG_PATH)) return;
1211
+ const cfg = JSON.parse(readFileSync5(REMOTE_CONFIG_PATH, "utf-8"));
1212
+ if (!cfg.enabled || !cfg.machineToken) return;
1213
+ const status = await checkHealth(info);
1214
+ if (status.healthy) {
1215
+ if (!gatewayStartTime) gatewayStartTime = Date.now();
1216
+ } else {
1217
+ gatewayStartTime = null;
1218
+ }
1219
+ const uptimeSeconds = gatewayStartTime ? Math.floor((Date.now() - gatewayStartTime) / 1e3) : 0;
1220
+ const body = {
1221
+ machineId: cfg.machineId,
1222
+ hostname: osHostname(),
1223
+ os: process.platform + "/" + process.arch,
1224
+ version: pkgVersion ?? "unknown",
1225
+ gateway: {
1226
+ healthy: status.healthy,
1227
+ port: info.gatewayPort,
1228
+ durationMs: status.durationMs ?? 0,
1229
+ label: info.launchdLabel ?? "",
1230
+ uptimeSeconds
1231
+ },
1232
+ agents: info.agents.map((a) => ({
1233
+ id: a.id,
1234
+ name: a.name,
1235
+ isDefault: a.isDefault
1236
+ })),
1237
+ channels: status.channels ?? [],
1238
+ ts: Date.now()
1239
+ };
1240
+ const controller = new AbortController();
1241
+ const timer = setTimeout(() => controller.abort(), 5e3);
1242
+ await proxyFetch2(cfg.reportUrl, {
1243
+ method: "POST",
1244
+ headers: {
1245
+ "Content-Type": "application/json",
1246
+ Authorization: "Bearer " + cfg.machineToken
1247
+ },
1248
+ body: JSON.stringify(body),
1249
+ signal: controller.signal
1250
+ });
1251
+ clearTimeout(timer);
1252
+ lastReportedHealthy = status.healthy;
1253
+ lastReportedChannels = JSON.stringify(status.channels ?? []);
1254
+ lastReportedAt = Date.now();
1255
+ log("info", `[remote-report] sent (reason=${reason})`);
1256
+ } catch {
1257
+ }
1258
+ };
1259
+ setInterval(async () => {
1260
+ try {
1261
+ if (!existsSync7(REMOTE_CONFIG_PATH)) return;
1262
+ const cfg = JSON.parse(readFileSync5(REMOTE_CONFIG_PATH, "utf-8"));
1263
+ if (!cfg.enabled || !cfg.machineToken) return;
1264
+ const status = await checkHealth(info);
1265
+ const channelsKey = JSON.stringify(status.channels ?? []);
1266
+ const now = Date.now();
1267
+ const healthChanged = lastReportedHealthy !== status.healthy;
1268
+ const channelsChanged = lastReportedChannels !== channelsKey;
1269
+ const heartbeatDue = now - lastReportedAt >= HEARTBEAT_INTERVAL_MS;
1270
+ if (healthChanged || channelsChanged || heartbeatDue) {
1271
+ const reason = healthChanged ? "health-change" : channelsChanged ? "channels-change" : "heartbeat";
1272
+ await doReport(reason);
1273
+ }
1274
+ } catch {
1275
+ }
1276
+ }, POLL_INTERVAL_MS);
1277
+ const executedCommands = /* @__PURE__ */ new Set();
1278
+ setInterval(async () => {
1279
+ try {
1280
+ if (!existsSync7(REMOTE_CONFIG_PATH)) return;
1281
+ const cfg = JSON.parse(readFileSync5(REMOTE_CONFIG_PATH, "utf-8"));
1282
+ if (!cfg.enabled || !cfg.machineToken) return;
1283
+ const controller = new AbortController();
1284
+ const timer = setTimeout(() => controller.abort(), 3e3);
1285
+ const res = await proxyFetch2(REMOTE_API_URL2 + "/v1/control/pending", {
1286
+ headers: { Authorization: "Bearer " + cfg.machineToken },
1287
+ signal: controller.signal
1288
+ });
1289
+ clearTimeout(timer);
1290
+ if (!res.ok) return;
1291
+ const data = await res.json();
1292
+ const cmd = data.command;
1293
+ if (!cmd || executedCommands.has(cmd.commandId)) return;
1294
+ executedCommands.add(cmd.commandId);
1295
+ log("info", `[remote-control] action=${cmd.action} commandId=${cmd.commandId}`);
1296
+ let ok = true;
1297
+ let error;
1298
+ try {
1299
+ if (cmd.action === "start") {
1300
+ await startGateway(info);
1301
+ gatewayStartTime = Date.now();
1302
+ } else if (cmd.action === "stop") {
1303
+ await stopGateway(info);
1304
+ gatewayStartTime = null;
1305
+ } else if (cmd.action === "restart") {
1306
+ await restartGateway(info);
1307
+ gatewayStartTime = Date.now();
1308
+ }
1309
+ } catch (err) {
1310
+ ok = false;
1311
+ error = String(err);
1312
+ log("warn", `[remote-control] failed: ${error}`);
1313
+ }
1314
+ const ackCtrl = new AbortController();
1315
+ const ackTimer = setTimeout(() => ackCtrl.abort(), 3e3);
1316
+ await proxyFetch2(REMOTE_API_URL2 + "/v1/control/ack", {
1317
+ method: "POST",
1318
+ headers: {
1319
+ "Content-Type": "application/json",
1320
+ Authorization: "Bearer " + cfg.machineToken
1321
+ },
1322
+ body: JSON.stringify({ commandId: cmd.commandId, ok, error }),
1323
+ signal: ackCtrl.signal
1324
+ }).catch(() => {
1325
+ });
1326
+ clearTimeout(ackTimer);
1327
+ } catch {
1328
+ }
1329
+ }, 15e3);
1330
+ }
1331
+ var _PKG_VER, pkgVersion;
1332
+ var init_server = __esm({
1333
+ "src/dashboard/server.ts"() {
1334
+ "use strict";
1335
+ init_config();
1336
+ init_openclaw();
1337
+ init_health_checker();
1338
+ init_logger();
1339
+ init_process_manager();
1340
+ init_workspace_scanner();
1341
+ init_cost_scanner();
1342
+ _PKG_VER = true ? "0.6.1" : "0.2.1";
1343
+ pkgVersion = _PKG_VER;
1344
+ }
1345
+ });
23
1346
 
24
1347
  // src/index.ts
1348
+ init_brand();
25
1349
  import { spawnSync } from "child_process";
26
1350
  import { Command } from "commander";
27
1351
 
28
1352
  // src/commands/watch.ts
1353
+ init_config();
1354
+ init_logger();
1355
+ init_health_checker();
1356
+ init_process_manager();
1357
+ init_openclaw();
29
1358
  import { spawn } from "child_process";
30
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, unlinkSync, openSync } from "fs";
31
- import chalk from "chalk";
32
- import { join as join2 } from "path";
1359
+ import { writeFileSync as writeFileSync3, readFileSync as readFileSync6, existsSync as existsSync8, unlinkSync, openSync } from "fs";
1360
+ import chalk3 from "chalk";
1361
+ import { join as join9 } from "path";
33
1362
 
34
1363
  // src/telemetry.ts
1364
+ init_brand();
35
1365
  import { createHash, randomUUID } from "crypto";
36
- import { execSync } from "child_process";
37
- import { readFileSync, writeFileSync, existsSync } from "fs";
38
- import { join } from "path";
1366
+ import { execSync as execSync2 } from "child_process";
1367
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
1368
+ import { join as join5 } from "path";
39
1369
  var MEASUREMENT_ID = "G-B46J8RT804";
40
1370
  var API_SECRET = "qkqms1nURj2S02Q3WqO7GQ";
41
1371
  var ENDPOINT = `https://www.google-analytics.com/mp/collect?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`;
42
- var TELEMETRY_FILE = join(APP_HOME, "telemetry.json");
1372
+ var TELEMETRY_FILE = join5(APP_HOME, "telemetry.json");
43
1373
  function loadState() {
44
- if (existsSync(TELEMETRY_FILE)) {
1374
+ if (existsSync4(TELEMETRY_FILE)) {
45
1375
  try {
46
- return JSON.parse(readFileSync(TELEMETRY_FILE, "utf-8"));
1376
+ return JSON.parse(readFileSync3(TELEMETRY_FILE, "utf-8"));
47
1377
  } catch {
48
1378
  }
49
1379
  }
@@ -57,7 +1387,7 @@ function loadState() {
57
1387
  }
58
1388
  function saveState(state) {
59
1389
  try {
60
- writeFileSync(TELEMETRY_FILE, JSON.stringify(state, null, 2) + "\n");
1390
+ writeFileSync2(TELEMETRY_FILE, JSON.stringify(state, null, 2) + "\n");
61
1391
  } catch {
62
1392
  }
63
1393
  }
@@ -66,7 +1396,7 @@ function sha256(input) {
66
1396
  }
67
1397
  function tryExec(cmd) {
68
1398
  try {
69
- return execSync(cmd, { stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).toString().trim();
1399
+ return execSync2(cmd, { stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).toString().trim();
70
1400
  } catch {
71
1401
  return null;
72
1402
  }
@@ -166,7 +1496,7 @@ async function watchDaemon(options) {
166
1496
  initLogger();
167
1497
  ensureDoctorHome();
168
1498
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
169
- writeFileSync2(PID_FILE, String(process.pid));
1499
+ writeFileSync3(PID_FILE, String(process.pid));
170
1500
  trackCommand("watch start", true).catch(() => {
171
1501
  });
172
1502
  log("info", "OpenClaw Doctor started (foreground)");
@@ -175,7 +1505,7 @@ async function watchDaemon(options) {
175
1505
  log("info", `Check interval: ${config.checkInterval}s`);
176
1506
  log("info", `PID: ${process.pid}`);
177
1507
  if (options.dashboard) {
178
- const { startDashboard: startDashboard2 } = await import("./server-3JMOADVR.js");
1508
+ const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_server(), server_exports));
179
1509
  startDashboard2({ config: options.config });
180
1510
  }
181
1511
  const throttle = new RestartThrottle(config.maxRestartsPerHour);
@@ -183,7 +1513,7 @@ async function watchDaemon(options) {
183
1513
  let isRestarting = false;
184
1514
  async function tick() {
185
1515
  if (isRestarting) return;
186
- if (existsSync2(STOP_FLAG_FILE)) {
1516
+ if (existsSync8(STOP_FLAG_FILE)) {
187
1517
  log("info", "Gateway is manually stopped \u2014 skipping auto-restart");
188
1518
  return;
189
1519
  }
@@ -231,7 +1561,7 @@ function daemonize(options) {
231
1561
  ensureDoctorHome();
232
1562
  const existingPid = readDaemonPid();
233
1563
  if (existingPid && isProcessAlive(existingPid)) {
234
- console.log(chalk.yellow(`Doctor is already running (PID ${existingPid})`));
1564
+ console.log(chalk3.yellow(`Doctor is already running (PID ${existingPid})`));
235
1565
  return;
236
1566
  }
237
1567
  const execArgv = process.execArgv.filter(
@@ -241,8 +1571,8 @@ function daemonize(options) {
241
1571
  (a) => a !== "-d" && a !== "--daemon"
242
1572
  );
243
1573
  const fullArgs = [...execArgv, ...scriptArgs];
244
- const outLog = join2(DOCTOR_LOG_DIR, "daemon.out.log");
245
- const errLog = join2(DOCTOR_LOG_DIR, "daemon.err.log");
1574
+ const outLog = join9(DOCTOR_LOG_DIR, "daemon.out.log");
1575
+ const errLog = join9(DOCTOR_LOG_DIR, "daemon.err.log");
246
1576
  const out = openSync(outLog, "a");
247
1577
  const err = openSync(errLog, "a");
248
1578
  const child = spawn(process.execPath, fullArgs, {
@@ -251,20 +1581,20 @@ function daemonize(options) {
251
1581
  env: { ...process.env, OPENCLAW_DOCTOR_DAEMON: "1" }
252
1582
  });
253
1583
  const pid = child.pid;
254
- writeFileSync2(PID_FILE, String(pid));
1584
+ writeFileSync3(PID_FILE, String(pid));
255
1585
  child.unref();
256
- console.log(chalk.green(`Doctor started in background (PID ${pid})`));
257
- console.log(chalk.gray(` Logs: ${outLog}`));
258
- console.log(chalk.gray(` Stop: openclaw-doctor stop`));
1586
+ console.log(chalk3.green(`Doctor started in background (PID ${pid})`));
1587
+ console.log(chalk3.gray(` Logs: ${outLog}`));
1588
+ console.log(chalk3.gray(` Stop: openclaw-doctor stop`));
259
1589
  }
260
1590
  async function stopDaemon(options) {
261
1591
  const pid = readDaemonPid();
262
1592
  if (!pid) {
263
- console.log(chalk.yellow("Doctor is not running (no PID file)"));
1593
+ console.log(chalk3.yellow("Doctor is not running (no PID file)"));
264
1594
  return;
265
1595
  }
266
1596
  if (!isProcessAlive(pid)) {
267
- console.log(chalk.yellow(`Doctor is not running (PID ${pid} is dead, cleaning up)`));
1597
+ console.log(chalk3.yellow(`Doctor is not running (PID ${pid} is dead, cleaning up)`));
268
1598
  try {
269
1599
  unlinkSync(PID_FILE);
270
1600
  } catch {
@@ -273,11 +1603,11 @@ async function stopDaemon(options) {
273
1603
  }
274
1604
  try {
275
1605
  process.kill(pid, "SIGTERM");
276
- console.log(chalk.green(`Doctor stopped (PID ${pid})`));
1606
+ console.log(chalk3.green(`Doctor stopped (PID ${pid})`));
277
1607
  trackCommand("watch stop", true).catch(() => {
278
1608
  });
279
1609
  } catch (err) {
280
- console.log(chalk.red(`Failed to stop Doctor (PID ${pid}): ${err}`));
1610
+ console.log(chalk3.red(`Failed to stop Doctor (PID ${pid}): ${err}`));
281
1611
  trackCommand("watch stop", false).catch(() => {
282
1612
  });
283
1613
  }
@@ -288,8 +1618,8 @@ async function stopDaemon(options) {
288
1618
  }
289
1619
  }
290
1620
  function readDaemonPid() {
291
- if (!existsSync2(PID_FILE)) return null;
292
- const raw = readFileSync2(PID_FILE, "utf-8").trim();
1621
+ if (!existsSync8(PID_FILE)) return null;
1622
+ const raw = readFileSync6(PID_FILE, "utf-8").trim();
293
1623
  const pid = parseInt(raw, 10);
294
1624
  return isNaN(pid) ? null : pid;
295
1625
  }
@@ -303,7 +1633,10 @@ function isProcessAlive(pid) {
303
1633
  }
304
1634
 
305
1635
  // src/commands/status.ts
306
- import chalk2 from "chalk";
1636
+ init_config();
1637
+ init_openclaw();
1638
+ init_health_checker();
1639
+ import chalk4 from "chalk";
307
1640
  async function showStatus(options) {
308
1641
  const config = loadConfig(options.config);
309
1642
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
@@ -337,32 +1670,32 @@ async function showStatus(options) {
337
1670
  process.exit(result.healthy ? 0 : 1);
338
1671
  return;
339
1672
  }
340
- console.log(chalk2.bold("\n OpenClaw Doctor\n"));
1673
+ console.log(chalk4.bold("\n OpenClaw Doctor\n"));
341
1674
  if (result.healthy) {
342
1675
  console.log(
343
- chalk2.green.bold(` Gateway: HEALTHY`) + chalk2.gray(` (port ${info.gatewayPort}, ${result.durationMs}ms)`)
1676
+ chalk4.green.bold(` Gateway: HEALTHY`) + chalk4.gray(` (port ${info.gatewayPort}, ${result.durationMs}ms)`)
344
1677
  );
345
1678
  } else if (result.gateway) {
346
- console.log(chalk2.yellow.bold(` Gateway: DEGRADED`) + chalk2.gray(` (responded but ok=false)`));
1679
+ console.log(chalk4.yellow.bold(` Gateway: DEGRADED`) + chalk4.gray(` (responded but ok=false)`));
347
1680
  } else {
348
- console.log(chalk2.red.bold(` Gateway: UNREACHABLE`));
349
- if (result.error) console.log(chalk2.red(` ${result.error}`));
1681
+ console.log(chalk4.red.bold(` Gateway: UNREACHABLE`));
1682
+ if (result.error) console.log(chalk4.red(` ${result.error}`));
350
1683
  }
351
1684
  if (result.channels && result.channels.length > 0) {
352
1685
  console.log();
353
1686
  for (const ch of result.channels ?? []) {
354
- const icon = ch.ok ? chalk2.green("ok") : chalk2.red("fail");
355
- console.log(` ${chalk2.gray("Channel")} ${ch.name}: ${icon}`);
1687
+ const icon = ch.ok ? chalk4.green("ok") : chalk4.red("fail");
1688
+ console.log(` ${chalk4.gray("Channel")} ${ch.name}: ${icon}`);
356
1689
  }
357
1690
  }
358
1691
  if (info.agents.length > 0) {
359
1692
  console.log();
360
1693
  const agentList = info.agents.map((a) => a.isDefault ? `${a.name} (default)` : a.name).join(", ");
361
- console.log(chalk2.gray(` Agents: ${agentList}`));
1694
+ console.log(chalk4.gray(` Agents: ${agentList}`));
362
1695
  }
363
1696
  console.log();
364
- console.log(chalk2.gray(` OpenClaw ${info.version ?? "unknown"}`));
365
- console.log(chalk2.gray(` Config: ${info.configPath}`));
1697
+ console.log(chalk4.gray(` OpenClaw ${info.version ?? "unknown"}`));
1698
+ console.log(chalk4.gray(` Config: ${info.configPath}`));
366
1699
  console.log();
367
1700
  trackCommand("status", true).catch(() => {
368
1701
  });
@@ -370,13 +1703,16 @@ async function showStatus(options) {
370
1703
  }
371
1704
 
372
1705
  // src/commands/doctor.ts
373
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3 } from "fs";
374
- import chalk3 from "chalk";
1706
+ init_config();
1707
+ init_openclaw();
1708
+ init_health_checker();
1709
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
1710
+ import chalk5 from "chalk";
375
1711
  function findConfigIssues(configPath) {
376
- if (!existsSync3(configPath)) return [];
1712
+ if (!existsSync9(configPath)) return [];
377
1713
  let raw;
378
1714
  try {
379
- raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1715
+ raw = JSON.parse(readFileSync7(configPath, "utf-8"));
380
1716
  } catch {
381
1717
  return [{ path: "root", message: "Config file is not valid JSON", fix: () => {
382
1718
  } }];
@@ -403,7 +1739,7 @@ function findConfigIssues(configPath) {
403
1739
  const origFix = originalFixes[i];
404
1740
  issues[i].fix = () => {
405
1741
  origFix();
406
- writeFileSync3(configPath, JSON.stringify(raw, null, 2) + "\n");
1742
+ writeFileSync4(configPath, JSON.stringify(raw, null, 2) + "\n");
407
1743
  };
408
1744
  }
409
1745
  }
@@ -412,53 +1748,53 @@ function findConfigIssues(configPath) {
412
1748
  async function runDoctor(options) {
413
1749
  const config = loadConfig(options.config);
414
1750
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
415
- console.log(chalk3.bold("\n OpenClaw Doctor \u2014 Full Diagnostics\n"));
416
- console.log(chalk3.gray(" [0/4] Config validation"));
1751
+ console.log(chalk5.bold("\n OpenClaw Doctor \u2014 Full Diagnostics\n"));
1752
+ console.log(chalk5.gray(" [0/4] Config validation"));
417
1753
  const issues = findConfigIssues(info.configPath);
418
1754
  if (issues.length === 0) {
419
- console.log(chalk3.green(" Config: valid"));
1755
+ console.log(chalk5.green(" Config: valid"));
420
1756
  } else {
421
1757
  for (const issue of issues) {
422
- console.log(chalk3.red(` ${issue.path}: ${issue.message}`));
1758
+ console.log(chalk5.red(` ${issue.path}: ${issue.message}`));
423
1759
  }
424
1760
  if (options.fix) {
425
1761
  for (const issue of issues) {
426
1762
  issue.fix();
427
- console.log(chalk3.green(` Fixed: ${issue.path}`));
1763
+ console.log(chalk5.green(` Fixed: ${issue.path}`));
428
1764
  }
429
- console.log(chalk3.green(` Config saved: ${info.configPath}`));
1765
+ console.log(chalk5.green(` Config saved: ${info.configPath}`));
430
1766
  } else {
431
- console.log(chalk3.yellow(" Run with --fix to auto-repair"));
1767
+ console.log(chalk5.yellow(" Run with --fix to auto-repair"));
432
1768
  }
433
1769
  }
434
- console.log(chalk3.gray("\n [1/4] OpenClaw binary"));
1770
+ console.log(chalk5.gray("\n [1/4] OpenClaw binary"));
435
1771
  if (info.cliBinPath) {
436
- console.log(chalk3.green(` Found: ${info.cliBinPath}`));
437
- console.log(chalk3.gray(` Node: ${info.nodePath}`));
438
- if (info.version) console.log(chalk3.gray(` Version: ${info.version}`));
1772
+ console.log(chalk5.green(` Found: ${info.cliBinPath}`));
1773
+ console.log(chalk5.gray(` Node: ${info.nodePath}`));
1774
+ if (info.version) console.log(chalk5.gray(` Version: ${info.version}`));
439
1775
  } else {
440
- console.log(chalk3.red(" Not found \u2014 openclaw CLI is not installed or not in PATH"));
1776
+ console.log(chalk5.red(" Not found \u2014 openclaw CLI is not installed or not in PATH"));
441
1777
  }
442
- console.log(chalk3.gray("\n [2/4] Gateway health"));
1778
+ console.log(chalk5.gray("\n [2/4] Gateway health"));
443
1779
  const result = await checkHealth(info);
444
1780
  if (result.healthy) {
445
- console.log(chalk3.green(` Gateway: healthy (port ${info.gatewayPort}, ${result.durationMs}ms)`));
1781
+ console.log(chalk5.green(` Gateway: healthy (port ${info.gatewayPort}, ${result.durationMs}ms)`));
446
1782
  } else if (result.gateway) {
447
- console.log(chalk3.yellow(` Gateway: responded but degraded`));
1783
+ console.log(chalk5.yellow(` Gateway: responded but degraded`));
448
1784
  } else {
449
- console.log(chalk3.red(` Gateway: unreachable`));
450
- if (result.error) console.log(chalk3.red(` ${result.error}`));
1785
+ console.log(chalk5.red(` Gateway: unreachable`));
1786
+ if (result.error) console.log(chalk5.red(` ${result.error}`));
451
1787
  }
452
- console.log(chalk3.gray("\n [3/4] Channels"));
1788
+ console.log(chalk5.gray("\n [3/4] Channels"));
453
1789
  if (result.channels.length > 0) {
454
1790
  for (const ch of result.channels) {
455
- const status = ch.ok ? chalk3.green("ok") : chalk3.red("fail");
1791
+ const status = ch.ok ? chalk5.green("ok") : chalk5.red("fail");
456
1792
  console.log(` ${ch.name}: ${status}`);
457
1793
  }
458
1794
  } else {
459
- console.log(chalk3.yellow(" No channel data available"));
1795
+ console.log(chalk5.yellow(" No channel data available"));
460
1796
  }
461
- console.log(chalk3.gray("\n [4/4] OpenClaw built-in doctor"));
1797
+ console.log(chalk5.gray("\n [4/4] OpenClaw built-in doctor"));
462
1798
  const doctorOutput = await runOpenClawCmd(info, "doctor");
463
1799
  if (doctorOutput) {
464
1800
  const lines = doctorOutput.split("\n");
@@ -468,24 +1804,24 @@ async function runDoctor(options) {
468
1804
  console.log(` ${line}`);
469
1805
  }
470
1806
  } else {
471
- console.log(chalk3.yellow(" Could not run openclaw doctor"));
1807
+ console.log(chalk5.yellow(" Could not run openclaw doctor"));
472
1808
  }
473
1809
  if (options.fix) {
474
- console.log(chalk3.gray("\n [5/5] Auto-repair"));
1810
+ console.log(chalk5.gray("\n [5/5] Auto-repair"));
475
1811
  if (!result.healthy) {
476
- console.log(chalk3.yellow(" Gateway unhealthy \u2014 running openclaw doctor --repair --non-interactive"));
1812
+ console.log(chalk5.yellow(" Gateway unhealthy \u2014 running openclaw doctor --repair --non-interactive"));
477
1813
  const repairOutput = await runOpenClawCmd(info, "doctor --repair --non-interactive");
478
1814
  if (repairOutput) {
479
1815
  const lines = repairOutput.split("\n");
480
1816
  for (const line of lines.slice(0, 30)) {
481
1817
  if (line.trim()) console.log(` ${line}`);
482
1818
  }
483
- console.log(chalk3.green(" Repair completed"));
1819
+ console.log(chalk5.green(" Repair completed"));
484
1820
  } else {
485
- console.log(chalk3.yellow(" Could not run repair (openclaw CLI unavailable)"));
1821
+ console.log(chalk5.yellow(" Could not run repair (openclaw CLI unavailable)"));
486
1822
  }
487
1823
  } else {
488
- console.log(chalk3.green(" Gateway healthy \u2014 no repair needed"));
1824
+ console.log(chalk5.green(" Gateway healthy \u2014 no repair needed"));
489
1825
  }
490
1826
  }
491
1827
  trackCommand("doctor", true).catch(() => {
@@ -494,8 +1830,11 @@ async function runDoctor(options) {
494
1830
  }
495
1831
 
496
1832
  // src/commands/logs.ts
497
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
498
- import chalk4 from "chalk";
1833
+ init_config();
1834
+ init_openclaw();
1835
+ init_config();
1836
+ import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
1837
+ import chalk6 from "chalk";
499
1838
  function showLogs(options) {
500
1839
  const config = loadConfig(options.config);
501
1840
  const maxLines = parseInt(options.lines ?? "50", 10);
@@ -505,64 +1844,69 @@ function showLogs(options) {
505
1844
  }
506
1845
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
507
1846
  const logFile = options.error ? `${info.logDir}/gateway.err.log` : `${info.logDir}/gateway.log`;
508
- if (!existsSync4(logFile)) {
509
- console.log(chalk4.yellow(`Log file not found: ${logFile}`));
1847
+ if (!existsSync10(logFile)) {
1848
+ console.log(chalk6.yellow(`Log file not found: ${logFile}`));
510
1849
  return;
511
1850
  }
512
- console.log(chalk4.blue.bold(`
1851
+ console.log(chalk6.blue.bold(`
513
1852
  ${logFile}
514
1853
  `));
515
- const content = readFileSync4(logFile, "utf-8");
1854
+ const content = readFileSync8(logFile, "utf-8");
516
1855
  const lines = content.trim().split("\n");
517
1856
  const tail = lines.slice(-maxLines);
518
1857
  for (const line of tail) {
519
1858
  if (line.includes("[error]") || line.includes("[ERROR]")) {
520
- console.log(chalk4.red(line));
1859
+ console.log(chalk6.red(line));
521
1860
  } else if (line.includes("[warn]") || line.includes("[WARN]")) {
522
- console.log(chalk4.yellow(line));
1861
+ console.log(chalk6.yellow(line));
523
1862
  } else {
524
- console.log(chalk4.gray(line));
1863
+ console.log(chalk6.gray(line));
525
1864
  }
526
1865
  }
527
1866
  console.log();
528
1867
  }
529
1868
  function showDoctorLogs(maxLines) {
530
- const { readdirSync } = __require("fs");
531
- const { join: join4 } = __require("path");
532
- if (!existsSync4(DOCTOR_LOG_DIR)) {
533
- console.log(chalk4.yellow("No doctor logs found."));
1869
+ const { readdirSync: readdirSync5 } = __require("fs");
1870
+ const { join: join12 } = __require("path");
1871
+ if (!existsSync10(DOCTOR_LOG_DIR)) {
1872
+ console.log(chalk6.yellow("No doctor logs found."));
534
1873
  return;
535
1874
  }
536
- const files = readdirSync(DOCTOR_LOG_DIR).filter((f) => f.endsWith(".log")).sort().reverse();
1875
+ const files = readdirSync5(DOCTOR_LOG_DIR).filter((f) => f.endsWith(".log")).sort().reverse();
537
1876
  if (files.length === 0) {
538
- console.log(chalk4.yellow("No doctor log files found."));
1877
+ console.log(chalk6.yellow("No doctor log files found."));
539
1878
  return;
540
1879
  }
541
1880
  const latest = files[0];
542
- console.log(chalk4.blue.bold(`
543
- ${join4(DOCTOR_LOG_DIR, latest)}
1881
+ console.log(chalk6.blue.bold(`
1882
+ ${join12(DOCTOR_LOG_DIR, latest)}
544
1883
  `));
545
- const content = readFileSync4(join4(DOCTOR_LOG_DIR, latest), "utf-8");
1884
+ const content = readFileSync8(join12(DOCTOR_LOG_DIR, latest), "utf-8");
546
1885
  const lines = content.trim().split("\n");
547
1886
  const tail = lines.slice(-maxLines);
548
1887
  for (const line of tail) {
549
1888
  if (line.includes("[ERROR]")) {
550
- console.log(chalk4.red(line));
1889
+ console.log(chalk6.red(line));
551
1890
  } else if (line.includes("[WARN]")) {
552
- console.log(chalk4.yellow(line));
1891
+ console.log(chalk6.yellow(line));
553
1892
  } else if (line.includes("[SUCCESS]")) {
554
- console.log(chalk4.green(line));
1893
+ console.log(chalk6.green(line));
555
1894
  } else {
556
- console.log(chalk4.gray(line));
1895
+ console.log(chalk6.gray(line));
557
1896
  }
558
1897
  }
559
1898
  console.log();
560
1899
  }
561
1900
 
562
1901
  // src/commands/gateway.ts
563
- import chalk5 from "chalk";
564
- import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
565
- var _VER = true ? "0.5.0" : void 0;
1902
+ init_config();
1903
+ init_config();
1904
+ init_openclaw();
1905
+ init_process_manager();
1906
+ init_logger();
1907
+ import chalk7 from "chalk";
1908
+ import { writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "fs";
1909
+ var _VER = true ? "0.6.1" : void 0;
566
1910
  async function gatewayStart(options) {
567
1911
  const config = loadConfig(options.config);
568
1912
  initLogger();
@@ -574,11 +1918,11 @@ async function gatewayStart(options) {
574
1918
  }
575
1919
  const result = await startGateway(info);
576
1920
  if (result.success) {
577
- console.log(chalk5.green("Gateway started (auto-restart resumed)"));
1921
+ console.log(chalk7.green("Gateway started (auto-restart resumed)"));
578
1922
  trackCommand("gateway start", true, _VER).catch(() => {
579
1923
  });
580
1924
  } else {
581
- console.log(chalk5.red(`Failed to start gateway: ${result.error}`));
1925
+ console.log(chalk7.red(`Failed to start gateway: ${result.error}`));
582
1926
  trackCommand("gateway start", false, _VER).catch(() => {
583
1927
  });
584
1928
  process.exit(1);
@@ -591,13 +1935,13 @@ async function gatewayStop(options) {
591
1935
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
592
1936
  const result = await stopGateway(info);
593
1937
  if (result.success) {
594
- writeFileSync4(STOP_FLAG_FILE, (/* @__PURE__ */ new Date()).toISOString());
595
- console.log(chalk5.green("Gateway stopped (auto-restart paused)"));
596
- console.log(chalk5.gray(" Run `gateway start` to resume."));
1938
+ writeFileSync5(STOP_FLAG_FILE, (/* @__PURE__ */ new Date()).toISOString());
1939
+ console.log(chalk7.green("Gateway stopped (auto-restart paused)"));
1940
+ console.log(chalk7.gray(" Run `gateway start` to resume."));
597
1941
  trackCommand("gateway stop", true, _VER).catch(() => {
598
1942
  });
599
1943
  } else {
600
- console.log(chalk5.red(`Failed to stop gateway: ${result.error}`));
1944
+ console.log(chalk7.red(`Failed to stop gateway: ${result.error}`));
601
1945
  trackCommand("gateway stop", false, _VER).catch(() => {
602
1946
  });
603
1947
  process.exit(1);
@@ -614,11 +1958,11 @@ async function gatewayRestart(options) {
614
1958
  }
615
1959
  const result = await restartGateway(info);
616
1960
  if (result.success) {
617
- console.log(chalk5.green("Gateway restarted (auto-restart resumed)"));
1961
+ console.log(chalk7.green("Gateway restarted (auto-restart resumed)"));
618
1962
  trackCommand("gateway restart", true, _VER).catch(() => {
619
1963
  });
620
1964
  } else {
621
- console.log(chalk5.red(`Failed to restart gateway: ${result.error}`));
1965
+ console.log(chalk7.red(`Failed to restart gateway: ${result.error}`));
622
1966
  trackCommand("gateway restart", false, _VER).catch(() => {
623
1967
  });
624
1968
  process.exit(1);
@@ -626,42 +1970,44 @@ async function gatewayRestart(options) {
626
1970
  }
627
1971
 
628
1972
  // src/commands/memory.ts
629
- import chalk6 from "chalk";
630
- import { existsSync as existsSync6, statSync } from "fs";
631
- import { join as join3 } from "path";
632
- import { homedir } from "os";
633
- function expandHome(p) {
634
- return p.startsWith("~/") ? join3(homedir(), p.slice(2)) : p;
1973
+ init_config();
1974
+ init_openclaw();
1975
+ import chalk8 from "chalk";
1976
+ import { existsSync as existsSync12, statSync as statSync3 } from "fs";
1977
+ import { join as join10 } from "path";
1978
+ import { homedir as homedir5 } from "os";
1979
+ function expandHome2(p) {
1980
+ return p.startsWith("~/") ? join10(homedir5(), p.slice(2)) : p;
635
1981
  }
636
1982
  async function memoryStatus(options) {
637
1983
  const config = loadConfig(options.config);
638
1984
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
639
- console.log(chalk6.bold("\n Memory Status\n"));
1985
+ console.log(chalk8.bold("\n Memory Status\n"));
640
1986
  for (const agent of info.agents) {
641
1987
  const ws = agent.workspace;
642
1988
  if (!ws) continue;
643
- const wsPath = expandHome(ws);
644
- const memPath = join3(wsPath, "MEMORY.md");
645
- const exists = existsSync6(memPath);
646
- const sizeKB = exists ? Math.round(statSync(memPath).size / 1024) : 0;
1989
+ const wsPath = expandHome2(ws);
1990
+ const memPath = join10(wsPath, "MEMORY.md");
1991
+ const exists = existsSync12(memPath);
1992
+ const sizeKB = exists ? Math.round(statSync3(memPath).size / 1024) : 0;
647
1993
  const warn = sizeKB > 50;
648
- const indicator = warn ? chalk6.yellow("\u26A0") : chalk6.green("\u2713");
649
- const sizeStr = warn ? chalk6.yellow(`${sizeKB}KB`) : chalk6.gray(`${sizeKB}KB`);
650
- console.log(` ${indicator} ${agent.name.padEnd(16)} MEMORY.md: ${sizeStr}${warn ? chalk6.yellow(" \u2014 exceeds 50KB, may waste tokens") : ""}`);
1994
+ const indicator = warn ? chalk8.yellow("\u26A0") : chalk8.green("\u2713");
1995
+ const sizeStr = warn ? chalk8.yellow(`${sizeKB}KB`) : chalk8.gray(`${sizeKB}KB`);
1996
+ console.log(` ${indicator} ${agent.name.padEnd(16)} MEMORY.md: ${sizeStr}${warn ? chalk8.yellow(" \u2014 exceeds 50KB, may waste tokens") : ""}`);
651
1997
  }
652
1998
  console.log();
653
1999
  }
654
2000
  async function memorySearch(query, options) {
655
2001
  const config = loadConfig(options.config);
656
2002
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
657
- console.log(chalk6.bold(`
2003
+ console.log(chalk8.bold(`
658
2004
  Searching memory: "${query}"
659
2005
  `));
660
2006
  const output = await runOpenClawCmd(info, `memory search "${query}"`);
661
2007
  if (output) {
662
2008
  console.log(output);
663
2009
  } else {
664
- console.log(chalk6.yellow(" No results or openclaw memory search unavailable"));
2010
+ console.log(chalk8.yellow(" No results or openclaw memory search unavailable"));
665
2011
  }
666
2012
  console.log();
667
2013
  }
@@ -669,41 +2015,276 @@ async function memoryCompact(options) {
669
2015
  const config = loadConfig(options.config);
670
2016
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
671
2017
  const flag = options.dryRun ? "--dry-run" : "";
672
- console.log(chalk6.bold(`
2018
+ console.log(chalk8.bold(`
673
2019
  Memory Compact${options.dryRun ? " (dry run)" : ""}
674
2020
  `));
675
2021
  const output = await runOpenClawCmd(info, `memory compact ${flag}`);
676
2022
  if (output) {
677
2023
  console.log(output);
678
2024
  } else {
679
- console.log(chalk6.yellow(" openclaw memory compact not available"));
2025
+ console.log(chalk8.yellow(" openclaw memory compact not available"));
680
2026
  }
681
2027
  console.log();
682
2028
  }
683
2029
 
2030
+ // src/index.ts
2031
+ init_server();
2032
+ init_openclaw();
2033
+
684
2034
  // src/commands/telemetry.ts
685
- import chalk7 from "chalk";
2035
+ import chalk9 from "chalk";
686
2036
  function telemetryOn() {
687
2037
  setOptOut(false);
688
- console.log(chalk7.green("\u2713 Telemetry enabled. Thanks for helping improve OpenClaw!"));
2038
+ console.log(chalk9.green("\u2713 Telemetry enabled. Thanks for helping improve OpenClaw!"));
689
2039
  }
690
2040
  function telemetryOff() {
691
2041
  setOptOut(true);
692
- console.log(chalk7.yellow("\u2713 Telemetry disabled. Set OPENCLAW_NO_TELEMETRY=1 to suppress permanently."));
2042
+ console.log(chalk9.yellow("\u2713 Telemetry disabled. Set OPENCLAW_NO_TELEMETRY=1 to suppress permanently."));
693
2043
  }
694
2044
  function telemetryStatus() {
695
2045
  const { optOut, clientId } = getTelemetryStatus();
696
- const status = optOut ? chalk7.red("disabled") : chalk7.green("enabled");
2046
+ const status = optOut ? chalk9.red("disabled") : chalk9.green("enabled");
697
2047
  console.log(`Telemetry: ${status}`);
698
- console.log(`Client ID: ${chalk7.dim(clientId)}`);
2048
+ console.log(`Client ID: ${chalk9.dim(clientId)}`);
2049
+ console.log();
2050
+ console.log(chalk9.dim("Toggle: openclaw telemetry on/off"));
2051
+ console.log(chalk9.dim("Env: OPENCLAW_NO_TELEMETRY=1"));
2052
+ }
2053
+
2054
+ // src/commands/remote.ts
2055
+ init_config();
2056
+ import chalk10 from "chalk";
2057
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync13, mkdirSync as mkdirSync3 } from "fs";
2058
+ import { join as join11 } from "path";
2059
+ import { randomUUID as randomUUID2, randomBytes, createHash as createHash2 } from "crypto";
2060
+ import { createServer as createServer2 } from "http";
2061
+ import { exec as exec3 } from "child_process";
2062
+ import { hostname } from "os";
2063
+ async function proxyFetch(url, init) {
2064
+ const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.ALL_PROXY || process.env.all_proxy;
2065
+ if (proxyUrl) {
2066
+ try {
2067
+ const { ProxyAgent, fetch: undiciFetch } = await import("undici");
2068
+ const dispatcher = new ProxyAgent(proxyUrl);
2069
+ return undiciFetch(url, { ...init, dispatcher });
2070
+ } catch {
2071
+ }
2072
+ }
2073
+ return fetch(url, init);
2074
+ }
2075
+ var REMOTE_API_URL = "https://api.openclaw-cli.app";
2076
+ var REMOTE_CONFIG_FILE = join11(DOCTOR_HOME, "remote.json");
2077
+ function loadRemoteConfig() {
2078
+ try {
2079
+ if (existsSync13(REMOTE_CONFIG_FILE)) {
2080
+ return JSON.parse(readFileSync10(REMOTE_CONFIG_FILE, "utf-8"));
2081
+ }
2082
+ } catch {
2083
+ }
2084
+ return {
2085
+ enabled: false,
2086
+ machineId: randomUUID2(),
2087
+ machineToken: "",
2088
+ reportUrl: REMOTE_API_URL + "/v1/report",
2089
+ lastReport: null
2090
+ };
2091
+ }
2092
+ function saveRemoteConfig(config) {
2093
+ const dir = join11(REMOTE_CONFIG_FILE, "..");
2094
+ if (!existsSync13(dir)) mkdirSync3(dir, { recursive: true });
2095
+ writeFileSync6(REMOTE_CONFIG_FILE, JSON.stringify(config, null, 2));
2096
+ }
2097
+ var OAUTH_CLIENT_ID = Buffer.from(
2098
+ "MjM5NDk1OTI0Nzk4LTJtZWFhaTllcjZybTR1bnN0bW4zZmRldHRqZHM2bGJjLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t",
2099
+ "base64"
2100
+ ).toString();
2101
+ var _OCS = Buffer.from("R09DU1BYLUNaUU9jN1RKYnp3dk1TNWRxMU91N0IwcFBRU1U=", "base64").toString();
2102
+ var OAUTH_REDIRECT_URI = "http://localhost:9876/callback";
2103
+ var OAUTH_AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
2104
+ var OAUTH_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
2105
+ var OAUTH_SCOPE = "openid email profile";
2106
+ var OAUTH_TIMEOUT_MS = 6e4;
2107
+ function generateCodeVerifier() {
2108
+ return randomBytes(32).toString("base64url");
2109
+ }
2110
+ function generateCodeChallenge(verifier) {
2111
+ return createHash2("sha256").update(verifier).digest("base64url");
2112
+ }
2113
+ function openBrowser(url) {
2114
+ const cmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
2115
+ exec3(cmd);
2116
+ }
2117
+ function waitForOAuthCallback(codeVerifier) {
2118
+ return new Promise((resolve3, reject) => {
2119
+ const server = createServer2(async (req, res) => {
2120
+ if (!req.url?.startsWith("/callback")) {
2121
+ res.writeHead(404);
2122
+ res.end("Not found");
2123
+ return;
2124
+ }
2125
+ const url = new URL(req.url, "http://localhost:9876");
2126
+ const code = url.searchParams.get("code");
2127
+ const error = url.searchParams.get("error");
2128
+ if (error || !code) {
2129
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
2130
+ res.end(
2131
+ `<html><body style='text-align:center;padding:40px;font-family:system-ui'><h2 style='color:#e53e3e'>Login failed</h2><p>${error || "No authorization code received."}</p></body></html>`
2132
+ );
2133
+ clearTimeout(timeout);
2134
+ server.close();
2135
+ reject(new Error(error || "No authorization code received"));
2136
+ return;
2137
+ }
2138
+ try {
2139
+ const tokenRes = await proxyFetch(OAUTH_TOKEN_ENDPOINT, {
2140
+ method: "POST",
2141
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2142
+ body: new URLSearchParams({
2143
+ code,
2144
+ client_id: OAUTH_CLIENT_ID,
2145
+ redirect_uri: OAUTH_REDIRECT_URI,
2146
+ grant_type: "authorization_code",
2147
+ code_verifier: codeVerifier,
2148
+ client_secret: _OCS
2149
+ }).toString()
2150
+ });
2151
+ if (!tokenRes.ok) {
2152
+ const errText = await tokenRes.text();
2153
+ throw new Error(`Token exchange failed: ${tokenRes.status} ${errText}`);
2154
+ }
2155
+ const tokenData = await tokenRes.json();
2156
+ const payload = JSON.parse(
2157
+ Buffer.from(tokenData.id_token.split(".")[1], "base64url").toString()
2158
+ );
2159
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
2160
+ res.end(
2161
+ "<html><head><meta charset='utf-8'></head><body style='text-align:center;padding:40px;font-family:system-ui;background:#0d0f14;color:#e2e8f0'><h2 style='color:#38a169'>&#10003; Logged in! This tab will close in <span id='c'>5</span>s...</h2><script>var n=5;var t=setInterval(function(){n--;document.getElementById('c').textContent=n;if(n<=0){clearInterval(t);window.close();}},1000);</script></body></html>"
2162
+ );
2163
+ clearTimeout(timeout);
2164
+ server.close();
2165
+ resolve3({ idToken: tokenData.id_token, email: payload.email });
2166
+ } catch (err) {
2167
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
2168
+ res.end(
2169
+ `<html><body style='text-align:center;padding:40px;font-family:system-ui'><h2 style='color:#e53e3e'>Login failed</h2><p>${String(err)}</p></body></html>`
2170
+ );
2171
+ clearTimeout(timeout);
2172
+ server.close();
2173
+ reject(err);
2174
+ }
2175
+ });
2176
+ const timeout = setTimeout(() => {
2177
+ server.close();
2178
+ reject(new Error("Login timed out \u2014 no callback received within 60 seconds"));
2179
+ }, OAUTH_TIMEOUT_MS);
2180
+ server.listen(9876, "127.0.0.1");
2181
+ });
2182
+ }
2183
+ async function remoteLogin(_options) {
2184
+ const config = loadRemoteConfig();
2185
+ console.log(chalk10.cyan.bold("\n Remote Monitoring Login\n"));
2186
+ console.log(chalk10.gray(" Opening browser for Google sign-in...\n"));
2187
+ const codeVerifier = generateCodeVerifier();
2188
+ const codeChallenge = generateCodeChallenge(codeVerifier);
2189
+ const authUrl = OAUTH_AUTH_ENDPOINT + "?" + new URLSearchParams({
2190
+ client_id: OAUTH_CLIENT_ID,
2191
+ redirect_uri: OAUTH_REDIRECT_URI,
2192
+ response_type: "code",
2193
+ scope: OAUTH_SCOPE,
2194
+ code_challenge: codeChallenge,
2195
+ code_challenge_method: "S256",
2196
+ access_type: "offline"
2197
+ }).toString();
2198
+ openBrowser(authUrl);
2199
+ let idToken;
2200
+ let email;
2201
+ try {
2202
+ const result = await waitForOAuthCallback(codeVerifier);
2203
+ idToken = result.idToken;
2204
+ email = result.email;
2205
+ } catch (err) {
2206
+ console.log(chalk10.red(` ${String(err)}`));
2207
+ return;
2208
+ }
2209
+ console.log(chalk10.gray(" Registering this machine..."));
2210
+ try {
2211
+ const host = hostname();
2212
+ const os = process.platform + "/" + process.arch;
2213
+ const controller = new AbortController();
2214
+ const timer = setTimeout(() => controller.abort(), 1e4);
2215
+ const res = await proxyFetch(REMOTE_API_URL + "/v1/machines/register", {
2216
+ method: "POST",
2217
+ headers: {
2218
+ "Content-Type": "application/json",
2219
+ Authorization: "Bearer " + idToken
2220
+ },
2221
+ body: JSON.stringify({ hostname: host, os, version: "unknown" }),
2222
+ signal: controller.signal
2223
+ });
2224
+ clearTimeout(timer);
2225
+ if (!res.ok) {
2226
+ const err = await res.text();
2227
+ console.log(chalk10.red(` Registration failed: ${res.status} ${err}`));
2228
+ return;
2229
+ }
2230
+ const data = await res.json();
2231
+ config.machineId = data.machineId;
2232
+ config.machineToken = data.machineToken;
2233
+ config.enabled = true;
2234
+ config.reportUrl = REMOTE_API_URL + "/v1/report";
2235
+ saveRemoteConfig(config);
2236
+ console.log(chalk10.green.bold(`
2237
+ Logged in as ${email}`));
2238
+ console.log(chalk10.gray(` Machine registered: ${host}`));
2239
+ console.log(
2240
+ chalk10.gray(" Remote monitoring ready. Run: openclaw-cli remote enable\n")
2241
+ );
2242
+ } catch (err) {
2243
+ console.log(chalk10.red(` Error: ${err}`));
2244
+ }
2245
+ }
2246
+ async function remoteEnable(_options) {
2247
+ const config = loadRemoteConfig();
2248
+ if (!config.machineToken) {
2249
+ console.log(
2250
+ chalk10.red(" \u2717 Not logged in. Run: openclaw-cli remote login")
2251
+ );
2252
+ return;
2253
+ }
2254
+ config.enabled = true;
2255
+ saveRemoteConfig(config);
2256
+ console.log(chalk10.green(" Remote reporting enabled."));
2257
+ }
2258
+ async function remoteDisable(_options) {
2259
+ const config = loadRemoteConfig();
2260
+ config.enabled = false;
2261
+ saveRemoteConfig(config);
2262
+ console.log(chalk10.yellow(" Remote reporting disabled."));
2263
+ }
2264
+ async function remoteStatus(_options) {
2265
+ const config = loadRemoteConfig();
2266
+ console.log(chalk10.cyan.bold("\n Remote Monitoring Status\n"));
2267
+ console.log(
2268
+ ` Enabled: ${config.enabled ? chalk10.green("yes") : chalk10.gray("no")}`
2269
+ );
2270
+ console.log(
2271
+ ` Machine ID: ${chalk10.white(config.machineId || "(none)")}`
2272
+ );
2273
+ console.log(
2274
+ ` Token: ${config.machineToken ? chalk10.green("configured") : chalk10.red("not set")}`
2275
+ );
2276
+ console.log(
2277
+ ` Report URL: ${chalk10.gray(config.reportUrl)}`
2278
+ );
2279
+ console.log(
2280
+ ` Last Report: ${config.lastReport ? chalk10.gray(config.lastReport) : chalk10.gray("never")}`
2281
+ );
699
2282
  console.log();
700
- console.log(chalk7.dim("Toggle: openclaw telemetry on/off"));
701
- console.log(chalk7.dim("Env: OPENCLAW_NO_TELEMETRY=1"));
702
2283
  }
703
2284
 
704
2285
  // src/index.ts
705
- var _PKG_VER = true ? "0.5.0" : "0.2.1";
706
- var version = _PKG_VER;
2286
+ var _PKG_VER2 = true ? "0.6.1" : "0.2.1";
2287
+ var version = _PKG_VER2;
707
2288
  printFirstRunNotice();
708
2289
  var program = new Command();
709
2290
  program.name(BINARY_NAME).description(`${DISPLAY_NAME} \u2014 health monitor and management for OpenClaw services`).version(version);
@@ -739,6 +2320,11 @@ addGlobalOpts(
739
2320
  addGlobalOpts(
740
2321
  mem.command("compact").description("Compact agent memory (proxies to openclaw memory compact)").option("--dry-run", "Preview without applying")
741
2322
  ).action(memoryCompact);
2323
+ var remote = program.command("remote").description("Remote monitoring \u2014 report gateway status to openclaw-cli.app");
2324
+ addGlobalOpts(remote.command("login").description("Authenticate and register this machine")).action(remoteLogin);
2325
+ addGlobalOpts(remote.command("enable").description("Enable remote reporting")).action(remoteEnable);
2326
+ addGlobalOpts(remote.command("disable").description("Disable remote reporting")).action(remoteDisable);
2327
+ addGlobalOpts(remote.command("status").description("Show remote monitoring config")).action(remoteStatus);
742
2328
  addGlobalOpts(
743
2329
  program.command("logs").description("View logs (proxies to openclaw logs; use --doctor for our own logs)").option("-n, --lines <count>", "Number of lines to show", "50").option("--error", "Show gateway error logs").option("--doctor", "Show our own event logs").option("--tail", "Follow logs in real time").allowUnknownOption()
744
2330
  ).action((options, cmd) => {