turbollm 1.12.1 → 1.12.2

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.
Files changed (126) hide show
  1. package/README.md +17 -0
  2. package/dist/cli.js +294 -32
  3. package/dist/webdist/assets/{AgentEditPage-B9mKK6Ob.js → AgentEditPage-DC5b8GLz.js} +1 -1
  4. package/dist/webdist/assets/{ChatScreen-DLD1CDS1.js → ChatScreen-_trAuOm4.js} +1 -1
  5. package/dist/webdist/assets/{CodeComposer-DHE_iTkC.js → CodeComposer-N7JTyzpl.js} +1 -1
  6. package/dist/webdist/assets/{CodeHomeScreen-6CKrrrZ5.js → CodeHomeScreen-CY7CTv_Q.js} +1 -1
  7. package/dist/webdist/assets/{CodeSessionScreen-JUpzeYHF.js → CodeSessionScreen-tx2s3iWr.js} +1 -1
  8. package/dist/webdist/assets/{CustomizeScreen-C7f4ZRSa.js → CustomizeScreen-BUiEu5vl.js} +1 -1
  9. package/dist/webdist/assets/{DeveloperScreen-n6gJC3-E.js → DeveloperScreen-JAV1WwIi.js} +1 -1
  10. package/dist/webdist/assets/EnginesScreen-CL-rA9uX.js +8 -0
  11. package/dist/webdist/assets/{ModelDetailDialog-BdPGMTTz.js → ModelDetailDialog-B_-j8TNF.js} +1 -1
  12. package/dist/webdist/assets/{ModelLoadMenu-D3zo79dw.js → ModelLoadMenu-8zL_YHxh.js} +1 -1
  13. package/dist/webdist/assets/{ModelsScreen-7AJUC2wa.js → ModelsScreen-Cse1wZKp.js} +1 -1
  14. package/dist/webdist/assets/{OnboardingScreen-CGbbx6bS.js → OnboardingScreen-DNUgdieT.js} +1 -1
  15. package/dist/webdist/assets/{RoutineEditPage-Dd_snYdQ.js → RoutineEditPage-Dkt04QR1.js} +1 -1
  16. package/dist/webdist/assets/{RoutinesPanel-5M00XOUM.js → RoutinesPanel-iULR-iL1.js} +1 -1
  17. package/dist/webdist/assets/{SettingsScreen-B489HDiC.js → SettingsScreen-CQXDBVEu.js} +1 -1
  18. package/dist/webdist/assets/{SidebarResizeHandle-B0wgsZJ6.js → SidebarResizeHandle-Bx_x_ICv.js} +1 -1
  19. package/dist/webdist/assets/{SkillEditPage-CVYFfzLc.js → SkillEditPage-BKrXTpY3.js} +1 -1
  20. package/dist/webdist/assets/{TokensScreen-CQ4m20pP.js → TokensScreen-DTazcqcy.js} +1 -1
  21. package/dist/webdist/assets/{ToolApprovalBar-B2nX56dI.js → ToolApprovalBar-DDgGZOxI.js} +3 -3
  22. package/dist/webdist/assets/WorkspaceScreen-DpkSZQhK.js +1 -0
  23. package/dist/webdist/assets/{abnfDiagram-N423BO3Z-DoOYvhD1.js → abnfDiagram-N423BO3Z-h22xWhjH.js} +1 -1
  24. package/dist/webdist/assets/{agent-api-BAMDY8-v.js → agent-api-BXUf3UuJ.js} +1 -1
  25. package/dist/webdist/assets/{alert-dialog-rMeq-XIF.js → alert-dialog-hkwXUo0d.js} +1 -1
  26. package/dist/webdist/assets/{arc-BmVPVvg0.js → arc-BSvZ7mxZ.js} +1 -1
  27. package/dist/webdist/assets/{architectureDiagram-T3A2C74G-Bh4HLM3j.js → architectureDiagram-T3A2C74G-D840pkTs.js} +1 -1
  28. package/dist/webdist/assets/{arrow-left-Bawx_G0m.js → arrow-left-tnJKNvU8.js} +1 -1
  29. package/dist/webdist/assets/{badge-BK8Xn6K3.js → badge-HTozgaNk.js} +1 -1
  30. package/dist/webdist/assets/{blockDiagram-VBNYF7ZC-D4dEMw56.js → blockDiagram-VBNYF7ZC-DlvennNB.js} +1 -1
  31. package/dist/webdist/assets/{bot-DmBk_i45.js → bot-BEKBqPcU.js} +1 -1
  32. package/dist/webdist/assets/{c4Diagram-5PPSVZJV-cnPwu_pX.js → c4Diagram-5PPSVZJV-CneJCPhz.js} +1 -1
  33. package/dist/webdist/assets/channel-ZaF9JSkw.js +1 -0
  34. package/dist/webdist/assets/{chat-api-Bgrb0rnf.js → chat-api-B_SlDoXA.js} +1 -1
  35. package/dist/webdist/assets/{chat-queries-zz2ch8OT.js → chat-queries-CxkwlyLc.js} +1 -1
  36. package/dist/webdist/assets/{chevron-down-CNDC69O9.js → chevron-down-DfzNO8NY.js} +1 -1
  37. package/dist/webdist/assets/{chevron-left-CxvUI3ck.js → chevron-left-DHH-nnSd.js} +1 -1
  38. package/dist/webdist/assets/{chevron-right-CpdnK5S2.js → chevron-right-DnT71PVy.js} +1 -1
  39. package/dist/webdist/assets/{chunk-2GRJ4B5K-CtDLA9D7.js → chunk-2GRJ4B5K-DfGpb0ih.js} +1 -1
  40. package/dist/webdist/assets/{chunk-2Q5K7J3B-CclvIDBO.js → chunk-2Q5K7J3B-CbNbsgTh.js} +1 -1
  41. package/dist/webdist/assets/{chunk-5RXB4S5H-DFYQzU9L.js → chunk-5RXB4S5H-Bc_97P9Q.js} +1 -1
  42. package/dist/webdist/assets/{chunk-5VM5RSS4-CGpXaRvE.js → chunk-5VM5RSS4-Dc1cXgV2.js} +1 -1
  43. package/dist/webdist/assets/{chunk-6Q2QTUOP-CI7s7JEA.js → chunk-6Q2QTUOP-CdFOJa-e.js} +1 -1
  44. package/dist/webdist/assets/{chunk-GF5L2VYU-y-SeWQKo.js → chunk-GF5L2VYU-D3PC0Vtx.js} +1 -1
  45. package/dist/webdist/assets/{chunk-JWPE2WC7-BvVsBdrN.js → chunk-JWPE2WC7-BPW77F2i.js} +1 -1
  46. package/dist/webdist/assets/{chunk-KBJHAD2P-CUc-YWns.js → chunk-KBJHAD2P-BHXAIZXB.js} +1 -1
  47. package/dist/webdist/assets/{chunk-RYQCIY6F-CABY0N-y.js → chunk-RYQCIY6F-CrnPyqjW.js} +1 -1
  48. package/dist/webdist/assets/{chunk-XXDRQBXY-ChEtYkzy.js → chunk-XXDRQBXY-Bv9q4UGg.js} +1 -1
  49. package/dist/webdist/assets/{circle-check-D-vn9nY2.js → circle-check-BgrXRJTZ.js} +1 -1
  50. package/dist/webdist/assets/{circle-x-BcnmRVTe.js → circle-x-CYZSobXW.js} +1 -1
  51. package/dist/webdist/assets/classDiagram-JCYQIIEL-CjrbKhLT.js +1 -0
  52. package/dist/webdist/assets/classDiagram-v2-OCEON4UE-CjrbKhLT.js +1 -0
  53. package/dist/webdist/assets/{common-xWkNwbHY.js → common-C48Zd9_u.js} +1 -1
  54. package/dist/webdist/assets/{cose-bilkent-JH36ORCC-CJpKH0B3.js → cose-bilkent-JH36ORCC-B9I1Xo2f.js} +1 -1
  55. package/dist/webdist/assets/{cynefin-VYW2F7L2-B97tFXrq.js → cynefin-VYW2F7L2-Djx9Q5Zl.js} +1 -1
  56. package/dist/webdist/assets/{cynefinDiagram-MW4NZA55-CTmmk7Xt.js → cynefinDiagram-MW4NZA55-egTrJ3T2.js} +1 -1
  57. package/dist/webdist/assets/{dagre-VZM6K2ZE-BMkcnGJJ.js → dagre-VZM6K2ZE-ClfYLjT5.js} +1 -1
  58. package/dist/webdist/assets/{diagram-7IWD3JNH-DxA708mT.js → diagram-7IWD3JNH-C-AaXLmL.js} +1 -1
  59. package/dist/webdist/assets/{diagram-B4RE2ZJO-Bkjy34TD.js → diagram-B4RE2ZJO-5ZsOP19O.js} +1 -1
  60. package/dist/webdist/assets/{diagram-LBJQPF4R-BorjsNJT.js → diagram-LBJQPF4R-BAa7bkkY.js} +1 -1
  61. package/dist/webdist/assets/{diagram-Q27KOJAE-hW5SDR9k.js → diagram-Q27KOJAE-HarfGeNl.js} +1 -1
  62. package/dist/webdist/assets/{diagram-UB23O5K3-c07HJJ5p.js → diagram-UB23O5K3-CvSFCTH8.js} +1 -1
  63. package/dist/webdist/assets/{dropdown-menu-CaecUWTU.js → dropdown-menu-BOUsiJco.js} +1 -1
  64. package/dist/webdist/assets/{ebnfDiagram-BXEA7PRR-CvgAWjhG.js → ebnfDiagram-BXEA7PRR-CD94llbm.js} +1 -1
  65. package/dist/webdist/assets/{erDiagram-JOGREHBK-C66LM90T.js → erDiagram-JOGREHBK-iSGYBlqE.js} +1 -1
  66. package/dist/webdist/assets/{external-link-NvytYVfT.js → external-link-c_B7G__W.js} +1 -1
  67. package/dist/webdist/assets/{file-text-BsC3M8zz.js → file-text-BMSTQeV2.js} +1 -1
  68. package/dist/webdist/assets/{flowDiagram-UKHOOZJN-BcXu-_m9.js → flowDiagram-UKHOOZJN-CfRmhZIc.js} +1 -1
  69. package/dist/webdist/assets/{folder-input-B5dvV2Rt.js → folder-input-BtE_5cPV.js} +1 -1
  70. package/dist/webdist/assets/{folder-plus-CfgNM2Bq.js → folder-plus-CFn90Mxi.js} +1 -1
  71. package/dist/webdist/assets/{ganttDiagram-PKOTCBZU-DtorJ7JE.js → ganttDiagram-PKOTCBZU-DoQkfMfx.js} +1 -1
  72. package/dist/webdist/assets/{git-branch-BRH6t3pJ.js → git-branch-ojL3vNiD.js} +1 -1
  73. package/dist/webdist/assets/{gitGraphDiagram-DS77QQ5N-krn_sqdS.js → gitGraphDiagram-DS77QQ5N-DsvnFgBc.js} +1 -1
  74. package/dist/webdist/assets/{index-C9ZXfFYo.js → index-CSavhRGL.js} +6 -6
  75. package/dist/webdist/assets/{infoDiagram-6WML65LV-DdSRJqp_.js → infoDiagram-6WML65LV-mDSA5sHn.js} +1 -1
  76. package/dist/webdist/assets/{ishikawaDiagram-WSZJBQD7-CXU7wA7v.js → ishikawaDiagram-WSZJBQD7-B5Yq0JqB.js} +1 -1
  77. package/dist/webdist/assets/{journeyDiagram-NVQOT4AX-CIzJLnh4.js → journeyDiagram-NVQOT4AX-5jpV1XZE.js} +1 -1
  78. package/dist/webdist/assets/{kanban-definition-27J2QSJJ-D7PciwFU.js → kanban-definition-27J2QSJJ-Cuyvwopj.js} +1 -1
  79. package/dist/webdist/assets/{linear-DJzOvrgv.js → linear-DqzYFlqe.js} +1 -1
  80. package/dist/webdist/assets/{link-queries-BfuN5xQI.js → link-queries-R-5RNwSV.js} +1 -1
  81. package/dist/webdist/assets/{lock-BpNNUDlH.js → lock-BMvhGs4r.js} +1 -1
  82. package/dist/webdist/assets/{mermaid.core-DEfoMsry.js → mermaid.core-BSsGYq_y.js} +4 -4
  83. package/dist/webdist/assets/{mindmap-definition-FAOFIHXS-B-p8gHME.js → mindmap-definition-FAOFIHXS-bGoQuSrf.js} +1 -1
  84. package/dist/webdist/assets/{monitor-3-JqrGh0.js → monitor-BgsqLI--.js} +1 -1
  85. package/dist/webdist/assets/{pegDiagram-VL7TDLO6-CVN-UtZ2.js → pegDiagram-VL7TDLO6-BSfifu-4.js} +1 -1
  86. package/dist/webdist/assets/{pencil-CLBnFmc5.js → pencil-CMc0BVmG.js} +1 -1
  87. package/dist/webdist/assets/{personas-Dkq9JD72.js → personas-CKCwxU6o.js} +1 -1
  88. package/dist/webdist/assets/{pieDiagram-7S7Q4E2Y-Cyowg7jU.js → pieDiagram-7S7Q4E2Y-DFMcMGg7.js} +1 -1
  89. package/dist/webdist/assets/{plus-BsBN79CH.js → plus-5OMKNJj_.js} +1 -1
  90. package/dist/webdist/assets/{quadrantDiagram-CIZ2JOQS-CLEre7Bo.js → quadrantDiagram-CIZ2JOQS-BRQ2ESFh.js} +1 -1
  91. package/dist/webdist/assets/{railroadDiagram-AXF67PYL-CstFYJPW.js → railroadDiagram-AXF67PYL-DPodM5Aj.js} +1 -1
  92. package/dist/webdist/assets/{refresh-cw-DHnSGytz.js → refresh-cw-ZKW14GBQ.js} +1 -1
  93. package/dist/webdist/assets/{remote-failure-DQsv9RYM.js → remote-failure-nVuOLb4V.js} +1 -1
  94. package/dist/webdist/assets/{requirementDiagram-LRYGKXZP-BVS10AjP.js → requirementDiagram-LRYGKXZP-DWCCxYHs.js} +1 -1
  95. package/dist/webdist/assets/{rocket-0CLawS4F.js → rocket-fYq9X9BT.js} +1 -1
  96. package/dist/webdist/assets/{rotate-ccw-kHM_pFpt.js → rotate-ccw-g_DuozeG.js} +1 -1
  97. package/dist/webdist/assets/{sankeyDiagram-W5VNT64P-DWg6hEFJ.js → sankeyDiagram-W5VNT64P-DrI6xZpj.js} +1 -1
  98. package/dist/webdist/assets/{sequenceDiagram-SI44F4Z6-CPe4ECQK.js → sequenceDiagram-SI44F4Z6-BrlRaYxD.js} +1 -1
  99. package/dist/webdist/assets/{sizeCapture-X5ZJPWSS-tYK0sN8k.js → sizeCapture-X5ZJPWSS-BWkHzhyu.js} +1 -1
  100. package/dist/webdist/assets/{skeleton-1pxWnguF.js → skeleton-Bv8b9m3J.js} +1 -1
  101. package/dist/webdist/assets/{sliders-horizontal-CJnL4ema.js → sliders-horizontal-ePjH5qK0.js} +1 -1
  102. package/dist/webdist/assets/{sparkles-CtVbnpZt.js → sparkles-CshkhEsq.js} +1 -1
  103. package/dist/webdist/assets/{star-BWg7n83j.js → star-ENKV_riY.js} +1 -1
  104. package/dist/webdist/assets/{stateDiagram-OKZ733FA-DdMFYbZ9.js → stateDiagram-OKZ733FA-T_6O5IHv.js} +1 -1
  105. package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-97ixhjGa.js +1 -0
  106. package/dist/webdist/assets/{swimlanes-SLNWSIFB-DgWqKJOe.js → swimlanes-SLNWSIFB-Bmme-2xD.js} +2 -2
  107. package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-BxJRyIBv.js +8 -0
  108. package/dist/webdist/assets/{switch-ChtEKTkl.js → switch-BtGEUEC2.js} +1 -1
  109. package/dist/webdist/assets/{terminal-BksEQh4H.js → terminal-C_QOTYmG.js} +1 -1
  110. package/dist/webdist/assets/{timeline-definition-Z64GVDOM-CrwOPdAi.js → timeline-definition-Z64GVDOM-B3fBfREF.js} +1 -1
  111. package/dist/webdist/assets/{trash-2-BXZkODa0.js → trash-2-B__gvkzD.js} +1 -1
  112. package/dist/webdist/assets/{triangle-alert-tsQmbLn8.js → triangle-alert-BbhRFFB2.js} +1 -1
  113. package/dist/webdist/assets/{useIsDesktop-BKLpQjiw.js → useIsDesktop-Do2taDTq.js} +1 -1
  114. package/dist/webdist/assets/{vennDiagram-T6HMQDX7-B7_yA6Pn.js → vennDiagram-T6HMQDX7-DnVI7ph_.js} +1 -1
  115. package/dist/webdist/assets/{wardleyDiagram-T6FBY63Y-CImMUYg_.js → wardleyDiagram-T6FBY63Y-CY4YnfZL.js} +1 -1
  116. package/dist/webdist/assets/{wrench-CuSXx_Zn.js → wrench-WKNRyiMt.js} +1 -1
  117. package/dist/webdist/assets/{xychartDiagram-ELKLHX3M-B2wGD83d.js → xychartDiagram-ELKLHX3M-DgqvW4os.js} +1 -1
  118. package/dist/webdist/index.html +1 -1
  119. package/package.json +1 -1
  120. package/dist/webdist/assets/EnginesScreen-BWRix4d7.js +0 -7
  121. package/dist/webdist/assets/WorkspaceScreen-RLGQrw5_.js +0 -1
  122. package/dist/webdist/assets/channel-BoFujHnv.js +0 -1
  123. package/dist/webdist/assets/classDiagram-JCYQIIEL-CWuM4mUX.js +0 -1
  124. package/dist/webdist/assets/classDiagram-v2-OCEON4UE-CWuM4mUX.js +0 -1
  125. package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-Pg2RCR6-.js +0 -1
  126. package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-D-HgKzxH.js +0 -8
