@tokenbuddy/tokenbuddy 1.0.17 → 1.0.18

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenbuddy/tokenbuddy",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "TokenBuddy Client CLI and Daemon",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
package/src/cli.ts CHANGED
@@ -81,13 +81,13 @@ const logger = createModuleLogger("tokenbuddy-cli");
81
81
  const SUPPORTED_PAYMENT_METHODS = ["mock", "clawtip"] as const;
82
82
  type SupportedPaymentMethod = typeof SUPPORTED_PAYMENT_METHODS[number];
83
83
 
84
- interface DaemonProbeResult {
84
+ export interface DaemonProbeResult {
85
85
  running: boolean;
86
86
  status?: unknown;
87
87
  error?: string;
88
88
  }
89
89
 
90
- interface DaemonRepairResult {
90
+ export interface DaemonRepairResult {
91
91
  attempted: boolean;
92
92
  fixed: boolean;
93
93
  pid?: number;
@@ -209,8 +209,9 @@ async function waitForDaemonStatus(controlPort: number, timeoutMs: number): Prom
209
209
  return latest;
210
210
  }
211
211
 
212
- function launchControlUi(controlPort: number): string {
213
- const url = `http://127.0.0.1:${controlPort}/`;
212
+ function launchControlUi(controlPort: number, pathname = "/"): string {
213
+ const normalizedPathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
214
+ const url = `http://127.0.0.1:${controlPort}${normalizedPathname}`;
214
215
  const platform = process.platform;
215
216
  const args: string[] = platform === "darwin" ? [url] : platform === "win32" ? ["/c", "start", "", url] : [url];
216
217
  const cmd = platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
@@ -332,14 +333,27 @@ function runLaunchctl(args: string[], ignoreFailure = false): void {
332
333
  }
333
334
  }
334
335
 
336
+ function launchAgentLoaded(label: string): boolean {
337
+ try {
338
+ runLaunchctl(["print", launchdServiceTarget(label)]);
339
+ return true;
340
+ } catch {
341
+ return false;
342
+ }
343
+ }
344
+
335
345
  function installLaunchAgent(plistPath: string, label: string): void {
336
346
  const domain = launchdUserDomain();
337
347
  for (const staleLabel of STALE_TOKENBUDDY_LAUNCHD_LABELS) {
338
348
  runLaunchctl(["bootout", `${domain}/${staleLabel}`], true);
339
349
  }
340
- runLaunchctl(["bootout", `${domain}/${label}`], true);
350
+ const target = launchdServiceTarget(label);
351
+ if (launchAgentLoaded(label)) {
352
+ runLaunchctl(["kickstart", target]);
353
+ return;
354
+ }
341
355
  runLaunchctl(["bootstrap", domain, plistPath]);
342
- runLaunchctl(["kickstart", "-k", `${domain}/${label}`]);
356
+ runLaunchctl(["kickstart", "-k", target]);
343
357
  }
344
358
 
345
359
  async function repairDaemon(controlPort: number): Promise<{ repair: DaemonRepairResult; probe: DaemonProbeResult }> {
@@ -449,6 +463,185 @@ export async function restartLaunchAgent(
449
463
  };
450
464
  }
451
465
 
466
+ export interface WebInitLauncherResult {
467
+ method: "launchd" | "detached";
468
+ controlPort: number;
469
+ proxyPort: number;
470
+ url: string;
471
+ probe: DaemonProbeResult;
472
+ repair?: DaemonRepairResult;
473
+ plistPath?: string;
474
+ serviceInstalled?: boolean;
475
+ error?: string;
476
+ }
477
+
478
+ interface WebInitStateForLaunch {
479
+ freshMachine?: boolean;
480
+ repairMode?: boolean;
481
+ setup?: {
482
+ status?: string;
483
+ };
484
+ }
485
+
486
+ export interface WebInitLauncherDeps {
487
+ platform?: NodeJS.Platform;
488
+ controlPort?: number;
489
+ proxyPort?: number;
490
+ sellerRegistryUrl?: string;
491
+ homeDir?: string;
492
+ nodePath?: string;
493
+ scriptPath?: string;
494
+ stdoutPath?: string;
495
+ stderrPath?: string;
496
+ clawtipProofCommand?: string;
497
+ clawtipProofTimeoutMs?: number;
498
+ openBrowser?: boolean;
499
+ mkdirSync?(dirPath: string, options: { recursive: boolean }): void;
500
+ writeFileSync?(filePath: string, content: string, encoding: BufferEncoding): void;
501
+ installLaunchAgent?(plistPath: string, label: string): void;
502
+ repairDaemon?(controlPort: number): Promise<{ repair: DaemonRepairResult; probe: DaemonProbeResult }>;
503
+ waitForDaemonStatus?(controlPort: number, timeoutMs: number): Promise<DaemonProbeResult>;
504
+ fetchInitState?(controlPort: number): Promise<WebInitStateForLaunch>;
505
+ launchControlUi?(controlPort: number, pathname?: string): string;
506
+ }
507
+
508
+ async function fetchInitStateForLaunch(controlPort: number): Promise<WebInitStateForLaunch> {
509
+ const res = await fetch(`http://127.0.0.1:${controlPort}/init/state`);
510
+ if (!res.ok) {
511
+ throw new Error(`/init/state returned ${res.status}`);
512
+ }
513
+ return await res.json() as WebInitStateForLaunch;
514
+ }
515
+
516
+ async function resolveWebInitPath(
517
+ controlPort: number,
518
+ fetchInitState: (controlPort: number) => Promise<WebInitStateForLaunch>
519
+ ): Promise<"/init" | "/overview"> {
520
+ try {
521
+ const state = await fetchInitState(controlPort);
522
+ if (state.repairMode === true) {
523
+ return "/init";
524
+ }
525
+ return state.freshMachine === false || state.setup?.status === "completed" ? "/overview" : "/init";
526
+ } catch (error: unknown) {
527
+ logger.warn("init.web.state_probe.failed", "web init state probe failed", {
528
+ errorMessage: error instanceof Error ? error.message : String(error)
529
+ });
530
+ return "/init";
531
+ }
532
+ }
533
+
534
+ export async function runWebInitLauncher(deps: WebInitLauncherDeps = {}): Promise<WebInitLauncherResult> {
535
+ const platform = deps.platform ?? process.platform;
536
+ const controlPort = deps.controlPort ?? configuredControlPort();
537
+ const proxyPort = deps.proxyPort ?? configuredProxyPort();
538
+ const sellerRegistryUrl = deps.sellerRegistryUrl ?? sellerRegistryUrlForInit();
539
+ const home = deps.homeDir ?? os.homedir();
540
+ const launchUi = deps.launchControlUi ?? launchControlUi;
541
+ const fetchInitState = deps.fetchInitState ?? fetchInitStateForLaunch;
542
+ const expectedUrl = `http://127.0.0.1:${controlPort}/init`;
543
+
544
+ if (platform === "darwin") {
545
+ const plistDir = path.join(home, "Library", "LaunchAgents");
546
+ const plistPath = path.join(plistDir, `${TOKENBUDDY_LAUNCHD_LABEL}.plist`);
547
+ const stdoutPath = deps.stdoutPath ?? path.join(home, ".tokenbuddy-store", "tb-proxyd.stdout.log");
548
+ const stderrPath = deps.stderrPath ?? path.join(home, ".tokenbuddy-store", "tb-proxyd.stderr.log");
549
+ try {
550
+ (deps.mkdirSync ?? fs.mkdirSync)(plistDir, { recursive: true });
551
+ (deps.mkdirSync ?? fs.mkdirSync)(path.dirname(stdoutPath), { recursive: true });
552
+ (deps.mkdirSync ?? fs.mkdirSync)(path.dirname(stderrPath), { recursive: true });
553
+ const plistContent = buildLaunchdPlistContent({
554
+ label: TOKENBUDDY_LAUNCHD_LABEL,
555
+ nodePath: deps.nodePath ?? process.execPath,
556
+ scriptPath: deps.scriptPath ?? tbProxydScriptPath(),
557
+ stdoutPath,
558
+ stderrPath,
559
+ controlPort,
560
+ proxyPort,
561
+ sellerRegistryUrl,
562
+ clawtipProofCommand: deps.clawtipProofCommand ?? process.env.TB_PROXYD_CLAWTIP_PROOF_COMMAND,
563
+ clawtipProofTimeoutMs: deps.clawtipProofTimeoutMs ?? (
564
+ process.env.TB_PROXYD_CLAWTIP_PROOF_TIMEOUT_MS
565
+ ? Number(process.env.TB_PROXYD_CLAWTIP_PROOF_TIMEOUT_MS)
566
+ : undefined
567
+ ),
568
+ });
569
+ (deps.writeFileSync ?? fs.writeFileSync)(plistPath, plistContent, "utf8");
570
+ (deps.installLaunchAgent ?? installLaunchAgent)(plistPath, TOKENBUDDY_LAUNCHD_LABEL);
571
+ } catch (error: unknown) {
572
+ const errorMessage = error instanceof Error ? error.message : String(error);
573
+ const probe = await (deps.waitForDaemonStatus ?? waitForDaemonStatus)(controlPort, 3_000);
574
+ if (probe.running) {
575
+ const pathname = await resolveWebInitPath(controlPort, fetchInitState);
576
+ const url = deps.openBrowser !== false
577
+ ? launchUi(controlPort, pathname)
578
+ : `http://127.0.0.1:${controlPort}${pathname}`;
579
+ logger.warn("init.web.launcher.recovered", "web init launchd install failed but service is ready", {
580
+ method: "launchd",
581
+ plistPath,
582
+ errorMessage,
583
+ });
584
+ return {
585
+ method: "launchd",
586
+ controlPort,
587
+ proxyPort,
588
+ plistPath,
589
+ serviceInstalled: true,
590
+ url,
591
+ probe,
592
+ };
593
+ }
594
+ logger.warn("init.web.launcher.failed", "web init launchd install failed", {
595
+ method: "launchd",
596
+ plistPath,
597
+ errorMessage
598
+ });
599
+ return {
600
+ method: "launchd",
601
+ controlPort,
602
+ proxyPort,
603
+ plistPath,
604
+ serviceInstalled: false,
605
+ url: expectedUrl,
606
+ probe: { ...probe, error: probe.error || errorMessage },
607
+ error: errorMessage,
608
+ };
609
+ }
610
+
611
+ const probe = await (deps.waitForDaemonStatus ?? waitForDaemonStatus)(controlPort, 12_000);
612
+ const pathname = probe.running ? await resolveWebInitPath(controlPort, fetchInitState) : "/init";
613
+ const url = probe.running && deps.openBrowser !== false
614
+ ? launchUi(controlPort, pathname)
615
+ : `http://127.0.0.1:${controlPort}${pathname}`;
616
+ return {
617
+ method: "launchd",
618
+ controlPort,
619
+ proxyPort,
620
+ plistPath,
621
+ serviceInstalled: true,
622
+ url,
623
+ probe,
624
+ error: probe.running ? undefined : probe.error || "tb-proxyd did not become ready"
625
+ };
626
+ }
627
+
628
+ const repaired = await (deps.repairDaemon ?? repairDaemon)(controlPort);
629
+ const pathname = repaired.probe.running ? await resolveWebInitPath(controlPort, fetchInitState) : "/init";
630
+ const url = repaired.probe.running && deps.openBrowser !== false
631
+ ? launchUi(controlPort, pathname)
632
+ : `http://127.0.0.1:${controlPort}${pathname}`;
633
+ return {
634
+ method: "detached",
635
+ controlPort,
636
+ proxyPort,
637
+ url,
638
+ probe: repaired.probe,
639
+ repair: repaired.repair,
640
+ serviceInstalled: repaired.probe.running,
641
+ error: repaired.probe.running ? undefined : repaired.repair.error || repaired.probe.error || "tb-proxyd did not become ready"
642
+ };
643
+ }
644
+
452
645
  function commandPath(command: Command): string {
453
646
  const names: string[] = [];
454
647
  let current: Command | null = command;
@@ -1391,8 +1584,34 @@ export function buildCli(): Command {
1391
1584
  // 4. tb init (WOW terminal guide向导)
1392
1585
  program
1393
1586
  .command("init")
1394
- .description("Launch step-by-step interactive setup wizard")
1395
- .action(async () => {
1587
+ .description("Start TokenBuddy and open the web setup wizard")
1588
+ .option("--terminal", "Run the legacy terminal setup wizard")
1589
+ .action(async (options: { terminal?: boolean }) => {
1590
+ if (!options.terminal) {
1591
+ const result = await runWebInitLauncher();
1592
+ if (result.probe.running) {
1593
+ console.log(`TokenBuddy local service is ready on :${result.controlPort}.`);
1594
+ console.log(`Opening ${result.url} in your default browser…`);
1595
+ console.log("Run `tb init --terminal` if you need the legacy terminal wizard.");
1596
+ return;
1597
+ }
1598
+
1599
+ console.error("TokenBuddy local service did not become ready.");
1600
+ console.error(`Checked: http://127.0.0.1:${result.controlPort}/status`);
1601
+ if (result.method === "launchd" && result.plistPath) {
1602
+ console.error(`LaunchAgent: ${result.plistPath}`);
1603
+ }
1604
+ if (result.error) {
1605
+ console.error(`Reason: ${result.error}`);
1606
+ }
1607
+ console.error("Run `tb doctor --fix` to retry background startup, or `tb init --terminal` for the legacy terminal wizard.");
1608
+ process.exitCode = 1;
1609
+ const error = new Error("TokenBuddy local service did not become ready") as CommandFailure;
1610
+ error.code = "tokenbuddy.init_daemon_not_ready";
1611
+ error.exitCode = 1;
1612
+ throw error;
1613
+ }
1614
+
1396
1615
  p.intro("🚀 Welcome to TokenBuddy Interactive Wizard!");
1397
1616
  const setupSummaryLines: string[] = [];
1398
1617
 
@@ -1765,12 +1984,16 @@ export function buildCli(): Command {
1765
1984
 
1766
1985
  const nodePath = process.execPath;
1767
1986
  const scriptPath = tbProxydScriptPath();
1987
+ const stdoutPath = path.join(home, ".tokenbuddy-store", "tb-proxyd.stdout.log");
1988
+ const stderrPath = path.join(home, ".tokenbuddy-store", "tb-proxyd.stderr.log");
1989
+ fs.mkdirSync(path.dirname(stdoutPath), { recursive: true });
1990
+ fs.mkdirSync(path.dirname(stderrPath), { recursive: true });
1768
1991
  const plistContent = buildLaunchdPlistContent({
1769
1992
  label: TOKENBUDDY_LAUNCHD_LABEL,
1770
1993
  nodePath,
1771
1994
  scriptPath,
1772
- stdoutPath: path.join(home, ".tokenbuddy-store", "tb-proxyd.stdout.log"),
1773
- stderrPath: path.join(home, ".tokenbuddy-store", "tb-proxyd.stderr.log"),
1995
+ stdoutPath,
1996
+ stderrPath,
1774
1997
  controlPort,
1775
1998
  proxyPort,
1776
1999
  sellerRegistryUrl,