@toolkit-cli/toolkode 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (676) hide show
  1. package/AGENTS.md +69 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/opencode +179 -0
  6. package/bin/toolkode +17 -0
  7. package/bin/toolkode.cjs +190 -0
  8. package/bunfig.toml +7 -0
  9. package/drizzle.config.ts +10 -0
  10. package/git +0 -0
  11. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  12. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  13. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  14. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  15. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  16. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  17. package/migration/20260225215848_workspace/migration.sql +7 -0
  18. package/migration/20260225215848_workspace/snapshot.json +959 -0
  19. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  20. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  21. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  22. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  23. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  24. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  25. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  26. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  27. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  28. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  29. package/migration/20260323234822_events/migration.sql +13 -0
  30. package/migration/20260323234822_events/snapshot.json +1271 -0
  31. package/package.json +160 -0
  32. package/parsers-config.ts +290 -0
  33. package/script/build-node.ts +54 -0
  34. package/script/build.ts +276 -0
  35. package/script/check-migrations.ts +16 -0
  36. package/script/postinstall.mjs +131 -0
  37. package/script/publish.ts +181 -0
  38. package/script/schema.ts +63 -0
  39. package/script/seed-e2e.ts +60 -0
  40. package/script/upgrade-opentui.ts +64 -0
  41. package/specs/effect-migration.md +293 -0
  42. package/specs/tui-plugins.md +389 -0
  43. package/src/account/account.sql.ts +39 -0
  44. package/src/account/index.ts +397 -0
  45. package/src/account/repo.ts +163 -0
  46. package/src/account/schema.ts +91 -0
  47. package/src/acp/README.md +174 -0
  48. package/src/acp/agent.ts +1743 -0
  49. package/src/acp/session.ts +116 -0
  50. package/src/acp/types.ts +24 -0
  51. package/src/agent/agent.ts +418 -0
  52. package/src/agent/generate.txt +75 -0
  53. package/src/agent/prompt/compaction.txt +14 -0
  54. package/src/agent/prompt/explore.txt +18 -0
  55. package/src/agent/prompt/summary.txt +11 -0
  56. package/src/agent/prompt/title.txt +44 -0
  57. package/src/auth/index.ts +115 -0
  58. package/src/bun/index.ts +128 -0
  59. package/src/bun/registry.ts +50 -0
  60. package/src/bus/bus-event.ts +40 -0
  61. package/src/bus/global.ts +10 -0
  62. package/src/bus/index.ts +184 -0
  63. package/src/channel/index.ts +231 -0
  64. package/src/cli/bootstrap.ts +17 -0
  65. package/src/cli/cmd/account.ts +257 -0
  66. package/src/cli/cmd/acp.ts +70 -0
  67. package/src/cli/cmd/agent.ts +245 -0
  68. package/src/cli/cmd/cmd.ts +7 -0
  69. package/src/cli/cmd/db.ts +119 -0
  70. package/src/cli/cmd/debug/agent.ts +167 -0
  71. package/src/cli/cmd/debug/config.ts +16 -0
  72. package/src/cli/cmd/debug/file.ts +97 -0
  73. package/src/cli/cmd/debug/index.ts +48 -0
  74. package/src/cli/cmd/debug/lsp.ts +53 -0
  75. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  76. package/src/cli/cmd/debug/scrap.ts +16 -0
  77. package/src/cli/cmd/debug/skill.ts +16 -0
  78. package/src/cli/cmd/debug/snapshot.ts +52 -0
  79. package/src/cli/cmd/export.ts +89 -0
  80. package/src/cli/cmd/generate.ts +38 -0
  81. package/src/cli/cmd/github.ts +1646 -0
  82. package/src/cli/cmd/import.ts +207 -0
  83. package/src/cli/cmd/mcp.ts +754 -0
  84. package/src/cli/cmd/models.ts +78 -0
  85. package/src/cli/cmd/plug.ts +231 -0
  86. package/src/cli/cmd/pr.ts +127 -0
  87. package/src/cli/cmd/providers.ts +482 -0
  88. package/src/cli/cmd/run.ts +738 -0
  89. package/src/cli/cmd/serve.ts +42 -0
  90. package/src/cli/cmd/session.ts +159 -0
  91. package/src/cli/cmd/stats.ts +410 -0
  92. package/src/cli/cmd/tui/app.tsx +1255 -0
  93. package/src/cli/cmd/tui/attach.ts +88 -0
  94. package/src/cli/cmd/tui/component/border.tsx +21 -0
  95. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  96. package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
  97. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  98. package/src/cli/cmd/tui/component/dialog-model.tsx +264 -0
  99. package/src/cli/cmd/tui/component/dialog-provider.tsx +334 -0
  100. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  101. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  102. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  103. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  104. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  105. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  106. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  107. package/src/cli/cmd/tui/component/dialog-variant.tsx +29 -0
  108. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
  109. package/src/cli/cmd/tui/component/error-component.tsx +91 -0
  110. package/src/cli/cmd/tui/component/logo.tsx +86 -0
  111. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  112. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +667 -0
  113. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  114. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  115. package/src/cli/cmd/tui/component/prompt/index.tsx +1353 -0
  116. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  117. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  118. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  119. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  120. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  121. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  122. package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
  123. package/src/cli/cmd/tui/context/args.tsx +15 -0
  124. package/src/cli/cmd/tui/context/directory.ts +13 -0
  125. package/src/cli/cmd/tui/context/exit.tsx +60 -0
  126. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  127. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  128. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  129. package/src/cli/cmd/tui/context/local.tsx +406 -0
  130. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  131. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  132. package/src/cli/cmd/tui/context/route.tsx +52 -0
  133. package/src/cli/cmd/tui/context/sdk.tsx +128 -0
  134. package/src/cli/cmd/tui/context/sync.tsx +504 -0
  135. package/src/cli/cmd/tui/context/theme/amber.json +245 -0
  136. package/src/cli/cmd/tui/context/theme/amiga.json +245 -0
  137. package/src/cli/cmd/tui/context/theme/atari.json +245 -0
  138. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  139. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  140. package/src/cli/cmd/tui/context/theme/borland.json +245 -0
  141. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  142. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  143. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  144. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  145. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  146. package/src/cli/cmd/tui/context/theme/commodore.json +245 -0
  147. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  148. package/src/cli/cmd/tui/context/theme/dos-edit.json +245 -0
  149. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  150. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  151. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  152. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  153. package/src/cli/cmd/tui/context/theme/gnu.json +245 -0
  154. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  155. package/src/cli/cmd/tui/context/theme/hacker.json +245 -0
  156. package/src/cli/cmd/tui/context/theme/irix.json +245 -0
  157. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  158. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  159. package/src/cli/cmd/tui/context/theme/mac84.json +245 -0
  160. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  161. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  162. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  163. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  164. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  165. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  166. package/src/cli/cmd/tui/context/theme/norton.json +245 -0
  167. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  168. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  169. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  170. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  171. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  172. package/src/cli/cmd/tui/context/theme/pine.json +245 -0
  173. package/src/cli/cmd/tui/context/theme/retrowave.json +245 -0
  174. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  175. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  176. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  177. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  178. package/src/cli/cmd/tui/context/theme/toolkode.json +245 -0
  179. package/src/cli/cmd/tui/context/theme/tron.json +245 -0
  180. package/src/cli/cmd/tui/context/theme/ubuntu.json +245 -0
  181. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  182. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  183. package/src/cli/cmd/tui/context/theme/vt100.json +245 -0
  184. package/src/cli/cmd/tui/context/theme/xcode.json +245 -0
  185. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  186. package/src/cli/cmd/tui/context/theme.tsx +1288 -0
  187. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  188. package/src/cli/cmd/tui/event.ts +49 -0
  189. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
  190. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  191. package/src/cli/cmd/tui/feature-plugins/sidebar/agents-panel.tsx +95 -0
  192. package/src/cli/cmd/tui/feature-plugins/sidebar/btw-panel.tsx +105 -0
  193. package/src/cli/cmd/tui/feature-plugins/sidebar/commands-panel.tsx +40 -0
  194. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  195. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  196. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  197. package/src/cli/cmd/tui/feature-plugins/sidebar/git-panel.tsx +36 -0
  198. package/src/cli/cmd/tui/feature-plugins/sidebar/loop-panel.tsx +124 -0
  199. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  200. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  201. package/src/cli/cmd/tui/feature-plugins/sidebar/session-panel.tsx +48 -0
  202. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  203. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
  204. package/src/cli/cmd/tui/plugin/api.tsx +420 -0
  205. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  206. package/src/cli/cmd/tui/plugin/internal.ts +37 -0
  207. package/src/cli/cmd/tui/plugin/runtime.ts +967 -0
  208. package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
  209. package/src/cli/cmd/tui/routes/home.tsx +173 -0
  210. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  211. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  212. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  213. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  214. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  215. package/src/cli/cmd/tui/routes/session/index.tsx +2229 -0
  216. package/src/cli/cmd/tui/routes/session/permission.tsx +685 -0
  217. package/src/cli/cmd/tui/routes/session/question.tsx +467 -0
  218. package/src/cli/cmd/tui/routes/session/sidebar.tsx +72 -0
  219. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +131 -0
  220. package/src/cli/cmd/tui/thread.ts +232 -0
  221. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  222. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
  223. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +208 -0
  224. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  225. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +106 -0
  226. package/src/cli/cmd/tui/ui/dialog-select.tsx +402 -0
  227. package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
  228. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  229. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  230. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  231. package/src/cli/cmd/tui/util/clipboard.ts +192 -0
  232. package/src/cli/cmd/tui/util/editor.ts +37 -0
  233. package/src/cli/cmd/tui/util/selection.ts +25 -0
  234. package/src/cli/cmd/tui/util/signal.ts +7 -0
  235. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  236. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  237. package/src/cli/cmd/tui/win32.ts +129 -0
  238. package/src/cli/cmd/tui/worker.ts +204 -0
  239. package/src/cli/cmd/uninstall.ts +353 -0
  240. package/src/cli/cmd/upgrade.ts +73 -0
  241. package/src/cli/cmd/web.ts +81 -0
  242. package/src/cli/effect/prompt.ts +25 -0
  243. package/src/cli/error.ts +46 -0
  244. package/src/cli/logo.ts +7 -0
  245. package/src/cli/network.ts +60 -0
  246. package/src/cli/ui.ts +116 -0
  247. package/src/cli/upgrade.ts +31 -0
  248. package/src/command/index.ts +195 -0
  249. package/src/command/template/initialize.txt +10 -0
  250. package/src/command/template/review.txt +101 -0
  251. package/src/config/config.ts +1693 -0
  252. package/src/config/markdown.ts +99 -0
  253. package/src/config/migrate-tui-config.ts +155 -0
  254. package/src/config/paths.ts +174 -0
  255. package/src/config/tui-schema.ts +36 -0
  256. package/src/config/tui.ts +212 -0
  257. package/src/control-plane/adaptors/index.ts +20 -0
  258. package/src/control-plane/adaptors/worktree.ts +38 -0
  259. package/src/control-plane/schema.ts +17 -0
  260. package/src/control-plane/sse.ts +66 -0
  261. package/src/control-plane/types.ts +21 -0
  262. package/src/control-plane/workspace.sql.ts +17 -0
  263. package/src/control-plane/workspace.ts +154 -0
  264. package/src/cron/index.ts +241 -0
  265. package/src/cron/parse.ts +189 -0
  266. package/src/effect/cross-spawn-spawner.ts +479 -0
  267. package/src/effect/instance-registry.ts +12 -0
  268. package/src/effect/instance-state.ts +47 -0
  269. package/src/effect/run-service.ts +19 -0
  270. package/src/env/index.ts +28 -0
  271. package/src/file/ignore.ts +82 -0
  272. package/src/file/index.ts +693 -0
  273. package/src/file/protected.ts +59 -0
  274. package/src/file/ripgrep.ts +376 -0
  275. package/src/file/time.ts +128 -0
  276. package/src/file/watcher.ts +171 -0
  277. package/src/filesystem/index.ts +226 -0
  278. package/src/flag/flag.ts +157 -0
  279. package/src/format/formatter.ts +396 -0
  280. package/src/format/index.ts +199 -0
  281. package/src/global/index.ts +54 -0
  282. package/src/hooks/index.ts +302 -0
  283. package/src/id/id.ts +85 -0
  284. package/src/ide/index.ts +74 -0
  285. package/src/index.ts +243 -0
  286. package/src/installation/index.ts +363 -0
  287. package/src/lsp/client.ts +252 -0
  288. package/src/lsp/index.ts +558 -0
  289. package/src/lsp/language.ts +120 -0
  290. package/src/lsp/launch.ts +21 -0
  291. package/src/lsp/server.ts +2093 -0
  292. package/src/mcp/auth.ts +181 -0
  293. package/src/mcp/index.ts +926 -0
  294. package/src/mcp/oauth-callback.ts +215 -0
  295. package/src/mcp/oauth-provider.ts +185 -0
  296. package/src/node.ts +1 -0
  297. package/src/patch/index.ts +680 -0
  298. package/src/permission/arity.ts +163 -0
  299. package/src/permission/evaluate.ts +15 -0
  300. package/src/permission/index.ts +322 -0
  301. package/src/permission/schema.ts +17 -0
  302. package/src/plugin/codex.ts +628 -0
  303. package/src/plugin/copilot.ts +343 -0
  304. package/src/plugin/index.ts +331 -0
  305. package/src/plugin/install.ts +384 -0
  306. package/src/plugin/meta.ts +165 -0
  307. package/src/plugin/shared.ts +172 -0
  308. package/src/project/bootstrap.ts +31 -0
  309. package/src/project/instance.ts +167 -0
  310. package/src/project/project.sql.ts +16 -0
  311. package/src/project/project.ts +519 -0
  312. package/src/project/schema.ts +16 -0
  313. package/src/project/state.ts +70 -0
  314. package/src/project/vcs.ts +124 -0
  315. package/src/provider/auth.ts +252 -0
  316. package/src/provider/error.ts +197 -0
  317. package/src/provider/models.ts +138 -0
  318. package/src/provider/provider.ts +1593 -0
  319. package/src/provider/schema.ts +39 -0
  320. package/src/provider/sdk/copilot/README.md +5 -0
  321. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  322. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  323. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  324. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  325. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  326. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  327. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  328. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  329. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  330. package/src/provider/sdk/copilot/index.ts +2 -0
  331. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  332. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  333. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  334. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  335. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  336. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  337. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
  338. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  339. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  340. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  341. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  342. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  343. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  344. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  345. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  346. package/src/provider/toolkit-manifest.ts +110 -0
  347. package/src/provider/transform.ts +1045 -0
  348. package/src/pty/index.ts +397 -0
  349. package/src/pty/schema.ts +17 -0
  350. package/src/question/index.ts +221 -0
  351. package/src/question/schema.ts +17 -0
  352. package/src/server/error.ts +36 -0
  353. package/src/server/event.ts +7 -0
  354. package/src/server/instance.ts +285 -0
  355. package/src/server/mdns.ts +60 -0
  356. package/src/server/middleware.ts +29 -0
  357. package/src/server/projectors.ts +28 -0
  358. package/src/server/router.ts +99 -0
  359. package/src/server/routes/config.ts +92 -0
  360. package/src/server/routes/event.ts +83 -0
  361. package/src/server/routes/experimental.ts +271 -0
  362. package/src/server/routes/file.ts +197 -0
  363. package/src/server/routes/global.ts +339 -0
  364. package/src/server/routes/mcp.ts +225 -0
  365. package/src/server/routes/permission.ts +69 -0
  366. package/src/server/routes/project.ts +118 -0
  367. package/src/server/routes/provider.ts +171 -0
  368. package/src/server/routes/pty.ts +211 -0
  369. package/src/server/routes/question.ts +99 -0
  370. package/src/server/routes/session.ts +1031 -0
  371. package/src/server/routes/tui.ts +379 -0
  372. package/src/server/routes/workspace.ts +94 -0
  373. package/src/server/server.ts +312 -0
  374. package/src/session/compaction.ts +424 -0
  375. package/src/session/index.ts +882 -0
  376. package/src/session/instruction.ts +321 -0
  377. package/src/session/llm.ts +341 -0
  378. package/src/session/message-v2.ts +1030 -0
  379. package/src/session/message.ts +191 -0
  380. package/src/session/overflow.ts +22 -0
  381. package/src/session/processor.ts +554 -0
  382. package/src/session/projectors.ts +135 -0
  383. package/src/session/prompt/anthropic.txt +105 -0
  384. package/src/session/prompt/beast.txt +147 -0
  385. package/src/session/prompt/build-switch.txt +5 -0
  386. package/src/session/prompt/codex.txt +79 -0
  387. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  388. package/src/session/prompt/default.txt +108 -0
  389. package/src/session/prompt/gemini.txt +155 -0
  390. package/src/session/prompt/gpt.txt +107 -0
  391. package/src/session/prompt/max-steps.txt +16 -0
  392. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  393. package/src/session/prompt/plan.txt +26 -0
  394. package/src/session/prompt/trinity.txt +97 -0
  395. package/src/session/prompt.ts +2058 -0
  396. package/src/session/retry.ts +106 -0
  397. package/src/session/revert.ts +138 -0
  398. package/src/session/schema.ts +38 -0
  399. package/src/session/session.sql.ts +103 -0
  400. package/src/session/status.ts +102 -0
  401. package/src/session/summary.ts +170 -0
  402. package/src/session/system.ts +74 -0
  403. package/src/session/todo.ts +57 -0
  404. package/src/share/share-next.ts +288 -0
  405. package/src/share/share.sql.ts +13 -0
  406. package/src/shell/shell.ts +73 -0
  407. package/src/skill/discovery.ts +116 -0
  408. package/src/skill/index.ts +284 -0
  409. package/src/skills-marketplace/index.ts +305 -0
  410. package/src/snapshot/index.ts +489 -0
  411. package/src/sql.d.ts +4 -0
  412. package/src/storage/db.bun.ts +8 -0
  413. package/src/storage/db.node.ts +8 -0
  414. package/src/storage/db.ts +177 -0
  415. package/src/storage/json-migration.ts +425 -0
  416. package/src/storage/schema.sql.ts +10 -0
  417. package/src/storage/schema.ts +5 -0
  418. package/src/storage/storage.ts +217 -0
  419. package/src/sync/README.md +179 -0
  420. package/src/sync/event.sql.ts +16 -0
  421. package/src/sync/index.ts +263 -0
  422. package/src/sync/schema.ts +14 -0
  423. package/src/team/index.ts +428 -0
  424. package/src/tool/apply_patch.ts +281 -0
  425. package/src/tool/apply_patch.txt +33 -0
  426. package/src/tool/bash.ts +271 -0
  427. package/src/tool/bash.txt +115 -0
  428. package/src/tool/batch.ts +183 -0
  429. package/src/tool/batch.txt +24 -0
  430. package/src/tool/codesearch.ts +132 -0
  431. package/src/tool/codesearch.txt +12 -0
  432. package/src/tool/cron-create.ts +54 -0
  433. package/src/tool/cron-create.txt +16 -0
  434. package/src/tool/cron-delete.ts +29 -0
  435. package/src/tool/cron-delete.txt +1 -0
  436. package/src/tool/cron-list.ts +41 -0
  437. package/src/tool/cron-list.txt +1 -0
  438. package/src/tool/edit.ts +667 -0
  439. package/src/tool/edit.txt +10 -0
  440. package/src/tool/external-directory.ts +32 -0
  441. package/src/tool/glob.ts +78 -0
  442. package/src/tool/glob.txt +6 -0
  443. package/src/tool/grep.ts +156 -0
  444. package/src/tool/grep.txt +8 -0
  445. package/src/tool/invalid.ts +17 -0
  446. package/src/tool/ls.ts +121 -0
  447. package/src/tool/ls.txt +1 -0
  448. package/src/tool/lsp.ts +97 -0
  449. package/src/tool/lsp.txt +19 -0
  450. package/src/tool/multiedit.ts +46 -0
  451. package/src/tool/multiedit.txt +41 -0
  452. package/src/tool/plan-enter.txt +14 -0
  453. package/src/tool/plan-exit.txt +13 -0
  454. package/src/tool/plan.ts +131 -0
  455. package/src/tool/question.ts +33 -0
  456. package/src/tool/question.txt +10 -0
  457. package/src/tool/read.ts +293 -0
  458. package/src/tool/read.txt +14 -0
  459. package/src/tool/registry.ts +232 -0
  460. package/src/tool/schema.ts +17 -0
  461. package/src/tool/send-message.ts +59 -0
  462. package/src/tool/send-message.txt +7 -0
  463. package/src/tool/skill.ts +105 -0
  464. package/src/tool/task.ts +230 -0
  465. package/src/tool/task.txt +62 -0
  466. package/src/tool/team.ts +235 -0
  467. package/src/tool/team.txt +22 -0
  468. package/src/tool/todo.ts +31 -0
  469. package/src/tool/todowrite.txt +167 -0
  470. package/src/tool/tool.ts +90 -0
  471. package/src/tool/truncate.ts +144 -0
  472. package/src/tool/truncation-dir.ts +4 -0
  473. package/src/tool/webfetch.ts +206 -0
  474. package/src/tool/webfetch.txt +13 -0
  475. package/src/tool/websearch.ts +150 -0
  476. package/src/tool/websearch.txt +14 -0
  477. package/src/tool/write.ts +84 -0
  478. package/src/tool/write.txt +8 -0
  479. package/src/util/abort.ts +35 -0
  480. package/src/util/archive.ts +17 -0
  481. package/src/util/color.ts +19 -0
  482. package/src/util/context.ts +25 -0
  483. package/src/util/data-url.ts +9 -0
  484. package/src/util/defer.ts +12 -0
  485. package/src/util/effect-http-client.ts +11 -0
  486. package/src/util/effect-zod.ts +98 -0
  487. package/src/util/error.ts +77 -0
  488. package/src/util/filesystem.ts +203 -0
  489. package/src/util/flock.ts +333 -0
  490. package/src/util/fn.ts +21 -0
  491. package/src/util/format.ts +20 -0
  492. package/src/util/git.ts +35 -0
  493. package/src/util/glob.ts +34 -0
  494. package/src/util/hash.ts +7 -0
  495. package/src/util/iife.ts +3 -0
  496. package/src/util/keybind.ts +103 -0
  497. package/src/util/lazy.ts +23 -0
  498. package/src/util/locale.ts +81 -0
  499. package/src/util/lock.ts +98 -0
  500. package/src/util/log.ts +182 -0
  501. package/src/util/network.ts +9 -0
  502. package/src/util/process.ts +172 -0
  503. package/src/util/queue.ts +32 -0
  504. package/src/util/record.ts +3 -0
  505. package/src/util/rpc.ts +66 -0
  506. package/src/util/schema.ts +53 -0
  507. package/src/util/scrap.ts +10 -0
  508. package/src/util/signal.ts +12 -0
  509. package/src/util/timeout.ts +14 -0
  510. package/src/util/token.ts +7 -0
  511. package/src/util/update-schema.ts +13 -0
  512. package/src/util/which.ts +14 -0
  513. package/src/util/wildcard.ts +59 -0
  514. package/src/worktree/index.ts +638 -0
  515. package/sst-env.d.ts +10 -0
  516. package/test/AGENTS.md +81 -0
  517. package/test/account/repo.test.ts +326 -0
  518. package/test/account/service.test.ts +282 -0
  519. package/test/acp/agent-interface.test.ts +51 -0
  520. package/test/acp/event-subscription.test.ts +685 -0
  521. package/test/agent/agent.test.ts +717 -0
  522. package/test/auth/auth.test.ts +58 -0
  523. package/test/bun.test.ts +53 -0
  524. package/test/bus/bus-effect.test.ts +164 -0
  525. package/test/bus/bus-integration.test.ts +87 -0
  526. package/test/bus/bus.test.ts +219 -0
  527. package/test/cli/account.test.ts +26 -0
  528. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  529. package/test/cli/github-action.test.ts +198 -0
  530. package/test/cli/github-remote.test.ts +80 -0
  531. package/test/cli/import.test.ts +54 -0
  532. package/test/cli/plugin-auth-picker.test.ts +120 -0
  533. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  534. package/test/cli/tui/plugin-add.test.ts +61 -0
  535. package/test/cli/tui/plugin-install.test.ts +95 -0
  536. package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
  537. package/test/cli/tui/plugin-loader-entrypoint.test.ts +189 -0
  538. package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
  539. package/test/cli/tui/plugin-loader.test.ts +563 -0
  540. package/test/cli/tui/plugin-toggle.test.ts +157 -0
  541. package/test/cli/tui/theme-store.test.ts +51 -0
  542. package/test/cli/tui/thread.test.ts +128 -0
  543. package/test/cli/tui/transcript.test.ts +322 -0
  544. package/test/config/agent-color.test.ts +71 -0
  545. package/test/config/config.test.ts +2187 -0
  546. package/test/config/fixtures/empty-frontmatter.md +4 -0
  547. package/test/config/fixtures/frontmatter.md +28 -0
  548. package/test/config/fixtures/markdown-header.md +11 -0
  549. package/test/config/fixtures/no-frontmatter.md +1 -0
  550. package/test/config/fixtures/weird-model-id.md +13 -0
  551. package/test/config/markdown.test.ts +228 -0
  552. package/test/config/tui.test.ts +667 -0
  553. package/test/control-plane/sse.test.ts +56 -0
  554. package/test/effect/cross-spawn-spawner.test.ts +402 -0
  555. package/test/effect/instance-state.test.ts +384 -0
  556. package/test/effect/run-service.test.ts +46 -0
  557. package/test/file/fsmonitor.test.ts +62 -0
  558. package/test/file/ignore.test.ts +10 -0
  559. package/test/file/index.test.ts +946 -0
  560. package/test/file/path-traversal.test.ts +198 -0
  561. package/test/file/ripgrep.test.ts +54 -0
  562. package/test/file/time.test.ts +354 -0
  563. package/test/file/watcher.test.ts +247 -0
  564. package/test/filesystem/filesystem.test.ts +319 -0
  565. package/test/fixture/db.ts +11 -0
  566. package/test/fixture/fixture.test.ts +26 -0
  567. package/test/fixture/fixture.ts +141 -0
  568. package/test/fixture/flock-worker.ts +72 -0
  569. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  570. package/test/fixture/plug-worker.ts +93 -0
  571. package/test/fixture/plugin-meta-worker.ts +26 -0
  572. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  573. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  574. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  575. package/test/fixture/skills/index.json +6 -0
  576. package/test/fixture/tui-plugin.ts +335 -0
  577. package/test/fixture/tui-runtime.ts +34 -0
  578. package/test/format/format.test.ts +179 -0
  579. package/test/ide/ide.test.ts +82 -0
  580. package/test/installation/installation.test.ts +151 -0
  581. package/test/keybind.test.ts +421 -0
  582. package/test/lib/effect.ts +37 -0
  583. package/test/lib/filesystem.ts +10 -0
  584. package/test/lsp/client.test.ts +95 -0
  585. package/test/lsp/index.test.ts +55 -0
  586. package/test/lsp/launch.test.ts +22 -0
  587. package/test/lsp/lifecycle.test.ts +147 -0
  588. package/test/mcp/headers.test.ts +153 -0
  589. package/test/mcp/lifecycle.test.ts +750 -0
  590. package/test/mcp/oauth-auto-connect.test.ts +199 -0
  591. package/test/mcp/oauth-browser.test.ts +249 -0
  592. package/test/memory/abort-leak.test.ts +137 -0
  593. package/test/patch/patch.test.ts +348 -0
  594. package/test/permission/arity.test.ts +33 -0
  595. package/test/permission/next.test.ts +1148 -0
  596. package/test/permission-task.test.ts +323 -0
  597. package/test/plugin/auth-override.test.ts +74 -0
  598. package/test/plugin/codex.test.ts +123 -0
  599. package/test/plugin/install-concurrency.test.ts +134 -0
  600. package/test/plugin/install.test.ts +504 -0
  601. package/test/plugin/loader-shared.test.ts +625 -0
  602. package/test/plugin/meta.test.ts +137 -0
  603. package/test/plugin/trigger.test.ts +111 -0
  604. package/test/preload.ts +90 -0
  605. package/test/project/migrate-global.test.ts +140 -0
  606. package/test/project/project.test.ts +459 -0
  607. package/test/project/state.test.ts +115 -0
  608. package/test/project/vcs.test.ts +116 -0
  609. package/test/project/worktree-remove.test.ts +96 -0
  610. package/test/project/worktree.test.ts +173 -0
  611. package/test/provider/amazon-bedrock.test.ts +447 -0
  612. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  613. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  614. package/test/provider/gitlab-duo.test.ts +412 -0
  615. package/test/provider/provider.test.ts +2284 -0
  616. package/test/provider/transform.test.ts +2758 -0
  617. package/test/pty/pty-output-isolation.test.ts +141 -0
  618. package/test/pty/pty-session.test.ts +92 -0
  619. package/test/question/question.test.ts +453 -0
  620. package/test/server/global-session-list.test.ts +89 -0
  621. package/test/server/project-init-git.test.ts +121 -0
  622. package/test/server/session-list.test.ts +90 -0
  623. package/test/server/session-messages.test.ts +132 -0
  624. package/test/server/session-select.test.ts +78 -0
  625. package/test/session/compaction.test.ts +1094 -0
  626. package/test/session/instruction.test.ts +170 -0
  627. package/test/session/llm.test.ts +882 -0
  628. package/test/session/message-v2.test.ts +957 -0
  629. package/test/session/messages-pagination.test.ts +115 -0
  630. package/test/session/processor-effect.test.ts +838 -0
  631. package/test/session/prompt.test.ts +518 -0
  632. package/test/session/retry.test.ts +232 -0
  633. package/test/session/revert-compact.test.ts +286 -0
  634. package/test/session/session.test.ts +142 -0
  635. package/test/session/structured-output-integration.test.ts +233 -0
  636. package/test/session/structured-output.test.ts +391 -0
  637. package/test/session/system.test.ts +59 -0
  638. package/test/share/share-next.test.ts +76 -0
  639. package/test/skill/discovery.test.ts +116 -0
  640. package/test/skill/skill.test.ts +392 -0
  641. package/test/snapshot/snapshot.test.ts +1235 -0
  642. package/test/storage/db.test.ts +14 -0
  643. package/test/storage/json-migration.test.ts +849 -0
  644. package/test/sync/index.test.ts +191 -0
  645. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  646. package/test/tool/apply_patch.test.ts +567 -0
  647. package/test/tool/bash.test.ts +403 -0
  648. package/test/tool/edit.test.ts +681 -0
  649. package/test/tool/external-directory.test.ts +128 -0
  650. package/test/tool/fixtures/large-image.png +0 -0
  651. package/test/tool/fixtures/models-api.json +38413 -0
  652. package/test/tool/grep.test.ts +111 -0
  653. package/test/tool/question.test.ts +108 -0
  654. package/test/tool/read.test.ts +509 -0
  655. package/test/tool/registry.test.ts +126 -0
  656. package/test/tool/skill.test.ts +167 -0
  657. package/test/tool/task.test.ts +49 -0
  658. package/test/tool/truncation.test.ts +161 -0
  659. package/test/tool/webfetch.test.ts +101 -0
  660. package/test/tool/write.test.ts +353 -0
  661. package/test/util/data-url.test.ts +14 -0
  662. package/test/util/effect-zod.test.ts +61 -0
  663. package/test/util/error.test.ts +38 -0
  664. package/test/util/filesystem.test.ts +558 -0
  665. package/test/util/flock.test.ts +383 -0
  666. package/test/util/format.test.ts +59 -0
  667. package/test/util/glob.test.ts +164 -0
  668. package/test/util/iife.test.ts +36 -0
  669. package/test/util/lazy.test.ts +50 -0
  670. package/test/util/lock.test.ts +72 -0
  671. package/test/util/module.test.ts +59 -0
  672. package/test/util/process.test.ts +128 -0
  673. package/test/util/timeout.test.ts +21 -0
  674. package/test/util/which.test.ts +100 -0
  675. package/test/util/wildcard.test.ts +90 -0
  676. package/tsconfig.json +23 -0