package/README.md CHANGED
@@ -735,6 +735,23 @@ access your GPU directly for accelerated local LLM inference.
735
735
  > [GitHub repository](https://github.com/mohitsoni48/TurboLLM), not in the npm package —
736
736
  > clone the repo to use them.
737
737
 
738
+ ### Getting your API key
739
+
740
+ The container publishes port 6996 on `0.0.0.0`, so TurboLLM's daemon requires an API key
741
+ for every request the moment it starts — there is no browser inside the container to log
742
+ in with. Both images launch with **`--print-token`**, which mints a fresh key and prints
743
+ it on every container start (not just the first), so it's always one command away:
744
+
745
+ ```bash
746
+ docker compose -f docker-compose-nvidia.yaml logs | grep 'Token:' # or docker-compose-amd.yaml
747
+ ```
748
+
749
+ Grab the value after `Token:` and either paste it into the web UI's login prompt, or send
750
+ it yourself as `X-TurboLLM-Auth: <key>` (or `Authorization: Bearer <key>`). Each restart
751
+ mints a new one, so re-run the command above after a restart rather than reusing an old
752
+ value — old keys keep working too and can be revoked from Developer → API Keys once
753
+ you're in.
754
+
738
755
  ---
739
756
 
740
757
  ### NVIDIA GPU (CUDA)
package/dist/cli.js CHANGED
@@ -2713,6 +2713,49 @@ function buildEnv(toolchainDirs = []) {
2713
2713
  if (env[key] !== void 0 || dirs.length > 0) env[key] = segments.join(delimiter);
2714
2714
  return env;
2715
2715
  }
2716
+ var LINUX_PACKAGE_MANAGERS = ["apt-get", "dnf", "pacman", "zypper"];
2717
+ var PACKAGES = {
2718
+ "apt-get": { git: ["git"], cmake: ["cmake"], gcc: ["build-essential"], cuda: ["nvidia-cuda-toolkit"] },
2719
+ dnf: { git: ["git"], cmake: ["cmake"], gcc: ["gcc-c++", "make"] },
2720
+ pacman: { git: ["git"], cmake: ["cmake"], gcc: ["base-devel"], cuda: ["cuda"] },
2721
+ zypper: { git: ["git"], cmake: ["cmake"], gcc: ["gcc-c++", "make"] },
2722
+ // macOS: the compiler is the Xcode Command Line Tools (`xcode-select --install` opens a GUI
2723
+ // prompt, so it isn't something to run for the user headlessly) and there is no CUDA at all.
2724
+ brew: { git: ["git"], cmake: ["cmake"] }
2725
+ };
2726
+ function privilegePrefix(asRoot) {
2727
+ return asRoot ? [] : ["sudo", "-n"];
2728
+ }
2729
+ function prereqInstallCommands(pm, id, asRoot) {
2730
+ const pkgs = PACKAGES[pm][id];
2731
+ if (!pkgs || pkgs.length === 0) return void 0;
2732
+ if (pm === "brew") return [["brew", "install", ...pkgs]];
2733
+ const sudo = privilegePrefix(asRoot);
2734
+ switch (pm) {
2735
+ case "apt-get":
2736
+ return [[...sudo, "apt-get", "update"], [...sudo, "apt-get", "install", "-y", ...pkgs]];
2737
+ case "dnf":
2738
+ return [[...sudo, "dnf", "install", "-y", ...pkgs]];
2739
+ case "pacman":
2740
+ return [[...sudo, "pacman", "-Sy", "--noconfirm", ...pkgs]];
2741
+ case "zypper":
2742
+ return [[...sudo, "zypper", "--non-interactive", "install", ...pkgs]];
2743
+ }
2744
+ }
2745
+ function runningAsRoot() {
2746
+ return typeof process.getuid === "function" && process.getuid() === 0;
2747
+ }
2748
+ async function detectPackageManager(env) {
2749
+ const candidates = process.platform === "linux" ? LINUX_PACKAGE_MANAGERS : process.platform === "darwin" ? ["brew"] : [];
2750
+ for (const pm of candidates) {
2751
+ try {
2752
+ await runVersion(pm, ["--version"], env);
2753
+ return pm;
2754
+ } catch {
2755
+ }
2756
+ }
2757
+ return null;
2758
+ }
2716
2759
  var INSTALL_URLS = {
2717
2760
  git: "https://git-scm.com/downloads",
2718
2761
  cmake: "https://cmake.org/download/",
@@ -2826,15 +2869,28 @@ async function checkGcc(env) {
2826
2869
  async function checkBuildPrereqs(toolchainDirs = []) {
2827
2870
  const platform = process.platform;
2828
2871
  if (platform !== "win32" && platform !== "linux" && platform !== "darwin") {
2829
- return { supported: false, os: "other", tools: [] };
2872
+ return { supported: false, os: "other", tools: [], packageManager: null };
2830
2873
  }
2831
2874
  const env = buildEnv(toolchainDirs);
2832
- if (platform === "darwin") {
2833
- const tools2 = await Promise.all([checkGit(env), checkCmake(env), checkGcc(env)]);
2834
- return { supported: true, os: "macos", tools: tools2 };
2835
- }
2836
- const tools = platform === "win32" ? await Promise.all([checkGit(env), checkCmake(env), checkCuda(env), checkMsvc(env)]) : await Promise.all([checkGit(env), checkCmake(env), checkCuda(env), checkGcc(env)]);
2837
- return { supported: true, os: platform === "win32" ? "windows" : "linux", tools };
2875
+ const [tools, packageManager] = await Promise.all([
2876
+ platform === "darwin" ? Promise.all([checkGit(env), checkCmake(env), checkGcc(env)]) : platform === "win32" ? Promise.all([checkGit(env), checkCmake(env), checkCuda(env), checkMsvc(env)]) : Promise.all([checkGit(env), checkCmake(env), checkCuda(env), checkGcc(env)]),
2877
+ detectPackageManager(env)
2878
+ ]);
2879
+ return {
2880
+ supported: true,
2881
+ os: platform === "win32" ? "windows" : platform === "darwin" ? "macos" : "linux",
2882
+ tools: withInstallCommands(tools, packageManager),
2883
+ packageManager
2884
+ };
2885
+ }
2886
+ function withInstallCommands(tools, pm) {
2887
+ if (!pm) return tools;
2888
+ const asRoot = runningAsRoot();
2889
+ return tools.map((t) => {
2890
+ if (t.found) return t;
2891
+ const installCommands = prereqInstallCommands(pm, t.id, asRoot);
2892
+ return installCommands ? { ...t, installCommands } : t;
2893
+ });
2838
2894
  }
2839
2895
  var CMAKE_CONFIGURE_ARGS = ["-DGGML_CUDA=ON", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_CUDA_FLAGS=-allow-unsupported-compiler"];
2840
2896
  var CMAKE_CONFIGURE_ARGS_MACOS = ["-DGGML_METAL=ON", "-DCMAKE_BUILD_TYPE=Release"];
@@ -2903,6 +2959,20 @@ function suggestEngineName(binPath, version2) {
2903
2959
 
2904
2960
  // src/engines/build-runner.ts
2905
2961
  var execFileP8 = promisify8(execFile10);
2962
+ async function findLibcudaDir(signal) {
2963
+ try {
2964
+ const { stdout } = await execFileP8("ldconfig", ["-p"], {
2965
+ timeout: 5e3,
2966
+ maxBuffer: 4 * 1024 * 1024,
2967
+ signal
2968
+ });
2969
+ const line = stdout.split("\n").find((l) => /\blibcuda\.so\b/.test(l));
2970
+ const m = line ? line.match(/=>\s*(\S+)/) : null;
2971
+ return m ? dirname5(m[1]) : null;
2972
+ } catch {
2973
+ return null;
2974
+ }
2975
+ }
2906
2976
  function chooseEngineName(name, priorName) {
2907
2977
  return name ?? priorName ?? "";
2908
2978
  }
@@ -3194,6 +3264,36 @@ function neutralizeGratuitousAsm(srcDir, log) {
3194
3264
  );
3195
3265
  }
3196
3266
  }
3267
+ function noInstallCommandError(tool, packageManager) {
3268
+ if ((tool.installCommands?.length ?? 0) > 0) return null;
3269
+ return (packageManager ? `TurboLLM doesn't have a one-command ${packageManager} install for ${tool.name} on this system. ` : `No supported package manager (apt-get, dnf, pacman, zypper, or Homebrew) was found on this system. `) + `Install ${tool.name} yourself \u2014 see ${tool.installUrl} \u2014 then re-check.`;
3270
+ }
3271
+ async function runPrereqInstall(req, hooks, signal) {
3272
+ hooks.phase("provisioning");
3273
+ const prereqs = await checkBuildPrereqs(req.toolchainDirs);
3274
+ if (!prereqs.supported) throw new Error("Installing build prerequisites is currently Linux and macOS only.");
3275
+ const tool = prereqs.tools.find((t) => t.id === req.tool);
3276
+ if (!tool) throw new Error(`"${req.tool}" isn't a build prerequisite on this system.`);
3277
+ if (tool.found) {
3278
+ hooks.log(`${tool.name} is already installed${tool.version ? ` (${tool.version})` : ""} \u2014 nothing to do.`);
3279
+ return;
3280
+ }
3281
+ const noCommand = noInstallCommandError(tool, prereqs.packageManager);
3282
+ if (noCommand) throw new Error(noCommand);
3283
+ const env = { ...buildEnv(req.toolchainDirs), DEBIAN_FRONTEND: "noninteractive" };
3284
+ for (const [cmd, ...args] of tool.installCommands) {
3285
+ hooks.log(`$ ${[cmd, ...args].join(" ")}`);
3286
+ await runStep(cmd, args, { env, signal, onLine: hooks.log });
3287
+ }
3288
+ const after = await checkBuildPrereqs(req.toolchainDirs);
3289
+ const now = after.tools.find((t) => t.id === req.tool);
3290
+ if (!now?.found) {
3291
+ throw new Error(
3292
+ `The install commands ran, but ${tool.name} still isn't detected. Install it yourself \u2014 see ${tool.installUrl} \u2014 or, if it landed somewhere off PATH, add that folder under Build environment and re-check.`
3293
+ );
3294
+ }
3295
+ hooks.log(`${now.name}${now.version ? ` ${now.version}` : ""} installed \u2713`);
3296
+ }
3197
3297
  async function runBuild(req, hooks, signal) {
3198
3298
  const isWindows = process.platform === "win32";
3199
3299
  const isMac = process.platform === "darwin";
@@ -3300,9 +3400,17 @@ async function runBuild(req, hooks, signal) {
3300
3400
  await runStep("cmake", compileArgs, { cwd: buildRoot, env, signal, onLine: captureLog });
3301
3401
  }
3302
3402
  };
3403
+ let linuxCudaArgs = CMAKE_CONFIGURE_ARGS;
3404
+ if (!isWindows && !isMac) {
3405
+ const libcudaDir = await findLibcudaDir(signal);
3406
+ if (libcudaDir) {
3407
+ hooks.log(`libcuda.so found outside the CUDA Toolkit path \u2014 adding CMAKE_LIBRARY_PATH=${libcudaDir}`);
3408
+ linuxCudaArgs = [...CMAKE_CONFIGURE_ARGS, `-DCMAKE_LIBRARY_PATH=${libcudaDir}`];
3409
+ }
3410
+ }
3303
3411
  const buildLog = [];
3304
3412
  try {
3305
- await configureAndCompile(isMac ? CMAKE_CONFIGURE_ARGS_MACOS : CMAKE_CONFIGURE_ARGS, buildLog);
3413
+ await configureAndCompile(isMac ? CMAKE_CONFIGURE_ARGS_MACOS : linuxCudaArgs, buildLog);
3306
3414
  } catch (e) {
3307
3415
  if (!isMac || !isIncompleteMetalBackendError(buildLog)) throw e;
3308
3416
  hooks.log(
@@ -4650,6 +4758,27 @@ function getLanIp() {
4650
4758
  const real = candidates.find((c) => !VIRTUAL_ADAPTER_NAME.test(c.name));
4651
4759
  return (real ?? candidates[0])?.address ?? null;
4652
4760
  }
4761
+ function getAdvertisedHost() {
4762
+ const raw = process.env.TURBOLLM_ADVERTISED_HOST?.trim();
4763
+ if (!raw) return null;
4764
+ let v = raw.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
4765
+ const slash = v.indexOf("/");
4766
+ if (slash >= 0) v = v.slice(0, slash);
4767
+ v = v.trim();
4768
+ if (!v) return null;
4769
+ const bracketed = /^(\[[^\]]+\])(?::(\d{1,5}))?$/.exec(v);
4770
+ if (bracketed) {
4771
+ const port2 = bracketed[2] ? Number(bracketed[2]) : null;
4772
+ return { host: bracketed[1], port: port2 && port2 > 0 && port2 <= 65535 ? port2 : null };
4773
+ }
4774
+ if (v.indexOf(":") !== v.lastIndexOf(":")) return { host: `[${v}]`, port: null };
4775
+ const withPort = /^(.+):(\d{1,5})$/.exec(v);
4776
+ if (withPort) {
4777
+ const port2 = Number(withPort[2]);
4778
+ if (port2 > 0 && port2 <= 65535) return { host: withPort[1], port: port2 };
4779
+ }
4780
+ return { host: v, port: null };
4781
+ }
4653
4782
 
4654
4783
  // src/chat/parser.ts
4655
4784
  var THINK_OPEN = "<think>";
@@ -5303,6 +5432,7 @@ function buildUpstream(remote, path) {
5303
5432
  return `${base2}${FACADE_PREFIX}${suffix}`;
5304
5433
  }
5305
5434
  var FORWARDED = ["content-type", "accept"];
5435
+ var LINK_AUTH_HEADER = "X-TurboLLM-Auth";
5306
5436
  function linkHeaders(remote, incoming) {
5307
5437
  const out = new Headers();
5308
5438
  if (incoming) {
@@ -5311,7 +5441,7 @@ function linkHeaders(remote, incoming) {
5311
5441
  if (v) out.set(name, v);
5312
5442
  }
5313
5443
  }
5314
- out.set("X-TurboLLM-Auth", remote.token);
5444
+ out.set(LINK_AUTH_HEADER, remote.token);
5315
5445
  return out;
5316
5446
  }
5317
5447
  async function proxyStream(remote, path, init, signal, fetchImpl = fetch) {
@@ -5496,11 +5626,11 @@ function generateApiKey() {
5496
5626
  const hash = createHash3("sha256").update(full).digest("hex");
5497
5627
  return { full, hash, prefix: full.slice(0, 12) };
5498
5628
  }
5499
- function provisionTunnelApiKey(d) {
5629
+ function provisionTunnelApiKey(d, label = "tunnel") {
5500
5630
  const { full, hash, prefix } = generateApiKey();
5501
5631
  const key = {
5502
5632
  id: randomUUID3(),
5503
- name: `tunnel-${(/* @__PURE__ */ new Date()).toISOString()}`,
5633
+ name: `${label}-${(/* @__PURE__ */ new Date()).toISOString()}`,
5504
5634
  hash,
5505
5635
  prefix,
5506
5636
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5509,6 +5639,28 @@ function provisionTunnelApiKey(d) {
5509
5639
  d.store.update((cfg2) => cfg2.apiKeys.push(key));
5510
5640
  return full;
5511
5641
  }
5642
+ function isLoopbackBindHost(host2) {
5643
+ const h = host2.trim().toLowerCase().replace(/^\[|\]$/g, "");
5644
+ if (h === "localhost" || h === "::1" || h === "::ffff:127.0.0.1") return true;
5645
+ return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
5646
+ }
5647
+ function provisionBootstrapApiKey(d, bindHost) {
5648
+ if (isLoopbackBindHost(bindHost)) return null;
5649
+ const cfg2 = d.store.snapshot();
5650
+ if (cfg2.daemon.requireApiKey !== true) return null;
5651
+ if (cfg2.apiKeys.length > 0) return null;
5652
+ const { full, hash, prefix } = generateApiKey();
5653
+ const key = {
5654
+ id: randomUUID3(),
5655
+ name: `bootstrap-${(/* @__PURE__ */ new Date()).toISOString()}`,
5656
+ hash,
5657
+ prefix,
5658
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5659
+ lastUsedAt: null
5660
+ };
5661
+ d.store.update((mut) => mut.apiKeys.push(key));
5662
+ return full;
5663
+ }
5512
5664
  function presentedKey(c) {
5513
5665
  const direct = c.req.header("X-TurboLLM-Auth") ?? c.req.header("x-api-key");
5514
5666
  if (direct && direct.trim()) return direct.trim();
@@ -10099,10 +10251,9 @@ ${m.content}` : m.content;
10099
10251
  const conv = db2.getConversation(convId);
10100
10252
  if (!conv) return err3(c, 404, "not_found", "Conversation not found.");
10101
10253
  const cfg2 = d.store.snapshot();
10102
- const lanIp = getLanIpForShare();
10103
- const url = `http://${lanIp}:${cfg2.daemon.port}/chat/${convId}`;
10104
- const onlyLocal = lanIp === "127.0.0.1";
10105
- return c.json({ url, onlyLocal });
10254
+ const share = getShareTarget(cfg2.daemon.port);
10255
+ const url = `http://${share.host}:${share.port}/chat/${convId}`;
10256
+ return c.json({ url, onlyLocal: share.onlyLocal });
10106
10257
  });
10107
10258
  app2.post("/api/v1/conversations/import", async (c) => {
10108
10259
  let raw;
@@ -10188,8 +10339,11 @@ function importOpenAiMessages(c, db2, messages, titleField, modelField) {
10188
10339
  }
10189
10340
  return c.json({ id: newConv.id }, 201);
10190
10341
  }
10191
- function getLanIpForShare() {
10192
- return getLanIp() ?? "127.0.0.1";
10342
+ function getShareTarget(defaultPort) {
10343
+ const advertised = getAdvertisedHost();
10344
+ if (advertised) return { host: advertised.host, port: advertised.port ?? defaultPort, onlyLocal: false };
10345
+ const host2 = getLanIp() ?? "127.0.0.1";
10346
+ return { host: host2, port: defaultPort, onlyLocal: host2 === "127.0.0.1" };
10193
10347
  }
10194
10348
  function reportChatBenchResult(d, ms, stats) {
10195
10349
  try {
@@ -11073,6 +11227,7 @@ Cannot execute bash commands.`);
11073
11227
 
11074
11228
  // src/code/code-upstream.ts
11075
11229
  var FALLBACK_CTX = 8192;
11230
+ var LINK_PLACEHOLDER_KEY = "turbo-link";
11076
11231
  function resolveCodeUpstream(d, requestedModel) {
11077
11232
  const res = resolveChatUpstream(d, requestedModel);
11078
11233
  if (!res.ok) throw new Error(res.code === "model_not_loaded" ? "model_not_loaded" : res.message);
@@ -11086,14 +11241,34 @@ function resolveCodeUpstream(d, requestedModel) {
11086
11241
  remote: u.remote,
11087
11242
  target: "",
11088
11243
  provider: {
11089
- baseUrl: `${u.remote.baseUrl}/v1`,
11090
- apiKey: u.remote.token,
11244
+ // THE FAÇADE, not the host's public `/v1` mount. pi's openai client appends
11245
+ // `/chat/completions` to this, so `<base>/v1` addressed `<host>/v1/chat/completions`
11246
+ // — the public gateway — where a link token is refused outright: it carries a
11247
+ // `grant`, `isFacadeOnlyKey` makes `verifyKeyValue` treat it as no match at all, and
11248
+ // lanAuth 401s in the LAN-open/key-required configuration every Turbo Link host runs
11249
+ // in (pinned by link-auth.test.ts, "a GRANTED link token is refused by lanAuth on the
11250
+ // public /v1/* gateway"). That is precisely why chat over a link worked while Code
11251
+ // did not: chat's transport is `proxyStream`, which derives the URL from
11252
+ // `buildUpstream`; Code was the one caller that spelled a link URL by hand.
11253
+ //
11254
+ // Built with `buildUpstream` rather than a second literal so there stays exactly ONE
11255
+ // place that knows how a link URL is spelled — which also normalises the trailing
11256
+ // slash a tunnel URL pasted from a browser address bar routinely carries (`https://h/`
11257
+ // → `https://h//v1/...`, a 404 on some proxies).
11258
+ //
11259
+ // Reaching the façade is not merely cosmetic: the public mount would bypass
11260
+ // `requireCapability('models:use')`, the model allowlist, the wake gate and the
11261
+ // link-chaining refusal — so on a host running LAN-open WITHOUT a required key, the
11262
+ // old URL would have silently *worked* while ignoring the entire capability grant.
11263
+ baseUrl: buildUpstream(u.remote, "/v1"),
11264
+ apiKey: LINK_PLACEHOLDER_KEY,
11091
11265
  // The link token travels as X-TurboLLM-Auth (linkHeaders, link-proxy.ts) and NOT as
11092
11266
  // a bearer: `Authorization` on the host is its own API-key credential, a different
11093
11267
  // secret with different scope, and presenting a link token there would either be
11094
- // rejected or — worse — accepted as something it is not.
11268
+ // rejected or — worse — accepted as something it is not. `authHeader: false` stops
11269
+ // pi adding one; `LINK_PLACEHOLDER_KEY` stops the openai SDK adding one from apiKey.
11095
11270
  authHeader: false,
11096
- headers: { "X-TurboLLM-Auth": u.remote.token }
11271
+ headers: { [LINK_AUTH_HEADER]: u.remote.token }
11097
11272
  }
11098
11273
  };
11099
11274
  }
@@ -18178,6 +18353,9 @@ var UI_ACTIONS = [
18178
18353
  "dismiss_build_success",
18179
18354
  "cancel_engine_build",
18180
18355
  "download_cuda_toolkit",
18356
+ // Install a missing build prereq with the host's own package manager (the headless fix:
18357
+ // the install LINK opens a page in the operator's browser, not on the daemon machine).
18358
+ "install_build_prereq",
18181
18359
  "toggle_agent_tool_group",
18182
18360
  "back_to_agents",
18183
18361
  "reset_agent_to_default",
@@ -24653,8 +24831,8 @@ function registerApi(app2, d) {
24653
24831
  async (c) => c.json(await checkBuildPrereqs(d.store.snapshot().build.toolchainDirs))
24654
24832
  );
24655
24833
  app2.post("/api/v1/build/run", async (c) => {
24656
- if (!isLocalRequest(c, d))
24657
- return err5(c, 403, "forbidden", "Building an engine is only available on the machine running TurboLLM.");
24834
+ if (!isLocalOrAuthenticated(c, d))
24835
+ return err5(c, 403, "forbidden", "Building an engine requires a valid API key from a non-host device.");
24658
24836
  if (process.platform !== "win32" && process.platform !== "linux" && process.platform !== "darwin")
24659
24837
  return err5(c, 409, "unsupported_platform", "In-app build is currently Windows, Linux, or macOS only.");
24660
24838
  const b = await body4(c);
@@ -24748,9 +24926,42 @@ function registerApi(app2, d) {
24748
24926
  }
24749
24927
  return c.json({ ok: false });
24750
24928
  });
24929
+ app2.post("/api/v1/build/install-prereq", async (c) => {
24930
+ if (!isLocalOrAuthenticated(c, d))
24931
+ return err5(c, 403, "forbidden", "Installing build prerequisites requires a valid API key from a non-host device.");
24932
+ if (process.platform !== "linux" && process.platform !== "darwin")
24933
+ return err5(c, 409, "unsupported_platform", "Installing build prerequisites is currently Linux and macOS only \u2014 on Windows these tools need their own installers.");
24934
+ const b = await body4(c);
24935
+ const tool = (b.tool ?? "").trim();
24936
+ if (tool !== "git" && tool !== "cmake" && tool !== "cuda" && tool !== "gcc")
24937
+ return err5(c, 400, "invalid_config_value", "tool must be one of: git, cmake, cuda, gcc.");
24938
+ const busy = engineWorkBusy(d);
24939
+ if (busy) return err5(c, 409, "engine_already_running", busy);
24940
+ const toolchainDirs = d.store.snapshot().build.toolchainDirs;
24941
+ const ac = new AbortController();
24942
+ buildAbort = ac;
24943
+ d.build.start(tool);
24944
+ d.build.phase("provisioning");
24945
+ void (async () => {
24946
+ try {
24947
+ await runPrereqInstall({ tool, toolchainDirs }, { phase: (p) => d.build.phase(p), log: (line) => d.build.log(line) }, ac.signal);
24948
+ d.build.done();
24949
+ } catch (e) {
24950
+ if (e?.name === "AbortError") {
24951
+ d.build.log("Install cancelled.");
24952
+ d.build.cancel("Install cancelled.");
24953
+ } else {
24954
+ d.build.fail(`Could not install ${tool}: ${e instanceof Error ? e.message : String(e)}`);
24955
+ }
24956
+ } finally {
24957
+ if (buildAbort === ac) buildAbort = null;
24958
+ }
24959
+ })();
24960
+ return c.json({ accepted: true }, 202);
24961
+ });
24751
24962
  app2.post("/api/v1/build/cuda", async (c) => {
24752
- if (!isLocalRequest(c, d))
24753
- return err5(c, 403, "forbidden", "Downloading CUDA is only available on the machine running TurboLLM.");
24963
+ if (!isLocalOrAuthenticated(c, d))
24964
+ return err5(c, 403, "forbidden", "Downloading CUDA requires a valid API key from a non-host device.");
24754
24965
  if (process.platform !== "win32")
24755
24966
  return err5(c, 409, "unsupported_platform", "Automatic CUDA download is currently Windows x86_64 only.");
24756
24967
  const busy = engineWorkBusy(d);
@@ -24922,8 +25133,8 @@ function registerApi(app2, d) {
24922
25133
  return c.json({ accepted: true, engine: "llamafile" }, 202);
24923
25134
  });
24924
25135
  app2.post("/api/v1/engines", async (c) => {
24925
- if (!isLocalRequest(c, d))
24926
- return err5(c, 403, "forbidden", "Engines can only be added from the machine running TurboLLM.");
25136
+ if (!isLocalOrAuthenticated(c, d))
25137
+ return err5(c, 403, "forbidden", "Adding an engine requires a valid API key from a non-host device.");
24927
25138
  const b = await body4(c);
24928
25139
  if (!b.binPath || !b.binPath.trim()) return err5(c, 400, "invalid_config_value", "binPath is required.");
24929
25140
  try {
@@ -24943,8 +25154,8 @@ function registerApi(app2, d) {
24943
25154
  }
24944
25155
  });
24945
25156
  app2.post("/api/v1/engines/scan", async (c) => {
24946
- if (!isLocalRequest(c, d))
24947
- return err5(c, 403, "forbidden", "Engine scanning is only available on the machine running TurboLLM.");
25157
+ if (!isLocalOrAuthenticated(c, d))
25158
+ return err5(c, 403, "forbidden", "Engine scanning requires a valid API key from a non-host device.");
24948
25159
  const b = await body4(c);
24949
25160
  const path = (b.path ?? "").trim();
24950
25161
  if (!path) return err5(c, 400, "invalid_config_value", "path is required.");
@@ -26905,7 +27116,7 @@ function registerLinkAdminRoutes(app2, d, opts) {
26905
27116
  const cfg2 = d.store.snapshot();
26906
27117
  const port2 = cfg2.daemon.port ?? 6996;
26907
27118
  const tunnelUrl = d.tunnel?.url() ?? null;
26908
- const baseUrl = tunnelUrl ?? `http://${lanHost()}:${port2}`;
27119
+ const baseUrl = tunnelUrl ?? lanBaseUrl(port2);
26909
27120
  if (d.telemetry) {
26910
27121
  const preset = typeof body10?.preset === "string" && Object.prototype.hasOwnProperty.call(LINK_PRESETS, body10.preset) ? body10.preset : void 0;
26911
27122
  emit(d.telemetry, linkMinted, { capabilityCount: caps.length, ...preset ? { preset } : {} });
@@ -27239,8 +27450,10 @@ function remoteFailure(c, rec, probe2) {
27239
27450
  status
27240
27451
  );
27241
27452
  }
27242
- function lanHost() {
27243
- return getLanIp() ?? "localhost";
27453
+ function lanBaseUrl(port2) {
27454
+ const advertised = getAdvertisedHost();
27455
+ if (advertised) return `http://${advertised.host}:${advertised.port ?? port2}`;
27456
+ return `http://${getLanIp() ?? "localhost"}:${port2}`;
27244
27457
  }
27245
27458
 
27246
27459
  // src/chat/chat-agent-routes.ts
@@ -33830,10 +34043,18 @@ Commands:
33830
34043
  Options:
33831
34044
  --port <n> Port to listen on / connect to (default: 6996)
33832
34045
  --addr <h:p> Full host:port override (e.g. 0.0.0.0:6996)
34046
+ Binding a non-loopback address turns on API-key enforcement;
34047
+ if no key exists yet, one is generated and printed on startup.
33833
34048
  --no-open Do not open a browser window on startup
33834
34049
  --tunnel Expose this daemon on the internet via a cloudflared quick
33835
34050
  tunnel (Cloud Launch) \u2014 prints the public URL + a required
33836
34051
  access token. For running TurboLLM on a rented cloud GPU box.
34052
+ --print-token Mint a fresh API key and print it as a stable 'Token: <key>'
34053
+ line on every startup, whether or not one already existed \u2014
34054
+ unlike the automatic non-loopback bootstrap key (which only
34055
+ ever mints once, on the very first boot), this always gives
34056
+ you a freshly known-good token to grep out of 'docker logs'
34057
+ after any restart. Default in the official Docker images.
33837
34058
  --config <f> Path to a custom config file
33838
34059
  --no-telemetry Disable telemetry entirely, whatever this install's saved
33839
34060
  setting says (same as TURBOLLM_TELEMETRY=off). For CI,
@@ -33847,11 +34068,22 @@ Examples:
33847
34068
  turbollm --no-open # start without opening a browser
33848
34069
  turbollm --addr 0.0.0.0:6996 # bind to all interfaces (LAN sharing)
33849
34070
  turbollm --tunnel --no-open # run on a rented GPU box, reachable via a public URL
34071
+ turbollm --addr 0.0.0.0:6996 --print-token # LAN/Docker: always print a usable token
33850
34072
  turbollm --stop # stop the running daemon
33851
34073
  turbollm launch claude # open Claude Code on your loaded model
33852
34074
  turbollm launch claude --model qwen3-8b # load qwen3-8b, then launch
33853
34075
  turbollm launch opencode # wire opencode to TurboLLM, then launch it
33854
34076
 
34077
+ Environment:
34078
+ TURBOLLM_ADVERTISED_HOST
34079
+ Host (or host:port) to put in the URLs TurboLLM hands out \u2014
34080
+ Turbo Link strings and chat-share links. Set this when the
34081
+ address others must use isn't one this machine can see, e.g.
34082
+ inside a Docker container, where the only interface found is
34083
+ the container-internal bridge IP (172.17.0.2) and the real
34084
+ address is the published host:port outside.
34085
+ e.g. TURBOLLM_ADVERTISED_HOST=192.168.1.50:6996
34086
+
33855
34087
  `
33856
34088
  );
33857
34089
  process.exit(0);
@@ -34065,6 +34297,8 @@ if (portFlag) {
34065
34297
  var lastColon = addr.lastIndexOf(":");
34066
34298
  var host = addr.slice(0, lastColon) || "127.0.0.1";
34067
34299
  var port = Number(addr.slice(lastColon + 1)) || 6996;
34300
+ var bootstrapKey = provisionBootstrapApiKey(deps, host);
34301
+ var advertisedHost = getAdvertisedHost();
34068
34302
  function openBrowser(url) {
34069
34303
  let cmd;
34070
34304
  let args;
@@ -34085,6 +34319,7 @@ function openBrowser(url) {
34085
34319
  });
34086
34320
  }
34087
34321
  var noOpen = hasFlag("--no-open");
34322
+ var printTokenRequested = hasFlag("--print-token");
34088
34323
  var tunnelToken = null;
34089
34324
  var server;
34090
34325
  var rebinding = false;
@@ -34105,10 +34340,37 @@ function listen(attempt = 0) {
34105
34340
  if (host === "0.0.0.0") {
34106
34341
  console.log(` Network: http://<your-ip>:${info.port} (LAN)`);
34107
34342
  }
34343
+ if (advertisedHost) {
34344
+ console.log(` Advertised: http://${advertisedHost.host}:${advertisedHost.port ?? info.port} (TURBOLLM_ADVERTISED_HOST)`);
34345
+ }
34108
34346
  console.log(``);
34109
34347
  console.log(` API: ${uiUrl}/api/v1/status`);
34110
34348
  console.log(` Stop: Ctrl+C`);
34111
34349
  console.log(``);
34350
+ if (bootstrapKey) {
34351
+ console.log(` ============================================================`);
34352
+ console.log(` This daemon is reachable from other machines, so the API`);
34353
+ console.log(` requires a key. One was generated for you just now:`);
34354
+ console.log(``);
34355
+ console.log(` ${bootstrapKey}`);
34356
+ console.log(``);
34357
+ console.log(` SAVE IT NOW \u2014 it is shown once and cannot be recovered.`);
34358
+ console.log(` Send it as 'X-TurboLLM-Auth: <key>' (or 'Authorization:`);
34359
+ console.log(` Bearer <key>'), or paste it into the web UI. Manage or`);
34360
+ console.log(` revoke it later under Developer -> API Keys.`);
34361
+ console.log(` ============================================================`);
34362
+ console.log(``);
34363
+ }
34364
+ if (printTokenRequested) {
34365
+ const printToken = bootstrapKey ?? provisionTunnelApiKey(deps, "print-token");
34366
+ if (!bootstrapKey) {
34367
+ console.log(` --print-token: a fresh API key was generated for you just now.`);
34368
+ console.log(` SAVE IT NOW \u2014 it is shown once and cannot be recovered.`);
34369
+ console.log(``);
34370
+ }
34371
+ console.log(` Token: ${printToken}`);
34372
+ console.log(``);
34373
+ }
34112
34374
  if (!noOpen) {
34113
34375
  openBrowser(uiUrl);
34114
34376
  }
@@ -1 +1 @@
1
- import{j as e}from"./radix-B0YBjihE.js";import{b as W,u as $,r as b}from"./vendor-C1I6euDx.js";import{u as G,d as H,e as Y,f as J,g as U,a as z,B as C,t as j,c as q,b as h,A as _}from"./index-C9ZXfFYo.js";import{f as V}from"./chat-api-Bgrb0rnf.js";import{f as X,a as Z}from"./agent-api-BAMDY8-v.js";import{P as B}from"./personas-Dkq9JD72.js";import{C as ee}from"./chevron-left-CxvUI3ck.js";import{R as te}from"./rotate-ccw-kHM_pFpt.js";import{T as se}from"./trash-2-BXZkODa0.js";import{C as ne}from"./chevron-right-CpdnK5S2.js";import"./dropdown-menu-CaecUWTU.js";const ae=()=>({name:"",description:"",systemPrompt:"",skillIds:[],tools:[]});function oe(s){const d=[],x=new Map;for(const n of s){const a=/^mcp__([^_]+(?:_[^_]+)*)__/.exec(n.name);if(!a){d.push(n);continue}const i=a[1],p=/^\[([^\]]+)\]/.exec(n.description),c=p?p[1]:i.replace(/_/g," ");x.has(i)||x.set(i,{key:i,label:c,tools:[]}),x.get(i).tools.push(n)}return{builtin:d,groups:[...x.values()].sort((n,a)=>n.label.localeCompare(a.label))}}function le(s){return s.replace(/^mcp__[^_]+(?:_[^_]+)*__/,"")}function re(s){return s.replace(/^\[[^\]]+\]\s*/,"")}function D({tool:s,checked:d,onToggle:x}){return e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[12px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:d,onChange:x}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"font-mono text-ink",children:le(s.name)}),s.description&&e.jsx("span",{className:"text-faint",children:re(s.description)})]})]})}function ie({group:s,selected:d,onToggleAll:x,onToggleOne:n}){const[a,i]=b.useState(!1),p=s.tools.map(l=>l.name),c=p.filter(l=>d.has(l)).length,o=c===p.length,g=c>0&&!o,u=b.useRef(null);return b.useEffect(()=>{u.current&&(u.current.indeterminate=g)},[g]),e.jsxs("div",{className:"rounded-md border border-border",children:[e.jsxs("div",{className:"flex items-center gap-2 px-2.5 py-2",children:[e.jsx("input",{ref:u,type:"checkbox",checked:o,onChange:()=>x(p,!o)}),e.jsxs("button",{type:"button",onClick:()=>{j("agents","toggle_agent_tool_group"),i(l=>!l)},className:"flex flex-1 items-center gap-1.5 text-left text-[13px] text-ink",children:[e.jsx(ne,{size:12,className:q("shrink-0 text-faint transition-transform",a&&"rotate-90")}),s.label,e.jsxs("span",{className:"text-[11px] text-faint",children:["(",c,"/",p.length,")"]})]})]}),a&&e.jsx("div",{className:"flex flex-col gap-1.5 border-t border-border px-3 py-2 pl-9",children:s.tools.map(l=>e.jsx(D,{tool:l,checked:d.has(l.name),onToggle:()=>n(l.name)},l.name))})]})}function ce({tools:s,selected:d,onChange:x}){const n=new Set(d),{builtin:a,groups:i}=oe(s),p=o=>x(n.has(o)?d.filter(g=>g!==o):[...d,o]),c=(o,g)=>{const u=d.filter(l=>!o.includes(l));x(g?[...u,...o]:u)};return s.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No tools available yet."}):e.jsxs("div",{className:"flex flex-col gap-2",children:[a.length>0&&e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:a.map(o=>e.jsx(D,{tool:o,checked:n.has(o.name),onToggle:()=>p(o.name)},o.name))}),i.map(o=>e.jsx(ie,{group:o,selected:n,onToggleAll:c,onToggleOne:p},o.key))]})}function je(){const{agentId:s="new"}=W(),d=$(),x=G(),n=s==="new",a=!n&&B.some(t=>t.id===s),i=a?B.find(t=>t.id===s):void 0,p=H(),c=!n&&!a?(p.data??[]).find(t=>t.id===s):void 0,o=Y(),g=J(),u=U(),l=a?g.data?.[s]:void 0,I=z({queryKey:Z.list(),queryFn:X,staleTime:0}),k=I.data??[],w=z({queryKey:["available-tools"],queryFn:V}),N=w.data??[],[m,f]=b.useState(ae()),[S,T]=b.useState(!1),[A,P]=b.useState(!1),[L,R]=b.useState(!1),[E,Q]=b.useState("skills");n&&!S&&(k.length||N.length)&&(f(t=>({...t,skillIds:k.map(r=>r.id),tools:N.map(r=>r.name)})),T(!0)),!n&&!a&&!S&&c&&(f({name:c.name,description:c.description,systemPrompt:c.systemPrompt,skillIds:c.skillIds,tools:c.tools}),T(!0)),a&&!S&&i&&g.isSuccess&&(k.length||N.length)&&(f({name:l?.name??i.name,description:l?.description??i.description,systemPrompt:l?.systemPrompt??i.systemPrompt,skillIds:l?.skillIds??k.map(t=>t.id),tools:l?.tools??N.map(t=>t.name)}),T(!0));const y=()=>{j("agents","back_to_agents"),d("/customize")},O=t=>f(r=>({...r,skillIds:r.skillIds.includes(t)?r.skillIds.filter(v=>v!==t):[...r.skillIds,t]})),F=()=>{if(!m.name.trim()){h.error("Name is required.");return}P(!0);const t={name:m.name.trim(),description:m.description.trim(),systemPrompt:m.systemPrompt,skillIds:m.skillIds,tools:m.tools};if(a){u.save.mutate({id:s,override:t},{onSuccess:()=>{h.success("Agent saved."),y()},onError:v=>{h.error(v instanceof _?v.message:"Could not save agent."),P(!1)}});return}const r={onSuccess:()=>{x.invalidateQueries({queryKey:["chat-agents"]}),h.success(n?"Agent created.":"Agent saved."),y()},onError:v=>{h.error(v instanceof _?v.message:"Could not save agent."),P(!1)}};n?o.add.mutate(t,r):o.update.mutate({id:s,patch:t},r)},K=()=>{u.reset.mutate(s,{onSuccess:()=>{h.success("Reset to default."),i&&f({name:i.name,description:i.description,systemPrompt:i.systemPrompt,skillIds:k.map(t=>t.id),tools:N.map(t=>t.name)})},onError:t=>h.error(t instanceof _?t.message:"Could not reset agent.")})},M=()=>{o.remove.mutate(s,{onSuccess:()=>{h.success("Agent deleted."),y()},onError:t=>h.error(t instanceof _?t.message:"Could not delete agent.")})};return!n&&!a&&p.isSuccess&&!c?e.jsxs("div",{className:"flex w-full flex-col items-center gap-3 px-8 py-16",children:[e.jsx("p",{className:"text-[14px] text-muted",children:"That agent no longer exists."}),e.jsx(C,{size:"sm",variant:"outline",onClick:y,children:"Back to agents"})]}):e.jsxs("div",{className:"flex w-full max-w-4xl flex-col gap-5 px-4 py-6 md:px-8",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",onClick:y,className:"text-faint hover:text-ink",title:"Back to agents",children:e.jsx(ee,{size:18})}),e.jsx("span",{className:"text-[15px] font-medium text-ink",children:n?"New agent":a?l?"Edit agent (modified built-in)":"Edit agent (built-in)":"Edit agent"}),e.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[a&&e.jsxs(C,{size:"sm",variant:"outline",onClick:()=>{j("agents","reset_agent_to_default"),K()},disabled:u.reset.isPending,children:[e.jsx(te,{size:13})," Reset to default"]}),e.jsx(C,{size:"sm",variant:"outline",onClick:y,children:"Cancel"}),e.jsx(C,{size:"sm",onClick:()=>{j("agents","save_agent"),F()},disabled:A||!m.name.trim(),children:A?"Saving…":"Save"})]})]}),a&&e.jsx("p",{className:"rounded-md border border-border bg-panel px-3 py-2 text-[12px] text-muted",children:"Built-in agents can be edited in place — Reset to default discards your changes and restores TurboLLM's original prompt and access."}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Name"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"Release Notes Writer",value:m.name,onChange:t=>f(r=>({...r,name:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Description"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"What this agent is for, in one line",value:m.description,onChange:t=>f(r=>({...r,description:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"System prompt"}),e.jsx("textarea",{className:"min-h-[160px] resize-y rounded-md border border-border bg-bg px-3 py-2 font-mono text-[12px] leading-relaxed text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"You are…",value:m.systemPrompt,onChange:t=>f(r=>({...r,systemPrompt:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-2.5",children:[e.jsx("div",{className:"inline-flex w-fit rounded-md border border-border p-0.5",children:["skills","tools"].map(t=>e.jsx("button",{type:"button",onClick:()=>{j("agents","switch_agent_form_tab"),Q(t)},className:q("rounded px-3 py-1.5 text-[13px] font-medium transition-colors",E===t?"bg-accent/12 text-accent":"text-muted hover:text-ink"),children:t==="skills"?`Skills (${m.skillIds.length})`:`Tools (${m.tools.length})`},t))}),E==="skills"?I.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):k.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No skills yet — create one under Customize → Skills."}):e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:k.map(t=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[13px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:m.skillIds.includes(t.id),onChange:()=>O(t.id)}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"text-ink",children:t.name}),t.description&&e.jsx("span",{className:"text-[12px] text-muted",children:t.description})]})]},t.id))}):w.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):e.jsx(ce,{tools:N,selected:m.tools,onChange:t=>f(r=>({...r,tools:t}))})]}),!n&&!a&&(L?e.jsxs("div",{className:"flex items-center gap-2 self-start rounded-md border border-border bg-panel p-2 text-[12px]",children:[e.jsx("span",{className:"flex-1 text-muted",children:"Delete this agent?"}),e.jsx("button",{type:"button",onClick:()=>{j("agents","delete_agent"),M()},className:"rounded px-2 py-1 text-[color:var(--err)] hover:bg-[color:color-mix(in_srgb,var(--err)_12%,transparent)]",children:"Delete"}),e.jsx("button",{type:"button",onClick:()=>{j("agents","cancel_delete_agent"),R(!1)},className:"rounded px-2 py-1 text-faint hover:text-ink",children:"Cancel"})]}):e.jsxs("button",{type:"button",onClick:()=>R(!0),className:"flex items-center gap-1.5 self-start text-[12px] text-faint hover:text-[color:var(--err)]",children:[e.jsx(se,{size:13})," Delete agent"]}))]})}export{je as AgentEditPage};
1
+ import{j as e}from"./radix-B0YBjihE.js";import{b as W,u as $,r as b}from"./vendor-C1I6euDx.js";import{u as G,d as H,e as Y,f as J,g as U,a as z,B as C,t as j,c as q,b as h,A as _}from"./index-CSavhRGL.js";import{f as V}from"./chat-api-B_SlDoXA.js";import{f as X,a as Z}from"./agent-api-BXUf3UuJ.js";import{P as B}from"./personas-CKCwxU6o.js";import{C as ee}from"./chevron-left-DHH-nnSd.js";import{R as te}from"./rotate-ccw-g_DuozeG.js";import{T as se}from"./trash-2-B__gvkzD.js";import{C as ne}from"./chevron-right-DnT71PVy.js";import"./dropdown-menu-BOUsiJco.js";const ae=()=>({name:"",description:"",systemPrompt:"",skillIds:[],tools:[]});function oe(s){const d=[],x=new Map;for(const n of s){const a=/^mcp__([^_]+(?:_[^_]+)*)__/.exec(n.name);if(!a){d.push(n);continue}const i=a[1],p=/^\[([^\]]+)\]/.exec(n.description),c=p?p[1]:i.replace(/_/g," ");x.has(i)||x.set(i,{key:i,label:c,tools:[]}),x.get(i).tools.push(n)}return{builtin:d,groups:[...x.values()].sort((n,a)=>n.label.localeCompare(a.label))}}function le(s){return s.replace(/^mcp__[^_]+(?:_[^_]+)*__/,"")}function re(s){return s.replace(/^\[[^\]]+\]\s*/,"")}function D({tool:s,checked:d,onToggle:x}){return e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[12px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:d,onChange:x}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"font-mono text-ink",children:le(s.name)}),s.description&&e.jsx("span",{className:"text-faint",children:re(s.description)})]})]})}function ie({group:s,selected:d,onToggleAll:x,onToggleOne:n}){const[a,i]=b.useState(!1),p=s.tools.map(l=>l.name),c=p.filter(l=>d.has(l)).length,o=c===p.length,g=c>0&&!o,u=b.useRef(null);return b.useEffect(()=>{u.current&&(u.current.indeterminate=g)},[g]),e.jsxs("div",{className:"rounded-md border border-border",children:[e.jsxs("div",{className:"flex items-center gap-2 px-2.5 py-2",children:[e.jsx("input",{ref:u,type:"checkbox",checked:o,onChange:()=>x(p,!o)}),e.jsxs("button",{type:"button",onClick:()=>{j("agents","toggle_agent_tool_group"),i(l=>!l)},className:"flex flex-1 items-center gap-1.5 text-left text-[13px] text-ink",children:[e.jsx(ne,{size:12,className:q("shrink-0 text-faint transition-transform",a&&"rotate-90")}),s.label,e.jsxs("span",{className:"text-[11px] text-faint",children:["(",c,"/",p.length,")"]})]})]}),a&&e.jsx("div",{className:"flex flex-col gap-1.5 border-t border-border px-3 py-2 pl-9",children:s.tools.map(l=>e.jsx(D,{tool:l,checked:d.has(l.name),onToggle:()=>n(l.name)},l.name))})]})}function ce({tools:s,selected:d,onChange:x}){const n=new Set(d),{builtin:a,groups:i}=oe(s),p=o=>x(n.has(o)?d.filter(g=>g!==o):[...d,o]),c=(o,g)=>{const u=d.filter(l=>!o.includes(l));x(g?[...u,...o]:u)};return s.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No tools available yet."}):e.jsxs("div",{className:"flex flex-col gap-2",children:[a.length>0&&e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:a.map(o=>e.jsx(D,{tool:o,checked:n.has(o.name),onToggle:()=>p(o.name)},o.name))}),i.map(o=>e.jsx(ie,{group:o,selected:n,onToggleAll:c,onToggleOne:p},o.key))]})}function je(){const{agentId:s="new"}=W(),d=$(),x=G(),n=s==="new",a=!n&&B.some(t=>t.id===s),i=a?B.find(t=>t.id===s):void 0,p=H(),c=!n&&!a?(p.data??[]).find(t=>t.id===s):void 0,o=Y(),g=J(),u=U(),l=a?g.data?.[s]:void 0,I=z({queryKey:Z.list(),queryFn:X,staleTime:0}),k=I.data??[],w=z({queryKey:["available-tools"],queryFn:V}),N=w.data??[],[m,f]=b.useState(ae()),[S,T]=b.useState(!1),[A,P]=b.useState(!1),[L,R]=b.useState(!1),[E,Q]=b.useState("skills");n&&!S&&(k.length||N.length)&&(f(t=>({...t,skillIds:k.map(r=>r.id),tools:N.map(r=>r.name)})),T(!0)),!n&&!a&&!S&&c&&(f({name:c.name,description:c.description,systemPrompt:c.systemPrompt,skillIds:c.skillIds,tools:c.tools}),T(!0)),a&&!S&&i&&g.isSuccess&&(k.length||N.length)&&(f({name:l?.name??i.name,description:l?.description??i.description,systemPrompt:l?.systemPrompt??i.systemPrompt,skillIds:l?.skillIds??k.map(t=>t.id),tools:l?.tools??N.map(t=>t.name)}),T(!0));const y=()=>{j("agents","back_to_agents"),d("/customize")},O=t=>f(r=>({...r,skillIds:r.skillIds.includes(t)?r.skillIds.filter(v=>v!==t):[...r.skillIds,t]})),F=()=>{if(!m.name.trim()){h.error("Name is required.");return}P(!0);const t={name:m.name.trim(),description:m.description.trim(),systemPrompt:m.systemPrompt,skillIds:m.skillIds,tools:m.tools};if(a){u.save.mutate({id:s,override:t},{onSuccess:()=>{h.success("Agent saved."),y()},onError:v=>{h.error(v instanceof _?v.message:"Could not save agent."),P(!1)}});return}const r={onSuccess:()=>{x.invalidateQueries({queryKey:["chat-agents"]}),h.success(n?"Agent created.":"Agent saved."),y()},onError:v=>{h.error(v instanceof _?v.message:"Could not save agent."),P(!1)}};n?o.add.mutate(t,r):o.update.mutate({id:s,patch:t},r)},K=()=>{u.reset.mutate(s,{onSuccess:()=>{h.success("Reset to default."),i&&f({name:i.name,description:i.description,systemPrompt:i.systemPrompt,skillIds:k.map(t=>t.id),tools:N.map(t=>t.name)})},onError:t=>h.error(t instanceof _?t.message:"Could not reset agent.")})},M=()=>{o.remove.mutate(s,{onSuccess:()=>{h.success("Agent deleted."),y()},onError:t=>h.error(t instanceof _?t.message:"Could not delete agent.")})};return!n&&!a&&p.isSuccess&&!c?e.jsxs("div",{className:"flex w-full flex-col items-center gap-3 px-8 py-16",children:[e.jsx("p",{className:"text-[14px] text-muted",children:"That agent no longer exists."}),e.jsx(C,{size:"sm",variant:"outline",onClick:y,children:"Back to agents"})]}):e.jsxs("div",{className:"flex w-full max-w-4xl flex-col gap-5 px-4 py-6 md:px-8",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",onClick:y,className:"text-faint hover:text-ink",title:"Back to agents",children:e.jsx(ee,{size:18})}),e.jsx("span",{className:"text-[15px] font-medium text-ink",children:n?"New agent":a?l?"Edit agent (modified built-in)":"Edit agent (built-in)":"Edit agent"}),e.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[a&&e.jsxs(C,{size:"sm",variant:"outline",onClick:()=>{j("agents","reset_agent_to_default"),K()},disabled:u.reset.isPending,children:[e.jsx(te,{size:13})," Reset to default"]}),e.jsx(C,{size:"sm",variant:"outline",onClick:y,children:"Cancel"}),e.jsx(C,{size:"sm",onClick:()=>{j("agents","save_agent"),F()},disabled:A||!m.name.trim(),children:A?"Saving…":"Save"})]})]}),a&&e.jsx("p",{className:"rounded-md border border-border bg-panel px-3 py-2 text-[12px] text-muted",children:"Built-in agents can be edited in place — Reset to default discards your changes and restores TurboLLM's original prompt and access."}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Name"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"Release Notes Writer",value:m.name,onChange:t=>f(r=>({...r,name:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Description"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"What this agent is for, in one line",value:m.description,onChange:t=>f(r=>({...r,description:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"System prompt"}),e.jsx("textarea",{className:"min-h-[160px] resize-y rounded-md border border-border bg-bg px-3 py-2 font-mono text-[12px] leading-relaxed text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"You are…",value:m.systemPrompt,onChange:t=>f(r=>({...r,systemPrompt:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-2.5",children:[e.jsx("div",{className:"inline-flex w-fit rounded-md border border-border p-0.5",children:["skills","tools"].map(t=>e.jsx("button",{type:"button",onClick:()=>{j("agents","switch_agent_form_tab"),Q(t)},className:q("rounded px-3 py-1.5 text-[13px] font-medium transition-colors",E===t?"bg-accent/12 text-accent":"text-muted hover:text-ink"),children:t==="skills"?`Skills (${m.skillIds.length})`:`Tools (${m.tools.length})`},t))}),E==="skills"?I.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):k.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No skills yet — create one under Customize → Skills."}):e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:k.map(t=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[13px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:m.skillIds.includes(t.id),onChange:()=>O(t.id)}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"text-ink",children:t.name}),t.description&&e.jsx("span",{className:"text-[12px] text-muted",children:t.description})]})]},t.id))}):w.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):e.jsx(ce,{tools:N,selected:m.tools,onChange:t=>f(r=>({...r,tools:t}))})]}),!n&&!a&&(L?e.jsxs("div",{className:"flex items-center gap-2 self-start rounded-md border border-border bg-panel p-2 text-[12px]",children:[e.jsx("span",{className:"flex-1 text-muted",children:"Delete this agent?"}),e.jsx("button",{type:"button",onClick:()=>{j("agents","delete_agent"),M()},className:"rounded px-2 py-1 text-[color:var(--err)] hover:bg-[color:color-mix(in_srgb,var(--err)_12%,transparent)]",children:"Delete"}),e.jsx("button",{type:"button",onClick:()=>{j("agents","cancel_delete_agent"),R(!1)},className:"rounded px-2 py-1 text-faint hover:text-ink",children:"Cancel"})]}):e.jsxs("button",{type:"button",onClick:()=>R(!0),className:"flex items-center gap-1.5 self-start text-[12px] text-faint hover:text-[color:var(--err)]",children:[e.jsx(se,{size:13})," Delete agent"]}))]})}export{je as AgentEditPage};