cli-jaw 2.1.0 → 2.1.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 (356) hide show
  1. package/README.ja.md +8 -6
  2. package/README.ko.md +8 -6
  3. package/README.md +12 -6
  4. package/README.zh-CN.md +8 -6
  5. package/dist/bin/cli-jaw.js +5 -1
  6. package/dist/bin/cli-jaw.js.map +1 -1
  7. package/dist/bin/commands/browser.js +1 -1
  8. package/dist/bin/commands/browser.js.map +1 -1
  9. package/dist/bin/commands/chat.js +98 -58
  10. package/dist/bin/commands/chat.js.map +1 -1
  11. package/dist/bin/commands/dispatch.js +130 -5
  12. package/dist/bin/commands/dispatch.js.map +1 -1
  13. package/dist/bin/commands/goal.js +18 -6
  14. package/dist/bin/commands/goal.js.map +1 -1
  15. package/dist/bin/commands/orchestrate.js.map +1 -1
  16. package/dist/bin/commands/tui/fullscreen-mode.js +237 -0
  17. package/dist/bin/commands/tui/fullscreen-mode.js.map +1 -0
  18. package/dist/bin/commands/tui/input-handler.js +139 -52
  19. package/dist/bin/commands/tui/input-handler.js.map +1 -1
  20. package/dist/bin/commands/tui/overlays.js +177 -70
  21. package/dist/bin/commands/tui/overlays.js.map +1 -1
  22. package/dist/bin/commands/tui/renderer.js +70 -20
  23. package/dist/bin/commands/tui/renderer.js.map +1 -1
  24. package/dist/bin/commands/tui/simple-mode.js +1 -1
  25. package/dist/bin/commands/tui/simple-mode.js.map +1 -1
  26. package/dist/bin/commands/tui/tui-io.js +8 -0
  27. package/dist/bin/commands/tui/tui-io.js.map +1 -0
  28. package/dist/bin/commands/tui/types.js +9 -0
  29. package/dist/bin/commands/tui/types.js.map +1 -1
  30. package/dist/bin/commands/tui/ws-handler.js +115 -23
  31. package/dist/bin/commands/tui/ws-handler.js.map +1 -1
  32. package/dist/bin/commands/worker.js +159 -0
  33. package/dist/bin/commands/worker.js.map +1 -0
  34. package/dist/bin/postinstall.js +0 -76
  35. package/dist/bin/postinstall.js.map +1 -1
  36. package/dist/lib/mcp/format-converters.js +21 -2
  37. package/dist/lib/mcp/format-converters.js.map +1 -1
  38. package/dist/lib/mcp/mcp-registry.js +75 -0
  39. package/dist/lib/mcp/mcp-registry.js.map +1 -0
  40. package/dist/lib/stt.js +1 -1
  41. package/dist/lib/stt.js.map +1 -1
  42. package/dist/scripts/fresh-install-smoke.js +1 -1
  43. package/dist/scripts/fresh-install-smoke.js.map +1 -1
  44. package/dist/server.js +27 -6
  45. package/dist/server.js.map +1 -1
  46. package/dist/src/agent/args.js +33 -1
  47. package/dist/src/agent/args.js.map +1 -1
  48. package/dist/src/agent/claude-e-runtime.js +1 -1
  49. package/dist/src/agent/claude-e-runtime.js.map +1 -1
  50. package/dist/src/agent/codex-app-client.js +1 -1
  51. package/dist/src/agent/error-classifier.js +6 -1
  52. package/dist/src/agent/error-classifier.js.map +1 -1
  53. package/dist/src/agent/events/claude.js +1 -0
  54. package/dist/src/agent/events/claude.js.map +1 -1
  55. package/dist/src/agent/events/codex.js +0 -44
  56. package/dist/src/agent/events/codex.js.map +1 -1
  57. package/dist/src/agent/events/helpers.js +52 -5
  58. package/dist/src/agent/events/helpers.js.map +1 -1
  59. package/dist/src/agent/events/opencode.js +6 -2
  60. package/dist/src/agent/events/opencode.js.map +1 -1
  61. package/dist/src/agent/events/summary.js +3 -2
  62. package/dist/src/agent/events/summary.js.map +1 -1
  63. package/dist/src/agent/events/tool-labels.js +8 -5
  64. package/dist/src/agent/events/tool-labels.js.map +1 -1
  65. package/dist/src/agent/kiro-auth.js +213 -0
  66. package/dist/src/agent/kiro-auth.js.map +1 -0
  67. package/dist/src/agent/kiro-models.js +79 -0
  68. package/dist/src/agent/kiro-models.js.map +1 -0
  69. package/dist/src/agent/kiro-runtime.js +306 -0
  70. package/dist/src/agent/kiro-runtime.js.map +1 -0
  71. package/dist/src/agent/lifecycle-handler.js +190 -12
  72. package/dist/src/agent/lifecycle-handler.js.map +1 -1
  73. package/dist/src/agent/memory-flush-controller.js +1 -3
  74. package/dist/src/agent/memory-flush-controller.js.map +1 -1
  75. package/dist/src/agent/resume-classifier.js +18 -1
  76. package/dist/src/agent/resume-classifier.js.map +1 -1
  77. package/dist/src/agent/session-persistence.js +1 -1
  78. package/dist/src/agent/session-persistence.js.map +1 -1
  79. package/dist/src/agent/smoke-detector.js +3 -0
  80. package/dist/src/agent/smoke-detector.js.map +1 -1
  81. package/dist/src/agent/spawn/queue.js +10 -4
  82. package/dist/src/agent/spawn/queue.js.map +1 -1
  83. package/dist/src/agent/spawn/resume.js +6 -1
  84. package/dist/src/agent/spawn/resume.js.map +1 -1
  85. package/dist/src/agent/spawn-env.js +1 -1
  86. package/dist/src/agent/spawn-env.js.map +1 -1
  87. package/dist/src/agent/spawn.js +188 -28
  88. package/dist/src/agent/spawn.js.map +1 -1
  89. package/dist/src/browser/web-ai/grok-live.js +2 -0
  90. package/dist/src/browser/web-ai/grok-live.js.map +1 -1
  91. package/dist/src/cli/claude-models.js +2 -2
  92. package/dist/src/cli/claude-models.js.map +1 -1
  93. package/dist/src/cli/commands.js +3 -2
  94. package/dist/src/cli/commands.js.map +1 -1
  95. package/dist/src/cli/handlers-runtime.js +2 -0
  96. package/dist/src/cli/handlers-runtime.js.map +1 -1
  97. package/dist/src/cli/handlers-workflows.js +9 -4
  98. package/dist/src/cli/handlers-workflows.js.map +1 -1
  99. package/dist/src/cli/handlers.js +47 -7
  100. package/dist/src/cli/handlers.js.map +1 -1
  101. package/dist/src/cli/readiness.js +17 -1
  102. package/dist/src/cli/readiness.js.map +1 -1
  103. package/dist/src/cli/registry-live.js +17 -0
  104. package/dist/src/cli/registry-live.js.map +1 -0
  105. package/dist/src/cli/registry.js +44 -3
  106. package/dist/src/cli/registry.js.map +1 -1
  107. package/dist/src/cli/tui/composer.js +135 -5
  108. package/dist/src/cli/tui/composer.js.map +1 -1
  109. package/dist/src/cli/tui/diffview.js +41 -0
  110. package/dist/src/cli/tui/diffview.js.map +1 -0
  111. package/dist/src/cli/tui/editor.js +45 -0
  112. package/dist/src/cli/tui/editor.js.map +1 -0
  113. package/dist/src/cli/tui/file-mention.js +78 -0
  114. package/dist/src/cli/tui/file-mention.js.map +1 -0
  115. package/dist/src/cli/tui/highlight.js +82 -0
  116. package/dist/src/cli/tui/highlight.js.map +1 -0
  117. package/dist/src/cli/tui/keymap.js +21 -0
  118. package/dist/src/cli/tui/keymap.js.map +1 -1
  119. package/dist/src/cli/tui/markdown.js +124 -0
  120. package/dist/src/cli/tui/markdown.js.map +1 -0
  121. package/dist/src/cli/tui/mode.js +26 -0
  122. package/dist/src/cli/tui/mode.js.map +1 -0
  123. package/dist/src/cli/tui/overlay.js +121 -98
  124. package/dist/src/cli/tui/overlay.js.map +1 -1
  125. package/dist/src/cli/tui/render/frame.js +72 -0
  126. package/dist/src/cli/tui/render/frame.js.map +1 -0
  127. package/dist/src/cli/tui/render/layout.js +22 -0
  128. package/dist/src/cli/tui/render/layout.js.map +1 -0
  129. package/dist/src/cli/tui/render/mouse.js +25 -0
  130. package/dist/src/cli/tui/render/mouse.js.map +1 -0
  131. package/dist/src/cli/tui/render/scheduler.js +37 -0
  132. package/dist/src/cli/tui/render/scheduler.js.map +1 -0
  133. package/dist/src/cli/tui/render/viewport.js +82 -0
  134. package/dist/src/cli/tui/render/viewport.js.map +1 -0
  135. package/dist/src/cli/tui/renderers.js +37 -0
  136. package/dist/src/cli/tui/renderers.js.map +1 -1
  137. package/dist/src/cli/tui/stream.js +48 -0
  138. package/dist/src/cli/tui/stream.js.map +1 -0
  139. package/dist/src/cli/tui/text-buffer.js +143 -0
  140. package/dist/src/cli/tui/text-buffer.js.map +1 -0
  141. package/dist/src/cli/tui/theme.js +128 -0
  142. package/dist/src/cli/tui/theme.js.map +1 -0
  143. package/dist/src/cli/tui/transcript.js +4 -0
  144. package/dist/src/cli/tui/transcript.js.map +1 -1
  145. package/dist/src/command-contract/help-renderer.js +1 -1
  146. package/dist/src/command-contract/help-renderer.js.map +1 -1
  147. package/dist/src/core/cli-detection.js +14 -0
  148. package/dist/src/core/cli-detection.js.map +1 -1
  149. package/dist/src/core/config.js +5 -0
  150. package/dist/src/core/config.js.map +1 -1
  151. package/dist/src/core/db.js +7 -0
  152. package/dist/src/core/db.js.map +1 -1
  153. package/dist/src/core/employees.js +2 -2
  154. package/dist/src/core/employees.js.map +1 -1
  155. package/dist/src/discord/bot.js.map +1 -1
  156. package/dist/src/discord/discord-file.js +1 -1
  157. package/dist/src/discord/discord-file.js.map +1 -1
  158. package/dist/src/discord/send-only-client.js +1 -3
  159. package/dist/src/discord/send-only-client.js.map +1 -1
  160. package/dist/src/goal/heartbeat.js +30 -7
  161. package/dist/src/goal/heartbeat.js.map +1 -1
  162. package/dist/src/goal/store.js +6 -2
  163. package/dist/src/goal/store.js.map +1 -1
  164. package/dist/src/ide/diff.js +37 -0
  165. package/dist/src/ide/diff.js.map +1 -1
  166. package/dist/src/manager/launchd-service.js +1 -1
  167. package/dist/src/manager/launchd-service.js.map +1 -1
  168. package/dist/src/manager/lifecycle-helpers.js +14 -0
  169. package/dist/src/manager/lifecycle-helpers.js.map +1 -1
  170. package/dist/src/manager/lifecycle.js +49 -4
  171. package/dist/src/manager/lifecycle.js.map +1 -1
  172. package/dist/src/manager/notes/routes.js +0 -31
  173. package/dist/src/manager/notes/routes.js.map +1 -1
  174. package/dist/src/manager/notes/ws.js.map +1 -1
  175. package/dist/src/manager/registry.js.map +1 -1
  176. package/dist/src/manager/routes/dashboard-memory.js.map +1 -1
  177. package/dist/src/manager/scan.js +36 -1
  178. package/dist/src/manager/scan.js.map +1 -1
  179. package/dist/src/manager/server.js +21 -2
  180. package/dist/src/manager/server.js.map +1 -1
  181. package/dist/src/manager/systemd-service.js +1 -1
  182. package/dist/src/manager/systemd-service.js.map +1 -1
  183. package/dist/src/memory/keyword-expand.js +0 -187
  184. package/dist/src/memory/keyword-expand.js.map +1 -1
  185. package/dist/src/memory/runtime.js +2 -2
  186. package/dist/src/memory/runtime.js.map +1 -1
  187. package/dist/src/memory/shared.js.map +1 -1
  188. package/dist/src/orchestrator/collect.js +3 -2
  189. package/dist/src/orchestrator/collect.js.map +1 -1
  190. package/dist/src/orchestrator/distribute.js +7 -1
  191. package/dist/src/orchestrator/distribute.js.map +1 -1
  192. package/dist/src/orchestrator/friction.js +59 -0
  193. package/dist/src/orchestrator/friction.js.map +1 -0
  194. package/dist/src/orchestrator/pipeline.js +88 -45
  195. package/dist/src/orchestrator/pipeline.js.map +1 -1
  196. package/dist/src/orchestrator/sanitize.js +19 -0
  197. package/dist/src/orchestrator/sanitize.js.map +1 -0
  198. package/dist/src/orchestrator/seed.js +50 -0
  199. package/dist/src/orchestrator/seed.js.map +1 -0
  200. package/dist/src/orchestrator/state-machine.js +206 -74
  201. package/dist/src/orchestrator/state-machine.js.map +1 -1
  202. package/dist/src/orchestrator/worker-monitor.js +1 -1
  203. package/dist/src/orchestrator/worker-monitor.js.map +1 -1
  204. package/dist/src/orchestrator/worker-progress.js +29 -0
  205. package/dist/src/orchestrator/worker-progress.js.map +1 -0
  206. package/dist/src/orchestrator/worker-registry.js +63 -2
  207. package/dist/src/orchestrator/worker-registry.js.map +1 -1
  208. package/dist/src/prompt/builder.js +19 -29
  209. package/dist/src/prompt/builder.js.map +1 -1
  210. package/dist/src/prompt/templates/a1-system.md +40 -5
  211. package/dist/src/prompt/templates/control-system.md +2 -2
  212. package/dist/src/prompt/templates/employee.md +4 -1
  213. package/dist/src/prompt/templates/orchestration.md +23 -8
  214. package/dist/src/routes/employees.js +4 -4
  215. package/dist/src/routes/employees.js.map +1 -1
  216. package/dist/src/routes/goal.js +11 -3
  217. package/dist/src/routes/goal.js.map +1 -1
  218. package/dist/src/routes/heartbeat.js +1 -1
  219. package/dist/src/routes/heartbeat.js.map +1 -1
  220. package/dist/src/routes/i18n.js +1 -1
  221. package/dist/src/routes/i18n.js.map +1 -1
  222. package/dist/src/routes/jaw-memory.js +1 -1
  223. package/dist/src/routes/jaw-memory.js.map +1 -1
  224. package/dist/src/routes/memory.js.map +1 -1
  225. package/dist/src/routes/messaging.js.map +1 -1
  226. package/dist/src/routes/orchestrate.js +265 -116
  227. package/dist/src/routes/orchestrate.js.map +1 -1
  228. package/dist/src/routes/quota-agy-reverse.js +0 -5
  229. package/dist/src/routes/quota-agy-reverse.js.map +1 -1
  230. package/dist/src/routes/quota-kiro-reverse.js +187 -0
  231. package/dist/src/routes/quota-kiro-reverse.js.map +1 -0
  232. package/dist/src/routes/settings.js +31 -2
  233. package/dist/src/routes/settings.js.map +1 -1
  234. package/dist/src/routes/skills.js.map +1 -1
  235. package/dist/src/security/security-audit-log.js.map +1 -1
  236. package/dist/src/shared/shell-command-display.js +51 -0
  237. package/dist/src/shared/shell-command-display.js.map +1 -0
  238. package/dist/src/telegram/bot.js.map +1 -1
  239. package/dist/src/trace/store.js +32 -0
  240. package/dist/src/trace/store.js.map +1 -1
  241. package/dist/src/types/cli-engine.js +1 -0
  242. package/dist/src/types/cli-engine.js.map +1 -1
  243. package/dist/src/workflows/employee-boundary.js +7 -7
  244. package/dist/src/workflows/employee-boundary.js.map +1 -1
  245. package/dist/src/workflows/handoff.js +16 -16
  246. package/dist/src/workflows/handoff.js.map +1 -1
  247. package/dist/src/workflows/scope-sandbox.js +46 -0
  248. package/dist/src/workflows/scope-sandbox.js.map +1 -0
  249. package/package.json +1 -1
  250. package/public/assets/providers/kiro-color.svg +15 -0
  251. package/public/assets/providers/kiro.svg +14 -0
  252. package/public/css/modals.css +79 -0
  253. package/public/css/orc-state.css +21 -2
  254. package/public/css/sidebar.css +85 -0
  255. package/public/css/variables.css +2 -2
  256. package/public/dist/assets/{Agent-CIUGaUVn.js → Agent-DkH7eoHd.js} +1 -1
  257. package/public/dist/assets/{DocPanel-C1pGgE-S.js → DocPanel-BU16GUlB.js} +1 -1
  258. package/public/dist/assets/DocPanel-gU-WkgyA.css +1 -0
  259. package/public/dist/assets/Employees-F0ssNuO-.js +1 -0
  260. package/public/dist/assets/Heartbeat-C3JS6gkF.js +1 -0
  261. package/public/dist/assets/Mcp-_Yq4N3Sk.js +4 -0
  262. package/public/dist/assets/{Memory-BgN8djV4.js → Memory-C-EQubN2.js} +1 -1
  263. package/public/dist/assets/{ModelProvider-DHQ1Zvw0.js → ModelProvider-BD2KrypI.js} +1 -1
  264. package/public/dist/assets/agent-meta-B1098B_a.js +1 -0
  265. package/public/dist/assets/app-ByHYOMZE.js +48 -0
  266. package/public/dist/assets/app-CYdhP6Vh.css +1 -0
  267. package/public/dist/assets/constants-s2UJodER.js +1 -0
  268. package/public/dist/assets/{employees-DZSPGlaH.js → employees-_A-p_bZg.js} +7 -7
  269. package/public/dist/assets/idb-cache-0LNMskFB.js +1 -0
  270. package/public/dist/assets/idb-cache-5H89a4l8.js +1 -0
  271. package/public/dist/assets/{manager-DyrwJLZr.css → manager-B2qEQRxN.css} +1 -1
  272. package/public/dist/assets/manager-CR9BA-wO.js +12 -0
  273. package/public/dist/assets/memory-D1RKYvyu.js +1 -0
  274. package/public/dist/assets/{memory-B4RpXXJ7.js → memory-D9AUn8fG.js} +9 -9
  275. package/public/dist/assets/{provider-icons-XEfJPGTe.js → provider-icons-CVVK5xUP.js} +35 -6
  276. package/public/dist/assets/{render-DnylWKO1.js → render-DFBujF8n.js} +4 -4
  277. package/public/dist/assets/settings-B4ZkeaU-.js +1 -0
  278. package/public/dist/assets/settings-D9jTceN0.js +151 -0
  279. package/public/dist/assets/sidebar-DPPRNiTc.js +48 -0
  280. package/public/dist/assets/skills-8nHJkv-r.js +1 -0
  281. package/public/dist/assets/{skills-CW7NEhew.js → skills-BDVLIrVT.js} +6 -6
  282. package/public/dist/assets/{slash-commands-CAydTUgf.js → slash-commands-C5da3q1p.js} +1 -1
  283. package/public/dist/assets/slash-commands-RJWO8wJZ.js +1 -0
  284. package/public/dist/assets/{trace-drawer-G-KP3IbQ.js → trace-drawer-5kqBzFMk.js} +1 -1
  285. package/public/dist/assets/ui-Crnp79bG.js +142 -0
  286. package/public/dist/assets/ui-HtSKByR3.js +1 -0
  287. package/public/dist/index.html +113 -86
  288. package/public/dist/manager/index.html +2 -2
  289. package/public/index.html +111 -84
  290. package/public/js/constants.ts +30 -7
  291. package/public/js/features/chat-search.ts +5 -1
  292. package/public/js/features/employees.ts +7 -2
  293. package/public/js/features/idb-cache.ts +4 -0
  294. package/public/js/features/message-history.ts +55 -7
  295. package/public/js/features/pending-queue.ts +8 -2
  296. package/public/js/features/process-block.ts +32 -17
  297. package/public/js/features/process-log-adapter.ts +11 -2
  298. package/public/js/features/settings-cli-status-render.ts +163 -0
  299. package/public/js/features/settings-cli-status.ts +130 -185
  300. package/public/js/features/settings-core.ts +5 -1
  301. package/public/js/features/settings-mcp.ts +513 -7
  302. package/public/js/features/settings.ts +1 -1
  303. package/public/js/features/tool-ui.ts +18 -4
  304. package/public/js/features/transport-status-row.ts +3 -0
  305. package/public/js/main.ts +3 -0
  306. package/public/js/preview-parent-origin.ts +14 -0
  307. package/public/js/provider-icons.ts +6 -1
  308. package/public/js/render/file-links.ts +10 -2
  309. package/public/js/ui.ts +5 -2
  310. package/public/js/virtual-scroll.ts +19 -0
  311. package/public/js/ws.ts +163 -10
  312. package/public/locales/en.json +8 -2
  313. package/public/locales/ja.json +8 -2
  314. package/public/locales/ko.json +8 -2
  315. package/public/locales/zh.json +8 -2
  316. package/public/manager/src/App.tsx +13 -70
  317. package/public/manager/src/InstancePreview.tsx +36 -11
  318. package/public/manager/src/SidebarRailRouter.tsx +1 -1
  319. package/public/manager/src/doc-panel/DocPanel.tsx +14 -1
  320. package/public/manager/src/doc-panel/doc-panel.css +16 -0
  321. package/public/manager/src/hooks/useInstanceMessageEvents.ts +4 -15
  322. package/public/manager/src/manager-components.css +1 -1
  323. package/public/manager/src/manager-polish.css +1 -1
  324. package/public/manager/src/manager-shortcut-runner.ts +107 -0
  325. package/public/manager/src/manager-shortcuts.ts +15 -0
  326. package/public/manager/src/panels/desktop-bridge.ts +2 -0
  327. package/public/manager/src/settings/SettingsSidebar.tsx +1 -1
  328. package/public/manager/src/settings/components/sidebar-filter.ts +2 -0
  329. package/public/manager/src/settings/pages/Mcp.tsx +311 -43
  330. package/public/manager/src/settings/pages/components/McpServerCard.tsx +89 -55
  331. package/public/manager/src/settings/pages/components/agent/agent-meta.ts +29 -6
  332. package/public/manager/src/settings/pages/components/employees-helpers.ts +1 -0
  333. package/public/manager/src/settings/pages/components/heartbeat-helpers.ts +1 -0
  334. package/public/manager/src/settings/pages/mcp-helpers.ts +54 -12
  335. package/public/manager/src/settings/types.ts +1 -0
  336. package/public/manager/src/types.ts +2 -1
  337. package/scripts/fresh-install-smoke.ts +1 -1
  338. package/public/dist/assets/DocPanel-CL1scIfq.css +0 -1
  339. package/public/dist/assets/Employees-BL9MAzzx.js +0 -1
  340. package/public/dist/assets/Heartbeat-D64JCg4t.js +0 -1
  341. package/public/dist/assets/Mcp-wYwJA_61.js +0 -3
  342. package/public/dist/assets/agent-meta-BhEbjy4P.js +0 -1
  343. package/public/dist/assets/app-DLDdZ41k.js +0 -48
  344. package/public/dist/assets/app-RpnKs-sT.css +0 -1
  345. package/public/dist/assets/constants-ahKI_aEG.js +0 -1
  346. package/public/dist/assets/idb-cache-BnZfG5FD.js +0 -1
  347. package/public/dist/assets/idb-cache-CZ3JdK8r.js +0 -1
  348. package/public/dist/assets/manager-CfyZloIP.js +0 -12
  349. package/public/dist/assets/memory-C9EVOb_D.js +0 -1
  350. package/public/dist/assets/settings-DIG_i79X.js +0 -1
  351. package/public/dist/assets/settings-VCXKhhhz.js +0 -66
  352. package/public/dist/assets/sidebar-Bi_ktFVw.js +0 -18
  353. package/public/dist/assets/skills-RjRqvykb.js +0 -1
  354. package/public/dist/assets/slash-commands-DdXu96Zd.js +0 -1
  355. package/public/dist/assets/ui-CIQjTLzc.js +0 -1
  356. package/public/dist/assets/ui-CkHB9Jmo.js +0 -140
