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,13 +1,3 @@
1
- // Phase 8 — MCP page: structured editor for server cards plus an Advanced
2
- // JSON view, plus action buttons for sync/install/reset against the
3
- // unified MCP config (`/api/mcp`).
4
- //
5
- // The page owns one synthetic dirty-store key (`mcp.config`) carrying the
6
- // full normalized config. Save PUTs back with `toPersistShape` to keep the
7
- // on-disk JSON minimal. Render-time validation (duplicate names, missing
8
- // commands, malformed env) gates the dirty entry's `valid` flag — invalid
9
- // configs cannot save.
10
-
11
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
12
2
  import type { SettingsPageProps } from '../types';
13
3
  import { JsonEditorField } from '../fields';
@@ -21,7 +11,9 @@ import {
21
11
  import { InlineWarn } from './components/InlineWarn';
22
12
  import { McpServerCard } from './components/McpServerCard';
23
13
  import {
14
+ countInstallBundleCandidates,
24
15
  findDuplicateNames,
16
+ getServerTag,
25
17
  makeEmptyServer,
26
18
  newServerName,
27
19
  normalizeMcpConfig,
@@ -46,6 +38,9 @@ type SyncResultsPayload = {
46
38
  servers?: unknown;
47
39
  };
48
40
 
41
+ type ModalTab = 'active' | 'add';
42
+ type AddSubTab = 'local' | 'remote';
43
+
49
44
  export default function Mcp({ port, client, dirty, registerSave }: SettingsPageProps) {
50
45
  const { state, refresh, setData } = usePageSnapshot<unknown>(client, '/api/mcp');
51
46
  const [draft, setDraft] = useState<McpConfig>({ servers: {} });
@@ -55,6 +50,18 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
55
50
  const [actionState, setActionState] = useState<ActionResult>({ kind: 'idle' });
56
51
  const actionRunIdRef = useRef(0);
57
52
 
53
+ const [modalOpen, setModalOpen] = useState(false);
54
+ const [modalTab, setModalTab] = useState<ModalTab>('active');
55
+ const [addSubTab, setAddSubTab] = useState<AddSubTab>('local');
56
+ const [addName, setAddName] = useState('');
57
+ const [addCommand, setAddCommand] = useState('');
58
+ const [addArgs, setAddArgs] = useState('');
59
+ const [addEnv, setAddEnv] = useState('');
60
+ const [addUrl, setAddUrl] = useState('');
61
+ const [addHeaders, setAddHeaders] = useState('');
62
+ const [addError, setAddError] = useState<string | null>(null);
63
+ const [addPending, setAddPending] = useState(false);
64
+
58
65
  const original = useMemo<McpConfig>(
59
66
  () => (state.kind === 'ready' ? normalizeMcpConfig(state.data) : { servers: {} }),
60
67
  [state],
@@ -69,9 +76,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
69
76
  }, [state]);
70
77
 
71
78
  useEffect(() => {
72
- return () => {
73
- dirty.remove(DIRTY_KEY);
74
- };
79
+ return () => { dirty.remove(DIRTY_KEY); };
75
80
  }, [dirty]);
76
81
 
77
82
  const names = useMemo(() => Object.keys(draft.servers), [draft]);
@@ -85,6 +90,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
85
90
  const hasFieldErrors = validations.some((v) => v.result.kind === 'invalid');
86
91
  const hasDupes = duplicates.size > 0;
87
92
  const isValid = !hasFieldErrors && !hasDupes && advancedValid;
93
+ const bundleCandidates = useMemo(
94
+ () => countInstallBundleCandidates(draft.servers),
95
+ [draft],
96
+ );
88
97
 
89
98
  const writeDirty = useCallback(
90
99
  (next: McpConfig, valid: boolean) => {
@@ -101,7 +110,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
101
110
  (next: McpConfig, nextOrder?: string[]) => {
102
111
  setDraft(next);
103
112
  if (nextOrder) setOrder(nextOrder);
104
- // Recompute validity on the next draft, not the stale `isValid`.
105
113
  const ns = nextOrder ?? Object.keys(next.servers);
106
114
  const dupes = findDuplicateNames(ns);
107
115
  const fieldOk = ns.every(
@@ -115,8 +123,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
115
123
  const onRenameServer = useCallback(
116
124
  (oldName: string, nextName: string) => {
117
125
  if (oldName === nextName) return;
118
- // Preserve insertion order; keep the same card in place even if the
119
- // new name collides with a later card (validation surfaces the dupe).
120
126
  const nextOrder = order.map((n) => (n === oldName ? nextName : n));
121
127
  const nextServers: Record<string, McpServer> = {};
122
128
  for (const n of nextOrder) {
@@ -185,9 +191,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
185
191
  if (!(DIRTY_KEY in bundle)) return;
186
192
  const body = bundle[DIRTY_KEY];
187
193
  const updated = await client.put<unknown>('/api/mcp', body);
188
- // /api/mcp PUT returns `{ ok, servers: string[] }` — re-fetch to get the
189
- // canonical persisted shape rather than reusing the (possibly trimmed)
190
- // request body.
191
194
  dirty.clear();
192
195
  const fresh = await client.get<unknown>('/api/mcp').catch(() => updated);
193
196
  const normalized = normalizeMcpConfig(fresh);
@@ -211,9 +214,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
211
214
  !window.confirm(
212
215
  `You have unsaved MCP edits. ${label} will use the on-disk config, not your unsaved changes. Continue?`,
213
216
  )
214
- ) {
215
- return;
216
- }
217
+ ) { return; }
217
218
  }
218
219
  const runId = actionRunIdRef.current + 1;
219
220
  actionRunIdRef.current = runId;
@@ -236,6 +237,94 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
236
237
  [client, dirty],
237
238
  );
238
239
 
240
+ const resetAddForm = useCallback(() => {
241
+ setAddName('');
242
+ setAddCommand('');
243
+ setAddArgs('');
244
+ setAddEnv('');
245
+ setAddUrl('');
246
+ setAddHeaders('');
247
+ setAddError(null);
248
+ setAddPending(false);
249
+ }, []);
250
+
251
+ const openModal = useCallback(() => {
252
+ setModalTab('active');
253
+ resetAddForm();
254
+ setModalOpen(true);
255
+ }, [resetAddForm]);
256
+
257
+ const closeModal = useCallback(() => {
258
+ setModalOpen(false);
259
+ resetAddForm();
260
+ }, [resetAddForm]);
261
+
262
+ const handleAddAndSync = useCallback(async () => {
263
+ const name = addName.trim() || newServerName(Object.keys(original.servers));
264
+ const server: McpServer = addSubTab === 'remote'
265
+ ? { url: addUrl.trim(), ...(addHeaders.trim() ? { headers: parseSimpleKV(addHeaders) } : {}) }
266
+ : {
267
+ command: addCommand.trim(),
268
+ ...(addArgs.trim() ? { args: addArgs.split(/\r?\n|,/).map(s => s.trim()).filter(Boolean) } : {}),
269
+ ...(addEnv.trim() ? { env: parseSimpleKV(addEnv) } : {}),
270
+ };
271
+
272
+ const validation = validateServer(name, server);
273
+ if (validation.kind === 'invalid') {
274
+ setAddError(validation.reason);
275
+ return;
276
+ }
277
+ if (original.servers[name]) {
278
+ setAddError(`Server "${name}" already exists.`);
279
+ return;
280
+ }
281
+
282
+ setAddPending(true);
283
+ setAddError(null);
284
+
285
+ try {
286
+ const merged: McpConfig = {
287
+ ...original,
288
+ servers: { ...original.servers, [name]: server },
289
+ };
290
+ await client.put<unknown>('/api/mcp', toPersistShape(merged));
291
+ await client.post<unknown>('/api/mcp/sync', {});
292
+ dirty.clear();
293
+ const fresh = await client.get<unknown>('/api/mcp').catch(() => merged);
294
+ const normalized = normalizeMcpConfig(fresh);
295
+ setDraft(normalized);
296
+ setOrder(Object.keys(normalized.servers));
297
+ setData(fresh);
298
+ await refresh();
299
+ closeModal();
300
+ } catch (err: unknown) {
301
+ setAddError(err instanceof Error ? err.message : String(err));
302
+ setAddPending(false);
303
+ }
304
+ }, [addName, addSubTab, addUrl, addHeaders, addCommand, addArgs, addEnv, original, client, dirty, refresh, setData, closeModal]);
305
+
306
+ const handleRemoveFromModal = useCallback(async (name: string) => {
307
+ const nextServers = { ...original.servers };
308
+ delete nextServers[name];
309
+ const merged: McpConfig = { ...original, servers: nextServers };
310
+ try {
311
+ await client.put<unknown>('/api/mcp', toPersistShape(merged));
312
+ await client.post<unknown>('/api/mcp/sync', {});
313
+ dirty.clear();
314
+ const fresh = await client.get<unknown>('/api/mcp').catch(() => merged);
315
+ const normalized = normalizeMcpConfig(fresh);
316
+ setDraft(normalized);
317
+ setOrder(Object.keys(normalized.servers));
318
+ setData(fresh);
319
+ await refresh();
320
+ } catch (err: unknown) {
321
+ setActionState({
322
+ kind: 'error',
323
+ message: err instanceof Error ? err.message : String(err),
324
+ });
325
+ }
326
+ }, [original, client, dirty, refresh, setData]);
327
+
239
328
  if (state.kind === 'loading') return <PageLoading />;
240
329
  if (state.kind === 'offline') return <PageOffline port={port} />;
241
330
  if (state.kind === 'error') return <PageError message={state.message} />;
@@ -277,15 +366,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
277
366
  );
278
367
  })}
279
368
  </div>
280
- <div className="mcp-servers-actions">
281
- <button
282
- type="button"
283
- className="settings-action"
284
- onClick={onAddServer}
285
- >
286
- Add server
287
- </button>
288
- </div>
289
369
  {hasDupes && (
290
370
  <InlineWarn role="alert">
291
371
  Duplicate server name{dupeNames.length === 1 ? '' : 's'}: {dupeNames.join(', ')}.
@@ -294,7 +374,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
294
374
  )}
295
375
  {hasFieldErrors && !hasDupes && (
296
376
  <InlineWarn role="alert">
297
- Some servers have errors (missing command or invalid name).
377
+ Some servers have errors (missing command/URL or invalid name).
298
378
  Saving is blocked until they are fixed.
299
379
  </InlineWarn>
300
380
  )}
@@ -305,6 +385,13 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
305
385
  hint="These act on the saved config. Save edits first if you want them included."
306
386
  >
307
387
  <div className="mcp-action-buttons">
388
+ <button
389
+ type="button"
390
+ className="settings-action"
391
+ onClick={openModal}
392
+ >
393
+ + Add, Set MCP
394
+ </button>
308
395
  <button
309
396
  type="button"
310
397
  className="settings-action"
@@ -316,10 +403,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
316
403
  <button
317
404
  type="button"
318
405
  className="settings-action"
319
- disabled={actionState.kind === 'pending'}
406
+ disabled={actionState.kind === 'pending' || bundleCandidates === 0}
320
407
  onClick={() => void runAction('Install bundle', '/api/mcp/install')}
321
408
  >
322
- Install bundle
409
+ Install bundle ({bundleCandidates})
323
410
  </button>
324
411
  <button
325
412
  type="button"
@@ -331,9 +418,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
331
418
  !window.confirm(
332
419
  'Reset MCP config to defaults? Your custom servers will be removed.',
333
420
  )
334
- ) {
335
- return;
336
- }
421
+ ) { return; }
337
422
  void runAction('Reset to defaults', '/api/mcp/reset');
