@xerg/cli 0.1.8 → 0.1.10

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.
package/dist/index.js CHANGED
@@ -4,6 +4,112 @@
4
4
  import { readFileSync as readFileSync7 } from "fs";
5
5
  import { styleText as styleText2 } from "util";
6
6
 
7
+ // src/command-display.ts
8
+ var PACKAGE_NAME = "@xerg/cli";
9
+ var DEFAULT_COMMAND_PREFIX = "xerg";
10
+ function resolveCommandDisplay(context) {
11
+ const runner = detectPackageExecutor(context);
12
+ if (!runner) {
13
+ return {
14
+ prefix: DEFAULT_COMMAND_PREFIX,
15
+ name: DEFAULT_COMMAND_PREFIX
16
+ };
17
+ }
18
+ return {
19
+ prefix: `${runner} ${PACKAGE_NAME}`,
20
+ name: PACKAGE_NAME
21
+ };
22
+ }
23
+ function formatCommand(command2, commandPrefix = resolveCommandDisplay().prefix) {
24
+ const suffix = Array.isArray(command2) ? command2.join(" ") : command2;
25
+ return suffix ? `${commandPrefix} ${suffix}` : commandPrefix;
26
+ }
27
+ function detectPackageExecutor(context) {
28
+ const env = context?.env ?? process.env;
29
+ const argv2 = context?.argv ?? process.argv;
30
+ const userAgent = normalizeSignal(env.npm_config_user_agent);
31
+ const execPath = normalizeSignal(env.npm_execpath);
32
+ const argvPath = normalizeSignal(argv2[1]);
33
+ const fromArgvPath = detectRunnerFromArgvPath(argvPath);
34
+ if (fromArgvPath) {
35
+ return fromArgvPath;
36
+ }
37
+ if (looksLikeInstalledCli(argvPath)) {
38
+ return null;
39
+ }
40
+ const fromUserAgent = detectRunnerFromSignal(userAgent);
41
+ if (fromUserAgent) {
42
+ return fromUserAgent;
43
+ }
44
+ const fromExecPath = detectRunnerFromSignal(execPath);
45
+ if (fromExecPath) {
46
+ return fromExecPath;
47
+ }
48
+ if (argvPath.includes("/_npx/") || argvPath.includes("\\_npx\\")) {
49
+ return "npx";
50
+ }
51
+ if (argvPath.includes("/.yarn/") && argvPath.includes("/dlx/")) {
52
+ return "yarn dlx";
53
+ }
54
+ if (argvPath.includes("/bunx/") || argvPath.includes("\\bunx\\")) {
55
+ return "bunx";
56
+ }
57
+ if (argvPath.includes("/dlx-") || argvPath.includes("\\dlx-")) {
58
+ return "pnpm dlx";
59
+ }
60
+ if (userAgent || execPath) {
61
+ return "npx";
62
+ }
63
+ return null;
64
+ }
65
+ function detectRunnerFromArgvPath(argvPath) {
66
+ if (!argvPath) {
67
+ return null;
68
+ }
69
+ if (argvPath.includes("/_npx/") || argvPath.includes("\\_npx\\")) {
70
+ return "npx";
71
+ }
72
+ if (argvPath.includes("/.yarn/") && argvPath.includes("/dlx/")) {
73
+ return "yarn dlx";
74
+ }
75
+ if (argvPath.includes("/bunx/") || argvPath.includes("\\bunx\\")) {
76
+ return "bunx";
77
+ }
78
+ if (argvPath.includes("/dlx-") || argvPath.includes("\\dlx-")) {
79
+ return "pnpm dlx";
80
+ }
81
+ return null;
82
+ }
83
+ function looksLikeInstalledCli(argvPath) {
84
+ if (!argvPath) {
85
+ return false;
86
+ }
87
+ const normalized = argvPath.replaceAll("\\", "/");
88
+ return normalized.endsWith("/node_modules/.bin/xerg") || normalized.includes("/node_modules/@xerg/cli/dist/index.js") || normalized.endsWith("/bin/xerg");
89
+ }
90
+ function detectRunnerFromSignal(signal) {
91
+ if (!signal) {
92
+ return null;
93
+ }
94
+ const tokens = signal.split(/[^a-z0-9]+/).filter(Boolean);
95
+ if (tokens.includes("pnpm")) {
96
+ return "pnpm dlx";
97
+ }
98
+ if (tokens.includes("yarn")) {
99
+ return "yarn dlx";
100
+ }
101
+ if (tokens.includes("bun")) {
102
+ return "bunx";
103
+ }
104
+ if (tokens.includes("npm")) {
105
+ return "npx";
106
+ }
107
+ return null;
108
+ }
109
+ function normalizeSignal(value) {
110
+ return value?.trim().toLowerCase() ?? "";
111
+ }
112
+
7
113
  // src/commands/audit.ts
8
114
  import { readFileSync as readFileSync5 } from "fs";
9
115
  import { rmSync as rmSync4 } from "fs";
@@ -798,10 +904,17 @@ function segmentToRegExp(segment) {
798
904
  return new RegExp(`^${escaped}$`);
799
905
  }
