openclaw-service 0.4.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 +1752 -147
  2. package/dist/index.pkg.cjs +7385 -0
  3. package/package.json +6 -7
  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-UXL57WT4.js +0 -1149
  72. package/dist/server-GZ3MD6AH.js +0 -7
package/dist/index.js CHANGED
@@ -1,48 +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
- __require,
10
- checkHealth,
11
- detectOpenClaw,
12
- ensureDoctorHome,
13
- initLogger,
14
- loadConfig,
15
- log,
16
- restartGateway,
17
- runOpenClawCmd,
18
- startDashboard,
19
- startGateway,
20
- stopGateway
21
- } from "./chunk-UXL57WT4.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
+ });
22
1346
 
23
1347
  // src/index.ts
1348
+ init_brand();
24
1349
  import { spawnSync } from "child_process";
25
1350
  import { Command } from "commander";
26
1351
 
27
1352
  // src/commands/watch.ts
1353
+ init_config();
1354
+ init_logger();
1355
+ init_health_checker();
1356
+ init_process_manager();
1357
+ init_openclaw();
28
1358
  import { spawn } from "child_process";
29
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, unlinkSync, openSync } from "fs";
30
- import chalk from "chalk";
31
- 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";
32
1362
 
33
1363
  // src/telemetry.ts
1364
+ init_brand();
34
1365
  import { createHash, randomUUID } from "crypto";
35
- import { execSync } from "child_process";
36
- import { readFileSync, writeFileSync, existsSync } from "fs";
37
- 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";
38
1369
  var MEASUREMENT_ID = "G-B46J8RT804";
39
1370
  var API_SECRET = "qkqms1nURj2S02Q3WqO7GQ";
40
1371
  var ENDPOINT = `https://www.google-analytics.com/mp/collect?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`;