338
423
  }}
339
424
  >
@@ -341,14 +426,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
341
426
  </button>
342
427
  </div>
343
428
  {actionState.kind === 'pending' && (
344
- <p className="settings-section-hint" role="status">
345
- {actionState.label}…
346
- </p>
429
+ <p className="settings-section-hint" role="status">{actionState.label}…</p>
347
430
  )}
348
431
  {actionState.kind === 'success' && (
349
- <p className="settings-section-hint" role="status">
350
- ✅ {actionState.message}
351
- </p>
432
+ <p className="settings-section-hint" role="status">{actionState.message}</p>
352
433
  )}
353
434
  {actionState.kind === 'error' && (
354
435
  <InlineWarn role="alert">{actionState.message}</InlineWarn>
@@ -382,6 +463,193 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
382
463
  </p>
383
464
  )}
384
465
  </SettingsSection>
466
+
467
+ {modalOpen && (
468
+ <div className="mcp-modal-backdrop" onClick={closeModal}>
469
+ <div
470
+ className="mcp-modal"
471
+ role="dialog"
472
+ aria-modal="true"
473
+ aria-label="MCP Server Management"
474
+ onClick={(e) => e.stopPropagation()}
475
+ >
476
+ <div className="mcp-modal-tabs">
477
+ <button
478
+ type="button"
479
+ className={`mcp-modal-tab${modalTab === 'active' ? ' is-active' : ''}`}
480
+ onClick={() => setModalTab('active')}
481
+ >
482
+ Active Servers
483
+ </button>
484
+ <button
485
+ type="button"
486
+ className={`mcp-modal-tab${modalTab === 'add' ? ' is-active' : ''}`}
487
+ onClick={() => { setModalTab('add'); resetAddForm(); }}
488
+ >
489
+ Add New
490
+ </button>
491
+ </div>
492
+
493
+ <div className="mcp-modal-body">
494
+ {modalTab === 'active' && (
495
+ <div className="mcp-active-list">
496
+ {Object.keys(original.servers).length === 0 && (
497
+ <p className="settings-section-hint">No active MCP servers.</p>
498
+ )}
499
+ {Object.entries(original.servers).map(([name, srv]) => {
500
+ const tag = getServerTag(srv);
501
+ return (
502
+ <div key={name} className="mcp-active-row">
503
+ <span className="mcp-active-name">{name}</span>
504
+ <span className="mcp-active-detail">
505
+ {srv.url || [srv.command, ...(srv.args || [])].filter(Boolean).join(' ')}
506
+ </span>
507
+ {tag && <span className="mcp-server-tag" data-tag={tag}>[{tag}]</span>}
508
+ <button
509
+ type="button"
510
+ className="settings-action settings-action-discard mcp-active-remove"
511
+ onClick={() => void handleRemoveFromModal(name)}
512
+ >
513
+ Remove
514
+ </button>
515
+ </div>
516
+ );
517
+ })}
518
+ </div>
519
+ )}
520
+
521
+ {modalTab === 'add' && (
522
+ <div className="mcp-add-form">
523
+ <div className="mcp-add-subtabs">
524
+ <button
525
+ type="button"
526
+ className={`mcp-modal-tab${addSubTab === 'local' ? ' is-active' : ''}`}
527
+ onClick={() => setAddSubTab('local')}
528
+ >
529
+ Local
530
+ </button>
531
+ <button
532
+ type="button"
533
+ className={`mcp-modal-tab${addSubTab === 'remote' ? ' is-active' : ''}`}
534
+ onClick={() => setAddSubTab('remote')}
535
+ >
536
+ Remote
537
+ </button>
538
+ </div>
539
+
540
+ <label className="settings-field settings-field-text">
541
+ <span className="settings-field-label">Name</span>
542
+ <input
543
+ type="text"
544
+ value={addName}
545
+ placeholder="my-server"
546
+ spellCheck={false}
547
+ onChange={(e) => setAddName(e.target.value)}
548
+ />
549
+ </label>
550
+
551
+ {addSubTab === 'local' ? (
552
+ <>
553
+ <label className="settings-field settings-field-text">
554
+ <span className="settings-field-label">Command</span>
555
+ <input
556
+ type="text"
557
+ value={addCommand}
558
+ placeholder="npx"
559
+ spellCheck={false}
560
+ onChange={(e) => setAddCommand(e.target.value)}
561
+ />
562
+ </label>
563
+ <label className="settings-field settings-field-text">
564
+ <span className="settings-field-label">Args (one per line)</span>
565
+ <textarea
566
+ value={addArgs}
567
+ rows={3}
568
+ spellCheck={false}
569
+ placeholder="-y&#10;@upstash/context7-mcp"
570
+ onChange={(e) => setAddArgs(e.target.value)}
571
+ />
572
+ </label>
573
+ <label className="settings-field settings-field-text">
574
+ <span className="settings-field-label">Env (KEY=value per line)</span>
575
+ <textarea
576
+ value={addEnv}
577
+ rows={2}
578
+ spellCheck={false}
579
+ onChange={(e) => setAddEnv(e.target.value)}
580
+ />
581
+ </label>
582
+ </>
583
+ ) : (
584
+ <>
585
+ <label className="settings-field settings-field-text">
586
+ <span className="settings-field-label">URL</span>
587
+ <input
588
+ type="text"
589
+ value={addUrl}
590
+ placeholder="https://mcp.example.com/sse"
591
+ spellCheck={false}
592
+ onChange={(e) => setAddUrl(e.target.value)}
593
+ />
594
+ </label>
595
+ <label className="settings-field settings-field-text">
596
+ <span className="settings-field-label">Headers (KEY=value per line)</span>
597
+ <textarea
598
+ value={addHeaders}
599
+ rows={2}
600
+ spellCheck={false}
601
+ onChange={(e) => setAddHeaders(e.target.value)}
602
+ />
603
+ </label>
604
+ </>
605
+ )}
606
+
607
+ {addError && <InlineWarn role="alert">{addError}</InlineWarn>}
608
+
609
+ <div className="mcp-add-actions">
610
+ <button
611
+ type="button"
612
+ className="settings-action"
613
+ onClick={closeModal}
614
+ >
615
+ Cancel
616
+ </button>
617
+ <button
618
+ type="button"
619
+ className="settings-action"
620
+ disabled={addPending}
621
+ onClick={() => void handleAddAndSync()}
622
+ >
623
+ {addPending ? 'Adding…' : 'Add & Sync'}
624
+ </button>
625
+ </div>
626
+ </div>
627
+ )}
628
+ </div>
629
+
630
+ <button
631
+ type="button"
632
+ className="mcp-modal-close"
633
+ aria-label="Close"
634
+ onClick={closeModal}
635
+ >
636
+ &times;
637
+ </button>
638
+ </div>
639
+ </div>
640
+ )}
385
641
  </form>
