@tokenbuddy/tokenbuddy 1.0.17 → 1.0.19

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.
@@ -0,0 +1,125 @@
1
+ export const INIT_SETUP_CONFIG_KEY = "init-setup";
2
+ export const INIT_SETUP_VERSION = 1;
3
+ export const INIT_SETUP_STEPS = [
4
+ "gateway_intro",
5
+ "model_access",
6
+ "supplier_routing",
7
+ "auto_purchase",
8
+ "connect_tools",
9
+ "verify_gateway",
10
+ "install_app",
11
+ ];
12
+ export const DEFAULT_INIT_RECOMMENDED_MODELS = [
13
+ "deepseek-v4-flash",
14
+ "kimi-2.6",
15
+ "gpt-5.5",
16
+ "gpt-5.4",
17
+ "claude-opus-4.7",
18
+ "claude-opus-4.8",
19
+ "claude-sonnet-4.6",
20
+ "gemma-4-26b-a4b-it",
21
+ "qwen3-235b-a22b",
22
+ "google/gemini-3.5-flash",
23
+ "google/gemini-3.1-pro-preview",
24
+ "qwen/qwen3.7-max",
25
+ "qwen/qwen3.7-plus",
26
+ ];
27
+ const INIT_SETUP_STEP_SET = new Set(INIT_SETUP_STEPS);
28
+ export function normalizeInitSetupMarker(value) {
29
+ if (!value || typeof value !== "object") {
30
+ return defaultInitSetupMarker();
31
+ }
32
+ const raw = value;
33
+ const status = normalizeInitSetupStatus(raw.status);
34
+ return {
35
+ status,
36
+ version: INIT_SETUP_VERSION,
37
+ completedSteps: normalizeInitSetupSteps(raw.completedSteps),
38
+ startedAt: typeof raw.startedAt === "string" ? raw.startedAt : undefined,
39
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : undefined,
40
+ completedAt: typeof raw.completedAt === "string" ? raw.completedAt : undefined,
41
+ };
42
+ }
43
+ export function buildInProgressInitSetupMarker(previous = defaultInitSetupMarker(), now = new Date()) {
44
+ const updatedAt = now.toISOString();
45
+ return {
46
+ ...previous,
47
+ status: previous.status === "completed" ? "completed" : "in_progress",
48
+ version: INIT_SETUP_VERSION,
49
+ startedAt: previous.startedAt || updatedAt,
50
+ updatedAt,
51
+ };
52
+ }
53
+ export function buildCompletedInitSetupMarker(completedSteps = INIT_SETUP_STEPS, now = new Date()) {
54
+ const normalizedSteps = normalizeInitSetupSteps(completedSteps);
55
+ const updatedAt = now.toISOString();
56
+ return {
57
+ status: "completed",
58
+ version: INIT_SETUP_VERSION,
59
+ completedSteps: normalizedSteps.length > 0 ? normalizedSteps : [...INIT_SETUP_STEPS],
60
+ startedAt: updatedAt,
61
+ updatedAt,
62
+ completedAt: updatedAt,
63
+ };
64
+ }
65
+ export function assertInitSetupSteps(value) {
66
+ if (value === undefined) {
67
+ return [...INIT_SETUP_STEPS];
68
+ }
69
+ if (!Array.isArray(value)) {
70
+ throw new Error("completedSteps must be an array");
71
+ }
72
+ const steps = [];
73
+ for (const entry of value) {
74
+ if (typeof entry !== "string" || !INIT_SETUP_STEP_SET.has(entry)) {
75
+ throw new Error(`unknown init setup step: ${String(entry)}`);
76
+ }
77
+ if (!steps.includes(entry)) {
78
+ steps.push(entry);
79
+ }
80
+ }
81
+ return steps;
82
+ }
83
+ export function isFreshInitMachine(marker) {
84
+ return marker.status !== "completed";
85
+ }
86
+ export function normalizeInitRecommendedModels(value) {
87
+ const entries = typeof value === "string"
88
+ ? value.split(",")
89
+ : value ?? [];
90
+ return Array.from(new Set(entries.map((entry) => entry.trim()).filter(Boolean)));
91
+ }
92
+ export function resolveInitRecommendedModels(input) {
93
+ const configured = normalizeInitRecommendedModels(input?.configuredModels);
94
+ if (configured.length > 0) {
95
+ return configured;
96
+ }
97
+ const envModels = normalizeInitRecommendedModels(input?.env?.TB_PROXYD_INIT_RECOMMENDED_MODELS);
98
+ if (envModels.length > 0) {
99
+ return envModels;
100
+ }
101
+ return [...DEFAULT_INIT_RECOMMENDED_MODELS];
102
+ }
103
+ function defaultInitSetupMarker() {
104
+ return {
105
+ status: "not_started",
106
+ version: INIT_SETUP_VERSION,
107
+ completedSteps: [],
108
+ };
109
+ }
110
+ function normalizeInitSetupStatus(value) {
111
+ return value === "in_progress" || value === "completed" ? value : "not_started";
112
+ }
113
+ function normalizeInitSetupSteps(value) {
114
+ if (!Array.isArray(value)) {
115
+ return [];
116
+ }
117
+ const steps = [];
118
+ for (const entry of value) {
119
+ if (typeof entry === "string" && INIT_SETUP_STEP_SET.has(entry) && !steps.includes(entry)) {
120
+ steps.push(entry);
121
+ }
122
+ }
123
+ return steps;
124
+ }
125
+ //# sourceMappingURL=init-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-setup.js","sourceRoot":"","sources":["../../src/init-setup.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@tokenbuddy/tokenbuddy",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "TokenBuddy Client CLI and Daemon",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
7
7
  "type": "module",
