bosun 0.41.7 → 0.41.9
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/README.md +23 -1
- package/agent/agent-event-bus.mjs +31 -2
- package/agent/agent-pool.mjs +251 -11
- package/agent/agent-prompts.mjs +5 -1
- package/agent/agent-supervisor.mjs +22 -0
- package/agent/primary-agent.mjs +115 -5
- package/cli.mjs +3 -2
- package/config/config.mjs +4 -1
- package/desktop/main.mjs +350 -25
- package/desktop/preload.cjs +8 -0
- package/desktop/preload.mjs +19 -0
- package/entrypoint.mjs +332 -0
- package/infra/health-status.mjs +72 -0
- package/infra/library-manager.mjs +58 -1
- package/infra/maintenance.mjs +1 -2
- package/infra/monitor.mjs +25 -7
- package/infra/session-tracker.mjs +30 -3
- package/package.json +10 -4
- package/server/bosun-mcp-server.mjs +1004 -0
- package/server/setup-web-server.mjs +287 -258
- package/server/ui-server.mjs +218 -23
- package/shell/claude-shell.mjs +14 -1
- package/shell/codex-model-profiles.mjs +166 -29
- package/shell/codex-shell.mjs +56 -18
- package/shell/opencode-providers.mjs +20 -8
- package/task/task-executor.mjs +28 -0
- package/task/task-store.mjs +13 -4
- package/tools/list-todos.mjs +7 -1
- package/ui/app.js +3 -2
- package/ui/components/agent-selector.js +127 -0
- package/ui/components/session-list.js +2 -0
- package/ui/demo-defaults.js +6 -6
- package/ui/modules/router.js +2 -0
- package/ui/modules/state.js +13 -5
- package/ui/tabs/chat.js +3 -0
- package/ui/tabs/library.js +284 -52
- package/ui/tabs/tasks.js +5 -13
- package/workflow/workflow-engine.mjs +16 -4
- package/workflow/workflow-nodes/definitions.mjs +37 -0
- package/workflow/workflow-nodes.mjs +489 -153
- package/workflow/workflow-templates.mjs +0 -5
- package/workflow-templates/github.mjs +106 -16
- package/workspace/worktree-manager.mjs +1 -1
|
@@ -69,11 +69,16 @@ function buildModelsProbeRequest({ apiKey = "", baseUrl = "" } = {}) {
|
|
|
69
69
|
return { endpoint: parsed.toString(), headers };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
if (isAzure && (lowerPath === "/openai/v1" || lowerPath.startsWith("/openai/v1/"))) {
|
|
73
|
+
parsed.pathname = "/openai/v1/models";
|
|
74
|
+
parsed.search = "";
|
|
75
|
+
return { endpoint: parsed.toString(), headers };
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
if (isAzure || lowerPath === "/openai" || lowerPath.startsWith("/openai/")) {
|
|
73
79
|
parsed.pathname = "/openai/models";
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
80
|
+
parsed.search = "";
|
|
81
|
+
parsed.searchParams.set("api-version", "2024-10-21");
|
|
77
82
|
return { endpoint: parsed.toString(), headers };
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -2438,6 +2443,281 @@ function handleApply(body) {
|
|
|
2438
2443
|
|
|
2439
2444
|
// ── Server ───────────────────────────────────────────────────────────────────
|
|
2440
2445
|
|
|
2446
|
+
/**
|
|
2447
|
+
* Handle /api/setup/* routes.
|
|
2448
|
+
* Reusable from both the standalone setup server and the unified ui-server.
|
|
2449
|
+
*
|
|
2450
|
+
* @param {import("node:http").IncomingMessage} req
|
|
2451
|
+
* @param {import("node:http").ServerResponse} res
|
|
2452
|
+
* @param {URL} url
|
|
2453
|
+
* @param {object} [options]
|
|
2454
|
+
* @param {() => void} [options.onComplete] - callback for unified mode (instead of process.exit)
|
|
2455
|
+
* @returns {Promise<boolean>} true if the route was handled
|
|
2456
|
+
*/
|
|
2457
|
+
async function handleSetupApi(req, res, url, options = {}) {
|
|
2458
|
+
if (!url.pathname.startsWith("/api/setup/")) return false;
|
|
2459
|
+
|
|
2460
|
+
const route = url.pathname.replace("/api/setup/", "");
|
|
2461
|
+
|
|
2462
|
+
try {
|
|
2463
|
+
switch (route) {
|
|
2464
|
+
case "status":
|
|
2465
|
+
jsonResponse(res, 200, await handleStatus());
|
|
2466
|
+
return true;
|
|
2467
|
+
case "vendor-status":
|
|
2468
|
+
jsonResponse(res, 200, checkVendorFiles());
|
|
2469
|
+
return true;
|
|
2470
|
+
case "prerequisites":
|
|
2471
|
+
jsonResponse(res, 200, handlePrerequisites());
|
|
2472
|
+
return true;
|
|
2473
|
+
case "defaults":
|
|
2474
|
+
jsonResponse(res, 200, handleDefaults());
|
|
2475
|
+
return true;
|
|
2476
|
+
case "models":
|
|
2477
|
+
jsonResponse(res, 200, handleModels());
|
|
2478
|
+
return true;
|
|
2479
|
+
case "models/probe":
|
|
2480
|
+
if (req.method !== "POST") {
|
|
2481
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2482
|
+
return true;
|
|
2483
|
+
}
|
|
2484
|
+
jsonResponse(res, 200, await handleModelsProbe(await readBody(req)));
|
|
2485
|
+
return true;
|
|
2486
|
+
case "executors":
|
|
2487
|
+
jsonResponse(res, 200, handleExecutors());
|
|
2488
|
+
return true;
|
|
2489
|
+
case "workflows":
|
|
2490
|
+
jsonResponse(res, 200, handleWorkflowTemplates());
|
|
2491
|
+
return true;
|
|
2492
|
+
case "voice/endpoints/test":
|
|
2493
|
+
if (req.method !== "POST") {
|
|
2494
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2495
|
+
return true;
|
|
2496
|
+
}
|
|
2497
|
+
jsonResponse(res, 200, await handleVoiceEndpointTest(await readBody(req)));
|
|
2498
|
+
return true;
|
|
2499
|
+
|
|
2500
|
+
// ── Voice OAuth auth routes ─────────────────────────────────────────
|
|
2501
|
+
case "voice/auth/openai/status":
|
|
2502
|
+
case "voice/auth/claude/status":
|
|
2503
|
+
case "voice/auth/gemini/status": {
|
|
2504
|
+
const provider = route.split("/")[2]; // openai | claude | gemini
|
|
2505
|
+
try {
|
|
2506
|
+
const statusFns = {
|
|
2507
|
+
openai: "getOpenAILoginStatus",
|
|
2508
|
+
claude: "getClaudeLoginStatus",
|
|
2509
|
+
gemini: "getGeminiLoginStatus",
|
|
2510
|
+
};
|
|
2511
|
+
const mod = await import("../voice/voice-auth-manager.mjs");
|
|
2512
|
+
const fn = mod[statusFns[provider]];
|
|
2513
|
+
if (!fn) throw new Error(`No status function for ${provider}`);
|
|
2514
|
+
jsonResponse(res, 200, { ok: true, ...fn() });
|
|
2515
|
+
} catch (err) {
|
|
2516
|
+
jsonResponse(res, 200, { ok: true, status: "idle", hasToken: false, error: err.message });
|
|
2517
|
+
}
|
|
2518
|
+
return true;
|
|
2519
|
+
}
|
|
2520
|
+
case "voice/auth/openai/login":
|
|
2521
|
+
case "voice/auth/claude/login":
|
|
2522
|
+
case "voice/auth/gemini/login": {
|
|
2523
|
+
if (req.method !== "POST") {
|
|
2524
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2525
|
+
return true;
|
|
2526
|
+
}
|
|
2527
|
+
const provider = route.split("/")[2];
|
|
2528
|
+
try {
|
|
2529
|
+
const loginFns = {
|
|
2530
|
+
openai: "startOpenAICodexLogin",
|
|
2531
|
+
claude: "startClaudeLogin",
|
|
2532
|
+
gemini: "startGeminiLogin",
|
|
2533
|
+
};
|
|
2534
|
+
const mod = await import("../voice/voice-auth-manager.mjs");
|
|
2535
|
+
const fn = mod[loginFns[provider]];
|
|
2536
|
+
if (!fn) throw new Error(`No login function for ${provider}`);
|
|
2537
|
+
const result = fn({ openBrowser: false });
|
|
2538
|
+
jsonResponse(res, 200, { ok: true, ...(result || {}) });
|
|
2539
|
+
} catch (err) {
|
|
2540
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2541
|
+
}
|
|
2542
|
+
return true;
|
|
2543
|
+
}
|
|
2544
|
+
case "voice/auth/openai/logout":
|
|
2545
|
+
case "voice/auth/claude/logout":
|
|
2546
|
+
case "voice/auth/gemini/logout": {
|
|
2547
|
+
if (req.method !== "POST") {
|
|
2548
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2549
|
+
return true;
|
|
2550
|
+
}
|
|
2551
|
+
const provider = route.split("/")[2];
|
|
2552
|
+
try {
|
|
2553
|
+
const logoutFns = {
|
|
2554
|
+
openai: "logoutOpenAI",
|
|
2555
|
+
claude: "logoutClaude",
|
|
2556
|
+
gemini: "logoutGemini",
|
|
2557
|
+
};
|
|
2558
|
+
const mod = await import("../voice/voice-auth-manager.mjs");
|
|
2559
|
+
const fn = mod[logoutFns[provider]];
|
|
2560
|
+
if (!fn) throw new Error(`No logout function for ${provider}`);
|
|
2561
|
+
const result = fn();
|
|
2562
|
+
jsonResponse(res, 200, { ok: true, ...(result || {}) });
|
|
2563
|
+
} catch (err) {
|
|
2564
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2565
|
+
}
|
|
2566
|
+
return true;
|
|
2567
|
+
}
|
|
2568
|
+
case "voice/auth/openai/cancel":
|
|
2569
|
+
case "voice/auth/claude/cancel":
|
|
2570
|
+
case "voice/auth/gemini/cancel": {
|
|
2571
|
+
if (req.method !== "POST") {
|
|
2572
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2573
|
+
return true;
|
|
2574
|
+
}
|
|
2575
|
+
const provider = route.split("/")[2];
|
|
2576
|
+
try {
|
|
2577
|
+
const cancelFns = {
|
|
2578
|
+
openai: "cancelOpenAILogin",
|
|
2579
|
+
claude: "cancelClaudeLogin",
|
|
2580
|
+
gemini: "cancelGeminiLogin",
|
|
2581
|
+
};
|
|
2582
|
+
const mod = await import("../voice/voice-auth-manager.mjs");
|
|
2583
|
+
const fn = mod[cancelFns[provider]];
|
|
2584
|
+
if (!fn) throw new Error(`No cancel function for ${provider}`);
|
|
2585
|
+
fn();
|
|
2586
|
+
jsonResponse(res, 200, { ok: true });
|
|
2587
|
+
} catch (err) {
|
|
2588
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2589
|
+
}
|
|
2590
|
+
return true;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
case "validate":
|
|
2594
|
+
if (req.method !== "POST") {
|
|
2595
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2596
|
+
return true;
|
|
2597
|
+
}
|
|
2598
|
+
jsonResponse(res, 200, handleValidate(await readBody(req)));
|
|
2599
|
+
return true;
|
|
2600
|
+
case "telegram-chat-id": {
|
|
2601
|
+
if (req.method !== "POST") {
|
|
2602
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2603
|
+
return true;
|
|
2604
|
+
}
|
|
2605
|
+
const result = await handleTelegramChatIdLookup(await readBody(req));
|
|
2606
|
+
jsonResponse(res, result.status, result);
|
|
2607
|
+
return true;
|
|
2608
|
+
}
|
|
2609
|
+
case "apply":
|
|
2610
|
+
if (req.method !== "POST") {
|
|
2611
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2612
|
+
return true;
|
|
2613
|
+
}
|
|
2614
|
+
jsonResponse(res, 200, handleApply(await readBody(req)));
|
|
2615
|
+
return true;
|
|
2616
|
+
case "install-startup": {
|
|
2617
|
+
if (req.method !== "POST") {
|
|
2618
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2619
|
+
return true;
|
|
2620
|
+
}
|
|
2621
|
+
try {
|
|
2622
|
+
const body_ = await readBody(req);
|
|
2623
|
+
const { installStartupService } = await import("../infra/startup-service.mjs");
|
|
2624
|
+
const result = await installStartupService({ daemon: body_?.daemon !== false });
|
|
2625
|
+
jsonResponse(res, 200, { ok: true, ...result });
|
|
2626
|
+
} catch (err) {
|
|
2627
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2628
|
+
}
|
|
2629
|
+
return true;
|
|
2630
|
+
}
|
|
2631
|
+
case "remove-startup": {
|
|
2632
|
+
if (req.method !== "POST") {
|
|
2633
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2634
|
+
return true;
|
|
2635
|
+
}
|
|
2636
|
+
try {
|
|
2637
|
+
const { removeStartupService } = await import("../infra/startup-service.mjs");
|
|
2638
|
+
const result = await removeStartupService();
|
|
2639
|
+
jsonResponse(res, 200, { ok: true, ...result });
|
|
2640
|
+
} catch (err) {
|
|
2641
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2642
|
+
}
|
|
2643
|
+
return true;
|
|
2644
|
+
}
|
|
2645
|
+
case "install-desktop-shortcut": {
|
|
2646
|
+
if (req.method !== "POST") {
|
|
2647
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2648
|
+
return true;
|
|
2649
|
+
}
|
|
2650
|
+
try {
|
|
2651
|
+
const { installDesktopShortcut } = await import("../infra/desktop-shortcut.mjs");
|
|
2652
|
+
const result = installDesktopShortcut();
|
|
2653
|
+
jsonResponse(res, 200, { ok: true, ...result });
|
|
2654
|
+
} catch (err) {
|
|
2655
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2656
|
+
}
|
|
2657
|
+
return true;
|
|
2658
|
+
}
|
|
2659
|
+
case "remove-desktop-shortcut": {
|
|
2660
|
+
if (req.method !== "POST") {
|
|
2661
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2662
|
+
return true;
|
|
2663
|
+
}
|
|
2664
|
+
try {
|
|
2665
|
+
const { removeDesktopShortcut } = await import("../infra/desktop-shortcut.mjs");
|
|
2666
|
+
const result = removeDesktopShortcut();
|
|
2667
|
+
jsonResponse(res, 200, { ok: true, ...result });
|
|
2668
|
+
} catch (err) {
|
|
2669
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2670
|
+
}
|
|
2671
|
+
return true;
|
|
2672
|
+
}
|
|
2673
|
+
case "complete":
|
|
2674
|
+
if (req.method !== "POST") {
|
|
2675
|
+
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2676
|
+
return true;
|
|
2677
|
+
}
|
|
2678
|
+
jsonResponse(res, 200, { ok: true, message: "Setup complete" });
|
|
2679
|
+
if (options.onComplete) {
|
|
2680
|
+
// Unified mode: signal completion instead of exiting
|
|
2681
|
+
setTimeout(() => options.onComplete(), 500);
|
|
2682
|
+
} else {
|
|
2683
|
+
// Standalone mode: shut down setup server
|
|
2684
|
+
setTimeout(() => {
|
|
2685
|
+
console.log("\n :check: Setup complete — shutting down wizard server.\n");
|
|
2686
|
+
if (callbackServer) callbackServer.close();
|
|
2687
|
+
server.close();
|
|
2688
|
+
process.exit(0);
|
|
2689
|
+
}, 500);
|
|
2690
|
+
}
|
|
2691
|
+
return true;
|
|
2692
|
+
case "oauth-state": {
|
|
2693
|
+
// The setup wizard polls this to detect when the GitHub OAuth callback
|
|
2694
|
+
// has been received (possibly on a different port).
|
|
2695
|
+
const pendingPath = oauthPendingPath();
|
|
2696
|
+
if (existsSync(pendingPath)) {
|
|
2697
|
+
try {
|
|
2698
|
+
const raw = readFileSync(pendingPath, "utf8");
|
|
2699
|
+
const data = JSON.parse(raw);
|
|
2700
|
+
// Delete the file so it's only claimed once
|
|
2701
|
+
try { unlinkSync(pendingPath); } catch { /* ignore */ }
|
|
2702
|
+
jsonResponse(res, 200, { ok: true, pending: true, ...data });
|
|
2703
|
+
} catch {
|
|
2704
|
+
jsonResponse(res, 200, { ok: true, pending: false });
|
|
2705
|
+
}
|
|
2706
|
+
} else {
|
|
2707
|
+
jsonResponse(res, 200, { ok: true, pending: false });
|
|
2708
|
+
}
|
|
2709
|
+
return true;
|
|
2710
|
+
}
|
|
2711
|
+
default:
|
|
2712
|
+
jsonResponse(res, 404, { ok: false, error: `Unknown route: ${route}` });
|
|
2713
|
+
return true;
|
|
2714
|
+
}
|
|
2715
|
+
} catch (err) {
|
|
2716
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2717
|
+
return true;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2441
2721
|
async function handleRequest(req, res) {
|
|
2442
2722
|
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
2443
2723
|
|
|
@@ -2503,262 +2783,10 @@ async function handleRequest(req, res) {
|
|
|
2503
2783
|
return;
|
|
2504
2784
|
}
|
|
2505
2785
|
|
|
2506
|
-
// API routes
|
|
2786
|
+
// API routes — delegate to the shared handler (standalone mode, no onComplete callback)
|
|
2507
2787
|
if (url.pathname.startsWith("/api/setup/")) {
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
try {
|
|
2511
|
-
switch (route) {
|
|
2512
|
-
case "status":
|
|
2513
|
-
jsonResponse(res, 200, await handleStatus());
|
|
2514
|
-
return;
|
|
2515
|
-
case "vendor-status":
|
|
2516
|
-
jsonResponse(res, 200, checkVendorFiles());
|
|
2517
|
-
return;
|
|
2518
|
-
case "prerequisites":
|
|
2519
|
-
jsonResponse(res, 200, handlePrerequisites());
|
|
2520
|
-
return;
|
|
2521
|
-
case "defaults":
|
|
2522
|
-
jsonResponse(res, 200, handleDefaults());
|
|
2523
|
-
return;
|
|
2524
|
-
case "models":
|
|
2525
|
-
jsonResponse(res, 200, handleModels());
|
|
2526
|
-
return;
|
|
2527
|
-
case "models/probe":
|
|
2528
|
-
if (req.method !== "POST") {
|
|
2529
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2530
|
-
return;
|
|
2531
|
-
}
|
|
2532
|
-
jsonResponse(res, 200, await handleModelsProbe(await readBody(req)));
|
|
2533
|
-
return;
|
|
2534
|
-
case "executors":
|
|
2535
|
-
jsonResponse(res, 200, handleExecutors());
|
|
2536
|
-
return;
|
|
2537
|
-
case "workflows":
|
|
2538
|
-
jsonResponse(res, 200, handleWorkflowTemplates());
|
|
2539
|
-
return;
|
|
2540
|
-
case "voice/endpoints/test":
|
|
2541
|
-
if (req.method !== "POST") {
|
|
2542
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2543
|
-
return;
|
|
2544
|
-
}
|
|
2545
|
-
jsonResponse(res, 200, await handleVoiceEndpointTest(await readBody(req)));
|
|
2546
|
-
return;
|
|
2547
|
-
|
|
2548
|
-
// ── Voice OAuth auth routes ─────────────────────────────────────────
|
|
2549
|
-
case "voice/auth/openai/status":
|
|
2550
|
-
case "voice/auth/claude/status":
|
|
2551
|
-
case "voice/auth/gemini/status": {
|
|
2552
|
-
const provider = route.split("/")[2]; // openai | claude | gemini
|
|
2553
|
-
try {
|
|
2554
|
-
const statusFns = {
|
|
2555
|
-
openai: "getOpenAILoginStatus",
|
|
2556
|
-
claude: "getClaudeLoginStatus",
|
|
2557
|
-
gemini: "getGeminiLoginStatus",
|
|
2558
|
-
};
|
|
2559
|
-
const mod = await import("../voice/voice-auth-manager.mjs");
|
|
2560
|
-
const fn = mod[statusFns[provider]];
|
|
2561
|
-
if (!fn) throw new Error(`No status function for ${provider}`);
|
|
2562
|
-
jsonResponse(res, 200, { ok: true, ...fn() });
|
|
2563
|
-
} catch (err) {
|
|
2564
|
-
jsonResponse(res, 200, { ok: true, status: "idle", hasToken: false, error: err.message });
|
|
2565
|
-
}
|
|
2566
|
-
return;
|
|
2567
|
-
}
|
|
2568
|
-
case "voice/auth/openai/login":
|
|
2569
|
-
case "voice/auth/claude/login":
|
|
2570
|
-
case "voice/auth/gemini/login": {
|
|
2571
|
-
if (req.method !== "POST") {
|
|
2572
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2573
|
-
return;
|
|
2574
|
-
}
|
|
2575
|
-
const provider = route.split("/")[2];
|
|
2576
|
-
try {
|
|
2577
|
-
const loginFns = {
|
|
2578
|
-
openai: "startOpenAICodexLogin",
|
|
2579
|
-
claude: "startClaudeLogin",
|
|
2580
|
-
gemini: "startGeminiLogin",
|
|
2581
|
-
};
|
|
2582
|
-
const mod = await import("../voice/voice-auth-manager.mjs");
|
|
2583
|
-
const fn = mod[loginFns[provider]];
|
|
2584
|
-
if (!fn) throw new Error(`No login function for ${provider}`);
|
|
2585
|
-
const result = fn({ openBrowser: false });
|
|
2586
|
-
jsonResponse(res, 200, { ok: true, ...(result || {}) });
|
|
2587
|
-
} catch (err) {
|
|
2588
|
-
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2589
|
-
}
|
|
2590
|
-
return;
|
|
2591
|
-
}
|
|
2592
|
-
case "voice/auth/openai/logout":
|
|
2593
|
-
case "voice/auth/claude/logout":
|
|
2594
|
-
case "voice/auth/gemini/logout": {
|
|
2595
|
-
if (req.method !== "POST") {
|
|
2596
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2597
|
-
return;
|
|
2598
|
-
}
|
|
2599
|
-
const provider = route.split("/")[2];
|
|
2600
|
-
try {
|
|
2601
|
-
const logoutFns = {
|
|
2602
|
-
openai: "logoutOpenAI",
|
|
2603
|
-
claude: "logoutClaude",
|
|
2604
|
-
gemini: "logoutGemini",
|
|
2605
|
-
};
|
|
2606
|
-
const mod = await import("../voice/voice-auth-manager.mjs");
|
|
2607
|
-
const fn = mod[logoutFns[provider]];
|
|
2608
|
-
if (!fn) throw new Error(`No logout function for ${provider}`);
|
|
2609
|
-
const result = fn();
|
|
2610
|
-
jsonResponse(res, 200, { ok: true, ...(result || {}) });
|
|
2611
|
-
} catch (err) {
|
|
2612
|
-
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2613
|
-
}
|
|
2614
|
-
return;
|
|
2615
|
-
}
|
|
2616
|
-
case "voice/auth/openai/cancel":
|
|
2617
|
-
case "voice/auth/claude/cancel":
|
|
2618
|
-
case "voice/auth/gemini/cancel": {
|
|
2619
|
-
if (req.method !== "POST") {
|
|
2620
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2621
|
-
return;
|
|
2622
|
-
}
|
|
2623
|
-
const provider = route.split("/")[2];
|
|
2624
|
-
try {
|
|
2625
|
-
const cancelFns = {
|
|
2626
|
-
openai: "cancelOpenAILogin",
|
|
2627
|
-
claude: "cancelClaudeLogin",
|
|
2628
|
-
gemini: "cancelGeminiLogin",
|
|
2629
|
-
};
|
|
2630
|
-
const mod = await import("../voice/voice-auth-manager.mjs");
|
|
2631
|
-
const fn = mod[cancelFns[provider]];
|
|
2632
|
-
if (!fn) throw new Error(`No cancel function for ${provider}`);
|
|
2633
|
-
fn();
|
|
2634
|
-
jsonResponse(res, 200, { ok: true });
|
|
2635
|
-
} catch (err) {
|
|
2636
|
-
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2637
|
-
}
|
|
2638
|
-
return;
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
case "validate":
|
|
2642
|
-
if (req.method !== "POST") {
|
|
2643
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2644
|
-
return;
|
|
2645
|
-
}
|
|
2646
|
-
jsonResponse(res, 200, handleValidate(await readBody(req)));
|
|
2647
|
-
return;
|
|
2648
|
-
case "telegram-chat-id": {
|
|
2649
|
-
if (req.method !== "POST") {
|
|
2650
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2651
|
-
return;
|
|
2652
|
-
}
|
|
2653
|
-
const result = await handleTelegramChatIdLookup(await readBody(req));
|
|
2654
|
-
jsonResponse(res, result.status, result);
|
|
2655
|
-
return;
|
|
2656
|
-
}
|
|
2657
|
-
case "apply":
|
|
2658
|
-
if (req.method !== "POST") {
|
|
2659
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2660
|
-
return;
|
|
2661
|
-
}
|
|
2662
|
-
jsonResponse(res, 200, handleApply(await readBody(req)));
|
|
2663
|
-
return;
|
|
2664
|
-
case "install-startup": {
|
|
2665
|
-
if (req.method !== "POST") {
|
|
2666
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2667
|
-
return;
|
|
2668
|
-
}
|
|
2669
|
-
try {
|
|
2670
|
-
const body_ = await readBody(req);
|
|
2671
|
-
const { installStartupService } = await import("../infra/startup-service.mjs");
|
|
2672
|
-
const result = await installStartupService({ daemon: body_?.daemon !== false });
|
|
2673
|
-
jsonResponse(res, 200, { ok: true, ...result });
|
|
2674
|
-
} catch (err) {
|
|
2675
|
-
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2676
|
-
}
|
|
2677
|
-
return;
|
|
2678
|
-
}
|
|
2679
|
-
case "remove-startup": {
|
|
2680
|
-
if (req.method !== "POST") {
|
|
2681
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2682
|
-
return;
|
|
2683
|
-
}
|
|
2684
|
-
try {
|
|
2685
|
-
const { removeStartupService } = await import("../infra/startup-service.mjs");
|
|
2686
|
-
const result = await removeStartupService();
|
|
2687
|
-
jsonResponse(res, 200, { ok: true, ...result });
|
|
2688
|
-
} catch (err) {
|
|
2689
|
-
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2690
|
-
}
|
|
2691
|
-
return;
|
|
2692
|
-
}
|
|
2693
|
-
case "install-desktop-shortcut": {
|
|
2694
|
-
if (req.method !== "POST") {
|
|
2695
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2696
|
-
return;
|
|
2697
|
-
}
|
|
2698
|
-
try {
|
|
2699
|
-
const { installDesktopShortcut } = await import("../infra/desktop-shortcut.mjs");
|
|
2700
|
-
const result = installDesktopShortcut();
|
|
2701
|
-
jsonResponse(res, 200, { ok: true, ...result });
|
|
2702
|
-
} catch (err) {
|
|
2703
|
-
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2704
|
-
}
|
|
2705
|
-
return;
|
|
2706
|
-
}
|
|
2707
|
-
case "remove-desktop-shortcut": {
|
|
2708
|
-
if (req.method !== "POST") {
|
|
2709
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2710
|
-
return;
|
|
2711
|
-
}
|
|
2712
|
-
try {
|
|
2713
|
-
const { removeDesktopShortcut } = await import("../infra/desktop-shortcut.mjs");
|
|
2714
|
-
const result = removeDesktopShortcut();
|
|
2715
|
-
jsonResponse(res, 200, { ok: true, ...result });
|
|
2716
|
-
} catch (err) {
|
|
2717
|
-
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2718
|
-
}
|
|
2719
|
-
return;
|
|
2720
|
-
}
|
|
2721
|
-
case "complete":
|
|
2722
|
-
if (req.method !== "POST") {
|
|
2723
|
-
jsonResponse(res, 405, { ok: false, error: "POST required" });
|
|
2724
|
-
return;
|
|
2725
|
-
}
|
|
2726
|
-
jsonResponse(res, 200, { ok: true, message: "Setup complete" });
|
|
2727
|
-
// Shut down server after response is sent
|
|
2728
|
-
setTimeout(() => {
|
|
2729
|
-
console.log("\n :check: Setup complete — shutting down wizard server.\n");
|
|
2730
|
-
if (callbackServer) callbackServer.close();
|
|
2731
|
-
server.close();
|
|
2732
|
-
process.exit(0);
|
|
2733
|
-
}, 500);
|
|
2734
|
-
return;
|
|
2735
|
-
case "oauth-state": {
|
|
2736
|
-
// The setup wizard polls this to detect when the GitHub OAuth callback
|
|
2737
|
-
// has been received (possibly on a different port).
|
|
2738
|
-
const pendingPath = oauthPendingPath();
|
|
2739
|
-
if (existsSync(pendingPath)) {
|
|
2740
|
-
try {
|
|
2741
|
-
const raw = readFileSync(pendingPath, "utf8");
|
|
2742
|
-
const data = JSON.parse(raw);
|
|
2743
|
-
// Delete the file so it's only claimed once
|
|
2744
|
-
try { unlinkSync(pendingPath); } catch { /* ignore */ }
|
|
2745
|
-
jsonResponse(res, 200, { ok: true, pending: true, ...data });
|
|
2746
|
-
} catch {
|
|
2747
|
-
jsonResponse(res, 200, { ok: true, pending: false });
|
|
2748
|
-
}
|
|
2749
|
-
} else {
|
|
2750
|
-
jsonResponse(res, 200, { ok: true, pending: false });
|
|
2751
|
-
}
|
|
2752
|
-
return;
|
|
2753
|
-
}
|
|
2754
|
-
default:
|
|
2755
|
-
jsonResponse(res, 404, { ok: false, error: `Unknown route: ${route}` });
|
|
2756
|
-
return;
|
|
2757
|
-
}
|
|
2758
|
-
} catch (err) {
|
|
2759
|
-
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
2760
|
-
return;
|
|
2761
|
-
}
|
|
2788
|
+
await handleSetupApi(req, res, url);
|
|
2789
|
+
return;
|
|
2762
2790
|
}
|
|
2763
2791
|
|
|
2764
2792
|
// Static file serving from ui/
|
|
@@ -3060,6 +3088,7 @@ export {
|
|
|
3060
3088
|
applyTelegramMiniAppSetupEnv,
|
|
3061
3089
|
applyNonBlockingSetupEnvDefaults,
|
|
3062
3090
|
buildModelsProbeRequest,
|
|
3091
|
+
handleSetupApi,
|
|
3063
3092
|
handleTelegramChatIdLookup,
|
|
3064
3093
|
isAzureOpenAIHost,
|
|
3065
3094
|
normalizeWorkflowTemplateOverrides,
|