@tokenbuddy/tokenbuddy 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/buyer-store.d.ts +48 -1
- package/dist/src/buyer-store.d.ts.map +1 -1
- package/dist/src/buyer-store.js +144 -17
- package/dist/src/buyer-store.js.map +1 -1
- package/dist/src/cli.d.ts +17 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +560 -63
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +11 -5
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +574 -161
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/doctor-clawtip-wallet.d.ts +14 -0
- package/dist/src/doctor-clawtip-wallet.d.ts.map +1 -0
- package/dist/src/doctor-clawtip-wallet.js +54 -0
- package/dist/src/doctor-clawtip-wallet.js.map +1 -0
- package/dist/src/doctor-diagnostics.d.ts +99 -0
- package/dist/src/doctor-diagnostics.d.ts.map +1 -0
- package/dist/src/doctor-diagnostics.js +552 -0
- package/dist/src/doctor-diagnostics.js.map +1 -0
- package/dist/src/init-clawtip-activation.d.ts +48 -0
- package/dist/src/init-clawtip-activation.d.ts.map +1 -0
- package/dist/src/init-clawtip-activation.js +395 -0
- package/dist/src/init-clawtip-activation.js.map +1 -0
- package/dist/src/init-payment-options.d.ts +56 -0
- package/dist/src/init-payment-options.d.ts.map +1 -0
- package/dist/src/init-payment-options.js +165 -0
- package/dist/src/init-payment-options.js.map +1 -0
- package/dist/src/provider-install.d.ts +37 -2
- package/dist/src/provider-install.d.ts.map +1 -1
- package/dist/src/provider-install.js +317 -67
- package/dist/src/provider-install.js.map +1 -1
- package/dist/src/seller-catalog.d.ts +79 -0
- package/dist/src/seller-catalog.d.ts.map +1 -0
- package/dist/src/seller-catalog.js +126 -0
- package/dist/src/seller-catalog.js.map +1 -0
- package/dist/src/tb-proxyd.js +13 -2
- package/dist/src/tb-proxyd.js.map +1 -1
- package/dist/src/terminal-image.d.ts +22 -0
- package/dist/src/terminal-image.d.ts.map +1 -0
- package/dist/src/terminal-image.js +135 -0
- package/dist/src/terminal-image.js.map +1 -0
- package/package.json +1 -1
- package/src/buyer-store.ts +253 -18
- package/src/cli.ts +709 -68
- package/src/daemon.ts +651 -167
- package/src/doctor-clawtip-wallet.ts +70 -0
- package/src/doctor-diagnostics.ts +861 -0
- package/src/init-clawtip-activation.ts +487 -0
- package/src/init-payment-options.ts +249 -0
- package/src/provider-install.ts +426 -76
- package/src/seller-catalog.ts +222 -0
- package/src/tb-proxyd.ts +14 -2
- package/src/terminal-image.ts +187 -0
- package/tests/e2e.test.ts +88 -5
- package/tests/tokenbuddy.test.ts +1362 -27
package/dist/src/cli.js
CHANGED
|
@@ -6,10 +6,15 @@ import * as os from "os";
|
|
|
6
6
|
import { execSync, spawn } from "child_process";
|
|
7
7
|
import Table from "cli-table3";
|
|
8
8
|
import { BuyerStore } from "./buyer-store.js";
|
|
9
|
-
import { applyProviderInstall, detectProviders } from "./provider-install.js";
|
|
9
|
+
import { applyProviderInstall, detectProviders, getProviderModelSelectionKind, getProviderProtocolPreference, } from "./provider-install.js";
|
|
10
10
|
import { createModuleLogger } from "@tokenbuddy/logging";
|
|
11
11
|
import * as crypto from "crypto";
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
|
+
import { discoverSellerBackedModels, filterCatalogByProtocol, filterCatalogBySeller, } from "./seller-catalog.js";
|
|
14
|
+
import { collectDoctorDiagnostics, collectDoctorModelsSummary, printDoctorProviders, printDoctorModelsSummary, readDoctorProviders, renderDoctorDiagnosticsProgressively, } from "./doctor-diagnostics.js";
|
|
15
|
+
import { buildInitSuccessMessage, buildInitTerminalSelectionState, buildInstalledTerminalMessage, INIT_PAYMENT_OPTIONS, inspectClawtipWalletReadiness, inspectOpenClawWalletConfig, noteInitComingSoonPayments, OTHER_TERMINAL_OPTION, validateInitTerminalSelection, } from "./init-payment-options.js";
|
|
16
|
+
import { checkOpenClawRuntime, readClawtipPayCredential, startClawtipWalletBootstrap, waitForClawtipActivationConfirmation, } from "./init-clawtip-activation.js";
|
|
17
|
+
import { displayTerminalImage } from "./terminal-image.js";
|
|
13
18
|
// @ts-ignore
|
|
14
19
|
import qrcode from "qrcode-terminal";
|
|
15
20
|
const CONTROL_PORT = 17820;
|
|
@@ -75,6 +80,7 @@ async function probeDaemonStatus(controlPort) {
|
|
|
75
80
|
};
|
|
76
81
|
}
|
|
77
82
|
}
|
|
83
|
+
const CLAWTIP_BOOTSTRAP_PLACEHOLDER_PAY_TO = "bootstrap-pay-to";
|
|
78
84
|
async function waitForDaemonStatus(controlPort, timeoutMs) {
|
|
79
85
|
const deadline = Date.now() + timeoutMs;
|
|
80
86
|
let latest = { running: false, error: "not checked" };
|
|
@@ -230,7 +236,7 @@ function printPaymentList(payments, asJson) {
|
|
|
230
236
|
console.log("=== TokenBuddy Payment Methods ===");
|
|
231
237
|
console.log(table.toString());
|
|
232
238
|
}
|
|
233
|
-
async function fetchClawtipBootstrap(bootstrapUrl) {
|
|
239
|
+
export async function fetchClawtipBootstrap(bootstrapUrl) {
|
|
234
240
|
const response = await fetch(`${bootstrapUrl.replace(/\/+$/, "")}/payments/clawtip/bootstrap`, {
|
|
235
241
|
method: "POST",
|
|
236
242
|
headers: { "Content-Type": "application/json" },
|
|
@@ -243,8 +249,32 @@ async function fetchClawtipBootstrap(bootstrapUrl) {
|
|
|
243
249
|
if (!body.payment?.orderNo || !body.payment.indicator || !body.payment.resourceUrl) {
|
|
244
250
|
throw new Error("ClawTip bootstrap response missing payment order fields");
|
|
245
251
|
}
|
|
252
|
+
if ((body.payment.payTo || "").trim() === CLAWTIP_BOOTSTRAP_PLACEHOLDER_PAY_TO) {
|
|
253
|
+
throw new Error([
|
|
254
|
+
`ClawTip bootstrap service is misconfigured: payTo is still the placeholder \`${CLAWTIP_BOOTSTRAP_PLACEHOLDER_PAY_TO}\`.`,
|
|
255
|
+
`Bootstrap URL: ${bootstrapUrl}`,
|
|
256
|
+
"Configure the bootstrap service with the real ClawTip merchant pay_to before retrying `tb init`.",
|
|
257
|
+
].join(" "));
|
|
258
|
+
}
|
|
259
|
+
body.payment.resourceUrl = normalizeClawtipBootstrapResourceUrl(bootstrapUrl, body.payment.resourceUrl);
|
|
246
260
|
return body;
|
|
247
261
|
}
|
|
262
|
+
export function normalizeClawtipBootstrapResourceUrl(bootstrapUrl, resourceUrl) {
|
|
263
|
+
try {
|
|
264
|
+
const bootstrap = new URL(bootstrapUrl);
|
|
265
|
+
const resource = new URL(resourceUrl);
|
|
266
|
+
if (resource.origin === bootstrap.origin && resource.pathname === "/registry/sellers") {
|
|
267
|
+
resource.pathname = bootstrap.pathname.replace(/\/+$/, "") || "/";
|
|
268
|
+
resource.search = "";
|
|
269
|
+
resource.hash = "";
|
|
270
|
+
return resource.toString().replace(/\/$/, "");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// Leave the server-provided value unchanged when URL parsing fails.
|
|
275
|
+
}
|
|
276
|
+
return resourceUrl;
|
|
277
|
+
}
|
|
248
278
|
function readProof(options) {
|
|
249
279
|
const proofFile = options.proofFile || process.env.TOKENBUDDY_CLAWTIP_PROOF_FILE;
|
|
250
280
|
if (!proofFile) {
|
|
@@ -262,6 +292,210 @@ function readProof(options) {
|
|
|
262
292
|
}
|
|
263
293
|
return proof;
|
|
264
294
|
}
|
|
295
|
+
function sellerRegistryUrlForInit() {
|
|
296
|
+
return process.env.TB_PROXYD_SELLER_REGISTRY_URL || "https://tb-wallet-bootstrap.fly.dev/registry/sellers";
|
|
297
|
+
}
|
|
298
|
+
function stableModelChoices(models) {
|
|
299
|
+
const grouped = new Map();
|
|
300
|
+
for (const entry of models) {
|
|
301
|
+
const list = grouped.get(entry.id) || [];
|
|
302
|
+
list.push(entry);
|
|
303
|
+
grouped.set(entry.id, list);
|
|
304
|
+
}
|
|
305
|
+
return Array.from(grouped.entries()).map(([modelId, entries]) => {
|
|
306
|
+
const sellerIds = Array.from(new Set(entries.map((entry) => entry.sellerId)));
|
|
307
|
+
const protocols = Array.from(new Set(entries.flatMap((entry) => entry.supportedProtocols)));
|
|
308
|
+
return {
|
|
309
|
+
value: modelId,
|
|
310
|
+
label: modelId,
|
|
311
|
+
hint: `${sellerIds.join(",")} · ${protocols.join(",") || "no-protocol"}`,
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async function promptSellerRoutingPreference(catalog) {
|
|
316
|
+
const healthySellers = catalog.sellers.filter((seller) => seller.status === "ok");
|
|
317
|
+
const mode = await p.select({
|
|
318
|
+
message: "Choose seller routing mode for tb-proxyd:",
|
|
319
|
+
options: [
|
|
320
|
+
{
|
|
321
|
+
value: "auto",
|
|
322
|
+
label: "Auto",
|
|
323
|
+
hint: "Automatically choose a compatible seller based on the requested model.",
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
value: "fixed",
|
|
327
|
+
label: "Fixed Seller",
|
|
328
|
+
hint: "Pin tb-proxyd to one seller and only use models from that seller.",
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
});
|
|
332
|
+
if (typeof mode !== "string") {
|
|
333
|
+
throw new Error("seller routing selection was cancelled");
|
|
334
|
+
}
|
|
335
|
+
if (mode === "auto") {
|
|
336
|
+
return { mode };
|
|
337
|
+
}
|
|
338
|
+
if (healthySellers.length === 0) {
|
|
339
|
+
throw new Error("no healthy sellers available for fixed routing");
|
|
340
|
+
}
|
|
341
|
+
const sellerId = await p.select({
|
|
342
|
+
message: "Choose the seller to pin tb-proxyd to:",
|
|
343
|
+
options: healthySellers.map((seller) => ({
|
|
344
|
+
value: seller.id,
|
|
345
|
+
label: seller.name ? `${seller.name} (${seller.id})` : seller.id,
|
|
346
|
+
hint: [
|
|
347
|
+
seller.discountRatio != null ? `discount x${seller.discountRatio}` : null,
|
|
348
|
+
seller.modelCount != null ? `${seller.modelCount} models` : null,
|
|
349
|
+
seller.supportedProtocols?.length ? seller.supportedProtocols.join(",") : null,
|
|
350
|
+
seller.paymentMethods?.length ? seller.paymentMethods.join(",") : null,
|
|
351
|
+
]
|
|
352
|
+
.filter(Boolean)
|
|
353
|
+
.join(" · ") || seller.url,
|
|
354
|
+
})),
|
|
355
|
+
});
|
|
356
|
+
if (typeof sellerId !== "string") {
|
|
357
|
+
throw new Error("fixed seller selection was cancelled");
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
mode,
|
|
361
|
+
sellerId,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async function promptSingleModelSelection(providerId, models, sellerRouting) {
|
|
365
|
+
const protocolPreference = getProviderProtocolPreference(providerId);
|
|
366
|
+
const protocolFiltered = protocolPreference
|
|
367
|
+
? filterCatalogByProtocol(models, protocolPreference)
|
|
368
|
+
: models;
|
|
369
|
+
const choices = stableModelChoices(protocolFiltered);
|
|
370
|
+
if (choices.length === 0) {
|
|
371
|
+
throw new Error(`no compatible models available for ${providerId}`);
|
|
372
|
+
}
|
|
373
|
+
const labelMap = {
|
|
374
|
+
opencode: "OpenCode",
|
|
375
|
+
codex: "Codex",
|
|
376
|
+
openclaw: "OpenClaw",
|
|
377
|
+
hermes: "Hermes",
|
|
378
|
+
"claude-desktop": "Claude Desktop",
|
|
379
|
+
"claude-code": "Claude Code",
|
|
380
|
+
};
|
|
381
|
+
const selectedModel = await p.select({
|
|
382
|
+
message: `Choose the default model for ${labelMap[providerId] || providerId}:`,
|
|
383
|
+
options: choices,
|
|
384
|
+
});
|
|
385
|
+
if (typeof selectedModel !== "string") {
|
|
386
|
+
throw new Error(`default model selection was cancelled for ${providerId}`);
|
|
387
|
+
}
|
|
388
|
+
const selectedEntry = protocolFiltered.find((entry) => entry.id === selectedModel);
|
|
389
|
+
return {
|
|
390
|
+
selectionKind: "single-model",
|
|
391
|
+
protocolPreference,
|
|
392
|
+
defaultModel: selectedModel,
|
|
393
|
+
sellerId: sellerRouting.mode === "fixed" ? selectedEntry?.sellerId : undefined,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function defaultClaudeDisplayName(modelId) {
|
|
397
|
+
return modelId.trim();
|
|
398
|
+
}
|
|
399
|
+
function makeClaudeRoleMapping(modelId) {
|
|
400
|
+
const displayName = defaultClaudeDisplayName(modelId);
|
|
401
|
+
return {
|
|
402
|
+
selectionKind: "claude-role-mapping",
|
|
403
|
+
protocolPreference: "messages",
|
|
404
|
+
fallbackModel: modelId,
|
|
405
|
+
roles: {
|
|
406
|
+
sonnet: {
|
|
407
|
+
upstreamModel: modelId,
|
|
408
|
+
displayName,
|
|
409
|
+
declareOneM: true,
|
|
410
|
+
},
|
|
411
|
+
opus: {
|
|
412
|
+
upstreamModel: modelId,
|
|
413
|
+
displayName,
|
|
414
|
+
declareOneM: true,
|
|
415
|
+
},
|
|
416
|
+
haiku: {
|
|
417
|
+
upstreamModel: modelId,
|
|
418
|
+
displayName,
|
|
419
|
+
declareOneM: false,
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
async function promptClaudeCodeModelSelection(models) {
|
|
425
|
+
const protocolFiltered = filterCatalogByProtocol(models, "messages");
|
|
426
|
+
const choices = stableModelChoices(protocolFiltered);
|
|
427
|
+
if (choices.length === 0) {
|
|
428
|
+
throw new Error("no compatible message models available for Claude Code");
|
|
429
|
+
}
|
|
430
|
+
const sonnetModel = await p.select({
|
|
431
|
+
message: "Choose the default Sonnet model for Claude Code:",
|
|
432
|
+
options: choices,
|
|
433
|
+
});
|
|
434
|
+
if (typeof sonnetModel !== "string") {
|
|
435
|
+
throw new Error("Claude Code model selection was cancelled");
|
|
436
|
+
}
|
|
437
|
+
const mirrorAllRoles = await p.confirm({
|
|
438
|
+
message: "Use the same model for Opus and Haiku as well?",
|
|
439
|
+
initialValue: true,
|
|
440
|
+
});
|
|
441
|
+
if (typeof mirrorAllRoles !== "boolean") {
|
|
442
|
+
throw new Error("Claude Code role mapping confirmation was cancelled");
|
|
443
|
+
}
|
|
444
|
+
if (mirrorAllRoles) {
|
|
445
|
+
return makeClaudeRoleMapping(sonnetModel);
|
|
446
|
+
}
|
|
447
|
+
const opusModel = await p.select({
|
|
448
|
+
message: "Choose the default Opus model for Claude Code:",
|
|
449
|
+
options: choices,
|
|
450
|
+
});
|
|
451
|
+
if (typeof opusModel !== "string") {
|
|
452
|
+
throw new Error("Claude Code Opus model selection was cancelled");
|
|
453
|
+
}
|
|
454
|
+
const haikuModel = await p.select({
|
|
455
|
+
message: "Choose the default Haiku model for Claude Code:",
|
|
456
|
+
options: choices,
|
|
457
|
+
});
|
|
458
|
+
if (typeof haikuModel !== "string") {
|
|
459
|
+
throw new Error("Claude Code Haiku model selection was cancelled");
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
selectionKind: "claude-role-mapping",
|
|
463
|
+
protocolPreference: "messages",
|
|
464
|
+
fallbackModel: sonnetModel,
|
|
465
|
+
roles: {
|
|
466
|
+
sonnet: {
|
|
467
|
+
upstreamModel: sonnetModel,
|
|
468
|
+
displayName: defaultClaudeDisplayName(sonnetModel),
|
|
469
|
+
declareOneM: true,
|
|
470
|
+
},
|
|
471
|
+
opus: {
|
|
472
|
+
upstreamModel: opusModel,
|
|
473
|
+
displayName: defaultClaudeDisplayName(opusModel),
|
|
474
|
+
declareOneM: true,
|
|
475
|
+
},
|
|
476
|
+
haiku: {
|
|
477
|
+
upstreamModel: haikuModel,
|
|
478
|
+
displayName: defaultClaudeDisplayName(haikuModel),
|
|
479
|
+
declareOneM: false,
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
async function promptProviderSelections(providerIds, catalog, sellerRouting) {
|
|
485
|
+
const baseModels = sellerRouting.mode === "fixed"
|
|
486
|
+
? filterCatalogBySeller(catalog.models, sellerRouting.sellerId)
|
|
487
|
+
: catalog.models;
|
|
488
|
+
const selections = {};
|
|
489
|
+
for (const providerId of providerIds) {
|
|
490
|
+
const selectionKind = getProviderModelSelectionKind(providerId);
|
|
491
|
+
if (selectionKind === "claude-role-mapping") {
|
|
492
|
+
selections[providerId] = await promptClaudeCodeModelSelection(baseModels);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
selections[providerId] = await promptSingleModelSelection(providerId, baseModels, sellerRouting);
|
|
496
|
+
}
|
|
497
|
+
return selections;
|
|
498
|
+
}
|
|
265
499
|
export function buildCli() {
|
|
266
500
|
const program = new Command();
|
|
267
501
|
program
|
|
@@ -284,7 +518,6 @@ export function buildCli() {
|
|
|
284
518
|
const plistPath = process.platform === "darwin"
|
|
285
519
|
? path.join(os.homedir(), "Library", "LaunchAgents", "com.tokenbuddy.proxyd.plist")
|
|
286
520
|
: undefined;
|
|
287
|
-
const candidates = detectProviders();
|
|
288
521
|
let probe = await probeDaemonStatus(controlPort);
|
|
289
522
|
let repair = { attempted: false, fixed: false };
|
|
290
523
|
if (!probe.running && options.fix) {
|
|
@@ -295,10 +528,22 @@ export function buildCli() {
|
|
|
295
528
|
const daemonInfo = probe.status;
|
|
296
529
|
const daemonRunning = probe.running;
|
|
297
530
|
const daemonError = probe.error;
|
|
531
|
+
const daemonStatus = daemonInfo && typeof daemonInfo === "object"
|
|
532
|
+
? daemonInfo
|
|
533
|
+
: undefined;
|
|
534
|
+
const providers = readDoctorProviders();
|
|
298
535
|
if (options.fix && repair.attempted && !repair.fixed) {
|
|
299
536
|
process.exitCode = 1;
|
|
300
537
|
}
|
|
301
538
|
if (options.json) {
|
|
539
|
+
const diagnostics = await collectDoctorDiagnostics({
|
|
540
|
+
controlPort,
|
|
541
|
+
proxyPort,
|
|
542
|
+
daemonRunning,
|
|
543
|
+
daemonError,
|
|
544
|
+
providers,
|
|
545
|
+
sellerRegistryUrl: daemonStatus?.sellerRegistryUrl,
|
|
546
|
+
});
|
|
302
547
|
console.log(JSON.stringify({
|
|
303
548
|
daemon: {
|
|
304
549
|
running: daemonRunning,
|
|
@@ -318,7 +563,7 @@ export function buildCli() {
|
|
|
318
563
|
plistPath,
|
|
319
564
|
plistExists: plistPath ? fs.existsSync(plistPath) : false
|
|
320
565
|
},
|
|
321
|
-
|
|
566
|
+
...diagnostics,
|
|
322
567
|
}, null, 2));
|
|
323
568
|
return;
|
|
324
569
|
}
|
|
@@ -351,12 +596,28 @@ export function buildCli() {
|
|
|
351
596
|
console.log("⚠️ LaunchAgent plist does NOT exist. Run `tb init` to install it as service.");
|
|
352
597
|
}
|
|
353
598
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
599
|
+
if (daemonStatus) {
|
|
600
|
+
console.log(` Control Plane URL: ${controlUrl}`);
|
|
601
|
+
console.log(` Proxy Plane URL: http://127.0.0.1:${proxyPort}`);
|
|
602
|
+
if (daemonStatus.sellerRoutingMode || daemonStatus.selectionMode) {
|
|
603
|
+
console.log(` Routing Mode: ${daemonStatus.sellerRoutingMode || daemonStatus.selectionMode}`);
|
|
604
|
+
}
|
|
605
|
+
if (daemonStatus.selectedSellerId) {
|
|
606
|
+
console.log(` Selected Seller: ${daemonStatus.selectedSellerId}`);
|
|
607
|
+
}
|
|
608
|
+
if (daemonStatus.sellerRegistryUrl) {
|
|
609
|
+
console.log(` Registry URL: ${daemonStatus.sellerRegistryUrl}`);
|
|
610
|
+
}
|
|
359
611
|
}
|
|
612
|
+
printDoctorProviders(providers);
|
|
613
|
+
await renderDoctorDiagnosticsProgressively({
|
|
614
|
+
controlPort,
|
|
615
|
+
proxyPort,
|
|
616
|
+
daemonRunning,
|
|
617
|
+
daemonError,
|
|
618
|
+
sellerRegistryUrl: daemonStatus?.sellerRegistryUrl,
|
|
619
|
+
providers,
|
|
620
|
+
});
|
|
360
621
|
});
|
|
361
622
|
// 2. tb payment
|
|
362
623
|
const payment = program.command("payment").description("Manage payment methods");
|
|
@@ -481,21 +742,30 @@ export function buildCli() {
|
|
|
481
742
|
.option("--json", "Output model list as JSON")
|
|
482
743
|
.action(async (options) => {
|
|
483
744
|
try {
|
|
745
|
+
const controlPort = configuredControlPort();
|
|
746
|
+
const proxyPort = configuredProxyPort();
|
|
747
|
+
const status = await probeDaemonStatus(controlPort);
|
|
748
|
+
const daemonInfo = status.status && typeof status.status === "object"
|
|
749
|
+
? status.status
|
|
750
|
+
: undefined;
|
|
751
|
+
const models = await collectDoctorModelsSummary({
|
|
752
|
+
controlPort,
|
|
753
|
+
proxyPort,
|
|
754
|
+
daemonRunning: status.running,
|
|
755
|
+
daemonError: status.error,
|
|
756
|
+
sellerRegistryUrl: daemonInfo?.sellerRegistryUrl,
|
|
757
|
+
});
|
|
484
758
|
if (options.json) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
throw new Error(body || `HTTP ${response.status}`);
|
|
759
|
+
console.log(JSON.stringify(models, null, 2));
|
|
760
|
+
if (!models.available) {
|
|
761
|
+
process.exitCode = 1;
|
|
489
762
|
}
|
|
490
|
-
JSON.parse(body);
|
|
491
|
-
console.log(body);
|
|
492
763
|
return;
|
|
493
764
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
console.log(table.toString());
|
|
765
|
+
printDoctorModelsSummary(models);
|
|
766
|
+
if (!models.available) {
|
|
767
|
+
process.exitCode = 1;
|
|
768
|
+
}
|
|
499
769
|
}
|
|
500
770
|
catch (err) {
|
|
501
771
|
console.error("Error connecting to local proxy:", err.message);
|
|
@@ -508,82 +778,307 @@ export function buildCli() {
|
|
|
508
778
|
.description("Launch step-by-step interactive setup wizard")
|
|
509
779
|
.action(async () => {
|
|
510
780
|
p.intro("🚀 Welcome to TokenBuddy Interactive Wizard!");
|
|
781
|
+
const setupSummaryLines = [];
|
|
511
782
|
// Step 1: Scan coding terminals
|
|
512
783
|
const spinner = p.spinner();
|
|
513
784
|
spinner.start("Scanning local system for programming terminals...");
|
|
514
785
|
const candidates = detectProviders();
|
|
515
|
-
const
|
|
786
|
+
const terminalSelection = buildInitTerminalSelectionState(candidates);
|
|
516
787
|
spinner.stop("Scan completed.");
|
|
517
|
-
|
|
518
|
-
|
|
788
|
+
const installedTerminalMessage = buildInstalledTerminalMessage(terminalSelection.installed);
|
|
789
|
+
if (installedTerminalMessage) {
|
|
790
|
+
p.note(installedTerminalMessage, "Already Configured");
|
|
791
|
+
setupSummaryLines.push(`${terminalSelection.installed.length} terminal${terminalSelection.installed.length === 1 ? "" : "s"} already configured.`);
|
|
792
|
+
}
|
|
793
|
+
if (terminalSelection.options.length === 1 && terminalSelection.options[0].value === OTHER_TERMINAL_OPTION.value) {
|
|
794
|
+
p.note("No active programming terminals detected. Install one of Codex, Claude Code, Claude Desktop, OpenCode, OpenClaw or Hermes first.");
|
|
519
795
|
}
|
|
520
796
|
else {
|
|
521
|
-
const choices = detected.map(c => ({
|
|
522
|
-
value: c.id,
|
|
523
|
-
label: c.name,
|
|
524
|
-
hint: c.configPath
|
|
525
|
-
}));
|
|
526
797
|
const selected = await p.multiselect({
|
|
527
798
|
message: "Select programming terminals to route via TokenBuddy (use Space to select, Enter to confirm):",
|
|
528
|
-
options:
|
|
799
|
+
options: terminalSelection.options,
|
|
529
800
|
required: false
|
|
530
801
|
});
|
|
531
|
-
|
|
532
|
-
|
|
802
|
+
const selectionError = validateInitTerminalSelection(selected);
|
|
803
|
+
if (selectionError) {
|
|
804
|
+
throw new Error(selectionError);
|
|
805
|
+
}
|
|
806
|
+
const selectedActionable = selected.filter((value) => !value.endsWith(":installed"));
|
|
807
|
+
const selectedOther = selectedActionable.includes(OTHER_TERMINAL_OPTION.value);
|
|
808
|
+
const selectedProviders = selectedActionable.filter((value) => value !== OTHER_TERMINAL_OPTION.value);
|
|
809
|
+
if (selectedOther) {
|
|
810
|
+
p.note([
|
|
811
|
+
"✅ OpenAI-compatible Proxy",
|
|
812
|
+
" URL: http://127.0.0.1:17821/v1",
|
|
813
|
+
" Probe: http://127.0.0.1:17821/v1/models",
|
|
814
|
+
" Token: TOKENBUDDY_PROXY",
|
|
815
|
+
"",
|
|
816
|
+
"✅ Anthropic-compatible Proxy",
|
|
817
|
+
" URL: http://127.0.0.1:17821"
|
|
818
|
+
].join("\n"), "TokenBuddy Proxy Interfaces");
|
|
819
|
+
setupSummaryLines.push("Manual terminal setup selected via Other.");
|
|
820
|
+
}
|
|
821
|
+
if (selectedProviders.length > 0) {
|
|
822
|
+
spinner.start("Fetching seller-backed model catalog...");
|
|
533
823
|
const proxyUrl = `http://127.0.0.1:${PROXY_PORT}`;
|
|
534
|
-
const
|
|
824
|
+
const registryUrl = sellerRegistryUrlForInit();
|
|
825
|
+
let catalog;
|
|
826
|
+
try {
|
|
827
|
+
catalog = await discoverSellerBackedModels(registryUrl);
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
spinner.stop("Failed to fetch seller-backed models.");
|
|
831
|
+
throw error;
|
|
832
|
+
}
|
|
833
|
+
spinner.stop("Seller-backed model catalog loaded.");
|
|
834
|
+
const providerIds = selectedProviders.filter((provider) => {
|
|
835
|
+
return [
|
|
836
|
+
"codex",
|
|
837
|
+
"claude-code",
|
|
838
|
+
"claude-desktop",
|
|
839
|
+
"openclaw",
|
|
840
|
+
"opencode",
|
|
841
|
+
"hermes",
|
|
842
|
+
].includes(provider);
|
|
843
|
+
});
|
|
844
|
+
const sellerRouting = await promptSellerRoutingPreference(catalog);
|
|
845
|
+
const providerSelections = await promptProviderSelections(providerIds, catalog, sellerRouting);
|
|
846
|
+
spinner.start("Configuring proxy routing in selected terminals...");
|
|
535
847
|
const store = openBuyerStore();
|
|
536
848
|
try {
|
|
537
849
|
applyProviderInstall({
|
|
538
|
-
providers:
|
|
850
|
+
providers: providerIds,
|
|
539
851
|
proxyUrl,
|
|
540
|
-
|
|
852
|
+
providerSelections,
|
|
853
|
+
sellerRouting,
|
|
541
854
|
}, store);
|
|
542
855
|
}
|
|
543
856
|
finally {
|
|
544
857
|
store.close();
|
|
545
858
|
}
|
|
546
859
|
spinner.stop("Selected terminals successfully configured.");
|
|
860
|
+
setupSummaryLines.push(`${providerIds.length} programming terminal${providerIds.length === 1 ? "" : "s"} configured for TokenBuddy.`);
|
|
547
861
|
}
|
|
548
862
|
}
|
|
549
863
|
// Step 2: Choose Payment Method & Scan QR Activation
|
|
864
|
+
noteInitComingSoonPayments();
|
|
550
865
|
const payMethod = await p.select({
|
|
551
866
|
message: "Choose your primary payment method for LLM token purchases:",
|
|
552
|
-
options:
|
|
553
|
-
{ value: "clawtip", label: "JD ClawTip Pay (Scan QR Code to activate)", hint: "1 Fen activation fee" },
|
|
554
|
-
{ value: "mock", label: "Mock Wallet (For local development and tests)" }
|
|
555
|
-
]
|
|
867
|
+
options: INIT_PAYMENT_OPTIONS
|
|
556
868
|
});
|
|
557
869
|
if (payMethod === "clawtip") {
|
|
558
|
-
|
|
870
|
+
const store = openBuyerStore();
|
|
559
871
|
try {
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
872
|
+
let walletConfig = inspectOpenClawWalletConfig();
|
|
873
|
+
const clawtipReadiness = inspectClawtipWalletReadiness(store.getPayment("clawtip"), walletConfig);
|
|
874
|
+
const existingClawtip = clawtipReadiness.reusableBinding;
|
|
875
|
+
if (existingClawtip) {
|
|
876
|
+
store.savePayment({
|
|
877
|
+
method: "clawtip",
|
|
878
|
+
enabled: true,
|
|
879
|
+
isDefault: true,
|
|
880
|
+
config: existingClawtip.config
|
|
881
|
+
});
|
|
882
|
+
const details = [
|
|
883
|
+
existingClawtip.orderNo ? `Order: ${existingClawtip.orderNo}` : undefined,
|
|
884
|
+
existingClawtip.resourceUrl ? `ResourceUrl: ${existingClawtip.resourceUrl}` : undefined
|
|
885
|
+
].filter(Boolean).join("\n");
|
|
886
|
+
logger.info("payment.channel.reused", "clawtip payment channel already configured locally", {
|
|
887
|
+
method: "clawtip",
|
|
888
|
+
hasOrderNo: Boolean(existingClawtip.orderNo),
|
|
889
|
+
hasResourceUrl: Boolean(existingClawtip.resourceUrl)
|
|
890
|
+
});
|
|
891
|
+
p.note(details
|
|
892
|
+
? `ClawTip wallet is already configured locally.\n${details}`
|
|
893
|
+
: "ClawTip wallet is already configured locally.", "ClawTip");
|
|
894
|
+
setupSummaryLines.push("ClawTip wallet already bound locally; activation skipped.");
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
if (clawtipReadiness.status === "metadata_missing_wallet") {
|
|
898
|
+
p.note([
|
|
899
|
+
clawtipReadiness.message,
|
|
900
|
+
`Expected: ${walletConfig.expectedPath}`,
|
|
901
|
+
walletConfig.alternatePaths.length > 0
|
|
902
|
+
? `Alternates: ${walletConfig.alternatePaths.join(", ")}`
|
|
903
|
+
: "Alternates: -"
|
|
904
|
+
].join("\n"), "ClawTip");
|
|
905
|
+
setupSummaryLines.push("Saved ClawTip metadata found, but local wallet config is missing; activation restarted.");
|
|
906
|
+
}
|
|
907
|
+
const walletReadyBeforePay = walletConfig.exists;
|
|
908
|
+
let openClawVersion;
|
|
909
|
+
if (!walletReadyBeforePay) {
|
|
910
|
+
spinner.start("Checking OpenClaw CLI before ClawTip wallet bootstrap...");
|
|
911
|
+
openClawVersion = await checkOpenClawRuntime();
|
|
912
|
+
spinner.stop("OpenClaw CLI detected.");
|
|
913
|
+
}
|
|
914
|
+
spinner.start("Requesting payment activation payload from public bootstrap registry...");
|
|
915
|
+
const bootstrapUrl = process.env.TOKENBUDDY_BOOTSTRAP_URL || "https://tb-wallet-bootstrap.fly.dev";
|
|
916
|
+
const bootstrap = await fetchClawtipBootstrap(bootstrapUrl);
|
|
917
|
+
spinner.stop("Bootstrap payload received.");
|
|
918
|
+
if (!bootstrap.payment?.orderNo || !bootstrap.payment?.indicator) {
|
|
919
|
+
throw new Error("ClawTip bootstrap response missing orderNo or indicator.");
|
|
920
|
+
}
|
|
921
|
+
const activationPayment = {
|
|
922
|
+
orderNo: bootstrap.payment.orderNo,
|
|
923
|
+
amountFen: bootstrap.payment.amountFen ?? bootstrap.activationFeeFen ?? 1,
|
|
924
|
+
payTo: bootstrap.payment.payTo,
|
|
925
|
+
encryptedData: bootstrap.payment.encryptedData,
|
|
926
|
+
indicator: bootstrap.payment.indicator,
|
|
927
|
+
slug: bootstrap.payment.slug,
|
|
928
|
+
skillId: bootstrap.payment.skillId,
|
|
929
|
+
description: bootstrap.payment.description,
|
|
930
|
+
resourceUrl: bootstrap.payment.resourceUrl,
|
|
931
|
+
};
|
|
932
|
+
spinner.start("Starting the ClawTip payment activation flow...");
|
|
933
|
+
let activation = await startClawtipWalletBootstrap(activationPayment);
|
|
934
|
+
spinner.stop("ClawTip payment activation finished.");
|
|
935
|
+
let payCredential = activation.payCredential;
|
|
936
|
+
for (let authAttempt = 1; (walletReadyBeforePay ? !payCredential : !walletConfig.exists)
|
|
937
|
+
&& activation.parsedOutput.requiresWalletAuth
|
|
938
|
+
&& authAttempt <= 3; authAttempt += 1) {
|
|
939
|
+
let qrDisplayMessage;
|
|
940
|
+
let manualOpenCommand;
|
|
941
|
+
if (activation.parsedOutput.mediaPath) {
|
|
942
|
+
const qrDisplay = await displayTerminalImage(activation.parsedOutput.mediaPath);
|
|
943
|
+
qrDisplayMessage = qrDisplay.message;
|
|
944
|
+
manualOpenCommand = qrDisplay.fallbackCommand;
|
|
945
|
+
}
|
|
946
|
+
if (!activation.parsedOutput.mediaPath && !activation.parsedOutput.authUrl) {
|
|
947
|
+
throw new Error(`ClawTip pay requested authorization but did not return QR media or authUrl. Order file: ${activation.orderFile}`);
|
|
948
|
+
}
|
|
949
|
+
p.note([
|
|
950
|
+
activation.parsedOutput.mediaPath
|
|
951
|
+
? `Open or scan this ClawTip wallet QR image with the JD app: ${activation.parsedOutput.mediaPath}`
|
|
952
|
+
: undefined,
|
|
953
|
+
activation.parsedOutput.authUrl
|
|
954
|
+
? `Open or scan this ClawTip wallet auth URL with the JD app: ${activation.parsedOutput.authUrl}`
|
|
955
|
+
: undefined,
|
|
956
|
+
qrDisplayMessage,
|
|
957
|
+
manualOpenCommand ? `Manual open command: ${manualOpenCommand}` : undefined,
|
|
958
|
+
activation.parsedOutput.clawtipId ? `clawtipId: ${activation.parsedOutput.clawtipId}` : undefined,
|
|
959
|
+
activation.parsedOutput.clawtipId
|
|
960
|
+
? "After scanning, ClawTip CLI will register the local agent wallet."
|
|
961
|
+
: "After scanning, TokenBuddy will retry ClawTip payment activation.",
|
|
962
|
+
`Expected wallet config: ${walletConfig.expectedPath}`,
|
|
963
|
+
openClawVersion ? `OpenClaw: ${openClawVersion}` : undefined,
|
|
964
|
+
].filter(Boolean).join("\n"), activation.parsedOutput.clawtipId ? "ClawTip Wallet QR" : "ClawTip Authorization QR");
|
|
965
|
+
if (activation.parsedOutput.clawtipId && !walletReadyBeforePay) {
|
|
966
|
+
spinner.start("Waiting for ClawTip wallet registration. Press Ctrl+C to cancel.");
|
|
967
|
+
const walletRegistered = await waitForClawtipActivationConfirmation({
|
|
968
|
+
clawtipId: activation.parsedOutput.clawtipId,
|
|
969
|
+
});
|
|
970
|
+
spinner.stop(walletRegistered
|
|
971
|
+
? "Detected local OpenClaw ClawTip wallet config."
|
|
972
|
+
: "ClawTip wallet registration cancelled.");
|
|
973
|
+
if (!walletRegistered) {
|
|
974
|
+
setupSummaryLines.push("ClawTip wallet registration was cancelled before local wallet config was saved.");
|
|
975
|
+
process.exitCode = 1;
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
walletConfig = inspectOpenClawWalletConfig();
|
|
979
|
+
if (!walletConfig.exists) {
|
|
980
|
+
throw new Error(`ClawTip wallet registration finished but wallet config is still missing: ${walletConfig.expectedPath}`);
|
|
981
|
+
}
|
|
982
|
+
setupSummaryLines.push("ClawTip wallet registered locally.");
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
const authorized = await p.confirm({
|
|
986
|
+
message: "Scan or authorize this ClawTip QR in the JD app, then press Enter to retry payment activation.",
|
|
987
|
+
initialValue: true,
|
|
988
|
+
});
|
|
989
|
+
if (authorized !== true) {
|
|
990
|
+
setupSummaryLines.push("ClawTip authorization was cancelled before payment activation completed.");
|
|
991
|
+
process.exitCode = 1;
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
walletConfig = inspectOpenClawWalletConfig();
|
|
995
|
+
}
|
|
996
|
+
payCredential = readClawtipPayCredential(activation.orderFile);
|
|
997
|
+
if (!payCredential) {
|
|
998
|
+
spinner.start(`Retrying ClawTip payment activation after authorization (${authAttempt}/3)...`);
|
|
999
|
+
activation = await startClawtipWalletBootstrap(activationPayment);
|
|
1000
|
+
spinner.stop("ClawTip payment activation retry finished.");
|
|
1001
|
+
payCredential = activation.payCredential;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
const refreshedWalletConfig = inspectOpenClawWalletConfig();
|
|
1005
|
+
if (!walletReadyBeforePay && !refreshedWalletConfig.exists) {
|
|
1006
|
+
const paymentQrMessage = activation.parsedOutput.mediaPath
|
|
1007
|
+
? ` ClawTip pay returned QR media: ${activation.parsedOutput.mediaPath}`
|
|
1008
|
+
: " ClawTip pay did not return QR media.";
|
|
1009
|
+
throw new Error([
|
|
1010
|
+
`ClawTip wallet config is still missing after payment activation: ${refreshedWalletConfig.expectedPath}.`,
|
|
1011
|
+
`Order file: ${activation.orderFile}.`,
|
|
1012
|
+
paymentQrMessage.trim(),
|
|
1013
|
+
].join(" "));
|
|
1014
|
+
}
|
|
1015
|
+
const completedByWalletConfig = !payCredential && !walletReadyBeforePay && refreshedWalletConfig.exists;
|
|
1016
|
+
if (!payCredential && !completedByWalletConfig) {
|
|
1017
|
+
const paymentQrMessage = activation.parsedOutput.mediaPath
|
|
1018
|
+
? ` ClawTip pay returned QR media: ${activation.parsedOutput.mediaPath}`
|
|
1019
|
+
: "";
|
|
1020
|
+
throw new Error(`ClawTip pay did not write payCredential to the order file. Order file: ${activation.orderFile}.${paymentQrMessage}`);
|
|
1021
|
+
}
|
|
1022
|
+
const activationCompletedBy = payCredential
|
|
1023
|
+
? (refreshedWalletConfig.exists ? "payCredential+wallet-config" : "payCredential")
|
|
1024
|
+
: "wallet-config";
|
|
1025
|
+
store.savePayment({
|
|
1026
|
+
method: "clawtip",
|
|
1027
|
+
enabled: true,
|
|
1028
|
+
isDefault: true,
|
|
1029
|
+
config: {
|
|
1030
|
+
bootstrapUrl,
|
|
1031
|
+
orderNo: bootstrap.payment?.orderNo,
|
|
1032
|
+
amountFen: bootstrap.payment?.amountFen ?? bootstrap.activationFeeFen,
|
|
1033
|
+
indicator: bootstrap.payment?.indicator,
|
|
1034
|
+
slug: bootstrap.payment?.slug,
|
|
1035
|
+
skillId: bootstrap.payment?.skillId,
|
|
1036
|
+
description: bootstrap.payment?.description,
|
|
1037
|
+
resourceUrl: bootstrap.payment?.resourceUrl,
|
|
1038
|
+
proofRequired: false,
|
|
1039
|
+
activationOrderFile: activation.orderFile,
|
|
1040
|
+
walletConfigPath: refreshedWalletConfig.expectedPath,
|
|
1041
|
+
walletConfigPresent: refreshedWalletConfig.exists,
|
|
1042
|
+
payCredentialWritten: Boolean(payCredential),
|
|
1043
|
+
activationCompletedBy
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
logger.info("payment.channel.added", "clawtip payment channel added during init", {
|
|
1047
|
+
method: "clawtip",
|
|
1048
|
+
orderNo: bootstrap.payment?.orderNo,
|
|
1049
|
+
payCredentialWritten: Boolean(payCredential),
|
|
1050
|
+
activationCompletedBy
|
|
1051
|
+
});
|
|
1052
|
+
if (refreshedWalletConfig.exists) {
|
|
1053
|
+
if (!payCredential) {
|
|
1054
|
+
p.note([
|
|
1055
|
+
"OpenClaw saved the local ClawTip wallet config, but the ClawTip order file did not contain payCredential.",
|
|
1056
|
+
`Order file: ${activation.orderFile}`,
|
|
1057
|
+
"TokenBuddy saved the wallet binding metadata and will rely on the local wallet for future ClawTip purchases."
|
|
1058
|
+
].join("\n"), "ClawTip");
|
|
1059
|
+
}
|
|
1060
|
+
setupSummaryLines.push("ClawTip wallet activated and set as the default payment method.");
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
p.note([
|
|
1064
|
+
"ClawTip payment metadata was saved, but the local OpenClaw wallet config is still missing.",
|
|
1065
|
+
`Expected: ${refreshedWalletConfig.expectedPath}`,
|
|
1066
|
+
refreshedWalletConfig.alternatePaths.length > 0
|
|
1067
|
+
? `Nearby files: ${refreshedWalletConfig.alternatePaths.join(", ")}`
|
|
1068
|
+
: "Nearby files: -",
|
|
1069
|
+
"Bind or restore the local wallet before using ClawTip-backed purchases."
|
|
1070
|
+
].join("\n"), "ClawTip Wallet Required");
|
|
1071
|
+
setupSummaryLines.push("ClawTip payment metadata saved; local wallet config still needs binding before use.");
|
|
1072
|
+
}
|
|
578
1073
|
}
|
|
579
|
-
spinner.stop("JD收银台 confirmed payment. ClawTip wallet is active! 🚀");
|
|
580
1074
|
}
|
|
581
1075
|
catch (err) {
|
|
582
|
-
spinner.stop(`Failed to
|
|
1076
|
+
spinner.stop(`Failed to finish ClawTip setup: ${err.message}`);
|
|
1077
|
+
setupSummaryLines.push("ClawTip activation requires follow-up because the bootstrap step did not complete.");
|
|
1078
|
+
}
|
|
1079
|
+
finally {
|
|
1080
|
+
store.close();
|
|
583
1081
|
}
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
p.note("Mock Wallet selected. No real payments will be made. Status is active.");
|
|
587
1082
|
}
|
|
588
1083
|
// Step 3: Install Launchd Daemon Service
|
|
589
1084
|
if (process.platform === "darwin") {
|
|
@@ -631,6 +1126,7 @@ export function buildCli() {
|
|
|
631
1126
|
catch { }
|
|
632
1127
|
execSync(`launchctl load ${plistPath}`);
|
|
633
1128
|
spinner.stop("LaunchAgent daemon successfully registered and started! 🚀");
|
|
1129
|
+
setupSummaryLines.push("Background tb-proxyd launchd service installed.");
|
|
634
1130
|
}
|
|
635
1131
|
catch (err) {
|
|
636
1132
|
spinner.stop(`Failed to write launchd plist: ${err.message}`);
|
|
@@ -640,8 +1136,9 @@ export function buildCli() {
|
|
|
640
1136
|
else {
|
|
641
1137
|
// Run background dettached child process in linux/windows
|
|
642
1138
|
p.note("System daemon is active. Process runs in dettached background.");
|
|
1139
|
+
setupSummaryLines.push("Background daemon mode is available on this system.");
|
|
643
1140
|
}
|
|
644
|
-
p.outro(
|
|
1141
|
+
p.outro(buildInitSuccessMessage(setupSummaryLines));
|
|
645
1142
|
});
|
|
646
1143
|
return program;
|
|
647
1144
|
}
|