@xerg/cli 0.3.0 → 0.4.0
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 +56 -33
- package/dist/index.js +685 -179
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/skills/xerg/SKILL.md +36 -12
package/dist/index.js
CHANGED
|
@@ -3887,7 +3887,8 @@ function loadPushConfig() {
|
|
|
3887
3887
|
if (envKey) {
|
|
3888
3888
|
return {
|
|
3889
3889
|
apiKey: envKey,
|
|
3890
|
-
apiUrl: envUrl || DEFAULT_API_URL
|
|
3890
|
+
apiUrl: envUrl || DEFAULT_API_URL,
|
|
3891
|
+
source: "env"
|
|
3891
3892
|
};
|
|
3892
3893
|
}
|
|
3893
3894
|
try {
|
|
@@ -3896,7 +3897,8 @@ function loadPushConfig() {
|
|
|
3896
3897
|
if (parsed.apiKey) {
|
|
3897
3898
|
return {
|
|
3898
3899
|
apiKey: parsed.apiKey,
|
|
3899
|
-
apiUrl: envUrl || parsed.apiUrl || DEFAULT_API_URL
|
|
3900
|
+
apiUrl: envUrl || parsed.apiUrl || DEFAULT_API_URL,
|
|
3901
|
+
source: "config"
|
|
3900
3902
|
};
|
|
3901
3903
|
}
|
|
3902
3904
|
} catch {
|
|
@@ -3905,7 +3907,8 @@ function loadPushConfig() {
|
|
|
3905
3907
|
if (storedToken) {
|
|
3906
3908
|
return {
|
|
3907
3909
|
apiKey: storedToken,
|
|
3908
|
-
apiUrl: envUrl || DEFAULT_API_URL
|
|
3910
|
+
apiUrl: envUrl || DEFAULT_API_URL,
|
|
3911
|
+
source: "stored"
|
|
3909
3912
|
};
|
|
3910
3913
|
}
|
|
3911
3914
|
throw new Error(
|
|
@@ -5255,6 +5258,348 @@ function cleanupPullResult(pullResult, keepFiles) {
|
|
|
5255
5258
|
}
|
|
5256
5259
|
}
|
|
5257
5260
|
|
|
5261
|
+
// src/commands/login.ts
|
|
5262
|
+
import { styleText } from "util";
|
|
5263
|
+
var DEFAULT_AUTH_URL = "https://xerg.ai/dashboard/settings";
|
|
5264
|
+
var DEFAULT_API_URL2 = "https://api.xerg.ai";
|
|
5265
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
5266
|
+
var POLL_TIMEOUT_MS = 3e5;
|
|
5267
|
+
async function runLoginCommand() {
|
|
5268
|
+
const existing = loadStoredCredentials();
|
|
5269
|
+
if (existing) {
|
|
5270
|
+
process.stderr.write(
|
|
5271
|
+
`Already logged in. Credentials stored at ${getCredentialsPath()}.
|
|
5272
|
+
Run ${colorBold(formatCommand("logout"))} first to re-authenticate.
|
|
5273
|
+
`
|
|
5274
|
+
);
|
|
5275
|
+
return;
|
|
5276
|
+
}
|
|
5277
|
+
const data = await performDeviceLogin();
|
|
5278
|
+
storeCredentials(data.token);
|
|
5279
|
+
const teamInfo = data.teamName ? ` (team: ${data.teamName})` : "";
|
|
5280
|
+
process.stderr.write(
|
|
5281
|
+
`
|
|
5282
|
+
${colorSuccess("Authenticated successfully")}${teamInfo}.
|
|
5283
|
+
Credentials saved to ${getCredentialsPath()}.
|
|
5284
|
+
`
|
|
5285
|
+
);
|
|
5286
|
+
}
|
|
5287
|
+
async function performDeviceLogin() {
|
|
5288
|
+
const apiUrl = process.env.XERG_API_URL || DEFAULT_API_URL2;
|
|
5289
|
+
const deviceCodeUrl = `${apiUrl}/v1/auth/device-code`;
|
|
5290
|
+
let deviceResponse;
|
|
5291
|
+
try {
|
|
5292
|
+
const res = await fetch(deviceCodeUrl, { method: "POST" });
|
|
5293
|
+
if (!res.ok) {
|
|
5294
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
5295
|
+
}
|
|
5296
|
+
deviceResponse = await res.json();
|
|
5297
|
+
} catch (err) {
|
|
5298
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
5299
|
+
throw new Error(
|
|
5300
|
+
`Could not start device auth flow (${msg}).
|
|
5301
|
+
|
|
5302
|
+
Alternative: create an API key at ${DEFAULT_AUTH_URL}
|
|
5303
|
+
and set XERG_API_KEY in your environment.`
|
|
5304
|
+
);
|
|
5305
|
+
}
|
|
5306
|
+
const verifyUrl = deviceResponse.verificationUrl || DEFAULT_AUTH_URL;
|
|
5307
|
+
const pollInterval = (deviceResponse.interval || 2) * 1e3;
|
|
5308
|
+
process.stderr.write(
|
|
5309
|
+
`
|
|
5310
|
+
Open this URL in your browser to authenticate:
|
|
5311
|
+
|
|
5312
|
+
${colorBold(verifyUrl)}
|
|
5313
|
+
|
|
5314
|
+
`
|
|
5315
|
+
);
|
|
5316
|
+
if (deviceResponse.userCode) {
|
|
5317
|
+
process.stderr.write(`Your code: ${colorBold(deviceResponse.userCode)}
|
|
5318
|
+
|
|
5319
|
+
`);
|
|
5320
|
+
}
|
|
5321
|
+
process.stderr.write("Waiting for authentication...\n");
|
|
5322
|
+
await openBrowser(verifyUrl);
|
|
5323
|
+
const tokenUrl = `${apiUrl}/v1/auth/device-token`;
|
|
5324
|
+
const startTime = Date.now();
|
|
5325
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
5326
|
+
await sleep(Math.max(pollInterval, POLL_INTERVAL_MS));
|
|
5327
|
+
try {
|
|
5328
|
+
const res = await fetch(tokenUrl, {
|
|
5329
|
+
method: "POST",
|
|
5330
|
+
headers: { "Content-Type": "application/json" },
|
|
5331
|
+
body: JSON.stringify({ deviceCode: deviceResponse.deviceCode })
|
|
5332
|
+
});
|
|
5333
|
+
if (res.status === 200) {
|
|
5334
|
+
return await res.json();
|
|
5335
|
+
}
|
|
5336
|
+
if (res.status === 428) {
|
|
5337
|
+
continue;
|
|
5338
|
+
}
|
|
5339
|
+
if (res.status === 410) {
|
|
5340
|
+
throw new Error(`Device code expired. Please run \`${formatCommand("login")}\` again.`);
|
|
5341
|
+
}
|
|
5342
|
+
const body = await res.json().catch(() => ({}));
|
|
5343
|
+
throw new Error(body.error || `Unexpected response: HTTP ${res.status}`);
|
|
5344
|
+
} catch (err) {
|
|
5345
|
+
if (err instanceof Error && (err.message.includes("expired") || err.message.includes("Unexpected"))) {
|
|
5346
|
+
throw err;
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
throw new Error(`Authentication timed out. Please run \`${formatCommand("login")}\` again.`);
|
|
5351
|
+
}
|
|
5352
|
+
async function openBrowser(url) {
|
|
5353
|
+
const { exec } = await import("child_process");
|
|
5354
|
+
const { platform: platform2 } = await import("os");
|
|
5355
|
+
const commands = {
|
|
5356
|
+
darwin: "open",
|
|
5357
|
+
win32: "start",
|
|
5358
|
+
linux: "xdg-open"
|
|
5359
|
+
};
|
|
5360
|
+
const cmd = commands[platform2()];
|
|
5361
|
+
if (!cmd) return;
|
|
5362
|
+
return new Promise((resolve4) => {
|
|
5363
|
+
exec(`${cmd} ${JSON.stringify(url)}`, () => resolve4());
|
|
5364
|
+
});
|
|
5365
|
+
}
|
|
5366
|
+
function sleep(ms) {
|
|
5367
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
5368
|
+
}
|
|
5369
|
+
function colorBold(text) {
|
|
5370
|
+
return process.stderr.isTTY ? styleText("bold", text) : text;
|
|
5371
|
+
}
|
|
5372
|
+
function colorSuccess(text) {
|
|
5373
|
+
return process.stderr.isTTY ? styleText("green", text) : text;
|
|
5374
|
+
}
|
|
5375
|
+
|
|
5376
|
+
// src/cloud.ts
|
|
5377
|
+
function loadPushConfigOrNull() {
|
|
5378
|
+
try {
|
|
5379
|
+
return loadPushConfig();
|
|
5380
|
+
} catch {
|
|
5381
|
+
return null;
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
async function authenticateAndLoadPushConfig() {
|
|
5385
|
+
const data = await performDeviceLogin();
|
|
5386
|
+
storeCredentials(data.token);
|
|
5387
|
+
const teamInfo = data.teamName ? ` (team: ${data.teamName})` : "";
|
|
5388
|
+
process.stderr.write(
|
|
5389
|
+
`
|
|
5390
|
+
Authenticated successfully${teamInfo}.
|
|
5391
|
+
Credentials saved to ${getCredentialsPath()}.
|
|
5392
|
+
`
|
|
5393
|
+
);
|
|
5394
|
+
return loadPushConfig();
|
|
5395
|
+
}
|
|
5396
|
+
function renderCloudDisclaimer() {
|
|
5397
|
+
return [
|
|
5398
|
+
"Xerg Cloud sync and hosted MCP are optional paid workspace features.",
|
|
5399
|
+
"Local audits and compare stay free, and you can keep using Xerg locally if you skip this step."
|
|
5400
|
+
].join("\n");
|
|
5401
|
+
}
|
|
5402
|
+
function renderMcpCredentialSourceMessage(config) {
|
|
5403
|
+
if (config.source === "stored") {
|
|
5404
|
+
return "Using your stored login token. If hosted MCP requires a workspace API key, create one at xerg.ai/dashboard/settings and set XERG_API_KEY.";
|
|
5405
|
+
}
|
|
5406
|
+
return "Using your workspace API key.";
|
|
5407
|
+
}
|
|
5408
|
+
|
|
5409
|
+
// src/prompts.ts
|
|
5410
|
+
import { confirm, select } from "@inquirer/prompts";
|
|
5411
|
+
function hasPromptTty() {
|
|
5412
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
5413
|
+
}
|
|
5414
|
+
async function promptConfirm(message, defaultValue = true) {
|
|
5415
|
+
return confirm({
|
|
5416
|
+
message,
|
|
5417
|
+
default: defaultValue
|
|
5418
|
+
});
|
|
5419
|
+
}
|
|
5420
|
+
async function promptSelect(message, choices) {
|
|
5421
|
+
return select({
|
|
5422
|
+
message,
|
|
5423
|
+
choices
|
|
5424
|
+
});
|
|
5425
|
+
}
|
|
5426
|
+
|
|
5427
|
+
// src/commands/push.ts
|
|
5428
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
5429
|
+
async function runPushCommand(options) {
|
|
5430
|
+
const payload = options.file ? loadPayloadFromFile(options.file) : loadLatestCachedAuditPayload();
|
|
5431
|
+
if (options.dryRun) {
|
|
5432
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
5433
|
+
`);
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
const config = loadPushConfig();
|
|
5437
|
+
const auditId = payload.summary.auditId;
|
|
5438
|
+
process.stderr.write(`Pushing audit ${auditId} to ${config.apiUrl}...
|
|
5439
|
+
`);
|
|
5440
|
+
const result = await pushAudit(payload, config);
|
|
5441
|
+
if (result.ok) {
|
|
5442
|
+
process.stderr.write(`Pushed successfully (audit: ${result.auditId}).
|
|
5443
|
+
`);
|
|
5444
|
+
} else {
|
|
5445
|
+
const statusInfo = result.status > 0 ? ` (HTTP ${result.status})` : "";
|
|
5446
|
+
throw new Error(`Push failed${statusInfo}: ${result.message}`);
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
function loadPayloadFromFile(filePath) {
|
|
5450
|
+
let raw;
|
|
5451
|
+
try {
|
|
5452
|
+
raw = readFileSync8(filePath, "utf8");
|
|
5453
|
+
} catch {
|
|
5454
|
+
throw new Error(`Cannot read file: ${filePath}`);
|
|
5455
|
+
}
|
|
5456
|
+
let parsed;
|
|
5457
|
+
try {
|
|
5458
|
+
parsed = JSON.parse(raw);
|
|
5459
|
+
} catch {
|
|
5460
|
+
throw new Error(`File is not valid JSON: ${filePath}`);
|
|
5461
|
+
}
|
|
5462
|
+
const payload = parsed;
|
|
5463
|
+
if (!payload.version || !payload.summary || !payload.meta) {
|
|
5464
|
+
throw new Error(
|
|
5465
|
+
`File does not look like an AuditPushPayload (missing version, summary, or meta): ${filePath}`
|
|
5466
|
+
);
|
|
5467
|
+
}
|
|
5468
|
+
return payload;
|
|
5469
|
+
}
|
|
5470
|
+
function loadLatestCachedAuditPayload() {
|
|
5471
|
+
const dbPath = getDefaultDbPath();
|
|
5472
|
+
let summaries;
|
|
5473
|
+
try {
|
|
5474
|
+
summaries = listStoredAuditSummaries(dbPath);
|
|
5475
|
+
} catch {
|
|
5476
|
+
throw new NoDataError(
|
|
5477
|
+
`No local audit database found. Run \`${formatCommand("audit")}\` first, or use \`${formatCommand("push --file <path>")}\`.`
|
|
5478
|
+
);
|
|
5479
|
+
}
|
|
5480
|
+
if (summaries.length === 0) {
|
|
5481
|
+
throw new NoDataError(
|
|
5482
|
+
`No cached audit snapshots found. Run \`${formatCommand("audit")}\` first, or use \`${formatCommand("push --file <path>")}\`.`
|
|
5483
|
+
);
|
|
5484
|
+
}
|
|
5485
|
+
const latest = summaries[0];
|
|
5486
|
+
const meta = buildMeta2(latest);
|
|
5487
|
+
process.stderr.write(
|
|
5488
|
+
`Using most recent cached audit: ${latest.auditId} (${latest.generatedAt})
|
|
5489
|
+
`
|
|
5490
|
+
);
|
|
5491
|
+
return toWirePayload(latest, meta);
|
|
5492
|
+
}
|
|
5493
|
+
function buildMeta2(summary) {
|
|
5494
|
+
const sourceMeta = buildCachedPushSourceMeta(summary);
|
|
5495
|
+
return {
|
|
5496
|
+
cliVersion: getCliVersion(),
|
|
5497
|
+
sourceId: sourceMeta.sourceId,
|
|
5498
|
+
sourceHost: sourceMeta.sourceHost,
|
|
5499
|
+
environment: sourceMeta.environment
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
// src/commands/connect.ts
|
|
5504
|
+
async function runConnectCommand() {
|
|
5505
|
+
await runConnectFlow();
|
|
5506
|
+
}
|
|
5507
|
+
async function runConnectFlow(options) {
|
|
5508
|
+
if (!options?.skipDisclaimer) {
|
|
5509
|
+
process.stderr.write(`${renderCloudDisclaimer()}
|
|
5510
|
+
`);
|
|
5511
|
+
}
|
|
5512
|
+
let config = loadPushConfigOrNull();
|
|
5513
|
+
if (config) {
|
|
5514
|
+
process.stderr.write("Xerg authentication detected.\n");
|
|
5515
|
+
} else {
|
|
5516
|
+
if (!hasPromptTty()) {
|
|
5517
|
+
process.stderr.write(
|
|
5518
|
+
`No Xerg authentication is configured, and ${formatCommand("connect")} needs an interactive terminal before it can start browser login.
|
|
5519
|
+
Run ${formatCommand("login")} from a TTY, or keep using local audits for free.
|
|
5520
|
+
`
|
|
5521
|
+
);
|
|
5522
|
+
process.exitCode = 1;
|
|
5523
|
+
return false;
|
|
5524
|
+
}
|
|
5525
|
+
const shouldLogin = await promptConfirm("Sign in to Xerg Cloud now?", true);
|
|
5526
|
+
if (!shouldLogin) {
|
|
5527
|
+
process.stderr.write(
|
|
5528
|
+
"Skipped Xerg Cloud setup. You can keep using local audits and compare without connecting.\n"
|
|
5529
|
+
);
|
|
5530
|
+
return false;
|
|
5531
|
+
}
|
|
5532
|
+
config = await authenticateAndLoadPushConfig();
|
|
5533
|
+
}
|
|
5534
|
+
if (!hasPromptTty()) {
|
|
5535
|
+
if (!options?.auditSummary) {
|
|
5536
|
+
process.stderr.write(
|
|
5537
|
+
`Non-interactive mode skips the push prompt. Run ${formatCommand("push")} when you want to sync a cached audit.
|
|
5538
|
+
`
|
|
5539
|
+
);
|
|
5540
|
+
} else {
|
|
5541
|
+
process.stderr.write(
|
|
5542
|
+
`Authentication is ready. Run ${formatCommand("push")} later if you want to sync this audit.
|
|
5543
|
+
`
|
|
5544
|
+
);
|
|
5545
|
+
}
|
|
5546
|
+
return true;
|
|
5547
|
+
}
|
|
5548
|
+
const shouldPush = await promptConfirm(
|
|
5549
|
+
options?.auditSummary ? "Push this audit to Xerg Cloud?" : "Push your latest cached audit to Xerg Cloud?",
|
|
5550
|
+
true
|
|
5551
|
+
);
|
|
5552
|
+
if (!shouldPush) {
|
|
5553
|
+
process.stderr.write(
|
|
5554
|
+
options?.auditSummary ? `Skipped push. Run ${formatCommand("push")} later if you want to sync a cached audit.
|
|
5555
|
+
` : `Skipped push. Run ${formatCommand("push")} when you want to sync a cached audit.
|
|
5556
|
+
`
|
|
5557
|
+
);
|
|
5558
|
+
return true;
|
|
5559
|
+
}
|
|
5560
|
+
const payload = options?.auditSummary ? toWirePayload(options.auditSummary, buildLocalMeta(options.auditSummary)) : loadStandalonePayload();
|
|
5561
|
+
if (!payload) {
|
|
5562
|
+
return true;
|
|
5563
|
+
}
|
|
5564
|
+
await pushResolvedPayload(payload, config ?? loadPushConfig());
|
|
5565
|
+
return true;
|
|
5566
|
+
}
|
|
5567
|
+
function buildLocalMeta(summary) {
|
|
5568
|
+
const sourceMeta = buildLocalPushSourceMeta(summary.runtime);
|
|
5569
|
+
return {
|
|
5570
|
+
cliVersion: getCliVersion(),
|
|
5571
|
+
sourceId: sourceMeta.sourceId,
|
|
5572
|
+
sourceHost: sourceMeta.sourceHost,
|
|
5573
|
+
environment: sourceMeta.environment
|
|
5574
|
+
};
|
|
5575
|
+
}
|
|
5576
|
+
function loadStandalonePayload() {
|
|
5577
|
+
try {
|
|
5578
|
+
return loadLatestCachedAuditPayload();
|
|
5579
|
+
} catch (error) {
|
|
5580
|
+
if (error instanceof NoDataError || error instanceof Error && error.name === "NoDataError") {
|
|
5581
|
+
process.stderr.write(
|
|
5582
|
+
`${error instanceof Error ? error.message : "No cached audit snapshots found."}
|
|
5583
|
+
`
|
|
5584
|
+
);
|
|
5585
|
+
return null;
|
|
5586
|
+
}
|
|
5587
|
+
throw error;
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
async function pushResolvedPayload(payload, config) {
|
|
5591
|
+
process.stderr.write(`Pushing audit ${payload.summary.auditId} to ${config.apiUrl}...
|
|
5592
|
+
`);
|
|
5593
|
+
const result = await pushAudit(payload, config);
|
|
5594
|
+
if (result.ok) {
|
|
5595
|
+
process.stderr.write(`Pushed successfully (audit: ${result.auditId}).
|
|
5596
|
+
`);
|
|
5597
|
+
return;
|
|
5598
|
+
}
|
|
5599
|
+
const statusInfo = result.status > 0 ? ` (HTTP ${result.status})` : "";
|
|
5600
|
+
throw new Error(`Push failed${statusInfo}: ${result.message}`);
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5258
5603
|
// src/commands/doctor.ts
|
|
5259
5604
|
async function runDoctorCommand(options) {
|
|
5260
5605
|
const logger = createCliLogger({ verbose: options.verbose });
|
|
@@ -5470,121 +5815,269 @@ function renderRailwayDoctorReport(report) {
|
|
|
5470
5815
|
);
|
|
5471
5816
|
}
|
|
5472
5817
|
}
|
|
5473
|
-
sections.push("", "## Notes", ...report.notes.map((n) => `[railway] ${n}`));
|
|
5474
|
-
return sections.join("\n");
|
|
5818
|
+
sections.push("", "## Notes", ...report.notes.map((n) => `[railway] ${n}`));
|
|
5819
|
+
return sections.join("\n");
|
|
5820
|
+
}
|
|
5821
|
+
|
|
5822
|
+
// src/commands/mcp-setup.ts
|
|
5823
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "fs";
|
|
5824
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
5825
|
+
var HOSTED_MCP_URL = "https://mcp.xerg.ai/mcp";
|
|
5826
|
+
async function runMcpSetupCommand() {
|
|
5827
|
+
await runMcpSetupFlow();
|
|
5828
|
+
}
|
|
5829
|
+
async function runMcpSetupFlow() {
|
|
5830
|
+
let config = loadPushConfigOrNull();
|
|
5831
|
+
if (!config) {
|
|
5832
|
+
process.stderr.write(`${renderCloudDisclaimer()}
|
|
5833
|
+
`);
|
|
5834
|
+
process.stderr.write("Hosted MCP requires Xerg Cloud authentication before client setup.\n");
|
|
5835
|
+
}
|
|
5836
|
+
if (!hasPromptTty()) {
|
|
5837
|
+
process.stderr.write(
|
|
5838
|
+
`${formatCommand("mcp-setup")} needs an interactive terminal so it can ask which MCP client you want to configure.
|
|
5839
|
+
`
|
|
5840
|
+
);
|
|
5841
|
+
process.exitCode = 1;
|
|
5842
|
+
return;
|
|
5843
|
+
}
|
|
5844
|
+
if (!config) {
|
|
5845
|
+
const shouldLogin = await promptConfirm("Authenticate with Xerg Cloud now?", true);
|
|
5846
|
+
if (!shouldLogin) {
|
|
5847
|
+
process.stderr.write(
|
|
5848
|
+
`Skipped hosted MCP setup. Run ${formatCommand("mcp-setup")} when you're ready.
|
|
5849
|
+
`
|
|
5850
|
+
);
|
|
5851
|
+
return;
|
|
5852
|
+
}
|
|
5853
|
+
config = await authenticateAndLoadPushConfig();
|
|
5854
|
+
}
|
|
5855
|
+
process.stderr.write(`${renderMcpCredentialSourceMessage(config)}
|
|
5856
|
+
`);
|
|
5857
|
+
const client = await promptSelect("Which MCP client do you want to configure?", [
|
|
5858
|
+
{
|
|
5859
|
+
name: "Cursor",
|
|
5860
|
+
value: "cursor",
|
|
5861
|
+
description: "Project-scoped or global Cursor MCP config"
|
|
5862
|
+
},
|
|
5863
|
+
{
|
|
5864
|
+
name: "Claude Code",
|
|
5865
|
+
value: "claude-code",
|
|
5866
|
+
description: "Project-scoped Claude Code MCP config"
|
|
5867
|
+
},
|
|
5868
|
+
{
|
|
5869
|
+
name: "Other",
|
|
5870
|
+
value: "other",
|
|
5871
|
+
description: "Print the hosted HTTP MCP snippet for another client"
|
|
5872
|
+
}
|
|
5873
|
+
]);
|
|
5874
|
+
const snippet = JSON.stringify(buildHostedMcpConfig(config), null, 2);
|
|
5875
|
+
if (client === "cursor") {
|
|
5876
|
+
await handleCursorSetup(snippet, config);
|
|
5877
|
+
return;
|
|
5878
|
+
}
|
|
5879
|
+
process.stdout.write(`${snippet}
|
|
5880
|
+
`);
|
|
5881
|
+
if (client === "claude-code") {
|
|
5882
|
+
process.stderr.write(
|
|
5883
|
+
"Add this to `.mcp.json` in your project root, or import the same `mcpServers.xerg` config through Claude Code MCP settings.\n"
|
|
5884
|
+
);
|
|
5885
|
+
return;
|
|
5886
|
+
}
|
|
5887
|
+
process.stderr.write(
|
|
5888
|
+
`Add this as a remote HTTP MCP server in your client. Endpoint: ${HOSTED_MCP_URL}
|
|
5889
|
+
`
|
|
5890
|
+
);
|
|
5891
|
+
}
|
|
5892
|
+
async function handleCursorSetup(snippet, config) {
|
|
5893
|
+
const cursorDir = join8(process.cwd(), ".cursor");
|
|
5894
|
+
const cursorConfigPath = join8(cursorDir, "mcp.json");
|
|
5895
|
+
if (existsSync2(cursorDir)) {
|
|
5896
|
+
const shouldWrite = await promptConfirm(
|
|
5897
|
+
"Write a project-scoped Cursor MCP config to .cursor/mcp.json?",
|
|
5898
|
+
true
|
|
5899
|
+
);
|
|
5900
|
+
if (shouldWrite) {
|
|
5901
|
+
writeCursorConfig(cursorConfigPath, config);
|
|
5902
|
+
process.stderr.write(`Wrote hosted MCP config to ${cursorConfigPath}.
|
|
5903
|
+
`);
|
|
5904
|
+
return;
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
process.stdout.write(`${snippet}
|
|
5908
|
+
`);
|
|
5909
|
+
process.stderr.write(
|
|
5910
|
+
"Add this to `.cursor/mcp.json` for a project-scoped Cursor config, or `~/.cursor/mcp.json` for a global Cursor config.\n"
|
|
5911
|
+
);
|
|
5912
|
+
}
|
|
5913
|
+
function buildHostedMcpConfig(config) {
|
|
5914
|
+
return {
|
|
5915
|
+
mcpServers: {
|
|
5916
|
+
xerg: {
|
|
5917
|
+
type: "http",
|
|
5918
|
+
url: HOSTED_MCP_URL,
|
|
5919
|
+
headers: {
|
|
5920
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
5921
|
+
}
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
};
|
|
5925
|
+
}
|
|
5926
|
+
function writeCursorConfig(filePath, config) {
|
|
5927
|
+
mkdirSync6(dirname3(filePath), { recursive: true });
|
|
5928
|
+
let parsed = {};
|
|
5929
|
+
if (existsSync2(filePath)) {
|
|
5930
|
+
try {
|
|
5931
|
+
parsed = JSON.parse(readFileSync9(filePath, "utf8"));
|
|
5932
|
+
} catch {
|
|
5933
|
+
throw new Error(`Cursor config is not valid JSON: ${filePath}`);
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5936
|
+
const existingServers = parsed.mcpServers;
|
|
5937
|
+
if (existingServers && typeof existingServers !== "object") {
|
|
5938
|
+
throw new Error(`Cursor config has an invalid "mcpServers" value: ${filePath}`);
|
|
5939
|
+
}
|
|
5940
|
+
parsed.mcpServers = {
|
|
5941
|
+
...existingServers ?? {},
|
|
5942
|
+
xerg: buildHostedMcpConfig(config).mcpServers.xerg
|
|
5943
|
+
};
|
|
5944
|
+
writeFileSync2(filePath, `${JSON.stringify(parsed, null, 2)}
|
|
5945
|
+
`);
|
|
5475
5946
|
}
|
|
5476
5947
|
|
|
5477
|
-
// src/commands/
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
var DEFAULT_API_URL2 = "https://api.xerg.ai";
|
|
5481
|
-
var POLL_INTERVAL_MS = 2e3;
|
|
5482
|
-
var POLL_TIMEOUT_MS = 3e5;
|
|
5483
|
-
async function runLoginCommand() {
|
|
5484
|
-
const existing = loadStoredCredentials();
|
|
5485
|
-
if (existing) {
|
|
5948
|
+
// src/commands/init.ts
|
|
5949
|
+
async function runInitCommand() {
|
|
5950
|
+
if (!hasPromptTty()) {
|
|
5486
5951
|
process.stderr.write(
|
|
5487
|
-
|
|
5488
|
-
Run ${colorBold(formatCommand("logout"))} first to re-authenticate.
|
|
5952
|
+
`${formatCommand("init")} is interactive in this release. Run ${formatCommand("audit")} directly when you need a non-interactive audit.
|
|
5489
5953
|
`
|
|
5490
5954
|
);
|
|
5955
|
+
process.exitCode = 1;
|
|
5956
|
+
return;
|
|
5957
|
+
}
|
|
5958
|
+
const candidates = await resolveRuntimeCandidates({ runtime: "auto" });
|
|
5959
|
+
const usable = candidates.filter((candidate) => candidate.usable);
|
|
5960
|
+
if (usable.length === 0) {
|
|
5961
|
+
renderNoDataGuidance();
|
|
5962
|
+
return;
|
|
5963
|
+
}
|
|
5964
|
+
const runtime = await chooseRuntime(usable);
|
|
5965
|
+
if (!runtime) {
|
|
5491
5966
|
return;
|
|
5492
5967
|
}
|
|
5493
|
-
const apiUrl = process.env.XERG_API_URL || DEFAULT_API_URL2;
|
|
5494
|
-
const deviceCodeUrl = `${apiUrl}/v1/auth/device-code`;
|
|
5495
|
-
let deviceResponse;
|
|
5496
5968
|
try {
|
|
5497
|
-
const
|
|
5498
|
-
|
|
5499
|
-
|
|
5969
|
+
const summary = await auditAgentRuntime({
|
|
5970
|
+
runtime,
|
|
5971
|
+
commandPrefix: formatCommand("")
|
|
5972
|
+
});
|
|
5973
|
+
process.stdout.write(`${renderTerminalSummary(summary)}
|
|
5974
|
+
`);
|
|
5975
|
+
process.stderr.write(
|
|
5976
|
+
`
|
|
5977
|
+
Next: after you make a fix, run ${formatCommand("audit --compare")} to measure the delta.
|
|
5978
|
+
`
|
|
5979
|
+
);
|
|
5980
|
+
const existingAuth = loadPushConfigOrNull();
|
|
5981
|
+
process.stderr.write(
|
|
5982
|
+
`${existingAuth ? "Xerg Cloud authentication is already configured. You can optionally push this audit and set up hosted MCP next." : renderCloudDisclaimer()}
|
|
5983
|
+
`
|
|
5984
|
+
);
|
|
5985
|
+
const shouldConnect = await promptConfirm("Continue with optional Xerg Cloud setup?", true);
|
|
5986
|
+
if (!shouldConnect) {
|
|
5987
|
+
process.stderr.write(
|
|
5988
|
+
`Skipped Xerg Cloud setup. Run ${formatCommand("connect")} or ${formatCommand("mcp-setup")} whenever you want the hosted follow-up.
|
|
5989
|
+
`
|
|
5990
|
+
);
|
|
5991
|
+
return;
|
|
5500
5992
|
}
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5993
|
+
const connected = await runConnectFlow({
|
|
5994
|
+
skipDisclaimer: true,
|
|
5995
|
+
auditSummary: summary
|
|
5996
|
+
});
|
|
5997
|
+
if (!connected) {
|
|
5998
|
+
return;
|
|
5999
|
+
}
|
|
6000
|
+
const shouldSetupMcp = await promptConfirm("Set up hosted MCP now?", true);
|
|
6001
|
+
if (!shouldSetupMcp) {
|
|
6002
|
+
process.stderr.write(
|
|
6003
|
+
`Skipped hosted MCP setup. Run ${formatCommand("mcp-setup")} when you're ready.
|
|
6004
|
+
`
|
|
6005
|
+
);
|
|
6006
|
+
return;
|
|
6007
|
+
}
|
|
6008
|
+
await runMcpSetupFlow();
|
|
6009
|
+
} catch (error) {
|
|
6010
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6011
|
+
const productName = getRuntimeAdapter(runtime).productName;
|
|
6012
|
+
process.stderr.write(
|
|
6013
|
+
`${[
|
|
6014
|
+
`${productName} audit failed: ${message}`,
|
|
6015
|
+
`Try ${formatCommand(["doctor", "--runtime", runtime])} to inspect the detected paths first.`,
|
|
6016
|
+
`Re-run ${formatCommand("audit --verbose")} for more detail.`
|
|
6017
|
+
].join("\n")}
|
|
6018
|
+
`
|
|
5509
6019
|
);
|
|
6020
|
+
process.exitCode = 1;
|
|
5510
6021
|
}
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
${colorBold(verifyUrl)}
|
|
5518
|
-
|
|
5519
|
-
`
|
|
5520
|
-
);
|
|
5521
|
-
if (deviceResponse.userCode) {
|
|
5522
|
-
process.stderr.write(`Your code: ${colorBold(deviceResponse.userCode)}
|
|
5523
|
-
|
|
6022
|
+
}
|
|
6023
|
+
async function chooseRuntime(candidates) {
|
|
6024
|
+
if (candidates.length === 1) {
|
|
6025
|
+
const candidate = candidates[0];
|
|
6026
|
+
process.stderr.write(`${describeCandidate(candidate)}
|
|
5524
6027
|
`);
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
try {
|
|
5533
|
-
const res = await fetch(tokenUrl, {
|
|
5534
|
-
method: "POST",
|
|
5535
|
-
headers: { "Content-Type": "application/json" },
|
|
5536
|
-
body: JSON.stringify({ deviceCode: deviceResponse.deviceCode })
|
|
5537
|
-
});
|
|
5538
|
-
if (res.status === 200) {
|
|
5539
|
-
const data = await res.json();
|
|
5540
|
-
storeCredentials(data.token);
|
|
5541
|
-
const teamInfo = data.teamName ? ` (team: ${data.teamName})` : "";
|
|
5542
|
-
process.stderr.write(
|
|
5543
|
-
`
|
|
5544
|
-
${colorSuccess("Authenticated successfully")}${teamInfo}.
|
|
5545
|
-
Credentials saved to ${getCredentialsPath()}.
|
|
6028
|
+
const shouldAudit = await promptConfirm(
|
|
6029
|
+
`Run your first ${candidate.adapter.productName} audit now?`,
|
|
6030
|
+
true
|
|
6031
|
+
);
|
|
6032
|
+
if (!shouldAudit) {
|
|
6033
|
+
process.stderr.write(
|
|
6034
|
+
`Skipped the first audit. Run ${formatCommand(["audit", "--runtime", candidate.adapter.runtime])} when you're ready.
|
|
5546
6035
|
`
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
}
|
|
5550
|
-
if (res.status === 428) {
|
|
5551
|
-
continue;
|
|
5552
|
-
}
|
|
5553
|
-
if (res.status === 410) {
|
|
5554
|
-
throw new Error(`Device code expired. Please run \`${formatCommand("login")}\` again.`);
|
|
5555
|
-
}
|
|
5556
|
-
const body = await res.json().catch(() => ({}));
|
|
5557
|
-
throw new Error(body.error || `Unexpected response: HTTP ${res.status}`);
|
|
5558
|
-
} catch (err) {
|
|
5559
|
-
if (err instanceof Error && (err.message.includes("expired") || err.message.includes("Unexpected"))) {
|
|
5560
|
-
throw err;
|
|
5561
|
-
}
|
|
6036
|
+
);
|
|
6037
|
+
return null;
|
|
5562
6038
|
}
|
|
6039
|
+
return candidate.adapter.runtime;
|
|
5563
6040
|
}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
win32: "start",
|
|
5572
|
-
linux: "xdg-open"
|
|
5573
|
-
};
|
|
5574
|
-
const cmd = commands[platform2()];
|
|
5575
|
-
if (!cmd) return;
|
|
5576
|
-
return new Promise((resolve4) => {
|
|
5577
|
-
exec(`${cmd} ${JSON.stringify(url)}`, () => resolve4());
|
|
5578
|
-
});
|
|
6041
|
+
return promptSelect("Choose the local runtime to audit first.", [
|
|
6042
|
+
...candidates.map((candidate) => ({
|
|
6043
|
+
name: candidate.adapter.productName,
|
|
6044
|
+
value: candidate.adapter.runtime,
|
|
6045
|
+
description: describeSources(candidate)
|
|
6046
|
+
}))
|
|
6047
|
+
]);
|
|
5579
6048
|
}
|
|
5580
|
-
function
|
|
5581
|
-
return
|
|
6049
|
+
function describeCandidate(candidate) {
|
|
6050
|
+
return `Found local ${candidate.adapter.productName} data (${describeSources(candidate)}).`;
|
|
5582
6051
|
}
|
|
5583
|
-
function
|
|
5584
|
-
|
|
6052
|
+
function describeSources(candidate) {
|
|
6053
|
+
const kinds = new Set(candidate.sources.map((source) => source.kind));
|
|
6054
|
+
const details = [
|
|
6055
|
+
kinds.has("gateway") ? "gateway logs" : null,
|
|
6056
|
+
kinds.has("sessions") ? "session transcripts" : null
|
|
6057
|
+
].filter((detail) => detail !== null);
|
|
6058
|
+
return details.join(" and ");
|
|
5585
6059
|
}
|
|
5586
|
-
function
|
|
5587
|
-
|
|
6060
|
+
function renderNoDataGuidance() {
|
|
6061
|
+
const openclawDefaults = getRuntimeAdapter("openclaw").defaultPaths();
|
|
6062
|
+
const hermesDefaults = getRuntimeAdapter("hermes").defaultPaths();
|
|
6063
|
+
process.stderr.write(
|
|
6064
|
+
`${[
|
|
6065
|
+
"No local OpenClaw or Hermes data was detected in the default locations Xerg checked.",
|
|
6066
|
+
"",
|
|
6067
|
+
"Checked defaults:",
|
|
6068
|
+
`- OpenClaw gateway logs: ${openclawDefaults.gatewayPattern}`,
|
|
6069
|
+
`- OpenClaw session transcripts: ${openclawDefaults.sessionsPattern}`,
|
|
6070
|
+
`- Hermes gateway logs: ${hermesDefaults.gatewayPattern}`,
|
|
6071
|
+
`- Hermes session transcripts: ${hermesDefaults.sessionsPattern}`,
|
|
6072
|
+
"",
|
|
6073
|
+
"Next steps:",
|
|
6074
|
+
`- Local OpenClaw paths: ${formatCommand("audit --runtime openclaw --log-file /path/to/openclaw.log")} or ${formatCommand("audit --runtime openclaw --sessions-dir /path/to/sessions")}`,
|
|
6075
|
+
`- Local Hermes paths: ${formatCommand("audit --runtime hermes --log-file ~/.hermes/logs/agent.log")} or ${formatCommand("audit --runtime hermes --sessions-dir ~/.hermes/sessions")}`,
|
|
6076
|
+
`- Remote OpenClaw only: ${formatCommand("audit --remote user@host")}`,
|
|
6077
|
+
`- Railway OpenClaw only: ${formatCommand("audit --railway")}`
|
|
6078
|
+
].join("\n")}
|
|
6079
|
+
`
|
|
6080
|
+
);
|
|
5588
6081
|
}
|
|
5589
6082
|
|
|
5590
6083
|
// src/commands/logout.ts
|
|
@@ -5598,103 +6091,51 @@ function runLogoutCommand() {
|
|
|
5598
6091
|
}
|
|
5599
6092
|
}
|
|
5600
6093
|
|
|
5601
|
-
// src/commands/push.ts
|
|
5602
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
5603
|
-
async function runPushCommand(options) {
|
|
5604
|
-
const payload = options.file ? loadPayloadFromFile(options.file) : loadPayloadFromCache();
|
|
5605
|
-
if (options.dryRun) {
|
|
5606
|
-
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
5607
|
-
`);
|
|
5608
|
-
return;
|
|
5609
|
-
}
|
|
5610
|
-
const config = loadPushConfig();
|
|
5611
|
-
const auditId = payload.summary.auditId;
|
|
5612
|
-
process.stderr.write(`Pushing audit ${auditId} to ${config.apiUrl}...
|
|
5613
|
-
`);
|
|
5614
|
-
const result = await pushAudit(payload, config);
|
|
5615
|
-
if (result.ok) {
|
|
5616
|
-
process.stderr.write(`Pushed successfully (audit: ${result.auditId}).
|
|
5617
|
-
`);
|
|
5618
|
-
} else {
|
|
5619
|
-
const statusInfo = result.status > 0 ? ` (HTTP ${result.status})` : "";
|
|
5620
|
-
throw new Error(`Push failed${statusInfo}: ${result.message}`);
|
|
5621
|
-
}
|
|
5622
|
-
}
|
|
5623
|
-
function loadPayloadFromFile(filePath) {
|
|
5624
|
-
let raw;
|
|
5625
|
-
try {
|
|
5626
|
-
raw = readFileSync8(filePath, "utf8");
|
|
5627
|
-
} catch {
|
|
5628
|
-
throw new Error(`Cannot read file: ${filePath}`);
|
|
5629
|
-
}
|
|
5630
|
-
let parsed;
|
|
5631
|
-
try {
|
|
5632
|
-
parsed = JSON.parse(raw);
|
|
5633
|
-
} catch {
|
|
5634
|
-
throw new Error(`File is not valid JSON: ${filePath}`);
|
|
5635
|
-
}
|
|
5636
|
-
const payload = parsed;
|
|
5637
|
-
if (!payload.version || !payload.summary || !payload.meta) {
|
|
5638
|
-
throw new Error(
|
|
5639
|
-
`File does not look like an AuditPushPayload (missing version, summary, or meta): ${filePath}`
|
|
5640
|
-
);
|
|
5641
|
-
}
|
|
5642
|
-
return payload;
|
|
5643
|
-
}
|
|
5644
|
-
function loadPayloadFromCache() {
|
|
5645
|
-
const dbPath = getDefaultDbPath();
|
|
5646
|
-
let summaries;
|
|
5647
|
-
try {
|
|
5648
|
-
summaries = listStoredAuditSummaries(dbPath);
|
|
5649
|
-
} catch {
|
|
5650
|
-
throw new NoDataError(
|
|
5651
|
-
`No local audit database found. Run \`${formatCommand("audit")}\` first, or use \`${formatCommand("push --file <path>")}\`.`
|
|
5652
|
-
);
|
|
5653
|
-
}
|
|
5654
|
-
if (summaries.length === 0) {
|
|
5655
|
-
throw new NoDataError(
|
|
5656
|
-
`No cached audit snapshots found. Run \`${formatCommand("audit")}\` first, or use \`${formatCommand("push --file <path>")}\`.`
|
|
5657
|
-
);
|
|
5658
|
-
}
|
|
5659
|
-
const latest = summaries[0];
|
|
5660
|
-
const meta = buildMeta2(latest);
|
|
5661
|
-
process.stderr.write(
|
|
5662
|
-
`Using most recent cached audit: ${latest.auditId} (${latest.generatedAt})
|
|
5663
|
-
`
|
|
5664
|
-
);
|
|
5665
|
-
return toWirePayload(latest, meta);
|
|
5666
|
-
}
|
|
5667
|
-
function buildMeta2(summary) {
|
|
5668
|
-
const sourceMeta = buildCachedPushSourceMeta(summary);
|
|
5669
|
-
return {
|
|
5670
|
-
cliVersion: getCliVersion(),
|
|
5671
|
-
sourceId: sourceMeta.sourceId,
|
|
5672
|
-
sourceHost: sourceMeta.sourceHost,
|
|
5673
|
-
environment: sourceMeta.environment
|
|
5674
|
-
};
|
|
5675
|
-
}
|
|
5676
|
-
|
|
5677
6094
|
// src/help.ts
|
|
5678
6095
|
function renderRootHelp(version, display) {
|
|
5679
6096
|
return `${display.name} ${version}
|
|
5680
6097
|
|
|
5681
|
-
Waste intelligence for OpenClaw and Hermes workflows
|
|
6098
|
+
Waste intelligence for OpenClaw and Hermes workflows.
|
|
5682
6099
|
|
|
5683
6100
|
Usage:
|
|
5684
6101
|
${formatCommand("<command> [options]", display.prefix)}
|
|
5685
6102
|
|
|
5686
|
-
|
|
6103
|
+
Getting started:
|
|
6104
|
+
init Detect local runtimes, run a first audit, and offer optional cloud follow-up.
|
|
6105
|
+
|
|
6106
|
+
Audit and inspect:
|
|
5687
6107
|
audit Analyze OpenClaw or Hermes logs, or a local Cursor usage CSV.
|
|
5688
6108
|
doctor Inspect OpenClaw or Hermes sources, or a local Cursor usage CSV.
|
|
6109
|
+
|
|
6110
|
+
Cloud:
|
|
6111
|
+
connect Authenticate and optionally push your latest audit to Xerg Cloud.
|
|
5689
6112
|
push Push a cached audit snapshot to the Xerg API.
|
|
5690
6113
|
login Authenticate with the Xerg API via browser.
|
|
5691
6114
|
logout Remove stored Xerg API credentials.
|
|
6115
|
+
mcp-setup Generate hosted MCP client configuration.
|
|
5692
6116
|
|
|
5693
6117
|
Global options:
|
|
5694
6118
|
-h, --help Show help
|
|
5695
6119
|
-v, --version Show version
|
|
5696
6120
|
`;
|
|
5697
6121
|
}
|
|
6122
|
+
function renderInitHelp(commandPrefix) {
|
|
6123
|
+
return `${formatCommand("init", commandPrefix)}
|
|
6124
|
+
|
|
6125
|
+
Detect local OpenClaw or Hermes runtimes, run a first audit, and offer optional cloud follow-up.
|
|
6126
|
+
|
|
6127
|
+
Usage:
|
|
6128
|
+
${formatCommand("init", commandPrefix)}
|
|
6129
|
+
|
|
6130
|
+
Notes:
|
|
6131
|
+
- Interactive only in v1
|
|
6132
|
+
- Uses local runtime auto-detection
|
|
6133
|
+
- Runs a first local audit with snapshot persistence enabled
|
|
6134
|
+
- Offers optional Xerg Cloud connect and hosted MCP setup after a successful audit
|
|
6135
|
+
|
|
6136
|
+
-h, --help Show help
|
|
6137
|
+
`;
|
|
6138
|
+
}
|
|
5698
6139
|
function renderAuditHelp(commandPrefix) {
|
|
5699
6140
|
return `${formatCommand("audit", commandPrefix)}
|
|
5700
6141
|
|
|
@@ -5761,7 +6202,7 @@ Options:
|
|
|
5761
6202
|
|
|
5762
6203
|
Authentication:
|
|
5763
6204
|
Set XERG_API_KEY in your environment, add "apiKey" to ~/.xerg/config.json,
|
|
5764
|
-
or run \`${formatCommand("login", commandPrefix)}\` to authenticate via browser.
|
|
6205
|
+
or run \`${formatCommand("connect", commandPrefix)}\` / \`${formatCommand("login", commandPrefix)}\` to authenticate via browser.
|
|
5765
6206
|
Browser login stores a token at ~/.config/xerg/credentials.json by default.
|
|
5766
6207
|
`;
|
|
5767
6208
|
}
|
|
@@ -5798,6 +6239,40 @@ Railway options (OpenClaw only):
|
|
|
5798
6239
|
-h, --help Show help
|
|
5799
6240
|
`;
|
|
5800
6241
|
}
|
|
6242
|
+
function renderConnectHelp(commandPrefix) {
|
|
6243
|
+
return `${formatCommand("connect", commandPrefix)}
|
|
6244
|
+
|
|
6245
|
+
Authenticate with Xerg Cloud and optionally push the latest audit.
|
|
6246
|
+
|
|
6247
|
+
Usage:
|
|
6248
|
+
${formatCommand("connect", commandPrefix)}
|
|
6249
|
+
|
|
6250
|
+
Notes:
|
|
6251
|
+
- Shows paid-workspace disclosure before hosted setup
|
|
6252
|
+
- Reuses existing auth from XERG_API_KEY, ~/.xerg/config.json, or stored browser login
|
|
6253
|
+
- Standalone non-interactive mode reports auth status and skips the push prompt
|
|
6254
|
+
- When called after ${formatCommand("init", commandPrefix)}, it can push the in-memory audit directly
|
|
6255
|
+
|
|
6256
|
+
-h, --help Show help
|
|
6257
|
+
`;
|
|
6258
|
+
}
|
|
6259
|
+
function renderMcpSetupHelp(commandPrefix) {
|
|
6260
|
+
return `${formatCommand("mcp-setup", commandPrefix)}
|
|
6261
|
+
|
|
6262
|
+
Generate hosted MCP client configuration for Cursor, Claude Code, or another MCP client.
|
|
6263
|
+
|
|
6264
|
+
Usage:
|
|
6265
|
+
${formatCommand("mcp-setup", commandPrefix)}
|
|
6266
|
+
|
|
6267
|
+
Notes:
|
|
6268
|
+
- Interactive in v1 because client selection is prompt-driven
|
|
6269
|
+
- Uses the hosted MCP endpoint at https://mcp.xerg.ai/mcp
|
|
6270
|
+
- Can write a project-scoped Cursor config when .cursor/ already exists
|
|
6271
|
+
- Local audits and compare stay available even if you skip hosted MCP setup
|
|
6272
|
+
|
|
6273
|
+
-h, --help Show help
|
|
6274
|
+
`;
|
|
6275
|
+
}
|
|
5801
6276
|
|
|
5802
6277
|
// src/index.ts
|
|
5803
6278
|
var VERSION = getCliVersion();
|
|
@@ -5831,6 +6306,11 @@ async function run() {
|
|
|
5831
6306
|
});
|
|
5832
6307
|
return;
|
|
5833
6308
|
}
|
|
6309
|
+
if (command === "init") {
|
|
6310
|
+
parseBareCommandOptions(argv.slice(1), renderInitHelp(commandDisplay.prefix), "init");
|
|
6311
|
+
await runInitCommand();
|
|
6312
|
+
return;
|
|
6313
|
+
}
|
|
5834
6314
|
if (command === "doctor") {
|
|
5835
6315
|
const options = parseDoctorOptions(argv.slice(1));
|
|
5836
6316
|
await runDoctorCommand({
|
|
@@ -5844,6 +6324,11 @@ async function run() {
|
|
|
5844
6324
|
await runPushCommand(options);
|
|
5845
6325
|
return;
|
|
5846
6326
|
}
|
|
6327
|
+
if (command === "connect") {
|
|
6328
|
+
parseBareCommandOptions(argv.slice(1), renderConnectHelp(commandDisplay.prefix), "connect");
|
|
6329
|
+
await runConnectCommand();
|
|
6330
|
+
return;
|
|
6331
|
+
}
|
|
5847
6332
|
if (command === "login") {
|
|
5848
6333
|
await runLoginCommand();
|
|
5849
6334
|
return;
|
|
@@ -5852,10 +6337,31 @@ async function run() {
|
|
|
5852
6337
|
runLogoutCommand();
|
|
5853
6338
|
return;
|
|
5854
6339
|
}
|
|
6340
|
+
if (command === "mcp-setup") {
|
|
6341
|
+
parseBareCommandOptions(argv.slice(1), renderMcpSetupHelp(commandDisplay.prefix), "mcp-setup");
|
|
6342
|
+
await runMcpSetupCommand();
|
|
6343
|
+
return;
|
|
6344
|
+
}
|
|
5855
6345
|
throw new Error(
|
|
5856
6346
|
`Unknown command "${command}". Run \`${formatCommand("--help", commandDisplay.prefix)}\` to see available commands.`
|
|
5857
6347
|
);
|
|
5858
6348
|
}
|
|
6349
|
+
function parseBareCommandOptions(raw, helpText, commandName) {
|
|
6350
|
+
const argv2 = expandEqualsArgs(raw);
|
|
6351
|
+
for (const arg of argv2) {
|
|
6352
|
+
switch (arg) {
|
|
6353
|
+
case "--help":
|
|
6354
|
+
case "-h":
|
|
6355
|
+
process.stdout.write(helpText);
|
|
6356
|
+
process.exit(0);
|
|
6357
|
+
break;
|
|
6358
|
+
default:
|
|
6359
|
+
throw new Error(
|
|
6360
|
+
`Unknown ${commandName} option "${arg}". Run \`${formatCommand([commandName, "--help"], commandDisplay.prefix)}\` for usage.`
|
|
6361
|
+
);
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
5859
6365
|
function parseAuditOptions(raw) {
|
|
5860
6366
|
const argv2 = expandEqualsArgs(raw);
|
|
5861
6367
|
const options = {};
|