@@ -0,0 +1,967 @@
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 "@toolkit-cli/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
+ isDeprecatedPlugin,
24
+ pluginSource,
25
+ readPluginId,
26
+ readV1Plugin,
27
+ resolvePluginEntrypoint,
28
+ resolvePluginId,
29
+ resolvePluginTarget,
30
+ type PluginSource,
31
+ } from "@/plugin/shared"
32
+ import { PluginMeta } from "@/plugin/meta"
33
+ import { installPlugin as installModulePlugin, patchPluginConfig, readPluginManifest } from "@/plugin/install"
34
+ import { addTheme, hasTheme } from "../context/theme"
35
+ import { Global } from "@/global"
36
+ import { Filesystem } from "@/util/filesystem"
37
+ import { Process } from "@/util/process"
38
+ import { Flag } from "@/flag/flag"
39
+ import { Installation } from "@/installation"
40
+ import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal"
41
+ import { setupSlots, Slot as View } from "./slots"
42
+ import type { HostPluginApi, HostSlots } from "./slots"
43
+
44
+ type PluginLoad = {
45
+ item?: Config.PluginSpec
46
+ spec: string
47
+ target: string
48
+ retry: boolean
49
+ source: PluginSource | "internal"
50
+ id: string
51
+ module: TuiPluginModule
52
+ install_theme: TuiTheme["install"]
53
+ }
54
+
55
+ type Api = HostPluginApi
56
+
57
+ type PluginScope = {
58
+ lifecycle: TuiPluginApi["lifecycle"]
59
+ track: (fn: (() => void) | undefined) => () => void
60
+ dispose: () => Promise<void>
61
+ }
62
+
63
+ type PluginEntry = {
64
+ id: string
65
+ load: PluginLoad
66
+ meta: TuiPluginMeta
67
+ plugin: TuiPlugin
68
+ options: Config.PluginOptions | undefined
69
+ enabled: boolean
70
+ scope?: PluginScope
71
+ }
72
+
73
+ type RuntimeState = {
74
+ directory: string
75
+ api: Api
76
+ slots: HostSlots
77
+ plugins: PluginEntry[]
78
+ plugins_by_id: Map<string, PluginEntry>
79
+ pending: Map<
80
+ string,
81
+ {
82
+ item: Config.PluginSpec
83
+ meta: TuiConfig.PluginMeta
84
+ }
85
+ >
86
+ }
87
+
88
+ const log = Log.create({ service: "tui.plugin" })
89
+ const DISPOSE_TIMEOUT_MS = 5000
90
+ const KV_KEY = "plugin_enabled"
91
+
92
+ function fail(message: string, data: Record<string, unknown>) {
93
+ if (!("error" in data)) {
94
+ log.error(message, data)
95
+ console.error(`[tui.plugin] ${message}`, data)
96
+ return
97
+ }
98
+
99
+ const text = `${message}: ${errorMessage(data.error)}`
100
+ const next = { ...data, error: errorData(data.error) }
101
+ log.error(text, next)
102
+ console.error(`[tui.plugin] ${text}`, next)
103
+ }
104
+
105
+ type CleanupResult = { type: "ok" } | { type: "error"; error: unknown } | { type: "timeout" }
106
+
107
+ function runCleanup(fn: () => unknown, ms: number): Promise<CleanupResult> {
108
+ return new Promise((resolve) => {
109
+ const timer = setTimeout(() => {
110
+ resolve({ type: "timeout" })
111
+ }, ms)
112
+
113
+ Promise.resolve()
114
+ .then(fn)
115
+ .then(
116
+ () => {
117
+ resolve({ type: "ok" })
118
+ },
119
+ (error) => {
120
+ resolve({ type: "error", error })
121
+ },
122
+ )
123
+ .finally(() => {
124
+ clearTimeout(timer)
125
+ })
126
+ })
127
+ }
128
+
129
+ function isTheme(value: unknown) {
130
+ if (!isRecord(value)) return false
131
+ if (!("theme" in value)) return false
132
+ if (!isRecord(value.theme)) return false
133
+ return true
134
+ }
135
+
136
+ function resolveRoot(root: string) {
137
+ if (root.startsWith("file://")) {
138
+ const file = fileURLToPath(root)
139
+ if (root.endsWith("/")) return file
140
+ return path.dirname(file)
141
+ }
142
+ if (path.isAbsolute(root)) return root
143
+ return path.resolve(process.cwd(), root)
144
+ }
145
+
146
+ function createThemeInstaller(meta: TuiConfig.PluginMeta, root: string, spec: string): TuiTheme["install"] {
147
+ return async (file) => {
148
+ const raw = file.startsWith("file://") ? fileURLToPath(file) : file
149
+ const src = path.isAbsolute(raw) ? raw : path.resolve(root, raw)
150
+ const theme = path.basename(src, path.extname(src))
151
+ if (hasTheme(theme)) return
152
+
153
+ const text = await Filesystem.readText(src).catch((error) => {
154
+ log.warn("failed to read tui plugin theme", { path: spec, theme: src, error })
155
+ return
156
+ })
157
+ if (text === undefined) return
158
+
159
+ const fail = Symbol()
160
+ const data = await Promise.resolve(text)
161
+ .then((x) => JSON.parse(x))
162
+ .catch((error) => {
163
+ log.warn("failed to parse tui plugin theme", { path: spec, theme: src, error })
164
+ return fail
165
+ })
166
+ if (data === fail) return
167
+
168
+ if (!isTheme(data)) {
169
+ log.warn("invalid tui plugin theme", { path: spec, theme: src })
170
+ return
171
+ }
172
+
173
+ const source_dir = path.dirname(meta.source)
174
+ const local_dir =
175
+ path.basename(source_dir) === ".toolkode"
176
+ ? path.join(source_dir, "themes")
177
+ : path.join(source_dir, ".toolkode", "themes")
178
+ const dest_dir = meta.scope === "local" ? local_dir : path.join(Global.Path.config, "themes")
179
+ const dest = path.join(dest_dir, `${theme}.json`)
180
+ if (!(await Filesystem.exists(dest))) {
181
+ await Filesystem.write(dest, text).catch((error) => {
182
+ log.warn("failed to persist tui plugin theme", { path: spec, theme: src, dest, error })
183
+ })
184
+ }
185
+
186
+ addTheme(theme, data)
187
+ }
188
+ }
189
+
190
+ async function loadExternalPlugin(
191
+ item: Config.PluginSpec,
192
+ meta: TuiConfig.PluginMeta | undefined,
193
+ retry = false,
194
+ ): Promise<PluginLoad | undefined> {
195
+ const spec = Config.pluginSpecifier(item)
196
+ if (isDeprecatedPlugin(spec)) return
197
+ log.info("loading tui plugin", { path: spec, retry })
198
+ const resolved = await resolvePluginTarget(spec).catch((error) => {
199
+ fail("failed to resolve tui plugin", { path: spec, retry, error })
200
+ return
201
+ })
202
+ if (!resolved) return
203
+
204
+ const source = pluginSource(spec)
205
+ if (source === "npm") {
206
+ const ok = await checkPluginCompatibility(resolved, Installation.VERSION)
207
+ .then(() => true)
208
+ .catch((error) => {
209
+ fail("tui plugin incompatible", { path: spec, retry, error })
210
+ return false
211
+ })
212
+ if (!ok) return
213
+ }
214
+
215
+ const target = resolved
216
+ if (!meta) {
217
+ fail("missing tui plugin metadata", {
218
+ path: spec,
219
+ retry,
220
+ })
221
+ return
222
+ }
223
+
224
+ const root = resolveRoot(source === "file" ? spec : target)
225
+ const install_theme = createThemeInstaller(meta, root, spec)
226
+ const entry = await resolvePluginEntrypoint(spec, target, "tui").catch((error) => {
227
+ fail("failed to resolve tui plugin entry", { path: spec, target, retry, error })
228
+ return
229
+ })
230
+ if (!entry) return
231
+
232
+ const mod = await import(entry)
233
+ .then((raw) => {
234
+ return readV1Plugin(raw as Record<string, unknown>, spec, "tui") as TuiPluginModule
235
+ })
236
+ .catch((error) => {
237
+ fail("failed to load tui plugin", { path: spec, target: entry, retry, error })
238
+ return
239
+ })
240
+ if (!mod) return
241
+
242
+ const id = await resolvePluginId(source, spec, target, readPluginId(mod.id, spec)).catch((error) => {
243
+ fail("failed to load tui plugin", { path: spec, target, retry, error })
244
+ return
245
+ })
246
+ if (!id) return
247
+
248
+ return {
249
+ item,
250
+ spec,
251
+ target,
252
+ retry,
253
+ source,
254
+ id,
255
+ module: mod,
256
+ install_theme,
257
+ }
258
+ }
259
+
260
+ function createMeta(
261
+ source: PluginLoad["source"],
262
+ spec: string,
263
+ target: string,
264
+ meta: { state: PluginMeta.State; entry: PluginMeta.Entry } | undefined,
265
+ id?: string,
266
+ ): TuiPluginMeta {
267
+ if (meta) {
268
+ return {
269
+ state: meta.state,
270
+ ...meta.entry,
271
+ }
272
+ }
273
+
274
+ const now = Date.now()
275
+ return {
276
+ state: source === "internal" ? "same" : "first",
277
+ id: id ?? spec,
278
+ source,
279
+ spec,
280
+ target,
281
+ first_time: now,
282
+ last_time: now,
283
+ time_changed: now,
284
+ load_count: 1,
285
+ fingerprint: target,
286
+ }
287
+ }
288
+
289
+ function loadInternalPlugin(item: InternalTuiPlugin): PluginLoad {
290
+ const spec = item.id
291
+ const target = spec
292
+
293
+ return {
294
+ spec,
295
+ target,
296
+ retry: false,
297
+ source: "internal",
298
+ id: item.id,
299
+ module: item,
300
+ install_theme: createThemeInstaller(
301
+ {
302
+ scope: "global",
303
+ source: target,
304
+ },
305
+ process.cwd(),
306
+ spec,
307
+ ),
308
+ }
309
+ }
310
+
311
+ function createPluginScope(load: PluginLoad, id: string) {
312
+ const ctrl = new AbortController()
313
+ let list: { key: symbol; fn: TuiDispose }[] = []
314
+ let done = false
315
+
316
+ const onDispose = (fn: TuiDispose) => {
317
+ if (done) return () => {}
318
+ const key = Symbol()
319
+ list.push({ key, fn })
320
+ let drop = false
321
+ return () => {
322
+ if (drop) return
323
+ drop = true
324
+ list = list.filter((x) => x.key !== key)
325
+ }
326
+ }
327
+
328
+ const track = (fn: (() => void) | undefined) => {
329
+ if (!fn) return () => {}
330
+ const off = onDispose(fn)
331
+ let drop = false
332
+ return () => {
333
+ if (drop) return
334
+ drop = true
335
+ off()
336
+ fn()
337
+ }
338
+ }
339
+
340
+ const lifecycle: TuiPluginApi["lifecycle"] = {
341
+ signal: ctrl.signal,
342
+ onDispose,
343
+ }
344
+
345
+ const dispose = async () => {
346
+ if (done) return
347
+ done = true
348
+ ctrl.abort()
349
+ const queue = [...list].reverse()
350
+ list = []
351
+ const until = Date.now() + DISPOSE_TIMEOUT_MS
352
+ for (const item of queue) {
353
+ const left = until - Date.now()
354
+ if (left <= 0) {
355
+ fail("timed out cleaning up tui plugin", {
356
+ path: load.spec,
357
+ id,
358
+ timeout: DISPOSE_TIMEOUT_MS,
359
+ })
360
+ break
361
+ }
362
+
363
+ const out = await runCleanup(item.fn, left)
364
+ if (out.type === "ok") continue
365
+ if (out.type === "timeout") {
366
+ fail("timed out cleaning up tui plugin", {
367
+ path: load.spec,
368
+ id,
369
+ timeout: DISPOSE_TIMEOUT_MS,
370
+ })
371
+ break
372
+ }
373
+
374
+ if (out.type === "error") {
375
+ fail("failed to clean up tui plugin", {
376
+ path: load.spec,
377
+ id,
378
+ error: out.error,
379
+ })
380
+ }
381
+ }
382
+ }
383
+
384
+ return {
385
+ lifecycle,
386
+ track,
387
+ dispose,
388
+ }
389
+ }
390
+
391
+ function readPluginEnabledMap(value: unknown) {
392
+ if (!isRecord(value)) return {}
393
+ return Object.fromEntries(
394
+ Object.entries(value).filter((item): item is [string, boolean] => typeof item[1] === "boolean"),
395
+ )
396
+ }
397
+
398
+ function pluginEnabledState(state: RuntimeState, config: TuiConfig.Info) {
399
+ return {
400
+ ...readPluginEnabledMap(config.plugin_enabled),
401
+ ...readPluginEnabledMap(state.api.kv.get(KV_KEY, {})),
402
+ }
403
+ }
404
+
405
+ function writePluginEnabledState(api: Api, id: string, enabled: boolean) {
406
+ api.kv.set(KV_KEY, {
407
+ ...readPluginEnabledMap(api.kv.get(KV_KEY, {})),
408
+ [id]: enabled,
409
+ })
410
+ }
411
+
412
+ function listPluginStatus(state: RuntimeState): TuiPluginStatus[] {
413
+ return state.plugins.map((plugin) => ({
414
+ id: plugin.id,
415
+ source: plugin.meta.source,
416
+ spec: plugin.meta.spec,
417
+ target: plugin.meta.target,
418
+ enabled: plugin.enabled,
419
+ active: plugin.scope !== undefined,
420
+ }))
421
+ }
422
+
423
+ async function deactivatePluginEntry(state: RuntimeState, plugin: PluginEntry, persist: boolean) {
424
+ plugin.enabled = false
425
+ if (persist) writePluginEnabledState(state.api, plugin.id, false)
426
+ if (!plugin.scope) return true
427
+ const scope = plugin.scope
428
+ plugin.scope = undefined
429
+ await scope.dispose()
430
+ return true
431
+ }
432
+
433
+ async function activatePluginEntry(state: RuntimeState, plugin: PluginEntry, persist: boolean) {
434
+ plugin.enabled = true
435
+ if (persist) writePluginEnabledState(state.api, plugin.id, true)
436
+ if (plugin.scope) return true
437
+
438
+ const scope = createPluginScope(plugin.load, plugin.id)
439
+ const api = pluginApi(state, plugin.load, scope, plugin.id)
440
+ const ok = await Promise.resolve()
441
+ .then(async () => {
442
+ await plugin.plugin(api, plugin.options, plugin.meta)
443
+ return true
444
+ })
445
+ .catch((error) => {
446
+ fail("failed to initialize tui plugin", {
447
+ path: plugin.load.spec,
448
+ id: plugin.id,
449
+ error,
450
+ })
451
+ return false
452
+ })
453
+
454
+ if (!ok) {
455
+ await scope.dispose()
456
+ return false
457
+ }
458
+
459
+ if (!plugin.enabled) {
460
+ await scope.dispose()
461
+ return true
462
+ }
463
+
464
+ plugin.scope = scope
465
+ return true
466
+ }
467
+
468
+ async function activatePluginById(state: RuntimeState | undefined, id: string, persist: boolean) {
469
+ if (!state) return false
470
+ const plugin = state.plugins_by_id.get(id)
471
+ if (!plugin) return false
472
+ return activatePluginEntry(state, plugin, persist)
473
+ }
474
+
475
+ async function deactivatePluginById(state: RuntimeState | undefined, id: string, persist: boolean) {
476
+ if (!state) return false
477
+ const plugin = state.plugins_by_id.get(id)
478
+ if (!plugin) return false
479
+ return deactivatePluginEntry(state, plugin, persist)
480
+ }
481
+
482
+ function pluginApi(runtime: RuntimeState, load: PluginLoad, scope: PluginScope, base: string): TuiPluginApi {
483
+ const api = runtime.api
484
+ const host = runtime.slots
485
+ const command: TuiPluginApi["command"] = {
486
+ register(cb) {
487
+ return scope.track(api.command.register(cb))
488
+ },
489
+ trigger(value) {
490
+ api.command.trigger(value)
491
+ },
492
+ }
493
+
494
+ const route: TuiPluginApi["route"] = {
495
+ register(list) {
496
+ return scope.track(api.route.register(list))
497
+ },
498
+ navigate(name, params) {
499
+ api.route.navigate(name, params)
500
+ },
501
+ get current() {
502
+ return api.route.current
503
+ },
504
+ }
505
+
506
+ const theme: TuiPluginApi["theme"] = Object.assign(Object.create(api.theme), {
507
+ install: load.install_theme,
508
+ })
509
+
510
+ const event: TuiPluginApi["event"] = {
511
+ on(type, handler) {
512
+ return scope.track(api.event.on(type, handler))
513
+ },
514
+ }
515
+
516
+ let count = 0
517
+
518
+ const slots: TuiPluginApi["slots"] = {
519
+ register(plugin) {
520
+ const id = count ? `${base}:${count}` : base
521
+ count += 1
522
+ scope.track(host.register({ ...plugin, id }))
523
+ return id
524
+ },
525
+ }
526
+
527
+ return {
528
+ app: api.app,
529
+ command,
530
+ route,
531
+ ui: api.ui,
532
+ keybind: api.keybind,
533
+ tuiConfig: api.tuiConfig,
534
+ kv: api.kv,
535
+ state: api.state,
536
+ theme,
537
+ get client() {
538
+ return api.client
539
+ },
540
+ scopedClient: api.scopedClient,
541
+ workspace: api.workspace,
542
+ event,
543
+ renderer: api.renderer,
544
+ slots,
545
+ plugins: {
546
+ list() {
547
+ return listPluginStatus(runtime)
548
+ },
549
+ activate(id) {
550
+ return activatePluginById(runtime, id, true)
551
+ },
552
+ deactivate(id) {
553
+ return deactivatePluginById(runtime, id, true)
554
+ },
555
+ add(spec) {
556
+ return addPluginBySpec(runtime, spec)
557
+ },
558
+ install(spec, options) {
559
+ return installPluginBySpec(runtime, spec, options?.global)
560
+ },
561
+ },
562
+ lifecycle: scope.lifecycle,
563
+ }
564
+ }
565
+
566
+ function collectPluginEntries(load: PluginLoad, meta: TuiPluginMeta) {
567
+ const options = load.item ? Config.pluginOptions(load.item) : undefined
568
+ return [
569
+ {
570
+ id: load.id,
571
+ load,
572
+ meta,
573
+ plugin: load.module.tui,
574
+ options,
575
+ enabled: true,
576
+ },
577
+ ]
578
+ }
579
+
580
+ function addPluginEntry(state: RuntimeState, plugin: PluginEntry) {
581
+ if (state.plugins_by_id.has(plugin.id)) {
582
+ fail("duplicate tui plugin id", {
583
+ id: plugin.id,
584
+ path: plugin.load.spec,
585
+ })
586
+ return false
587
+ }
588
+
589
+ state.plugins_by_id.set(plugin.id, plugin)
590
+ state.plugins.push(plugin)
591
+ return true
592
+ }
593
+
594
+ function applyInitialPluginEnabledState(state: RuntimeState, config: TuiConfig.Info) {
595
+ const map = pluginEnabledState(state, config)
596
+ for (const plugin of state.plugins) {
597
+ const enabled = map[plugin.id]
598
+ if (enabled === undefined) continue
599
+ plugin.enabled = enabled
600
+ }
601
+ }
602
+
603
+ async function resolveExternalPlugins(
604
+ list: Config.PluginSpec[],
605
+ wait: () => Promise<void>,
606
+ meta: (item: Config.PluginSpec) => TuiConfig.PluginMeta | undefined,
607
+ ) {
608
+ const loaded = await Promise.all(list.map((item) => loadExternalPlugin(item, meta(item))))
609
+ const ready: PluginLoad[] = []
610
+ let deps: Promise<void> | undefined
611
+
612
+ for (let i = 0; i < list.length; i++) {
613
+ let entry = loaded[i]
614
+ if (!entry) {
615
+ const item = list[i]
616
+ if (!item) continue
617
+ const spec = Config.pluginSpecifier(item)
618
+ if (pluginSource(spec) !== "file") continue
619
+ deps ??= wait().catch((error) => {
620
+ log.warn("failed waiting for tui plugin dependencies", { error })
621
+ })
622
+ await deps
623
+ entry = await loadExternalPlugin(item, meta(item), true)
624
+ }
625
+ if (!entry) continue
626
+ ready.push(entry)
627
+ }
628
+
629
+ return ready
630
+ }
631
+
632
+ async function addExternalPluginEntries(state: RuntimeState, ready: PluginLoad[]) {
633
+ if (!ready.length) return { plugins: [] as PluginEntry[], ok: true }
634
+
635
+ const meta = await PluginMeta.touchMany(
636
+ ready.map((item) => ({
637
+ spec: item.spec,
638
+ target: item.target,
639
+ id: item.id,
640
+ })),
641
+ ).catch((error) => {
642
+ log.warn("failed to track tui plugins", { error })
643
+ return undefined
644
+ })
645
+
646
+ const plugins: PluginEntry[] = []
647
+ let ok = true
648
+ for (let i = 0; i < ready.length; i++) {
649
+ const entry = ready[i]
650
+ if (!entry) continue
651
+ const hit = meta?.[i]
652
+ if (hit && hit.state !== "same") {
653
+ log.info("tui plugin metadata updated", {
654
+ path: entry.spec,
655
+ retry: entry.retry,
656
+ state: hit.state,
657
+ source: hit.entry.source,
658
+ version: hit.entry.version,
659
+ modified: hit.entry.modified,
660
+ })
661
+ }
662
+
663
+ const row = createMeta(entry.source, entry.spec, entry.target, hit, entry.id)
664
+ for (const plugin of collectPluginEntries(entry, row)) {
665
+ if (!addPluginEntry(state, plugin)) {
666
+ ok = false
667
+ continue
668
+ }
669
+ plugins.push(plugin)
670
+ }
671
+ }
672
+
673
+ return { plugins, ok }
674
+ }
675
+
676
+ function defaultPluginMeta(state: RuntimeState): TuiConfig.PluginMeta {
677
+ return {
678
+ scope: "local",
679
+ source: state.api.state.path.config || path.join(state.directory, ".toolkode", "tui.json"),
680
+ }
681
+ }
682
+
683
+ function installCause(err: unknown) {
684
+ if (!err || typeof err !== "object") return
685
+ if (!("cause" in err)) return
686
+ return (err as { cause?: unknown }).cause
687
+ }
688
+
689
+ function installDetail(err: unknown) {
690
+ const hit = installCause(err) ?? err
691
+ if (!(hit instanceof Process.RunFailedError)) {
692
+ return {
693
+ message: errorMessage(hit),
694
+ missing: false,
695
+ }
696
+ }
697
+
698
+ const lines = hit.stderr
699
+ .toString()
700
+ .split(/\r?\n/)
701
+ .map((line) => line.trim())
702
+ .filter(Boolean)
703
+ const errs = lines.filter((line) => line.startsWith("error:")).map((line) => line.replace(/^error:\s*/, ""))
704
+ return {
705
+ message: errs[0] ?? lines.at(-1) ?? errorMessage(hit),
706
+ missing: lines.some((line) => line.includes("No version matching")),
707
+ }
708
+ }
709
+
710
+ async function addPluginBySpec(state: RuntimeState | undefined, raw: string) {
711
+ if (!state) return false
712
+ const spec = raw.trim()
713
+ if (!spec) return false
714
+
715
+ const pending = state.pending.get(spec)
716
+ const item = pending?.item ?? spec
717
+ const nextSpec = Config.pluginSpecifier(item)
718
+ if (state.plugins.some((plugin) => plugin.load.spec === nextSpec)) {
719
+ state.pending.delete(spec)
720
+ return true
721
+ }
722
+
723
+ const meta = pending?.meta ?? defaultPluginMeta(state)
724
+
725
+ const ready = await Instance.provide({
726
+ directory: state.directory,
727
+ fn: () =>
728
+ resolveExternalPlugins(
729
+ [item],
730
+ () => TuiConfig.waitForDependencies(),
731
+ () => meta,
732
+ ),
733
+ }).catch((error) => {
734
+ fail("failed to add tui plugin", { path: nextSpec, error })
735
+ return [] as PluginLoad[]
736
+ })
737
+ if (!ready.length) {
738
+ fail("failed to add tui plugin", { path: nextSpec })
739
+ return false
740
+ }
741
+
742
+ const first = ready[0]
743
+ if (!first) {
744
+ fail("failed to add tui plugin", { path: nextSpec })
745
+ return false
746
+ }
747
+ if (state.plugins_by_id.has(first.id)) {
748
+ state.pending.delete(spec)
749
+ return true
750
+ }
751
+
752
+ const out = await addExternalPluginEntries(state, [first])
753
+ let ok = out.ok && out.plugins.length > 0
754
+ for (const plugin of out.plugins) {
755
+ const active = await activatePluginEntry(state, plugin, false)
756
+ if (!active) ok = false
757
+ }
758
+
759
+ if (ok) state.pending.delete(spec)
760
+ if (!ok) {
761
+ fail("failed to add tui plugin", { path: nextSpec })
762
+ }
763
+ return ok
764
+ }
765
+
766
+ async function installPluginBySpec(
767
+ state: RuntimeState | undefined,
768
+ raw: string,
769
+ global = false,
770
+ ): Promise<TuiPluginInstallResult> {
771
+ if (!state) {
772
+ return {
773
+ ok: false,
774
+ message: "Plugin runtime is not ready.",
775
+ }
776
+ }
777
+
778
+ const spec = raw.trim()
779
+ if (!spec) {
780
+ return {
781
+ ok: false,
782
+ message: "Plugin package name is required",
783
+ }
784
+ }
785
+
786
+ const dir = state.api.state.path
787
+ if (!dir.directory) {
788
+ return {
789
+ ok: false,
790
+ message: "Paths are still syncing. Try again in a moment.",
791
+ }
792
+ }
793
+
794
+ const install = await installModulePlugin(spec)
795
+ if (!install.ok) {
796
+ const out = installDetail(install.error)
797
+ return {
798
+ ok: false,
799
+ message: out.message,
800
+ missing: out.missing,
801
+ }
802
+ }
803
+
804
+ const manifest = await readPluginManifest(install.target)
805
+ if (!manifest.ok) {
806
+ if (manifest.code === "manifest_no_targets") {
807
+ return {
808
+ ok: false,
809
+ message: `"${spec}" does not declare supported targets in package.json`,
810
+ }
811
+ }
812
+
813
+ return {
814
+ ok: false,
815
+ message: `Installed "${spec}" but failed to read ${manifest.file}`,
816
+ }
817
+ }
818
+
819
+ const patch = await patchPluginConfig({
820
+ spec,
821
+ targets: manifest.targets,
822
+ global,
823
+ vcs: dir.worktree && dir.worktree !== "/" ? "git" : undefined,
824
+ worktree: dir.worktree,
825
+ directory: dir.directory,
826
+ })
827
+ if (!patch.ok) {
828
+ if (patch.code === "invalid_json") {
829
+ return {
830
+ ok: false,
831
+ message: `Invalid JSON in ${patch.file} (${patch.parse} at line ${patch.line}, column ${patch.col})`,
832
+ }
833
+ }
834
+
835
+ return {
836
+ ok: false,
837
+ message: errorMessage(patch.error),
838
+ }
839
+ }
840
+
841
+ const tui = manifest.targets.find((item) => item.kind === "tui")
842
+ if (tui) {
843
+ const file = patch.items.find((item) => item.kind === "tui")?.file
844
+ state.pending.set(spec, {
845
+ item: tui.opts ? [spec, tui.opts] : spec,
846
+ meta: {
847
+ scope: global ? "global" : "local",
848
+ source: (file ?? dir.config) || path.join(patch.dir, "tui.json"),
849
+ },
850
+ })
851
+ }
852
+
853
+ return {
854
+ ok: true,
855
+ dir: patch.dir,
856
+ tui: Boolean(tui),
857
+ }
858
+ }
859
+
860
+ export namespace TuiPluginRuntime {
861
+ let dir = ""
862
+ let loaded: Promise<void> | undefined
863
+ let runtime: RuntimeState | undefined
864
+ export const Slot = View
865
+
866
+ export async function init(api: HostPluginApi) {
867
+ const cwd = process.cwd()
868
+ if (loaded) {
869
+ if (dir !== cwd) {
870
+ throw new Error(`TuiPluginRuntime.init() called with a different working directory. expected=${dir} got=${cwd}`)
871
+ }
872
+ return loaded
873
+ }
874
+
875
+ dir = cwd
876
+ loaded = load(api)
877
+ return loaded
878
+ }
879
+
880
+ export function list() {
881
+ if (!runtime) return []
882
+ return listPluginStatus(runtime)
883
+ }
884
+
885
+ export async function activatePlugin(id: string) {
886
+ return activatePluginById(runtime, id, true)
887
+ }
888
+
889
+ export async function deactivatePlugin(id: string) {
890
+ return deactivatePluginById(runtime, id, true)
891
+ }
892
+
893
+ export async function addPlugin(spec: string) {
894
+ return addPluginBySpec(runtime, spec)
895
+ }
896
+
897
+ export async function installPlugin(spec: string, options?: { global?: boolean }) {
898
+ return installPluginBySpec(runtime, spec, options?.global)
899
+ }
900
+
901
+ export async function dispose() {
902
+ const task = loaded
903
+ loaded = undefined
904
+ dir = ""
905
+ if (task) await task
906
+ const state = runtime
907
+ runtime = undefined
908
+ if (!state) return
909
+ const queue = [...state.plugins].reverse()
910
+ for (const plugin of queue) {
911
+ await deactivatePluginEntry(state, plugin, false)
912
+ }
913
+ }
914
+
915
+ async function load(api: Api) {
916
+ const cwd = process.cwd()
917
+ const slots = setupSlots(api)
918
+ const next: RuntimeState = {
919
+ directory: cwd,
920
+ api,
921
+ slots,
922
+ plugins: [],
923
+ plugins_by_id: new Map(),
924
+ pending: new Map(),
925
+ }
926
+ runtime = next
927
+
928
+ await Instance.provide({
929
+ directory: cwd,
930
+ fn: async () => {
931
+ const config = await TuiConfig.get()
932
+ const plugins = Flag.OPENCODE_PURE ? [] : (config.plugin ?? [])
933
+ if (Flag.OPENCODE_PURE && config.plugin?.length) {
934
+ log.info("skipping external tui plugins in pure mode", { count: config.plugin.length })
935
+ }
936
+
937
+ for (const item of INTERNAL_TUI_PLUGINS) {
938
+ log.info("loading internal tui plugin", { id: item.id })
939
+ const entry = loadInternalPlugin(item)
940
+ const meta = createMeta(entry.source, entry.spec, entry.target, undefined, entry.id)
941
+ for (const plugin of collectPluginEntries(entry, meta)) {
942
+ addPluginEntry(next, plugin)
943
+ }
944
+ }
945
+
946
+ const ready = await resolveExternalPlugins(
947
+ plugins,
948
+ () => TuiConfig.waitForDependencies(),
949
+ (item) => config.plugin_meta?.[Config.pluginSpecifier(item)],
950
+ )
951
+ await addExternalPluginEntries(next, ready)
952
+
953
+ applyInitialPluginEnabledState(next, config)
954
+ for (const plugin of next.plugins) {
955
+ if (!plugin.enabled) continue
956
+ // Keep plugin execution sequential for deterministic side effects:
957
+ // command registration order affects keybind/command precedence,
958
+ // route registration is last-wins when ids collide,
959
+ // and hook chains rely on stable plugin ordering.
960
+ await activatePluginEntry(next, plugin, false)
961
+ }
962
+ },
963
+ }).catch((error) => {
964
+ fail("failed to load tui plugins", { directory: cwd, error })
965
+ })
966
+ }
967
+ }