@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,384 @@
1
+ import path from "path"
2
+ import {
3
+ type ParseError as JsoncParseError,
4
+ applyEdits,
5
+ modify,
6
+ parse as parseJsonc,
7
+ printParseErrorCode,
8
+ } from "jsonc-parser"
9
+
10
+ import { ConfigPaths } from "@/config/paths"
11
+ import { Global } from "@/global"
12
+ import { Filesystem } from "@/util/filesystem"
13
+ import { Flock } from "@/util/flock"
14
+
15
+ import { parsePluginSpecifier, readPluginPackage, resolvePluginTarget } from "./shared"
16
+
17
+ type Mode = "noop" | "add" | "replace"
18
+ type Kind = "server" | "tui"
19
+
20
+ export type Target = {
21
+ kind: Kind
22
+ opts?: Record<string, unknown>
23
+ }
24
+
25
+ export type InstallDeps = {
26
+ resolve: (spec: string) => Promise<string>
27
+ }
28
+
29
+ export type PatchDeps = {
30
+ readText: (file: string) => Promise<string>
31
+ write: (file: string, text: string) => Promise<void>
32
+ exists: (file: string) => Promise<boolean>
33
+ files: (dir: string, name: "toolkode" | "tui") => string[]
34
+ }
35
+
36
+ export type PatchInput = {
37
+ spec: string
38
+ targets: Target[]
39
+ force?: boolean
40
+ global?: boolean
41
+ vcs?: string
42
+ worktree: string
43
+ directory: string
44
+ config?: string
45
+ }
46
+
47
+ type Ok<T> = {
48
+ ok: true
49
+ } & T
50
+
51
+ type Err<C extends string, T> = {
52
+ ok: false
53
+ code: C
54
+ } & T
55
+
56
+ export type InstallResult = Ok<{ target: string }> | Err<"install_failed", { error: unknown }>
57
+
58
+ export type ManifestResult =
59
+ | Ok<{ targets: Target[] }>
60
+ | Err<"manifest_read_failed", { file: string; error: unknown }>
61
+ | Err<"manifest_no_targets", { file: string }>
62
+
63
+ export type PatchItem = {
64
+ kind: Kind
65
+ mode: Mode
66
+ file: string
67
+ }
68
+
69
+ type PatchErr =
70
+ | Err<"invalid_json", { kind: Kind; file: string; line: number; col: number; parse: string }>
71
+ | Err<"patch_failed", { kind: Kind; error: unknown }>
72
+
73
+ type PatchOne = Ok<{ item: PatchItem }> | PatchErr
74
+
75
+ export type PatchResult = Ok<{ dir: string; items: PatchItem[] }> | (PatchErr & { dir: string })
76
+
77
+ const defaultInstallDeps: InstallDeps = {
78
+ resolve: (spec) => resolvePluginTarget(spec),
79
+ }
80
+
81
+ const defaultPatchDeps: PatchDeps = {
82
+ readText: (file) => Filesystem.readText(file),
83
+ write: async (file, text) => {
84
+ await Filesystem.write(file, text)
85
+ },
86
+ exists: (file) => Filesystem.exists(file),
87
+ files: (dir, name) => ConfigPaths.fileInDirectory(dir, name),
88
+ }
89
+
90
+ function pluginSpec(item: unknown) {
91
+ if (typeof item === "string") return item
92
+ if (!Array.isArray(item)) return
93
+ if (typeof item[0] !== "string") return
94
+ return item[0]
95
+ }
96
+
97
+ function pluginList(data: unknown) {
98
+ if (!data || typeof data !== "object" || Array.isArray(data)) return
99
+ const item = data as { plugin?: unknown }
100
+ if (!Array.isArray(item.plugin)) return
101
+ return item.plugin
102
+ }
103
+
104
+ function parseTarget(item: unknown): Target | undefined {
105
+ if (item === "server" || item === "tui") return { kind: item }
106
+ if (!Array.isArray(item)) return
107
+ if (item[0] !== "server" && item[0] !== "tui") return
108
+ if (item.length < 2) return { kind: item[0] }
109
+ const opt = item[1]
110
+ if (!opt || typeof opt !== "object" || Array.isArray(opt)) return { kind: item[0] }
111
+ return {
112
+ kind: item[0],
113
+ opts: opt,
114
+ }
115
+ }
116
+
117
+ function parseTargets(raw: unknown) {
118
+ if (!Array.isArray(raw)) return []
119
+ const map = new Map<Kind, Target>()
120
+ for (const item of raw) {
121
+ const hit = parseTarget(item)
122
+ if (!hit) continue
123
+ map.set(hit.kind, hit)
124
+ }
125
+ return [...map.values()]
126
+ }
127
+
128
+ function patch(text: string, path: Array<string | number>, value: unknown, insert = false) {
129
+ return applyEdits(
130
+ text,
131
+ modify(text, path, value, {
132
+ formattingOptions: {
133
+ tabSize: 2,
134
+ insertSpaces: true,
135
+ },
136
+ isArrayInsertion: insert,
137
+ }),
138
+ )
139
+ }
140
+
141
+ function patchPluginList(
142
+ text: string,
143
+ list: unknown[] | undefined,
144
+ spec: string,
145
+ next: unknown,
146
+ force = false,
147
+ ): { mode: Mode; text: string } {
148
+ const pkg = parsePluginSpecifier(spec).pkg
149
+ const rows = (list ?? []).map((item, i) => ({
150
+ item,
151
+ i,
152
+ spec: pluginSpec(item),
153
+ }))
154
+ const dup = rows.filter((item) => {
155
+ if (!item.spec) return false
156
+ if (item.spec === spec) return true
157
+ if (item.spec.startsWith("file://")) return false
158
+ return parsePluginSpecifier(item.spec).pkg === pkg
159
+ })
160
+
161
+ if (!dup.length) {
162
+ if (!list) {
163
+ return {
164
+ mode: "add",
165
+ text: patch(text, ["plugin"], [next]),
166
+ }
167
+ }
168
+ return {
169
+ mode: "add",
170
+ text: patch(text, ["plugin", list.length], next, true),
171
+ }
172
+ }
173
+
174
+ if (!force) {
175
+ return {
176
+ mode: "noop",
177
+ text,
178
+ }
179
+ }
180
+
181
+ const keep = dup[0]
182
+ if (!keep) {
183
+ return {
184
+ mode: "noop",
185
+ text,
186
+ }
187
+ }
188
+
189
+ if (dup.length === 1 && keep.spec === spec) {
190
+ return {
191
+ mode: "noop",
192
+ text,
193
+ }
194
+ }
195
+
196
+ let out = text
197
+ if (typeof keep.item === "string") {
198
+ out = patch(out, ["plugin", keep.i], next)
199
+ }
200
+ if (Array.isArray(keep.item) && typeof keep.item[0] === "string") {
201
+ out = patch(out, ["plugin", keep.i, 0], spec)
202
+ }
203
+
204
+ const del = dup
205
+ .map((item) => item.i)
206
+ .filter((i) => i !== keep.i)
207
+ .sort((a, b) => b - a)
208
+
209
+ for (const i of del) {
210
+ out = patch(out, ["plugin", i], undefined)
211
+ }
212
+
213
+ return {
214
+ mode: "replace",
215
+ text: out,
216
+ }
217
+ }
218
+
219
+ export async function installPlugin(spec: string, dep: InstallDeps = defaultInstallDeps): Promise<InstallResult> {
220
+ const target = await dep.resolve(spec).then(
221
+ (item) => ({
222
+ ok: true as const,
223
+ item,
224
+ }),
225
+ (error: unknown) => ({
226
+ ok: false as const,
227
+ error,
228
+ }),
229
+ )
230
+ if (!target.ok) {
231
+ return {
232
+ ok: false,
233
+ code: "install_failed",
234
+ error: target.error,
235
+ }
236
+ }
237
+ return {
238
+ ok: true,
239
+ target: target.item,
240
+ }
241
+ }
242
+
243
+ export async function readPluginManifest(target: string): Promise<ManifestResult> {
244
+ const pkg = await readPluginPackage(target).then(
245
+ (item) => ({
246
+ ok: true as const,
247
+ item,
248
+ }),
249
+ (error: unknown) => ({
250
+ ok: false as const,
251
+ error,
252
+ }),
253
+ )
254
+ if (!pkg.ok) {
255
+ return {
256
+ ok: false,
257
+ code: "manifest_read_failed",
258
+ file: target,
259
+ error: pkg.error,
260
+ }
261
+ }
262
+
263
+ const targets = parseTargets(pkg.item.json["oc-plugin"])
264
+ if (!targets.length) {
265
+ return {
266
+ ok: false,
267
+ code: "manifest_no_targets",
268
+ file: pkg.item.pkg,
269
+ }
270
+ }
271
+
272
+ return {
273
+ ok: true,
274
+ targets,
275
+ }
276
+ }
277
+
278
+ function patchDir(input: PatchInput) {
279
+ if (input.global) return input.config ?? Global.Path.config
280
+ const git = input.vcs === "git" && input.worktree !== "/"
281
+ const root = git ? input.worktree : input.directory
282
+ return path.join(root, ".toolkode")
283
+ }
284
+
285
+ function patchName(kind: Kind): "toolkode" | "tui" {
286
+ if (kind === "server") return "toolkode"
287
+ return "tui"
288
+ }
289
+
290
+ async function patchOne(dir: string, target: Target, spec: string, force: boolean, dep: PatchDeps): Promise<PatchOne> {
291
+ const name = patchName(target.kind)
292
+ await using _ = await Flock.acquire(`plug-config:${Filesystem.resolve(path.join(dir, name))}`)
293
+
294
+ const files = dep.files(dir, name)
295
+ let cfg = files[0]
296
+ for (const file of files) {
297
+ if (!(await dep.exists(file))) continue
298
+ cfg = file
299
+ break
300
+ }
301
+
302
+ const src = await dep.readText(cfg).catch((err: NodeJS.ErrnoException) => {
303
+ if (err.code === "ENOENT") return "{}"
304
+ return err
305
+ })
306
+ if (src instanceof Error) {
307
+ return {
308
+ ok: false,
309
+ code: "patch_failed",
310
+ kind: target.kind,
311
+ error: src,
312
+ }
313
+ }
314
+ const text = src.trim() ? src : "{}"
315
+
316
+ const errs: JsoncParseError[] = []
317
+ const data = parseJsonc(text, errs, { allowTrailingComma: true })
318
+ if (errs.length) {
319
+ const err = errs[0]
320
+ const lines = text.substring(0, err.offset).split("\n")
321
+ return {
322
+ ok: false,
323
+ code: "invalid_json",
324
+ kind: target.kind,
325
+ file: cfg,
326
+ line: lines.length,
327
+ col: lines[lines.length - 1].length + 1,
328
+ parse: printParseErrorCode(err.error),
329
+ }
330
+ }
331
+
332
+ const list = pluginList(data)
333
+ const item = target.opts ? [spec, target.opts] : spec
334
+ const out = patchPluginList(text, list, spec, item, force)
335
+ if (out.mode === "noop") {
336
+ return {
337
+ ok: true,
338
+ item: {
339
+ kind: target.kind,
340
+ mode: out.mode,
341
+ file: cfg,
342
+ },
343
+ }
344
+ }
345
+
346
+ const write = await dep.write(cfg, out.text).catch((error: unknown) => error)
347
+ if (write instanceof Error) {
348
+ return {
349
+ ok: false,
350
+ code: "patch_failed",
351
+ kind: target.kind,
352
+ error: write,
353
+ }
354
+ }
355
+
356
+ return {
357
+ ok: true,
358
+ item: {
359
+ kind: target.kind,
360
+ mode: out.mode,
361
+ file: cfg,
362
+ },
363
+ }
364
+ }
365
+
366
+ export async function patchPluginConfig(input: PatchInput, dep: PatchDeps = defaultPatchDeps): Promise<PatchResult> {
367
+ const dir = patchDir(input)
368
+ const items: PatchItem[] = []
369
+ for (const target of input.targets) {
370
+ const hit = await patchOne(dir, target, input.spec, Boolean(input.force), dep)
371
+ if (!hit.ok) {
372
+ return {
373
+ ...hit,
374
+ dir,
375
+ }
376
+ }
377
+ items.push(hit.item)
378
+ }
379
+ return {
380
+ ok: true,
381
+ dir,
382
+ items,
383
+ }
384
+ }
@@ -0,0 +1,165 @@
1
+ import path from "path"
2
+ import { fileURLToPath } from "url"
3
+
4
+ import { Flag } from "@/flag/flag"
5
+ import { Global } from "@/global"
6
+ import { Filesystem } from "@/util/filesystem"
7
+ import { Flock } from "@/util/flock"
8
+
9
+ import { parsePluginSpecifier, pluginSource } from "./shared"
10
+
11
+ export namespace PluginMeta {
12
+ type Source = "file" | "npm"
13
+
14
+ export type Entry = {
15
+ id: string
16
+ source: Source
17
+ spec: string
18
+ target: string
19
+ requested?: string
20
+ version?: string
21
+ modified?: number
22
+ first_time: number
23
+ last_time: number
24
+ time_changed: number
25
+ load_count: number
26
+ fingerprint: string
27
+ }
28
+
29
+ export type State = "first" | "updated" | "same"
30
+
31
+ export type Touch = {
32
+ spec: string
33
+ target: string
34
+ id: string
35
+ }
36
+
37
+ type Store = Record<string, Entry>
38
+ type Core = Omit<Entry, "first_time" | "last_time" | "time_changed" | "load_count" | "fingerprint">
39
+ type Row = Touch & { core: Core }
40
+
41
+ function storePath() {
42
+ return Flag.OPENCODE_PLUGIN_META_FILE ?? path.join(Global.Path.state, "plugin-meta.json")
43
+ }
44
+
45
+ function lock(file: string) {
46
+ return `plugin-meta:${file}`
47
+ }
48
+
49
+ function fileTarget(spec: string, target: string) {
50
+ if (spec.startsWith("file://")) return fileURLToPath(spec)
51
+ if (target.startsWith("file://")) return fileURLToPath(target)
52
+ return
53
+ }
54
+
55
+ function modifiedAt(file: string) {
56
+ const stat = Filesystem.stat(file)
57
+ if (!stat) return
58
+ const value = stat.mtimeMs
59
+ return Math.floor(typeof value === "bigint" ? Number(value) : value)
60
+ }
61
+
62
+ function resolvedTarget(target: string) {
63
+ if (target.startsWith("file://")) return fileURLToPath(target)
64
+ return target
65
+ }
66
+
67
+ async function npmVersion(target: string) {
68
+ const resolved = resolvedTarget(target)
69
+ const stat = Filesystem.stat(resolved)
70
+ const dir = stat?.isDirectory() ? resolved : path.dirname(resolved)
71
+ return Filesystem.readJson<{ version?: string }>(path.join(dir, "package.json"))
72
+ .then((item) => item.version)
73
+ .catch(() => undefined)
74
+ }
75
+
76
+ async function entryCore(item: Touch): Promise<Core> {
77
+ const spec = item.spec
78
+ const target = item.target
79
+ const source = pluginSource(spec)
80
+ if (source === "file") {
81
+ const file = fileTarget(spec, target)
82
+ return {
83
+ id: item.id,
84
+ source,
85
+ spec,
86
+ target,
87
+ modified: file ? modifiedAt(file) : undefined,
88
+ }
89
+ }
90
+
91
+ return {
92
+ id: item.id,
93
+ source,
94
+ spec,
95
+ target,
96
+ requested: parsePluginSpecifier(spec).version,
97
+ version: await npmVersion(target),
98
+ }
99
+ }
100
+
101
+ function fingerprint(value: Core) {
102
+ if (value.source === "file") return [value.target, value.modified ?? ""].join("|")
103
+ return [value.target, value.requested ?? "", value.version ?? ""].join("|")
104
+ }
105
+
106
+ async function read(file: string): Promise<Store> {
107
+ return Filesystem.readJson<Store>(file).catch(() => ({}) as Store)
108
+ }
109
+
110
+ async function row(item: Touch): Promise<Row> {
111
+ return {
112
+ ...item,
113
+ core: await entryCore(item),
114
+ }
115
+ }
116
+
117
+ function next(prev: Entry | undefined, core: Core, now: number): { state: State; entry: Entry } {
118
+ const entry: Entry = {
119
+ ...core,
120
+ first_time: prev?.first_time ?? now,
121
+ last_time: now,
122
+ time_changed: prev?.time_changed ?? now,
123
+ load_count: (prev?.load_count ?? 0) + 1,
124
+ fingerprint: fingerprint(core),
125
+ }
126
+ const state: State = !prev ? "first" : prev.fingerprint === entry.fingerprint ? "same" : "updated"
127
+ if (state === "updated") entry.time_changed = now
128
+ return {
129
+ state,
130
+ entry,
131
+ }
132
+ }
133
+
134
+ export async function touchMany(items: Touch[]): Promise<Array<{ state: State; entry: Entry }>> {
135
+ if (!items.length) return []
136
+ const file = storePath()
137
+ const rows = await Promise.all(items.map((item) => row(item)))
138
+
139
+ return Flock.withLock(lock(file), async () => {
140
+ const store = await read(file)
141
+ const now = Date.now()
142
+ const out: Array<{ state: State; entry: Entry }> = []
143
+ for (const item of rows) {
144
+ const hit = next(store[item.id], item.core, now)
145
+ store[item.id] = hit.entry
146
+ out.push(hit)
147
+ }
148
+ await Filesystem.writeJson(file, store)
149
+ return out
150
+ })
151
+ }
152
+
153
+ export async function touch(spec: string, target: string, id: string): Promise<{ state: State; entry: Entry }> {
154
+ return touchMany([{ spec, target, id }]).then((item) => {
155
+ const hit = item[0]
156
+ if (hit) return hit
157
+ throw new Error("Failed to touch plugin metadata.")
158
+ })
159
+ }
160
+
161
+ export async function list(): Promise<Store> {
162
+ const file = storePath()
163
+ return Flock.withLock(lock(file), async () => read(file))
164
+ }
165
+ }
@@ -0,0 +1,172 @@
1
+ import path from "path"
2
+ import { fileURLToPath, pathToFileURL } from "url"
3
+ import semver from "semver"
4
+ import { BunProc } from "@/bun"
5
+ import { Filesystem } from "@/util/filesystem"
6
+ import { isRecord } from "@/util/record"
7
+
8
+ // Old npm package names for plugins that are now built-in
9
+ export const DEPRECATED_PLUGIN_PACKAGES = ["opencode-openai-codex-auth", "opencode-copilot-auth"]
10
+
11
+ export function isDeprecatedPlugin(spec: string) {
12
+ return DEPRECATED_PLUGIN_PACKAGES.some((pkg) => spec.includes(pkg))
13
+ }
14
+
15
+ export function parsePluginSpecifier(spec: string) {
16
+ const lastAt = spec.lastIndexOf("@")
17
+ const pkg = lastAt > 0 ? spec.substring(0, lastAt) : spec
18
+ const version = lastAt > 0 ? spec.substring(lastAt + 1) : "latest"
19
+ return { pkg, version }
20
+ }
21
+
22
+ export type PluginSource = "file" | "npm"
23
+ export type PluginKind = "server" | "tui"
24
+ type PluginMode = "strict" | "detect"
25
+
26
+ export function pluginSource(spec: string): PluginSource {
27
+ return spec.startsWith("file://") ? "file" : "npm"
28
+ }
29
+
30
+ function hasEntrypoint(json: Record<string, unknown>, kind: PluginKind) {
31
+ if (!isRecord(json.exports)) return false
32
+ return `./${kind}` in json.exports
33
+ }
34
+
35
+ function resolveExportPath(raw: string, dir: string) {
36
+ if (raw.startsWith("./") || raw.startsWith("../")) return path.resolve(dir, raw)
37
+ if (raw.startsWith("file://")) return fileURLToPath(raw)
38
+ return raw
39
+ }
40
+
41
+ function extractExportValue(value: unknown): string | undefined {
42
+ if (typeof value === "string") return value
43
+ if (!isRecord(value)) return undefined
44
+ for (const key of ["import", "default"]) {
45
+ const nested = value[key]
46
+ if (typeof nested === "string") return nested
47
+ }
48
+ return undefined
49
+ }
50
+
51
+ export async function resolvePluginEntrypoint(spec: string, target: string, kind: PluginKind) {
52
+ const pkg = await readPluginPackage(target).catch(() => undefined)
53
+ if (!pkg) return target
54
+ if (!hasEntrypoint(pkg.json, kind)) return target
55
+
56
+ const exports = pkg.json.exports
57
+ if (!isRecord(exports)) return target
58
+ const raw = extractExportValue(exports[`./${kind}`])
59
+ if (!raw) return target
60
+
61
+ const resolved = resolveExportPath(raw, pkg.dir)
62
+ const root = Filesystem.resolve(pkg.dir)
63
+ const next = Filesystem.resolve(resolved)
64
+ if (!Filesystem.contains(root, next)) {
65
+ throw new Error(`Plugin ${spec} resolved ${kind} entry outside plugin directory`)
66
+ }
67
+
68
+ return pathToFileURL(next).href
69
+ }
70
+
71
+ export function isPathPluginSpec(spec: string) {
72
+ return spec.startsWith("file://") || spec.startsWith(".") || path.isAbsolute(spec) || /^[A-Za-z]:[\\/]/.test(spec)
73
+ }
74
+
75
+ export async function resolvePathPluginTarget(spec: string) {
76
+ const raw = spec.startsWith("file://") ? fileURLToPath(spec) : spec
77
+ const file = path.isAbsolute(raw) || /^[A-Za-z]:[\\/]/.test(raw) ? raw : path.resolve(raw)
78
+ const stat = await Filesystem.stat(file)
79
+ if (!stat?.isDirectory()) {
80
+ if (spec.startsWith("file://")) return spec
81
+ return pathToFileURL(file).href
82
+ }
83
+
84
+ const pkg = await Filesystem.readJson<Record<string, unknown>>(path.join(file, "package.json")).catch(() => undefined)
85
+ if (!pkg) throw new Error(`Plugin directory ${file} is missing package.json`)
86
+ if (typeof pkg.main !== "string" || !pkg.main.trim()) {
87
+ throw new Error(`Plugin directory ${file} must define package.json main`)
88
+ }
89
+ return pathToFileURL(path.resolve(file, pkg.main)).href
90
+ }
91
+
92
+ export async function checkPluginCompatibility(target: string, opencodeVersion: string) {
93
+ if (!semver.valid(opencodeVersion) || semver.major(opencodeVersion) === 0) return
94
+ const pkg = await readPluginPackage(target).catch(() => undefined)
95
+ if (!pkg) return
96
+ const engines = pkg.json.engines
97
+ if (!isRecord(engines)) return
98
+ const range = engines.opencode
99
+ if (typeof range !== "string") return
100
+ if (!semver.satisfies(opencodeVersion, range)) {
101
+ throw new Error(`Plugin requires opencode ${range} but running ${opencodeVersion}`)
102
+ }
103
+ }
104
+
105
+ export async function resolvePluginTarget(spec: string, parsed = parsePluginSpecifier(spec)) {
106
+ if (isPathPluginSpec(spec)) return resolvePathPluginTarget(spec)
107
+ return BunProc.install(parsed.pkg, parsed.version)
108
+ }
109
+
110
+ export async function readPluginPackage(target: string) {
111
+ const file = target.startsWith("file://") ? fileURLToPath(target) : target
112
+ const stat = await Filesystem.stat(file)
113
+ const dir = stat?.isDirectory() ? file : path.dirname(file)
114
+ const pkg = path.join(dir, "package.json")
115
+ const json = await Filesystem.readJson<Record<string, unknown>>(pkg)
116
+ return { dir, pkg, json }
117
+ }
118
+
119
+ export function readPluginId(id: unknown, spec: string) {
120
+ if (id === undefined) return
121
+ if (typeof id !== "string") throw new TypeError(`Plugin ${spec} has invalid id type ${typeof id}`)
122
+ const value = id.trim()
123
+ if (!value) throw new TypeError(`Plugin ${spec} has an empty id`)
124
+ return value
125
+ }
126
+
127
+ export function readV1Plugin(
128
+ mod: Record<string, unknown>,
129
+ spec: string,
130
+ kind: PluginKind,
131
+ mode: PluginMode = "strict",
132
+ ) {
133
+ const value = mod.default
134
+ if (!isRecord(value)) {
135
+ if (mode === "detect") return
136
+ throw new TypeError(`Plugin ${spec} must default export an object with ${kind}()`)
137
+ }
138
+ if (mode === "detect" && !("id" in value) && !("server" in value) && !("tui" in value)) return
139
+
140
+ const server = "server" in value ? value.server : undefined
141
+ const tui = "tui" in value ? value.tui : undefined
142
+ if (server !== undefined && typeof server !== "function") {
143
+ throw new TypeError(`Plugin ${spec} has invalid server export`)
144
+ }
145
+ if (tui !== undefined && typeof tui !== "function") {
146
+ throw new TypeError(`Plugin ${spec} has invalid tui export`)
147
+ }
148
+ if (server !== undefined && tui !== undefined) {
149
+ throw new TypeError(`Plugin ${spec} must default export either server() or tui(), not both`)
150
+ }
151
+ if (kind === "server" && server === undefined) {
152
+ throw new TypeError(`Plugin ${spec} must default export an object with server()`)
153
+ }
154
+ if (kind === "tui" && tui === undefined) {
155
+ throw new TypeError(`Plugin ${spec} must default export an object with tui()`)
156
+ }
157
+
158
+ return value
159
+ }
160
+
161
+ export async function resolvePluginId(source: PluginSource, spec: string, target: string, id: string | undefined) {
162
+ if (source === "file") {
163
+ if (id) return id
164
+ throw new TypeError(`Path plugin ${spec} must export id`)
165
+ }
166
+ if (id) return id
167
+ const pkg = await readPluginPackage(target)
168
+ if (typeof pkg.json.name !== "string" || !pkg.json.name.trim()) {
169
+ throw new TypeError(`Plugin package ${pkg.pkg} is missing name`)
170
+ }
171
+ return pkg.json.name.trim()
172
+ }