386
642
  );
387
643
  }
644
+
645
+ function parseSimpleKV(text: string): Record<string, string> {
646
+ const out: Record<string, string> = {};
647
+ for (const line of text.split(/\r?\n/)) {
648
+ const t = line.trim();
649
+ if (!t || t.startsWith('#')) continue;
650
+ const eq = t.indexOf('=');
651
+ if (eq === -1) continue;
652
+ out[t.slice(0, eq).trim()] = t.slice(eq + 1);
653
+ }
654
+ return out;
655
+ }
@@ -1,17 +1,10 @@
1
- // Phase 8 — single MCP server card. The MCP page composes a list of these
2
- // over the `servers` map returned by `/api/mcp`. Pure presentation; the
3
- // parent owns the canonical server map and the dirty entry.
4
- //
5
- // The shape is defensive: `args` may be missing on legacy entries (treated
6
- // as `[]`), `env` may be missing or null. We only persist back a clean
7
- // `McpServer` so loadUnifiedMcp/saveUnifiedMcp round-trip stays stable.
8
-
9
1
  import type { McpServer } from '../mcp-helpers';
10
2
  import {
11
3
  formatArgsText,
12
4
  formatEnvText,
13
5
  parseArgsText,
14
6
  parseEnvText,
7
+ getServerTag,
15
8
  } from '../mcp-helpers';