@@ -1,28 +1,58 @@
1
- // ── MCP Server Settings ──
2
1
  import { api, apiJson } from '../api.js';
3
2
  import { escapeHtml } from '../render.js';
4
3
  import { t } from './i18n.js';
5
4
  import { ICONS } from '../icons.js';
6
5
 
7
- interface McpData { servers: Record<string, { command: string; args?: string[] }>; }
6
+ interface McpServer { command?: string; args?: string[]; url?: string; headers?: Record<string, string>; env?: Record<string, string>; }
7
+ interface McpData { servers: Record<string, McpServer>; }
8
8
  interface McpSyncResult { results: Record<string, boolean>; }
9
9
  interface McpInstallEntry { status: string; bin?: string; }
10
10
  interface McpInstallResult { results: Record<string, McpInstallEntry>; }
11
11
 
12
+ function getServerTag(s: McpServer): string | null {
13
+ if (s.url) return 'remote';
14
+ if (!s.command) return null;
15
+ const cmd = s.command;
16
+ if (cmd === 'npx' || cmd.endsWith('/npx')) return 'npx';
17
+ if (cmd === 'uvx' || cmd === 'uv' || cmd.endsWith('/uvx')) return 'uvx';
18
+ if (cmd === 'docker' || cmd.endsWith('/docker')) return 'docker';
19
+ return null;
20
+ }
21
+
22
+ function countBundleCandidates(servers: Record<string, McpServer>): number {
23
+ return Object.values(servers).filter(s =>
24
+ s.command === 'npx' || s.command === 'uv' || s.command === 'uvx'
25
+ ).length;
26
+ }
27
+
28
+ let cachedConfig: McpData | null = null;
29
+
12
30
  export async function loadMcpServers(): Promise<void> {
13
31
  try {
14
32
  const d = await api<McpData>('/api/mcp');
15
33
  if (!d) return;
34
+ cachedConfig = d;
16
35
  const el = document.getElementById('mcpServerList');
17
36
  if (!el) return;
18
- const names = Object.entries(d.servers || {});
19
- if (!names.length) { el.textContent = t('mcp.noServers'); return; }
20
- el.innerHTML = names.map(([n, s]) =>
21
- `<div style="padding:2px 0">• <b>${escapeHtml(n)}</b> <span style="opacity:.6">${escapeHtml(s.command)} ${(s.args || []).slice(0, 2).map(a => escapeHtml(a)).join(' ')}</span></div>`
22
- ).join('');
37
+ const entries = Object.entries(d.servers || {});
38
+ if (!entries.length) { el.textContent = t('mcp.noServers'); updateBundleLabel(0); return; }
39
+ el.innerHTML = entries.map(([n, s]) => {
40
+ const tag = getServerTag(s);
41
+ const detail = s.url || [s.command, ...(s.args || []).slice(0, 2)].filter(Boolean).map(a => escapeHtml(a!)).join(' ');
42
+ const tagHtml = tag ? ` <span style="opacity:.5;font-size:0.85em">[${escapeHtml(tag)}]</span>` : '';
43
+ return `<div style="padding:2px 0">• <b>${escapeHtml(n)}</b> <span style="opacity:.6">${detail}</span>${tagHtml}</div>`;
44
+ }).join('');
45
+ updateBundleLabel(countBundleCandidates(d.servers));
23
46
  } catch { }
24
47
  }