800
906
  async function inspectOpenClawSources(options) {
907
+ options.onProgress?.("Checking local OpenClaw defaults...");
801
908
  const sources = await detectOpenClawSources(options);
802
909
  const notes = [];
910
+ options.onProgress?.(
911
+ sources.length > 0 ? `Detected ${sources.length} local source file${sources.length === 1 ? "" : "s"}.` : "No local OpenClaw source files were detected."
912
+ );
803
913
  if (sources.length === 0) {
804
914
  notes.push("No OpenClaw gateway logs or session files were detected.");
915
+ notes.push(
916
+ "Doctor checks local defaults by default. Use --remote or --railway to inspect remote targets."
917
+ );
805
918
  notes.push(
806
919
  "Use --log-file or --sessions-dir if your OpenClaw data lives outside the defaults."
807
920
  );
@@ -1383,6 +1496,7 @@ async function doctorOpenClaw(options) {
1383
1496
  return inspectOpenClawSources(options);
1384
1497
  }
1385
1498
  async function auditOpenClaw(options) {
1499
+ options.onProgress?.("Scanning for OpenClaw source files...");
1386
1500
  if (options.compare && options.noDb) {
1387
1501
  throw new Error(
1388
1502
  "The --compare flag needs local snapshot history. Remove --no-db or provide --db <path>."
@@ -1390,13 +1504,19 @@ async function auditOpenClaw(options) {
1390
1504
  }
1391
1505
  const sources = await detectOpenClawSources(options);
1392
1506
  if (sources.length === 0) {
1507
+ options.onProgress?.("No OpenClaw source files were detected.");
1393
1508
  throw new Error(
1394
- "No OpenClaw sources were detected. Run `xerg doctor` or provide --log-file / --sessions-dir."
1509
+ `No OpenClaw sources were detected. Run \`${options.commandPrefix ?? "xerg"} doctor\` or provide --log-file / --sessions-dir.`
1395
1510
  );
1396
1511
  }
1512
+ options.onProgress?.(`Detected ${sources.length} source file${sources.length === 1 ? "" : "s"}.`);
1513
+ options.onProgress?.("Normalizing OpenClaw source files...");
1397
1514
  const runs = normalizeOpenClawSources(sources, options.since);
1515
+ options.onProgress?.(`Normalized ${runs.length} run${runs.length === 1 ? "" : "s"}.`);
1516
+ options.onProgress?.("Computing waste and savings findings...");
1398
1517
  const findings = buildFindings(runs);
1399
1518
  const dbPath = options.noDb ? void 0 : options.dbPath ?? getDefaultDbPath();
1519
+ options.onProgress?.("Building audit summary...");
1400
1520
  const summary = buildAuditSummary({
1401
1521
  runs,
1402
1522
  findings,
@@ -1406,6 +1526,7 @@ async function auditOpenClaw(options) {
1406
1526
  comparisonKeyOverride: options.comparisonKeyOverride
1407
1527
  });
1408
1528
  if (options.compare && dbPath) {
1529
+ options.onProgress?.("Looking for a comparable baseline audit...");
1409
1530
  const baseline = readLatestComparableAuditSummary({
1410
1531
  dbPath,
1411
1532
  comparisonKey: summary.comparisonKey,
@@ -1421,6 +1542,7 @@ async function auditOpenClaw(options) {
1421
1542
  }
1422
1543
  }
1423
1544
  if (dbPath) {
1545
+ options.onProgress?.(`Persisting local snapshot to ${dbPath}...`);
1424
1546
  persistAudit(
1425
1547
  {
1426
1548
  summary,
@@ -1429,6 +1551,9 @@ async function auditOpenClaw(options) {
1429
1551
  },
1430
1552
  dbPath
1431
1553
  );
1554
+ options.onProgress?.("Local snapshot stored.");
1555
+ } else {
1556
+ options.onProgress?.("Skipping local snapshot persistence (--no-db).");
1432
1557
  }
1433
1558
  return summary;
1434
1559
  }
@@ -1638,7 +1763,16 @@ function renderCompareBlock(summary) {
1638
1763
  ...findingChanges.length > 0 ? findingChanges : ["- High-confidence waste changes: none"]
1639
1764
  ];
1640
1765
  }
1641
- function renderDoctorReport(report) {
1766
+ function renderDoctorReport(report, options) {
1767
+ const commandPrefix = options?.commandPrefix ?? "xerg";
1768
+ const nextSteps = report.canAudit ? [] : [
1769
+ "",
1770
+ "## Next steps",
1771
+ `- Try explicit local paths: ${commandPrefix} doctor --log-file /path/to/openclaw.log --sessions-dir /path/to/sessions`,
1772
+ `- Inspect an SSH host: ${commandPrefix} doctor --remote user@host`,
1773
+ `- Inspect a Railway service: ${commandPrefix} doctor --railway`,
1774
+ "- Remote audits still analyze locally after Xerg pulls the source files to your machine."
1775
+ ];
1642
1776
  const sections = [
1643
1777
  "# Xerg doctor",
1644
1778
  "",
@@ -1652,7 +1786,8 @@ function renderDoctorReport(report) {
1652
1786
  ...report.sources.length > 0 ? report.sources.map((source) => `- [${source.kind}] ${source.path}`) : ["- none"],
1653
1787
  "",
1654
1788
  "## Notes",
1655
- ...report.notes.map((note) => `- ${note}`)
1789
+ ...report.notes.map((note) => `- ${note}`),
1790
+ ...nextSteps
1656
1791
  ];
1657
1792
  return sections.join("\n");
1658
1793
  }
@@ -1811,6 +1946,23 @@ var NoDataError = class extends Error {
1811
1946
  }
1812
1947
  };
1813
1948
 
1949
+ // src/log.ts
1950
+ function createCliLogger(options) {
1951
+ return {
1952
+ info(message) {
1953
+ process.stderr.write(`${message}
1954
+ `);
1955
+ },
1956
+ verbose(message) {
1957
+ if (!options.verbose) {
1958
+ return;
1959
+ }
1960
+ process.stderr.write(`[verbose] ${message}
1961
+ `);
1962
+ }
1963
+ };
1964
+ }
1965
+
1814
1966
  // src/push/client.ts
1815
1967
  async function pushAudit(payload, config) {
1816
1968
  const url = `${config.apiUrl}/v1/audits`;
@@ -1916,7 +2068,7 @@ function loadPushConfig() {
1916
2068
  };
1917
2069
  }
1918
2070
  throw new Error(
1919
- `No API key configured. Set XERG_API_KEY, add "apiKey" to ${CONFIG_PATH}, or run \`xerg login\`.
2071
+ `No API key configured. Set XERG_API_KEY, add "apiKey" to ${CONFIG_PATH}, or run \`${formatCommand("login")}\`.
1920
2072
  Get your key at https://xerg.ai/dashboard/settings`
1921
2073
  );
1922
2074
  }
@@ -2124,14 +2276,19 @@ function buildComparisonKeyForRemote(source) {
2124
2276
  return `${source.host}:${logPath}:${sessPath}`;
2125
2277
  }
2126
2278
  async function pullRemoteFiles(opts) {
2127
- const { source, since, keepFiles = false } = opts;
2279
+ const { source, since, keepFiles = false, onProgress } = opts;
2280
+ onProgress?.(`Testing SSH connectivity to ${source.host}...`);
2128
2281
  const connectivity = testSshConnectivity(source);
2129
2282
  if (!connectivity.ok) {
2130
2283
  throw new Error(
2131
2284
  `Cannot connect to ${source.host}. Check SSH config and key access.${connectivity.error ? ` (${connectivity.error})` : ""}`
2132
2285
  );
2133
2286
  }
2287
+ onProgress?.("SSH connectivity OK.");
2134
2288
  const useRsync = isRsyncAvailable();
2289
+ onProgress?.(
2290
+ useRsync ? "Local rsync detected. Xerg will prefer rsync and fall back to tar over SSH if needed." : "Local rsync not detected. Xerg will pull files with tar over SSH."
2291
+ );
2135
2292
  const localBase = resolveLocalPath(source, keepFiles);
2136
2293
  const gatewayDir = join5(localBase, "gateway");
2137
2294
  const sessionsDir = join5(localBase, "sessions");
@@ -2139,6 +2296,7 @@ async function pullRemoteFiles(opts) {
2139
2296
  const remoteSessionsPath = source.sessionsDir ?? DEFAULT_SESSIONS_DIR;
2140
2297
  const { stdout: expandedSessions } = sshExec(source, `eval echo ${remoteSessionsPath}`);
2141
2298
  const resolvedSessionsPath = expandedSessions || remoteSessionsPath;
2299
+ onProgress?.("Checking remote default paths for gateway logs and sessions...");
2142
2300
  const { status: logPathExists } = sshExec(source, `test -e ${remoteLogPath} && echo exists`);
2143
2301
  const { status: sessPathExists } = sshExec(
2144
2302
  source,
@@ -2147,6 +2305,7 @@ async function pullRemoteFiles(opts) {
2147
2305
  let pulledLog = false;
2148
2306
  let pulledSessions = false;
2149
2307
  if (logPathExists === 0) {
2308
+ onProgress?.(`Pulling gateway logs from ${remoteLogPath}...`);
2150
2309
  const { stdout: isFile } = sshExec(source, `test -f ${remoteLogPath} && echo file`);
2151
2310
  if (isFile === "file") {
2152
2311
  const parentDir = remoteLogPath.slice(0, remoteLogPath.lastIndexOf("/")) || "/tmp";
@@ -2171,6 +2330,7 @@ async function pullRemoteFiles(opts) {
2171
2330
  }
2172
2331
  }
2173
2332
  if (sessPathExists === 0) {
2333
+ onProgress?.(`Pulling session files from ${resolvedSessionsPath}...`);
2174
2334
  pulledSessions = pullDirectory({
2175
2335
  source,
2176
2336
  remotePath: resolvedSessionsPath,
@@ -2194,11 +2354,13 @@ async function pullRemoteFiles(opts) {
2194
2354
  };
2195
2355
  if (pulledLog) result.logFile = gatewayDir;
2196
2356
  if (pulledSessions) result.sessionsDir = sessionsDir;
2357
+ onProgress?.("Remote files pulled successfully.");
2197
2358
  return result;
2198
2359
  }
2199
2360
  async function runRemoteDoctor(opts) {
2200
- const { source } = opts;
2361
+ const { source, onProgress } = opts;
2201
2362
  const notes = [];
2363
+ onProgress?.(`Testing SSH connectivity to ${source.host}...`);
2202
2364
  const connectivity = testSshConnectivity(source);
2203
2365
  if (!connectivity.ok) {
2204
2366
  return {
@@ -2222,7 +2384,9 @@ async function runRemoteDoctor(opts) {
2222
2384
  ]
2223
2385
  };
2224
2386
  }
2387
+ onProgress?.("SSH connectivity OK.");
2225
2388
  notes.push("SSH connectivity: OK");
2389
+ onProgress?.("Checking rsync availability locally and on the remote host...");
2226
2390
  const rsyncLocal = isRsyncAvailable();
2227
2391
  const rsyncRemote = isRemoteRsyncAvailable(source);
2228
2392
  notes.push(`rsync available locally: ${rsyncLocal ? "yes" : "no"}`);
@@ -2246,6 +2410,7 @@ async function runRemoteDoctor(opts) {
2246
2410
  totalBytes: Number.parseInt(sizeOut, 10) || 0
2247
2411
  };
2248
2412
  }
2413
+ onProgress?.("Inspecting remote default paths...");
2249
2414
  const gateway = checkPath(DEFAULT_GATEWAY_DIR);
2250
2415
  const sessions = checkPath(DEFAULT_SESSIONS_DIR);
2251
2416
  if (gateway.exists) {
@@ -2458,23 +2623,27 @@ function buildComparisonKeyForRailway(source) {
2458
2623
  return `railway-linked:${logPath}:${sessPath}`;
2459
2624
  }
2460
2625
  async function pullRemoteFilesRailway(opts) {
2461
- const { source, since, keepFiles = false } = opts;
2626
+ const { source, since, keepFiles = false, onProgress } = opts;
2462
2627
  const target = source.railway;
2628
+ onProgress?.("Testing Railway service connectivity...");
2463
2629
  const { status } = railwayExec("echo ok", target);
2464
2630
  if (status !== 0) {
2465
2631
  throw new Error(
2466
- `Cannot reach Railway service${target ? ` (project: ${target.projectId})` : " (linked project)"}. Check railway CLI auth and service configuration.`
2632
+ target ? `Cannot reach Railway service ${target.serviceId} (project: ${target.projectId}). Check the provided --project / --environment / --service values and confirm the Railway CLI can reach that service.` : "Cannot reach the Railway service linked to this directory. Run `railway link` here and choose the OpenClaw app service, or pass --project / --environment / --service explicitly."
2467
2633
  );
2468
2634
  }
2635
+ onProgress?.("Railway service reachable.");
2469
2636
  const localBase = resolveLocalPath2(source, keepFiles);
2470
2637
  const gatewayDir = join6(localBase, "gateway");
2471
2638
  const sessionsDir = join6(localBase, "sessions");
2472
2639
  const remoteLogPath = source.logFile ?? DEFAULT_GATEWAY_DIR2;
2640
+ onProgress?.("Checking Railway default paths for gateway logs and sessions...");
2473
2641
  const logCheck = checkRemotePath(remoteLogPath, target);
2474
2642
  const resolvedSessionsPath = findSessionsPath(target, source.sessionsDir);
2475
2643
  let pulledLog = false;
2476
2644
  let pulledSessions = false;
2477
- if (logCheck.exists) {
2645
+ if (logCheck.fileCount > 0) {
2646
+ onProgress?.(`Pulling gateway logs from ${remoteLogPath}...`);
2478
2647
  const { stdout: isFile } = railwayExec(`test -f ${remoteLogPath} && echo file`, target);
2479
2648
  if (isFile === "file") {
2480
2649
  const parentDir = remoteLogPath.slice(0, remoteLogPath.lastIndexOf("/")) || "/tmp";
@@ -2494,6 +2663,7 @@ async function pullRemoteFilesRailway(opts) {
2494
2663
  }
2495
2664
  }
2496
2665
  if (resolvedSessionsPath) {
2666
+ onProgress?.(`Pulling session files from ${resolvedSessionsPath}...`);
2497
2667
  pulledSessions = tarRailwayPull({
2498
2668
  target,
2499
2669
  remotePath: resolvedSessionsPath,
@@ -2508,8 +2678,9 @@ async function pullRemoteFilesRailway(opts) {
2508
2678
  const checkedPaths = [remoteLogPath, DEFAULT_SESSIONS_DIR2, ...ALTERNATE_SESSION_PATHS].join(
2509
2679
  ", "
2510
2680
  );
2681
+ const wrongServiceHint = target ? " Verify that the selected service is the OpenClaw app, or use --remote-log-file / --remote-sessions-dir for custom paths." : " If this directory is linked to a database or sidecar instead of the OpenClaw app, run `railway link` again and choose the app service.";
2511
2682
  throw new Error(
2512
- `No OpenClaw data found on Railway service. Checked: ${checkedPaths}. Use --remote-log-file or --remote-sessions-dir to specify custom paths.`
2683
+ `No OpenClaw data found on Railway service. Checked: ${checkedPaths}. Use --remote-log-file or --remote-sessions-dir to specify custom paths.${wrongServiceHint}`
2513
2684
  );
2514
2685
  }
2515
2686
  const result = {
@@ -2518,12 +2689,14 @@ async function pullRemoteFilesRailway(opts) {
2518
2689
  };
2519
2690
  if (pulledLog) result.logFile = gatewayDir;
2520
2691
  if (pulledSessions) result.sessionsDir = sessionsDir;
2692
+ onProgress?.("Railway files pulled successfully.");
2521
2693
  return result;
2522
2694
  }
2523
2695
  async function runRailwayDoctor(opts) {
2524
- const { source } = opts;
2696
+ const { source, onProgress } = opts;
2525
2697
  const target = source.railway;
2526
2698
  const notes = [];
2699
+ onProgress?.("Checking whether the Railway CLI is installed...");
2527
2700
  const whichCheck = spawnSync2("which", ["railway"], { stdio: "pipe", timeout: 5e3 });
2528
2701
  const railwayCliInstalled = whichCheck.status === 0;
2529
2702
  if (!railwayCliInstalled) {
@@ -2539,6 +2712,7 @@ async function runRailwayDoctor(opts) {
2539
2712
  };
2540
2713
  }
2541
2714
  const railwayPath = whichCheck.stdout?.toString().trim() ?? "railway";
2715
+ onProgress?.("Checking Railway CLI authentication...");
2542
2716
  const versionCheck = spawnSync2("railway", ["version"], { stdio: "pipe", timeout: 1e4 });
2543
2717
  const versionStr = versionCheck.status === 0 ? versionCheck.stdout?.toString().trim() : railwayPath;
2544
2718
  notes.push(`Railway CLI: installed (${versionStr})`);
@@ -2558,6 +2732,7 @@ async function runRailwayDoctor(opts) {
2558
2732
  };
2559
2733
  }
2560
2734
  notes.push(`Authenticated as: ${railwayAuthUser}`);
2735
+ onProgress?.("Testing Railway service connectivity...");
2561
2736
  const { status: reachStatus } = railwayExec("echo ok", target);
2562
2737
  const serviceReachable = reachStatus === 0;
2563
2738
  if (!serviceReachable) {
@@ -2568,16 +2743,17 @@ async function runRailwayDoctor(opts) {
2568
2743
  railwayAuthenticated: true,
2569
2744
  railwayAuthUser,
2570
2745
  serviceReachable: false,
2571
- serviceError: target ? `Cannot reach service ${target.serviceId}` : "Cannot reach linked service. Run: railway link",
2746
+ serviceError: target ? `Cannot reach service ${target.serviceId} in project ${target.projectId}` : "Current directory is not linked to a reachable Railway service. Run `railway link` here and choose the OpenClaw app service, or pass --project / --environment / --service.",
2572
2747
  defaultPaths: emptyDefaultPaths(),
2573
2748
  alternateSessionPaths: [],
2574
2749
  notes: [
2575
2750
  ...notes,
2576
- target ? `Service unreachable (project: ${target.projectId}, service: ${target.serviceId})` : "Service unreachable. Ensure a project is linked with: railway link"
2751
+ target ? `Service unreachable (project: ${target.projectId}, service: ${target.serviceId}). Verify the provided Railway IDs point at the OpenClaw app service.` : "Service unreachable for the current directory. Run `railway link` here and choose the OpenClaw app service, or pass explicit Railway IDs."
2577
2752
  ]
2578
2753
  };
2579
2754
  }
2580
2755
  notes.push("Service connectivity: OK");
2756
+ onProgress?.("Inspecting Railway default paths...");
2581
2757
  const gateway = checkRemotePath(DEFAULT_GATEWAY_DIR2, target);
2582
2758
  const { stdout: expandedDefault } = railwayExec(`eval echo ${DEFAULT_SESSIONS_DIR2}`, target);
2583
2759
  const resolvedDefault = expandedDefault || DEFAULT_SESSIONS_DIR2;
@@ -2632,9 +2808,11 @@ async function runRailwayDoctor(opts) {
2632
2808
  alternateSessionPaths,
2633
2809
  notes
2634
2810
  };
2811
+ let logCheck = null;
2812
+ let sessCheck = null;
2635
2813
  if (source.logFile || source.sessionsDir) {
2636
- const logCheck = source.logFile ? checkRemotePath(source.logFile, target) : null;
2637
- const sessCheck = source.sessionsDir ? checkRemotePath(source.sessionsDir, target) : null;
2814
+ logCheck = source.logFile ? checkRemotePath(source.logFile, target) : null;
2815
+ sessCheck = source.sessionsDir ? checkRemotePath(source.sessionsDir, target) : null;
2638
2816
  report.customPaths = {
2639
2817
  logFileExists: logCheck?.exists ?? false,
2640
2818
  logFilePath: source.logFile ?? "",
@@ -2657,6 +2835,12 @@ async function runRailwayDoctor(opts) {
2657
2835
  notes.push(`Custom sessions path ${source.sessionsDir}: not found`);
2658
2836
  }
2659
2837
  }
2838
+ const foundOpenClawData = gateway.fileCount > 0 || sessions.fileCount > 0 || alternateSessionPaths.some((path) => path.fileCount > 0) || (logCheck?.fileCount ?? 0) > 0 || (sessCheck?.fileCount ?? 0) > 0;
2839
+ if (!foundOpenClawData) {
2840
+ notes.push(
2841
+ target ? "No OpenClaw data was found on this Railway service. Verify that the selected service is the OpenClaw app, or use --remote-log-file / --remote-sessions-dir for custom paths." : "No OpenClaw data was found on the linked Railway service. If this directory is linked to a database or sidecar instead of the OpenClaw app, run `railway link` again and choose the app service."
2842
+ );
2843
+ }
2660
2844
  return report;
2661
2845
  }
2662
2846
  function emptyDefaultPaths() {
@@ -2770,6 +2954,7 @@ async function auditOrNoData(...args) {
2770
2954
  }
2771
2955
  }
2772
2956
  async function runAuditCommand(options) {
2957
+ const logger = createCliLogger({ verbose: options.verbose });
2773
2958
  if (options.dryRun && !options.push) {
2774
2959
  throw new Error("--dry-run requires --push.");
2775
2960
  }
@@ -2780,7 +2965,7 @@ async function runAuditCommand(options) {
2780
2965
  throw new Error("Use only one of --remote, --remote-config, or --railway.");
2781
2966
  }
2782
2967
  if (!options.remote && !options.remoteConfig && !options.railway) {
2783
- return runLocalAudit(options);
2968
+ return runLocalAudit(options, logger);
2784
2969
  }
2785
2970
  if (options.railway) {
2786
2971
  const railwayTarget = buildRailwayTarget(options);
@@ -2789,14 +2974,14 @@ async function runAuditCommand(options) {
2789
2974
  remoteLogFile: options.remoteLogFile,
2790
2975
  remoteSessionsDir: options.remoteSessionsDir
2791
2976
  });
2792
- return runSingleRemoteAudit(source2, options);
2977
+ return runSingleRemoteAudit(source2, options, logger);
2793
2978
  }
2794
2979
  if (options.remoteConfig) {
2795
2980
  const sources = loadRemoteConfig(options.remoteConfig);
2796
2981
  if (sources.length === 1) {
2797
- return runSingleRemoteAudit(sources[0], options);
2982
+ return runSingleRemoteAudit(sources[0], options, logger);
2798
2983
  }
2799
- return runMultiRemoteAudit(sources, options);
2984
+ return runMultiRemoteAudit(sources, options, logger);
2800
2985
  }
2801
2986
  const remote = options.remote;
2802
2987
  const source = buildSourceFromFlags({
@@ -2804,7 +2989,7 @@ async function runAuditCommand(options) {
2804
2989
  remoteLogFile: options.remoteLogFile,
2805
2990
  remoteSessionsDir: options.remoteSessionsDir
2806
2991
  });
2807
- return runSingleRemoteAudit(source, options);
2992
+ return runSingleRemoteAudit(source, options, logger);
2808
2993
  }
2809
2994
  function buildRailwayTarget(options) {
2810
2995
  if (options.railwayProject && options.railwayEnvironment && options.railwayService) {
@@ -2816,14 +3001,23 @@ function buildRailwayTarget(options) {
2816
3001
  }
2817
3002
  return void 0;
2818
3003
  }
2819
- async function runLocalAudit(options) {
3004
+ async function runLocalAudit(options, logger) {
3005
+ logger.verbose("Running a local audit.");
3006
+ if (options.logFile) {
3007
+ logger.verbose(`Using explicit local log file: ${options.logFile}`);
3008
+ }
3009
+ if (options.sessionsDir) {
3010
+ logger.verbose(`Using explicit local sessions directory: ${options.sessionsDir}`);
3011
+ }
2820
3012
  const summary = await auditOrNoData({
2821
3013
  logFile: options.logFile,
2822
3014
  sessionsDir: options.sessionsDir,
2823
3015
  since: options.since,
2824
3016
  compare: options.compare,
2825
3017
  dbPath: options.db,
2826
- noDb: options.noDb
3018
+ noDb: options.noDb,
3019
+ commandPrefix: options.commandPrefix,
3020
+ onProgress: logger.verbose
2827
3021
  });
2828
3022
  renderOutput(summary, options);
2829
3023
  if (options.push) {
@@ -2838,11 +3032,11 @@ function getComparisonKey(source) {
2838
3032
  }
2839
3033
  return buildComparisonKeyForRemote(source);
2840
3034
  }
2841
- function pullFiles(source, since, keepFiles) {
3035
+ function pullFiles(source, since, keepFiles, onProgress) {
2842
3036
  if (source.transport === "railway") {
2843
- return pullRemoteFilesRailway({ source, since, keepFiles });
3037
+ return pullRemoteFilesRailway({ source, since, keepFiles, onProgress });
2844
3038
  }
2845
- return pullRemoteFiles({ source, since, keepFiles });
3039
+ return pullRemoteFiles({ source, since, keepFiles, onProgress });
2846
3040
  }
2847
3041
  function describeSource(source) {
2848
3042
  if (source.transport === "railway") {
@@ -2853,10 +3047,15 @@ function describeSource(source) {
2853
3047
  function sourceEnvironment(source) {
2854
3048
  return source.transport === "railway" ? "railway" : "remote";
2855
3049
  }
2856
- async function runSingleRemoteAudit(source, options) {
2857
- process.stderr.write(`Pulling files from ${describeSource(source)}...
2858
- `);
2859
- const pullResult = await pullFiles(source, options.since, options.keepRemoteFiles);
3050
+ async function runSingleRemoteAudit(source, options, logger) {
3051
+ logger.info(`Pulling files from ${describeSource(source)}...`);
3052
+ const pullResult = await pullFiles(
3053
+ source,
3054
+ options.since,
3055
+ options.keepRemoteFiles,
3056
+ logger.verbose
3057
+ );
3058
+ logger.verbose(`Files staged at ${pullResult.localPath}.`);
2860
3059
  try {
2861
3060
  const comparisonKeyOverride = getComparisonKey(source);
2862
3061
  const summary = await auditOrNoData({
@@ -2866,7 +3065,9 @@ async function runSingleRemoteAudit(source, options) {
2866
3065
  compare: options.compare,
2867
3066
  dbPath: options.db,
2868
3067
  noDb: options.noDb,
2869
- comparisonKeyOverride
3068
+ comparisonKeyOverride,
3069
+ commandPrefix: options.commandPrefix,
3070
+ onProgress: logger.verbose
2870
3071
  });
2871
3072
  renderOutput(summary, options);
2872
3073
  if (options.push) {
@@ -2882,14 +3083,18 @@ async function runSingleRemoteAudit(source, options) {
2882
3083
  cleanupPullResult(pullResult, options.keepRemoteFiles);
2883
3084
  }
2884
3085
  }
2885
- async function runMultiRemoteAudit(sources, options) {
3086
+ async function runMultiRemoteAudit(sources, options, logger) {
2886
3087
  const results = [];
2887
3088
  const errors = [];
2888
3089
  for (const source of sources) {
2889
- process.stderr.write(`Pulling files from ${source.name} (${describeSource(source)})...
2890
- `);
3090
+ logger.info(`Pulling files from ${source.name} (${describeSource(source)})...`);
2891
3091
  try {
2892
- const pullResult = await pullFiles(source, options.since, options.keepRemoteFiles);
3092
+ const pullResult = await pullFiles(
3093
+ source,
3094
+ options.since,
3095
+ options.keepRemoteFiles,
3096
+ logger.verbose
3097
+ );
2893
3098
  results.push({ source, pullResult });
2894
3099
  } catch (err) {
2895
3100
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -2914,7 +3119,9 @@ ${errorMessages}`);
2914
3119
  compare: options.compare,
2915
3120
  dbPath: options.db,
2916
3121
  noDb: options.noDb,
2917
- comparisonKeyOverride
3122
+ comparisonKeyOverride,
3123
+ commandPrefix: options.commandPrefix,
3124
+ onProgress: logger.verbose
2918
3125
  });
2919
3126
  summaries.push({ name: source.name, source, summary });
2920
3127
  }
@@ -3058,34 +3265,45 @@ function cleanupPullResult(pullResult, keepFiles) {
3058
3265
 
3059
3266
  // src/commands/doctor.ts
3060
3267
  async function runDoctorCommand(options) {
3268
+ const logger = createCliLogger({ verbose: options.verbose });
3061
3269
  if (options.railway) {
3270
+ logger.verbose("Inspecting Railway audit readiness.");
3062
3271
  const railwayTarget = buildRailwayTarget2(options);
3063
3272
  const source = buildRailwaySourceFromFlags({
3064
3273
  railway: railwayTarget,
3065
3274
  remoteLogFile: options.remoteLogFile,
3066
3275
  remoteSessionsDir: options.remoteSessionsDir
3067
3276
  });
3068
- const report2 = await runRailwayDoctor({ source });
3277
+ const report2 = await runRailwayDoctor({ source, onProgress: logger.verbose });
3069
3278
  process.stdout.write(`${renderRailwayDoctorReport(report2)}
3070
3279
  `);
3071
3280
  return;
3072
3281
  }
3073
3282
  if (options.remote) {
3283
+ logger.verbose(`Inspecting SSH audit readiness for ${options.remote}.`);
3074
3284
  const source = buildSourceFromFlags({
3075
3285
  remote: options.remote,
3076
3286
  remoteLogFile: options.remoteLogFile,
3077
3287
  remoteSessionsDir: options.remoteSessionsDir
3078
3288
  });
3079
- const report2 = await runRemoteDoctor({ source });
3289
+ const report2 = await runRemoteDoctor({ source, onProgress: logger.verbose });
3080
3290
  process.stdout.write(`${renderRemoteDoctorReport(report2)}
3081
3291
  `);
3082
3292
  return;
3083
3293
  }
3294
+ logger.verbose("Inspecting local OpenClaw audit readiness.");
3295
+ if (options.logFile) {
3296
+ logger.verbose(`Using explicit local log file: ${options.logFile}`);
3297
+ }
3298
+ if (options.sessionsDir) {
3299
+ logger.verbose(`Using explicit local sessions directory: ${options.sessionsDir}`);
3300
+ }
3084
3301
  const report = await doctorOpenClaw({
3085
3302
  logFile: options.logFile,
3086
- sessionsDir: options.sessionsDir
3303
+ sessionsDir: options.sessionsDir,
3304
+ onProgress: logger.verbose
3087
3305
  });
3088
- process.stdout.write(`${renderDoctorReport(report)}
3306
+ process.stdout.write(`${renderDoctorReport(report, { commandPrefix: options.commandPrefix })}
3089
3307
  `);
3090
3308
  }
3091
3309
  function buildRailwayTarget2(options) {
@@ -3209,7 +3427,7 @@ async function runLoginCommand() {
3209
3427
  if (existing) {
3210
3428
  process.stderr.write(
3211
3429
  `Already logged in. Credentials stored at ${getCredentialsPath()}.
3212
- Run ${colorBold("xerg logout")} first to re-authenticate.
3430
+ Run ${colorBold(formatCommand("logout"))} first to re-authenticate.
3213
3431
  `
3214
3432
  );
3215
3433
  return;
@@ -3275,7 +3493,7 @@ Credentials saved to ${getCredentialsPath()}.
3275
3493
  continue;
3276
3494
  }
3277
3495
  if (res.status === 410) {
3278
- throw new Error("Device code expired. Please run `xerg login` again.");
3496
+ throw new Error(`Device code expired. Please run \`${formatCommand("login")}\` again.`);
3279
3497
  }
3280
3498
  const body = await res.json().catch(() => ({}));
3281
3499
  throw new Error(body.error || `Unexpected response: HTTP ${res.status}`);
@@ -3285,7 +3503,7 @@ Credentials saved to ${getCredentialsPath()}.
3285
3503
  }
3286
3504
  }
3287
3505
  }
3288
- throw new Error("Authentication timed out. Please run `xerg login` again.");
3506
+ throw new Error(`Authentication timed out. Please run \`${formatCommand("login")}\` again.`);
3289
3507
  }
3290
3508
  async function openBrowser(url) {
3291
3509
  const { exec } = await import("child_process");
@@ -3373,12 +3591,12 @@ function loadPayloadFromCache() {
3373
3591
  summaries = listStoredAuditSummaries(dbPath);
3374
3592
  } catch {
3375
3593
  throw new NoDataError(
3376
- "No local audit database found. Run `xerg audit` first, or use `xerg push --file <path>`."
3594
+ `No local audit database found. Run \`${formatCommand("audit")}\` first, or use \`${formatCommand("push --file <path>")}\`.`
3377
3595
  );
3378
3596
  }
3379
3597
  if (summaries.length === 0) {
3380
3598
  throw new NoDataError(
3381
- "No cached audit snapshots found. Run `xerg audit` first, or use `xerg push --file <path>`."
3599
+ `No cached audit snapshots found. Run \`${formatCommand("audit")}\` first, or use \`${formatCommand("push --file <path>")}\`.`
3382
3600
  );
3383
3601
  }
3384
3602
  const latest = summaries[0];
@@ -3410,9 +3628,10 @@ function buildMeta2() {
3410
3628
  // src/index.ts
3411
3629
  var VERSION = readVersion();
3412
3630
  var argv = process.argv.slice(2);
3631
+ var commandDisplay = resolveCommandDisplay();
3413
3632
  var command = argv[0];
3414
3633
  if (!command || command === "--help" || command === "-h" || command === "help") {
3415
- process.stdout.write(renderRootHelp());
3634
+ process.stdout.write(renderRootHelp(commandDisplay));
3416
3635
  process.exit(0);
3417
3636
  }
3418
3637
  if (command === "--version" || command === "-v" || command === "version") {
@@ -3422,7 +3641,7 @@ if (command === "--version" || command === "-v" || command === "version") {
3422
3641
  }
3423
3642
  run().catch((error) => {
3424
3643
  const message = error instanceof Error ? error.message : "Unknown error";
3425
- process.stderr.write(`${colorError(`xerg failed: ${message}`)}
3644
+ process.stderr.write(`${colorError(`${commandDisplay.name} failed: ${message}`)}
3426
3645
  `);
3427
3646
  process.exitCode = error instanceof NoDataError ? 2 : 1;
3428
3647
  });
@@ -3432,12 +3651,18 @@ async function run() {
3432
3651
  if (options.json && options.markdown) {
3433
3652
  throw new Error("Use either --json or --markdown, not both.");
3434
3653
  }
3435
- await runAuditCommand(options);
3654
+ await runAuditCommand({
3655
+ ...options,
3656
+ commandPrefix: commandDisplay.prefix
3657
+ });
3436
3658
  return;
3437
3659
  }
3438
3660
  if (command === "doctor") {
3439
3661
  const options = parseDoctorOptions(argv.slice(1));
3440
- await runDoctorCommand(options);
3662
+ await runDoctorCommand({
3663
+ ...options,
3664
+ commandPrefix: commandDisplay.prefix
3665
+ });
3441
3666
  return;
3442
3667
  }
3443
3668
  if (command === "push") {
@@ -3453,7 +3678,9 @@ async function run() {
3453
3678
  runLogoutCommand();
3454
3679
  return;
3455
3680
  }
3456
- throw new Error(`Unknown command "${command}". Run \`xerg --help\` to see available commands.`);
3681
+ throw new Error(
3682
+ `Unknown command "${command}". Run \`${formatCommand("--help", commandDisplay.prefix)}\` to see available commands.`
3683
+ );
3457
3684
  }
3458
3685
  function parseAuditOptions(raw) {
3459
3686
  const argv2 = expandEqualsArgs(raw);
@@ -3463,7 +3690,7 @@ function parseAuditOptions(raw) {
3463
3690
  switch (arg) {
3464
3691
  case "--help":
3465
3692
  case "-h":
3466
- process.stdout.write(renderAuditHelp());
3693
+ process.stdout.write(renderAuditHelp(commandDisplay.prefix));
3467
3694
  process.exit(0);
3468
3695
  break;
3469
3696
  case "--log-file":
@@ -3534,6 +3761,9 @@ function parseAuditOptions(raw) {
3534
3761
  case "--dry-run":
3535
3762
  options.dryRun = true;
3536
3763
  break;
3764
+ case "--verbose":
3765
+ options.verbose = true;
3766
+ break;
3537
3767
  case "--fail-above-waste-rate":
3538
3768
  options.failAboveWasteRate = readFloat(arg, argv2[index + 1]);
3539
3769
  index += 1;
@@ -3543,7 +3773,9 @@ function parseAuditOptions(raw) {
3543
3773
  index += 1;
3544
3774
  break;
3545
3775
  default:
3546
- throw new Error(`Unknown audit option "${arg}". Run \`xerg audit --help\` for usage.`);
3776
+ throw new Error(
3777
+ `Unknown audit option "${arg}". Run \`${formatCommand(["audit", "--help"], commandDisplay.prefix)}\` for usage.`
3778
+ );
3547
3779
  }
3548
3780
  }
3549
3781
  return options;
@@ -3556,7 +3788,7 @@ function parsePushOptions(raw) {
3556
3788
  switch (arg) {
3557
3789
  case "--help":
3558
3790
  case "-h":
3559
- process.stdout.write(renderPushHelp());
3791
+ process.stdout.write(renderPushHelp(commandDisplay.prefix));
3560
3792
  process.exit(0);
3561
3793
  break;
3562
3794
  case "--file":
@@ -3567,7 +3799,9 @@ function parsePushOptions(raw) {
3567
3799
  options.dryRun = true;
3568
3800
  break;
3569
3801
  default:
3570
- throw new Error(`Unknown push option "${arg}". Run \`xerg push --help\` for usage.`);
3802
+ throw new Error(
3803
+ `Unknown push option "${arg}". Run \`${formatCommand(["push", "--help"], commandDisplay.prefix)}\` for usage.`
3804
+ );
3571
3805
  }
3572
3806
  }
3573
3807
  return options;
@@ -3580,7 +3814,7 @@ function parseDoctorOptions(raw) {
3580
3814
  switch (arg) {
3581
3815
  case "--help":
3582
3816
  case "-h":
3583
- process.stdout.write(renderDoctorHelp());
3817
+ process.stdout.write(renderDoctorHelp(commandDisplay.prefix));
3584
3818
  process.exit(0);
3585
3819
  break;
3586
3820
  case "--log-file":
@@ -3618,8 +3852,13 @@ function parseDoctorOptions(raw) {
3618
3852
  options.railwayService = readValue(arg, argv2[index + 1]);
3619
3853
  index += 1;
3620
3854
  break;
3855
+ case "--verbose":
3856
+ options.verbose = true;
3857
+ break;
3621
3858
  default:
3622
- throw new Error(`Unknown doctor option "${arg}". Run \`xerg doctor --help\` for usage.`);
3859
+ throw new Error(
3860
+ `Unknown doctor option "${arg}". Run \`${formatCommand(["doctor", "--help"], commandDisplay.prefix)}\` for usage.`
3861
+ );
3623
3862
  }
3624
3863
  }
3625
3864
  return options;
@@ -3650,13 +3889,13 @@ function readFloat(flag, value) {
3650
3889
  }
3651
3890
  return num;
3652
3891
  }
3653
- function renderRootHelp() {
3654
- return `xerg ${VERSION}
3892
+ function renderRootHelp(display = commandDisplay) {
3893
+ return `${display.name} ${VERSION}
3655
3894
 
3656
3895
  Waste intelligence for OpenClaw workflows.
3657
3896
 
3658
3897
  Usage:
3659
- xerg <command> [options]
3898
+ ${formatCommand("<command> [options]", display.prefix)}
3660
3899
 
3661
3900
  Commands:
3662
3901
  audit Analyze OpenClaw logs and produce a waste intelligence report.
@@ -3670,13 +3909,13 @@ Global options:
3670
3909
  -v, --version Show version
3671
3910
  `;
3672
3911
  }
3673
- function renderAuditHelp() {
3674
- return `xerg audit
3912
+ function renderAuditHelp(commandPrefix = commandDisplay.prefix) {
3913
+ return `${formatCommand("audit", commandPrefix)}
3675
3914
 
3676
3915
  Analyze OpenClaw logs and produce a waste intelligence report.
3677
3916
 
3678
3917
  Usage:
3679
- xerg audit [options]
3918
+ ${formatCommand("audit [options]", commandPrefix)}
3680
3919
 
3681
3920
  Options:
3682
3921
  --log-file <path> Explicit OpenClaw gateway log file to analyze
@@ -3709,6 +3948,7 @@ Railway options:
3709
3948
  Push options:
3710
3949
  --push Push the audit summary to the Xerg API after computing it
3711
3950
  --dry-run With --push: print the payload to stdout without sending it
3951
+ --verbose Print progress updates to stderr while the audit runs
3712
3952
 
3713
3953
  Threshold options:
3714
3954
  --fail-above-waste-rate <n> Exit with code 3 if structural waste rate exceeds threshold (e.g. 0.30)
@@ -3717,13 +3957,13 @@ Threshold options:
3717
3957
  -h, --help Show help
3718
3958
  `;
3719
3959
  }
3720
- function renderPushHelp() {
3721
- return `xerg push
3960
+ function renderPushHelp(commandPrefix = commandDisplay.prefix) {
3961
+ return `${formatCommand("push", commandPrefix)}
3722
3962
 
3723
3963
  Push a cached audit snapshot to the Xerg API.
3724
3964
 
3725
3965
  Usage:
3726
- xerg push [options]
3966
+ ${formatCommand("push [options]", commandPrefix)}
3727
3967
 
3728
3968
  Options:
3729
3969
  --file <path> Push a specific snapshot file instead of the most recent cached audit
@@ -3733,21 +3973,22 @@ Options:
3733
3973
 
3734
3974
  Authentication:
3735
3975
  Set XERG_API_KEY in your environment, add "apiKey" to ~/.xerg/config.json,
3736
- or run \`xerg login\` to authenticate via browser.
3976
+ or run \`${formatCommand("login", commandPrefix)}\` to authenticate via browser.
3737
3977
  Browser login stores a token at ~/.config/xerg/credentials.json by default.
3738
3978
  `;
3739
3979
  }
3740
- function renderDoctorHelp() {
3741
- return `xerg doctor
3980
+ function renderDoctorHelp(commandPrefix = commandDisplay.prefix) {
3981
+ return `${formatCommand("doctor", commandPrefix)}
3742
3982
 
3743
3983
  Inspect your machine for OpenClaw sources and audit readiness.
3744
3984
 
3745
3985
  Usage:
3746
- xerg doctor [options]
3986
+ ${formatCommand("doctor [options]", commandPrefix)}
3747
3987
 
3748
3988
  Options:
3749
3989
  --log-file <path> Explicit OpenClaw gateway log file to inspect
3750
3990
  --sessions-dir <path> Explicit OpenClaw sessions directory to inspect
3991
+ --verbose Print progress updates to stderr while doctor runs
3751
3992
 
3752
3993
  Remote options (SSH):
3753
3994
  --remote <user@host> SSH target in user@host or user@host:port format