nikcli 0.0.6
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/.turbo/turbo-typecheck.log +1 -0
- package/AGENTS.md +27 -0
- package/Dockerfile +18 -0
- package/README.md +15 -0
- package/bin/nikcli +84 -0
- package/config.json +13 -0
- package/docs/tailscale-mobile/01-tailscale-setup.md +94 -0
- package/docs/tailscale-mobile/02-host-setup.md +115 -0
- package/docs/tailscale-mobile/03-phone-and-serve.md +134 -0
- package/docs/tailscale-mobile/README.md +59 -0
- package/examples/README.md +54 -0
- package/package.json +147 -0
- package/parsers-config.ts +253 -0
- package/script/build.ts +179 -0
- package/script/postinstall.mjs +125 -0
- package/script/publish-registries.ts +187 -0
- package/script/publish.ts +100 -0
- package/script/schema.ts +47 -0
- package/script/seed-e2e.ts +50 -0
- package/sequential-prancing-forest.md +373 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1303 -0
- package/src/acp/session.ts +105 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +528 -0
- package/src/agent/generate.txt +32 -0
- package/src/agent/prompt/compaction.txt +14 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +11 -0
- package/src/agent/prompt/title.txt +44 -0
- package/src/auth/index.ts +73 -0
- package/src/bun/index.ts +119 -0
- package/src/bun/registry.ts +54 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/chatbot/handlers.ts +150 -0
- package/src/chatbot/index.ts +132 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +69 -0
- package/src/cli/cmd/ads.ts +377 -0
- package/src/cli/cmd/agent.ts +259 -0
- package/src/cli/cmd/auth.ts +400 -0
- package/src/cli/cmd/chatbot.ts +420 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/companion.ts +81 -0
- package/src/cli/cmd/connectors.ts +593 -0
- package/src/cli/cmd/debug/agent.ts +166 -0
- package/src/cli/cmd/debug/config.ts +16 -0
- package/src/cli/cmd/debug/file.ts +97 -0
- package/src/cli/cmd/debug/index.ts +48 -0
- package/src/cli/cmd/debug/lsp.ts +52 -0
- package/src/cli/cmd/debug/ripgrep.ts +87 -0
- package/src/cli/cmd/debug/scrap.ts +16 -0
- package/src/cli/cmd/debug/skill.ts +16 -0
- package/src/cli/cmd/debug/snapshot.ts +52 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +412 -0
- package/src/cli/cmd/image-model.ts +128 -0
- package/src/cli/cmd/import.ts +201 -0
- package/src/cli/cmd/lovable.ts +128 -0
- package/src/cli/cmd/mcp.ts +738 -0
- package/src/cli/cmd/mobile.ts +223 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/plug.ts +231 -0
- package/src/cli/cmd/pr.ts +104 -0
- package/src/cli/cmd/rag-model.ts +167 -0
- package/src/cli/cmd/remote.ts +416 -0
- package/src/cli/cmd/run.ts +589 -0
- package/src/cli/cmd/serve.ts +51 -0
- package/src/cli/cmd/session.ts +133 -0
- package/src/cli/cmd/speak-model.ts +204 -0
- package/src/cli/cmd/stats.ts +402 -0
- package/src/cli/cmd/tui/app.tsx +841 -0
- package/src/cli/cmd/tui/attach.ts +31 -0
- package/src/cli/cmd/tui/component/border.tsx +75 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +172 -0
- package/src/cli/cmd/tui/component/dialog-config.tsx +291 -0
- package/src/cli/cmd/tui/component/dialog-connectors.tsx +440 -0
- package/src/cli/cmd/tui/component/dialog-image-model.tsx +97 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +260 -0
- package/src/cli/cmd/tui/component/dialog-rag-model.tsx +217 -0
- package/src/cli/cmd/tui/component/dialog-remote.tsx +489 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +170 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-settings/index.tsx +59 -0
- package/src/cli/cmd/tui/component/dialog-settings/prompt.tsx +40 -0
- package/src/cli/cmd/tui/component/dialog-settings/sidebar.tsx +39 -0
- package/src/cli/cmd/tui/component/dialog-settings/spinner.tsx +62 -0
- package/src/cli/cmd/tui/component/dialog-settings/ui.tsx +58 -0
- package/src/cli/cmd/tui/component/dialog-skills.tsx +117 -0
- package/src/cli/cmd/tui/component/dialog-speak-model.tsx +304 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +165 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-create.tsx +717 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +52 -0
- package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +350 -0
- package/src/cli/cmd/tui/component/error-component.tsx +91 -0
- package/src/cli/cmd/tui/component/logo.tsx +103 -0
- package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +669 -0
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +2165 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +63 -0
- package/src/cli/cmd/tui/component/spinner.tsx +24 -0
- package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
- package/src/cli/cmd/tui/component/table/markdown-table.tsx +267 -0
- package/src/cli/cmd/tui/component/table-db/db/connections.ts +75 -0
- package/src/cli/cmd/tui/component/table-db/db/db-connection.ts +223 -0
- package/src/cli/cmd/tui/component/table-db/db/db-preview.ts +202 -0
- package/src/cli/cmd/tui/component/table-db/db/factory.ts +77 -0
- package/src/cli/cmd/tui/component/table-db/db/index.ts +9 -0
- package/src/cli/cmd/tui/component/table-db/db/mysql-connection.ts +330 -0
- package/src/cli/cmd/tui/component/table-db/db/postgres-connection.ts +338 -0
- package/src/cli/cmd/tui/component/table-db/db/sqlite-connection.ts +302 -0
- package/src/cli/cmd/tui/component/table-db/db/types.ts +108 -0
- package/src/cli/cmd/tui/component/table-db/table/dbedit-hooks.ts +74 -0
- package/src/cli/cmd/tui/component/table-db/table/index.ts +15 -0
- package/src/cli/cmd/tui/component/table-db/table/table-events.ts +54 -0
- package/src/cli/cmd/tui/component/table-db/table/table-formatters.ts +191 -0
- package/src/cli/cmd/tui/component/table-db/table/table-hooks.ts +105 -0
- package/src/cli/cmd/tui/component/table-db/table/table-keyboard-handler.ts +255 -0
- package/src/cli/cmd/tui/component/table-db/table/table-layout-engine.ts +208 -0
- package/src/cli/cmd/tui/component/table-db/table/table-renderable.ts +486 -0
- package/src/cli/cmd/tui/component/table-db/table/table-selection-manager.ts +136 -0
- package/src/cli/cmd/tui/component/table-db/table/table-state.ts +198 -0
- package/src/cli/cmd/tui/component/table-db/table/types.ts +69 -0
- package/src/cli/cmd/tui/component/table-db/ui/db-visualizer.tsx +71 -0
- package/src/cli/cmd/tui/component/table-db/ui/index.ts +2 -0
- package/src/cli/cmd/tui/component/table-db/ui/table-renderer.ts +607 -0
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
- package/src/cli/cmd/tui/component/tips.tsx +195 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +24 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +102 -0
- package/src/cli/cmd/tui/context/kv.tsx +52 -0
- package/src/cli/cmd/tui/context/local.tsx +458 -0
- package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +54 -0
- package/src/cli/cmd/tui/context/sdk.tsx +128 -0
- package/src/cli/cmd/tui/context/server.tsx +8 -0
- package/src/cli/cmd/tui/context/sync.tsx +510 -0
- package/src/cli/cmd/tui/context/theme/abyss.json +233 -0
- package/src/cli/cmd/tui/context/theme/apple.json +235 -0
- package/src/cli/cmd/tui/context/theme/arctic.json +232 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/ayuai.json +229 -0
- package/src/cli/cmd/tui/context/theme/blood.json +229 -0
- package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
- package/src/cli/cmd/tui/context/theme/catmoe.json +235 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-latte.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +259 -0
- package/src/cli/cmd/tui/context/theme/charcoal.json +230 -0
- package/src/cli/cmd/tui/context/theme/chromatic.json +235 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cosmic.json +234 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/cyber.json +235 -0
- package/src/cli/cmd/tui/context/theme/dawnfox.json +229 -0
- package/src/cli/cmd/tui/context/theme/dimension.json +235 -0
- package/src/cli/cmd/tui/context/theme/dracula-official.json +222 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/dream.json +235 -0
- package/src/cli/cmd/tui/context/theme/duo.json +235 -0
- package/src/cli/cmd/tui/context/theme/dusk.json +235 -0
- package/src/cli/cmd/tui/context/theme/ebony.json +232 -0
- package/src/cli/cmd/tui/context/theme/equilibrium.json +232 -0
- package/src/cli/cmd/tui/context/theme/ethereal.json +235 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/fusion.json +235 -0
- package/src/cli/cmd/tui/context/theme/ghost.json +235 -0
- package/src/cli/cmd/tui/context/theme/github-dark.json +229 -0
- package/src/cli/cmd/tui/context/theme/github-dimmed.json +231 -0
- package/src/cli/cmd/tui/context/theme/github-light.json +229 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/glass.json +235 -0
- package/src/cli/cmd/tui/context/theme/gold.json +235 -0
- package/src/cli/cmd/tui/context/theme/gone.json +234 -0
- package/src/cli/cmd/tui/context/theme/greyscale.json +229 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
- package/src/cli/cmd/tui/context/theme/hacker.json +229 -0
- package/src/cli/cmd/tui/context/theme/holo.json +235 -0
- package/src/cli/cmd/tui/context/theme/ink.json +235 -0
- package/src/cli/cmd/tui/context/theme/jet.json +233 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +227 -0
- package/src/cli/cmd/tui/context/theme/lavender.json +236 -0
- package/src/cli/cmd/tui/context/theme/lightph.json +235 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
- package/src/cli/cmd/tui/context/theme/material-ocean.json +230 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +227 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +245 -0
- package/src/cli/cmd/tui/context/theme/midnight.json +235 -0
- package/src/cli/cmd/tui/context/theme/modern.json +235 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/muted.json +229 -0
- package/src/cli/cmd/tui/context/theme/neon.json +229 -0
- package/src/cli/cmd/tui/context/theme/neonfusion.json +235 -0
- package/src/cli/cmd/tui/context/theme/neutral.json +235 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nikcli.json +245 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/nordic.json +235 -0
- package/src/cli/cmd/tui/context/theme/nova.json +235 -0
- package/src/cli/cmd/tui/context/theme/obsidian.json +234 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +231 -0
- package/src/cli/cmd/tui/context/theme/one-pro.json +229 -0
- package/src/cli/cmd/tui/context/theme/onyx.json +233 -0
- package/src/cli/cmd/tui/context/theme/orng.json +249 -0
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +240 -0
- package/src/cli/cmd/tui/context/theme/oxocarbon.json +229 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/poimandres.json +230 -0
- package/src/cli/cmd/tui/context/theme/prism.json +235 -0
- package/src/cli/cmd/tui/context/theme/radiant.json +235 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/shadow.json +235 -0
- package/src/cli/cmd/tui/context/theme/silicon.json +235 -0
- package/src/cli/cmd/tui/context/theme/slate.json +233 -0
- package/src/cli/cmd/tui/context/theme/soft.json +235 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/spectrum.json +235 -0
- package/src/cli/cmd/tui/context/theme/starlight.json +233 -0
- package/src/cli/cmd/tui/context/theme/sunrise.json +235 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tech.json +235 -0
- package/src/cli/cmd/tui/context/theme/tokyonight-storm.json +245 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vapor.json +235 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/vivid.json +232 -0
- package/src/cli/cmd/tui/context/theme/void.json +235 -0
- package/src/cli/cmd/tui/context/theme/vscode.json +235 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme/zinc.json +236 -0
- package/src/cli/cmd/tui/context/theme.tsx +1303 -0
- package/src/cli/cmd/tui/event.ts +48 -0
- package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
- package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
- package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +288 -0
- package/src/cli/cmd/tui/plugin/api.tsx +407 -0
- package/src/cli/cmd/tui/plugin/index.ts +3 -0
- package/src/cli/cmd/tui/plugin/internal.ts +25 -0
- package/src/cli/cmd/tui/plugin/runtime.ts +1048 -0
- package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
- package/src/cli/cmd/tui/routes/home.tsx +153 -0
- package/src/cli/cmd/tui/routes/session/dbedit.tsx +474 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +105 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +75 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +177 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +2280 -0
- package/src/cli/cmd/tui/routes/session/permission.tsx +540 -0
- package/src/cli/cmd/tui/routes/session/question.tsx +435 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +313 -0
- package/src/cli/cmd/tui/thread.ts +174 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +102 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +389 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +180 -0
- package/src/cli/cmd/tui/ui/link.tsx +34 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +138 -0
- package/src/cli/cmd/tui/util/clipboard.ts +154 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/signal.ts +7 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/util/transcript.ts +98 -0
- package/src/cli/cmd/tui/win32.ts +110 -0
- package/src/cli/cmd/tui/worker.ts +156 -0
- package/src/cli/cmd/uninstall.ts +357 -0
- package/src/cli/cmd/upgrade.ts +72 -0
- package/src/cli/cmd/web.ts +87 -0
- package/src/cli/cmd/workspace-serve.ts +16 -0
- package/src/cli/error.ts +57 -0
- package/src/cli/network.ts +55 -0
- package/src/cli/remote/index.ts +36 -0
- package/src/cli/remote/notifications.ts +104 -0
- package/src/cli/remote/qr-renderer.ts +86 -0
- package/src/cli/remote/remote-service.ts +757 -0
- package/src/cli/remote/session-manager.ts +284 -0
- package/src/cli/remote/subagent-hooks.ts +151 -0
- package/src/cli/remote/types.ts +121 -0
- package/src/cli/ui.ts +96 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +174 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +99 -0
- package/src/config/config.ts +1760 -0
- package/src/config/markdown.ts +88 -0
- package/src/config/migrate-tui-config.ts +155 -0
- package/src/config/paths.ts +174 -0
- package/src/config/tui-schema.ts +36 -0
- package/src/config/tui.ts +209 -0
- package/src/connectors/api/base.ts +75 -0
- package/src/connectors/api/figma.ts +103 -0
- package/src/connectors/api/github.ts +247 -0
- package/src/connectors/api/lovable.ts +126 -0
- package/src/connectors/api/slack.ts +137 -0
- package/src/connectors/auth.ts +68 -0
- package/src/connectors/cache.ts +119 -0
- package/src/connectors/credentials.ts +81 -0
- package/src/connectors/index.ts +202 -0
- package/src/connectors/registry.ts +358 -0
- package/src/docs/context.ts +120 -0
- package/src/docs/library.ts +189 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +411 -0
- package/src/file/ripgrep.ts +402 -0
- package/src/file/time.ts +65 -0
- package/src/file/watcher.ts +127 -0
- package/src/flag/flag.ts +128 -0
- package/src/format/formatter.ts +356 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +57 -0
- package/src/id/id.ts +83 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +184 -0
- package/src/installation/index.ts +246 -0
- package/src/lsp/client.ts +250 -0
- package/src/lsp/index.ts +483 -0
- package/src/lsp/language.ts +119 -0
- package/src/lsp/server.ts +2046 -0
- package/src/mcp/auth.ts +121 -0
- package/src/mcp/index.ts +860 -0
- package/src/mcp/oauth-callback.ts +198 -0
- package/src/mcp/oauth-provider.ts +148 -0
- package/src/mobile/auth.ts +97 -0
- package/src/mobile/github-repo.ts +185 -0
- package/src/patch/index.ts +631 -0
- package/src/permission/arity.ts +150 -0
- package/src/permission/dbedit.ts +236 -0
- package/src/permission/index.ts +210 -0
- package/src/permission/next.ts +287 -0
- package/src/plugin/codex.ts +493 -0
- package/src/plugin/copilot.ts +261 -0
- package/src/plugin/index.ts +714 -0
- package/src/plugin/install.ts +379 -0
- package/src/plugin/meta.ts +165 -0
- package/src/plugin/shared.ts +188 -0
- package/src/project/bootstrap.ts +35 -0
- package/src/project/instance.ts +84 -0
- package/src/project/project.ts +373 -0
- package/src/project/state.ts +66 -0
- package/src/project/vcs.ts +76 -0
- package/src/prompt/stash-store.ts +93 -0
- package/src/provider/auth.ts +147 -0
- package/src/provider/models-macro.ts +22 -0
- package/src/provider/models.ts +216 -0
- package/src/provider/provider.ts +1483 -0
- package/src/provider/sdk/openai-compatible/src/README.md +5 -0
- package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1732 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +828 -0
- package/src/pty/index.ts +241 -0
- package/src/question/index.ts +171 -0
- package/src/rag/chunk.ts +43 -0
- package/src/rag/embed.ts +179 -0
- package/src/rag/index.ts +376 -0
- package/src/rag/storage.ts +76 -0
- package/src/scheduler/index.ts +61 -0
- package/src/server/error.ts +36 -0
- package/src/server/event.ts +7 -0
- package/src/server/mdns.ts +59 -0
- package/src/server/routes/chatbot.ts +205 -0
- package/src/server/routes/companion.ts +729 -0
- package/src/server/routes/config.ts +92 -0
- package/src/server/routes/connectors.ts +121 -0
- package/src/server/routes/dbedit.ts +76 -0
- package/src/server/routes/experimental.ts +210 -0
- package/src/server/routes/file.ts +197 -0
- package/src/server/routes/global.ts +135 -0
- package/src/server/routes/mcp.ts +225 -0
- package/src/server/routes/mobile.ts +2044 -0
- package/src/server/routes/permission.ts +68 -0
- package/src/server/routes/project.ts +82 -0
- package/src/server/routes/provider.ts +235 -0
- package/src/server/routes/pty.ts +169 -0
- package/src/server/routes/question.ts +98 -0
- package/src/server/routes/session.ts +968 -0
- package/src/server/routes/tui.ts +379 -0
- package/src/server/routes/workspace.ts +104 -0
- package/src/server/server.ts +761 -0
- package/src/server/ssh.ts +207 -0
- package/src/session/auth.ts +402 -0
- package/src/session/compaction.ts +253 -0
- package/src/session/generate.ts +38 -0
- package/src/session/index.ts +598 -0
- package/src/session/llm.ts +273 -0
- package/src/session/message-v2.ts +836 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +408 -0
- package/src/session/prompt/anthropic-20250930.txt +165 -0
- package/src/session/prompt/anthropic.txt +105 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex_header.txt +79 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/gemini.txt +155 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
- package/src/session/prompt/plan.txt +25 -0
- package/src/session/prompt/qwen.txt +108 -0
- package/src/session/prompt.ts +1942 -0
- package/src/session/retry.ts +90 -0
- package/src/session/revert.ts +120 -0
- package/src/session/stats.ts +404 -0
- package/src/session/status.ts +84 -0
- package/src/session/summary.ts +184 -0
- package/src/session/system.ts +195 -0
- package/src/session/toast.tsx +105 -0
- package/src/session/todo.ts +258 -0
- package/src/session/uninstall.ts +357 -0
- package/src/share/share-next.ts +421 -0
- package/src/share/share.ts +92 -0
- package/src/shell/shell.ts +65 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +232 -0
- package/src/snapshot/index.ts +297 -0
- package/src/storage/storage.ts +227 -0
- package/src/tool/apply_patch.ts +288 -0
- package/src/tool/apply_patch.txt +33 -0
- package/src/tool/bash.ts +252 -0
- package/src/tool/bash.txt +115 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +132 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/context_collect.ts +152 -0
- package/src/tool/context_collect.txt +9 -0
- package/src/tool/context_diagnostics.ts +81 -0
- package/src/tool/context_diagnostics.txt +5 -0
- package/src/tool/context_related.ts +117 -0
- package/src/tool/context_related.txt +5 -0
- package/src/tool/context_search.ts +108 -0
- package/src/tool/context_search.txt +8 -0
- package/src/tool/db-diff.ts +434 -0
- package/src/tool/db-table.txt +15 -0
- package/src/tool/docs_add.ts +50 -0
- package/src/tool/docs_add.txt +5 -0
- package/src/tool/docs_context.ts +56 -0
- package/src/tool/docs_context.txt +4 -0
- package/src/tool/docs_gap_report.ts +79 -0
- package/src/tool/docs_gap_report.txt +7 -0
- package/src/tool/docs_load.ts +41 -0
- package/src/tool/docs_load.txt +4 -0
- package/src/tool/docs_request.ts +129 -0
- package/src/tool/docs_request.txt +7 -0
- package/src/tool/docs_search.ts +51 -0
- package/src/tool/docs_search.txt +6 -0
- package/src/tool/docs_unload.ts +38 -0
- package/src/tool/docs_unload.txt +5 -0
- package/src/tool/edit.ts +614 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/external-directory.ts +32 -0
- package/src/tool/generate_image.ts +174 -0
- package/src/tool/generate_image.txt +12 -0
- package/src/tool/glob.ts +79 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +153 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +116 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp.ts +96 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/memory_search.ts +141 -0
- package/src/tool/memory_search.txt +8 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/plan-enter.txt +14 -0
- package/src/tool/plan-exit.txt +13 -0
- package/src/tool/plan.ts +130 -0
- package/src/tool/question.ts +33 -0
- package/src/tool/question.txt +10 -0
- package/src/tool/rag_index.ts +77 -0
- package/src/tool/rag_index.txt +10 -0
- package/src/tool/rag_reset.ts +26 -0
- package/src/tool/rag_reset.txt +4 -0
- package/src/tool/rag_search.ts +62 -0
- package/src/tool/rag_search.txt +6 -0
- package/src/tool/rag_status.ts +45 -0
- package/src/tool/rag_status.txt +4 -0
- package/src/tool/read.ts +203 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +214 -0
- package/src/tool/skill.ts +169 -0
- package/src/tool/skill.txt +3 -0
- package/src/tool/smart_docs.ts +74 -0
- package/src/tool/smart_docs.txt +7 -0
- package/src/tool/speak/elevenlabs.ts +201 -0
- package/src/tool/speak/openrouter.ts +240 -0
- package/src/tool/speak/provider.ts +83 -0
- package/src/tool/speak.ts +440 -0
- package/src/tool/task.ts +194 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +53 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +87 -0
- package/src/tool/tree.ts +218 -0
- package/src/tool/tree.txt +8 -0
- package/src/tool/truncation.ts +106 -0
- package/src/tool/use-connector.ts +47 -0
- package/src/tool/voice.ts +188 -0
- package/src/tool/webfetch.ts +205 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +14 -0
- package/src/tool/write.ts +80 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +16 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/error.ts +77 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +125 -0
- package/src/util/flock.ts +329 -0
- package/src/util/fn.ts +11 -0
- package/src/util/format.ts +20 -0
- package/src/util/hash.ts +7 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +103 -0
- package/src/util/lazy.ts +18 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +180 -0
- package/src/util/network.ts +9 -0
- package/src/util/process.ts +15 -0
- package/src/util/queue.ts +32 -0
- package/src/util/record.ts +3 -0
- package/src/util/rpc.ts +66 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +56 -0
- package/src/workspace/adaptors/index.ts +271 -0
- package/src/workspace/adaptors/types.ts +14 -0
- package/src/workspace/adaptors/worktree.ts +31 -0
- package/src/workspace/config.ts +19 -0
- package/src/workspace/index.ts +223 -0
- package/src/workspace/session-proxy-middleware.ts +97 -0
- package/src/workspace/sse.ts +66 -0
- package/src/workspace/workspace-context.ts +23 -0
- package/src/workspace/workspace-server/routes.ts +33 -0
- package/src/workspace/workspace-server/server.ts +47 -0
- package/src/worktree/index.ts +487 -0
- package/sst-env.d.ts +10 -0
- package/test/benchmark.test.ts +121 -0
- package/test/build-optimizations.test.ts +124 -0
- package/test/id-benchmark.test.ts +132 -0
- package/test/optimizations.test.ts +302 -0
- package/test/preload.ts +1 -0
- package/test/solidjs-benchmark.test.ts +262 -0
- package/test/solidjs-optimizations.test.ts +259 -0
- package/test/tui-benchmark.test.ts +230 -0
- package/test/wildcard-benchmark.test.ts +180 -0
- package/tsconfig.json +26 -0
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
import { dynamicTool, type Tool, jsonSchema, type JSONSchema7 } from "ai"
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
|
3
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
|
4
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
|
|
5
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
6
|
+
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
|
|
7
|
+
import {
|
|
8
|
+
CallToolResultSchema,
|
|
9
|
+
type Tool as MCPToolDef,
|
|
10
|
+
ToolListChangedNotificationSchema,
|
|
11
|
+
} from "@modelcontextprotocol/sdk/types.js"
|
|
12
|
+
import { Config } from "../config/config"
|
|
13
|
+
import { Log } from "../util/log"
|
|
14
|
+
import { NamedError } from "@nikcli-ai/util/error"
|
|
15
|
+
import z from "zod/v4"
|
|
16
|
+
import { Instance } from "../project/instance"
|
|
17
|
+
import { Installation } from "../installation"
|
|
18
|
+
import { withTimeout } from "@/util/timeout"
|
|
19
|
+
import { McpOAuthProvider } from "./oauth-provider"
|
|
20
|
+
import { McpOAuthCallback } from "./oauth-callback"
|
|
21
|
+
import { McpAuth } from "./auth"
|
|
22
|
+
import { BusEvent } from "../bus/bus-event"
|
|
23
|
+
import { Bus } from "@/bus"
|
|
24
|
+
import { TuiEvent } from "@/cli/cmd/tui/event"
|
|
25
|
+
import open from "open"
|
|
26
|
+
|
|
27
|
+
export namespace MCP {
|
|
28
|
+
const log = Log.create({ service: "mcp" })
|
|
29
|
+
const DEFAULT_TIMEOUT = 30_000
|
|
30
|
+
|
|
31
|
+
export const Resource = z
|
|
32
|
+
.object({
|
|
33
|
+
name: z.string(),
|
|
34
|
+
uri: z.string(),
|
|
35
|
+
description: z.string().optional(),
|
|
36
|
+
mimeType: z.string().optional(),
|
|
37
|
+
client: z.string(),
|
|
38
|
+
})
|
|
39
|
+
.meta({ ref: "McpResource" })
|
|
40
|
+
export type Resource = z.infer<typeof Resource>
|
|
41
|
+
|
|
42
|
+
export const ToolsChanged = BusEvent.define(
|
|
43
|
+
"mcp.tools.changed",
|
|
44
|
+
z.object({
|
|
45
|
+
server: z.string(),
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
export const BrowserOpenFailed = BusEvent.define(
|
|
50
|
+
"mcp.browser.open.failed",
|
|
51
|
+
z.object({
|
|
52
|
+
mcpName: z.string(),
|
|
53
|
+
url: z.string(),
|
|
54
|
+
}),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
export const Failed = NamedError.create(
|
|
58
|
+
"MCPFailed",
|
|
59
|
+
z.object({
|
|
60
|
+
name: z.string(),
|
|
61
|
+
}),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
type MCPClient = Client
|
|
65
|
+
|
|
66
|
+
export const Status = z
|
|
67
|
+
.discriminatedUnion("status", [
|
|
68
|
+
z
|
|
69
|
+
.object({
|
|
70
|
+
status: z.literal("connected"),
|
|
71
|
+
})
|
|
72
|
+
.meta({
|
|
73
|
+
ref: "MCPStatusConnected",
|
|
74
|
+
}),
|
|
75
|
+
z
|
|
76
|
+
.object({
|
|
77
|
+
status: z.literal("disabled"),
|
|
78
|
+
})
|
|
79
|
+
.meta({
|
|
80
|
+
ref: "MCPStatusDisabled",
|
|
81
|
+
}),
|
|
82
|
+
z
|
|
83
|
+
.object({
|
|
84
|
+
status: z.literal("failed"),
|
|
85
|
+
error: z.string(),
|
|
86
|
+
})
|
|
87
|
+
.meta({
|
|
88
|
+
ref: "MCPStatusFailed",
|
|
89
|
+
}),
|
|
90
|
+
z
|
|
91
|
+
.object({
|
|
92
|
+
status: z.literal("needs_auth"),
|
|
93
|
+
})
|
|
94
|
+
.meta({
|
|
95
|
+
ref: "MCPStatusNeedsAuth",
|
|
96
|
+
}),
|
|
97
|
+
z
|
|
98
|
+
.object({
|
|
99
|
+
status: z.literal("needs_client_registration"),
|
|
100
|
+
error: z.string(),
|
|
101
|
+
})
|
|
102
|
+
.meta({
|
|
103
|
+
ref: "MCPStatusNeedsClientRegistration",
|
|
104
|
+
}),
|
|
105
|
+
])
|
|
106
|
+
.meta({
|
|
107
|
+
ref: "MCPStatus",
|
|
108
|
+
})
|
|
109
|
+
export type Status = z.infer<typeof Status>
|
|
110
|
+
|
|
111
|
+
function registerNotificationHandlers(client: MCPClient, serverName: string) {
|
|
112
|
+
client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
|
|
113
|
+
log.info("tools list changed notification received", { server: serverName })
|
|
114
|
+
Bus.publish(ToolsChanged, { server: serverName })
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient, timeout?: number): Promise<Tool> {
|
|
119
|
+
const inputSchema = mcpTool.inputSchema
|
|
120
|
+
|
|
121
|
+
const schema: JSONSchema7 = {
|
|
122
|
+
...(inputSchema as JSONSchema7),
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: (inputSchema.properties ?? {}) as JSONSchema7["properties"],
|
|
125
|
+
additionalProperties: false,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return dynamicTool({
|
|
129
|
+
description: mcpTool.description ?? "",
|
|
130
|
+
inputSchema: jsonSchema(schema),
|
|
131
|
+
execute: async (args: unknown) => {
|
|
132
|
+
return client.callTool(
|
|
133
|
+
{
|
|
134
|
+
name: mcpTool.name,
|
|
135
|
+
arguments: args as Record<string, unknown>,
|
|
136
|
+
},
|
|
137
|
+
CallToolResultSchema,
|
|
138
|
+
{
|
|
139
|
+
resetTimeoutOnProgress: true,
|
|
140
|
+
timeout,
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type TransportWithAuth = StreamableHTTPClientTransport | SSEClientTransport
|
|
148
|
+
const pendingOAuthTransports = new Map<string, TransportWithAuth>()
|
|
149
|
+
|
|
150
|
+
type PromptInfo = Awaited<ReturnType<MCPClient["listPrompts"]>>["prompts"][number]
|
|
151
|
+
type ResourceInfo = Awaited<ReturnType<MCPClient["listResources"]>>["resources"][number]
|
|
152
|
+
type McpEntry = NonNullable<Config.Info["mcp"]>[string]
|
|
153
|
+
function isMcpConfigured(entry: McpEntry): entry is Config.Mcp {
|
|
154
|
+
return typeof entry === "object" && entry !== null && "type" in entry
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const state = Instance.state(
|
|
158
|
+
async () => {
|
|
159
|
+
const cfg = await Config.get()
|
|
160
|
+
const config = cfg.mcp ?? {}
|
|
161
|
+
const clients: Record<string, MCPClient> = {}
|
|
162
|
+
const status: Record<string, Status> = {}
|
|
163
|
+
|
|
164
|
+
await Promise.all(
|
|
165
|
+
Object.entries(config).map(async ([key, mcp]) => {
|
|
166
|
+
if (!isMcpConfigured(mcp)) {
|
|
167
|
+
log.error("Ignoring MCP config entry without type", { key })
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (mcp.enabled === false) {
|
|
172
|
+
status[key] = { status: "disabled" }
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const result = await create(key, mcp).catch(() => undefined)
|
|
177
|
+
if (!result) return
|
|
178
|
+
|
|
179
|
+
status[key] = result.status
|
|
180
|
+
|
|
181
|
+
if (result.mcpClient) {
|
|
182
|
+
clients[key] = result.mcpClient
|
|
183
|
+
}
|
|
184
|
+
}),
|
|
185
|
+
)
|
|
186
|
+
return {
|
|
187
|
+
status,
|
|
188
|
+
clients,
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
async (state) => {
|
|
192
|
+
await Promise.all(
|
|
193
|
+
Object.values(state.clients).map((client) =>
|
|
194
|
+
client.close().catch((error) => {
|
|
195
|
+
log.error("Failed to close MCP client", {
|
|
196
|
+
error,
|
|
197
|
+
})
|
|
198
|
+
}),
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
pendingOAuthTransports.clear()
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
async function fetchPromptsForClient(clientName: string, client: Client) {
|
|
206
|
+
const prompts = await client.listPrompts().catch((e) => {
|
|
207
|
+
log.error("failed to get prompts", { clientName, error: e.message })
|
|
208
|
+
return undefined
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
if (!prompts) {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const commands: Record<string, PromptInfo & { client: string }> = {}
|
|
216
|
+
|
|
217
|
+
for (const prompt of prompts.prompts) {
|
|
218
|
+
const sanitizedClientName = clientName.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
219
|
+
const sanitizedPromptName = prompt.name.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
220
|
+
const key = sanitizedClientName + ":" + sanitizedPromptName
|
|
221
|
+
|
|
222
|
+
commands[key] = { ...prompt, client: clientName }
|
|
223
|
+
}
|
|
224
|
+
return commands
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function fetchResourcesForClient(clientName: string, client: Client) {
|
|
228
|
+
const resources = await client.listResources().catch((e) => {
|
|
229
|
+
log.error("failed to get prompts", { clientName, error: e.message })
|
|
230
|
+
return undefined
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
if (!resources) {
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const commands: Record<string, ResourceInfo & { client: string }> = {}
|
|
238
|
+
|
|
239
|
+
for (const resource of resources.resources) {
|
|
240
|
+
const sanitizedClientName = clientName.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
241
|
+
const sanitizedResourceName = resource.name.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
242
|
+
const key = sanitizedClientName + ":" + sanitizedResourceName
|
|
243
|
+
|
|
244
|
+
commands[key] = { ...resource, client: clientName }
|
|
245
|
+
}
|
|
246
|
+
return commands
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function add(name: string, mcp: Config.Mcp) {
|
|
250
|
+
const s = await state()
|
|
251
|
+
const result = await create(name, mcp)
|
|
252
|
+
if (!result) {
|
|
253
|
+
const status = {
|
|
254
|
+
status: "failed" as const,
|
|
255
|
+
error: "unknown error",
|
|
256
|
+
}
|
|
257
|
+
s.status[name] = status
|
|
258
|
+
return {
|
|
259
|
+
status,
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (!result.mcpClient) {
|
|
263
|
+
s.status[name] = result.status
|
|
264
|
+
return {
|
|
265
|
+
status: s.status,
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const existingClient = s.clients[name]
|
|
269
|
+
if (existingClient) {
|
|
270
|
+
await existingClient.close().catch((error) => {
|
|
271
|
+
log.error("Failed to close existing MCP client", { name, error })
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
s.clients[name] = result.mcpClient
|
|
275
|
+
s.status[name] = result.status
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
status: s.status,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function create(key: string, mcp: Config.Mcp) {
|
|
283
|
+
if (mcp.enabled === false) {
|
|
284
|
+
log.info("mcp server disabled", { key })
|
|
285
|
+
return {
|
|
286
|
+
mcpClient: undefined,
|
|
287
|
+
status: { status: "disabled" as const },
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
log.info("found", { key, type: mcp.type })
|
|
292
|
+
let mcpClient: MCPClient | undefined
|
|
293
|
+
let status: Status | undefined = undefined
|
|
294
|
+
|
|
295
|
+
if (mcp.type === "remote") {
|
|
296
|
+
const oauthDisabled = mcp.oauth === false
|
|
297
|
+
const oauthConfig = typeof mcp.oauth === "object" ? mcp.oauth : undefined
|
|
298
|
+
let authProvider: McpOAuthProvider | undefined
|
|
299
|
+
|
|
300
|
+
if (!oauthDisabled) {
|
|
301
|
+
authProvider = new McpOAuthProvider(
|
|
302
|
+
key,
|
|
303
|
+
mcp.url,
|
|
304
|
+
{
|
|
305
|
+
clientId: oauthConfig?.clientId,
|
|
306
|
+
clientSecret: oauthConfig?.clientSecret,
|
|
307
|
+
scope: oauthConfig?.scope,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
onRedirect: async (url) => {
|
|
311
|
+
log.info("oauth redirect requested", { key, url: url.toString() })
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const transports: Array<{ name: string; transport: TransportWithAuth }> = [
|
|
318
|
+
{
|
|
319
|
+
name: "StreamableHTTP",
|
|
320
|
+
transport: new StreamableHTTPClientTransport(new URL(mcp.url), {
|
|
321
|
+
authProvider,
|
|
322
|
+
requestInit: mcp.headers ? { headers: mcp.headers } : undefined,
|
|
323
|
+
}),
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "SSE",
|
|
327
|
+
transport: new SSEClientTransport(new URL(mcp.url), {
|
|
328
|
+
authProvider,
|
|
329
|
+
requestInit: mcp.headers ? { headers: mcp.headers } : undefined,
|
|
330
|
+
}),
|
|
331
|
+
},
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
let lastError: Error | undefined
|
|
335
|
+
const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT
|
|
336
|
+
for (const { name, transport } of transports) {
|
|
337
|
+
try {
|
|
338
|
+
const client = new Client({
|
|
339
|
+
name: "nikcli",
|
|
340
|
+
version: Installation.VERSION,
|
|
341
|
+
})
|
|
342
|
+
await withTimeout(client.connect(transport), connectTimeout)
|
|
343
|
+
registerNotificationHandlers(client, key)
|
|
344
|
+
mcpClient = client
|
|
345
|
+
log.info("connected", { key, transport: name })
|
|
346
|
+
status = { status: "connected" }
|
|
347
|
+
break
|
|
348
|
+
} catch (error) {
|
|
349
|
+
lastError = error instanceof Error ? error : new Error(String(error))
|
|
350
|
+
|
|
351
|
+
if (error instanceof UnauthorizedError) {
|
|
352
|
+
log.info("mcp server requires authentication", { key, transport: name })
|
|
353
|
+
|
|
354
|
+
if (lastError.message.includes("registration") || lastError.message.includes("client_id")) {
|
|
355
|
+
status = {
|
|
356
|
+
status: "needs_client_registration" as const,
|
|
357
|
+
error: "Server does not support dynamic client registration. Please provide clientId in config.",
|
|
358
|
+
}
|
|
359
|
+
Bus.publish(TuiEvent.ToastShow, {
|
|
360
|
+
title: "MCP Authentication Required",
|
|
361
|
+
message: `Server "${key}" requires a pre-registered client ID. Add clientId to your config.`,
|
|
362
|
+
variant: "warning",
|
|
363
|
+
duration: 8000,
|
|
364
|
+
}).catch((e) => log.debug("failed to show toast", { error: e }))
|
|
365
|
+
} else {
|
|
366
|
+
pendingOAuthTransports.set(key, transport)
|
|
367
|
+
status = { status: "needs_auth" as const }
|
|
368
|
+
Bus.publish(TuiEvent.ToastShow, {
|
|
369
|
+
title: "MCP Authentication Required",
|
|
370
|
+
message: `Server "${key}" requires authentication. Run: nikcli mcp auth ${key}`,
|
|
371
|
+
variant: "warning",
|
|
372
|
+
duration: 8000,
|
|
373
|
+
}).catch((e) => log.debug("failed to show toast", { error: e }))
|
|
374
|
+
}
|
|
375
|
+
break
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
log.debug("transport connection failed", {
|
|
379
|
+
key,
|
|
380
|
+
transport: name,
|
|
381
|
+
url: mcp.url,
|
|
382
|
+
error: lastError.message,
|
|
383
|
+
})
|
|
384
|
+
status = {
|
|
385
|
+
status: "failed" as const,
|
|
386
|
+
error: lastError.message,
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (mcp.type === "local") {
|
|
393
|
+
const [cmd, ...args] = mcp.command
|
|
394
|
+
const cwd = Instance.directory
|
|
395
|
+
const transport = new StdioClientTransport({
|
|
396
|
+
stderr: "ignore",
|
|
397
|
+
command: cmd,
|
|
398
|
+
args,
|
|
399
|
+
cwd,
|
|
400
|
+
env: {
|
|
401
|
+
...process.env,
|
|
402
|
+
...(cmd === "nikcli" ? { BUN_BE_BUN: "1" } : {}),
|
|
403
|
+
...mcp.environment,
|
|
404
|
+
},
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT
|
|
408
|
+
try {
|
|
409
|
+
const client = new Client({
|
|
410
|
+
name: "nikcli",
|
|
411
|
+
version: Installation.VERSION,
|
|
412
|
+
})
|
|
413
|
+
await withTimeout(client.connect(transport), connectTimeout)
|
|
414
|
+
registerNotificationHandlers(client, key)
|
|
415
|
+
mcpClient = client
|
|
416
|
+
status = {
|
|
417
|
+
status: "connected",
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
log.error("local mcp startup failed", {
|
|
421
|
+
key,
|
|
422
|
+
command: mcp.command,
|
|
423
|
+
cwd,
|
|
424
|
+
error: error instanceof Error ? error.message : String(error),
|
|
425
|
+
})
|
|
426
|
+
status = {
|
|
427
|
+
status: "failed" as const,
|
|
428
|
+
error: error instanceof Error ? error.message : String(error),
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!status) {
|
|
434
|
+
status = {
|
|
435
|
+
status: "failed" as const,
|
|
436
|
+
error: "Unknown error",
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!mcpClient) {
|
|
441
|
+
return {
|
|
442
|
+
mcpClient: undefined,
|
|
443
|
+
status,
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const result = await withTimeout(mcpClient.listTools(), mcp.timeout ?? DEFAULT_TIMEOUT).catch((err) => {
|
|
448
|
+
log.error("failed to get tools from client", { key, error: err })
|
|
449
|
+
return undefined
|
|
450
|
+
})
|
|
451
|
+
if (!result) {
|
|
452
|
+
await mcpClient.close().catch((error) => {
|
|
453
|
+
log.error("Failed to close MCP client", {
|
|
454
|
+
error,
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
|
+
status = {
|
|
458
|
+
status: "failed",
|
|
459
|
+
error: "Failed to get tools",
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
mcpClient: undefined,
|
|
463
|
+
status: {
|
|
464
|
+
status: "failed" as const,
|
|
465
|
+
error: "Failed to get tools",
|
|
466
|
+
},
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
log.info("create() successfully created client", { key, toolCount: result.tools.length })
|
|
471
|
+
return {
|
|
472
|
+
mcpClient,
|
|
473
|
+
status,
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export async function status() {
|
|
478
|
+
const s = await state()
|
|
479
|
+
const cfg = await Config.get()
|
|
480
|
+
const config = cfg.mcp ?? {}
|
|
481
|
+
const result: Record<string, Status> = {}
|
|
482
|
+
|
|
483
|
+
for (const [key, mcp] of Object.entries(config)) {
|
|
484
|
+
if (!isMcpConfigured(mcp)) continue
|
|
485
|
+
result[key] = s.status[key] ?? { status: "disabled" }
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return result
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export async function clients() {
|
|
492
|
+
return state().then((state) => state.clients)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export async function connect(name: string) {
|
|
496
|
+
const cfg = await Config.get()
|
|
497
|
+
const config = cfg.mcp ?? {}
|
|
498
|
+
const mcp = config[name]
|
|
499
|
+
if (!mcp) {
|
|
500
|
+
log.error("MCP config not found", { name })
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!isMcpConfigured(mcp)) {
|
|
505
|
+
log.error("Ignoring MCP connect request for config without type", { name })
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const result = await create(name, { ...mcp, enabled: true })
|
|
510
|
+
|
|
511
|
+
if (!result) {
|
|
512
|
+
const s = await state()
|
|
513
|
+
s.status[name] = {
|
|
514
|
+
status: "failed",
|
|
515
|
+
error: "Unknown error during connection",
|
|
516
|
+
}
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const s = await state()
|
|
521
|
+
s.status[name] = result.status
|
|
522
|
+
if (result.mcpClient) {
|
|
523
|
+
const existingClient = s.clients[name]
|
|
524
|
+
if (existingClient) {
|
|
525
|
+
await existingClient.close().catch((error) => {
|
|
526
|
+
log.error("Failed to close existing MCP client", { name, error })
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
s.clients[name] = result.mcpClient
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export async function disconnect(name: string) {
|
|
534
|
+
const s = await state()
|
|
535
|
+
const client = s.clients[name]
|
|
536
|
+
if (client) {
|
|
537
|
+
await client.close().catch((error) => {
|
|
538
|
+
log.error("Failed to close MCP client", { name, error })
|
|
539
|
+
})
|
|
540
|
+
delete s.clients[name]
|
|
541
|
+
}
|
|
542
|
+
s.status[name] = { status: "disabled" }
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export async function tools() {
|
|
546
|
+
const result: Record<string, Tool> = {}
|
|
547
|
+
const s = await state()
|
|
548
|
+
const cfg = await Config.get()
|
|
549
|
+
const config = cfg.mcp ?? {}
|
|
550
|
+
const clientsSnapshot = await clients()
|
|
551
|
+
const defaultTimeout = cfg.experimental?.mcp_timeout
|
|
552
|
+
|
|
553
|
+
for (const [clientName, client] of Object.entries(clientsSnapshot)) {
|
|
554
|
+
if (s.status[clientName]?.status !== "connected") {
|
|
555
|
+
continue
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const toolsResult = await client.listTools().catch((e) => {
|
|
559
|
+
log.error("failed to get tools", { clientName, error: e.message })
|
|
560
|
+
const failedStatus = {
|
|
561
|
+
status: "failed" as const,
|
|
562
|
+
error: e instanceof Error ? e.message : String(e),
|
|
563
|
+
}
|
|
564
|
+
s.status[clientName] = failedStatus
|
|
565
|
+
delete s.clients[clientName]
|
|
566
|
+
return undefined
|
|
567
|
+
})
|
|
568
|
+
if (!toolsResult) {
|
|
569
|
+
continue
|
|
570
|
+
}
|
|
571
|
+
const mcpConfig = config[clientName]
|
|
572
|
+
const entry = isMcpConfigured(mcpConfig) ? mcpConfig : undefined
|
|
573
|
+
const timeout = entry?.timeout ?? defaultTimeout
|
|
574
|
+
for (const mcpTool of toolsResult.tools) {
|
|
575
|
+
const sanitizedClientName = clientName.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
576
|
+
const sanitizedToolName = mcpTool.name.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
577
|
+
result[sanitizedClientName + "_" + sanitizedToolName] = await convertMcpTool(mcpTool, client, timeout)
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return result
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export async function prompts() {
|
|
584
|
+
const s = await state()
|
|
585
|
+
const clientsSnapshot = await clients()
|
|
586
|
+
|
|
587
|
+
const prompts = Object.fromEntries<PromptInfo & { client: string }>(
|
|
588
|
+
(
|
|
589
|
+
await Promise.all(
|
|
590
|
+
Object.entries(clientsSnapshot).map(async ([clientName, client]) => {
|
|
591
|
+
if (s.status[clientName]?.status !== "connected") {
|
|
592
|
+
return []
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return Object.entries((await fetchPromptsForClient(clientName, client)) ?? {})
|
|
596
|
+
}),
|
|
597
|
+
)
|
|
598
|
+
).flat(),
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
return prompts
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export async function resources() {
|
|
605
|
+
const s = await state()
|
|
606
|
+
const clientsSnapshot = await clients()
|
|
607
|
+
|
|
608
|
+
const result = Object.fromEntries<ResourceInfo & { client: string }>(
|
|
609
|
+
(
|
|
610
|
+
await Promise.all(
|
|
611
|
+
Object.entries(clientsSnapshot).map(async ([clientName, client]) => {
|
|
612
|
+
if (s.status[clientName]?.status !== "connected") {
|
|
613
|
+
return []
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return Object.entries((await fetchResourcesForClient(clientName, client)) ?? {})
|
|
617
|
+
}),
|
|
618
|
+
)
|
|
619
|
+
).flat(),
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
return result
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export async function getPrompt(clientName: string, name: string, args?: Record<string, string>) {
|
|
626
|
+
const clientsSnapshot = await clients()
|
|
627
|
+
const client = clientsSnapshot[clientName]
|
|
628
|
+
|
|
629
|
+
if (!client) {
|
|
630
|
+
log.warn("client not found for prompt", {
|
|
631
|
+
clientName,
|
|
632
|
+
})
|
|
633
|
+
return undefined
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const result = await client
|
|
637
|
+
.getPrompt({
|
|
638
|
+
name: name,
|
|
639
|
+
arguments: args,
|
|
640
|
+
})
|
|
641
|
+
.catch((e) => {
|
|
642
|
+
log.error("failed to get prompt from MCP server", {
|
|
643
|
+
clientName,
|
|
644
|
+
promptName: name,
|
|
645
|
+
error: e.message,
|
|
646
|
+
})
|
|
647
|
+
return undefined
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
return result
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export async function readResource(clientName: string, resourceUri: string) {
|
|
654
|
+
const clientsSnapshot = await clients()
|
|
655
|
+
const client = clientsSnapshot[clientName]
|
|
656
|
+
|
|
657
|
+
if (!client) {
|
|
658
|
+
log.warn("client not found for prompt", {
|
|
659
|
+
clientName: clientName,
|
|
660
|
+
})
|
|
661
|
+
return undefined
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const result = await client
|
|
665
|
+
.readResource({
|
|
666
|
+
uri: resourceUri,
|
|
667
|
+
})
|
|
668
|
+
.catch((e) => {
|
|
669
|
+
log.error("failed to get prompt from MCP server", {
|
|
670
|
+
clientName: clientName,
|
|
671
|
+
resourceUri: resourceUri,
|
|
672
|
+
error: e.message,
|
|
673
|
+
})
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
return result
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export async function
|
|
680
|
+
startAuth(mcpName: string): Promise<{ authorizationUrl: string }> {
|
|
681
|
+
const cfg = await Config.get()
|
|
682
|
+
const mcpConfig = cfg.mcp?.[mcpName]
|
|
683
|
+
|
|
684
|
+
if (!mcpConfig) {
|
|
685
|
+
throw new Error(`MCP server not found: ${mcpName}`)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!isMcpConfigured(mcpConfig)) {
|
|
689
|
+
throw new Error(`MCP server ${mcpName} is disabled or missing configuration`)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (mcpConfig.type !== "remote") {
|
|
693
|
+
throw new Error(`MCP server ${mcpName} is not a remote server`)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (mcpConfig.oauth === false) {
|
|
697
|
+
throw new Error(`MCP server ${mcpName} has OAuth explicitly disabled`)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
await McpOAuthCallback.ensureRunning()
|
|
701
|
+
|
|
702
|
+
const oauthState = Array.from(crypto.getRandomValues(new Uint8Array(32)))
|
|
703
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
704
|
+
.join("")
|
|
705
|
+
await McpAuth.updateOAuthState(mcpName, oauthState)
|
|
706
|
+
|
|
707
|
+
const oauthConfig = typeof mcpConfig.oauth === "object" ? mcpConfig.oauth : undefined
|
|
708
|
+
let capturedUrl: URL | undefined
|
|
709
|
+
const authProvider = new McpOAuthProvider(
|
|
710
|
+
mcpName,
|
|
711
|
+
mcpConfig.url,
|
|
712
|
+
{
|
|
713
|
+
clientId: oauthConfig?.clientId,
|
|
714
|
+
clientSecret: oauthConfig?.clientSecret,
|
|
715
|
+
scope: oauthConfig?.scope,
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
onRedirect: async (url) => {
|
|
719
|
+
capturedUrl = url
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
const transport = new StreamableHTTPClientTransport(new URL(mcpConfig.url), {
|
|
725
|
+
authProvider,
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
const client = new Client({
|
|
730
|
+
name: "nikcli",
|
|
731
|
+
version: Installation.VERSION,
|
|
732
|
+
})
|
|
733
|
+
await client.connect(transport)
|
|
734
|
+
return { authorizationUrl: "" }
|
|
735
|
+
} catch (error) {
|
|
736
|
+
if (error instanceof UnauthorizedError && capturedUrl) {
|
|
737
|
+
pendingOAuthTransports.set(mcpName, transport)
|
|
738
|
+
return { authorizationUrl: capturedUrl.toString() }
|
|
739
|
+
}
|
|
740
|
+
throw error
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export async function authenticate(mcpName: string): Promise<Status> {
|
|
745
|
+
const { authorizationUrl } = await startAuth(mcpName)
|
|
746
|
+
|
|
747
|
+
if (!authorizationUrl) {
|
|
748
|
+
const s = await state()
|
|
749
|
+
return s.status[mcpName] ?? { status: "connected" }
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const oauthState = await McpAuth.getOAuthState(mcpName)
|
|
753
|
+
if (!oauthState) {
|
|
754
|
+
throw new Error("OAuth state not found - this should not happen")
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
log.info("opening browser for oauth", { mcpName, url: authorizationUrl, state: oauthState })
|
|
758
|
+
|
|
759
|
+
const callbackPromise = McpOAuthCallback.waitForCallback(oauthState)
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
const subprocess = await open(authorizationUrl)
|
|
763
|
+
await new Promise<void>((resolve, reject) => {
|
|
764
|
+
const timeout = setTimeout(() => resolve(), 500)
|
|
765
|
+
subprocess.on("error", (error) => {
|
|
766
|
+
clearTimeout(timeout)
|
|
767
|
+
reject(error)
|
|
768
|
+
})
|
|
769
|
+
subprocess.on("exit", (code) => {
|
|
770
|
+
if (code !== null && code !== 0) {
|
|
771
|
+
clearTimeout(timeout)
|
|
772
|
+
reject(new Error(`Browser open failed with exit code ${code}`))
|
|
773
|
+
}
|
|
774
|
+
})
|
|
775
|
+
})
|
|
776
|
+
} catch (error) {
|
|
777
|
+
log.warn("failed to open browser, user must open URL manually", { mcpName, error })
|
|
778
|
+
Bus.publish(BrowserOpenFailed, { mcpName, url: authorizationUrl })
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const code = await callbackPromise
|
|
782
|
+
|
|
783
|
+
const storedState = await McpAuth.getOAuthState(mcpName)
|
|
784
|
+
if (storedState !== oauthState) {
|
|
785
|
+
await McpAuth.clearOAuthState(mcpName)
|
|
786
|
+
throw new Error("OAuth state mismatch - potential CSRF attack")
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
await McpAuth.clearOAuthState(mcpName)
|
|
790
|
+
|
|
791
|
+
return finishAuth(mcpName, code)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
export async function finishAuth(mcpName: string, authorizationCode: string): Promise<Status> {
|
|
795
|
+
const transport = pendingOAuthTransports.get(mcpName)
|
|
796
|
+
|
|
797
|
+
if (!transport) {
|
|
798
|
+
throw new Error(`No pending OAuth flow for MCP server: ${mcpName}`)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
try {
|
|
802
|
+
await transport.finishAuth(authorizationCode)
|
|
803
|
+
|
|
804
|
+
await McpAuth.clearCodeVerifier(mcpName)
|
|
805
|
+
|
|
806
|
+
const cfg = await Config.get()
|
|
807
|
+
const mcpConfig = cfg.mcp?.[mcpName]
|
|
808
|
+
|
|
809
|
+
if (!mcpConfig) {
|
|
810
|
+
throw new Error(`MCP server not found: ${mcpName}`)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (!isMcpConfigured(mcpConfig)) {
|
|
814
|
+
throw new Error(`MCP server ${mcpName} is disabled or missing configuration`)
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
pendingOAuthTransports.delete(mcpName)
|
|
818
|
+
const result = await add(mcpName, mcpConfig)
|
|
819
|
+
|
|
820
|
+
const statusRecord = result.status as Record<string, Status>
|
|
821
|
+
return statusRecord[mcpName] ?? { status: "failed", error: "Unknown error after auth" }
|
|
822
|
+
} catch (error) {
|
|
823
|
+
log.error("failed to finish oauth", { mcpName, error })
|
|
824
|
+
return {
|
|
825
|
+
status: "failed",
|
|
826
|
+
error: error instanceof Error ? error.message : String(error),
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export async function removeAuth(mcpName: string): Promise<void> {
|
|
832
|
+
await McpAuth.remove(mcpName)
|
|
833
|
+
McpOAuthCallback.cancelPending(mcpName)
|
|
834
|
+
pendingOAuthTransports.delete(mcpName)
|
|
835
|
+
await McpAuth.clearOAuthState(mcpName)
|
|
836
|
+
log.info("removed oauth credentials", { mcpName })
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export async function supportsOAuth(mcpName: string): Promise<boolean> {
|
|
840
|
+
const cfg = await Config.get()
|
|
841
|
+
const mcpConfig = cfg.mcp?.[mcpName]
|
|
842
|
+
if (!mcpConfig) return false
|
|
843
|
+
if (!isMcpConfigured(mcpConfig)) return false
|
|
844
|
+
return mcpConfig.type === "remote" && mcpConfig.oauth !== false
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
export async function hasStoredTokens(mcpName: string): Promise<boolean> {
|
|
848
|
+
const entry = await McpAuth.get(mcpName)
|
|
849
|
+
return !!entry?.tokens
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
export type AuthStatus = "authenticated" | "expired" | "not_authenticated"
|
|
853
|
+
|
|
854
|
+
export async function getAuthStatus(mcpName: string): Promise<AuthStatus> {
|
|
855
|
+
const hasTokens = await hasStoredTokens(mcpName)
|
|
856
|
+
if (!hasTokens) return "not_authenticated"
|
|
857
|
+
const expired = await McpAuth.isTokenExpired(mcpName)
|
|
858
|
+
return expired ? "expired" : "authenticated"
|
|
859
|
+
}
|
|
860
|
+
}
|