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
|
@@ -0,0 +1,1048 @@
|
|
|
1
|
+
import "@opentui/solid/runtime-plugin-support"
|
|
2
|
+
import {
|
|
3
|
+
type TuiDispose,
|
|
4
|
+
type TuiPlugin,
|
|
5
|
+
type TuiPluginApi,
|
|
6
|
+
type TuiPluginInstallResult,
|
|
7
|
+
type TuiPluginModule,
|
|
8
|
+
type TuiPluginMeta,
|
|
9
|
+
type TuiPluginStatus,
|
|
10
|
+
type TuiTheme,
|
|
11
|
+
} from "@nikcli-ai/plugin/tui"
|
|
12
|
+
import path from "path"
|
|
13
|
+
import { fileURLToPath } from "url"
|
|
14
|
+
|
|
15
|
+
import { Config } from "@/config/config"
|
|
16
|
+
import { TuiConfig } from "@/config/tui"
|
|
17
|
+
import { Log } from "@/util/log"
|
|
18
|
+
import { errorData, errorMessage } from "@/util/error"
|
|
19
|
+
import { isRecord } from "@/util/record"
|
|
20
|
+
import { Instance } from "@/project/instance"
|
|
21
|
+
import {
|
|
22
|
+
checkPluginCompatibility,
|
|
23
|
+
getPluginIdFromPackage,
|
|
24
|
+
isDeprecatedPlugin,
|
|
25
|
+
parsePluginSpecifier,
|
|
26
|
+
pluginSource,
|
|
27
|
+
readPluginId,
|
|
28
|
+
readV1Plugin,
|
|
29
|
+
resolvePluginEntrypoint,
|
|
30
|
+
resolvePluginId,
|
|
31
|
+
resolvePluginTarget,
|
|
32
|
+
type PluginSource,
|
|
33
|
+
} from "@/plugin/shared"
|
|
34
|
+
import { PluginMeta } from "@/plugin/meta"
|
|
35
|
+
import { installPlugin as installModulePlugin, patchPluginConfig, readPluginManifest } from "@/plugin/install"
|
|
36
|
+
import { addTheme, hasTheme } from "../context/theme"
|
|
37
|
+
import { Global } from "@/global"
|
|
38
|
+
import { Filesystem } from "@/util/filesystem"
|
|
39
|
+
import { Process } from "@/util/process"
|
|
40
|
+
import { Flag } from "@/flag/flag"
|
|
41
|
+
import { Installation } from "@/installation"
|
|
42
|
+
import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal"
|
|
43
|
+
import { setupSlots, Slot as View } from "./slots"
|
|
44
|
+
import type { HostPluginApi, HostSlots } from "./slots"
|
|
45
|
+
|
|
46
|
+
type PluginLoad = {
|
|
47
|
+
item?: Config.PluginSpec
|
|
48
|
+
spec: string
|
|
49
|
+
target: string
|
|
50
|
+
retry: boolean
|
|
51
|
+
source: PluginSource | "internal"
|
|
52
|
+
id: string
|
|
53
|
+
module: TuiPluginModule
|
|
54
|
+
install_theme: TuiTheme["install"]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type Api = HostPluginApi
|
|
58
|
+
|
|
59
|
+
type PluginScope = {
|
|
60
|
+
lifecycle: TuiPluginApi["lifecycle"]
|
|
61
|
+
track: (fn: (() => void) | undefined) => () => void
|
|
62
|
+
dispose: () => Promise<void>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type PluginEntry = {
|
|
66
|
+
id: string
|
|
67
|
+
load: PluginLoad
|
|
68
|
+
meta: TuiPluginMeta
|
|
69
|
+
plugin: TuiPlugin
|
|
70
|
+
options: Config.PluginOptions | undefined
|
|
71
|
+
enabled: boolean
|
|
72
|
+
scope?: PluginScope
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type ServerPluginEntry = {
|
|
76
|
+
id: string
|
|
77
|
+
spec: string
|
|
78
|
+
source: PluginSource | "internal"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type RuntimeState = {
|
|
82
|
+
directory: string
|
|
83
|
+
api: Api
|
|
84
|
+
slots: HostSlots
|
|
85
|
+
plugins: PluginEntry[]
|
|
86
|
+
plugins_by_id: Map<string, PluginEntry>
|
|
87
|
+
serverPlugins: ServerPluginEntry[]
|
|
88
|
+
pending: Map<
|
|
89
|
+
string,
|
|
90
|
+
{
|
|
91
|
+
item: Config.PluginSpec
|
|
92
|
+
meta: TuiConfig.PluginMeta
|
|
93
|
+
}
|
|
94
|
+
>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const log = Log.create({ service: "tui.plugin" })
|
|
98
|
+
const DISPOSE_TIMEOUT_MS = 5000
|
|
99
|
+
const KV_KEY = "plugin_enabled"
|
|
100
|
+
|
|
101
|
+
function fail(message: string, data: Record<string, unknown>) {
|
|
102
|
+
if (!("error" in data)) {
|
|
103
|
+
log.error(message, data)
|
|
104
|
+
console.error(`[tui.plugin] ${message}`, data)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const text = `${message}: ${errorMessage(data.error)}`
|
|
109
|
+
const next = { ...data, error: errorData(data.error) }
|
|
110
|
+
log.error(text, next)
|
|
111
|
+
console.error(`[tui.plugin] ${text}`, next)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type CleanupResult = { type: "ok" } | { type: "error"; error: unknown } | { type: "timeout" }
|
|
115
|
+
|
|
116
|
+
function runCleanup(fn: () => unknown, ms: number): Promise<CleanupResult> {
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
resolve({ type: "timeout" })
|
|
120
|
+
}, ms)
|
|
121
|
+
|
|
122
|
+
Promise.resolve()
|
|
123
|
+
.then(fn)
|
|
124
|
+
.then(
|
|
125
|
+
() => {
|
|
126
|
+
resolve({ type: "ok" })
|
|
127
|
+
},
|
|
128
|
+
(error) => {
|
|
129
|
+
resolve({ type: "error", error })
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
.finally(() => {
|
|
133
|
+
clearTimeout(timer)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isTheme(value: unknown) {
|
|
139
|
+
if (!isRecord(value)) return false
|
|
140
|
+
if (!("theme" in value)) return false
|
|
141
|
+
if (!isRecord(value.theme)) return false
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveRoot(root: string) {
|
|
146
|
+
if (root.startsWith("file://")) {
|
|
147
|
+
const file = fileURLToPath(root)
|
|
148
|
+
if (root.endsWith("/")) return file
|
|
149
|
+
return path.dirname(file)
|
|
150
|
+
}
|
|
151
|
+
if (path.isAbsolute(root)) return root
|
|
152
|
+
return path.resolve(process.cwd(), root)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function createThemeInstaller(meta: TuiConfig.PluginMeta, root: string, spec: string): TuiTheme["install"] {
|
|
156
|
+
return async (file) => {
|
|
157
|
+
const raw = file.startsWith("file://") ? fileURLToPath(file) : file
|
|
158
|
+
const src = path.isAbsolute(raw) ? raw : path.resolve(root, raw)
|
|
159
|
+
const theme = path.basename(src, path.extname(src))
|
|
160
|
+
if (hasTheme(theme)) return
|
|
161
|
+
|
|
162
|
+
const text = await Filesystem.readText(src).catch((error) => {
|
|
163
|
+
log.warn("failed to read tui plugin theme", { path: spec, theme: src, error })
|
|
164
|
+
return
|
|
165
|
+
})
|
|
166
|
+
if (text === undefined) return
|
|
167
|
+
|
|
168
|
+
const fail = Symbol()
|
|
169
|
+
const data = await Promise.resolve(text)
|
|
170
|
+
.then((x) => JSON.parse(x))
|
|
171
|
+
.catch((error) => {
|
|
172
|
+
log.warn("failed to parse tui plugin theme", { path: spec, theme: src, error })
|
|
173
|
+
return fail
|
|
174
|
+
})
|
|
175
|
+
if (data === fail) return
|
|
176
|
+
|
|
177
|
+
if (!isTheme(data)) {
|
|
178
|
+
log.warn("invalid tui plugin theme", { path: spec, theme: src })
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const source_dir = path.dirname(meta.source)
|
|
183
|
+
const local_dir =
|
|
184
|
+
path.basename(source_dir) === ".nikcli"
|
|
185
|
+
? path.join(source_dir, "themes")
|
|
186
|
+
: path.join(source_dir, ".nikcli", "themes")
|
|
187
|
+
const dest_dir = meta.scope === "local" ? local_dir : path.join(Global.Path.config, "themes")
|
|
188
|
+
const dest = path.join(dest_dir, `${theme}.json`)
|
|
189
|
+
if (!(await Filesystem.exists(dest))) {
|
|
190
|
+
await Filesystem.write(dest, text).catch((error) => {
|
|
191
|
+
log.warn("failed to persist tui plugin theme", { path: spec, theme: src, dest, error })
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
addTheme(theme, data)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function loadExternalPlugin(
|
|
200
|
+
item: Config.PluginSpec,
|
|
201
|
+
meta: TuiConfig.PluginMeta | undefined,
|
|
202
|
+
retry = false,
|
|
203
|
+
): Promise<PluginLoad | undefined> {
|
|
204
|
+
const spec = Config.pluginSpecifier(item)
|
|
205
|
+
if (isDeprecatedPlugin(spec)) return
|
|
206
|
+
log.info("loading tui plugin", { path: spec, retry })
|
|
207
|
+
const resolved = await resolvePluginTarget(spec).catch((error) => {
|
|
208
|
+
fail("failed to resolve tui plugin", { path: spec, retry, error })
|
|
209
|
+
return
|
|
210
|
+
})
|
|
211
|
+
if (!resolved) return
|
|
212
|
+
|
|
213
|
+
const source = pluginSource(spec)
|
|
214
|
+
if (source === "npm") {
|
|
215
|
+
const ok = await checkPluginCompatibility(resolved, Installation.VERSION)
|
|
216
|
+
.then(() => true)
|
|
217
|
+
.catch((error) => {
|
|
218
|
+
fail("tui plugin incompatible", { path: spec, retry, error })
|
|
219
|
+
return false
|
|
220
|
+
})
|
|
221
|
+
if (!ok) return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const target = resolved
|
|
225
|
+
if (!meta) {
|
|
226
|
+
fail("missing tui plugin metadata", {
|
|
227
|
+
path: spec,
|
|
228
|
+
retry,
|
|
229
|
+
})
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const root = resolveRoot(source === "file" ? spec : target)
|
|
234
|
+
const install_theme = createThemeInstaller(meta, root, spec)
|
|
235
|
+
const entry = await resolvePluginEntrypoint(spec, target, "tui").catch((error) => {
|
|
236
|
+
fail("failed to resolve tui plugin entry", { path: spec, target, retry, error })
|
|
237
|
+
return
|
|
238
|
+
})
|
|
239
|
+
if (!entry) return
|
|
240
|
+
|
|
241
|
+
const mod = await import(entry)
|
|
242
|
+
.then((raw) => {
|
|
243
|
+
return readV1Plugin(raw as Record<string, unknown>, spec, "tui", "detect") as TuiPluginModule | undefined
|
|
244
|
+
})
|
|
245
|
+
.catch((error) => {
|
|
246
|
+
fail("failed to load tui plugin", { path: spec, target: entry, retry, error })
|
|
247
|
+
return
|
|
248
|
+
})
|
|
249
|
+
if (!mod) return
|
|
250
|
+
|
|
251
|
+
const id = await resolvePluginId(source, spec, target, readPluginId(mod.id, spec)).catch((error) => {
|
|
252
|
+
fail("failed to load tui plugin", { path: spec, target, retry, error })
|
|
253
|
+
return
|
|
254
|
+
})
|
|
255
|
+
if (!id) return
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
item,
|
|
259
|
+
spec,
|
|
260
|
+
target,
|
|
261
|
+
retry,
|
|
262
|
+
source,
|
|
263
|
+
id,
|
|
264
|
+
module: mod,
|
|
265
|
+
install_theme,
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function createMeta(
|
|
270
|
+
source: PluginLoad["source"],
|
|
271
|
+
spec: string,
|
|
272
|
+
target: string,
|
|
273
|
+
meta: { state: PluginMeta.State; entry: PluginMeta.Entry } | undefined,
|
|
274
|
+
id?: string,
|
|
275
|
+
): TuiPluginMeta {
|
|
276
|
+
if (meta) {
|
|
277
|
+
return {
|
|
278
|
+
state: meta.state,
|
|
279
|
+
...meta.entry,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const now = Date.now()
|
|
284
|
+
return {
|
|
285
|
+
state: source === "internal" ? "same" : "first",
|
|
286
|
+
id: id ?? spec,
|
|
287
|
+
source,
|
|
288
|
+
spec,
|
|
289
|
+
target,
|
|
290
|
+
first_time: now,
|
|
291
|
+
last_time: now,
|
|
292
|
+
time_changed: now,
|
|
293
|
+
load_count: 1,
|
|
294
|
+
fingerprint: target,
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function loadInternalPlugin(item: InternalTuiPlugin): PluginLoad {
|
|
299
|
+
const spec = item.id
|
|
300
|
+
const target = spec
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
spec,
|
|
304
|
+
target,
|
|
305
|
+
retry: false,
|
|
306
|
+
source: "internal",
|
|
307
|
+
id: item.id,
|
|
308
|
+
module: item,
|
|
309
|
+
install_theme: createThemeInstaller(
|
|
310
|
+
{
|
|
311
|
+
scope: "global",
|
|
312
|
+
source: target,
|
|
313
|
+
},
|
|
314
|
+
process.cwd(),
|
|
315
|
+
spec,
|
|
316
|
+
),
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function createPluginScope(load: PluginLoad, id: string) {
|
|
321
|
+
const ctrl = new AbortController()
|
|
322
|
+
let list: { key: symbol; fn: TuiDispose }[] = []
|
|
323
|
+
let done = false
|
|
324
|
+
|
|
325
|
+
const onDispose = (fn: TuiDispose) => {
|
|
326
|
+
if (done) return () => {}
|
|
327
|
+
const key = Symbol()
|
|
328
|
+
list.push({ key, fn })
|
|
329
|
+
let drop = false
|
|
330
|
+
return () => {
|
|
331
|
+
if (drop) return
|
|
332
|
+
drop = true
|
|
333
|
+
list = list.filter((x) => x.key !== key)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const track = (fn: (() => void) | undefined) => {
|
|
338
|
+
if (!fn) return () => {}
|
|
339
|
+
const off = onDispose(fn)
|
|
340
|
+
let drop = false
|
|
341
|
+
return () => {
|
|
342
|
+
if (drop) return
|
|
343
|
+
drop = true
|
|
344
|
+
off()
|
|
345
|
+
fn()
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const lifecycle: TuiPluginApi["lifecycle"] = {
|
|
350
|
+
signal: ctrl.signal,
|
|
351
|
+
onDispose,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const dispose = async () => {
|
|
355
|
+
if (done) return
|
|
356
|
+
done = true
|
|
357
|
+
ctrl.abort()
|
|
358
|
+
const queue = [...list].reverse()
|
|
359
|
+
list = []
|
|
360
|
+
const until = Date.now() + DISPOSE_TIMEOUT_MS
|
|
361
|
+
for (const item of queue) {
|
|
362
|
+
const left = until - Date.now()
|
|
363
|
+
if (left <= 0) {
|
|
364
|
+
fail("timed out cleaning up tui plugin", {
|
|
365
|
+
path: load.spec,
|
|
366
|
+
id,
|
|
367
|
+
timeout: DISPOSE_TIMEOUT_MS,
|
|
368
|
+
})
|
|
369
|
+
break
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const out = await runCleanup(item.fn, left)
|
|
373
|
+
if (out.type === "ok") continue
|
|
374
|
+
if (out.type === "timeout") {
|
|
375
|
+
fail("timed out cleaning up tui plugin", {
|
|
376
|
+
path: load.spec,
|
|
377
|
+
id,
|
|
378
|
+
timeout: DISPOSE_TIMEOUT_MS,
|
|
379
|
+
})
|
|
380
|
+
break
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (out.type === "error") {
|
|
384
|
+
fail("failed to clean up tui plugin", {
|
|
385
|
+
path: load.spec,
|
|
386
|
+
id,
|
|
387
|
+
error: out.error,
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
lifecycle,
|
|
395
|
+
track,
|
|
396
|
+
dispose,
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function readPluginEnabledMap(value: unknown) {
|
|
401
|
+
if (!isRecord(value)) return {}
|
|
402
|
+
return Object.fromEntries(
|
|
403
|
+
Object.entries(value).filter((item): item is [string, boolean] => typeof item[1] === "boolean"),
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function pluginEnabledState(state: RuntimeState, config: TuiConfig.Info) {
|
|
408
|
+
return {
|
|
409
|
+
...readPluginEnabledMap(config.plugin_enabled),
|
|
410
|
+
...readPluginEnabledMap(state.api.kv.get(KV_KEY, {})),
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function writePluginEnabledState(api: Api, id: string, enabled: boolean) {
|
|
415
|
+
api.kv.set(KV_KEY, {
|
|
416
|
+
...readPluginEnabledMap(api.kv.get(KV_KEY, {})),
|
|
417
|
+
[id]: enabled,
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function listPluginStatus(state: RuntimeState): TuiPluginStatus[] {
|
|
422
|
+
const seen = new Set<string>()
|
|
423
|
+
const tuiPlugins: TuiPluginStatus[] = []
|
|
424
|
+
|
|
425
|
+
for (const plugin of state.plugins) {
|
|
426
|
+
if (seen.has(plugin.id)) continue
|
|
427
|
+
seen.add(plugin.id)
|
|
428
|
+
tuiPlugins.push({
|
|
429
|
+
id: plugin.id,
|
|
430
|
+
source: plugin.meta.source,
|
|
431
|
+
spec: plugin.meta.spec,
|
|
432
|
+
target: plugin.meta.target,
|
|
433
|
+
enabled: plugin.enabled,
|
|
434
|
+
active: plugin.scope !== undefined,
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
for (const sp of state.serverPlugins) {
|
|
439
|
+
if (seen.has(sp.id)) continue
|
|
440
|
+
seen.add(sp.id)
|
|
441
|
+
tuiPlugins.push({
|
|
442
|
+
id: sp.id,
|
|
443
|
+
source: sp.source,
|
|
444
|
+
spec: sp.spec,
|
|
445
|
+
target: "server",
|
|
446
|
+
enabled: true,
|
|
447
|
+
active: false,
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return tuiPlugins
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function deactivatePluginEntry(state: RuntimeState, plugin: PluginEntry, persist: boolean) {
|
|
455
|
+
plugin.enabled = false
|
|
456
|
+
if (persist) writePluginEnabledState(state.api, plugin.id, false)
|
|
457
|
+
if (!plugin.scope) return true
|
|
458
|
+
const scope = plugin.scope
|
|
459
|
+
plugin.scope = undefined
|
|
460
|
+
await scope.dispose()
|
|
461
|
+
return true
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function activatePluginEntry(state: RuntimeState, plugin: PluginEntry, persist: boolean) {
|
|
465
|
+
plugin.enabled = true
|
|
466
|
+
if (persist) writePluginEnabledState(state.api, plugin.id, true)
|
|
467
|
+
if (plugin.scope) return true
|
|
468
|
+
|
|
469
|
+
const scope = createPluginScope(plugin.load, plugin.id)
|
|
470
|
+
const api = pluginApi(state, plugin.load, scope, plugin.id)
|
|
471
|
+
const ok = await Promise.resolve()
|
|
472
|
+
.then(async () => {
|
|
473
|
+
await plugin.plugin(api, plugin.options, plugin.meta)
|
|
474
|
+
return true
|
|
475
|
+
})
|
|
476
|
+
.catch((error) => {
|
|
477
|
+
fail("failed to initialize tui plugin", {
|
|
478
|
+
path: plugin.load.spec,
|
|
479
|
+
id: plugin.id,
|
|
480
|
+
error,
|
|
481
|
+
})
|
|
482
|
+
return false
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
if (!ok) {
|
|
486
|
+
await scope.dispose()
|
|
487
|
+
return false
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (!plugin.enabled) {
|
|
491
|
+
await scope.dispose()
|
|
492
|
+
return true
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
plugin.scope = scope
|
|
496
|
+
return true
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function activatePluginById(state: RuntimeState | undefined, id: string, persist: boolean) {
|
|
500
|
+
if (!state) return false
|
|
501
|
+
const plugin = state.plugins_by_id.get(id)
|
|
502
|
+
if (!plugin) return false
|
|
503
|
+
return activatePluginEntry(state, plugin, persist)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async function deactivatePluginById(state: RuntimeState | undefined, id: string, persist: boolean) {
|
|
507
|
+
if (!state) return false
|
|
508
|
+
const plugin = state.plugins_by_id.get(id)
|
|
509
|
+
if (!plugin) return false
|
|
510
|
+
return deactivatePluginEntry(state, plugin, persist)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function pluginApi(runtime: RuntimeState, load: PluginLoad, scope: PluginScope, base: string): TuiPluginApi {
|
|
514
|
+
const api = runtime.api
|
|
515
|
+
const host = runtime.slots
|
|
516
|
+
const command: TuiPluginApi["command"] = {
|
|
517
|
+
register(cb) {
|
|
518
|
+
return scope.track(api.command.register(cb))
|
|
519
|
+
},
|
|
520
|
+
trigger(value) {
|
|
521
|
+
api.command.trigger(value)
|
|
522
|
+
},
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const route: TuiPluginApi["route"] = {
|
|
526
|
+
register(list) {
|
|
527
|
+
return scope.track(api.route.register(list))
|
|
528
|
+
},
|
|
529
|
+
navigate(name, params) {
|
|
530
|
+
api.route.navigate(name, params)
|
|
531
|
+
},
|
|
532
|
+
get current() {
|
|
533
|
+
return api.route.current
|
|
534
|
+
},
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const theme: TuiPluginApi["theme"] = Object.assign(Object.create(api.theme), {
|
|
538
|
+
install: load.install_theme,
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
const event: TuiPluginApi["event"] = {
|
|
542
|
+
on(type, handler) {
|
|
543
|
+
return scope.track(api.event.on(type, handler))
|
|
544
|
+
},
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
let count = 0
|
|
548
|
+
|
|
549
|
+
const slots: TuiPluginApi["slots"] = {
|
|
550
|
+
register(plugin) {
|
|
551
|
+
const id = count ? `${base}:${count}` : base
|
|
552
|
+
count += 1
|
|
553
|
+
scope.track(host.register({ ...plugin, id }))
|
|
554
|
+
return id
|
|
555
|
+
},
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
app: api.app,
|
|
560
|
+
command,
|
|
561
|
+
route,
|
|
562
|
+
ui: api.ui,
|
|
563
|
+
keybind: api.keybind,
|
|
564
|
+
tuiConfig: api.tuiConfig,
|
|
565
|
+
kv: api.kv,
|
|
566
|
+
state: api.state,
|
|
567
|
+
theme,
|
|
568
|
+
get client() {
|
|
569
|
+
return api.client
|
|
570
|
+
},
|
|
571
|
+
scopedClient: api.scopedClient,
|
|
572
|
+
workspace: api.workspace,
|
|
573
|
+
event,
|
|
574
|
+
renderer: api.renderer,
|
|
575
|
+
slots,
|
|
576
|
+
plugins: {
|
|
577
|
+
list() {
|
|
578
|
+
return listPluginStatus(runtime)
|
|
579
|
+
},
|
|
580
|
+
activate(id) {
|
|
581
|
+
return activatePluginById(runtime, id, true)
|
|
582
|
+
},
|
|
583
|
+
deactivate(id) {
|
|
584
|
+
return deactivatePluginById(runtime, id, true)
|
|
585
|
+
},
|
|
586
|
+
add(spec) {
|
|
587
|
+
return addPluginBySpec(runtime, spec)
|
|
588
|
+
},
|
|
589
|
+
install(spec, options) {
|
|
590
|
+
return installPluginBySpec(runtime, spec, options?.global)
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
lifecycle: scope.lifecycle,
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function collectPluginEntries(load: PluginLoad, meta: TuiPluginMeta): PluginEntry[] {
|
|
598
|
+
if (!load.module.tui) return []
|
|
599
|
+
const options = load.item ? Config.pluginOptions(load.item) : undefined
|
|
600
|
+
return [
|
|
601
|
+
{
|
|
602
|
+
id: load.id,
|
|
603
|
+
load,
|
|
604
|
+
meta,
|
|
605
|
+
plugin: load.module.tui,
|
|
606
|
+
options,
|
|
607
|
+
enabled: true,
|
|
608
|
+
},
|
|
609
|
+
]
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function addPluginEntry(state: RuntimeState, plugin: PluginEntry) {
|
|
613
|
+
if (state.plugins_by_id.has(plugin.id)) {
|
|
614
|
+
fail("duplicate tui plugin id", {
|
|
615
|
+
id: plugin.id,
|
|
616
|
+
path: plugin.load.spec,
|
|
617
|
+
})
|
|
618
|
+
return false
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
state.plugins_by_id.set(plugin.id, plugin)
|
|
622
|
+
state.plugins.push(plugin)
|
|
623
|
+
return true
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function applyInitialPluginEnabledState(state: RuntimeState, config: TuiConfig.Info) {
|
|
627
|
+
const map = pluginEnabledState(state, config)
|
|
628
|
+
for (const plugin of state.plugins) {
|
|
629
|
+
const enabled = map[plugin.id]
|
|
630
|
+
if (enabled === undefined) continue
|
|
631
|
+
plugin.enabled = enabled
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async function resolveExternalPlugins(
|
|
636
|
+
list: Config.PluginSpec[],
|
|
637
|
+
wait: () => Promise<void>,
|
|
638
|
+
meta: (item: Config.PluginSpec) => TuiConfig.PluginMeta | undefined,
|
|
639
|
+
) {
|
|
640
|
+
const loaded = await Promise.all(list.map((item) => loadExternalPlugin(item, meta(item))))
|
|
641
|
+
const ready: PluginLoad[] = []
|
|
642
|
+
let deps: Promise<void> | undefined
|
|
643
|
+
|
|
644
|
+
for (let i = 0; i < list.length; i++) {
|
|
645
|
+
let entry = loaded[i]
|
|
646
|
+
if (!entry) {
|
|
647
|
+
const item = list[i]
|
|
648
|
+
if (!item) continue
|
|
649
|
+
const spec = Config.pluginSpecifier(item)
|
|
650
|
+
if (pluginSource(spec) !== "file") continue
|
|
651
|
+
deps ??= wait().catch((error) => {
|
|
652
|
+
log.warn("failed waiting for tui plugin dependencies", { error })
|
|
653
|
+
})
|
|
654
|
+
await deps
|
|
655
|
+
entry = await loadExternalPlugin(item, meta(item), true)
|
|
656
|
+
}
|
|
657
|
+
if (!entry) continue
|
|
658
|
+
ready.push(entry)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return ready
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async function loadAllPluginInfo(list: Config.PluginSpec[]) {
|
|
665
|
+
const results: ServerPluginEntry[] = []
|
|
666
|
+
const seen = new Set<string>()
|
|
667
|
+
|
|
668
|
+
for (const item of list) {
|
|
669
|
+
const spec = Config.pluginSpecifier(item)
|
|
670
|
+
if (seen.has(spec)) continue
|
|
671
|
+
seen.add(spec)
|
|
672
|
+
|
|
673
|
+
if (isDeprecatedPlugin(spec)) continue
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
const source = pluginSource(spec)
|
|
677
|
+
const id = await getPluginIdFromPackage(spec, spec)
|
|
678
|
+
if (id) {
|
|
679
|
+
results.push({ id, spec, source })
|
|
680
|
+
} else {
|
|
681
|
+
// Fallback: use the package name as id
|
|
682
|
+
const parsed = parsePluginSpecifier(spec)
|
|
683
|
+
results.push({ id: parsed.pkg, spec, source })
|
|
684
|
+
}
|
|
685
|
+
} catch {
|
|
686
|
+
// Fallback: use the package name as id
|
|
687
|
+
const parsed = parsePluginSpecifier(spec)
|
|
688
|
+
results.push({ id: parsed.pkg, spec, source: pluginSource(spec) })
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return results
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async function addExternalPluginEntries(state: RuntimeState, ready: PluginLoad[]) {
|
|
696
|
+
if (!ready.length) return { plugins: [] as PluginEntry[], ok: true }
|
|
697
|
+
|
|
698
|
+
const meta = await PluginMeta.touchMany(
|
|
699
|
+
ready.map((item) => ({
|
|
700
|
+
spec: item.spec,
|
|
701
|
+
target: item.target,
|
|
702
|
+
id: item.id,
|
|
703
|
+
})),
|
|
704
|
+
).catch((error) => {
|
|
705
|
+
log.warn("failed to track tui plugins", { error })
|
|
706
|
+
return undefined
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
const plugins: PluginEntry[] = []
|
|
710
|
+
let ok = true
|
|
711
|
+
for (let i = 0; i < ready.length; i++) {
|
|
712
|
+
const entry = ready[i]
|
|
713
|
+
if (!entry) continue
|
|
714
|
+
const hit = meta?.[i]
|
|
715
|
+
if (hit && hit.state !== "same") {
|
|
716
|
+
log.info("tui plugin metadata updated", {
|
|
717
|
+
path: entry.spec,
|
|
718
|
+
retry: entry.retry,
|
|
719
|
+
state: hit.state,
|
|
720
|
+
source: hit.entry.source,
|
|
721
|
+
version: hit.entry.version,
|
|
722
|
+
modified: hit.entry.modified,
|
|
723
|
+
})
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const row = createMeta(entry.source, entry.spec, entry.target, hit, entry.id)
|
|
727
|
+
for (const plugin of collectPluginEntries(entry, row)) {
|
|
728
|
+
if (!addPluginEntry(state, plugin)) {
|
|
729
|
+
ok = false
|
|
730
|
+
continue
|
|
731
|
+
}
|
|
732
|
+
plugins.push(plugin)
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return { plugins, ok }
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function defaultPluginMeta(state: RuntimeState): TuiConfig.PluginMeta {
|
|
740
|
+
return {
|
|
741
|
+
scope: "local",
|
|
742
|
+
source: state.api.state.path.config || path.join(state.directory, ".nikcli", "tui.json"),
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function installCause(err: unknown) {
|
|
747
|
+
if (!err || typeof err !== "object") return
|
|
748
|
+
if (!("cause" in err)) return
|
|
749
|
+
return (err as { cause?: unknown }).cause
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function installDetail(err: unknown) {
|
|
753
|
+
const hit = installCause(err) ?? err
|
|
754
|
+
if (!(hit instanceof Process.RunFailedError)) {
|
|
755
|
+
return {
|
|
756
|
+
message: errorMessage(hit),
|
|
757
|
+
missing: false,
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const lines = hit.stderr
|
|
762
|
+
.toString()
|
|
763
|
+
.split(/\r?\n/)
|
|
764
|
+
.map((line) => line.trim())
|
|
765
|
+
.filter(Boolean)
|
|
766
|
+
const errs = lines.filter((line) => line.startsWith("error:")).map((line) => line.replace(/^error:\s*/, ""))
|
|
767
|
+
return {
|
|
768
|
+
message: errs[0] ?? lines.at(-1) ?? errorMessage(hit),
|
|
769
|
+
missing: lines.some((line) => line.includes("No version matching")),
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async function addPluginBySpec(state: RuntimeState | undefined, raw: string) {
|
|
774
|
+
if (!state) return false
|
|
775
|
+
const spec = raw.trim()
|
|
776
|
+
if (!spec) return false
|
|
777
|
+
|
|
778
|
+
const pending = state.pending.get(spec)
|
|
779
|
+
const item = pending?.item ?? spec
|
|
780
|
+
const nextSpec = Config.pluginSpecifier(item)
|
|
781
|
+
if (state.plugins.some((plugin) => plugin.load.spec === nextSpec)) {
|
|
782
|
+
state.pending.delete(spec)
|
|
783
|
+
return true
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const meta = pending?.meta ?? defaultPluginMeta(state)
|
|
787
|
+
|
|
788
|
+
const ready = await Instance.provide({
|
|
789
|
+
directory: state.directory,
|
|
790
|
+
fn: () =>
|
|
791
|
+
resolveExternalPlugins(
|
|
792
|
+
[item],
|
|
793
|
+
() => TuiConfig.waitForDependencies(),
|
|
794
|
+
() => meta,
|
|
795
|
+
),
|
|
796
|
+
}).catch((error) => {
|
|
797
|
+
fail("failed to add tui plugin", { path: nextSpec, error })
|
|
798
|
+
return [] as PluginLoad[]
|
|
799
|
+
})
|
|
800
|
+
if (!ready.length) {
|
|
801
|
+
fail("failed to add tui plugin", { path: nextSpec })
|
|
802
|
+
return false
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const first = ready[0]
|
|
806
|
+
if (!first) {
|
|
807
|
+
fail("failed to add tui plugin", { path: nextSpec })
|
|
808
|
+
return false
|
|
809
|
+
}
|
|
810
|
+
if (state.plugins_by_id.has(first.id)) {
|
|
811
|
+
state.pending.delete(spec)
|
|
812
|
+
return true
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const out = await addExternalPluginEntries(state, [first])
|
|
816
|
+
let ok = out.ok && out.plugins.length > 0
|
|
817
|
+
for (const plugin of out.plugins) {
|
|
818
|
+
const active = await activatePluginEntry(state, plugin, false)
|
|
819
|
+
if (!active) ok = false
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (ok) state.pending.delete(spec)
|
|
823
|
+
if (!ok) {
|
|
824
|
+
fail("failed to add tui plugin", { path: nextSpec })
|
|
825
|
+
}
|
|
826
|
+
return ok
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
async function installPluginBySpec(
|
|
830
|
+
state: RuntimeState | undefined,
|
|
831
|
+
raw: string,
|
|
832
|
+
global = false,
|
|
833
|
+
): Promise<TuiPluginInstallResult> {
|
|
834
|
+
if (!state) {
|
|
835
|
+
return {
|
|
836
|
+
ok: false,
|
|
837
|
+
message: "Plugin runtime is not ready.",
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const spec = raw.trim()
|
|
842
|
+
if (!spec) {
|
|
843
|
+
return {
|
|
844
|
+
ok: false,
|
|
845
|
+
message: "Plugin package name is required",
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const dir = state.api.state.path
|
|
850
|
+
if (!dir.directory) {
|
|
851
|
+
return {
|
|
852
|
+
ok: false,
|
|
853
|
+
message: "Paths are still syncing. Try again in a moment.",
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const install = await installModulePlugin(spec)
|
|
858
|
+
if (!install.ok) {
|
|
859
|
+
const out = installDetail(install.error)
|
|
860
|
+
return {
|
|
861
|
+
ok: false,
|
|
862
|
+
message: out.message,
|
|
863
|
+
missing: out.missing,
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const manifest = await readPluginManifest(install.target)
|
|
868
|
+
if (!manifest.ok) {
|
|
869
|
+
if (manifest.code === "manifest_no_targets") {
|
|
870
|
+
return {
|
|
871
|
+
ok: false,
|
|
872
|
+
message: `"${spec}" does not declare supported targets in package.json`,
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
return {
|
|
877
|
+
ok: false,
|
|
878
|
+
message: `Installed "${spec}" but failed to read ${manifest.file}`,
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const patch = await patchPluginConfig({
|
|
883
|
+
spec,
|
|
884
|
+
targets: manifest.targets,
|
|
885
|
+
global,
|
|
886
|
+
vcs: dir.worktree && dir.worktree !== "/" ? "git" : undefined,
|
|
887
|
+
worktree: dir.worktree,
|
|
888
|
+
directory: dir.directory,
|
|
889
|
+
})
|
|
890
|
+
if (!patch.ok) {
|
|
891
|
+
if (patch.code === "invalid_json") {
|
|
892
|
+
return {
|
|
893
|
+
ok: false,
|
|
894
|
+
message: `Invalid JSON in ${patch.file} (${patch.parse} at line ${patch.line}, column ${patch.col})`,
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
ok: false,
|
|
900
|
+
message: errorMessage(patch.error),
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const tui = manifest.targets.find((item) => item.kind === "tui")
|
|
905
|
+
if (tui) {
|
|
906
|
+
const file = patch.items.find((item) => item.kind === "tui")?.file
|
|
907
|
+
state.pending.set(spec, {
|
|
908
|
+
item: tui.opts ? [spec, tui.opts] : spec,
|
|
909
|
+
meta: {
|
|
910
|
+
scope: global ? "global" : "local",
|
|
911
|
+
source: (file ?? dir.config) || path.join(patch.dir, "tui.json"),
|
|
912
|
+
},
|
|
913
|
+
})
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
return {
|
|
917
|
+
ok: true,
|
|
918
|
+
dir: patch.dir,
|
|
919
|
+
tui: Boolean(tui),
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
export namespace TuiPluginRuntime {
|
|
924
|
+
let dir = ""
|
|
925
|
+
let loaded: Promise<void> | undefined
|
|
926
|
+
let runtime: RuntimeState | undefined
|
|
927
|
+
export const Slot = View
|
|
928
|
+
|
|
929
|
+
export async function init(api: HostPluginApi) {
|
|
930
|
+
const cwd = process.cwd()
|
|
931
|
+
if (loaded) {
|
|
932
|
+
if (dir !== cwd) {
|
|
933
|
+
throw new Error(`TuiPluginRuntime.init() called with a different working directory. expected=${dir} got=${cwd}`)
|
|
934
|
+
}
|
|
935
|
+
return loaded
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
dir = cwd
|
|
939
|
+
loaded = load(api)
|
|
940
|
+
return loaded
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export function list() {
|
|
944
|
+
if (!runtime) return []
|
|
945
|
+
return listPluginStatus(runtime)
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export async function activatePlugin(id: string) {
|
|
949
|
+
return activatePluginById(runtime, id, true)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
export async function deactivatePlugin(id: string) {
|
|
953
|
+
return deactivatePluginById(runtime, id, true)
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
export async function addPlugin(spec: string) {
|
|
957
|
+
return addPluginBySpec(runtime, spec)
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
export async function installPlugin(spec: string, options?: { global?: boolean }) {
|
|
961
|
+
return installPluginBySpec(runtime, spec, options?.global)
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
export async function dispose() {
|
|
965
|
+
const task = loaded
|
|
966
|
+
loaded = undefined
|
|
967
|
+
dir = ""
|
|
968
|
+
if (task) await task
|
|
969
|
+
const state = runtime
|
|
970
|
+
runtime = undefined
|
|
971
|
+
if (!state) return
|
|
972
|
+
const queue = [...state.plugins].reverse()
|
|
973
|
+
for (const plugin of queue) {
|
|
974
|
+
await deactivatePluginEntry(state, plugin, false)
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
async function load(api: Api) {
|
|
979
|
+
const cwd = process.cwd()
|
|
980
|
+
const slots = setupSlots(api)
|
|
981
|
+
const next: RuntimeState = {
|
|
982
|
+
directory: cwd,
|
|
983
|
+
api,
|
|
984
|
+
slots,
|
|
985
|
+
plugins: [],
|
|
986
|
+
plugins_by_id: new Map(),
|
|
987
|
+
serverPlugins: [],
|
|
988
|
+
pending: new Map(),
|
|
989
|
+
}
|
|
990
|
+
runtime = next
|
|
991
|
+
|
|
992
|
+
await Instance.provide({
|
|
993
|
+
directory: cwd,
|
|
994
|
+
fn: async () => {
|
|
995
|
+
const config = await TuiConfig.get()
|
|
996
|
+
const plugins = Flag.NIKCLI_PURE ? [] : (config.plugin ?? [])
|
|
997
|
+
if (Flag.NIKCLI_PURE && config.plugin?.length) {
|
|
998
|
+
log.info("skipping external tui plugins in pure mode", { count: config.plugin.length })
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
for (const item of INTERNAL_TUI_PLUGINS) {
|
|
1002
|
+
log.info("loading internal tui plugin", { id: item.id })
|
|
1003
|
+
const entry = loadInternalPlugin(item)
|
|
1004
|
+
const meta = createMeta(entry.source, entry.spec, entry.target, undefined, entry.id)
|
|
1005
|
+
for (const plugin of collectPluginEntries(entry, meta)) {
|
|
1006
|
+
addPluginEntry(next, plugin)
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const ready = await resolveExternalPlugins(
|
|
1011
|
+
plugins,
|
|
1012
|
+
() => TuiConfig.waitForDependencies(),
|
|
1013
|
+
(item) => config.plugin_meta?.[Config.pluginSpecifier(item)],
|
|
1014
|
+
)
|
|
1015
|
+
await addExternalPluginEntries(next, ready)
|
|
1016
|
+
|
|
1017
|
+
for (const entry of ready) {
|
|
1018
|
+
if (!next.serverPlugins.some((p) => p.id === entry.id)) {
|
|
1019
|
+
next.serverPlugins.push({
|
|
1020
|
+
id: entry.id,
|
|
1021
|
+
spec: entry.spec,
|
|
1022
|
+
source: entry.source,
|
|
1023
|
+
})
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const allPluginInfo = await loadAllPluginInfo(plugins)
|
|
1028
|
+
for (const info of allPluginInfo) {
|
|
1029
|
+
if (!next.serverPlugins.some((p) => p.id === info.id)) {
|
|
1030
|
+
next.serverPlugins.push(info)
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
applyInitialPluginEnabledState(next, config)
|
|
1035
|
+
for (const plugin of next.plugins) {
|
|
1036
|
+
if (!plugin.enabled) continue
|
|
1037
|
+
// Keep plugin execution sequential for deterministic side effects:
|
|
1038
|
+
// command registration order affects keybind/command precedence,
|
|
1039
|
+
// route registration is last-wins when ids collide,
|
|
1040
|
+
// and hook chains rely on stable plugin ordering.
|
|
1041
|
+
await activatePluginEntry(next, plugin, false)
|
|
1042
|
+
}
|
|
1043
|
+
},
|
|
1044
|
+
}).catch((error) => {
|
|
1045
|
+
fail("failed to load tui plugins", { directory: cwd, error })
|
|
1046
|
+
})
|
|
1047
|
+
}
|
|
1048
|
+
}
|