@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/README.md +9 -0
- package/dist/index.js +309 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/xerg/SKILL.md +5 -0
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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}` : "
|
|
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})
|
|
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
|
-
|
|
2637
|
-
|
|
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
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
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
|
-
|
|
2890
|
-
`);
|
|
3090
|
+
logger.info(`Pulling files from ${source.name} (${describeSource(source)})...`);
|
|
2891
3091
|
try {
|
|
2892
|
-
const pullResult = await pullFiles(
|
|
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("
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|