@xerg/cli 0.1.8 → 0.1.9
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 +135 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/xerg/SKILL.md +5 -0
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Everything runs locally by default. No account is required for local audits. No
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx @xerg/cli doctor
|
|
13
|
+
npx @xerg/cli doctor --verbose
|
|
13
14
|
npx @xerg/cli audit
|
|
14
15
|
npx @xerg/cli audit --compare
|
|
15
16
|
```
|
|
@@ -114,6 +115,13 @@ xerg audit --log-file /path/to/openclaw.log
|
|
|
114
115
|
xerg audit --sessions-dir /path/to/sessions
|
|
115
116
|
```
|
|
116
117
|
|
|
118
|
+
If your local machine has no OpenClaw files, inspect remote targets directly instead:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
xerg doctor --remote user@host
|
|
122
|
+
xerg doctor --railway
|
|
123
|
+
```
|
|
124
|
+
|
|
117
125
|
## Authentication and config
|
|
118
126
|
|
|
119
127
|
Push commands resolve credentials in this order:
|
|
@@ -161,6 +169,7 @@ Xerg v0 stores economic metadata and audit summaries locally. It does not store
|
|
|
161
169
|
## Troubleshooting
|
|
162
170
|
|
|
163
171
|
- `better-sqlite3` is a native dependency. If install fails, retry on a supported Node version and make sure standard native build tooling is available for your platform.
|
|
172
|
+
- `--verbose` prints progress updates to stderr for `xerg doctor` and `xerg audit`, which helps distinguish package install time from CLI runtime.
|
|
164
173
|
- If `xerg audit --remote ...` fails before pulling files, verify that both `ssh` and `rsync` are installed and reachable on your `PATH`.
|
|
165
174
|
- If `xerg audit --railway` fails immediately, verify that the `railway` CLI is installed, authenticated, and can access the target project.
|
|
166
175
|
|
package/dist/index.js
CHANGED
|
@@ -798,10 +798,17 @@ function segmentToRegExp(segment) {
|
|
|
798
798
|
return new RegExp(`^${escaped}$`);
|
|
799
799
|
}
|
|
800
800
|
async function inspectOpenClawSources(options) {
|
|
801
|
+
options.onProgress?.("Checking local OpenClaw defaults...");
|
|
801
802
|
const sources = await detectOpenClawSources(options);
|
|
802
803
|
const notes = [];
|
|
804
|
+
options.onProgress?.(
|
|
805
|
+
sources.length > 0 ? `Detected ${sources.length} local source file${sources.length === 1 ? "" : "s"}.` : "No local OpenClaw source files were detected."
|
|
806
|
+
);
|
|
803
807
|
if (sources.length === 0) {
|
|
804
808
|
notes.push("No OpenClaw gateway logs or session files were detected.");
|
|
809
|
+
notes.push(
|
|
810
|
+
"Doctor checks local defaults by default. Use --remote or --railway to inspect remote targets."
|
|
811
|
+
);
|
|
805
812
|
notes.push(
|
|
806
813
|
"Use --log-file or --sessions-dir if your OpenClaw data lives outside the defaults."
|
|
807
814
|
);
|
|
@@ -1383,6 +1390,7 @@ async function doctorOpenClaw(options) {
|
|
|
1383
1390
|
return inspectOpenClawSources(options);
|
|
1384
1391
|
}
|
|
1385
1392
|
async function auditOpenClaw(options) {
|
|
1393
|
+
options.onProgress?.("Scanning for OpenClaw source files...");
|
|
1386
1394
|
if (options.compare && options.noDb) {
|
|
1387
1395
|
throw new Error(
|
|
1388
1396
|
"The --compare flag needs local snapshot history. Remove --no-db or provide --db <path>."
|
|
@@ -1390,13 +1398,19 @@ async function auditOpenClaw(options) {
|
|
|
1390
1398
|
}
|
|
1391
1399
|
const sources = await detectOpenClawSources(options);
|
|
1392
1400
|
if (sources.length === 0) {
|
|
1401
|
+
options.onProgress?.("No OpenClaw source files were detected.");
|
|
1393
1402
|
throw new Error(
|
|
1394
1403
|
"No OpenClaw sources were detected. Run `xerg doctor` or provide --log-file / --sessions-dir."
|
|
1395
1404
|
);
|
|
1396
1405
|
}
|
|
1406
|
+
options.onProgress?.(`Detected ${sources.length} source file${sources.length === 1 ? "" : "s"}.`);
|
|
1407
|
+
options.onProgress?.("Normalizing OpenClaw source files...");
|
|
1397
1408
|
const runs = normalizeOpenClawSources(sources, options.since);
|
|
1409
|
+
options.onProgress?.(`Normalized ${runs.length} run${runs.length === 1 ? "" : "s"}.`);
|
|
1410
|
+
options.onProgress?.("Computing waste and savings findings...");
|
|
1398
1411
|
const findings = buildFindings(runs);
|
|
1399
1412
|
const dbPath = options.noDb ? void 0 : options.dbPath ?? getDefaultDbPath();
|
|
1413
|
+
options.onProgress?.("Building audit summary...");
|
|
1400
1414
|
const summary = buildAuditSummary({
|
|
1401
1415
|
runs,
|
|
1402
1416
|
findings,
|
|
@@ -1406,6 +1420,7 @@ async function auditOpenClaw(options) {
|
|
|
1406
1420
|
comparisonKeyOverride: options.comparisonKeyOverride
|
|
1407
1421
|
});
|
|
1408
1422
|
if (options.compare && dbPath) {
|
|
1423
|
+
options.onProgress?.("Looking for a comparable baseline audit...");
|
|
1409
1424
|
const baseline = readLatestComparableAuditSummary({
|
|
1410
1425
|
dbPath,
|
|
1411
1426
|
comparisonKey: summary.comparisonKey,
|
|
@@ -1421,6 +1436,7 @@ async function auditOpenClaw(options) {
|
|
|
1421
1436
|
}
|
|
1422
1437
|
}
|
|
1423
1438
|
if (dbPath) {
|
|
1439
|
+
options.onProgress?.(`Persisting local snapshot to ${dbPath}...`);
|
|
1424
1440
|
persistAudit(
|
|
1425
1441
|
{
|
|
1426
1442
|
summary,
|
|
@@ -1429,6 +1445,9 @@ async function auditOpenClaw(options) {
|
|
|
1429
1445
|
},
|
|
1430
1446
|
dbPath
|
|
1431
1447
|
);
|
|
1448
|
+
options.onProgress?.("Local snapshot stored.");
|
|
1449
|
+
} else {
|
|
1450
|
+
options.onProgress?.("Skipping local snapshot persistence (--no-db).");
|
|
1432
1451
|
}
|
|
1433
1452
|
return summary;
|
|
1434
1453
|
}
|
|
@@ -1639,6 +1658,14 @@ function renderCompareBlock(summary) {
|
|
|
1639
1658
|
];
|
|
1640
1659
|
}
|
|
1641
1660
|
function renderDoctorReport(report) {
|
|
1661
|
+
const nextSteps = report.canAudit ? [] : [
|
|
1662
|
+
"",
|
|
1663
|
+
"## Next steps",
|
|
1664
|
+
"- Try explicit local paths: xerg doctor --log-file /path/to/openclaw.log --sessions-dir /path/to/sessions",
|
|
1665
|
+
"- Inspect an SSH host: xerg doctor --remote user@host",
|
|
1666
|
+
"- Inspect a Railway service: xerg doctor --railway",
|
|
1667
|
+
"- Remote audits still analyze locally after Xerg pulls the source files to your machine."
|
|
1668
|
+
];
|
|
1642
1669
|
const sections = [
|
|
1643
1670
|
"# Xerg doctor",
|
|
1644
1671
|
"",
|
|
@@ -1652,7 +1679,8 @@ function renderDoctorReport(report) {
|
|
|
1652
1679
|
...report.sources.length > 0 ? report.sources.map((source) => `- [${source.kind}] ${source.path}`) : ["- none"],
|
|
1653
1680
|
"",
|
|
1654
1681
|
"## Notes",
|
|
1655
|
-
...report.notes.map((note) => `- ${note}`)
|
|
1682
|
+
...report.notes.map((note) => `- ${note}`),
|
|
1683
|
+
...nextSteps
|
|
1656
1684
|
];
|
|
1657
1685
|
return sections.join("\n");
|
|
1658
1686
|
}
|
|
@@ -1811,6 +1839,23 @@ var NoDataError = class extends Error {
|
|
|
1811
1839
|
}
|
|
1812
1840
|
};
|
|
1813
1841
|
|
|
1842
|
+
// src/log.ts
|
|
1843
|
+
function createCliLogger(options) {
|
|
1844
|
+
return {
|
|
1845
|
+
info(message) {
|
|
1846
|
+
process.stderr.write(`${message}
|
|
1847
|
+
`);
|
|
1848
|
+
},
|
|
1849
|
+
verbose(message) {
|
|
1850
|
+
if (!options.verbose) {
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
process.stderr.write(`[verbose] ${message}
|
|
1854
|
+
`);
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1814
1859
|
// src/push/client.ts
|
|
1815
1860
|
async function pushAudit(payload, config) {
|
|
1816
1861
|
const url = `${config.apiUrl}/v1/audits`;
|
|
@@ -2124,14 +2169,19 @@ function buildComparisonKeyForRemote(source) {
|
|
|
2124
2169
|
return `${source.host}:${logPath}:${sessPath}`;
|
|
2125
2170
|
}
|
|
2126
2171
|
async function pullRemoteFiles(opts) {
|
|
2127
|
-
const { source, since, keepFiles = false } = opts;
|
|
2172
|
+
const { source, since, keepFiles = false, onProgress } = opts;
|
|
2173
|
+
onProgress?.(`Testing SSH connectivity to ${source.host}...`);
|
|
2128
2174
|
const connectivity = testSshConnectivity(source);
|
|
2129
2175
|
if (!connectivity.ok) {
|
|
2130
2176
|
throw new Error(
|
|
2131
2177
|
`Cannot connect to ${source.host}. Check SSH config and key access.${connectivity.error ? ` (${connectivity.error})` : ""}`
|
|
2132
2178
|
);
|
|
2133
2179
|
}
|
|
2180
|
+
onProgress?.("SSH connectivity OK.");
|
|
2134
2181
|
const useRsync = isRsyncAvailable();
|
|
2182
|
+
onProgress?.(
|
|
2183
|
+
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."
|
|
2184
|
+
);
|
|
2135
2185
|
const localBase = resolveLocalPath(source, keepFiles);
|
|
2136
2186
|
const gatewayDir = join5(localBase, "gateway");
|
|
2137
2187
|
const sessionsDir = join5(localBase, "sessions");
|
|
@@ -2139,6 +2189,7 @@ async function pullRemoteFiles(opts) {
|
|
|
2139
2189
|
const remoteSessionsPath = source.sessionsDir ?? DEFAULT_SESSIONS_DIR;
|
|
2140
2190
|
const { stdout: expandedSessions } = sshExec(source, `eval echo ${remoteSessionsPath}`);
|
|
2141
2191
|
const resolvedSessionsPath = expandedSessions || remoteSessionsPath;
|
|
2192
|
+
onProgress?.("Checking remote default paths for gateway logs and sessions...");
|
|
2142
2193
|
const { status: logPathExists } = sshExec(source, `test -e ${remoteLogPath} && echo exists`);
|
|
2143
2194
|
const { status: sessPathExists } = sshExec(
|
|
2144
2195
|
source,
|
|
@@ -2147,6 +2198,7 @@ async function pullRemoteFiles(opts) {
|
|
|
2147
2198
|
let pulledLog = false;
|
|
2148
2199
|
let pulledSessions = false;
|
|
2149
2200
|
if (logPathExists === 0) {
|
|
2201
|
+
onProgress?.(`Pulling gateway logs from ${remoteLogPath}...`);
|
|
2150
2202
|
const { stdout: isFile } = sshExec(source, `test -f ${remoteLogPath} && echo file`);
|
|
2151
2203
|
if (isFile === "file") {
|
|
2152
2204
|
const parentDir = remoteLogPath.slice(0, remoteLogPath.lastIndexOf("/")) || "/tmp";
|
|
@@ -2171,6 +2223,7 @@ async function pullRemoteFiles(opts) {
|
|
|
2171
2223
|
}
|
|
2172
2224
|
}
|
|
2173
2225
|
if (sessPathExists === 0) {
|
|
2226
|
+
onProgress?.(`Pulling session files from ${resolvedSessionsPath}...`);
|
|
2174
2227
|
pulledSessions = pullDirectory({
|
|
2175
2228
|
source,
|
|
2176
2229
|
remotePath: resolvedSessionsPath,
|
|
@@ -2194,11 +2247,13 @@ async function pullRemoteFiles(opts) {
|
|
|
2194
2247
|
};
|
|
2195
2248
|
if (pulledLog) result.logFile = gatewayDir;
|
|
2196
2249
|
if (pulledSessions) result.sessionsDir = sessionsDir;
|
|
2250
|
+
onProgress?.("Remote files pulled successfully.");
|
|
2197
2251
|
return result;
|
|
2198
2252
|
}
|
|
2199
2253
|
async function runRemoteDoctor(opts) {
|
|
2200
|
-
const { source } = opts;
|
|
2254
|
+
const { source, onProgress } = opts;
|
|
2201
2255
|
const notes = [];
|
|
2256
|
+
onProgress?.(`Testing SSH connectivity to ${source.host}...`);
|
|
2202
2257
|
const connectivity = testSshConnectivity(source);
|
|
2203
2258
|
if (!connectivity.ok) {
|
|
2204
2259
|
return {
|
|
@@ -2222,7 +2277,9 @@ async function runRemoteDoctor(opts) {
|
|
|
2222
2277
|
]
|
|
2223
2278
|
};
|
|
2224
2279
|
}
|
|
2280
|
+
onProgress?.("SSH connectivity OK.");
|
|
2225
2281
|
notes.push("SSH connectivity: OK");
|
|
2282
|
+
onProgress?.("Checking rsync availability locally and on the remote host...");
|
|
2226
2283
|
const rsyncLocal = isRsyncAvailable();
|
|
2227
2284
|
const rsyncRemote = isRemoteRsyncAvailable(source);
|
|
2228
2285
|
notes.push(`rsync available locally: ${rsyncLocal ? "yes" : "no"}`);
|
|
@@ -2246,6 +2303,7 @@ async function runRemoteDoctor(opts) {
|
|
|
2246
2303
|
totalBytes: Number.parseInt(sizeOut, 10) || 0
|
|
2247
2304
|
};
|
|
2248
2305
|
}
|
|
2306
|
+
onProgress?.("Inspecting remote default paths...");
|
|
2249
2307
|
const gateway = checkPath(DEFAULT_GATEWAY_DIR);
|
|
2250
2308
|
const sessions = checkPath(DEFAULT_SESSIONS_DIR);
|
|
2251
2309
|
if (gateway.exists) {
|
|
@@ -2458,23 +2516,27 @@ function buildComparisonKeyForRailway(source) {
|
|
|
2458
2516
|
return `railway-linked:${logPath}:${sessPath}`;
|
|
2459
2517
|
}
|
|
2460
2518
|
async function pullRemoteFilesRailway(opts) {
|
|
2461
|
-
const { source, since, keepFiles = false } = opts;
|
|
2519
|
+
const { source, since, keepFiles = false, onProgress } = opts;
|
|
2462
2520
|
const target = source.railway;
|
|
2521
|
+
onProgress?.("Testing Railway service connectivity...");
|
|
2463
2522
|
const { status } = railwayExec("echo ok", target);
|
|
2464
2523
|
if (status !== 0) {
|
|
2465
2524
|
throw new Error(
|
|
2466
2525
|
`Cannot reach Railway service${target ? ` (project: ${target.projectId})` : " (linked project)"}. Check railway CLI auth and service configuration.`
|
|
2467
2526
|
);
|
|
2468
2527
|
}
|
|
2528
|
+
onProgress?.("Railway service reachable.");
|
|
2469
2529
|
const localBase = resolveLocalPath2(source, keepFiles);
|
|
2470
2530
|
const gatewayDir = join6(localBase, "gateway");
|
|
2471
2531
|
const sessionsDir = join6(localBase, "sessions");
|
|
2472
2532
|
const remoteLogPath = source.logFile ?? DEFAULT_GATEWAY_DIR2;
|
|
2533
|
+
onProgress?.("Checking Railway default paths for gateway logs and sessions...");
|
|
2473
2534
|
const logCheck = checkRemotePath(remoteLogPath, target);
|
|
2474
2535
|
const resolvedSessionsPath = findSessionsPath(target, source.sessionsDir);
|
|
2475
2536
|
let pulledLog = false;
|
|
2476
2537
|
let pulledSessions = false;
|
|
2477
2538
|
if (logCheck.exists) {
|
|
2539
|
+
onProgress?.(`Pulling gateway logs from ${remoteLogPath}...`);
|
|
2478
2540
|
const { stdout: isFile } = railwayExec(`test -f ${remoteLogPath} && echo file`, target);
|
|
2479
2541
|
if (isFile === "file") {
|
|
2480
2542
|
const parentDir = remoteLogPath.slice(0, remoteLogPath.lastIndexOf("/")) || "/tmp";
|
|
@@ -2494,6 +2556,7 @@ async function pullRemoteFilesRailway(opts) {
|
|
|
2494
2556
|
}
|
|
2495
2557
|
}
|
|
2496
2558
|
if (resolvedSessionsPath) {
|
|
2559
|
+
onProgress?.(`Pulling session files from ${resolvedSessionsPath}...`);
|
|
2497
2560
|
pulledSessions = tarRailwayPull({
|
|
2498
2561
|
target,
|
|
2499
2562
|
remotePath: resolvedSessionsPath,
|
|
@@ -2518,12 +2581,14 @@ async function pullRemoteFilesRailway(opts) {
|
|
|
2518
2581
|
};
|
|
2519
2582
|
if (pulledLog) result.logFile = gatewayDir;
|
|
2520
2583
|
if (pulledSessions) result.sessionsDir = sessionsDir;
|
|
2584
|
+
onProgress?.("Railway files pulled successfully.");
|
|
2521
2585
|
return result;
|
|
2522
2586
|
}
|
|
2523
2587
|
async function runRailwayDoctor(opts) {
|
|
2524
|
-
const { source } = opts;
|
|
2588
|
+
const { source, onProgress } = opts;
|
|
2525
2589
|
const target = source.railway;
|
|
2526
2590
|
const notes = [];
|
|
2591
|
+
onProgress?.("Checking whether the Railway CLI is installed...");
|
|
2527
2592
|
const whichCheck = spawnSync2("which", ["railway"], { stdio: "pipe", timeout: 5e3 });
|
|
2528
2593
|
const railwayCliInstalled = whichCheck.status === 0;
|
|
2529
2594
|
if (!railwayCliInstalled) {
|
|
@@ -2539,6 +2604,7 @@ async function runRailwayDoctor(opts) {
|
|
|
2539
2604
|
};
|
|
2540
2605
|
}
|
|
2541
2606
|
const railwayPath = whichCheck.stdout?.toString().trim() ?? "railway";
|
|
2607
|
+
onProgress?.("Checking Railway CLI authentication...");
|
|
2542
2608
|
const versionCheck = spawnSync2("railway", ["version"], { stdio: "pipe", timeout: 1e4 });
|
|
2543
2609
|
const versionStr = versionCheck.status === 0 ? versionCheck.stdout?.toString().trim() : railwayPath;
|
|
2544
2610
|
notes.push(`Railway CLI: installed (${versionStr})`);
|
|
@@ -2558,6 +2624,7 @@ async function runRailwayDoctor(opts) {
|
|
|
2558
2624
|
};
|
|
2559
2625
|
}
|
|
2560
2626
|
notes.push(`Authenticated as: ${railwayAuthUser}`);
|
|
2627
|
+
onProgress?.("Testing Railway service connectivity...");
|
|
2561
2628
|
const { status: reachStatus } = railwayExec("echo ok", target);
|
|
2562
2629
|
const serviceReachable = reachStatus === 0;
|
|
2563
2630
|
if (!serviceReachable) {
|
|
@@ -2578,6 +2645,7 @@ async function runRailwayDoctor(opts) {
|
|
|
2578
2645
|
};
|
|
2579
2646
|
}
|
|
2580
2647
|
notes.push("Service connectivity: OK");
|
|
2648
|
+
onProgress?.("Inspecting Railway default paths...");
|
|
2581
2649
|
const gateway = checkRemotePath(DEFAULT_GATEWAY_DIR2, target);
|
|
2582
2650
|
const { stdout: expandedDefault } = railwayExec(`eval echo ${DEFAULT_SESSIONS_DIR2}`, target);
|
|
2583
2651
|
const resolvedDefault = expandedDefault || DEFAULT_SESSIONS_DIR2;
|
|
@@ -2770,6 +2838,7 @@ async function auditOrNoData(...args) {
|
|
|
2770
2838
|
}
|
|
2771
2839
|
}
|
|
2772
2840
|
async function runAuditCommand(options) {
|
|
2841
|
+
const logger = createCliLogger({ verbose: options.verbose });
|
|
2773
2842
|
if (options.dryRun && !options.push) {
|
|
2774
2843
|
throw new Error("--dry-run requires --push.");
|
|
2775
2844
|
}
|
|
@@ -2780,7 +2849,7 @@ async function runAuditCommand(options) {
|
|
|
2780
2849
|
throw new Error("Use only one of --remote, --remote-config, or --railway.");
|
|
2781
2850
|
}
|
|
2782
2851
|
if (!options.remote && !options.remoteConfig && !options.railway) {
|
|
2783
|
-
return runLocalAudit(options);
|
|
2852
|
+
return runLocalAudit(options, logger);
|
|
2784
2853
|
}
|
|
2785
2854
|
if (options.railway) {
|
|
2786
2855
|
const railwayTarget = buildRailwayTarget(options);
|
|
@@ -2789,14 +2858,14 @@ async function runAuditCommand(options) {
|
|
|
2789
2858
|
remoteLogFile: options.remoteLogFile,
|
|
2790
2859
|
remoteSessionsDir: options.remoteSessionsDir
|
|
2791
2860
|
});
|
|
2792
|
-
return runSingleRemoteAudit(source2, options);
|
|
2861
|
+
return runSingleRemoteAudit(source2, options, logger);
|
|
2793
2862
|
}
|
|
2794
2863
|
if (options.remoteConfig) {
|
|
2795
2864
|
const sources = loadRemoteConfig(options.remoteConfig);
|
|
2796
2865
|
if (sources.length === 1) {
|
|
2797
|
-
return runSingleRemoteAudit(sources[0], options);
|
|
2866
|
+
return runSingleRemoteAudit(sources[0], options, logger);
|
|
2798
2867
|
}
|
|
2799
|
-
return runMultiRemoteAudit(sources, options);
|
|
2868
|
+
return runMultiRemoteAudit(sources, options, logger);
|
|
2800
2869
|
}
|
|
2801
2870
|
const remote = options.remote;
|
|
2802
2871
|
const source = buildSourceFromFlags({
|
|
@@ -2804,7 +2873,7 @@ async function runAuditCommand(options) {
|
|
|
2804
2873
|
remoteLogFile: options.remoteLogFile,
|
|
2805
2874
|
remoteSessionsDir: options.remoteSessionsDir
|
|
2806
2875
|
});
|
|
2807
|
-
return runSingleRemoteAudit(source, options);
|
|
2876
|
+
return runSingleRemoteAudit(source, options, logger);
|
|
2808
2877
|
}
|
|
2809
2878
|
function buildRailwayTarget(options) {
|
|
2810
2879
|
if (options.railwayProject && options.railwayEnvironment && options.railwayService) {
|
|
@@ -2816,14 +2885,22 @@ function buildRailwayTarget(options) {
|
|
|
2816
2885
|
}
|
|
2817
2886
|
return void 0;
|
|
2818
2887
|
}
|
|
2819
|
-
async function runLocalAudit(options) {
|
|
2888
|
+
async function runLocalAudit(options, logger) {
|
|
2889
|
+
logger.verbose("Running a local audit.");
|
|
2890
|
+
if (options.logFile) {
|
|
2891
|
+
logger.verbose(`Using explicit local log file: ${options.logFile}`);
|
|
2892
|
+
}
|
|
2893
|
+
if (options.sessionsDir) {
|
|
2894
|
+
logger.verbose(`Using explicit local sessions directory: ${options.sessionsDir}`);
|
|
2895
|
+
}
|
|
2820
2896
|
const summary = await auditOrNoData({
|
|
2821
2897
|
logFile: options.logFile,
|
|
2822
2898
|
sessionsDir: options.sessionsDir,
|
|
2823
2899
|
since: options.since,
|
|
2824
2900
|
compare: options.compare,
|
|
2825
2901
|
dbPath: options.db,
|
|
2826
|
-
noDb: options.noDb
|
|
2902
|
+
noDb: options.noDb,
|
|
2903
|
+
onProgress: logger.verbose
|
|
2827
2904
|
});
|
|
2828
2905
|
renderOutput(summary, options);
|
|
2829
2906
|
if (options.push) {
|
|
@@ -2838,11 +2915,11 @@ function getComparisonKey(source) {
|
|
|
2838
2915
|
}
|
|
2839
2916
|
return buildComparisonKeyForRemote(source);
|
|
2840
2917
|
}
|
|
2841
|
-
function pullFiles(source, since, keepFiles) {
|
|
2918
|
+
function pullFiles(source, since, keepFiles, onProgress) {
|
|
2842
2919
|
if (source.transport === "railway") {
|
|
2843
|
-
return pullRemoteFilesRailway({ source, since, keepFiles });
|
|
2920
|
+
return pullRemoteFilesRailway({ source, since, keepFiles, onProgress });
|
|
2844
2921
|
}
|
|
2845
|
-
return pullRemoteFiles({ source, since, keepFiles });
|
|
2922
|
+
return pullRemoteFiles({ source, since, keepFiles, onProgress });
|
|
2846
2923
|
}
|
|
2847
2924
|
function describeSource(source) {
|
|
2848
2925
|
if (source.transport === "railway") {
|
|
@@ -2853,10 +2930,15 @@ function describeSource(source) {
|
|
|
2853
2930
|
function sourceEnvironment(source) {
|
|
2854
2931
|
return source.transport === "railway" ? "railway" : "remote";
|
|
2855
2932
|
}
|
|
2856
|
-
async function runSingleRemoteAudit(source, options) {
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2933
|
+
async function runSingleRemoteAudit(source, options, logger) {
|
|
2934
|
+
logger.info(`Pulling files from ${describeSource(source)}...`);
|
|
2935
|
+
const pullResult = await pullFiles(
|
|
2936
|
+
source,
|
|
2937
|
+
options.since,
|
|
2938
|
+
options.keepRemoteFiles,
|
|
2939
|
+
logger.verbose
|
|
2940
|
+
);
|
|
2941
|
+
logger.verbose(`Files staged at ${pullResult.localPath}.`);
|
|
2860
2942
|
try {
|
|
2861
2943
|
const comparisonKeyOverride = getComparisonKey(source);
|
|
2862
2944
|
const summary = await auditOrNoData({
|
|
@@ -2866,7 +2948,8 @@ async function runSingleRemoteAudit(source, options) {
|
|
|
2866
2948
|
compare: options.compare,
|
|
2867
2949
|
dbPath: options.db,
|
|
2868
2950
|
noDb: options.noDb,
|
|
2869
|
-
comparisonKeyOverride
|
|
2951
|
+
comparisonKeyOverride,
|
|
2952
|
+
onProgress: logger.verbose
|
|
2870
2953
|
});
|
|
2871
2954
|
renderOutput(summary, options);
|
|
2872
2955
|
if (options.push) {
|
|
@@ -2882,14 +2965,18 @@ async function runSingleRemoteAudit(source, options) {
|
|
|
2882
2965
|
cleanupPullResult(pullResult, options.keepRemoteFiles);
|
|
2883
2966
|
}
|
|
2884
2967
|
}
|
|
2885
|
-
async function runMultiRemoteAudit(sources, options) {
|
|
2968
|
+
async function runMultiRemoteAudit(sources, options, logger) {
|
|
2886
2969
|
const results = [];
|
|
2887
2970
|
const errors = [];
|
|
2888
2971
|
for (const source of sources) {
|
|
2889
|
-
|
|
2890
|
-
`);
|
|
2972
|
+
logger.info(`Pulling files from ${source.name} (${describeSource(source)})...`);
|
|
2891
2973
|
try {
|
|
2892
|
-
const pullResult = await pullFiles(
|
|
2974
|
+
const pullResult = await pullFiles(
|
|
2975
|
+
source,
|
|
2976
|
+
options.since,
|
|
2977
|
+
options.keepRemoteFiles,
|
|
2978
|
+
logger.verbose
|
|
2979
|
+
);
|
|
2893
2980
|
results.push({ source, pullResult });
|
|
2894
2981
|
} catch (err) {
|
|
2895
2982
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -2914,7 +3001,8 @@ ${errorMessages}`);
|
|
|
2914
3001
|
compare: options.compare,
|
|
2915
3002
|
dbPath: options.db,
|
|
2916
3003
|
noDb: options.noDb,
|
|
2917
|
-
comparisonKeyOverride
|
|
3004
|
+
comparisonKeyOverride,
|
|
3005
|
+
onProgress: logger.verbose
|
|
2918
3006
|
});
|
|
2919
3007
|
summaries.push({ name: source.name, source, summary });
|
|
2920
3008
|
}
|
|
@@ -3058,32 +3146,43 @@ function cleanupPullResult(pullResult, keepFiles) {
|
|
|
3058
3146
|
|
|
3059
3147
|
// src/commands/doctor.ts
|
|
3060
3148
|
async function runDoctorCommand(options) {
|
|
3149
|
+
const logger = createCliLogger({ verbose: options.verbose });
|
|
3061
3150
|
if (options.railway) {
|
|
3151
|
+
logger.verbose("Inspecting Railway audit readiness.");
|
|
3062
3152
|
const railwayTarget = buildRailwayTarget2(options);
|
|
3063
3153
|
const source = buildRailwaySourceFromFlags({
|
|
3064
3154
|
railway: railwayTarget,
|
|
3065
3155
|
remoteLogFile: options.remoteLogFile,
|
|
3066
3156
|
remoteSessionsDir: options.remoteSessionsDir
|
|
3067
3157
|
});
|
|
3068
|
-
const report2 = await runRailwayDoctor({ source });
|
|
3158
|
+
const report2 = await runRailwayDoctor({ source, onProgress: logger.verbose });
|
|
3069
3159
|
process.stdout.write(`${renderRailwayDoctorReport(report2)}
|
|
3070
3160
|
`);
|
|
3071
3161
|
return;
|
|
3072
3162
|
}
|
|
3073
3163
|
if (options.remote) {
|
|
3164
|
+
logger.verbose(`Inspecting SSH audit readiness for ${options.remote}.`);
|
|
3074
3165
|
const source = buildSourceFromFlags({
|
|
3075
3166
|
remote: options.remote,
|
|
3076
3167
|
remoteLogFile: options.remoteLogFile,
|
|
3077
3168
|
remoteSessionsDir: options.remoteSessionsDir
|
|
3078
3169
|
});
|
|
3079
|
-
const report2 = await runRemoteDoctor({ source });
|
|
3170
|
+
const report2 = await runRemoteDoctor({ source, onProgress: logger.verbose });
|
|
3080
3171
|
process.stdout.write(`${renderRemoteDoctorReport(report2)}
|
|
3081
3172
|
`);
|
|
3082
3173
|
return;
|
|
3083
3174
|
}
|
|
3175
|
+
logger.verbose("Inspecting local OpenClaw audit readiness.");
|
|
3176
|
+
if (options.logFile) {
|
|
3177
|
+
logger.verbose(`Using explicit local log file: ${options.logFile}`);
|
|
3178
|
+
}
|
|
3179
|
+
if (options.sessionsDir) {
|
|
3180
|
+
logger.verbose(`Using explicit local sessions directory: ${options.sessionsDir}`);
|
|
3181
|
+
}
|
|
3084
3182
|
const report = await doctorOpenClaw({
|
|
3085
3183
|
logFile: options.logFile,
|
|
3086
|
-
sessionsDir: options.sessionsDir
|
|
3184
|
+
sessionsDir: options.sessionsDir,
|
|
3185
|
+
onProgress: logger.verbose
|
|
3087
3186
|
});
|
|
3088
3187
|
process.stdout.write(`${renderDoctorReport(report)}
|
|
3089
3188
|
`);
|
|
@@ -3534,6 +3633,9 @@ function parseAuditOptions(raw) {
|
|
|
3534
3633
|
case "--dry-run":
|
|
3535
3634
|
options.dryRun = true;
|
|
3536
3635
|
break;
|
|
3636
|
+
case "--verbose":
|
|
3637
|
+
options.verbose = true;
|
|
3638
|
+
break;
|
|
3537
3639
|
case "--fail-above-waste-rate":
|
|
3538
3640
|
options.failAboveWasteRate = readFloat(arg, argv2[index + 1]);
|
|
3539
3641
|
index += 1;
|
|
@@ -3618,6 +3720,9 @@ function parseDoctorOptions(raw) {
|
|
|
3618
3720
|
options.railwayService = readValue(arg, argv2[index + 1]);
|
|
3619
3721
|
index += 1;
|
|
3620
3722
|
break;
|
|
3723
|
+
case "--verbose":
|
|
3724
|
+
options.verbose = true;
|
|
3725
|
+
break;
|
|
3621
3726
|
default:
|
|
3622
3727
|
throw new Error(`Unknown doctor option "${arg}". Run \`xerg doctor --help\` for usage.`);
|
|
3623
3728
|
}
|
|
@@ -3709,6 +3814,7 @@ Railway options:
|
|
|
3709
3814
|
Push options:
|
|
3710
3815
|
--push Push the audit summary to the Xerg API after computing it
|
|
3711
3816
|
--dry-run With --push: print the payload to stdout without sending it
|
|
3817
|
+
--verbose Print progress updates to stderr while the audit runs
|
|
3712
3818
|
|
|
3713
3819
|
Threshold options:
|
|
3714
3820
|
--fail-above-waste-rate <n> Exit with code 3 if structural waste rate exceeds threshold (e.g. 0.30)
|
|
@@ -3748,6 +3854,7 @@ Usage:
|
|
|
3748
3854
|
Options:
|
|
3749
3855
|
--log-file <path> Explicit OpenClaw gateway log file to inspect
|
|
3750
3856
|
--sessions-dir <path> Explicit OpenClaw sessions directory to inspect
|
|
3857
|
+
--verbose Print progress updates to stderr while doctor runs
|
|
3751
3858
|
|
|
3752
3859
|
Remote options (SSH):
|
|
3753
3860
|
--remote <user@host> SSH target in user@host or user@host:port format
|