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.
- package/README.ja.md +8 -6
- package/README.ko.md +8 -6
- package/README.md +12 -6
- package/README.zh-CN.md +8 -6
- package/dist/bin/cli-jaw.js +5 -1
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/browser.js +1 -1
- package/dist/bin/commands/browser.js.map +1 -1
- package/dist/bin/commands/chat.js +98 -58
- package/dist/bin/commands/chat.js.map +1 -1
- package/dist/bin/commands/dispatch.js +130 -5
- package/dist/bin/commands/dispatch.js.map +1 -1
- package/dist/bin/commands/goal.js +18 -6
- package/dist/bin/commands/goal.js.map +1 -1
- package/dist/bin/commands/orchestrate.js.map +1 -1
- package/dist/bin/commands/tui/fullscreen-mode.js +237 -0
- package/dist/bin/commands/tui/fullscreen-mode.js.map +1 -0
- package/dist/bin/commands/tui/input-handler.js +139 -52
- package/dist/bin/commands/tui/input-handler.js.map +1 -1
- package/dist/bin/commands/tui/overlays.js +177 -70
- package/dist/bin/commands/tui/overlays.js.map +1 -1
- package/dist/bin/commands/tui/renderer.js +70 -20
- package/dist/bin/commands/tui/renderer.js.map +1 -1
- package/dist/bin/commands/tui/simple-mode.js +1 -1
- package/dist/bin/commands/tui/simple-mode.js.map +1 -1
- package/dist/bin/commands/tui/tui-io.js +8 -0
- package/dist/bin/commands/tui/tui-io.js.map +1 -0
- package/dist/bin/commands/tui/types.js +9 -0
- package/dist/bin/commands/tui/types.js.map +1 -1
- package/dist/bin/commands/tui/ws-handler.js +115 -23
- package/dist/bin/commands/tui/ws-handler.js.map +1 -1
- package/dist/bin/commands/worker.js +159 -0
- package/dist/bin/commands/worker.js.map +1 -0
- package/dist/bin/postinstall.js +0 -76
- package/dist/bin/postinstall.js.map +1 -1
- package/dist/lib/mcp/format-converters.js +21 -2
- package/dist/lib/mcp/format-converters.js.map +1 -1
- package/dist/lib/mcp/mcp-registry.js +75 -0
- package/dist/lib/mcp/mcp-registry.js.map +1 -0
- package/dist/lib/stt.js +1 -1
- package/dist/lib/stt.js.map +1 -1
- package/dist/scripts/fresh-install-smoke.js +1 -1
- package/dist/scripts/fresh-install-smoke.js.map +1 -1
- package/dist/server.js +27 -6
- package/dist/server.js.map +1 -1
- package/dist/src/agent/args.js +33 -1
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/claude-e-runtime.js +1 -1
- package/dist/src/agent/claude-e-runtime.js.map +1 -1
- package/dist/src/agent/codex-app-client.js +1 -1
- package/dist/src/agent/error-classifier.js +6 -1
- package/dist/src/agent/error-classifier.js.map +1 -1
- package/dist/src/agent/events/claude.js +1 -0
- package/dist/src/agent/events/claude.js.map +1 -1
- package/dist/src/agent/events/codex.js +0 -44
- package/dist/src/agent/events/codex.js.map +1 -1
- package/dist/src/agent/events/helpers.js +52 -5
- package/dist/src/agent/events/helpers.js.map +1 -1
- package/dist/src/agent/events/opencode.js +6 -2
- package/dist/src/agent/events/opencode.js.map +1 -1
- package/dist/src/agent/events/summary.js +3 -2
- package/dist/src/agent/events/summary.js.map +1 -1
- package/dist/src/agent/events/tool-labels.js +8 -5
- package/dist/src/agent/events/tool-labels.js.map +1 -1
- package/dist/src/agent/kiro-auth.js +213 -0
- package/dist/src/agent/kiro-auth.js.map +1 -0
- package/dist/src/agent/kiro-models.js +79 -0
- package/dist/src/agent/kiro-models.js.map +1 -0
- package/dist/src/agent/kiro-runtime.js +306 -0
- package/dist/src/agent/kiro-runtime.js.map +1 -0
- package/dist/src/agent/lifecycle-handler.js +190 -12
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/memory-flush-controller.js +1 -3
- package/dist/src/agent/memory-flush-controller.js.map +1 -1
- package/dist/src/agent/resume-classifier.js +18 -1
- package/dist/src/agent/resume-classifier.js.map +1 -1
- package/dist/src/agent/session-persistence.js +1 -1
- package/dist/src/agent/session-persistence.js.map +1 -1
- package/dist/src/agent/smoke-detector.js +3 -0
- package/dist/src/agent/smoke-detector.js.map +1 -1
- package/dist/src/agent/spawn/queue.js +10 -4
- package/dist/src/agent/spawn/queue.js.map +1 -1
- package/dist/src/agent/spawn/resume.js +6 -1
- package/dist/src/agent/spawn/resume.js.map +1 -1
- package/dist/src/agent/spawn-env.js +1 -1
- package/dist/src/agent/spawn-env.js.map +1 -1
- package/dist/src/agent/spawn.js +188 -28
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/browser/web-ai/grok-live.js +2 -0
- package/dist/src/browser/web-ai/grok-live.js.map +1 -1
- package/dist/src/cli/claude-models.js +2 -2
- package/dist/src/cli/claude-models.js.map +1 -1
- package/dist/src/cli/commands.js +3 -2
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers-runtime.js +2 -0
- package/dist/src/cli/handlers-runtime.js.map +1 -1
- package/dist/src/cli/handlers-workflows.js +9 -4
- package/dist/src/cli/handlers-workflows.js.map +1 -1
- package/dist/src/cli/handlers.js +47 -7
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/readiness.js +17 -1
- package/dist/src/cli/readiness.js.map +1 -1
- package/dist/src/cli/registry-live.js +17 -0
- package/dist/src/cli/registry-live.js.map +1 -0
- package/dist/src/cli/registry.js +44 -3
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/cli/tui/composer.js +135 -5
- package/dist/src/cli/tui/composer.js.map +1 -1
- package/dist/src/cli/tui/diffview.js +41 -0
- package/dist/src/cli/tui/diffview.js.map +1 -0
- package/dist/src/cli/tui/editor.js +45 -0
- package/dist/src/cli/tui/editor.js.map +1 -0
- package/dist/src/cli/tui/file-mention.js +78 -0
- package/dist/src/cli/tui/file-mention.js.map +1 -0
- package/dist/src/cli/tui/highlight.js +82 -0
- package/dist/src/cli/tui/highlight.js.map +1 -0
- package/dist/src/cli/tui/keymap.js +21 -0
- package/dist/src/cli/tui/keymap.js.map +1 -1
- package/dist/src/cli/tui/markdown.js +124 -0
- package/dist/src/cli/tui/markdown.js.map +1 -0
- package/dist/src/cli/tui/mode.js +26 -0
- package/dist/src/cli/tui/mode.js.map +1 -0
- package/dist/src/cli/tui/overlay.js +121 -98
- package/dist/src/cli/tui/overlay.js.map +1 -1
- package/dist/src/cli/tui/render/frame.js +72 -0
- package/dist/src/cli/tui/render/frame.js.map +1 -0
- package/dist/src/cli/tui/render/layout.js +22 -0
- package/dist/src/cli/tui/render/layout.js.map +1 -0
- package/dist/src/cli/tui/render/mouse.js +25 -0
- package/dist/src/cli/tui/render/mouse.js.map +1 -0
- package/dist/src/cli/tui/render/scheduler.js +37 -0
- package/dist/src/cli/tui/render/scheduler.js.map +1 -0
- package/dist/src/cli/tui/render/viewport.js +82 -0
- package/dist/src/cli/tui/render/viewport.js.map +1 -0
- package/dist/src/cli/tui/renderers.js +37 -0
- package/dist/src/cli/tui/renderers.js.map +1 -1
- package/dist/src/cli/tui/stream.js +48 -0
- package/dist/src/cli/tui/stream.js.map +1 -0
- package/dist/src/cli/tui/text-buffer.js +143 -0
- package/dist/src/cli/tui/text-buffer.js.map +1 -0
- package/dist/src/cli/tui/theme.js +128 -0
- package/dist/src/cli/tui/theme.js.map +1 -0
- package/dist/src/cli/tui/transcript.js +4 -0
- package/dist/src/cli/tui/transcript.js.map +1 -1
- package/dist/src/command-contract/help-renderer.js +1 -1
- package/dist/src/command-contract/help-renderer.js.map +1 -1
- package/dist/src/core/cli-detection.js +14 -0
- package/dist/src/core/cli-detection.js.map +1 -1
- package/dist/src/core/config.js +5 -0
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +7 -0
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/employees.js +2 -2
- package/dist/src/core/employees.js.map +1 -1
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/discord-file.js +1 -1
- package/dist/src/discord/discord-file.js.map +1 -1
- package/dist/src/discord/send-only-client.js +1 -3
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/goal/heartbeat.js +30 -7
- package/dist/src/goal/heartbeat.js.map +1 -1
- package/dist/src/goal/store.js +6 -2
- package/dist/src/goal/store.js.map +1 -1
- package/dist/src/ide/diff.js +37 -0
- package/dist/src/ide/diff.js.map +1 -1
- package/dist/src/manager/launchd-service.js +1 -1
- package/dist/src/manager/launchd-service.js.map +1 -1
- package/dist/src/manager/lifecycle-helpers.js +14 -0
- package/dist/src/manager/lifecycle-helpers.js.map +1 -1
- package/dist/src/manager/lifecycle.js +49 -4
- package/dist/src/manager/lifecycle.js.map +1 -1
- package/dist/src/manager/notes/routes.js +0 -31
- package/dist/src/manager/notes/routes.js.map +1 -1
- package/dist/src/manager/notes/ws.js.map +1 -1
- package/dist/src/manager/registry.js.map +1 -1
- package/dist/src/manager/routes/dashboard-memory.js.map +1 -1
- package/dist/src/manager/scan.js +36 -1
- package/dist/src/manager/scan.js.map +1 -1
- package/dist/src/manager/server.js +21 -2
- package/dist/src/manager/server.js.map +1 -1
- package/dist/src/manager/systemd-service.js +1 -1
- package/dist/src/manager/systemd-service.js.map +1 -1
- package/dist/src/memory/keyword-expand.js +0 -187
- package/dist/src/memory/keyword-expand.js.map +1 -1
- package/dist/src/memory/runtime.js +2 -2
- package/dist/src/memory/runtime.js.map +1 -1
- package/dist/src/memory/shared.js.map +1 -1
- package/dist/src/orchestrator/collect.js +3 -2
- package/dist/src/orchestrator/collect.js.map +1 -1
- package/dist/src/orchestrator/distribute.js +7 -1
- package/dist/src/orchestrator/distribute.js.map +1 -1
- package/dist/src/orchestrator/friction.js +59 -0
- package/dist/src/orchestrator/friction.js.map +1 -0
- package/dist/src/orchestrator/pipeline.js +88 -45
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/sanitize.js +19 -0
- package/dist/src/orchestrator/sanitize.js.map +1 -0
- package/dist/src/orchestrator/seed.js +50 -0
- package/dist/src/orchestrator/seed.js.map +1 -0
- package/dist/src/orchestrator/state-machine.js +206 -74
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/orchestrator/worker-monitor.js +1 -1
- package/dist/src/orchestrator/worker-monitor.js.map +1 -1
- package/dist/src/orchestrator/worker-progress.js +29 -0
- package/dist/src/orchestrator/worker-progress.js.map +1 -0
- package/dist/src/orchestrator/worker-registry.js +63 -2
- package/dist/src/orchestrator/worker-registry.js.map +1 -1
- package/dist/src/prompt/builder.js +19 -29
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +40 -5
- package/dist/src/prompt/templates/control-system.md +2 -2
- package/dist/src/prompt/templates/employee.md +4 -1
- package/dist/src/prompt/templates/orchestration.md +23 -8
- package/dist/src/routes/employees.js +4 -4
- package/dist/src/routes/employees.js.map +1 -1
- package/dist/src/routes/goal.js +11 -3
- package/dist/src/routes/goal.js.map +1 -1
- package/dist/src/routes/heartbeat.js +1 -1
- package/dist/src/routes/heartbeat.js.map +1 -1
- package/dist/src/routes/i18n.js +1 -1
- package/dist/src/routes/i18n.js.map +1 -1
- package/dist/src/routes/jaw-memory.js +1 -1
- package/dist/src/routes/jaw-memory.js.map +1 -1
- package/dist/src/routes/memory.js.map +1 -1
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +265 -116
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/quota-agy-reverse.js +0 -5
- package/dist/src/routes/quota-agy-reverse.js.map +1 -1
- package/dist/src/routes/quota-kiro-reverse.js +187 -0
- package/dist/src/routes/quota-kiro-reverse.js.map +1 -0
- package/dist/src/routes/settings.js +31 -2
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/routes/skills.js.map +1 -1
- package/dist/src/security/security-audit-log.js.map +1 -1
- package/dist/src/shared/shell-command-display.js +51 -0
- package/dist/src/shared/shell-command-display.js.map +1 -0
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/trace/store.js +32 -0
- package/dist/src/trace/store.js.map +1 -1
- package/dist/src/types/cli-engine.js +1 -0
- package/dist/src/types/cli-engine.js.map +1 -1
- package/dist/src/workflows/employee-boundary.js +7 -7
- package/dist/src/workflows/employee-boundary.js.map +1 -1
- package/dist/src/workflows/handoff.js +16 -16
- package/dist/src/workflows/handoff.js.map +1 -1
- package/dist/src/workflows/scope-sandbox.js +46 -0
- package/dist/src/workflows/scope-sandbox.js.map +1 -0
- package/package.json +1 -1
- package/public/assets/providers/kiro-color.svg +15 -0
- package/public/assets/providers/kiro.svg +14 -0
- package/public/css/modals.css +79 -0
- package/public/css/orc-state.css +21 -2
- package/public/css/sidebar.css +85 -0
- package/public/css/variables.css +2 -2
- package/public/dist/assets/{Agent-CIUGaUVn.js → Agent-DkH7eoHd.js} +1 -1
- package/public/dist/assets/{DocPanel-C1pGgE-S.js → DocPanel-BU16GUlB.js} +1 -1
- package/public/dist/assets/DocPanel-gU-WkgyA.css +1 -0
- package/public/dist/assets/Employees-F0ssNuO-.js +1 -0
- package/public/dist/assets/Heartbeat-C3JS6gkF.js +1 -0
- package/public/dist/assets/Mcp-_Yq4N3Sk.js +4 -0
- package/public/dist/assets/{Memory-BgN8djV4.js → Memory-C-EQubN2.js} +1 -1
- package/public/dist/assets/{ModelProvider-DHQ1Zvw0.js → ModelProvider-BD2KrypI.js} +1 -1
- package/public/dist/assets/agent-meta-B1098B_a.js +1 -0
- package/public/dist/assets/app-ByHYOMZE.js +48 -0
- package/public/dist/assets/app-CYdhP6Vh.css +1 -0
- package/public/dist/assets/constants-s2UJodER.js +1 -0
- package/public/dist/assets/{employees-DZSPGlaH.js → employees-_A-p_bZg.js} +7 -7
- package/public/dist/assets/idb-cache-0LNMskFB.js +1 -0
- package/public/dist/assets/idb-cache-5H89a4l8.js +1 -0
- package/public/dist/assets/{manager-DyrwJLZr.css → manager-B2qEQRxN.css} +1 -1
- package/public/dist/assets/manager-CR9BA-wO.js +12 -0
- package/public/dist/assets/memory-D1RKYvyu.js +1 -0
- package/public/dist/assets/{memory-B4RpXXJ7.js → memory-D9AUn8fG.js} +9 -9
- package/public/dist/assets/{provider-icons-XEfJPGTe.js → provider-icons-CVVK5xUP.js} +35 -6
- package/public/dist/assets/{render-DnylWKO1.js → render-DFBujF8n.js} +4 -4
- package/public/dist/assets/settings-B4ZkeaU-.js +1 -0
- package/public/dist/assets/settings-D9jTceN0.js +151 -0
- package/public/dist/assets/sidebar-DPPRNiTc.js +48 -0
- package/public/dist/assets/skills-8nHJkv-r.js +1 -0
- package/public/dist/assets/{skills-CW7NEhew.js → skills-BDVLIrVT.js} +6 -6
- package/public/dist/assets/{slash-commands-CAydTUgf.js → slash-commands-C5da3q1p.js} +1 -1
- package/public/dist/assets/slash-commands-RJWO8wJZ.js +1 -0
- package/public/dist/assets/{trace-drawer-G-KP3IbQ.js → trace-drawer-5kqBzFMk.js} +1 -1
- package/public/dist/assets/ui-Crnp79bG.js +142 -0
- package/public/dist/assets/ui-HtSKByR3.js +1 -0
- package/public/dist/index.html +113 -86
- package/public/dist/manager/index.html +2 -2
- package/public/index.html +111 -84
- package/public/js/constants.ts +30 -7
- package/public/js/features/chat-search.ts +5 -1
- package/public/js/features/employees.ts +7 -2
- package/public/js/features/idb-cache.ts +4 -0
- package/public/js/features/message-history.ts +55 -7
- package/public/js/features/pending-queue.ts +8 -2
- package/public/js/features/process-block.ts +32 -17
- package/public/js/features/process-log-adapter.ts +11 -2
- package/public/js/features/settings-cli-status-render.ts +163 -0
- package/public/js/features/settings-cli-status.ts +130 -185
- package/public/js/features/settings-core.ts +5 -1
- package/public/js/features/settings-mcp.ts +513 -7
- package/public/js/features/settings.ts +1 -1
- package/public/js/features/tool-ui.ts +18 -4
- package/public/js/features/transport-status-row.ts +3 -0
- package/public/js/main.ts +3 -0
- package/public/js/preview-parent-origin.ts +14 -0
- package/public/js/provider-icons.ts +6 -1
- package/public/js/render/file-links.ts +10 -2
- package/public/js/ui.ts +5 -2
- package/public/js/virtual-scroll.ts +19 -0
- package/public/js/ws.ts +163 -10
- package/public/locales/en.json +8 -2
- package/public/locales/ja.json +8 -2
- package/public/locales/ko.json +8 -2
- package/public/locales/zh.json +8 -2
- package/public/manager/src/App.tsx +13 -70
- package/public/manager/src/InstancePreview.tsx +36 -11
- package/public/manager/src/SidebarRailRouter.tsx +1 -1
- package/public/manager/src/doc-panel/DocPanel.tsx +14 -1
- package/public/manager/src/doc-panel/doc-panel.css +16 -0
- package/public/manager/src/hooks/useInstanceMessageEvents.ts +4 -15
- package/public/manager/src/manager-components.css +1 -1
- package/public/manager/src/manager-polish.css +1 -1
- package/public/manager/src/manager-shortcut-runner.ts +107 -0
- package/public/manager/src/manager-shortcuts.ts +15 -0
- package/public/manager/src/panels/desktop-bridge.ts +2 -0
- package/public/manager/src/settings/SettingsSidebar.tsx +1 -1
- package/public/manager/src/settings/components/sidebar-filter.ts +2 -0
- package/public/manager/src/settings/pages/Mcp.tsx +311 -43
- package/public/manager/src/settings/pages/components/McpServerCard.tsx +89 -55
- package/public/manager/src/settings/pages/components/agent/agent-meta.ts +29 -6
- package/public/manager/src/settings/pages/components/employees-helpers.ts +1 -0
- package/public/manager/src/settings/pages/components/heartbeat-helpers.ts +1 -0
- package/public/manager/src/settings/pages/mcp-helpers.ts +54 -12
- package/public/manager/src/settings/types.ts +1 -0
- package/public/manager/src/types.ts +2 -1
- package/scripts/fresh-install-smoke.ts +1 -1
- package/public/dist/assets/DocPanel-CL1scIfq.css +0 -1
- package/public/dist/assets/Employees-BL9MAzzx.js +0 -1
- package/public/dist/assets/Heartbeat-D64JCg4t.js +0 -1
- package/public/dist/assets/Mcp-wYwJA_61.js +0 -3
- package/public/dist/assets/agent-meta-BhEbjy4P.js +0 -1
- package/public/dist/assets/app-DLDdZ41k.js +0 -48
- package/public/dist/assets/app-RpnKs-sT.css +0 -1
- package/public/dist/assets/constants-ahKI_aEG.js +0 -1
- package/public/dist/assets/idb-cache-BnZfG5FD.js +0 -1
- package/public/dist/assets/idb-cache-CZ3JdK8r.js +0 -1
- package/public/dist/assets/manager-CfyZloIP.js +0 -12
- package/public/dist/assets/memory-C9EVOb_D.js +0 -1
- package/public/dist/assets/settings-DIG_i79X.js +0 -1
- package/public/dist/assets/settings-VCXKhhhz.js +0 -66
- package/public/dist/assets/sidebar-Bi_ktFVw.js +0 -18
- package/public/dist/assets/skills-RjRqvykb.js +0 -1
- package/public/dist/assets/slash-commands-DdXu96Zd.js +0 -1
- package/public/dist/assets/ui-CIQjTLzc.js +0 -1
- 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
|
|
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
|
|
19
|
-
if (!
|
|
20
|
-
el.innerHTML =
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
38
|
-
const
|
|
39
|
-
const
|
|
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(
|
|
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
|
+
}
|