@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.
- package/dist/src/cli.d.ts +52 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +178 -8
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +8 -0
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +335 -21
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/init-setup.d.ts +25 -0
- package/dist/src/init-setup.d.ts.map +1 -0
- package/dist/src/init-setup.js +125 -0
- package/dist/src/init-setup.js.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +233 -10
- package/src/daemon.ts +385 -20
- package/src/init-setup.ts +165 -0
- package/static/ui/assets/index-BILQcD8g.js +226 -0
- package/static/ui/assets/index-BILQcD8g.js.map +1 -0
- package/static/ui/assets/index-C5KsebCA.css +1 -0
- package/static/ui/index.html +2 -2
- package/static/ui/manifest.webmanifest +1 -1
- package/static/ui/sw.js +1 -1
- package/tests/control-plane-ui-endpoints.test.ts +193 -1
- package/tests/tokenbuddy.test.ts +209 -0
- package/static/ui/assets/index-UMiTTeo8.css +0 -1
- package/static/ui/assets/index-YHs-Ca0f.js +0 -206
- package/static/ui/assets/index-YHs-Ca0f.js.map +0 -1
|
@@ -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
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
|
|
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
|
-
|
|
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",
|
|
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("
|
|
1395
|
-
.
|
|
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
|
|
1773
|
-
stderrPath
|
|
1995
|
+
stdoutPath,
|
|
1996
|
+
stderrPath,
|
|
1774
1997
|
controlPort,
|
|
1775
1998
|
proxyPort,
|
|
1776
1999
|
sellerRegistryUrl,
|