@tokenbuddy/tokenbuddy 1.0.4 â 1.0.6
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 +20 -0
- package/dist/src/buyer-store.d.ts.map +1 -1
- package/dist/src/buyer-store.js +73 -1
- package/dist/src/buyer-store.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +390 -62
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +6 -5
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +298 -92
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/doctor-diagnostics.d.ts +97 -0
- package/dist/src/doctor-diagnostics.d.ts.map +1 -0
- package/dist/src/doctor-diagnostics.js +547 -0
- package/dist/src/doctor-diagnostics.js.map +1 -0
- package/dist/src/init-payment-options.d.ts +34 -0
- package/dist/src/init-payment-options.d.ts.map +1 -0
- package/dist/src/init-payment-options.js +90 -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/package.json +4 -4
- package/src/buyer-store.ts +113 -1
- package/src/cli.ts +490 -67
- package/src/daemon.ts +346 -117
- package/src/doctor-diagnostics.ts +850 -0
- package/src/init-payment-options.ts +131 -0
- package/src/provider-install.ts +426 -76
- package/src/seller-catalog.ts +222 -0
- package/src/tb-proxyd.ts +14 -2
- package/tests/e2e.test.ts +9 -0
- package/tests/tokenbuddy.test.ts +628 -19
- package/bin/tb-proxyd.js +0 -2
- package/bin/tb.js +0 -3
package/dist/src/cli.js
CHANGED
|
@@ -6,10 +6,13 @@ 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 { buildInitTerminalOptions, buildInitSuccessMessage, detectExistingClawtipBinding, INIT_PAYMENT_OPTIONS, noteInitComingSoonPayments, OTHER_TERMINAL_OPTION, validateInitTerminalSelection, } from "./init-payment-options.js";
|
|
13
16
|
// @ts-ignore
|
|
14
17
|
import qrcode from "qrcode-terminal";
|
|
15
18
|
const CONTROL_PORT = 17820;
|
|
@@ -262,6 +265,210 @@ function readProof(options) {
|
|
|
262
265
|
}
|
|
263
266
|
return proof;
|
|
264
267
|
}
|
|
268
|
+
function sellerRegistryUrlForInit() {
|
|
269
|
+
return process.env.TB_PROXYD_SELLER_REGISTRY_URL || "https://tb-wallet-bootstrap.fly.dev/registry/sellers";
|
|
270
|
+
}
|
|
271
|
+
function stableModelChoices(models) {
|
|
272
|
+
const grouped = new Map();
|
|
273
|
+
for (const entry of models) {
|
|
274
|
+
const list = grouped.get(entry.id) || [];
|
|
275
|
+
list.push(entry);
|
|
276
|
+
grouped.set(entry.id, list);
|
|
277
|
+
}
|
|
278
|
+
return Array.from(grouped.entries()).map(([modelId, entries]) => {
|
|
279
|
+
const sellerIds = Array.from(new Set(entries.map((entry) => entry.sellerId)));
|
|
280
|
+
const protocols = Array.from(new Set(entries.flatMap((entry) => entry.supportedProtocols)));
|
|
281
|
+
return {
|
|
282
|
+
value: modelId,
|
|
283
|
+
label: modelId,
|
|
284
|
+
hint: `${sellerIds.join(",")} · ${protocols.join(",") || "no-protocol"}`,
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async function promptSellerRoutingPreference(catalog) {
|
|
289
|
+
const healthySellers = catalog.sellers.filter((seller) => seller.status === "ok");
|
|
290
|
+
const mode = await p.select({
|
|
291
|
+
message: "Choose seller routing mode for tb-proxyd:",
|
|
292
|
+
options: [
|
|
293
|
+
{
|
|
294
|
+
value: "auto",
|
|
295
|
+
label: "Auto",
|
|
296
|
+
hint: "Automatically choose a compatible seller based on the requested model.",
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
value: "fixed",
|
|
300
|
+
label: "Fixed Seller",
|
|
301
|
+
hint: "Pin tb-proxyd to one seller and only use models from that seller.",
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
});
|
|
305
|
+
if (typeof mode !== "string") {
|
|
306
|
+
throw new Error("seller routing selection was cancelled");
|
|
307
|
+
}
|
|
308
|
+
if (mode === "auto") {
|
|
309
|
+
return { mode };
|
|
310
|
+
}
|
|
311
|
+
if (healthySellers.length === 0) {
|
|
312
|
+
throw new Error("no healthy sellers available for fixed routing");
|
|
313
|
+
}
|
|
314
|
+
const sellerId = await p.select({
|
|
315
|
+
message: "Choose the seller to pin tb-proxyd to:",
|
|
316
|
+
options: healthySellers.map((seller) => ({
|
|
317
|
+
value: seller.id,
|
|
318
|
+
label: seller.name ? `${seller.name} (${seller.id})` : seller.id,
|
|
319
|
+
hint: [
|
|
320
|
+
seller.discountRatio != null ? `discount x${seller.discountRatio}` : null,
|
|
321
|
+
seller.modelCount != null ? `${seller.modelCount} models` : null,
|
|
322
|
+
seller.supportedProtocols?.length ? seller.supportedProtocols.join(",") : null,
|
|
323
|
+
seller.paymentMethods?.length ? seller.paymentMethods.join(",") : null,
|
|
324
|
+
]
|
|
325
|
+
.filter(Boolean)
|
|
326
|
+
.join(" · ") || seller.url,
|
|
327
|
+
})),
|
|
328
|
+
});
|
|
329
|
+
if (typeof sellerId !== "string") {
|
|
330
|
+
throw new Error("fixed seller selection was cancelled");
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
mode,
|
|
334
|
+
sellerId,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
async function promptSingleModelSelection(providerId, models, sellerRouting) {
|
|
338
|
+
const protocolPreference = getProviderProtocolPreference(providerId);
|
|
339
|
+
const protocolFiltered = protocolPreference
|
|
340
|
+
? filterCatalogByProtocol(models, protocolPreference)
|
|
341
|
+
: models;
|
|
342
|
+
const choices = stableModelChoices(protocolFiltered);
|
|
343
|
+
if (choices.length === 0) {
|
|
344
|
+
throw new Error(`no compatible models available for ${providerId}`);
|
|
345
|
+
}
|
|
346
|
+
const labelMap = {
|
|
347
|
+
opencode: "OpenCode",
|
|
348
|
+
codex: "Codex",
|
|
349
|
+
openclaw: "OpenClaw",
|
|
350
|
+
hermes: "Hermes",
|
|
351
|
+
"claude-desktop": "Claude Desktop",
|
|
352
|
+
"claude-code": "Claude Code",
|
|
353
|
+
};
|
|
354
|
+
const selectedModel = await p.select({
|
|
355
|
+
message: `Choose the default model for ${labelMap[providerId] || providerId}:`,
|
|
356
|
+
options: choices,
|
|
357
|
+
});
|
|
358
|
+
if (typeof selectedModel !== "string") {
|
|
359
|
+
throw new Error(`default model selection was cancelled for ${providerId}`);
|
|
360
|
+
}
|
|
361
|
+
const selectedEntry = protocolFiltered.find((entry) => entry.id === selectedModel);
|
|
362
|
+
return {
|
|
363
|
+
selectionKind: "single-model",
|
|
364
|
+
protocolPreference,
|
|
365
|
+
defaultModel: selectedModel,
|
|
366
|
+
sellerId: sellerRouting.mode === "fixed" ? selectedEntry?.sellerId : undefined,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function defaultClaudeDisplayName(modelId) {
|
|
370
|
+
return modelId.trim();
|
|
371
|
+
}
|
|
372
|
+
function makeClaudeRoleMapping(modelId) {
|
|
373
|
+
const displayName = defaultClaudeDisplayName(modelId);
|
|
374
|
+
return {
|
|
375
|
+
selectionKind: "claude-role-mapping",
|
|
376
|
+
protocolPreference: "messages",
|
|
377
|
+
fallbackModel: modelId,
|
|
378
|
+
roles: {
|
|
379
|
+
sonnet: {
|
|
380
|
+
upstreamModel: modelId,
|
|
381
|
+
displayName,
|
|
382
|
+
declareOneM: true,
|
|
383
|
+
},
|
|
384
|
+
opus: {
|
|
385
|
+
upstreamModel: modelId,
|
|
386
|
+
displayName,
|
|
387
|
+
declareOneM: true,
|
|
388
|
+
},
|
|
389
|
+
haiku: {
|
|
390
|
+
upstreamModel: modelId,
|
|
391
|
+
displayName,
|
|
392
|
+
declareOneM: false,
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
async function promptClaudeCodeModelSelection(models) {
|
|
398
|
+
const protocolFiltered = filterCatalogByProtocol(models, "messages");
|
|
399
|
+
const choices = stableModelChoices(protocolFiltered);
|
|
400
|
+
if (choices.length === 0) {
|
|
401
|
+
throw new Error("no compatible message models available for Claude Code");
|
|
402
|
+
}
|
|
403
|
+
const sonnetModel = await p.select({
|
|
404
|
+
message: "Choose the default Sonnet model for Claude Code:",
|
|
405
|
+
options: choices,
|
|
406
|
+
});
|
|
407
|
+
if (typeof sonnetModel !== "string") {
|
|
408
|
+
throw new Error("Claude Code model selection was cancelled");
|
|
409
|
+
}
|
|
410
|
+
const mirrorAllRoles = await p.confirm({
|
|
411
|
+
message: "Use the same model for Opus and Haiku as well?",
|
|
412
|
+
initialValue: true,
|
|
413
|
+
});
|
|
414
|
+
if (typeof mirrorAllRoles !== "boolean") {
|
|
415
|
+
throw new Error("Claude Code role mapping confirmation was cancelled");
|
|
416
|
+
}
|
|
417
|
+
if (mirrorAllRoles) {
|
|
418
|
+
return makeClaudeRoleMapping(sonnetModel);
|
|
419
|
+
}
|
|
420
|
+
const opusModel = await p.select({
|
|
421
|
+
message: "Choose the default Opus model for Claude Code:",
|
|
422
|
+
options: choices,
|
|
423
|
+
});
|
|
424
|
+
if (typeof opusModel !== "string") {
|
|
425
|
+
throw new Error("Claude Code Opus model selection was cancelled");
|
|
426
|
+
}
|
|
427
|
+
const haikuModel = await p.select({
|
|
428
|
+
message: "Choose the default Haiku model for Claude Code:",
|
|
429
|
+
options: choices,
|
|
430
|
+
});
|
|
431
|
+
if (typeof haikuModel !== "string") {
|
|
432
|
+
throw new Error("Claude Code Haiku model selection was cancelled");
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
selectionKind: "claude-role-mapping",
|
|
436
|
+
protocolPreference: "messages",
|
|
437
|
+
fallbackModel: sonnetModel,
|
|
438
|
+
roles: {
|
|
439
|
+
sonnet: {
|
|
440
|
+
upstreamModel: sonnetModel,
|
|
441
|
+
displayName: defaultClaudeDisplayName(sonnetModel),
|
|
442
|
+
declareOneM: true,
|
|
443
|
+
},
|
|
444
|
+
opus: {
|
|
445
|
+
upstreamModel: opusModel,
|
|
446
|
+
displayName: defaultClaudeDisplayName(opusModel),
|
|
447
|
+
declareOneM: true,
|
|
448
|
+
},
|
|
449
|
+
haiku: {
|
|
450
|
+
upstreamModel: haikuModel,
|
|
451
|
+
displayName: defaultClaudeDisplayName(haikuModel),
|
|
452
|
+
declareOneM: false,
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
async function promptProviderSelections(providerIds, catalog, sellerRouting) {
|
|
458
|
+
const baseModels = sellerRouting.mode === "fixed"
|
|
459
|
+
? filterCatalogBySeller(catalog.models, sellerRouting.sellerId)
|
|
460
|
+
: catalog.models;
|
|
461
|
+
const selections = {};
|
|
462
|
+
for (const providerId of providerIds) {
|
|
463
|
+
const selectionKind = getProviderModelSelectionKind(providerId);
|
|
464
|
+
if (selectionKind === "claude-role-mapping") {
|
|
465
|
+
selections[providerId] = await promptClaudeCodeModelSelection(baseModels);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
selections[providerId] = await promptSingleModelSelection(providerId, baseModels, sellerRouting);
|
|
469
|
+
}
|
|
470
|
+
return selections;
|
|
471
|
+
}
|
|
265
472
|
export function buildCli() {
|
|
266
473
|
const program = new Command();
|
|
267
474
|
program
|
|
@@ -284,7 +491,6 @@ export function buildCli() {
|
|
|
284
491
|
const plistPath = process.platform === "darwin"
|
|
285
492
|
? path.join(os.homedir(), "Library", "LaunchAgents", "com.tokenbuddy.proxyd.plist")
|
|
286
493
|
: undefined;
|
|
287
|
-
const candidates = detectProviders();
|
|
288
494
|
let probe = await probeDaemonStatus(controlPort);
|
|
289
495
|
let repair = { attempted: false, fixed: false };
|
|
290
496
|
if (!probe.running && options.fix) {
|
|
@@ -295,10 +501,22 @@ export function buildCli() {
|
|
|
295
501
|
const daemonInfo = probe.status;
|
|
296
502
|
const daemonRunning = probe.running;
|
|
297
503
|
const daemonError = probe.error;
|
|
504
|
+
const daemonStatus = daemonInfo && typeof daemonInfo === "object"
|
|
505
|
+
? daemonInfo
|
|
506
|
+
: undefined;
|
|
507
|
+
const providers = readDoctorProviders();
|
|
298
508
|
if (options.fix && repair.attempted && !repair.fixed) {
|
|
299
509
|
process.exitCode = 1;
|
|
300
510
|
}
|
|
301
511
|
if (options.json) {
|
|
512
|
+
const diagnostics = await collectDoctorDiagnostics({
|
|
513
|
+
controlPort,
|
|
514
|
+
proxyPort,
|
|
515
|
+
daemonRunning,
|
|
516
|
+
daemonError,
|
|
517
|
+
providers,
|
|
518
|
+
sellerRegistryUrl: daemonStatus?.sellerRegistryUrl,
|
|
519
|
+
});
|
|
302
520
|
console.log(JSON.stringify({
|
|
303
521
|
daemon: {
|
|
304
522
|
running: daemonRunning,
|
|
@@ -318,7 +536,7 @@ export function buildCli() {
|
|
|
318
536
|
plistPath,
|
|
319
537
|
plistExists: plistPath ? fs.existsSync(plistPath) : false
|
|
320
538
|
},
|
|
321
|
-
|
|
539
|
+
...diagnostics,
|
|
322
540
|
}, null, 2));
|
|
323
541
|
return;
|
|
324
542
|
}
|
|
@@ -351,12 +569,28 @@ export function buildCli() {
|
|
|
351
569
|
console.log("â ïž LaunchAgent plist does NOT exist. Run `tb init` to install it as service.");
|
|
352
570
|
}
|
|
353
571
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
572
|
+
if (daemonStatus) {
|
|
573
|
+
console.log(` Control Plane URL: ${controlUrl}`);
|
|
574
|
+
console.log(` Proxy Plane URL: http://127.0.0.1:${proxyPort}`);
|
|
575
|
+
if (daemonStatus.sellerRoutingMode || daemonStatus.selectionMode) {
|
|
576
|
+
console.log(` Routing Mode: ${daemonStatus.sellerRoutingMode || daemonStatus.selectionMode}`);
|
|
577
|
+
}
|
|
578
|
+
if (daemonStatus.selectedSellerId) {
|
|
579
|
+
console.log(` Selected Seller: ${daemonStatus.selectedSellerId}`);
|
|
580
|
+
}
|
|
581
|
+
if (daemonStatus.sellerRegistryUrl) {
|
|
582
|
+
console.log(` Registry URL: ${daemonStatus.sellerRegistryUrl}`);
|
|
583
|
+
}
|
|
359
584
|
}
|
|
585
|
+
printDoctorProviders(providers);
|
|
586
|
+
await renderDoctorDiagnosticsProgressively({
|
|
587
|
+
controlPort,
|
|
588
|
+
proxyPort,
|
|
589
|
+
daemonRunning,
|
|
590
|
+
daemonError,
|
|
591
|
+
sellerRegistryUrl: daemonStatus?.sellerRegistryUrl,
|
|
592
|
+
providers,
|
|
593
|
+
});
|
|
360
594
|
});
|
|
361
595
|
// 2. tb payment
|
|
362
596
|
const payment = program.command("payment").description("Manage payment methods");
|
|
@@ -481,21 +715,30 @@ export function buildCli() {
|
|
|
481
715
|
.option("--json", "Output model list as JSON")
|
|
482
716
|
.action(async (options) => {
|
|
483
717
|
try {
|
|
718
|
+
const controlPort = configuredControlPort();
|
|
719
|
+
const proxyPort = configuredProxyPort();
|
|
720
|
+
const status = await probeDaemonStatus(controlPort);
|
|
721
|
+
const daemonInfo = status.status && typeof status.status === "object"
|
|
722
|
+
? status.status
|
|
723
|
+
: undefined;
|
|
724
|
+
const models = await collectDoctorModelsSummary({
|
|
725
|
+
controlPort,
|
|
726
|
+
proxyPort,
|
|
727
|
+
daemonRunning: status.running,
|
|
728
|
+
daemonError: status.error,
|
|
729
|
+
sellerRegistryUrl: daemonInfo?.sellerRegistryUrl,
|
|
730
|
+
});
|
|
484
731
|
if (options.json) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
throw new Error(body || `HTTP ${response.status}`);
|
|
732
|
+
console.log(JSON.stringify(models, null, 2));
|
|
733
|
+
if (!models.available) {
|
|
734
|
+
process.exitCode = 1;
|
|
489
735
|
}
|
|
490
|
-
JSON.parse(body);
|
|
491
|
-
console.log(body);
|
|
492
736
|
return;
|
|
493
737
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
console.log(table.toString());
|
|
738
|
+
printDoctorModelsSummary(models);
|
|
739
|
+
if (!models.available) {
|
|
740
|
+
process.exitCode = 1;
|
|
741
|
+
}
|
|
499
742
|
}
|
|
500
743
|
catch (err) {
|
|
501
744
|
console.error("Error connecting to local proxy:", err.message);
|
|
@@ -508,82 +751,165 @@ export function buildCli() {
|
|
|
508
751
|
.description("Launch step-by-step interactive setup wizard")
|
|
509
752
|
.action(async () => {
|
|
510
753
|
p.intro("ð Welcome to TokenBuddy Interactive Wizard!");
|
|
754
|
+
const setupSummaryLines = [];
|
|
511
755
|
// Step 1: Scan coding terminals
|
|
512
756
|
const spinner = p.spinner();
|
|
513
757
|
spinner.start("Scanning local system for programming terminals...");
|
|
514
758
|
const candidates = detectProviders();
|
|
515
|
-
const
|
|
759
|
+
const terminalOptions = buildInitTerminalOptions(candidates);
|
|
516
760
|
spinner.stop("Scan completed.");
|
|
517
|
-
if (
|
|
518
|
-
p.note("No active programming terminals detected. Install one of Codex, Claude Code, Claude Desktop, OpenClaw or Hermes first.");
|
|
761
|
+
if (terminalOptions.length === 1 && terminalOptions[0].value === OTHER_TERMINAL_OPTION.value) {
|
|
762
|
+
p.note("No active programming terminals detected. Install one of Codex, Claude Code, Claude Desktop, OpenCode, OpenClaw or Hermes first.");
|
|
519
763
|
}
|
|
520
764
|
else {
|
|
521
|
-
const choices = detected.map(c => ({
|
|
522
|
-
value: c.id,
|
|
523
|
-
label: c.name,
|
|
524
|
-
hint: c.configPath
|
|
525
|
-
}));
|
|
526
765
|
const selected = await p.multiselect({
|
|
527
766
|
message: "Select programming terminals to route via TokenBuddy (use Space to select, Enter to confirm):",
|
|
528
|
-
options:
|
|
767
|
+
options: terminalOptions,
|
|
529
768
|
required: false
|
|
530
769
|
});
|
|
531
|
-
|
|
532
|
-
|
|
770
|
+
const selectionError = validateInitTerminalSelection(selected);
|
|
771
|
+
if (selectionError) {
|
|
772
|
+
throw new Error(selectionError);
|
|
773
|
+
}
|
|
774
|
+
const selectedActionable = selected.filter((value) => !value.endsWith(":installed"));
|
|
775
|
+
const selectedOther = selectedActionable.includes(OTHER_TERMINAL_OPTION.value);
|
|
776
|
+
const selectedProviders = selectedActionable.filter((value) => value !== OTHER_TERMINAL_OPTION.value);
|
|
777
|
+
if (selectedOther) {
|
|
778
|
+
p.note([
|
|
779
|
+
"â
OpenAI-compatible Proxy",
|
|
780
|
+
" URL: http://127.0.0.1:17821/v1",
|
|
781
|
+
" Probe: http://127.0.0.1:17821/v1/models",
|
|
782
|
+
" Token: TOKENBUDDY_PROXY",
|
|
783
|
+
"",
|
|
784
|
+
"â
Anthropic-compatible Proxy",
|
|
785
|
+
" URL: http://127.0.0.1:17821"
|
|
786
|
+
].join("\n"), "TokenBuddy Proxy Interfaces");
|
|
787
|
+
setupSummaryLines.push("Manual terminal setup selected via Other.");
|
|
788
|
+
}
|
|
789
|
+
if (selectedProviders.length > 0) {
|
|
790
|
+
spinner.start("Fetching seller-backed model catalog...");
|
|
533
791
|
const proxyUrl = `http://127.0.0.1:${PROXY_PORT}`;
|
|
534
|
-
const
|
|
792
|
+
const registryUrl = sellerRegistryUrlForInit();
|
|
793
|
+
let catalog;
|
|
794
|
+
try {
|
|
795
|
+
catalog = await discoverSellerBackedModels(registryUrl);
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
spinner.stop("Failed to fetch seller-backed models.");
|
|
799
|
+
throw error;
|
|
800
|
+
}
|
|
801
|
+
spinner.stop("Seller-backed model catalog loaded.");
|
|
802
|
+
const providerIds = selectedProviders.filter((provider) => {
|
|
803
|
+
return [
|
|
804
|
+
"codex",
|
|
805
|
+
"claude-code",
|
|
806
|
+
"claude-desktop",
|
|
807
|
+
"openclaw",
|
|
808
|
+
"opencode",
|
|
809
|
+
"hermes",
|
|
810
|
+
].includes(provider);
|
|
811
|
+
});
|
|
812
|
+
const sellerRouting = await promptSellerRoutingPreference(catalog);
|
|
813
|
+
const providerSelections = await promptProviderSelections(providerIds, catalog, sellerRouting);
|
|
814
|
+
spinner.start("Configuring proxy routing in selected terminals...");
|
|
535
815
|
const store = openBuyerStore();
|
|
536
816
|
try {
|
|
537
817
|
applyProviderInstall({
|
|
538
|
-
providers:
|
|
818
|
+
providers: providerIds,
|
|
539
819
|
proxyUrl,
|
|
540
|
-
|
|
820
|
+
providerSelections,
|
|
821
|
+
sellerRouting,
|
|
541
822
|
}, store);
|
|
542
823
|
}
|
|
543
824
|
finally {
|
|
544
825
|
store.close();
|
|
545
826
|
}
|
|
546
827
|
spinner.stop("Selected terminals successfully configured.");
|
|
828
|
+
setupSummaryLines.push(`${providerIds.length} programming terminal${providerIds.length === 1 ? "" : "s"} configured for TokenBuddy.`);
|
|
547
829
|
}
|
|
548
830
|
}
|
|
549
831
|
// Step 2: Choose Payment Method & Scan QR Activation
|
|
832
|
+
noteInitComingSoonPayments();
|
|
550
833
|
const payMethod = await p.select({
|
|
551
834
|
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
|
-
]
|
|
835
|
+
options: INIT_PAYMENT_OPTIONS
|
|
556
836
|
});
|
|
557
837
|
if (payMethod === "clawtip") {
|
|
558
|
-
|
|
838
|
+
const store = openBuyerStore();
|
|
559
839
|
try {
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
840
|
+
const existingClawtip = detectExistingClawtipBinding(store.getPayment("clawtip"));
|
|
841
|
+
if (existingClawtip) {
|
|
842
|
+
store.savePayment({
|
|
843
|
+
method: "clawtip",
|
|
844
|
+
enabled: true,
|
|
845
|
+
isDefault: true,
|
|
846
|
+
config: existingClawtip.config
|
|
847
|
+
});
|
|
848
|
+
const details = [
|
|
849
|
+
existingClawtip.orderNo ? `Order: ${existingClawtip.orderNo}` : undefined,
|
|
850
|
+
existingClawtip.resourceUrl ? `ResourceUrl: ${existingClawtip.resourceUrl}` : undefined
|
|
851
|
+
].filter(Boolean).join("\n");
|
|
852
|
+
logger.info("payment.channel.reused", "clawtip payment channel already configured locally", {
|
|
853
|
+
method: "clawtip",
|
|
854
|
+
hasOrderNo: Boolean(existingClawtip.orderNo),
|
|
855
|
+
hasResourceUrl: Boolean(existingClawtip.resourceUrl)
|
|
856
|
+
});
|
|
857
|
+
p.note(details
|
|
858
|
+
? `ClawTip wallet is already configured locally.\n${details}`
|
|
859
|
+
: "ClawTip wallet is already configured locally.", "ClawTip");
|
|
860
|
+
setupSummaryLines.push("ClawTip wallet already bound locally; activation skipped.");
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
spinner.start("Requesting payment activation payload from public bootstrap registry...");
|
|
864
|
+
const bootstrapUrl = process.env.TOKENBUDDY_BOOTSTRAP_URL || "https://tb-wallet-bootstrap.fly.dev";
|
|
865
|
+
const res = await fetch(`${bootstrapUrl}/payments/clawtip/bootstrap`, {
|
|
866
|
+
method: "POST",
|
|
867
|
+
headers: { "Content-Type": "application/json" },
|
|
868
|
+
body: JSON.stringify({ clientTag: "cli-init" })
|
|
869
|
+
});
|
|
870
|
+
const data = await res.json();
|
|
871
|
+
spinner.stop("Bootstrap payload received.");
|
|
872
|
+
const qrUrl = data.payment?.resourceUrl || "https://example.com";
|
|
873
|
+
p.note("Scan the QR code below using your JD / WeChat App to complete the 1 Fen payment activation:");
|
|
874
|
+
// ð¡ High fidelity QR code rendering directly inside the CLI terminal
|
|
875
|
+
qrcode.generate(qrUrl, { small: true });
|
|
876
|
+
// Start 5-second polling interval
|
|
877
|
+
spinner.start("Waiting for JDæ¶é¶å° payment confirmation (polling activation status)...");
|
|
878
|
+
for (let i = 0; i < 5; i++) {
|
|
879
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
880
|
+
// Simulate/Wait confirmed. For real deployment, poll actual backend
|
|
881
|
+
}
|
|
882
|
+
spinner.stop("JDæ¶é¶å° confirmed payment. ClawTip wallet is active! ð");
|
|
883
|
+
store.savePayment({
|
|
884
|
+
method: "clawtip",
|
|
885
|
+
enabled: true,
|
|
886
|
+
isDefault: true,
|
|
887
|
+
config: {
|
|
888
|
+
bootstrapUrl,
|
|
889
|
+
orderNo: data.payment?.orderNo,
|
|
890
|
+
amountFen: data.payment?.amountFen ?? data.activationFeeFen,
|
|
891
|
+
indicator: data.payment?.indicator,
|
|
892
|
+
slug: data.payment?.slug,
|
|
893
|
+
skillId: data.payment?.skillId,
|
|
894
|
+
description: data.payment?.description,
|
|
895
|
+
resourceUrl: data.payment?.resourceUrl,
|
|
896
|
+
proofRequired: false
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
logger.info("payment.channel.added", "clawtip payment channel added during init", {
|
|
900
|
+
method: "clawtip",
|
|
901
|
+
orderNo: data.payment?.orderNo
|
|
902
|
+
});
|
|
903
|
+
setupSummaryLines.push("ClawTip wallet activated and set as the default payment method.");
|
|
578
904
|
}
|
|
579
|
-
spinner.stop("JDæ¶é¶å° confirmed payment. ClawTip wallet is active! ð");
|
|
580
905
|
}
|
|
581
906
|
catch (err) {
|
|
582
|
-
spinner.stop(`Failed to
|
|
907
|
+
spinner.stop(`Failed to finish ClawTip setup: ${err.message}`);
|
|
908
|
+
setupSummaryLines.push("ClawTip activation requires follow-up because the bootstrap step did not complete.");
|
|
909
|
+
}
|
|
910
|
+
finally {
|
|
911
|
+
store.close();
|
|
583
912
|
}
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
p.note("Mock Wallet selected. No real payments will be made. Status is active.");
|
|
587
913
|
}
|
|
588
914
|
// Step 3: Install Launchd Daemon Service
|
|
589
915
|
if (process.platform === "darwin") {
|
|
@@ -631,6 +957,7 @@ export function buildCli() {
|
|
|
631
957
|
catch { }
|
|
632
958
|
execSync(`launchctl load ${plistPath}`);
|
|
633
959
|
spinner.stop("LaunchAgent daemon successfully registered and started! ð");
|
|
960
|
+
setupSummaryLines.push("Background tb-proxyd launchd service installed.");
|
|
634
961
|
}
|
|
635
962
|
catch (err) {
|
|
636
963
|
spinner.stop(`Failed to write launchd plist: ${err.message}`);
|
|
@@ -640,8 +967,9 @@ export function buildCli() {
|
|
|
640
967
|
else {
|
|
641
968
|
// Run background dettached child process in linux/windows
|
|
642
969
|
p.note("System daemon is active. Process runs in dettached background.");
|
|
970
|
+
setupSummaryLines.push("Background daemon mode is available on this system.");
|
|
643
971
|
}
|
|
644
|
-
p.outro(
|
|
972
|
+
p.outro(buildInitSuccessMessage(setupSummaryLines));
|
|
645
973
|
});
|
|
646
974
|
return program;
|
|
647
975
|
}
|