16
9
 
17
10
  type Props = {
@@ -34,6 +27,9 @@ export function McpServerCard({
34
27
  const id = `mcp-${name || 'unnamed'}`;
35
28
  const argsText = formatArgsText(server.args);
36
29
  const envText = formatEnvText(server.env);
30
+ const headersText = formatEnvText(server.headers);
31
+ const tag = getServerTag(server);
32
+ const isRemote = tag === 'remote';
37
33
 
38
34
  return (
39
35
  <article className="mcp-server-card" aria-label={`MCP server ${name || '(unnamed)'}`}>
@@ -55,57 +51,95 @@ export function McpServerCard({
55
51
  </span>
56
52
  ) : null}
57
53
  </label>
58
- <button
59
- type="button"
60
- className="settings-action settings-action-discard"
61
- onClick={onRemove}
62
- aria-label={`Remove ${name || 'server'}`}
63
- >
64
- Remove
65
- </button>
54
+ <div className="mcp-server-card-actions">
55
+ {tag ? (
56
+ <span className="mcp-server-tag" data-tag={tag}>[{tag}]</span>
57
+ ) : null}
58
+ <button
59
+ type="button"
60
+ className="settings-action settings-action-discard"
61
+ onClick={onRemove}
62
+ aria-label={`Remove ${name || 'server'}`}
63
+ >
64
+ Remove
65
+ </button>
66
+ </div>
66
67
  </header>
67
68
 
68
- <label className="settings-field settings-field-text" htmlFor={`${id}-command`}>
69
- <span className="settings-field-label">Command</span>
70
- <input
71
- id={`${id}-command`}
72
- type="text"
73
- value={server.command || ''}
74
- placeholder="npx"
75
- spellCheck={false}
76
- onChange={(event) =>
77
- onChange({ ...server, command: event.target.value })
78
- }
79
- />
80
- </label>
69
+ {isRemote ? (
70
+ <>
71
+ <label className="settings-field settings-field-text" htmlFor={`${id}-url`}>
72
+ <span className="settings-field-label">URL</span>
73
+ <input
74
+ id={`${id}-url`}
75
+ type="text"
76
+ value={server.url || ''}
77
+ placeholder="https://mcp.example.com/sse"
78
+ spellCheck={false}
79
+ onChange={(event) =>
80
+ onChange({ ...server, url: event.target.value })
81
+ }
82
+ />
83
+ </label>
81
84
 
82
- <label className="settings-field settings-field-text" htmlFor={`${id}-args`}>
83
- <span className="settings-field-label">
84
- Args (one per line, or comma-separated)
85
- </span>
86
- <textarea
87
- id={`${id}-args`}
88
- value={argsText}
89
- rows={3}
90
- spellCheck={false}
91
- onChange={(event) =>
92
- onChange({ ...server, args: parseArgsText(event.target.value) })
93
- }
94
- />
95
- </label>
85
+ <label className="settings-field settings-field-text" htmlFor={`${id}-headers`}>
86
+ <span className="settings-field-label">Headers (KEY=value per line)</span>
87
+ <textarea
88
+ id={`${id}-headers`}
89
+ value={headersText}
90
+ rows={3}
91
+ spellCheck={false}
92
+ onChange={(event) =>
93
+ onChange({ ...server, headers: parseEnvText(event.target.value) })
94
+ }
95
+ />
96
+ </label>
97
+ </>
98
+ ) : (
99
+ <>
100
+ <label className="settings-field settings-field-text" htmlFor={`${id}-command`}>
101
+ <span className="settings-field-label">Command</span>
102
+ <input
103
+ id={`${id}-command`}
104
+ type="text"
105
+ value={server.command || ''}
106
+ placeholder="npx"
107
+ spellCheck={false}
108
+ onChange={(event) =>
109
+ onChange({ ...server, command: event.target.value })
110
+ }
111
+ />
112
+ </label>
96
113
 
97
- <label className="settings-field settings-field-text" htmlFor={`${id}-env`}>
98
- <span className="settings-field-label">Env (KEY=value per line)</span>
99
- <textarea
100
- id={`${id}-env`}
101
- value={envText}
102
- rows={3}
103
- spellCheck={false}
104
- onChange={(event) =>
105
- onChange({ ...server, env: parseEnvText(event.target.value) })
106
- }
107
- />
108
- </label>
114
+ <label className="settings-field settings-field-text" htmlFor={`${id}-args`}>
115
+ <span className="settings-field-label">
116
+ Args (one per line, or comma-separated)
117
+ </span>
118
+ <textarea
119
+ id={`${id}-args`}
120
+ value={argsText}
121
+ rows={3}
122
+ spellCheck={false}
123
+ onChange={(event) =>
124
+ onChange({ ...server, args: parseArgsText(event.target.value) })
125
+ }
126
+ />
127
+ </label>
128
+
129
+ <label className="settings-field settings-field-text" htmlFor={`${id}-env`}>
130
+ <span className="settings-field-label">Env (KEY=value per line)</span>
131
+ <textarea
132
+ id={`${id}-env`}
133
+ value={envText}
134
+ rows={3}
135
+ spellCheck={false}
136
+ onChange={(event) =>
137
+ onChange({ ...server, env: parseEnvText(event.target.value) })
138
+ }
139
+ />
140
+ </label>
141
+ </>
142
+ )}
109
143
 
110
144
  <label className="settings-field settings-field-toggle" htmlFor={`${id}-autostart`}>
111
145
  <input