8
8
  "bin": {
9
- "tb": "./bin/tb.js",
10
- "tb-proxyd": "./bin/tb-proxyd.js"
9
+ "tb": "bin/tb.js",
10
+ "tb-proxyd": "bin/tb-proxyd.js"
11
11
  },
12
12
  "scripts": {
13
13
  "build": "tsc"
package/src/cli.ts CHANGED
@@ -78,16 +78,47 @@ const STALE_TOKENBUDDY_LAUNCHD_LABELS = [
78
78
  "homebrew.mxcl.tokenbuddy",
79
79
  ] as const;
80
80
  const logger = createModuleLogger("tokenbuddy-cli");
81
+
82
+ function readCliPackageVersion(): string {
83
+ const candidateRoots = [
84
+ process.argv[1],
85
+ path.join(process.cwd(), "packages", "tokenbuddy-cli"),
86
+ process.cwd(),
87
+ ];
88
+ const seen = new Set<string>();
89
+ for (const candidateRoot of candidateRoots) {
90
+ if (!candidateRoot) continue;
91
+ let current = fs.existsSync(candidateRoot) ? fs.realpathSync(candidateRoot) : candidateRoot;
92
+ if (!fs.existsSync(current)) continue;
93
+ if (!fs.statSync(current).isDirectory()) {
94
+ current = path.dirname(current);
95
+ }
96
+ while (!seen.has(current)) {
97
+ seen.add(current);
98
+ const packageJsonPath = path.join(current, "package.json");
99
+ if (fs.existsSync(packageJsonPath)) {
100
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as { name?: unknown; version?: unknown };
101
+ if (packageJson.name === "@tokenbuddy/tokenbuddy" && typeof packageJson.version === "string" && packageJson.version.length > 0) {
102
+ return packageJson.version;
103
+ }
104
+ }
105
+ const parent = path.dirname(current);
106
+ if (parent === current) break;
107
+ current = parent;
108
+ }
109
+ }
110
+ return "0.0.0";
111
+ }
81
112
  const SUPPORTED_PAYMENT_METHODS = ["mock", "clawtip"] as const;
82
113
  type SupportedPaymentMethod = typeof SUPPORTED_PAYMENT_METHODS[number];
83
114
 
84
- interface DaemonProbeResult {
115
+ export interface DaemonProbeResult {
85
116
  running: boolean;
86
117
  status?: unknown;
87
118
  error?: string;
88
119
  }
89
120
 
90
- interface DaemonRepairResult {
121
+ export interface DaemonRepairResult {
91
122
  attempted: boolean;
92
123
  fixed: boolean;
93
124
  pid?: number;
@@ -209,8 +240,9 @@ async function waitForDaemonStatus(controlPort: number, timeoutMs: number): Prom
209
240
  return latest;
210
241
  }
211
242
 
212
- function launchControlUi(controlPort: number): string {
213
- const url = `http://127.0.0.1:${controlPort}/`;
243
+ function launchControlUi(controlPort: number, pathname = "/"): string {
244
+ const normalizedPathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
245
+ const url = `http://127.0.0.1:${controlPort}${normalizedPathname}`;
214
246
  const platform = process.platform;
215
247
  const args: string[] = platform === "darwin" ? [url] : platform === "win32" ? ["/c", "start", "", url] : [url];
216
248
  const cmd = platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
@@ -332,14 +364,27 @@ function runLaunchctl(args: string[], ignoreFailure = false): void {
332
364
  }
333
365
  }
334
366
 
367
+ function launchAgentLoaded(label: string): boolean {
368
+ try {
369
+ runLaunchctl(["print", launchdServiceTarget(label)]);
370
+ return true;
371
+ } catch {
372
+ return false;
373
+ }
374
+ }
375
+
335
376
  function installLaunchAgent(plistPath: string, label: string): void {
336
377
  const domain = launchdUserDomain();
337
378
  for (const staleLabel of STALE_TOKENBUDDY_LAUNCHD_LABELS) {
338
379
  runLaunchctl(["bootout", `${domain}/${staleLabel}`], true);
339
380
  }
340
- runLaunchctl(["bootout", `${domain}/${label}`], true);
381
+ const target = launchdServiceTarget(label);
382
+ if (launchAgentLoaded(label)) {
383
+ runLaunchctl(["kickstart", target]);
384
+ return;
385
+ }
341
386
  runLaunchctl(["bootstrap", domain, plistPath]);
342
- runLaunchctl(["kickstart", "-k", `${domain}/${label}`]);
387
+ runLaunchctl(["kickstart", "-k", target]);
343
388
  }
344
389
 
345
390
  async function repairDaemon(controlPort: number): Promise<{ repair: DaemonRepairResult; probe: DaemonProbeResult }> {
@@ -449,6 +494,185 @@ export async function restartLaunchAgent(
449
494
  };
450
495
  }
451
496
 
497
+ export interface WebInitLauncherResult {
498
+ method: "launchd" | "detached";
499
+ controlPort: number;
500
+ proxyPort: number;
501
+ url: string;
502
+ probe: DaemonProbeResult;
503
+ repair?: DaemonRepairResult;
504
+ plistPath?: string;
505
+ serviceInstalled?: boolean;
506
+ error?: string;
507
+ }
508
+
509
+ interface WebInitStateForLaunch {
510
+ freshMachine?: boolean;
511
+ repairMode?: boolean;
512
+ setup?: {
513
+ status?: string;
514
+ };
515
+ }
516
+
517
+ export interface WebInitLauncherDeps {
518
+ platform?: NodeJS.Platform;
519
+ controlPort?: number;
520
+ proxyPort?: number;
521
+ sellerRegistryUrl?: string;
522
+ homeDir?: string;
523
+ nodePath?: string;
524
+ scriptPath?: string;
525
+ stdoutPath?: string;
526
+ stderrPath?: string;
527
+ clawtipProofCommand?: string;
528
+ clawtipProofTimeoutMs?: number;
529
+ openBrowser?: boolean;
530
+ mkdirSync?(dirPath: string, options: { recursive: boolean }): void;
531
+ writeFileSync?(filePath: string, content: string, encoding: BufferEncoding): void;
532
+ installLaunchAgent?(plistPath: string, label: string): void;
533
+ repairDaemon?(controlPort: number): Promise<{ repair: DaemonRepairResult; probe: DaemonProbeResult }>;
534
+ waitForDaemonStatus?(controlPort: number, timeoutMs: number): Promise<DaemonProbeResult>;
535
+ fetchInitState?(controlPort: number): Promise<WebInitStateForLaunch>;
536
+ launchControlUi?(controlPort: number, pathname?: string): string;
537
+ }
538
+
539
+ async function fetchInitStateForLaunch(controlPort: number): Promise<WebInitStateForLaunch> {
540
+ const res = await fetch(`http://127.0.0.1:${controlPort}/init/state`);
541
+ if (!res.ok) {
542
+ throw new Error(`/init/state returned ${res.status}`);
543
+ }
544
+ return await res.json() as WebInitStateForLaunch;
545
+ }
546
+
547
+ async function resolveWebInitPath(
548
+ controlPort: number,
549
+ fetchInitState: (controlPort: number) => Promise<WebInitStateForLaunch>
550
+ ): Promise<"/init" | "/overview"> {
551
+ try {
552
+ const state = await fetchInitState(controlPort);
553
+ if (state.repairMode === true) {
554
+ return "/init";
555
+ }
556
+ return state.freshMachine === false || state.setup?.status === "completed" ? "/overview" : "/init";
557
+ } catch (error: unknown) {
558
+ logger.warn("init.web.state_probe.failed", "web init state probe failed", {
559
+ errorMessage: error instanceof Error ? error.message : String(error)
560
+ });
561
+ return "/init";
562
+ }
563
+ }
564
+
565
+ export async function runWebInitLauncher(deps: WebInitLauncherDeps = {}): Promise<WebInitLauncherResult> {
566
+ const platform = deps.platform ?? process.platform;
567
+ const controlPort = deps.controlPort ?? configuredControlPort();
568
+ const proxyPort = deps.proxyPort ?? configuredProxyPort();
569
+ const sellerRegistryUrl = deps.sellerRegistryUrl ?? sellerRegistryUrlForInit();
570
+ const home = deps.homeDir ?? os.homedir();
571
+ const launchUi = deps.launchControlUi ?? launchControlUi;
572
+ const fetchInitState = deps.fetchInitState ?? fetchInitStateForLaunch;
573
+ const expectedUrl = `http://127.0.0.1:${controlPort}/init`;
574
+
575
+ if (platform === "darwin") {
576
+ const plistDir = path.join(home, "Library", "LaunchAgents");
577
+ const plistPath = path.join(plistDir, `${TOKENBUDDY_LAUNCHD_LABEL}.plist`);
578
+ const stdoutPath = deps.stdoutPath ?? path.join(home, ".tokenbuddy-store", "tb-proxyd.stdout.log");
579
+ const stderrPath = deps.stderrPath ?? path.join(home, ".tokenbuddy-store", "tb-proxyd.stderr.log");
580
+ try {
581
+ (deps.mkdirSync ?? fs.mkdirSync)(plistDir, { recursive: true });
582
+ (deps.mkdirSync ?? fs.mkdirSync)(path.dirname(stdoutPath), { recursive: true });
583
+ (deps.mkdirSync ?? fs.mkdirSync)(path.dirname(stderrPath), { recursive: true });
584
+ const plistContent = buildLaunchdPlistContent({
585
+ label: TOKENBUDDY_LAUNCHD_LABEL,
586
+ nodePath: deps.nodePath ?? process.execPath,
587
+ scriptPath: deps.scriptPath ?? tbProxydScriptPath(),
588
+ stdoutPath,
589
+ stderrPath,
590
+ controlPort,
591
+ proxyPort,
592
+ sellerRegistryUrl,
593
+ clawtipProofCommand: deps.clawtipProofCommand ?? process.env.TB_PROXYD_CLAWTIP_PROOF_COMMAND,
594
+ clawtipProofTimeoutMs: deps.clawtipProofTimeoutMs ?? (
595
+ process.env.TB_PROXYD_CLAWTIP_PROOF_TIMEOUT_MS
596
+ ? Number(process.env.TB_PROXYD_CLAWTIP_PROOF_TIMEOUT_MS)
597
+ : undefined
598
+ ),
599
+ });
600
+ (deps.writeFileSync ?? fs.writeFileSync)(plistPath, plistContent, "utf8");
601
+ (deps.installLaunchAgent ?? installLaunchAgent)(plistPath, TOKENBUDDY_LAUNCHD_LABEL);
602
+ } catch (error: unknown) {
603
+ const errorMessage = error instanceof Error ? error.message : String(error);
604
+ const probe = await (deps.waitForDaemonStatus ?? waitForDaemonStatus)(controlPort, 3_000);
605
+ if (probe.running) {
606
+ const pathname = await resolveWebInitPath(controlPort, fetchInitState);
607
+ const url = deps.openBrowser !== false
608
+ ? launchUi(controlPort, pathname)
609
+ : `http://127.0.0.1:${controlPort}${pathname}`;
610
+ logger.warn("init.web.launcher.recovered", "web init launchd install failed but service is ready", {
611
+ method: "launchd",
612
+ plistPath,
613
+ errorMessage,
614
+ });
615
+ return {
616
+ method: "launchd",
617
+ controlPort,
618
+ proxyPort,
619
+ plistPath,
620
+ serviceInstalled: true,
621
+ url,
622
+ probe,
623
+ };
624
+ }
625
+ logger.warn("init.web.launcher.failed", "web init launchd install failed", {
626
+ method: "launchd",
627
+ plistPath,
628
+ errorMessage
629
+ });
630
+ return {
631
+ method: "launchd",
632
+ controlPort,
633
+ proxyPort,
634
+ plistPath,
635
+ serviceInstalled: false,
636
+ url: expectedUrl,
637
+ probe: { ...probe, error: probe.error || errorMessage },
638
+ error: errorMessage,
639
+ };
640
+ }
641
+
642
+ const probe = await (deps.waitForDaemonStatus ?? waitForDaemonStatus)(controlPort, 12_000);
643
+ const pathname = probe.running ? await resolveWebInitPath(controlPort, fetchInitState) : "/init";
644
+ const url = probe.running && deps.openBrowser !== false
645
+ ? launchUi(controlPort, pathname)
646
+ : `http://127.0.0.1:${controlPort}${pathname}`;
647
+ return {
648
+ method: "launchd",
649
+ controlPort,
650
+ proxyPort,
651
+ plistPath,
652
+ serviceInstalled: true,
653
+ url,
654
+ probe,
655
+ error: probe.running ? undefined : probe.error || "tb-proxyd did not become ready"
656
+ };
657
+ }
658
+
659
+ const repaired = await (deps.repairDaemon ?? repairDaemon)(controlPort);
660
+ const pathname = repaired.probe.running ? await resolveWebInitPath(controlPort, fetchInitState) : "/init";
661
+ const url = repaired.probe.running && deps.openBrowser !== false
662
+ ? launchUi(controlPort, pathname)
663
+ : `http://127.0.0.1:${controlPort}${pathname}`;
664
+ return {
665
+ method: "detached",
666
+ controlPort,
667
+ proxyPort,
668
+ url,
669
+ probe: repaired.probe,
670
+ repair: repaired.repair,
671
+ serviceInstalled: repaired.probe.running,
672
+ error: repaired.probe.running ? undefined : repaired.repair.error || repaired.probe.error || "tb-proxyd did not become ready"
673
+ };
674
+ }
675
+
452
676
  function commandPath(command: Command): string {
453
677
  const names: string[] = [];
454
678
  let current: Command | null = command;
@@ -932,7 +1156,7 @@ export function buildCli(): Command {
932
1156
  program
933
1157
  .name("tb")
934
1158
  .description("Buyer CLI for TokenBuddy")
935
- .version("1.0.0");
1159
+ .version(readCliPackageVersion());
936
1160
 
937
1161
  program.hook("preAction", async (_thisCommand, actionCommand) => {
938
1162
  await enforceDaemonGate(actionCommand);
@@ -1391,8 +1615,34 @@ export function buildCli(): Command {
1391
1615
  // 4. tb init (WOW terminal guide向导)
1392
1616
  program
1393
1617
  .command("init")
1394
- .description("Launch step-by-step interactive setup wizard")
1395
- .action(async () => {
1618
+ .description("Start TokenBuddy and open the web setup wizard")
1619
+ .option("--terminal", "Run the legacy terminal setup wizard")
1620
+ .action(async (options: { terminal?: boolean }) => {
1621
+ if (!options.terminal) {
1622
+ const result = await runWebInitLauncher();
1623
+ if (result.probe.running) {
1624
+ console.log(`TokenBuddy local service is ready on :${result.controlPort}.`);
1625
+ console.log(`Opening ${result.url} in your default browser…`);
1626
+ console.log("Run `tb init --terminal` if you need the legacy terminal wizard.");
1627
+ return;
1628
+ }
1629
+
1630
+ console.error("TokenBuddy local service did not become ready.");
1631
+ console.error(`Checked: http://127.0.0.1:${result.controlPort}/status`);
1632
+ if (result.method === "launchd" && result.plistPath) {
1633
+ console.error(`LaunchAgent: ${result.plistPath}`);
1634
+ }
1635
+ if (result.error) {
1636
+ console.error(`Reason: ${result.error}`);
1637
+ }
1638
+ console.error("Run `tb doctor --fix` to retry background startup, or `tb init --terminal` for the legacy terminal wizard.");
1639
+ process.exitCode = 1;
1640
+ const error = new Error("TokenBuddy local service did not become ready") as CommandFailure;
1641
+ error.code = "tokenbuddy.init_daemon_not_ready";
1642
+ error.exitCode = 1;
1643
+ throw error;
1644
+ }
1645
+
1396
1646
  p.intro("🚀 Welcome to TokenBuddy Interactive Wizard!");
1397
1647
  const setupSummaryLines: string[] = [];
1398
1648
 
@@ -1765,12 +2015,16 @@ export function buildCli(): Command {
1765
2015
 
1766
2016
  const nodePath = process.execPath;
1767
2017
  const scriptPath = tbProxydScriptPath();
2018
+ const stdoutPath = path.join(home, ".tokenbuddy-store", "tb-proxyd.stdout.log");
2019
+ const stderrPath = path.join(home, ".tokenbuddy-store", "tb-proxyd.stderr.log");
2020
+ fs.mkdirSync(path.dirname(stdoutPath), { recursive: true });
2021
+ fs.mkdirSync(path.dirname(stderrPath), { recursive: true });
1768
2022
  const plistContent = buildLaunchdPlistContent({
1769
2023
  label: TOKENBUDDY_LAUNCHD_LABEL,
1770
2024
  nodePath,
1771
2025
  scriptPath,
1772
- stdoutPath: path.join(home, ".tokenbuddy-store", "tb-proxyd.stdout.log"),
1773
- stderrPath: path.join(home, ".tokenbuddy-store", "tb-proxyd.stderr.log"),
2026
+ stdoutPath,
2027
+ stderrPath,
1774
2028
  controlPort,
1775
2029
  proxyPort,
1776
2030
  sellerRegistryUrl,