viveworker 0.5.5 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -27
- package/package.json +7 -2
- package/scripts/a2a-cli.mjs +11 -6
- package/scripts/moltbook-api.mjs +319 -72
- package/scripts/share-cli.mjs +576 -0
- package/scripts/viveworker-bridge.mjs +61 -14
- package/scripts/viveworker.mjs +771 -130
- package/web/i18n.js +164 -6
package/scripts/viveworker.mjs
CHANGED
|
@@ -81,6 +81,12 @@ async function main(cliOptions) {
|
|
|
81
81
|
case "setup":
|
|
82
82
|
await runSetup(cliOptions);
|
|
83
83
|
return;
|
|
84
|
+
case "pair":
|
|
85
|
+
await runPair(cliOptions);
|
|
86
|
+
return;
|
|
87
|
+
case "enable":
|
|
88
|
+
await runEnable(cliOptions);
|
|
89
|
+
return;
|
|
84
90
|
case "start":
|
|
85
91
|
await runStart(cliOptions);
|
|
86
92
|
return;
|
|
@@ -219,6 +225,14 @@ async function runSetup(cliOptions) {
|
|
|
219
225
|
|
|
220
226
|
await fs.writeFile(envFile, `${envLines.join("\n")}\n`, "utf8");
|
|
221
227
|
|
|
228
|
+
progress.update("cli.setup.progress.providers");
|
|
229
|
+
const providerSetup = await autoConfigureProvidersDuringSetup({
|
|
230
|
+
cliOptions,
|
|
231
|
+
envFile,
|
|
232
|
+
sessionSecret,
|
|
233
|
+
codexHome: existing.CODEX_HOME || process.env.CODEX_HOME || path.join(os.homedir(), ".codex"),
|
|
234
|
+
});
|
|
235
|
+
|
|
222
236
|
if (!cliOptions.noLaunchd) {
|
|
223
237
|
progress.update("cli.setup.progress.launchd");
|
|
224
238
|
const plist = buildLaunchAgentPlist({
|
|
@@ -258,8 +272,9 @@ async function runSetup(cliOptions) {
|
|
|
258
272
|
let caDownloadLocalUrl = `${publicBaseUrl}${caPath}`;
|
|
259
273
|
let caDownloadIpUrl = `${fallbackBaseUrl}${caPath}`;
|
|
260
274
|
let temporaryCaServer = null;
|
|
275
|
+
const shouldPromptCaTrust = Boolean(webPushEnabled && canShowCaDownload && !cliOptions.pair && tlsAssets?.ranMkcertInstall);
|
|
261
276
|
|
|
262
|
-
if (
|
|
277
|
+
if (shouldPromptCaTrust) {
|
|
263
278
|
temporaryCaServer = await startTemporaryCaDownloadServer({
|
|
264
279
|
rootCaFile: mkcertRootCaFile,
|
|
265
280
|
preferredPort: port + 1,
|
|
@@ -289,77 +304,184 @@ async function runSetup(cliOptions) {
|
|
|
289
304
|
}
|
|
290
305
|
}
|
|
291
306
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
307
|
+
printCliSection(locale, "cli.section.ready", [
|
|
308
|
+
t(locale, "cli.setup.primaryUrl", { url: publicBaseUrl }),
|
|
309
|
+
t(locale, "cli.setup.fallbackUrl", { url: fallbackBaseUrl }),
|
|
310
|
+
t(locale, "cli.setup.pairingCode", { code: pairCode }),
|
|
311
|
+
t(locale, webPushEnabled ? "cli.setup.webPushEnabled" : "cli.setup.webPushDisabled"),
|
|
312
|
+
...getSetupProviderSummaryLines(locale, providerSetup),
|
|
313
|
+
]);
|
|
314
|
+
printCliSection(locale, "cli.section.needsAttention", [
|
|
315
|
+
allowInsecureHttpLan ? t(locale, "cli.setup.warning.insecureHttpLan") : "",
|
|
316
|
+
healthy && !pairingReady ? t(locale, "cli.setup.warning.stalePairingServer", { port }) : "",
|
|
317
|
+
]);
|
|
318
|
+
printCliSection(locale, "cli.section.next", [
|
|
319
|
+
cliOptions.pair ? t(locale, "cli.setup.pairRefresh.copy") : "",
|
|
320
|
+
cliOptions.pair ? t(locale, "cli.setup.pairRefresh.reminder") : "",
|
|
321
|
+
webPushEnabled
|
|
322
|
+
? t(locale, shouldPromptCaTrust ? "cli.setup.instructions.afterCa" : "cli.setup.instructions.https")
|
|
323
|
+
: allowInsecureHttpLan
|
|
324
|
+
? t(locale, "cli.setup.instructions.insecureHttpLan")
|
|
325
|
+
: t(locale, "cli.setup.instructions.localOnlyHttp"),
|
|
326
|
+
]);
|
|
327
|
+
printCliSection(locale, "cli.section.pairingLinks", [
|
|
328
|
+
t(locale, "cli.setup.pairingUrlLocal", { url: `${publicBaseUrl}${pairPath}` }),
|
|
329
|
+
t(locale, "cli.setup.pairingUrlIp", { url: `${fallbackBaseUrl}${pairPath}` }),
|
|
330
|
+
]);
|
|
331
|
+
if (canShowCaDownload && !shouldPromptCaTrust && !cliOptions.pair) {
|
|
332
|
+
printCliSection(locale, "cli.section.caDownload", [
|
|
333
|
+
t(locale, "cli.setup.caDownloadLocal", { url: caDownloadLocalUrl }),
|
|
334
|
+
t(locale, "cli.setup.caDownloadIp", { url: caDownloadIpUrl }),
|
|
335
|
+
]);
|
|
311
336
|
}
|
|
312
337
|
console.log("");
|
|
313
|
-
|
|
314
|
-
console.log(t(locale, cliOptions.installMkcert ? "cli.setup.instructions.afterCa" : "cli.setup.instructions.https"));
|
|
315
|
-
} else if (allowInsecureHttpLan) {
|
|
316
|
-
console.log(t(locale, "cli.setup.instructions.insecureHttpLan"));
|
|
317
|
-
} else {
|
|
318
|
-
console.log(t(locale, "cli.setup.instructions.localOnlyHttp"));
|
|
319
|
-
}
|
|
320
|
-
console.log("");
|
|
321
|
-
console.log(t(locale, "cli.setup.qrPairing"));
|
|
338
|
+
console.log(t(locale, "cli.section.qrPairing"));
|
|
322
339
|
await printQrCode(`${publicBaseUrl}${pairPath}`);
|
|
323
|
-
if (canShowCaDownload && !
|
|
340
|
+
if (canShowCaDownload && !shouldPromptCaTrust && !cliOptions.pair) {
|
|
324
341
|
console.log("");
|
|
325
|
-
console.log(t(locale, "cli.
|
|
342
|
+
console.log(t(locale, "cli.section.qrCaDownload"));
|
|
326
343
|
await printQrCode(caDownloadIpUrl);
|
|
327
344
|
}
|
|
328
345
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
346
|
+
await maybeRunLegacySetupFeatureAliases(cliOptions, { envFile, locale, sessionSecret, port });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function runPair(cliOptions) {
|
|
350
|
+
const setup = await loadExistingSetup(cliOptions);
|
|
351
|
+
const progress = createCliProgressReporter(setup.locale);
|
|
352
|
+
progress.update("cli.start.progress.refreshPairing");
|
|
353
|
+
await refreshPairingCredentials(setup.envFile, setup.config, { force: true });
|
|
354
|
+
progress.clear();
|
|
355
|
+
await runStart(cliOptions);
|
|
356
|
+
const nextConfig = await ensureDefaultLocalePersisted(setup.envFile, cliOptions);
|
|
357
|
+
printCliTitle(setup.locale, "cli.pair.done");
|
|
358
|
+
printCliSection(setup.locale, "cli.section.ready", [
|
|
359
|
+
t(setup.locale, "cli.setup.primaryUrl", { url: String(nextConfig.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "").trim() || "(not configured)" }),
|
|
360
|
+
t(setup.locale, "cli.setup.pairingCode", { code: String(nextConfig.PAIRING_CODE || "").trim() || "(missing)" }),
|
|
361
|
+
]);
|
|
362
|
+
printCliSection(setup.locale, "cli.section.next", [
|
|
363
|
+
t(setup.locale, "cli.setup.pairRefresh.copy"),
|
|
364
|
+
t(setup.locale, "cli.setup.pairRefresh.reminder"),
|
|
365
|
+
]);
|
|
366
|
+
await printPairingInfo(setup.locale, nextConfig, { sectioned: true });
|
|
367
|
+
const pairToken = String(nextConfig.PAIRING_TOKEN || "").trim();
|
|
368
|
+
if (pairToken && nextConfig.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL) {
|
|
340
369
|
console.log("");
|
|
341
|
-
console.log(t(locale, "cli.
|
|
370
|
+
console.log(t(setup.locale, "cli.section.qrPairing"));
|
|
371
|
+
await printQrCode(
|
|
372
|
+
`${String(nextConfig.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "").trim()}/app?pairToken=${encodeURIComponent(pairToken)}`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function runEnable(cliOptions) {
|
|
378
|
+
const target = String(cliOptions.enableTarget || "").trim().toLowerCase();
|
|
379
|
+
if (!target) {
|
|
380
|
+
throw new Error("Usage: viveworker enable <claude|a2a|moltbook|scout> [...]");
|
|
342
381
|
}
|
|
343
382
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
await
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
383
|
+
switch (target) {
|
|
384
|
+
case "claude":
|
|
385
|
+
await runEnableClaude(cliOptions);
|
|
386
|
+
return;
|
|
387
|
+
case "a2a": {
|
|
388
|
+
const { runA2ACli } = await import("./a2a-cli.mjs");
|
|
389
|
+
await runA2ACli(["setup", ...(cliOptions.enableArgs || [])]);
|
|
390
|
+
return;
|
|
350
391
|
}
|
|
392
|
+
case "moltbook":
|
|
393
|
+
await runEnableMoltbook(cliOptions);
|
|
394
|
+
return;
|
|
395
|
+
case "scout":
|
|
396
|
+
await runEnableScout(cliOptions);
|
|
397
|
+
return;
|
|
398
|
+
default:
|
|
399
|
+
throw new Error(`Unknown feature: ${target}`);
|
|
351
400
|
}
|
|
401
|
+
}
|
|
352
402
|
|
|
403
|
+
async function runEnableClaude(cliOptions) {
|
|
404
|
+
const setup = await loadExistingSetup(cliOptions);
|
|
405
|
+
const settingsFile = resolvePath(
|
|
406
|
+
cliOptions.claudeSettingsFile || path.join(os.homedir(), ".claude", "settings.json")
|
|
407
|
+
);
|
|
408
|
+
if (!cliOptions.claudeSettingsFile && !(await fileExists(path.dirname(settingsFile)))) {
|
|
409
|
+
throw new Error("Claude Desktop settings were not found. Install Claude Desktop first or pass --settings-file.");
|
|
410
|
+
}
|
|
411
|
+
if (!setup.config.SESSION_SECRET) {
|
|
412
|
+
throw new Error("SESSION_SECRET is missing. Run `npx viveworker doctor --fix` or `npx viveworker setup` first.");
|
|
413
|
+
}
|
|
414
|
+
const settingsPath = await installClaudeHooks({
|
|
415
|
+
envFile: setup.envFile,
|
|
416
|
+
claudeSettingsFile: cliOptions.claudeSettingsFile,
|
|
417
|
+
sessionSecret: setup.config.SESSION_SECRET,
|
|
418
|
+
suppressOutput: true,
|
|
419
|
+
});
|
|
420
|
+
printCliTitle(setup.locale, "cli.enable.claude.title");
|
|
421
|
+
printCliSection(setup.locale, "cli.section.changed", [
|
|
422
|
+
t(setup.locale, "cli.enable.claude.changed", { path: settingsPath }),
|
|
423
|
+
]);
|
|
424
|
+
printCliSection(setup.locale, "cli.section.next", [
|
|
425
|
+
t(setup.locale, "cli.enable.claude.next"),
|
|
426
|
+
]);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function runEnableMoltbook(cliOptions) {
|
|
430
|
+
const setup = await loadExistingSetup(cliOptions);
|
|
431
|
+
if (!setup.config.SESSION_SECRET) {
|
|
432
|
+
throw new Error("SESSION_SECRET is missing. Run `npx viveworker doctor --fix` or `npx viveworker setup` first.");
|
|
433
|
+
}
|
|
434
|
+
const watcherResult = await installMoltbookWatcher({
|
|
435
|
+
cliOptions,
|
|
436
|
+
sessionSecret: setup.config.SESSION_SECRET,
|
|
437
|
+
port: Number(setup.config.NATIVE_APPROVAL_SERVER_PORT) || defaultServerPort,
|
|
438
|
+
suppressOutput: true,
|
|
439
|
+
});
|
|
440
|
+
let scoutResult = null;
|
|
441
|
+
if (!cliOptions.noScout) {
|
|
442
|
+
scoutResult = await installMoltbookScout({ cliOptions, suppressOutput: true });
|
|
443
|
+
}
|
|
444
|
+
printCliTitle(setup.locale, "cli.enable.moltbook.title");
|
|
445
|
+
printCliSection(setup.locale, "cli.section.changed", [
|
|
446
|
+
t(setup.locale, "cli.enable.moltbook.watcher", { path: watcherResult.plistPath }),
|
|
447
|
+
t(setup.locale, "cli.enable.moltbook.env", { path: watcherResult.moltbookEnvFile }),
|
|
448
|
+
scoutResult ? t(setup.locale, "cli.enable.moltbook.scout", { interval: scoutResult.interval }) : t(setup.locale, "cli.enable.moltbook.watcherOnly"),
|
|
449
|
+
]);
|
|
450
|
+
printCliSection(setup.locale, "cli.section.needsAttention", [
|
|
451
|
+
watcherResult.warning || "",
|
|
452
|
+
scoutResult?.warning || "",
|
|
453
|
+
scoutResult?.manualNote || "",
|
|
454
|
+
scoutResult && !scoutResult.hasPersona ? t(setup.locale, "cli.enable.moltbook.personaTip") : "",
|
|
455
|
+
]);
|
|
456
|
+
printCliSection(setup.locale, "cli.section.next", [
|
|
457
|
+
t(setup.locale, "cli.enable.moltbook.next"),
|
|
458
|
+
]);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function runEnableScout(cliOptions) {
|
|
462
|
+
const setup = await loadExistingSetup(cliOptions);
|
|
353
463
|
if (cliOptions.autoScoutUninstall) {
|
|
354
|
-
await uninstallMoltbookScout();
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
} catch (error) {
|
|
359
|
-
console.log("");
|
|
360
|
-
console.log(`Moltbook auto-scout install failed: ${error.message}`);
|
|
361
|
-
}
|
|
464
|
+
const uninstallResult = await uninstallMoltbookScout({ suppressOutput: true });
|
|
465
|
+
printCliTitle(setup.locale, "cli.enable.scout.removed");
|
|
466
|
+
printCliSection(setup.locale, "cli.section.changed", [uninstallResult.message]);
|
|
467
|
+
return;
|
|
362
468
|
}
|
|
469
|
+
const scoutResult = await installMoltbookScout({ cliOptions, suppressOutput: true });
|
|
470
|
+
printCliTitle(setup.locale, "cli.enable.scout.title");
|
|
471
|
+
printCliSection(setup.locale, "cli.section.changed", [
|
|
472
|
+
t(setup.locale, "cli.enable.scout.installed", { interval: scoutResult.interval }),
|
|
473
|
+
scoutResult.harness.kind === "manual"
|
|
474
|
+
? t(setup.locale, "cli.enable.scout.manualHarness")
|
|
475
|
+
: t(setup.locale, "cli.enable.scout.harness", { path: scoutResult.harness.bin }),
|
|
476
|
+
]);
|
|
477
|
+
printCliSection(setup.locale, "cli.section.needsAttention", [
|
|
478
|
+
scoutResult.warning || "",
|
|
479
|
+
scoutResult.manualNote || "",
|
|
480
|
+
!scoutResult.hasPersona ? t(setup.locale, "cli.enable.moltbook.personaTip") : "",
|
|
481
|
+
]);
|
|
482
|
+
printCliSection(setup.locale, "cli.section.next", [
|
|
483
|
+
t(setup.locale, "cli.enable.scout.next"),
|
|
484
|
+
]);
|
|
363
485
|
}
|
|
364
486
|
|
|
365
487
|
async function detectScoutHarness(preferred) {
|
|
@@ -382,7 +504,7 @@ async function detectScoutHarness(preferred) {
|
|
|
382
504
|
return { kind: "manual", bin: "" };
|
|
383
505
|
}
|
|
384
506
|
|
|
385
|
-
async function uninstallMoltbookScout() {
|
|
507
|
+
async function uninstallMoltbookScout({ suppressOutput = false } = {}) {
|
|
386
508
|
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${"com.viveworker.moltbook-scout"}.plist`);
|
|
387
509
|
const { spawn } = await import("node:child_process");
|
|
388
510
|
await new Promise((resolve) => {
|
|
@@ -392,15 +514,23 @@ async function uninstallMoltbookScout() {
|
|
|
392
514
|
});
|
|
393
515
|
try {
|
|
394
516
|
await fs.unlink(plistPath);
|
|
395
|
-
|
|
396
|
-
|
|
517
|
+
const message = `Moltbook auto-scout removed: ${plistPath}`;
|
|
518
|
+
if (!suppressOutput) {
|
|
519
|
+
console.log("");
|
|
520
|
+
console.log(message);
|
|
521
|
+
}
|
|
522
|
+
return { removed: true, plistPath, message };
|
|
397
523
|
} catch {
|
|
398
|
-
|
|
399
|
-
|
|
524
|
+
const message = `No auto-scout plist was installed at ${plistPath}.`;
|
|
525
|
+
if (!suppressOutput) {
|
|
526
|
+
console.log("");
|
|
527
|
+
console.log(message);
|
|
528
|
+
}
|
|
529
|
+
return { removed: false, plistPath, message };
|
|
400
530
|
}
|
|
401
531
|
}
|
|
402
532
|
|
|
403
|
-
async function installMoltbookScout({ cliOptions }) {
|
|
533
|
+
async function installMoltbookScout({ cliOptions, suppressOutput = false }) {
|
|
404
534
|
const moltbookEnvFile = path.join(os.homedir(), ".viveworker", "moltbook.env");
|
|
405
535
|
try {
|
|
406
536
|
await fs.access(moltbookEnvFile);
|
|
@@ -481,6 +611,7 @@ async function installMoltbookScout({ cliOptions }) {
|
|
|
481
611
|
p.on("exit", () => resolve());
|
|
482
612
|
p.on("error", () => resolve());
|
|
483
613
|
});
|
|
614
|
+
let warning = "";
|
|
484
615
|
try {
|
|
485
616
|
await new Promise((resolve, reject) => {
|
|
486
617
|
const p = spawn("launchctl", ["bootstrap", `gui/${process.getuid()}`, plistPath], { stdio: "ignore" });
|
|
@@ -488,33 +619,45 @@ async function installMoltbookScout({ cliOptions }) {
|
|
|
488
619
|
p.on("error", reject);
|
|
489
620
|
});
|
|
490
621
|
} catch (error) {
|
|
491
|
-
|
|
492
|
-
console.log(`Moltbook scout plist written but launchctl bootstrap failed: ${error.message}`);
|
|
493
|
-
console.log(`Run manually: launchctl bootstrap gui/$(id -u) ${plistPath}`);
|
|
622
|
+
warning = `Moltbook scout plist was written, but launchctl bootstrap failed: ${error.message}. Run manually: launchctl bootstrap gui/$(id -u) ${plistPath}`;
|
|
494
623
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (!
|
|
509
|
-
console.log(
|
|
510
|
-
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
|
|
624
|
+
const manualNote = harness.kind === "manual"
|
|
625
|
+
? `Neither 'codex' nor 'claude' CLI was found. The scheduled task will only run scout and print candidates to the log. Install Codex CLI (npm i -g @openai/codex) or Claude Code CLI to enable automated drafting.`
|
|
626
|
+
: "";
|
|
627
|
+
const result = {
|
|
628
|
+
plistPath,
|
|
629
|
+
interval,
|
|
630
|
+
harness,
|
|
631
|
+
hasPersona,
|
|
632
|
+
personaPath,
|
|
633
|
+
warning,
|
|
634
|
+
manualNote,
|
|
635
|
+
logsPath: "/tmp/viveworker-moltbook-scout.{out,err}.log",
|
|
636
|
+
};
|
|
637
|
+
if (!suppressOutput) {
|
|
638
|
+
console.log("");
|
|
639
|
+
console.log(`Moltbook auto-scout installed (${harness.kind}).`);
|
|
640
|
+
console.log(` plist: ${plistPath}`);
|
|
641
|
+
console.log(` interval: ${interval}s`);
|
|
642
|
+
if (manualNote) {
|
|
643
|
+
console.log(` note: ${manualNote}`);
|
|
644
|
+
} else {
|
|
645
|
+
console.log(` harness: ${harness.bin}`);
|
|
646
|
+
}
|
|
647
|
+
console.log(` logs: ${result.logsPath}`);
|
|
648
|
+
if (!hasPersona) {
|
|
649
|
+
console.log(` tip: No persona file found. Run 'npx viveworker moltbook persona init' to describe your agent — draft quality improves a lot.`);
|
|
650
|
+
} else {
|
|
651
|
+
console.log(` persona: ${personaPath}`);
|
|
652
|
+
}
|
|
653
|
+
if (warning) {
|
|
654
|
+
console.log(` warning: ${warning}`);
|
|
655
|
+
}
|
|
514
656
|
}
|
|
657
|
+
return result;
|
|
515
658
|
}
|
|
516
659
|
|
|
517
|
-
async function installMoltbookWatcher({ cliOptions, sessionSecret, port }) {
|
|
660
|
+
async function installMoltbookWatcher({ cliOptions, sessionSecret, port, suppressOutput = false }) {
|
|
518
661
|
if (!cliOptions.moltbookApiKey || !cliOptions.moltbookAgentId) {
|
|
519
662
|
throw new Error(
|
|
520
663
|
"--moltbook requires --moltbook-api-key and --moltbook-agent-id"
|
|
@@ -564,6 +707,7 @@ async function installMoltbookWatcher({ cliOptions, sessionSecret, port }) {
|
|
|
564
707
|
await fs.writeFile(plistPath, plistBody, "utf8");
|
|
565
708
|
|
|
566
709
|
// Reload the agent if launchctl is available
|
|
710
|
+
let warning = "";
|
|
567
711
|
try {
|
|
568
712
|
const { spawn } = await import("node:child_process");
|
|
569
713
|
await new Promise((resolve) => {
|
|
@@ -577,20 +721,28 @@ async function installMoltbookWatcher({ cliOptions, sessionSecret, port }) {
|
|
|
577
721
|
load.on("error", reject);
|
|
578
722
|
});
|
|
579
723
|
} catch (error) {
|
|
724
|
+
warning = `Moltbook watcher plist was written, but launchctl load failed: ${error.message}. Run manually: launchctl load ${plistPath}`;
|
|
725
|
+
}
|
|
726
|
+
const result = {
|
|
727
|
+
moltbookEnvFile,
|
|
728
|
+
plistPath,
|
|
729
|
+
logsPath: "/tmp/viveworker-moltbook-watcher.{out,err}.log",
|
|
730
|
+
warning,
|
|
731
|
+
};
|
|
732
|
+
if (!suppressOutput) {
|
|
580
733
|
console.log("");
|
|
581
|
-
console.log(`Moltbook watcher
|
|
582
|
-
console.log(`
|
|
583
|
-
|
|
734
|
+
console.log(`Moltbook watcher installed.`);
|
|
735
|
+
console.log(` env: ${moltbookEnvFile}`);
|
|
736
|
+
console.log(` plist: ${plistPath}`);
|
|
737
|
+
console.log(` logs: ${result.logsPath}`);
|
|
738
|
+
if (warning) {
|
|
739
|
+
console.log(` warning: ${warning}`);
|
|
740
|
+
}
|
|
584
741
|
}
|
|
585
|
-
|
|
586
|
-
console.log("");
|
|
587
|
-
console.log(`Moltbook watcher installed.`);
|
|
588
|
-
console.log(` env: ${moltbookEnvFile}`);
|
|
589
|
-
console.log(` plist: ${plistPath}`);
|
|
590
|
-
console.log(` logs: /tmp/viveworker-moltbook-watcher.{out,err}.log`);
|
|
742
|
+
return result;
|
|
591
743
|
}
|
|
592
744
|
|
|
593
|
-
async function installClaudeHooks({ envFile, claudeSettingsFile, sessionSecret }) {
|
|
745
|
+
async function installClaudeHooks({ envFile, claudeSettingsFile, sessionSecret, suppressOutput = false }) {
|
|
594
746
|
const hookScript = path.join(packageRoot, "scripts", "viveworker-claude-hook.mjs");
|
|
595
747
|
const nodePath = process.execPath;
|
|
596
748
|
const settingsFile = resolvePath(
|
|
@@ -638,8 +790,12 @@ async function installClaudeHooks({ envFile, claudeSettingsFile, sessionSecret }
|
|
|
638
790
|
await fs.mkdir(path.dirname(settingsFile), { recursive: true });
|
|
639
791
|
await fs.writeFile(settingsFile, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
640
792
|
|
|
641
|
-
|
|
642
|
-
|
|
793
|
+
if (!suppressOutput) {
|
|
794
|
+
console.log("");
|
|
795
|
+
console.log(`Claude hooks installed: ${settingsFile}`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return settingsFile;
|
|
643
799
|
}
|
|
644
800
|
|
|
645
801
|
async function runStart(cliOptions) {
|
|
@@ -675,12 +831,22 @@ async function runStart(cliOptions) {
|
|
|
675
831
|
? await waitForExpectedPairing(config.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "", rotatedPairing.pairingToken)
|
|
676
832
|
: true;
|
|
677
833
|
progress.done(healthy && pairingReady ? "cli.start.launchdStarted" : "cli.start.launchdStartedPending");
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
834
|
+
printCliTitle(locale, "cli.start.title");
|
|
835
|
+
printCliSection(locale, "cli.section.ready", [
|
|
836
|
+
t(locale, "cli.status.baseUrl", { value: config.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "(not configured)" }),
|
|
837
|
+
t(locale, "cli.start.readyLaunchd"),
|
|
838
|
+
t(locale, "cli.status.health", { value: t(locale, healthy ? "cli.status.ok" : "cli.status.failed") }),
|
|
839
|
+
]);
|
|
840
|
+
printCliSection(locale, "cli.section.needsAttention", [
|
|
841
|
+
healthy && !pairingReady
|
|
842
|
+
? t(locale, "cli.setup.warning.stalePairingServer", { port: config.NATIVE_APPROVAL_SERVER_PORT || defaultServerPort })
|
|
843
|
+
: "",
|
|
844
|
+
]);
|
|
845
|
+
printCliSection(locale, "cli.section.next", [
|
|
846
|
+
rotatedPairing.rotated ? t(locale, "cli.start.nextPairing") : "",
|
|
847
|
+
]);
|
|
682
848
|
if (rotatedPairing.rotated) {
|
|
683
|
-
await printPairingInfo(locale, config);
|
|
849
|
+
await printPairingInfo(locale, config, { sectioned: true });
|
|
684
850
|
}
|
|
685
851
|
return;
|
|
686
852
|
}
|
|
@@ -697,12 +863,22 @@ async function runStart(cliOptions) {
|
|
|
697
863
|
? await waitForExpectedPairing(config.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "", rotatedPairing.pairingToken)
|
|
698
864
|
: true;
|
|
699
865
|
progress.done(healthy && pairingReady ? "cli.start.bridgeStarted" : "cli.start.bridgeStartedPending");
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
866
|
+
printCliTitle(locale, "cli.start.title");
|
|
867
|
+
printCliSection(locale, "cli.section.ready", [
|
|
868
|
+
t(locale, "cli.status.baseUrl", { value: config.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "(not configured)" }),
|
|
869
|
+
t(locale, "cli.start.readyBridge"),
|
|
870
|
+
t(locale, "cli.status.health", { value: t(locale, healthy ? "cli.status.ok" : "cli.status.failed") }),
|
|
871
|
+
]);
|
|
872
|
+
printCliSection(locale, "cli.section.needsAttention", [
|
|
873
|
+
healthy && !pairingReady
|
|
874
|
+
? t(locale, "cli.setup.warning.stalePairingServer", { port: config.NATIVE_APPROVAL_SERVER_PORT || defaultServerPort })
|
|
875
|
+
: "",
|
|
876
|
+
]);
|
|
877
|
+
printCliSection(locale, "cli.section.next", [
|
|
878
|
+
rotatedPairing.rotated ? t(locale, "cli.start.nextPairing") : "",
|
|
879
|
+
]);
|
|
704
880
|
if (rotatedPairing.rotated) {
|
|
705
|
-
await printPairingInfo(locale, config);
|
|
881
|
+
await printPairingInfo(locale, config, { sectioned: true });
|
|
706
882
|
}
|
|
707
883
|
}
|
|
708
884
|
|
|
@@ -710,15 +886,22 @@ async function runStop(cliOptions) {
|
|
|
710
886
|
const configDir = resolvePath(cliOptions.configDir || defaultConfigDir);
|
|
711
887
|
const launchAgentPath = resolvePath(cliOptions.launchAgentPath || defaultLaunchAgentPath);
|
|
712
888
|
const pidFile = resolvePath(cliOptions.pidFile || path.join(configDir, "viveworker.pid"));
|
|
889
|
+
const locale = await resolveCliLocale(cliOptions);
|
|
713
890
|
if (await fileExists(launchAgentPath)) {
|
|
714
891
|
await execCommand(["launchctl", "bootout", `gui/${process.getuid()}`, launchAgentPath], { ignoreError: true });
|
|
715
|
-
|
|
892
|
+
printCliTitle(locale, "cli.stop.title");
|
|
893
|
+
printCliSection(locale, "cli.section.changed", [
|
|
894
|
+
t(locale, "cli.stop.changedLaunchd"),
|
|
895
|
+
]);
|
|
716
896
|
return;
|
|
717
897
|
}
|
|
718
898
|
|
|
719
899
|
const pid = await maybeReadPid(pidFile);
|
|
720
900
|
if (!pid) {
|
|
721
|
-
|
|
901
|
+
printCliTitle(locale, "cli.stop.alreadyStopped");
|
|
902
|
+
printCliSection(locale, "cli.section.ready", [
|
|
903
|
+
t(locale, "cli.stop.noProcess"),
|
|
904
|
+
]);
|
|
722
905
|
return;
|
|
723
906
|
}
|
|
724
907
|
|
|
@@ -730,7 +913,10 @@ async function runStop(cliOptions) {
|
|
|
730
913
|
}
|
|
731
914
|
}
|
|
732
915
|
await fs.rm(pidFile, { force: true });
|
|
733
|
-
|
|
916
|
+
printCliTitle(locale, "cli.stop.title");
|
|
917
|
+
printCliSection(locale, "cli.section.changed", [
|
|
918
|
+
t(locale, "cli.stop.changedPid", { pid }),
|
|
919
|
+
]);
|
|
734
920
|
}
|
|
735
921
|
|
|
736
922
|
async function runStatus(cliOptions) {
|
|
@@ -745,15 +931,17 @@ async function runStatus(cliOptions) {
|
|
|
745
931
|
const httpsEnabled = isHttpsUrl(baseUrl);
|
|
746
932
|
const locale = await resolveCliLocale(cliOptions, config);
|
|
747
933
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
934
|
+
const readyLines = [
|
|
935
|
+
t(locale, "cli.status.envFile", { value: envFile }),
|
|
936
|
+
t(locale, "cli.status.baseUrl", { value: baseUrl || "(not configured)" }),
|
|
937
|
+
t(locale, "cli.status.webPush", { value: t(locale, webPushEnabled ? "cli.status.enabled" : "cli.status.disabled") }),
|
|
938
|
+
t(locale, "cli.status.https", { value: t(locale, httpsEnabled ? "cli.status.enabled" : "cli.status.disabled") }),
|
|
939
|
+
];
|
|
752
940
|
if (webPushEnabled) {
|
|
753
|
-
|
|
754
|
-
|
|
941
|
+
readyLines.push(t(locale, "cli.status.tlsCert", { value: config.TLS_CERT_FILE || "(missing)" }));
|
|
942
|
+
readyLines.push(t(locale, "cli.status.tlsKey", { value: config.TLS_KEY_FILE || "(missing)" }));
|
|
755
943
|
}
|
|
756
|
-
|
|
944
|
+
readyLines.push(
|
|
757
945
|
t(locale, "cli.status.launchAgent", {
|
|
758
946
|
value: (await fileExists(launchAgentPath)) ? launchAgentPath : "(not installed)",
|
|
759
947
|
})
|
|
@@ -764,23 +952,25 @@ async function runStatus(cliOptions) {
|
|
|
764
952
|
["launchctl", "print", `gui/${process.getuid()}/${defaultLabel}`],
|
|
765
953
|
{ ignoreError: true }
|
|
766
954
|
);
|
|
767
|
-
|
|
955
|
+
readyLines.push(
|
|
768
956
|
t(locale, "cli.status.launchd", {
|
|
769
957
|
value: t(locale, printed.ok ? "cli.status.installed" : "cli.status.notRunning"),
|
|
770
958
|
})
|
|
771
959
|
);
|
|
772
960
|
} else {
|
|
773
961
|
const pid = await maybeReadPid(pidFile);
|
|
774
|
-
|
|
962
|
+
readyLines.push(t(locale, "cli.status.pid", { value: pid || "(not running)" }));
|
|
775
963
|
}
|
|
776
964
|
|
|
965
|
+
let healthOutput = "";
|
|
777
966
|
if (healthUrl) {
|
|
778
967
|
const health = await execCommand(buildHealthCheckArgs(healthUrl), { ignoreError: true });
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
console.log(health.stdout.trim());
|
|
782
|
-
}
|
|
968
|
+
readyLines.push(t(locale, "cli.status.health", { value: t(locale, health.ok ? "cli.status.ok" : "cli.status.failed") }));
|
|
969
|
+
healthOutput = health.stdout.trim();
|
|
783
970
|
}
|
|
971
|
+
printCliTitle(locale, "cli.status.title");
|
|
972
|
+
printCliSection(locale, "cli.section.ready", readyLines);
|
|
973
|
+
printCliSection(locale, "cli.section.details", summarizeHealthOutput(healthOutput));
|
|
784
974
|
}
|
|
785
975
|
|
|
786
976
|
async function runDoctor(cliOptions) {
|
|
@@ -850,14 +1040,38 @@ async function runDoctor(cliOptions) {
|
|
|
850
1040
|
}
|
|
851
1041
|
|
|
852
1042
|
if (issues.length === 0) {
|
|
853
|
-
|
|
1043
|
+
printCliTitle(locale, "cli.doctor.ok");
|
|
1044
|
+
printCliSection(locale, "cli.section.ready", [
|
|
1045
|
+
t(locale, "cli.status.baseUrl", { value: baseUrl || "(not configured)" }),
|
|
1046
|
+
healthUrl ? t(locale, "cli.status.health", { value: t(locale, "cli.status.ok") }) : "",
|
|
1047
|
+
]);
|
|
854
1048
|
return;
|
|
855
1049
|
}
|
|
856
1050
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1051
|
+
printCliTitle(locale, "cli.doctor.titleIssues");
|
|
1052
|
+
printCliSection(locale, "cli.section.needsAttention", issues);
|
|
1053
|
+
|
|
1054
|
+
if (!cliOptions.doctorFix) {
|
|
1055
|
+
printCliSection(locale, "cli.section.next", [
|
|
1056
|
+
t(locale, "cli.doctor.nextFix"),
|
|
1057
|
+
]);
|
|
1058
|
+
return;
|
|
860
1059
|
}
|
|
1060
|
+
|
|
1061
|
+
if (!(await fileExists(envFile))) {
|
|
1062
|
+
throw new Error("No viveworker config was found. Run `npx viveworker setup` first.");
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
console.log("");
|
|
1066
|
+
console.log(t(locale, "cli.doctor.fixing"));
|
|
1067
|
+
const appliedChanges = await repairDoctorIssues(cliOptions, { envFile, config, locale, hostname, localHostname, chosenIp, webPushEnabled, allowInsecureHttpLan });
|
|
1068
|
+
printCliTitle(locale, "cli.doctor.titleFix");
|
|
1069
|
+
printCliSection(locale, "cli.section.changed", appliedChanges);
|
|
1070
|
+
printCliSection(locale, "cli.section.next", [
|
|
1071
|
+
t(locale, "cli.doctor.nextRestarting"),
|
|
1072
|
+
]);
|
|
1073
|
+
console.log(t(locale, "cli.doctor.fixed"));
|
|
1074
|
+
await runStart(cliOptions);
|
|
861
1075
|
}
|
|
862
1076
|
|
|
863
1077
|
async function runUpdate(cliOptions) {
|
|
@@ -893,40 +1107,248 @@ async function runUpdate(cliOptions) {
|
|
|
893
1107
|
|
|
894
1108
|
// 3. Restart bridge
|
|
895
1109
|
const launchAgentPath = resolvePath(cliOptions.launchAgentPath || defaultLaunchAgentPath);
|
|
1110
|
+
let restartedBridge = false;
|
|
896
1111
|
if (await fileExists(launchAgentPath)) {
|
|
897
1112
|
progress.update("cli.update.progress.restartBridge");
|
|
898
1113
|
await execCommand(["launchctl", "bootout", `gui/${process.getuid()}`, launchAgentPath], { ignoreError: true });
|
|
899
1114
|
await execCommand(["launchctl", "bootstrap", `gui/${process.getuid()}`, launchAgentPath], { ignoreError: true });
|
|
900
1115
|
await execCommand(["launchctl", "kickstart", "-k", `gui/${process.getuid()}/${defaultLabel}`], { ignoreError: true });
|
|
1116
|
+
restartedBridge = true;
|
|
901
1117
|
}
|
|
902
1118
|
|
|
903
1119
|
// 4. Restart moltbook-watcher if present
|
|
904
1120
|
const watcherLabel = "com.viveworker.moltbook-watcher";
|
|
905
1121
|
const watcherPlistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${watcherLabel}.plist`);
|
|
1122
|
+
let restartedWatcher = false;
|
|
906
1123
|
if (await fileExists(watcherPlistPath)) {
|
|
907
1124
|
progress.update("cli.update.progress.restartWatcher");
|
|
908
1125
|
await execCommand(["launchctl", "bootout", `gui/${process.getuid()}`, watcherPlistPath], { ignoreError: true });
|
|
909
1126
|
await execCommand(["launchctl", "bootstrap", `gui/${process.getuid()}`, watcherPlistPath], { ignoreError: true });
|
|
1127
|
+
restartedWatcher = true;
|
|
910
1128
|
}
|
|
911
1129
|
|
|
912
1130
|
// 5. Restart moltbook-scout if present
|
|
913
1131
|
const scoutLabel = "com.viveworker.moltbook-scout";
|
|
914
1132
|
const scoutPlistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${scoutLabel}.plist`);
|
|
1133
|
+
let restartedScout = false;
|
|
915
1134
|
if (await fileExists(scoutPlistPath)) {
|
|
916
1135
|
progress.update("cli.update.progress.restartScout");
|
|
917
1136
|
await execCommand(["launchctl", "bootout", `gui/${process.getuid()}`, scoutPlistPath], { ignoreError: true });
|
|
918
1137
|
await execCommand(["launchctl", "bootstrap", `gui/${process.getuid()}`, scoutPlistPath], { ignoreError: true });
|
|
1138
|
+
restartedScout = true;
|
|
919
1139
|
}
|
|
920
1140
|
|
|
921
1141
|
progress.done("cli.update.done", { version: latestVersion });
|
|
1142
|
+
printCliTitle(locale, "cli.update.title");
|
|
1143
|
+
printCliSection(locale, "cli.section.changed", [
|
|
1144
|
+
t(locale, "cli.update.changedVersion", { current: currentVersion, latest: latestVersion }),
|
|
1145
|
+
restartedBridge ? t(locale, "cli.update.changedBridge") : "",
|
|
1146
|
+
restartedWatcher ? t(locale, "cli.update.changedWatcher") : "",
|
|
1147
|
+
restartedScout ? t(locale, "cli.update.changedScout") : "",
|
|
1148
|
+
]);
|
|
1149
|
+
printCliSection(locale, "cli.section.next", [
|
|
1150
|
+
t(locale, "cli.update.next"),
|
|
1151
|
+
]);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
async function loadExistingSetup(cliOptions) {
|
|
1155
|
+
const configDir = resolvePath(cliOptions.configDir || defaultConfigDir);
|
|
1156
|
+
const envFile = resolvePath(cliOptions.envFile || path.join(configDir, "config.env"));
|
|
1157
|
+
const logFile = resolvePath(cliOptions.logFile || path.join(configDir, "logs", "viveworker.log"));
|
|
1158
|
+
const pidFile = resolvePath(cliOptions.pidFile || path.join(configDir, "viveworker.pid"));
|
|
1159
|
+
const launchAgentPath = resolvePath(cliOptions.launchAgentPath || defaultLaunchAgentPath);
|
|
1160
|
+
const locale = await resolveCliLocale(cliOptions);
|
|
1161
|
+
if (!(await fileExists(envFile))) {
|
|
1162
|
+
throw new Error("No viveworker config was found. Run `npx viveworker setup` first.");
|
|
1163
|
+
}
|
|
1164
|
+
const config = await ensureDefaultLocalePersisted(envFile, cliOptions);
|
|
1165
|
+
return { configDir, envFile, logFile, pidFile, launchAgentPath, locale, config };
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function refreshPairingCredentials(envFile, config = {}, { force = false } = {}) {
|
|
1169
|
+
const now = Date.now();
|
|
1170
|
+
const rotated = shouldRotatePairing({
|
|
1171
|
+
force,
|
|
1172
|
+
pairingCode: config.PAIRING_CODE,
|
|
1173
|
+
pairingToken: config.PAIRING_TOKEN,
|
|
1174
|
+
pairingExpiresAtMs: config.PAIRING_EXPIRES_AT_MS,
|
|
1175
|
+
}, now);
|
|
1176
|
+
|
|
1177
|
+
if (!rotated) {
|
|
1178
|
+
return { rotated: false };
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const nextPairing = generatePairingCredentials(now);
|
|
1182
|
+
const currentText = (await fileExists(envFile)) ? await fs.readFile(envFile, "utf8") : "";
|
|
1183
|
+
const nextText = upsertEnvText(currentText, {
|
|
1184
|
+
PAIRING_CODE: nextPairing.pairingCode,
|
|
1185
|
+
PAIRING_TOKEN: nextPairing.pairingToken,
|
|
1186
|
+
PAIRING_EXPIRES_AT_MS: String(nextPairing.pairingExpiresAtMs),
|
|
1187
|
+
});
|
|
1188
|
+
await fs.mkdir(path.dirname(envFile), { recursive: true });
|
|
1189
|
+
await fs.writeFile(envFile, nextText, "utf8");
|
|
1190
|
+
|
|
1191
|
+
return {
|
|
1192
|
+
rotated: true,
|
|
1193
|
+
...nextPairing,
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
async function maybeRunLegacySetupFeatureAliases(cliOptions, { envFile, locale, sessionSecret, port }) {
|
|
1198
|
+
const legacyActions = [];
|
|
1199
|
+
if (cliOptions.moltbook) {
|
|
1200
|
+
legacyActions.push("moltbook");
|
|
1201
|
+
}
|
|
1202
|
+
if (cliOptions.autoScout || cliOptions.autoScoutUninstall) {
|
|
1203
|
+
legacyActions.push("scout");
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
if (legacyActions.length === 0) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
console.log("");
|
|
1211
|
+
console.log(t(locale, "cli.setup.legacyFeatureFlags", {
|
|
1212
|
+
commands: legacyActions.map((action) => `viveworker enable ${action}`).join(", "),
|
|
1213
|
+
}));
|
|
1214
|
+
|
|
1215
|
+
if (cliOptions.moltbook) {
|
|
1216
|
+
try {
|
|
1217
|
+
await installMoltbookWatcher({ cliOptions, sessionSecret, port });
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
console.log("");
|
|
1220
|
+
console.log(`Moltbook watcher install failed: ${error.message}`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (cliOptions.autoScoutUninstall) {
|
|
1225
|
+
await uninstallMoltbookScout();
|
|
1226
|
+
} else if (cliOptions.autoScout) {
|
|
1227
|
+
try {
|
|
1228
|
+
await installMoltbookScout({ cliOptions });
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
console.log("");
|
|
1231
|
+
console.log(`Moltbook auto-scout install failed: ${error.message}`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
async function repairDoctorIssues(cliOptions, { envFile, config, locale, hostname, localHostname, chosenIp, webPushEnabled, allowInsecureHttpLan }) {
|
|
1237
|
+
const updates = {};
|
|
1238
|
+
const changed = [];
|
|
1239
|
+
|
|
1240
|
+
if (!config.SESSION_SECRET) {
|
|
1241
|
+
updates.SESSION_SECRET = crypto.randomBytes(32).toString("hex");
|
|
1242
|
+
changed.push(t(locale, "cli.doctor.fixed.sessionSecret"));
|
|
1243
|
+
}
|
|
1244
|
+
if (!config.PAIRING_CODE || !config.PAIRING_TOKEN) {
|
|
1245
|
+
const nextPairing = generatePairingCredentials();
|
|
1246
|
+
updates.PAIRING_CODE = nextPairing.pairingCode;
|
|
1247
|
+
updates.PAIRING_TOKEN = nextPairing.pairingToken;
|
|
1248
|
+
updates.PAIRING_EXPIRES_AT_MS = String(nextPairing.pairingExpiresAtMs);
|
|
1249
|
+
changed.push(t(locale, "cli.doctor.fixed.pairing"));
|
|
1250
|
+
}
|
|
1251
|
+
if (!config.DEFAULT_LOCALE) {
|
|
1252
|
+
updates.DEFAULT_LOCALE = locale;
|
|
1253
|
+
changed.push(t(locale, "cli.doctor.fixed.locale"));
|
|
1254
|
+
}
|
|
1255
|
+
if (!config.NATIVE_APPROVAL_SERVER_HOST) {
|
|
1256
|
+
updates.NATIVE_APPROVAL_SERVER_HOST = webPushEnabled || allowInsecureHttpLan ? "0.0.0.0" : "127.0.0.1";
|
|
1257
|
+
changed.push(t(locale, "cli.doctor.fixed.host"));
|
|
1258
|
+
}
|
|
1259
|
+
if (!config.NATIVE_APPROVAL_SERVER_PORT) {
|
|
1260
|
+
updates.NATIVE_APPROVAL_SERVER_PORT = String(defaultServerPort);
|
|
1261
|
+
changed.push(t(locale, "cli.doctor.fixed.port"));
|
|
1262
|
+
}
|
|
1263
|
+
if (!config.VIVEWORKER_HOSTNAME) {
|
|
1264
|
+
updates.VIVEWORKER_HOSTNAME = hostname;
|
|
1265
|
+
changed.push(t(locale, "cli.doctor.fixed.hostname"));
|
|
1266
|
+
}
|
|
1267
|
+
if (!config.DEVICE_TRUST_TTL_MS) {
|
|
1268
|
+
updates.DEVICE_TRUST_TTL_MS = String(30 * 24 * 60 * 60 * 1000);
|
|
1269
|
+
changed.push(t(locale, "cli.doctor.fixed.deviceTrust"));
|
|
1270
|
+
}
|
|
1271
|
+
if (!config.WEB_UI_ENABLED) {
|
|
1272
|
+
updates.WEB_UI_ENABLED = "1";
|
|
1273
|
+
changed.push(t(locale, "cli.doctor.fixed.webUi"));
|
|
1274
|
+
}
|
|
1275
|
+
if (!config.AUTH_REQUIRED) {
|
|
1276
|
+
updates.AUTH_REQUIRED = "1";
|
|
1277
|
+
changed.push(t(locale, "cli.doctor.fixed.auth"));
|
|
1278
|
+
}
|
|
1279
|
+
if (!config.CHOICE_PAGE_SIZE) {
|
|
1280
|
+
updates.CHOICE_PAGE_SIZE = "5";
|
|
1281
|
+
changed.push(t(locale, "cli.doctor.fixed.choicePageSize"));
|
|
1282
|
+
}
|
|
1283
|
+
if (!config.MAX_HISTORY_ITEMS) {
|
|
1284
|
+
updates.MAX_HISTORY_ITEMS = "100";
|
|
1285
|
+
changed.push(t(locale, "cli.doctor.fixed.maxHistory"));
|
|
1286
|
+
}
|
|
1287
|
+
if (!config.NATIVE_APPROVALS) {
|
|
1288
|
+
updates.NATIVE_APPROVALS = "1";
|
|
1289
|
+
changed.push(t(locale, "cli.doctor.fixed.nativeApprovals"));
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
const nextPort = Number(updates.NATIVE_APPROVAL_SERVER_PORT || config.NATIVE_APPROVAL_SERVER_PORT) || defaultServerPort;
|
|
1293
|
+
const nextHostname = updates.VIVEWORKER_HOSTNAME || config.VIVEWORKER_HOSTNAME || hostname;
|
|
1294
|
+
if (!config.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL) {
|
|
1295
|
+
const scheme = webPushEnabled ? "https" : "http";
|
|
1296
|
+
const publicHost = webPushEnabled || allowInsecureHttpLan
|
|
1297
|
+
? (nextHostname.endsWith(".local") ? nextHostname : `${nextHostname}.local`)
|
|
1298
|
+
: "127.0.0.1";
|
|
1299
|
+
updates.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL = `${scheme}://${publicHost}:${nextPort}`;
|
|
1300
|
+
changed.push(t(locale, "cli.doctor.fixed.baseUrl"));
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const nextConfig = { ...config, ...updates };
|
|
1304
|
+
if (webPushEnabled) {
|
|
1305
|
+
const tlsCertFile = resolvePath(
|
|
1306
|
+
nextConfig.TLS_CERT_FILE || path.join(path.dirname(envFile), "tls", "cert.pem")
|
|
1307
|
+
);
|
|
1308
|
+
const tlsKeyFile = resolvePath(
|
|
1309
|
+
nextConfig.TLS_KEY_FILE || path.join(path.dirname(envFile), "tls", "key.pem")
|
|
1310
|
+
);
|
|
1311
|
+
const progress = createCliProgressReporter(locale);
|
|
1312
|
+
const tlsAssets = await ensureWebPushAssets({
|
|
1313
|
+
cliOptions,
|
|
1314
|
+
existing: nextConfig,
|
|
1315
|
+
hostname: nextHostname,
|
|
1316
|
+
localHostname: nextHostname.endsWith(".local") ? nextHostname : `${nextHostname}.local`,
|
|
1317
|
+
locale,
|
|
1318
|
+
progress,
|
|
1319
|
+
chosenIp,
|
|
1320
|
+
tlsCertFile,
|
|
1321
|
+
tlsKeyFile,
|
|
1322
|
+
});
|
|
1323
|
+
progress.clear();
|
|
1324
|
+
updates.TLS_CERT_FILE = tlsAssets.certFile;
|
|
1325
|
+
updates.TLS_KEY_FILE = tlsAssets.keyFile;
|
|
1326
|
+
updates.WEB_PUSH_VAPID_PUBLIC_KEY = tlsAssets.vapidPublicKey;
|
|
1327
|
+
updates.WEB_PUSH_VAPID_PRIVATE_KEY = tlsAssets.vapidPrivateKey;
|
|
1328
|
+
changed.push(t(locale, "cli.doctor.fixed.webPushAssets"));
|
|
1329
|
+
if (!nextConfig.WEB_PUSH_SUBJECT) {
|
|
1330
|
+
updates.WEB_PUSH_SUBJECT = "mailto:viveworker@example.com";
|
|
1331
|
+
changed.push(t(locale, "cli.doctor.fixed.webPushSubject"));
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
const currentText = (await fileExists(envFile)) ? await fs.readFile(envFile, "utf8") : "";
|
|
1336
|
+
const nextText = upsertEnvText(currentText, updates);
|
|
1337
|
+
await fs.writeFile(envFile, nextText, "utf8");
|
|
1338
|
+
return changed;
|
|
922
1339
|
}
|
|
923
1340
|
|
|
924
1341
|
function parseArgs(argv) {
|
|
925
1342
|
const parsed = {
|
|
926
1343
|
command: "help",
|
|
1344
|
+
enableTarget: "",
|
|
1345
|
+
enableArgs: [],
|
|
927
1346
|
enableNtfy: false,
|
|
928
1347
|
enableWebPush: false,
|
|
929
1348
|
disableWebPush: false,
|
|
1349
|
+
noAutoMkcert: false,
|
|
1350
|
+
noAutoClaude: false,
|
|
1351
|
+
noScout: false,
|
|
930
1352
|
allowInsecureHttpLan: false,
|
|
931
1353
|
installMkcert: false,
|
|
932
1354
|
noLaunchd: false,
|
|
@@ -950,6 +1372,7 @@ function parseArgs(argv) {
|
|
|
950
1372
|
locale: "",
|
|
951
1373
|
mkcertTrustStores: "",
|
|
952
1374
|
claudeSettingsFile: "",
|
|
1375
|
+
doctorFix: false,
|
|
953
1376
|
moltbook: false,
|
|
954
1377
|
moltbookApiKey: "",
|
|
955
1378
|
moltbookAgentId: "",
|
|
@@ -967,6 +1390,15 @@ function parseArgs(argv) {
|
|
|
967
1390
|
argv = argv.slice(1);
|
|
968
1391
|
}
|
|
969
1392
|
|
|
1393
|
+
if (parsed.command === "enable" && argv[0] && !argv[0].startsWith("-")) {
|
|
1394
|
+
parsed.enableTarget = argv[0];
|
|
1395
|
+
argv = argv.slice(1);
|
|
1396
|
+
if (parsed.enableTarget === "a2a") {
|
|
1397
|
+
parsed.enableArgs = argv.slice();
|
|
1398
|
+
return parsed;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
970
1402
|
for (let index = 0; index < argv.length; index += 1) {
|
|
971
1403
|
const arg = argv[index];
|
|
972
1404
|
const next = argv[index + 1] ?? "";
|
|
@@ -976,6 +1408,12 @@ function parseArgs(argv) {
|
|
|
976
1408
|
parsed.enableWebPush = true;
|
|
977
1409
|
} else if (arg === "--disable-web-push") {
|
|
978
1410
|
parsed.disableWebPush = true;
|
|
1411
|
+
} else if (arg === "--no-auto-mkcert") {
|
|
1412
|
+
parsed.noAutoMkcert = true;
|
|
1413
|
+
} else if (arg === "--no-auto-claude") {
|
|
1414
|
+
parsed.noAutoClaude = true;
|
|
1415
|
+
} else if (arg === "--no-scout") {
|
|
1416
|
+
parsed.noScout = true;
|
|
979
1417
|
} else if (arg === "--allow-insecure-http-lan") {
|
|
980
1418
|
parsed.allowInsecureHttpLan = true;
|
|
981
1419
|
} else if (arg === "--install-mkcert") {
|
|
@@ -1030,6 +1468,8 @@ function parseArgs(argv) {
|
|
|
1030
1468
|
} else if (arg === "--locale") {
|
|
1031
1469
|
parsed.locale = next;
|
|
1032
1470
|
index += 1;
|
|
1471
|
+
} else if (arg === "--fix") {
|
|
1472
|
+
parsed.doctorFix = true;
|
|
1033
1473
|
} else if (arg === "--vapid-public-key") {
|
|
1034
1474
|
parsed.vapidPublicKey = next;
|
|
1035
1475
|
index += 1;
|
|
@@ -1038,9 +1478,23 @@ function parseArgs(argv) {
|
|
|
1038
1478
|
index += 1;
|
|
1039
1479
|
} else if (arg === "--pair") {
|
|
1040
1480
|
parsed.pair = true;
|
|
1481
|
+
} else if (arg === "--settings-file") {
|
|
1482
|
+
parsed.claudeSettingsFile = next;
|
|
1483
|
+
index += 1;
|
|
1041
1484
|
} else if (arg === "--claude-settings-file") {
|
|
1042
1485
|
parsed.claudeSettingsFile = next;
|
|
1043
1486
|
index += 1;
|
|
1487
|
+
} else if (arg === "--api-key") {
|
|
1488
|
+
parsed.moltbook = true;
|
|
1489
|
+
parsed.moltbookApiKey = next;
|
|
1490
|
+
index += 1;
|
|
1491
|
+
} else if (arg === "--agent-id") {
|
|
1492
|
+
parsed.moltbook = true;
|
|
1493
|
+
parsed.moltbookAgentId = next;
|
|
1494
|
+
index += 1;
|
|
1495
|
+
} else if (arg === "--agent-name") {
|
|
1496
|
+
parsed.moltbookAgentName = next;
|
|
1497
|
+
index += 1;
|
|
1044
1498
|
} else if (arg === "--moltbook") {
|
|
1045
1499
|
parsed.moltbook = true;
|
|
1046
1500
|
} else if (arg === "--moltbook-api-key") {
|
|
@@ -1056,6 +1510,20 @@ function parseArgs(argv) {
|
|
|
1056
1510
|
index += 1;
|
|
1057
1511
|
} else if (arg === "--auto-scout") {
|
|
1058
1512
|
parsed.autoScout = true;
|
|
1513
|
+
} else if (arg === "--uninstall") {
|
|
1514
|
+
parsed.autoScoutUninstall = true;
|
|
1515
|
+
} else if (arg === "--interval") {
|
|
1516
|
+
parsed.autoScoutInterval = Number(next) || 120;
|
|
1517
|
+
index += 1;
|
|
1518
|
+
} else if (arg === "--harness") {
|
|
1519
|
+
parsed.autoScoutHarness = next;
|
|
1520
|
+
index += 1;
|
|
1521
|
+
} else if (arg === "--submolts") {
|
|
1522
|
+
parsed.autoScoutSubmolts = next;
|
|
1523
|
+
index += 1;
|
|
1524
|
+
} else if (arg === "--max-daily") {
|
|
1525
|
+
parsed.autoScoutMaxDaily = Number(next) || 0;
|
|
1526
|
+
index += 1;
|
|
1059
1527
|
} else if (arg === "--auto-scout-uninstall") {
|
|
1060
1528
|
parsed.autoScoutUninstall = true;
|
|
1061
1529
|
} else if (arg === "--auto-scout-interval") {
|
|
@@ -1090,6 +1558,8 @@ function printHelp() {
|
|
|
1090
1558
|
|
|
1091
1559
|
${t(locale, "cli.help.commands")}
|
|
1092
1560
|
${t(locale, "cli.help.setup")}
|
|
1561
|
+
${t(locale, "cli.help.pair")}
|
|
1562
|
+
${t(locale, "cli.help.enable")}
|
|
1093
1563
|
${t(locale, "cli.help.start")}
|
|
1094
1564
|
${t(locale, "cli.help.stop")}
|
|
1095
1565
|
${t(locale, "cli.help.status")}
|
|
@@ -1103,6 +1573,8 @@ ${t(locale, "cli.help.commonOptions")}
|
|
|
1103
1573
|
--config-dir <path>
|
|
1104
1574
|
--disable-web-push
|
|
1105
1575
|
--enable-web-push
|
|
1576
|
+
--no-auto-mkcert
|
|
1577
|
+
--no-auto-claude
|
|
1106
1578
|
--allow-insecure-http-lan
|
|
1107
1579
|
--install-mkcert
|
|
1108
1580
|
--mkcert-trust-stores <system[,java][,nss]>
|
|
@@ -1115,6 +1587,13 @@ ${t(locale, "cli.help.commonOptions")}
|
|
|
1115
1587
|
--enable-ntfy
|
|
1116
1588
|
--no-launchd
|
|
1117
1589
|
--pair
|
|
1590
|
+
|
|
1591
|
+
${t(locale, "cli.help.featureOptions")}
|
|
1592
|
+
${t(locale, "cli.help.enableClaude")}
|
|
1593
|
+
${t(locale, "cli.help.enableA2a")}
|
|
1594
|
+
${t(locale, "cli.help.enableMoltbook")}
|
|
1595
|
+
${t(locale, "cli.help.enableScout")}
|
|
1596
|
+
${t(locale, "cli.help.doctorFix")}
|
|
1118
1597
|
`);
|
|
1119
1598
|
}
|
|
1120
1599
|
|
|
@@ -1287,6 +1766,119 @@ async function maybeRotateStartupPairing(envFile, config = {}) {
|
|
|
1287
1766
|
};
|
|
1288
1767
|
}
|
|
1289
1768
|
|
|
1769
|
+
async function autoConfigureProvidersDuringSetup({ cliOptions, envFile, sessionSecret, codexHome }) {
|
|
1770
|
+
const codex = await detectCodexAvailability(codexHome);
|
|
1771
|
+
const explicitClaudeSettingsFile = cliOptions.claudeSettingsFile
|
|
1772
|
+
? resolvePath(cliOptions.claudeSettingsFile)
|
|
1773
|
+
: "";
|
|
1774
|
+
const defaultClaudeSettingsFile = resolvePath(path.join(os.homedir(), ".claude", "settings.json"));
|
|
1775
|
+
const claudeSettingsFile = explicitClaudeSettingsFile || defaultClaudeSettingsFile;
|
|
1776
|
+
|
|
1777
|
+
if (!explicitClaudeSettingsFile && cliOptions.noAutoClaude) {
|
|
1778
|
+
return {
|
|
1779
|
+
codex,
|
|
1780
|
+
claude: {
|
|
1781
|
+
status: "skipped",
|
|
1782
|
+
settingsFile: "",
|
|
1783
|
+
message: "",
|
|
1784
|
+
},
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
const claudeDetected = explicitClaudeSettingsFile
|
|
1789
|
+
? true
|
|
1790
|
+
: await detectClaudeAvailability(defaultClaudeSettingsFile);
|
|
1791
|
+
|
|
1792
|
+
if (!claudeDetected) {
|
|
1793
|
+
return {
|
|
1794
|
+
codex,
|
|
1795
|
+
claude: {
|
|
1796
|
+
status: "not_detected",
|
|
1797
|
+
settingsFile: "",
|
|
1798
|
+
message: "",
|
|
1799
|
+
},
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
try {
|
|
1804
|
+
const installedPath = await installClaudeHooks({
|
|
1805
|
+
envFile,
|
|
1806
|
+
claudeSettingsFile,
|
|
1807
|
+
sessionSecret,
|
|
1808
|
+
suppressOutput: true,
|
|
1809
|
+
});
|
|
1810
|
+
return {
|
|
1811
|
+
codex,
|
|
1812
|
+
claude: {
|
|
1813
|
+
status: "enabled",
|
|
1814
|
+
settingsFile: installedPath,
|
|
1815
|
+
message: "",
|
|
1816
|
+
},
|
|
1817
|
+
};
|
|
1818
|
+
} catch (error) {
|
|
1819
|
+
return {
|
|
1820
|
+
codex,
|
|
1821
|
+
claude: {
|
|
1822
|
+
status: "failed",
|
|
1823
|
+
settingsFile: claudeSettingsFile,
|
|
1824
|
+
message: error?.message || String(error),
|
|
1825
|
+
},
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
async function detectCodexAvailability(codexHome) {
|
|
1831
|
+
const candidates = Array.from(new Set([
|
|
1832
|
+
resolvePath(codexHome || path.join(os.homedir(), ".codex")),
|
|
1833
|
+
"/Applications/Codex.app",
|
|
1834
|
+
path.join(os.homedir(), "Applications", "Codex.app"),
|
|
1835
|
+
]));
|
|
1836
|
+
for (const candidate of candidates) {
|
|
1837
|
+
if (await fileExists(candidate)) {
|
|
1838
|
+
return {
|
|
1839
|
+
detected: true,
|
|
1840
|
+
path: candidate,
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return {
|
|
1845
|
+
detected: false,
|
|
1846
|
+
path: "",
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
async function detectClaudeAvailability(settingsFile) {
|
|
1851
|
+
return (await fileExists(settingsFile)) || (await fileExists(path.dirname(settingsFile)));
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
function getSetupProviderSummaryLines(locale, providerSetup) {
|
|
1855
|
+
const lines = [
|
|
1856
|
+
t(locale, providerSetup.codex.detected
|
|
1857
|
+
? "cli.setup.providers.codexReady"
|
|
1858
|
+
: "cli.setup.providers.codexNotDetected"),
|
|
1859
|
+
];
|
|
1860
|
+
switch (providerSetup.claude.status) {
|
|
1861
|
+
case "enabled":
|
|
1862
|
+
lines.push(t(locale, "cli.setup.providers.claudeEnabled", {
|
|
1863
|
+
path: providerSetup.claude.settingsFile,
|
|
1864
|
+
}));
|
|
1865
|
+
break;
|
|
1866
|
+
case "skipped":
|
|
1867
|
+
lines.push(t(locale, "cli.setup.providers.claudeSkipped"));
|
|
1868
|
+
break;
|
|
1869
|
+
case "failed":
|
|
1870
|
+
lines.push(t(locale, "cli.setup.providers.claudeFailed", {
|
|
1871
|
+
message: providerSetup.claude.message,
|
|
1872
|
+
}));
|
|
1873
|
+
break;
|
|
1874
|
+
case "not_detected":
|
|
1875
|
+
default:
|
|
1876
|
+
lines.push(t(locale, "cli.setup.providers.claudeNotDetected"));
|
|
1877
|
+
break;
|
|
1878
|
+
}
|
|
1879
|
+
return lines;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1290
1882
|
async function findLocalIpv4Addresses() {
|
|
1291
1883
|
const interfaces = os.networkInterfaces();
|
|
1292
1884
|
const result = [];
|
|
@@ -1688,13 +2280,15 @@ async function ensureWebPushAssets({
|
|
|
1688
2280
|
throw new Error("TLS_CERT_FILE and TLS_KEY_FILE must both exist.");
|
|
1689
2281
|
}
|
|
1690
2282
|
|
|
2283
|
+
let ranMkcertInstall = false;
|
|
2284
|
+
|
|
1691
2285
|
if (!certExists) {
|
|
1692
2286
|
if (manualCertOverride) {
|
|
1693
2287
|
throw new Error("The provided TLS certificate or key file does not exist.");
|
|
1694
2288
|
}
|
|
1695
2289
|
|
|
1696
2290
|
let mkcertPath = await findExecutable("mkcert");
|
|
1697
|
-
if (!mkcertPath && cliOptions.
|
|
2291
|
+
if (!mkcertPath && !cliOptions.noAutoMkcert) {
|
|
1698
2292
|
mkcertPath = await installMkcertForMac(progress, locale);
|
|
1699
2293
|
}
|
|
1700
2294
|
if (!mkcertPath) {
|
|
@@ -1716,6 +2310,7 @@ async function ensureWebPushAssets({
|
|
|
1716
2310
|
streamOutput: true,
|
|
1717
2311
|
beforeStreamOutput: () => progress?.clear(),
|
|
1718
2312
|
});
|
|
2313
|
+
ranMkcertInstall = true;
|
|
1719
2314
|
progress?.update("cli.setup.progress.generateCert");
|
|
1720
2315
|
await fs.mkdir(path.dirname(tlsCertFile), { recursive: true });
|
|
1721
2316
|
await execCommand([
|
|
@@ -1740,6 +2335,7 @@ async function ensureWebPushAssets({
|
|
|
1740
2335
|
streamOutput: true,
|
|
1741
2336
|
beforeStreamOutput: () => progress?.clear(),
|
|
1742
2337
|
});
|
|
2338
|
+
ranMkcertInstall = true;
|
|
1743
2339
|
}
|
|
1744
2340
|
}
|
|
1745
2341
|
|
|
@@ -1757,6 +2353,7 @@ async function ensureWebPushAssets({
|
|
|
1757
2353
|
keyFile: tlsKeyFile,
|
|
1758
2354
|
vapidPublicKey,
|
|
1759
2355
|
vapidPrivateKey,
|
|
2356
|
+
ranMkcertInstall,
|
|
1760
2357
|
};
|
|
1761
2358
|
}
|
|
1762
2359
|
|
|
@@ -1767,6 +2364,7 @@ async function ensureWebPushAssets({
|
|
|
1767
2364
|
keyFile: tlsKeyFile,
|
|
1768
2365
|
vapidPublicKey: generated.publicKey,
|
|
1769
2366
|
vapidPrivateKey: generated.privateKey,
|
|
2367
|
+
ranMkcertInstall,
|
|
1770
2368
|
};
|
|
1771
2369
|
}
|
|
1772
2370
|
|
|
@@ -1874,7 +2472,7 @@ function escapeXml(value) {
|
|
|
1874
2472
|
.replace(/"/gu, """);
|
|
1875
2473
|
}
|
|
1876
2474
|
|
|
1877
|
-
async function printPairingInfo(locale, config) {
|
|
2475
|
+
async function printPairingInfo(locale, config, { sectioned = false } = {}) {
|
|
1878
2476
|
const baseUrl = String(config.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "").trim();
|
|
1879
2477
|
const pairCode = String(config.PAIRING_CODE || "").trim();
|
|
1880
2478
|
const pairToken = String(config.PAIRING_TOKEN || "").trim();
|
|
@@ -1886,6 +2484,14 @@ async function printPairingInfo(locale, config) {
|
|
|
1886
2484
|
const ips = await findLocalIpv4Addresses();
|
|
1887
2485
|
const fallbackBaseUrl = buildFallbackBaseUrl(baseUrl, ips[0] || "127.0.0.1");
|
|
1888
2486
|
|
|
2487
|
+
if (sectioned) {
|
|
2488
|
+
printCliSection(locale, "cli.section.pairingLinks", [
|
|
2489
|
+
t(locale, "cli.setup.pairingUrlLocal", { url: `${baseUrl}${pairPath}` }),
|
|
2490
|
+
t(locale, "cli.setup.pairingUrlIp", { url: `${fallbackBaseUrl}${pairPath}` }),
|
|
2491
|
+
]);
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
1889
2495
|
console.log("");
|
|
1890
2496
|
console.log(t(locale, "cli.setup.pairingCode", { code: pairCode }));
|
|
1891
2497
|
console.log(t(locale, "cli.setup.pairingUrlLocal", { url: `${baseUrl}${pairPath}` }));
|
|
@@ -1901,3 +2507,38 @@ function buildFallbackBaseUrl(baseUrl, ipAddress) {
|
|
|
1901
2507
|
return baseUrl;
|
|
1902
2508
|
}
|
|
1903
2509
|
}
|
|
2510
|
+
|
|
2511
|
+
function summarizeHealthOutput(rawValue) {
|
|
2512
|
+
const text = String(rawValue || "").trim();
|
|
2513
|
+
if (!text) {
|
|
2514
|
+
return [];
|
|
2515
|
+
}
|
|
2516
|
+
try {
|
|
2517
|
+
const parsed = JSON.parse(text);
|
|
2518
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2519
|
+
return [text];
|
|
2520
|
+
}
|
|
2521
|
+
return Object.entries(parsed)
|
|
2522
|
+
.filter(([key]) => key !== "ok")
|
|
2523
|
+
.map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`);
|
|
2524
|
+
} catch {
|
|
2525
|
+
return [text];
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
function printCliTitle(locale, key, vars = {}) {
|
|
2530
|
+
console.log("");
|
|
2531
|
+
console.log(t(locale, key, vars));
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
function printCliSection(locale, key, lines = [], vars = {}) {
|
|
2535
|
+
const filtered = (lines || []).filter((line) => String(line || "").trim());
|
|
2536
|
+
if (filtered.length === 0) {
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
console.log("");
|
|
2540
|
+
console.log(t(locale, key, vars));
|
|
2541
|
+
for (const line of filtered) {
|
|
2542
|
+
console.log(`- ${line}`);
|
|
2543
|
+
}
|
|
2544
|
+
}
|