41
- var TELEMETRY_FILE = join(APP_HOME, "telemetry.json");
1372
+ var TELEMETRY_FILE = join5(APP_HOME, "telemetry.json");
42
1373
  function loadState() {
43
- if (existsSync(TELEMETRY_FILE)) {
1374
+ if (existsSync4(TELEMETRY_FILE)) {
44
1375
  try {
45
- return JSON.parse(readFileSync(TELEMETRY_FILE, "utf-8"));
1376
+ return JSON.parse(readFileSync3(TELEMETRY_FILE, "utf-8"));
46
1377
  } catch {
47
1378
  }
48
1379
  }
@@ -56,7 +1387,7 @@ function loadState() {
56
1387
  }
57
1388
  function saveState(state) {
58
1389
  try {
59
- writeFileSync(TELEMETRY_FILE, JSON.stringify(state, null, 2) + "\n");
1390
+ writeFileSync2(TELEMETRY_FILE, JSON.stringify(state, null, 2) + "\n");
60
1391
  } catch {
61
1392
  }
62
1393
  }
@@ -65,7 +1396,7 @@ function sha256(input) {
65
1396
  }
66
1397
  function tryExec(cmd) {
67
1398
  try {
68
- return execSync(cmd, { stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).toString().trim();
1399
+ return execSync2(cmd, { stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).toString().trim();
69
1400
  } catch {
70
1401
  return null;
71
1402
  }
@@ -165,7 +1496,7 @@ async function watchDaemon(options) {
165
1496
  initLogger();
166
1497
  ensureDoctorHome();
167
1498
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
168
- writeFileSync2(PID_FILE, String(process.pid));
1499
+ writeFileSync3(PID_FILE, String(process.pid));
169
1500
  trackCommand("watch start", true).catch(() => {
170
1501
  });
171
1502
  log("info", "OpenClaw Doctor started (foreground)");
@@ -174,7 +1505,7 @@ async function watchDaemon(options) {
174
1505
  log("info", `Check interval: ${config.checkInterval}s`);
175
1506
  log("info", `PID: ${process.pid}`);
176
1507
  if (options.dashboard) {
177
- const { startDashboard: startDashboard2 } = await import("./server-GZ3MD6AH.js");
1508
+ const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_server(), server_exports));
178
1509
  startDashboard2({ config: options.config });
179
1510
  }
180
1511
  const throttle = new RestartThrottle(config.maxRestartsPerHour);
@@ -182,6 +1513,10 @@ async function watchDaemon(options) {
182
1513
  let isRestarting = false;
183
1514
  async function tick() {
184
1515
  if (isRestarting) return;
1516
+ if (existsSync8(STOP_FLAG_FILE)) {
1517
+ log("info", "Gateway is manually stopped \u2014 skipping auto-restart");
1518
+ return;
1519
+ }
185
1520
  const result = await checkHealth(info);
186
1521
  if (result.healthy) {
187
1522
  consecutiveFailures = 0;
@@ -204,7 +1539,7 @@ async function watchDaemon(options) {
204
1539
  consecutiveFailures = 0;
205
1540
  throttle.record();
206
1541
  await restartGateway(info);
207
- log("info", "Waiting 30s for gateway to start...");
1542
+ log("info", "Waiting 60s for gateway to start...");
208
1543
  await new Promise((r) => setTimeout(r, 6e4));
209
1544
  isRestarting = false;
210
1545
  }
@@ -226,7 +1561,7 @@ function daemonize(options) {
226
1561
  ensureDoctorHome();
227
1562
  const existingPid = readDaemonPid();
228
1563
  if (existingPid && isProcessAlive(existingPid)) {
229
- console.log(chalk.yellow(`Doctor is already running (PID ${existingPid})`));
1564
+ console.log(chalk3.yellow(`Doctor is already running (PID ${existingPid})`));
230
1565
  return;
231
1566
  }
232
1567
  const execArgv = process.execArgv.filter(
@@ -236,8 +1571,8 @@ function daemonize(options) {
236
1571
  (a) => a !== "-d" && a !== "--daemon"
237
1572
  );
238
1573
  const fullArgs = [...execArgv, ...scriptArgs];
239
- const outLog = join2(DOCTOR_LOG_DIR, "daemon.out.log");
240
- 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");
241
1576
  const out = openSync(outLog, "a");
242
1577
  const err = openSync(errLog, "a");
243
1578
  const child = spawn(process.execPath, fullArgs, {
@@ -246,20 +1581,20 @@ function daemonize(options) {
246
1581
  env: { ...process.env, OPENCLAW_DOCTOR_DAEMON: "1" }
247
1582
  });
248
1583
  const pid = child.pid;
249
- writeFileSync2(PID_FILE, String(pid));
1584
+ writeFileSync3(PID_FILE, String(pid));
250
1585
  child.unref();
251
- console.log(chalk.green(`Doctor started in background (PID ${pid})`));
252
- console.log(chalk.gray(` Logs: ${outLog}`));
253
- 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`));
254
1589
  }
255
1590
  async function stopDaemon(options) {
256
1591
  const pid = readDaemonPid();
257
1592
  if (!pid) {
258
- console.log(chalk.yellow("Doctor is not running (no PID file)"));
1593
+ console.log(chalk3.yellow("Doctor is not running (no PID file)"));
259
1594
  return;
260
1595
  }
261
1596
  if (!isProcessAlive(pid)) {
262
- 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)`));
263
1598
  try {
264
1599
  unlinkSync(PID_FILE);
265
1600
  } catch {
@@ -268,11 +1603,11 @@ async function stopDaemon(options) {
268
1603
  }
269
1604
  try {
270
1605
  process.kill(pid, "SIGTERM");
271
- console.log(chalk.green(`Doctor stopped (PID ${pid})`));
1606
+ console.log(chalk3.green(`Doctor stopped (PID ${pid})`));
272
1607
  trackCommand("watch stop", true).catch(() => {
273
1608
  });
274
1609
  } catch (err) {
275
- console.log(chalk.red(`Failed to stop Doctor (PID ${pid}): ${err}`));
1610
+ console.log(chalk3.red(`Failed to stop Doctor (PID ${pid}): ${err}`));
276
1611
  trackCommand("watch stop", false).catch(() => {
277
1612
  });
278
1613
  }
@@ -283,8 +1618,8 @@ async function stopDaemon(options) {
283
1618
  }
284
1619
  }
285
1620
  function readDaemonPid() {
286
- if (!existsSync2(PID_FILE)) return null;
287
- const raw = readFileSync2(PID_FILE, "utf-8").trim();
1621
+ if (!existsSync8(PID_FILE)) return null;
1622
+ const raw = readFileSync6(PID_FILE, "utf-8").trim();
288
1623
  const pid = parseInt(raw, 10);
289
1624
  return isNaN(pid) ? null : pid;
290
1625
  }
@@ -298,7 +1633,10 @@ function isProcessAlive(pid) {
298
1633
  }
299
1634
 
300
1635
  // src/commands/status.ts
301
- import chalk2 from "chalk";
1636
+ init_config();
1637
+ init_openclaw();
1638
+ init_health_checker();
1639
+ import chalk4 from "chalk";
302
1640
  async function showStatus(options) {
303
1641
  const config = loadConfig(options.config);
304
1642
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
@@ -332,32 +1670,32 @@ async function showStatus(options) {
332
1670
  process.exit(result.healthy ? 0 : 1);
333
1671
  return;
334
1672
  }
335
- console.log(chalk2.bold("\n OpenClaw Doctor\n"));
1673
+ console.log(chalk4.bold("\n OpenClaw Doctor\n"));
336
1674
  if (result.healthy) {
337
1675
  console.log(
338
- 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)`)
339
1677
  );
340
1678
  } else if (result.gateway) {
341
- 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)`));
342
1680
  } else {
343
- console.log(chalk2.red.bold(` Gateway: UNREACHABLE`));
344
- 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}`));
345
1683
  }
346
1684
  if (result.channels && result.channels.length > 0) {
347
1685
  console.log();
348
1686
  for (const ch of result.channels ?? []) {
349
- const icon = ch.ok ? chalk2.green("ok") : chalk2.red("fail");
350
- 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}`);
351
1689
  }
352
1690
  }
353
1691
  if (info.agents.length > 0) {
354
1692
  console.log();
355
1693
  const agentList = info.agents.map((a) => a.isDefault ? `${a.name} (default)` : a.name).join(", ");
356
- console.log(chalk2.gray(` Agents: ${agentList}`));
1694
+ console.log(chalk4.gray(` Agents: ${agentList}`));
357
1695
  }
358
1696
  console.log();
359
- console.log(chalk2.gray(` OpenClaw ${info.version ?? "unknown"}`));
360
- console.log(chalk2.gray(` Config: ${info.configPath}`));
1697
+ console.log(chalk4.gray(` OpenClaw ${info.version ?? "unknown"}`));
1698
+ console.log(chalk4.gray(` Config: ${info.configPath}`));
361
1699
  console.log();
362
1700
  trackCommand("status", true).catch(() => {
363
1701
  });
@@ -365,13 +1703,16 @@ async function showStatus(options) {
365
1703
  }
366
1704
 
367
1705
  // src/commands/doctor.ts
368
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3 } from "fs";
369
- 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";
370
1711
  function findConfigIssues(configPath) {
371
- if (!existsSync3(configPath)) return [];
1712
+ if (!existsSync9(configPath)) return [];
372
1713
  let raw;
373
1714
  try {
374
- raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1715
+ raw = JSON.parse(readFileSync7(configPath, "utf-8"));
375
1716
  } catch {
376
1717
  return [{ path: "root", message: "Config file is not valid JSON", fix: () => {
377
1718
  } }];
@@ -398,7 +1739,7 @@ function findConfigIssues(configPath) {
398
1739
  const origFix = originalFixes[i];
399
1740
  issues[i].fix = () => {
400
1741
  origFix();
401
- writeFileSync3(configPath, JSON.stringify(raw, null, 2) + "\n");
1742
+ writeFileSync4(configPath, JSON.stringify(raw, null, 2) + "\n");
402
1743
  };
403
1744
  }
404
1745
  }
@@ -407,53 +1748,53 @@ function findConfigIssues(configPath) {
407
1748
  async function runDoctor(options) {
408
1749
  const config = loadConfig(options.config);
409
1750
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
410
- console.log(chalk3.bold("\n OpenClaw Doctor \u2014 Full Diagnostics\n"));
411
- 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"));
412
1753
  const issues = findConfigIssues(info.configPath);
413
1754
  if (issues.length === 0) {
414
- console.log(chalk3.green(" Config: valid"));
1755
+ console.log(chalk5.green(" Config: valid"));
415
1756
  } else {
416
1757
  for (const issue of issues) {
417
- console.log(chalk3.red(` ${issue.path}: ${issue.message}`));
1758
+ console.log(chalk5.red(` ${issue.path}: ${issue.message}`));
418
1759
  }
419
1760
  if (options.fix) {
420
1761
  for (const issue of issues) {
421
1762
  issue.fix();
422
- console.log(chalk3.green(` Fixed: ${issue.path}`));
1763
+ console.log(chalk5.green(` Fixed: ${issue.path}`));
423
1764
  }
424
- console.log(chalk3.green(` Config saved: ${info.configPath}`));
1765
+ console.log(chalk5.green(` Config saved: ${info.configPath}`));
425
1766
  } else {
426
- console.log(chalk3.yellow(" Run with --fix to auto-repair"));
1767
+ console.log(chalk5.yellow(" Run with --fix to auto-repair"));
427
1768
  }
428
1769
  }
429
- console.log(chalk3.gray("\n [1/4] OpenClaw binary"));
1770
+ console.log(chalk5.gray("\n [1/4] OpenClaw binary"));
430
1771
  if (info.cliBinPath) {
431
- console.log(chalk3.green(` Found: ${info.cliBinPath}`));
432
- console.log(chalk3.gray(` Node: ${info.nodePath}`));
433
- 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}`));
434
1775
  } else {
435
- 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"));
436
1777
  }
437
- console.log(chalk3.gray("\n [2/4] Gateway health"));
1778
+ console.log(chalk5.gray("\n [2/4] Gateway health"));
438
1779
  const result = await checkHealth(info);
439
1780
  if (result.healthy) {
440
- 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)`));
441
1782
  } else if (result.gateway) {
442
- console.log(chalk3.yellow(` Gateway: responded but degraded`));
1783
+ console.log(chalk5.yellow(` Gateway: responded but degraded`));
443
1784
  } else {
444
- console.log(chalk3.red(` Gateway: unreachable`));
445
- 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}`));
446
1787
  }
447
- console.log(chalk3.gray("\n [3/4] Channels"));
1788
+ console.log(chalk5.gray("\n [3/4] Channels"));
448
1789
  if (result.channels.length > 0) {
449
1790
  for (const ch of result.channels) {
450
- const status = ch.ok ? chalk3.green("ok") : chalk3.red("fail");
1791
+ const status = ch.ok ? chalk5.green("ok") : chalk5.red("fail");
451
1792
  console.log(` ${ch.name}: ${status}`);
452
1793
  }
453
1794
  } else {
454
- console.log(chalk3.yellow(" No channel data available"));
1795
+ console.log(chalk5.yellow(" No channel data available"));
455
1796
  }
456
- console.log(chalk3.gray("\n [4/4] OpenClaw built-in doctor"));
1797
+ console.log(chalk5.gray("\n [4/4] OpenClaw built-in doctor"));
457
1798
  const doctorOutput = await runOpenClawCmd(info, "doctor");
458
1799
  if (doctorOutput) {
459
1800
  const lines = doctorOutput.split("\n");
@@ -463,24 +1804,24 @@ async function runDoctor(options) {
463
1804
  console.log(` ${line}`);
464
1805
  }
465
1806
  } else {
466
- console.log(chalk3.yellow(" Could not run openclaw doctor"));
1807
+ console.log(chalk5.yellow(" Could not run openclaw doctor"));
467
1808
  }
468
1809
  if (options.fix) {
469
- console.log(chalk3.gray("\n [5/5] Auto-repair"));
1810
+ console.log(chalk5.gray("\n [5/5] Auto-repair"));
470
1811
  if (!result.healthy) {
471
- 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"));
472
1813
  const repairOutput = await runOpenClawCmd(info, "doctor --repair --non-interactive");
473
1814
  if (repairOutput) {
474
1815
  const lines = repairOutput.split("\n");
475
1816
  for (const line of lines.slice(0, 30)) {
476
1817
  if (line.trim()) console.log(` ${line}`);
477
1818
  }
478
- console.log(chalk3.green(" Repair completed"));
1819
+ console.log(chalk5.green(" Repair completed"));
479
1820
  } else {
480
- console.log(chalk3.yellow(" Could not run repair (openclaw CLI unavailable)"));
1821
+ console.log(chalk5.yellow(" Could not run repair (openclaw CLI unavailable)"));
481
1822
  }
482
1823
  } else {
483
- console.log(chalk3.green(" Gateway healthy \u2014 no repair needed"));
1824
+ console.log(chalk5.green(" Gateway healthy \u2014 no repair needed"));
484
1825
  }
485
1826
  }
486
1827
  trackCommand("doctor", true).catch(() => {
@@ -489,8 +1830,11 @@ async function runDoctor(options) {
489
1830
  }
490
1831
 
491
1832
  // src/commands/logs.ts
492
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
493
- 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";
494
1838
  function showLogs(options) {
495
1839
  const config = loadConfig(options.config);
496
1840
  const maxLines = parseInt(options.lines ?? "50", 10);
@@ -500,74 +1844,85 @@ function showLogs(options) {
500
1844
  }
501
1845
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
502
1846
  const logFile = options.error ? `${info.logDir}/gateway.err.log` : `${info.logDir}/gateway.log`;
503
- if (!existsSync4(logFile)) {
504
- console.log(chalk4.yellow(`Log file not found: ${logFile}`));
1847
+ if (!existsSync10(logFile)) {
1848
+ console.log(chalk6.yellow(`Log file not found: ${logFile}`));
505
1849
  return;
506
1850
  }
507
- console.log(chalk4.blue.bold(`
1851
+ console.log(chalk6.blue.bold(`
508
1852
  ${logFile}
509
1853
  `));
510
- const content = readFileSync4(logFile, "utf-8");
1854
+ const content = readFileSync8(logFile, "utf-8");
511
1855
  const lines = content.trim().split("\n");
512
1856
  const tail = lines.slice(-maxLines);
513
1857
  for (const line of tail) {
514
1858
  if (line.includes("[error]") || line.includes("[ERROR]")) {
515
- console.log(chalk4.red(line));
1859
+ console.log(chalk6.red(line));
516
1860
  } else if (line.includes("[warn]") || line.includes("[WARN]")) {
517
- console.log(chalk4.yellow(line));
1861
+ console.log(chalk6.yellow(line));
518
1862
  } else {
519
- console.log(chalk4.gray(line));
1863
+ console.log(chalk6.gray(line));
520
1864
  }
521
1865
  }
522
1866
  console.log();
523
1867
  }
524
1868
  function showDoctorLogs(maxLines) {
525
- const { readdirSync } = __require("fs");
526
- const { join: join4 } = __require("path");
527
- if (!existsSync4(DOCTOR_LOG_DIR)) {
528
- 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."));
529
1873
  return;
530
1874
  }
531
- 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();
532
1876
  if (files.length === 0) {
533
- console.log(chalk4.yellow("No doctor log files found."));
1877
+ console.log(chalk6.yellow("No doctor log files found."));
534
1878
  return;
535
1879
  }
536
1880
  const latest = files[0];
537
- console.log(chalk4.blue.bold(`
538
- ${join4(DOCTOR_LOG_DIR, latest)}
1881
+ console.log(chalk6.blue.bold(`
1882
+ ${join12(DOCTOR_LOG_DIR, latest)}
539
1883
  `));
540
- const content = readFileSync4(join4(DOCTOR_LOG_DIR, latest), "utf-8");
1884
+ const content = readFileSync8(join12(DOCTOR_LOG_DIR, latest), "utf-8");
541
1885
  const lines = content.trim().split("\n");
542
1886
  const tail = lines.slice(-maxLines);
543
1887
  for (const line of tail) {
544
1888
  if (line.includes("[ERROR]")) {
545
- console.log(chalk4.red(line));
1889
+ console.log(chalk6.red(line));
546
1890
  } else if (line.includes("[WARN]")) {
547
- console.log(chalk4.yellow(line));
1891
+ console.log(chalk6.yellow(line));
548
1892
  } else if (line.includes("[SUCCESS]")) {
549
- console.log(chalk4.green(line));
1893
+ console.log(chalk6.green(line));
550
1894
  } else {
551
- console.log(chalk4.gray(line));
1895
+ console.log(chalk6.gray(line));
552
1896
  }
553
1897
  }
554
1898
  console.log();
555
1899
  }
556
1900
 
557
1901
  // src/commands/gateway.ts
558
- import chalk5 from "chalk";
559
- var _VER = true ? "0.4.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;
560
1910
  async function gatewayStart(options) {
561
1911
  const config = loadConfig(options.config);
562
1912
  initLogger();
1913
+ ensureDoctorHome();
563
1914
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
1915
+ try {
1916
+ unlinkSync2(STOP_FLAG_FILE);
1917
+ } catch {
1918
+ }
564
1919
  const result = await startGateway(info);
565
1920
  if (result.success) {
566
- console.log(chalk5.green("Gateway started"));
1921
+ console.log(chalk7.green("Gateway started (auto-restart resumed)"));
567
1922
  trackCommand("gateway start", true, _VER).catch(() => {
568
1923
  });
569
1924
  } else {
570
- console.log(chalk5.red(`Failed to start gateway: ${result.error}`));
1925
+ console.log(chalk7.red(`Failed to start gateway: ${result.error}`));
571
1926
  trackCommand("gateway start", false, _VER).catch(() => {
572
1927
  });
573
1928
  process.exit(1);
@@ -576,14 +1931,17 @@ async function gatewayStart(options) {
576
1931
  async function gatewayStop(options) {
577
1932
  const config = loadConfig(options.config);
578
1933
  initLogger();
1934
+ ensureDoctorHome();
579
1935
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
580
1936
  const result = await stopGateway(info);
581
1937
  if (result.success) {
582
- console.log(chalk5.green("Gateway stopped"));
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."));
583
1941
  trackCommand("gateway stop", true, _VER).catch(() => {
584
1942
  });
585
1943
  } else {
586
- console.log(chalk5.red(`Failed to stop gateway: ${result.error}`));
1944
+ console.log(chalk7.red(`Failed to stop gateway: ${result.error}`));
587
1945
  trackCommand("gateway stop", false, _VER).catch(() => {
588
1946
  });
589
1947
  process.exit(1);
@@ -592,14 +1950,19 @@ async function gatewayStop(options) {
592
1950
  async function gatewayRestart(options) {
593
1951
  const config = loadConfig(options.config);
594
1952
  initLogger();
1953
+ ensureDoctorHome();
595
1954
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
1955
+ try {
1956
+ unlinkSync2(STOP_FLAG_FILE);
1957
+ } catch {
1958
+ }
596
1959
  const result = await restartGateway(info);
597
1960
  if (result.success) {
598
- console.log(chalk5.green("Gateway restarted"));
1961
+ console.log(chalk7.green("Gateway restarted (auto-restart resumed)"));
599
1962
  trackCommand("gateway restart", true, _VER).catch(() => {
600
1963
  });
601
1964
  } else {
602
- console.log(chalk5.red(`Failed to restart gateway: ${result.error}`));
1965
+ console.log(chalk7.red(`Failed to restart gateway: ${result.error}`));
603
1966
  trackCommand("gateway restart", false, _VER).catch(() => {
604
1967
  });
605
1968
  process.exit(1);
@@ -607,42 +1970,44 @@ async function gatewayRestart(options) {
607
1970
  }
608
1971
 
609
1972
  // src/commands/memory.ts
610
- import chalk6 from "chalk";
611
- import { existsSync as existsSync5, statSync } from "fs";
612
- import { join as join3 } from "path";
613
- import { homedir } from "os";
614
- function expandHome(p) {
615
- 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;
616
1981
  }
617
1982
  async function memoryStatus(options) {
618
1983
  const config = loadConfig(options.config);
619
1984
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
620
- console.log(chalk6.bold("\n Memory Status\n"));
1985
+ console.log(chalk8.bold("\n Memory Status\n"));
621
1986
  for (const agent of info.agents) {
622
1987
  const ws = agent.workspace;
623
1988
  if (!ws) continue;
624
- const wsPath = expandHome(ws);
625
- const memPath = join3(wsPath, "MEMORY.md");
626
- const exists = existsSync5(memPath);
627
- 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;
628
1993
  const warn = sizeKB > 50;
629
- const indicator = warn ? chalk6.yellow("\u26A0") : chalk6.green("\u2713");
630
- const sizeStr = warn ? chalk6.yellow(`${sizeKB}KB`) : chalk6.gray(`${sizeKB}KB`);
631
- 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") : ""}`);
632
1997
  }
633
1998
  console.log();
634
1999
  }
635
2000
  async function memorySearch(query, options) {
636
2001
  const config = loadConfig(options.config);
637
2002
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
638
- console.log(chalk6.bold(`
2003
+ console.log(chalk8.bold(`
639
2004
  Searching memory: "${query}"
640
2005
  `));
641
2006
  const output = await runOpenClawCmd(info, `memory search "${query}"`);
642
2007
  if (output) {
643
2008
  console.log(output);
644
2009
  } else {
645
- console.log(chalk6.yellow(" No results or openclaw memory search unavailable"));
2010
+ console.log(chalk8.yellow(" No results or openclaw memory search unavailable"));
646
2011
  }
647
2012
  console.log();
648
2013
  }
@@ -650,41 +2015,276 @@ async function memoryCompact(options) {
650
2015
  const config = loadConfig(options.config);
651
2016
  const info = detectOpenClaw(options.profile ?? config.openclawProfile);
652
2017
  const flag = options.dryRun ? "--dry-run" : "";
653
- console.log(chalk6.bold(`
2018
+ console.log(chalk8.bold(`
654
2019
  Memory Compact${options.dryRun ? " (dry run)" : ""}
655
2020
  `));
656
2021
  const output = await runOpenClawCmd(info, `memory compact ${flag}`);
657
2022
  if (output) {
658
2023
  console.log(output);
659
2024
  } else {
660
- console.log(chalk6.yellow(" openclaw memory compact not available"));
2025
+ console.log(chalk8.yellow(" openclaw memory compact not available"));
661
2026
  }
662
2027
  console.log();
663
2028
  }
664
2029
 
2030
+ // src/index.ts
2031
+ init_server();
2032
+ init_openclaw();
2033
+
665
2034
  // src/commands/telemetry.ts
666
- import chalk7 from "chalk";
2035
+ import chalk9 from "chalk";
667
2036
  function telemetryOn() {
668
2037
  setOptOut(false);
669
- 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!"));
670
2039
  }
671
2040
  function telemetryOff() {
672
2041
  setOptOut(true);
673
- 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."));
674
2043
  }
675
2044
  function telemetryStatus() {
676
2045
  const { optOut, clientId } = getTelemetryStatus();
677
- const status = optOut ? chalk7.red("disabled") : chalk7.green("enabled");
2046
+ const status = optOut ? chalk9.red("disabled") : chalk9.green("enabled");
678
2047
  console.log(`Telemetry: ${status}`);
679
- 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
+ );
680
2282
  console.log();
681
- console.log(chalk7.dim("Toggle: openclaw telemetry on/off"));
682
- console.log(chalk7.dim("Env: OPENCLAW_NO_TELEMETRY=1"));
683
2283
  }
684
2284
 
685
2285
  // src/index.ts
686
- var _PKG_VER = true ? "0.4.0" : "0.2.1";
687
- var version = _PKG_VER;
2286
+ var _PKG_VER2 = true ? "0.6.1" : "0.2.1";
2287
+ var version = _PKG_VER2;
688
2288
  printFirstRunNotice();
689
2289
  var program = new Command();
690
2290
  program.name(BINARY_NAME).description(`${DISPLAY_NAME} \u2014 health monitor and management for OpenClaw services`).version(version);
@@ -720,6 +2320,11 @@ addGlobalOpts(
720
2320
  addGlobalOpts(
721
2321
  mem.command("compact").description("Compact agent memory (proxies to openclaw memory compact)").option("--dry-run", "Preview without applying")
722
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);
723
2328
  addGlobalOpts(
724
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()
725
2330
  ).action((options, cmd) => {