25
48
 
49
+ function updateBundleLabel(n: number): void {
50
+ const lbl = document.getElementById('installBundleLabel');
51
+ if (lbl) lbl.textContent = `Install bundle (${n})`;
52
+ const btn = lbl?.closest('button');
53
+ if (btn) (btn as HTMLButtonElement).disabled = n === 0;
54
+ }
55
+
26
56
  export async function syncMcpServers(): Promise<void> {
27
57
  const resultEl = document.getElementById('mcpSyncResult');
28
58
  if (!resultEl) return;
@@ -53,3 +83,479 @@ export async function installMcpGlobal(): Promise<void> {
53
83
  loadMcpServers();
54
84
  } catch (e) { resultEl.innerHTML = `${ICONS.error} ${escapeHtml((e as Error).message)}`; }
55
85
  }
86
+
87
+ // ── MCP Modal (dynamic, document.body) ──
88
+
89
+ let overlay: HTMLDivElement | null = null;
90
+ let bodyEl: HTMLDivElement | null = null;
91
+ let activeListEl: HTMLDivElement | null = null;
92
+ let addFormEl: HTMLDivElement | null = null;
93
+ let addErrorEl: HTMLDivElement | null = null;
94
+ let browseEl: HTMLDivElement | null = null;
95
+ let currentSubTab: 'local' | 'remote' = 'local';
96
+ let openState = false;
97
+ let tabBtns: HTMLButtonElement[] = [];
98
+
99
+ function ensureModal(): void {
100
+ if (overlay) return;
101
+
102
+ overlay = document.createElement('div');
103
+ overlay.id = 'mcpModal';
104
+ overlay.className = 'modal-overlay mcp-modal-overlay';
105
+ overlay.setAttribute('role', 'presentation');
106
+ overlay.setAttribute('aria-hidden', 'true');
107
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) closeMcpModal(); });
108
+
109
+ const box = document.createElement('div');
110
+ box.className = 'modal-box mcp-modal-box';
111
+ box.setAttribute('role', 'dialog');
112
+ box.setAttribute('aria-modal', 'true');
113
+ box.setAttribute('aria-label', 'MCP Server Management');
114
+ box.addEventListener('click', (e) => e.stopPropagation());
115
+
116
+ const header = document.createElement('div');
117
+ header.className = 'modal-header';
118
+ const titleSpan = document.createElement('span');
119
+ titleSpan.textContent = 'MCP Server Management';
120
+ const helpBtn = document.createElement('button');
121
+ helpBtn.type = 'button';
122
+ helpBtn.className = 'help-trigger';
123
+ helpBtn.textContent = '?';
124
+ helpBtn.setAttribute('aria-label', 'MCP help');
125
+ helpBtn.addEventListener('click', () => showMcpHelp());
126
+ const closeBtn = document.createElement('button');
127
+ closeBtn.type = 'button';
128
+ closeBtn.className = 'btn-modal-close';
129
+ closeBtn.setAttribute('aria-label', 'Close');
130
+ closeBtn.textContent = 'x';
131
+ closeBtn.addEventListener('click', () => closeMcpModal());
132
+ const headerLeft = document.createElement('div');
133
+ headerLeft.style.cssText = 'display:flex;align-items:center;gap:6px';
134
+ headerLeft.append(titleSpan, helpBtn);
135
+ header.append(headerLeft, closeBtn);
136
+
137
+ bodyEl = document.createElement('div');
138
+ bodyEl.style.cssText = 'padding:16px;overflow-y:auto;flex:1';
139
+
140
+ const tabs = document.createElement('div');
141
+ tabs.style.cssText = 'display:flex;gap:4px;margin-bottom:12px';
142
+ const tabActive = createTabBtn('Active Servers', true);
143
+ const tabAdd = createTabBtn('Add New', false);
144
+ const tabBrowse = createTabBtn('Browse', false);
145
+ tabBtns = [tabActive, tabAdd, tabBrowse];
146
+ tabActive.addEventListener('click', () => switchTab('active'));
147
+ tabAdd.addEventListener('click', () => switchTab('add'));
148
+ tabBrowse.addEventListener('click', () => switchTab('browse'));
149
+ tabs.append(tabActive, tabAdd, tabBrowse);
150
+
151
+ activeListEl = document.createElement('div');
152
+
153
+ browseEl = document.createElement('div');
154
+ browseEl.style.display = 'none';
155
+ browseEl.innerHTML = '<p style="opacity:.5">Loading registry...</p>';
156
+
157
+ addFormEl = document.createElement('div');
158
+ addFormEl.style.display = 'none';
159
+ addFormEl.innerHTML = buildAddFormHTML();
160
+ addFormEl.querySelectorAll<HTMLButtonElement>('[data-mcp-subtab]').forEach(btn => {
161
+ btn.addEventListener('click', () => {
162
+ currentSubTab = btn.dataset['mcpSubtab'] as 'local' | 'remote';
163
+ switchSubTab();
164
+ });
165
+ });
166
+ addErrorEl = addFormEl.querySelector('#mcpAddError');
167
+ const addBtn = addFormEl.querySelector('[data-action="mcpAddAndSync"]');
168
+ if (addBtn) addBtn.addEventListener('click', () => void handleAddAndSync());
169
+ const cancelBtn = addFormEl.querySelector('[data-action="closeMcpModal"]');
170
+ if (cancelBtn) cancelBtn.addEventListener('click', () => closeMcpModal());
171
+
172
+ bodyEl.append(tabs, activeListEl, browseEl, addFormEl);
173
+
174
+ const footer = document.createElement('div');
175
+ footer.className = 'modal-footer';
176
+ const doneBtn = document.createElement('button');
177
+ doneBtn.type = 'button';
178
+ doneBtn.className = 'btn-save';
179
+ doneBtn.textContent = 'Close';
180
+ doneBtn.addEventListener('click', () => closeMcpModal());
181
+ footer.append(doneBtn);
182
+
183
+ box.append(header, bodyEl, footer);
184
+ overlay.append(box);
185
+ document.body.append(overlay);
186
+ }
187
+
188
+ function createTabBtn(label: string, active: boolean): HTMLButtonElement {
189
+ const btn = document.createElement('button');
190
+ btn.type = 'button';
191
+ btn.className = `btn-clear btn-tab${active ? ' is-active' : ''}`;
192
+ btn.textContent = label;
193
+ return btn;
194
+ }
195
+
196
+ function buildAddFormHTML(): string {
197
+ return `
198
+ <div style="display:flex;gap:4px;margin-bottom:8px">
199
+ <button type="button" class="btn-clear btn-tab is-active" data-mcp-subtab="local">Local</button>
200
+ <button type="button" class="btn-clear btn-tab" data-mcp-subtab="remote">Remote</button>
201
+ </div>
202
+ <div id="mcpAddLocal">
203
+ <div class="settings-row"><label>Name</label><input type="text" id="mcpAddName" placeholder="my-server"></div>
204
+ <div class="settings-row"><label>Command</label><input type="text" id="mcpAddCommand" placeholder="npx"></div>
205
+ <div class="settings-row"><label>Args (comma separated)</label><input type="text" id="mcpAddArgs" placeholder="-y, @upstash/context7-mcp"></div>
206
+ <div class="settings-row"><label>Env (KEY=value per line)</label><textarea id="mcpAddEnv" rows="2" placeholder="API_KEY=xxx"></textarea></div>
207
+ </div>
208
+ <div id="mcpAddRemote" style="display:none">
209
+ <div class="settings-row"><label>Name</label><input type="text" id="mcpAddRemoteName" placeholder="my-api"></div>
210
+ <div class="settings-row"><label>URL</label><input type="text" id="mcpAddUrl" placeholder="https://mcp.example.com/sse"></div>
211
+ <div class="settings-row"><label>Headers (KEY=value per line)</label><textarea id="mcpAddHeaders" rows="2"></textarea></div>
212
+ </div>
213
+ <div id="mcpAddError" class="text-error text-xs py-1" style="display:none"></div>
214
+ <div style="display:flex;gap:8px;margin-top:12px;justify-content:flex-end">
215
+ <button type="button" class="btn-clear" data-action="closeMcpModal">Cancel</button>
216
+ <button type="button" class="btn-clear btn-save" data-action="mcpAddAndSync">Add & Sync</button>
217
+ </div>`;
218
+ }
219
+
220
+ function switchTab(tab: 'active' | 'add' | 'browse'): void {
221
+ if (activeListEl) activeListEl.style.display = tab === 'active' ? '' : 'none';
222
+ if (addFormEl) addFormEl.style.display = tab === 'add' ? '' : 'none';
223
+ if (browseEl) browseEl.style.display = tab === 'browse' ? '' : 'none';
224
+ tabBtns.forEach((btn, i) => {
225
+ const t = ['active', 'add', 'browse'][i];
226
+ btn.classList.toggle('is-active', t === tab);
227
+ });
228
+ if (tab === 'active') renderActiveList();
229
+ if (tab === 'add') clearAddInputs();
230
+ if (tab === 'browse') void loadRegistry();
231
+ }
232
+
233
+ function switchSubTab(): void {
234
+ const local = addFormEl?.querySelector('#mcpAddLocal') as HTMLElement | null;
235
+ const remote = addFormEl?.querySelector('#mcpAddRemote') as HTMLElement | null;
236
+ if (local) local.style.display = currentSubTab === 'local' ? '' : 'none';
237
+ if (remote) remote.style.display = currentSubTab === 'remote' ? '' : 'none';
238
+ addFormEl?.querySelectorAll<HTMLButtonElement>('[data-mcp-subtab]').forEach(btn => {
239
+ btn.classList.toggle('is-active', btn.dataset['mcpSubtab'] === currentSubTab);
240
+ });
241
+ }
242
+
243
+ function renderActiveList(): void {
244
+ if (!activeListEl || !cachedConfig) return;
245
+ const entries = Object.entries(cachedConfig.servers || {});
246
+ if (!entries.length) { activeListEl.innerHTML = '<p style="opacity:.5">No active MCP servers.</p>'; return; }
247
+ activeListEl.innerHTML = entries.map(([name, srv]) => {
248
+ const tag = getServerTag(srv);
249
+ const tagHtml = tag ? `<span class="mcp-active-tag">[${escapeHtml(tag)}]</span>` : '';
250
+ const cmdLine = srv.command ? escapeHtml(srv.command) + (srv.args?.length ? ' ' + srv.args.map(a => escapeHtml(a)).join(' ') : '') : '';
251
+ const urlLine = srv.url ? escapeHtml(srv.url) : '';
252
+ const envKeys = srv.env ? Object.keys(srv.env) : [];
253
+ const headerKeys = srv.headers ? Object.keys(srv.headers) : [];
254
+
255
+ let detailHtml = '';
256
+ if (urlLine) detailHtml += `<div class="mcp-active-detail">URL: ${urlLine}</div>`;
257
+ if (cmdLine) detailHtml += `<div class="mcp-active-detail">Command: ${cmdLine}</div>`;
258
+ if (envKeys.length) detailHtml += `<div class="mcp-active-detail">Env: ${envKeys.map(k => escapeHtml(k)).join(', ')}</div>`;
259
+ if (headerKeys.length) detailHtml += `<div class="mcp-active-detail">Headers: ${headerKeys.map(k => escapeHtml(k)).join(', ')}</div>`;
260
+
261
+ return `<div class="mcp-active-row">
262
+ <div style="flex:1">
263
+ <div style="display:flex;align-items:center;gap:6px">
264
+ <span class="mcp-active-name">${escapeHtml(name)}</span>
265
+ ${tagHtml}
266
+ </div>
267
+ ${detailHtml}
268
+ </div>
269
+ <button type="button" class="btn-clear mcp-active-remove" data-mcp-remove="${escapeHtml(name)}">Remove</button>
270
+ </div>`;
271
+ }).join('');
272
+ activeListEl.querySelectorAll<HTMLButtonElement>('[data-mcp-remove]').forEach(btn => {
273
+ btn.addEventListener('click', () => {
274
+ const name = btn.dataset['mcpRemove'];
275
+ if (name) void removeMcpServer(name);
276
+ });
277
+ });
278
+ }
279
+
280
+ async function removeMcpServer(name: string): Promise<void> {
281
+ if (!cachedConfig) return;
282
+ const nextServers = { ...cachedConfig.servers };
283
+ delete nextServers[name];
284
+ try {
285
+ await apiJson('/api/mcp', 'PUT', { ...cachedConfig, servers: nextServers });
286
+ await apiJson('/api/mcp/sync', 'POST', {});
287
+ await loadMcpServers();
288
+ renderActiveList();
289
+ } catch (e) {
290
+ showAddError((e as Error).message);
291
+ }
292
+ }
293
+
294
+ function clearAddInputs(): void {
295
+ const ids = ['mcpAddName', 'mcpAddCommand', 'mcpAddArgs', 'mcpAddEnv', 'mcpAddRemoteName', 'mcpAddUrl', 'mcpAddHeaders'];
296
+ for (const id of ids) {
297
+ const el = (addFormEl ?? document).querySelector(`#${id}`) as HTMLInputElement | HTMLTextAreaElement | null;
298
+ if (el) el.value = '';
299
+ }
300
+ hideAddError();
301
+ }
302
+
303
+ function showAddError(msg: string): void {
304
+ if (!addErrorEl) return;
305
+ addErrorEl.style.display = 'block';
306
+ addErrorEl.textContent = msg;
307
+ }
308
+
309
+ function hideAddError(): void {
310
+ if (addErrorEl) addErrorEl.style.display = 'none';
311
+ }
312
+
313
+ function parseKV(text: string): Record<string, string> {
314
+ const out: Record<string, string> = {};
315
+ for (const line of text.split(/\r?\n/)) {
316
+ const trimmed = line.trim();
317
+ if (!trimmed || trimmed.startsWith('#')) continue;
318
+ const eq = trimmed.indexOf('=');
319
+ if (eq === -1) continue;
320
+ out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1);
321
+ }
322
+ return out;
323
+ }
324
+
325
+ function getInput(id: string): string {
326
+ const el = (addFormEl ?? document).querySelector(`#${id}`) as HTMLInputElement | HTMLTextAreaElement | null;
327
+ return el?.value?.trim() ?? '';
328
+ }
329
+
330
+ async function handleAddAndSync(): Promise<void> {
331
+ if (!cachedConfig) return;
332
+ const isRemote = currentSubTab === 'remote';
333
+ let name: string;
334
+ let server: McpServer;
335
+
336
+ if (isRemote) {
337
+ name = getInput('mcpAddRemoteName');
338
+ const url = getInput('mcpAddUrl');
339
+ const headersText = getInput('mcpAddHeaders');
340
+ if (!name) { showAddError('Name is required.'); return; }
341
+ if (!url) { showAddError('URL is required.'); return; }
342
+ server = { url };
343
+ const h = parseKV(headersText);
344
+ if (Object.keys(h).length > 0) server.headers = h;
345
+ } else {
346
+ name = getInput('mcpAddName');
347
+ const command = getInput('mcpAddCommand');
348
+ const argsText = getInput('mcpAddArgs');
349
+ const envText = getInput('mcpAddEnv');
350
+ if (!name) { showAddError('Name is required.'); return; }
351
+ if (!command) { showAddError('Command is required.'); return; }
352
+ server = { command };
353
+ const args = argsText.split(/,|\n/).map(s => s.trim()).filter(Boolean);
354
+ if (args.length > 0) server.args = args;
355
+ const env = parseKV(envText);
356
+ if (Object.keys(env).length > 0) server.env = env;
357
+ }
358
+
359
+ if (cachedConfig.servers[name]) {
360
+ showAddError(`Server "${name}" already exists.`);
361
+ return;
362
+ }
363
+
364
+ try {
365
+ const merged = { ...cachedConfig, servers: { ...cachedConfig.servers, [name]: server } };
366
+ await apiJson('/api/mcp', 'PUT', merged);
367
+ await apiJson('/api/mcp/sync', 'POST', {});
368
+ await loadMcpServers();
369
+ closeMcpModal();
370
+ } catch (e) {
371
+ showAddError((e as Error).message);
372
+ }
373
+ }
374
+
375
+ // ── MCP Help popup (in-popup) ──
376
+
377
+ function showMcpHelp(): void {
378
+ const existing = document.getElementById('mcpHelpOverlay');
379
+ if (existing) { existing.remove(); return; }
380
+
381
+ const helpOverlay = document.createElement('div');
382
+ helpOverlay.id = 'mcpHelpOverlay';
383
+ helpOverlay.className = 'mcp-help-overlay';
384
+ helpOverlay.addEventListener('click', (e) => { if (e.target === helpOverlay) helpOverlay.remove(); });
385
+
386
+ const inner = document.createElement('div');
387
+ inner.className = 'mcp-help-inner';
388
+ inner.innerHTML = `
389
+ <h4>MCP Server</h4>
390
+ <p>MCP (Model Context Protocol) adds capabilities to AI agents — like installing apps on a phone.</p>
391
+
392
+ <h4>Local Server</h4>
393
+ <p>Runs on your machine. Provide a command and args.</p>
394
+ <pre>Name: context7
395
+ Command: npx
396
+ Args: -y, @upstash/context7-mcp</pre>
397
+
398
+ <h4>Remote Server</h4>
399
+ <p>Connects to an external URL (SSE or Streamable HTTP).</p>
400
+ <pre>Name: my-api
401
+ URL: https://mcp.example.com/sse</pre>
402
+
403
+ <h4>Server Types</h4>
404
+ <p><code>[npx]</code> Node.js package (npx -y ...)<br>
405
+ <code>[uvx]</code> Python package (uvx ...)<br>
406
+ <code>[docker]</code> Docker container<br>
407
+ <code>[remote]</code> External URL</p>
408
+
409
+ <h4>After Adding</h4>
410
+ <p>The server is automatically synced to all installed CLIs (Claude, Codex, Gemini, Cursor, Copilot, OpenCode).</p>
411
+ <p><code>Install bundle</code> converts npx/uvx servers to global binaries for faster startup.</p>
412
+
413
+ <div style="text-align:right;margin-top:12px">
414
+ <button type="button" class="btn-save" id="mcpHelpClose">OK</button>
415
+ </div>
416
+ `;
417
+ helpOverlay.append(inner);
418
+ document.body.append(helpOverlay);
419
+ inner.querySelector('#mcpHelpClose')?.addEventListener('click', () => helpOverlay.remove());
420
+ }
421
+
422
+ interface RegistryEntry {
423
+ id: string; name: string; description: string; category: string;
424
+ type: string; config: Record<string, unknown>; tags: string[]; url: string;
425
+ }
426
+
427
+ interface HarnessBuiltin {
428
+ id: string; name: string; description: string; source: string; harness: string;
429
+ standalone_config?: Record<string, unknown>;
430
+ }
431
+
432
+ async function loadRegistry(): Promise<void> {
433
+ if (!browseEl) return;
434
+ browseEl.innerHTML = '<p style="opacity:.5">Loading registry...</p>';
435
+ if (!cachedConfig) await loadMcpServers();
436
+ try {
437
+ const res = await api<{ ok: boolean; entries: RegistryEntry[]; builtins?: HarnessBuiltin[] }>('/api/mcp/registry');
438
+ if (!res?.entries?.length && !res?.builtins?.length) {
439
+ browseEl.innerHTML = '<p style="opacity:.5">No MCP servers in registry.</p>';
440
+ return;
441
+ }
442
+ const installed = cachedConfig ? Object.keys(cachedConfig.servers) : [];
443
+ let html = '';
444
+ html += res.entries.map(entry => {
445
+ const isInstalled = installed.includes(entry.id);
446
+ const tagHtml = entry.tags?.slice(0, 3).map(t => `<span style="background:var(--border);padding:1px 4px;border-radius:3px;font-size:10px">${escapeHtml(t)}</span>`).join(' ') || '';
447
+ return `<div style="padding:10px 0;border-bottom:1px solid var(--border)">
448
+ <div style="display:flex;align-items:center;gap:8px">
449
+ <b>${escapeHtml(entry.name)}</b>
450
+ <span style="opacity:.5;font-size:0.85em">[${escapeHtml(entry.type)}]</span>
451
+ <span style="opacity:.4;font-size:0.85em">${escapeHtml(entry.category)}</span>
452
+ ${isInstalled
453
+ ? '<span style="color:var(--success);font-size:11px">Installed</span>'
454
+ : `<button type="button" class="btn-clear text-xs" style="color:var(--accent)" data-registry-install="${escapeHtml(entry.id)}">+ Install</button>`
455
+ }
456
+ </div>
457
+ <p style="margin:4px 0 4px 0;font-size:12px;opacity:.75">${escapeHtml(entry.description)}</p>
458
+ <div style="display:flex;gap:4px;align-items:center">
459
+ ${tagHtml}
460
+ ${entry.url ? `<a href="${escapeHtml(entry.url)}" target="_blank" rel="noopener noreferrer" style="font-size:10px;opacity:.5;margin-left:auto">source</a>` : ''}
461
+ </div>
462
+ </div>`;
463
+ }).join('');
464
+
465
+ if (res.builtins?.length) {
466
+ html += `<div style="margin-top:16px;padding-top:12px;border-top:2px solid var(--border)">
467
+ <h4 style="font-size:12px;opacity:.6;margin:0 0 8px">Harness Built-ins (omo / omx)</h4>
468
+ <p style="font-size:11px;opacity:.4;margin:0 0 8px">Not installable via registry — built into harness runtimes. Listed for reference.</p>`;
469
+ html += res.builtins.map(b => {
470
+ const hasStandalone = b.standalone_config && Object.keys(b.standalone_config).length > 0;
471
+ return `<div style="padding:6px 0;border-bottom:1px solid var(--border)">
472
+ <div style="display:flex;align-items:center;gap:6px">
473
+ <b style="font-size:12px">${escapeHtml(b.name)}</b>
474
+ <span style="opacity:.4;font-size:0.8em">${escapeHtml(b.harness)}</span>
475
+ ${hasStandalone ? `<button type="button" class="btn-clear text-xs" style="color:var(--accent)" data-builtin-install="${escapeHtml(b.id)}">+ Install standalone</button>` : ''}
476
+ </div>
477
+ <p style="margin:2px 0;font-size:11px;opacity:.65">${escapeHtml(b.description)}</p>
478
+ </div>`;
479
+ }).join('');
480
+ html += '</div>';
481
+ }
482
+
483
+ browseEl.innerHTML = html;
484
+ browseEl.querySelectorAll<HTMLButtonElement>('[data-registry-install]').forEach(btn => {
485
+ btn.addEventListener('click', () => {
486
+ const id = btn.dataset['registryInstall'];
487
+ const entry = res.entries.find(e => e.id === id);
488
+ if (entry) void installFromRegistry(entry, btn);
489
+ });
490
+ });
491
+ browseEl.querySelectorAll<HTMLButtonElement>('[data-builtin-install]').forEach(btn => {
492
+ btn.addEventListener('click', () => {
493
+ const id = btn.dataset['builtinInstall'];
494
+ const builtin = res.builtins?.find(b => b.id === id);
495
+ if (builtin?.standalone_config) {
496
+ void installFromRegistry({
497
+ id: builtin.id,
498
+ name: builtin.name,
499
+ description: builtin.description,
500
+ category: 'harness',
501
+ type: 'remote',
502
+ config: builtin.standalone_config,
503
+ tags: [],
504
+ url: builtin.source,
505
+ }, btn);
506
+ }
507
+ });
508
+ });
509
+ } catch (e) {
510
+ browseEl.innerHTML = `<p style="color:var(--color-error)">${escapeHtml((e as Error).message)}</p>`;
511
+ }
512
+ }
513
+
514
+ async function installFromRegistry(entry: RegistryEntry, btn: HTMLButtonElement): Promise<void> {
515
+ if (!cachedConfig) return;
516
+ btn.disabled = true;
517
+ btn.textContent = 'Installing...';
518
+ try {
519
+ const server = entry.config as McpServer;
520
+ const merged = { ...cachedConfig, servers: { ...cachedConfig.servers, [entry.id]: server } };
521
+ await apiJson('/api/mcp', 'PUT', merged);
522
+ await apiJson('/api/mcp/sync', 'POST', {});
523
+ await loadMcpServers();
524
+ btn.textContent = 'Installed';
525
+ btn.style.color = 'var(--success)';
526
+ } catch (e) {
527
+ btn.textContent = 'Error';
528
+ btn.disabled = false;
529
+ console.error('[mcp-registry] install failed:', (e as Error).message);
530
+ }
531
+ }
532
+
533
+ export function openMcpModal(): void {
534
+ ensureModal();
535
+ overlay?.classList.add('open');
536
+ overlay?.setAttribute('aria-hidden', 'false');
537
+ openState = true;
538
+ switchTab('active');
539
+ }
540
+
541
+ export function closeMcpModal(): void {
542
+ if (!openState) return;
543
+ overlay?.classList.remove('open');
544
+ overlay?.setAttribute('aria-hidden', 'true');
545
+ openState = false;
546
+ clearAddInputs();
547
+ document.getElementById('mcpHelpOverlay')?.remove();
548
+ }
549
+
550
+ export function initMcpModal(): void {
551
+ document.addEventListener('keydown', (e) => {
552
+ if (document.getElementById('mcpHelpOverlay')) {
553
+ if (e.key === 'Escape') { e.preventDefault(); document.getElementById('mcpHelpOverlay')?.remove(); return; }
554
+ }
555
+ if (openState && e.key === 'Escape') {
556
+ e.preventDefault();
557
+ e.stopImmediatePropagation();
558
+ closeMcpModal();
559
+ }
560
+ }, true);
561
+ }
@@ -3,7 +3,7 @@ export { loadSettings, updateSettings, setPerm, getModelValue, handleModelSelect
3
3
  export { setTelegram, setForwardAll, setTelegramMentionOnly, saveTelegramSettings } from './settings-telegram.js';
4
4
  export { setDiscord, setDiscordForwardAll, setDiscordAllowBots, setDiscordMentionOnly, saveDiscordSettings } from './settings-discord.js';
5
5
  export { setActiveChannel, loadFallbackOrder, saveFallbackOrder } from './settings-channel.js';
6
- export { loadMcpServers, syncMcpServers, installMcpGlobal } from './settings-mcp.js';
6
+ export { loadMcpServers, syncMcpServers, installMcpGlobal, openMcpModal, initMcpModal } from './settings-mcp.js';
7
7
  export { loadCliStatus, scheduleCliStatusRefresh, setCliStatusInterval } from './settings-cli-status.js';
8
8
  export { initCliStatusToggle, initCliStatusPreviewHooks, isCliStatusExpanded, expandCliStatus, isEmbeddedPreviewFrame } from './settings-cli-status.js';
9
9
  export { initSttSettings } from './settings-stt.js';
@@ -4,6 +4,10 @@
4
4
 
5
5
  import { escapeHtml } from '../render.js';
6
6
  import { ICONS } from '../icons.js';
7
+ import {
8
+ displayShellCommand,
9
+ displayShellCommandDetail,
10
+ } from '../../../src/shared/shell-command-display.js';
7
11
 
8
12
  export interface ToolLogEntry {
9
13
  icon: string;
@@ -33,13 +37,23 @@ function previewText(text: string, max = 100): string {
33
37
  return singleLine.length > max ? `${singleLine.slice(0, max - 1)}…` : singleLine;
34
38
  }
35
39
 
40
+ function normalizeToolEntryForDisplay(tl: ToolLogEntry): ToolLogEntry {
41
+ if (tl.toolType && tl.toolType !== 'tool') return tl;
42
+ return {
43
+ ...tl,
44
+ label: displayShellCommand(tl.label || ''),
45
+ detail: displayShellCommandDetail(tl.detail || ''),
46
+ };
47
+ }
48
+
36
49
  function renderToolItem(tl: ToolLogEntry, idx: number): string {
37
- const icon = escapeHtml(tl.icon);
38
- const label = escapeHtml(tl.label);
39
- const detail = tl.detail || '';
50
+ const displayTool = normalizeToolEntryForDisplay(tl);
51
+ const icon = escapeHtml(displayTool.icon);
52
+ const label = escapeHtml(displayTool.label);
53
+ const detail = displayTool.detail || '';
40
54
  const detailId = `tool-detail-${Date.now()}-${idx}`;
41
55
 
42
- if (hasExpandableDetail(tl)) {
56
+ if (hasExpandableDetail(displayTool)) {
43
57
  const snippet = previewText(detail);
44
58
  const snippetHtml = snippet
45
59
  ? `<span class="tool-item-snippet">${escapeHtml(snippet)}</span>`
@@ -82,10 +82,13 @@ export async function refreshTransportStatusRow(): Promise<void> {
82
82
  const health = parseChannelHealth(payload);
83
83
  if (!health) {
84
84
  container.innerHTML = '';
85
+ container.style.display = 'none';
85
86
  return;
86
87
  }
87
88
  renderTransportStatusRow(container, health);
89
+ container.style.display = '';
88
90
  } catch {
89
91
  container.innerHTML = '';
92
+ container.style.display = 'none';
90
93
  }
91
94
  }
package/public/js/main.ts CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  onPerCliAiEProviderChange, saveActiveCliSettings, savePerCli, openPromptModal,
52
52
  onFlushCliChange, loadFlushAgentSidebar,
53
53
  closePromptModal, savePromptFromModal, syncMcpServers, installMcpGlobal,
54
+ openMcpModal, initMcpModal,
54
55
  loadCliStatus, scheduleCliStatusRefresh, setCliStatusInterval,
55
56
  initCliStatusToggle, initCliStatusPreviewHooks, isCliStatusExpanded, expandCliStatus, isEmbeddedPreviewFrame,
56
57
  setTelegram, setForwardAll, setTelegramMentionOnly, saveTelegramSettings,
@@ -367,8 +368,10 @@ function bindPerCliControlEvents(): void {
367
368
  }
368
369
 
369
370
  // MCP
371
+ document.querySelector('[data-action="openMcpModal"]')?.addEventListener('click', openMcpModal);
370
372
  document.querySelector('[data-action="syncMcp"]')?.addEventListener('click', syncMcpServers);
371
373
  document.querySelector('[data-action="installMcp"]')?.addEventListener('click', installMcpGlobal);
374
+ initMcpModal();
372
375
  document.querySelector('[data-action="refreshCli"]')?.addEventListener('click', () => {
373
376
  if (!isCliStatusExpanded()) expandCliStatus();
374
377
  else loadCliStatus(true);
@@ -36,3 +36,17 @@ export function postPreviewOpenNotes(path: string): boolean {
36
36
  window.parent.postMessage({ type: 'jaw-preview-open-notes', path }, targetOrigin);
37
37
  return true;
38
38
  }
39
+
40
+ export function postPreviewOpenDoc(absolutePath: string): boolean {
41
+ const targetOrigin = previewParentOrigin();
42
+ if (!targetOrigin || !absolutePath.trim()) return false;
43
+ window.parent.postMessage({ type: 'jaw-preview-open-doc', path: absolutePath }, targetOrigin);
44
+ return true;
45
+ }
46
+
47
+ export function postPreviewInvalidate(topics: string[], reason: string): boolean {
48
+ const targetOrigin = previewParentOrigin();
49
+ if (!targetOrigin || topics.length === 0) return false;
50
+ window.parent.postMessage({ type: 'dashboard.invalidate', topics, reason }, targetOrigin);
51
+ return true;
52
+ }