@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.
- package/dist/src/cli.d.ts +52 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +212 -9
- 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 +3 -3
- package/src/cli.ts +265 -11
- 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 +218 -2
- 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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenbuddy/tokenbuddy",
|
|
3
|
-
"version": "1.0.
|
|
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": "
|
|
10
|
-
"tb-proxyd": "
|
|
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
|
|
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
|
-
|
|
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",
|
|
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(
|
|
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("
|
|
1395
|
-
.
|
|
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
|
|
1773
|
-
stderrPath
|
|
2026
|
+
stdoutPath,
|
|
2027
|
+
stderrPath,
|
|
1774
2028
|
controlPort,
|
|
1775
2029
|
proxyPort,
|
|
1776
2030
|
sellerRegistryUrl,
|