nikcli 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (602) hide show
  1. package/.turbo/turbo-typecheck.log +1 -0
  2. package/AGENTS.md +27 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/nikcli +84 -0
  6. package/config.json +13 -0
  7. package/docs/tailscale-mobile/01-tailscale-setup.md +94 -0
  8. package/docs/tailscale-mobile/02-host-setup.md +115 -0
  9. package/docs/tailscale-mobile/03-phone-and-serve.md +134 -0
  10. package/docs/tailscale-mobile/README.md +59 -0
  11. package/examples/README.md +54 -0
  12. package/package.json +147 -0
  13. package/parsers-config.ts +253 -0
  14. package/script/build.ts +179 -0
  15. package/script/postinstall.mjs +125 -0
  16. package/script/publish-registries.ts +187 -0
  17. package/script/publish.ts +100 -0
  18. package/script/schema.ts +47 -0
  19. package/script/seed-e2e.ts +50 -0
  20. package/sequential-prancing-forest.md +373 -0
  21. package/src/acp/README.md +164 -0
  22. package/src/acp/agent.ts +1303 -0
  23. package/src/acp/session.ts +105 -0
  24. package/src/acp/types.ts +22 -0
  25. package/src/agent/agent.ts +528 -0
  26. package/src/agent/generate.txt +32 -0
  27. package/src/agent/prompt/compaction.txt +14 -0
  28. package/src/agent/prompt/explore.txt +18 -0
  29. package/src/agent/prompt/summary.txt +11 -0
  30. package/src/agent/prompt/title.txt +44 -0
  31. package/src/auth/index.ts +73 -0
  32. package/src/bun/index.ts +119 -0
  33. package/src/bun/registry.ts +54 -0
  34. package/src/bus/bus-event.ts +43 -0
  35. package/src/bus/global.ts +10 -0
  36. package/src/bus/index.ts +105 -0
  37. package/src/chatbot/handlers.ts +150 -0
  38. package/src/chatbot/index.ts +132 -0
  39. package/src/cli/bootstrap.ts +17 -0
  40. package/src/cli/cmd/acp.ts +69 -0
  41. package/src/cli/cmd/ads.ts +377 -0
  42. package/src/cli/cmd/agent.ts +259 -0
  43. package/src/cli/cmd/auth.ts +400 -0
  44. package/src/cli/cmd/chatbot.ts +420 -0
  45. package/src/cli/cmd/cmd.ts +7 -0
  46. package/src/cli/cmd/companion.ts +81 -0
  47. package/src/cli/cmd/connectors.ts +593 -0
  48. package/src/cli/cmd/debug/agent.ts +166 -0
  49. package/src/cli/cmd/debug/config.ts +16 -0
  50. package/src/cli/cmd/debug/file.ts +97 -0
  51. package/src/cli/cmd/debug/index.ts +48 -0
  52. package/src/cli/cmd/debug/lsp.ts +52 -0
  53. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  54. package/src/cli/cmd/debug/scrap.ts +16 -0
  55. package/src/cli/cmd/debug/skill.ts +16 -0
  56. package/src/cli/cmd/debug/snapshot.ts +52 -0
  57. package/src/cli/cmd/export.ts +88 -0
  58. package/src/cli/cmd/generate.ts +38 -0
  59. package/src/cli/cmd/github.ts +412 -0
  60. package/src/cli/cmd/image-model.ts +128 -0
  61. package/src/cli/cmd/import.ts +201 -0
  62. package/src/cli/cmd/lovable.ts +128 -0
  63. package/src/cli/cmd/mcp.ts +738 -0
  64. package/src/cli/cmd/mobile.ts +223 -0
  65. package/src/cli/cmd/models.ts +77 -0
  66. package/src/cli/cmd/plug.ts +231 -0
  67. package/src/cli/cmd/pr.ts +104 -0
  68. package/src/cli/cmd/rag-model.ts +167 -0
  69. package/src/cli/cmd/remote.ts +416 -0
  70. package/src/cli/cmd/run.ts +589 -0
  71. package/src/cli/cmd/serve.ts +51 -0
  72. package/src/cli/cmd/session.ts +133 -0
  73. package/src/cli/cmd/speak-model.ts +204 -0
  74. package/src/cli/cmd/stats.ts +402 -0
  75. package/src/cli/cmd/tui/app.tsx +841 -0
  76. package/src/cli/cmd/tui/attach.ts +31 -0
  77. package/src/cli/cmd/tui/component/border.tsx +75 -0
  78. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  79. package/src/cli/cmd/tui/component/dialog-command.tsx +172 -0
  80. package/src/cli/cmd/tui/component/dialog-config.tsx +291 -0
  81. package/src/cli/cmd/tui/component/dialog-connectors.tsx +440 -0
  82. package/src/cli/cmd/tui/component/dialog-image-model.tsx +97 -0
  83. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  84. package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
  85. package/src/cli/cmd/tui/component/dialog-provider.tsx +260 -0
  86. package/src/cli/cmd/tui/component/dialog-rag-model.tsx +217 -0
  87. package/src/cli/cmd/tui/component/dialog-remote.tsx +489 -0
  88. package/src/cli/cmd/tui/component/dialog-session-list.tsx +170 -0
  89. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  90. package/src/cli/cmd/tui/component/dialog-settings/index.tsx +59 -0
  91. package/src/cli/cmd/tui/component/dialog-settings/prompt.tsx +40 -0
  92. package/src/cli/cmd/tui/component/dialog-settings/sidebar.tsx +39 -0
  93. package/src/cli/cmd/tui/component/dialog-settings/spinner.tsx +62 -0
  94. package/src/cli/cmd/tui/component/dialog-settings/ui.tsx +58 -0
  95. package/src/cli/cmd/tui/component/dialog-skills.tsx +117 -0
  96. package/src/cli/cmd/tui/component/dialog-speak-model.tsx +304 -0
  97. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  98. package/src/cli/cmd/tui/component/dialog-status.tsx +165 -0
  99. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  100. package/src/cli/cmd/tui/component/dialog-theme-create.tsx +717 -0
  101. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +52 -0
  102. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +350 -0
  103. package/src/cli/cmd/tui/component/error-component.tsx +91 -0
  104. package/src/cli/cmd/tui/component/logo.tsx +103 -0
  105. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  106. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +669 -0
  107. package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
  108. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  109. package/src/cli/cmd/tui/component/prompt/index.tsx +2165 -0
  110. package/src/cli/cmd/tui/component/prompt/stash.tsx +63 -0
  111. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  112. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  113. package/src/cli/cmd/tui/component/table/markdown-table.tsx +267 -0
  114. package/src/cli/cmd/tui/component/table-db/db/connections.ts +75 -0
  115. package/src/cli/cmd/tui/component/table-db/db/db-connection.ts +223 -0
  116. package/src/cli/cmd/tui/component/table-db/db/db-preview.ts +202 -0
  117. package/src/cli/cmd/tui/component/table-db/db/factory.ts +77 -0
  118. package/src/cli/cmd/tui/component/table-db/db/index.ts +9 -0
  119. package/src/cli/cmd/tui/component/table-db/db/mysql-connection.ts +330 -0
  120. package/src/cli/cmd/tui/component/table-db/db/postgres-connection.ts +338 -0
  121. package/src/cli/cmd/tui/component/table-db/db/sqlite-connection.ts +302 -0
  122. package/src/cli/cmd/tui/component/table-db/db/types.ts +108 -0
  123. package/src/cli/cmd/tui/component/table-db/table/dbedit-hooks.ts +74 -0
  124. package/src/cli/cmd/tui/component/table-db/table/index.ts +15 -0
  125. package/src/cli/cmd/tui/component/table-db/table/table-events.ts +54 -0
  126. package/src/cli/cmd/tui/component/table-db/table/table-formatters.ts +191 -0
  127. package/src/cli/cmd/tui/component/table-db/table/table-hooks.ts +105 -0
  128. package/src/cli/cmd/tui/component/table-db/table/table-keyboard-handler.ts +255 -0
  129. package/src/cli/cmd/tui/component/table-db/table/table-layout-engine.ts +208 -0
  130. package/src/cli/cmd/tui/component/table-db/table/table-renderable.ts +486 -0
  131. package/src/cli/cmd/tui/component/table-db/table/table-selection-manager.ts +136 -0
  132. package/src/cli/cmd/tui/component/table-db/table/table-state.ts +198 -0
  133. package/src/cli/cmd/tui/component/table-db/table/types.ts +69 -0
  134. package/src/cli/cmd/tui/component/table-db/ui/db-visualizer.tsx +71 -0
  135. package/src/cli/cmd/tui/component/table-db/ui/index.ts +2 -0
  136. package/src/cli/cmd/tui/component/table-db/ui/table-renderer.ts +607 -0
  137. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  138. package/src/cli/cmd/tui/component/tips.tsx +195 -0
  139. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  140. package/src/cli/cmd/tui/context/args.tsx +14 -0
  141. package/src/cli/cmd/tui/context/directory.ts +13 -0
  142. package/src/cli/cmd/tui/context/exit.tsx +24 -0
  143. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  144. package/src/cli/cmd/tui/context/keybind.tsx +102 -0
  145. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  146. package/src/cli/cmd/tui/context/local.tsx +458 -0
  147. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  148. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  149. package/src/cli/cmd/tui/context/route.tsx +54 -0
  150. package/src/cli/cmd/tui/context/sdk.tsx +128 -0
  151. package/src/cli/cmd/tui/context/server.tsx +8 -0
  152. package/src/cli/cmd/tui/context/sync.tsx +510 -0
  153. package/src/cli/cmd/tui/context/theme/abyss.json +233 -0
  154. package/src/cli/cmd/tui/context/theme/apple.json +235 -0
  155. package/src/cli/cmd/tui/context/theme/arctic.json +232 -0
  156. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  157. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  158. package/src/cli/cmd/tui/context/theme/ayuai.json +229 -0
  159. package/src/cli/cmd/tui/context/theme/blood.json +229 -0
  160. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  161. package/src/cli/cmd/tui/context/theme/catmoe.json +235 -0
  162. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  163. package/src/cli/cmd/tui/context/theme/catppuccin-latte.json +233 -0
  164. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  165. package/src/cli/cmd/tui/context/theme/catppuccin.json +259 -0
  166. package/src/cli/cmd/tui/context/theme/charcoal.json +230 -0
  167. package/src/cli/cmd/tui/context/theme/chromatic.json +235 -0
  168. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  169. package/src/cli/cmd/tui/context/theme/cosmic.json +234 -0
  170. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  171. package/src/cli/cmd/tui/context/theme/cyber.json +235 -0
  172. package/src/cli/cmd/tui/context/theme/dawnfox.json +229 -0
  173. package/src/cli/cmd/tui/context/theme/dimension.json +235 -0
  174. package/src/cli/cmd/tui/context/theme/dracula-official.json +222 -0
  175. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  176. package/src/cli/cmd/tui/context/theme/dream.json +235 -0
  177. package/src/cli/cmd/tui/context/theme/duo.json +235 -0
  178. package/src/cli/cmd/tui/context/theme/dusk.json +235 -0
  179. package/src/cli/cmd/tui/context/theme/ebony.json +232 -0
  180. package/src/cli/cmd/tui/context/theme/equilibrium.json +232 -0
  181. package/src/cli/cmd/tui/context/theme/ethereal.json +235 -0
  182. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  183. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  184. package/src/cli/cmd/tui/context/theme/fusion.json +235 -0
  185. package/src/cli/cmd/tui/context/theme/ghost.json +235 -0
  186. package/src/cli/cmd/tui/context/theme/github-dark.json +229 -0
  187. package/src/cli/cmd/tui/context/theme/github-dimmed.json +231 -0
  188. package/src/cli/cmd/tui/context/theme/github-light.json +229 -0
  189. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  190. package/src/cli/cmd/tui/context/theme/glass.json +235 -0
  191. package/src/cli/cmd/tui/context/theme/gold.json +235 -0
  192. package/src/cli/cmd/tui/context/theme/gone.json +234 -0
  193. package/src/cli/cmd/tui/context/theme/greyscale.json +229 -0
  194. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  195. package/src/cli/cmd/tui/context/theme/hacker.json +229 -0
  196. package/src/cli/cmd/tui/context/theme/holo.json +235 -0
  197. package/src/cli/cmd/tui/context/theme/ink.json +235 -0
  198. package/src/cli/cmd/tui/context/theme/jet.json +233 -0
  199. package/src/cli/cmd/tui/context/theme/kanagawa.json +227 -0
  200. package/src/cli/cmd/tui/context/theme/lavender.json +236 -0
  201. package/src/cli/cmd/tui/context/theme/lightph.json +235 -0
  202. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  203. package/src/cli/cmd/tui/context/theme/material-ocean.json +230 -0
  204. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  205. package/src/cli/cmd/tui/context/theme/matrix.json +227 -0
  206. package/src/cli/cmd/tui/context/theme/mercury.json +245 -0
  207. package/src/cli/cmd/tui/context/theme/midnight.json +235 -0
  208. package/src/cli/cmd/tui/context/theme/modern.json +235 -0
  209. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  210. package/src/cli/cmd/tui/context/theme/muted.json +229 -0
  211. package/src/cli/cmd/tui/context/theme/neon.json +229 -0
  212. package/src/cli/cmd/tui/context/theme/neonfusion.json +235 -0
  213. package/src/cli/cmd/tui/context/theme/neutral.json +235 -0
  214. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  215. package/src/cli/cmd/tui/context/theme/nikcli.json +245 -0
  216. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  217. package/src/cli/cmd/tui/context/theme/nordic.json +235 -0
  218. package/src/cli/cmd/tui/context/theme/nova.json +235 -0
  219. package/src/cli/cmd/tui/context/theme/obsidian.json +234 -0
  220. package/src/cli/cmd/tui/context/theme/one-dark.json +231 -0
  221. package/src/cli/cmd/tui/context/theme/one-pro.json +229 -0
  222. package/src/cli/cmd/tui/context/theme/onyx.json +233 -0
  223. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  224. package/src/cli/cmd/tui/context/theme/osaka-jade.json +240 -0
  225. package/src/cli/cmd/tui/context/theme/oxocarbon.json +229 -0
  226. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  227. package/src/cli/cmd/tui/context/theme/poimandres.json +230 -0
  228. package/src/cli/cmd/tui/context/theme/prism.json +235 -0
  229. package/src/cli/cmd/tui/context/theme/radiant.json +235 -0
  230. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  231. package/src/cli/cmd/tui/context/theme/shadow.json +235 -0
  232. package/src/cli/cmd/tui/context/theme/silicon.json +235 -0
  233. package/src/cli/cmd/tui/context/theme/slate.json +233 -0
  234. package/src/cli/cmd/tui/context/theme/soft.json +235 -0
  235. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  236. package/src/cli/cmd/tui/context/theme/spectrum.json +235 -0
  237. package/src/cli/cmd/tui/context/theme/starlight.json +233 -0
  238. package/src/cli/cmd/tui/context/theme/sunrise.json +235 -0
  239. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  240. package/src/cli/cmd/tui/context/theme/tech.json +235 -0
  241. package/src/cli/cmd/tui/context/theme/tokyonight-storm.json +245 -0
  242. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  243. package/src/cli/cmd/tui/context/theme/vapor.json +235 -0
  244. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  245. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  246. package/src/cli/cmd/tui/context/theme/vivid.json +232 -0
  247. package/src/cli/cmd/tui/context/theme/void.json +235 -0
  248. package/src/cli/cmd/tui/context/theme/vscode.json +235 -0
  249. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  250. package/src/cli/cmd/tui/context/theme/zinc.json +236 -0
  251. package/src/cli/cmd/tui/context/theme.tsx +1303 -0
  252. package/src/cli/cmd/tui/event.ts +48 -0
  253. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
  254. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  255. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  256. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  257. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  258. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  259. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  260. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  261. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +288 -0
  262. package/src/cli/cmd/tui/plugin/api.tsx +407 -0
  263. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  264. package/src/cli/cmd/tui/plugin/internal.ts +25 -0
  265. package/src/cli/cmd/tui/plugin/runtime.ts +1048 -0
  266. package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
  267. package/src/cli/cmd/tui/routes/home.tsx +153 -0
  268. package/src/cli/cmd/tui/routes/session/dbedit.tsx +474 -0
  269. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  270. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  271. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +105 -0
  272. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  273. package/src/cli/cmd/tui/routes/session/footer.tsx +75 -0
  274. package/src/cli/cmd/tui/routes/session/header.tsx +177 -0
  275. package/src/cli/cmd/tui/routes/session/index.tsx +2280 -0
  276. package/src/cli/cmd/tui/routes/session/permission.tsx +540 -0
  277. package/src/cli/cmd/tui/routes/session/question.tsx +435 -0
  278. package/src/cli/cmd/tui/routes/session/sidebar.tsx +313 -0
  279. package/src/cli/cmd/tui/thread.ts +174 -0
  280. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  281. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  282. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
  283. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  284. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +102 -0
  285. package/src/cli/cmd/tui/ui/dialog-select.tsx +389 -0
  286. package/src/cli/cmd/tui/ui/dialog.tsx +180 -0
  287. package/src/cli/cmd/tui/ui/link.tsx +34 -0
  288. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  289. package/src/cli/cmd/tui/ui/toast.tsx +138 -0
  290. package/src/cli/cmd/tui/util/clipboard.ts +154 -0
  291. package/src/cli/cmd/tui/util/editor.ts +32 -0
  292. package/src/cli/cmd/tui/util/signal.ts +7 -0
  293. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  294. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  295. package/src/cli/cmd/tui/win32.ts +110 -0
  296. package/src/cli/cmd/tui/worker.ts +156 -0
  297. package/src/cli/cmd/uninstall.ts +357 -0
  298. package/src/cli/cmd/upgrade.ts +72 -0
  299. package/src/cli/cmd/web.ts +87 -0
  300. package/src/cli/cmd/workspace-serve.ts +16 -0
  301. package/src/cli/error.ts +57 -0
  302. package/src/cli/network.ts +55 -0
  303. package/src/cli/remote/index.ts +36 -0
  304. package/src/cli/remote/notifications.ts +104 -0
  305. package/src/cli/remote/qr-renderer.ts +86 -0
  306. package/src/cli/remote/remote-service.ts +757 -0
  307. package/src/cli/remote/session-manager.ts +284 -0
  308. package/src/cli/remote/subagent-hooks.ts +151 -0
  309. package/src/cli/remote/types.ts +121 -0
  310. package/src/cli/ui.ts +96 -0
  311. package/src/cli/upgrade.ts +25 -0
  312. package/src/command/index.ts +174 -0
  313. package/src/command/template/initialize.txt +10 -0
  314. package/src/command/template/review.txt +99 -0
  315. package/src/config/config.ts +1760 -0
  316. package/src/config/markdown.ts +88 -0
  317. package/src/config/migrate-tui-config.ts +155 -0
  318. package/src/config/paths.ts +174 -0
  319. package/src/config/tui-schema.ts +36 -0
  320. package/src/config/tui.ts +209 -0
  321. package/src/connectors/api/base.ts +75 -0
  322. package/src/connectors/api/figma.ts +103 -0
  323. package/src/connectors/api/github.ts +247 -0
  324. package/src/connectors/api/lovable.ts +126 -0
  325. package/src/connectors/api/slack.ts +137 -0
  326. package/src/connectors/auth.ts +68 -0
  327. package/src/connectors/cache.ts +119 -0
  328. package/src/connectors/credentials.ts +81 -0
  329. package/src/connectors/index.ts +202 -0
  330. package/src/connectors/registry.ts +358 -0
  331. package/src/docs/context.ts +120 -0
  332. package/src/docs/library.ts +189 -0
  333. package/src/env/index.ts +26 -0
  334. package/src/file/ignore.ts +83 -0
  335. package/src/file/index.ts +411 -0
  336. package/src/file/ripgrep.ts +402 -0
  337. package/src/file/time.ts +65 -0
  338. package/src/file/watcher.ts +127 -0
  339. package/src/flag/flag.ts +128 -0
  340. package/src/format/formatter.ts +356 -0
  341. package/src/format/index.ts +137 -0
  342. package/src/global/index.ts +57 -0
  343. package/src/id/id.ts +83 -0
  344. package/src/ide/index.ts +76 -0
  345. package/src/index.ts +184 -0
  346. package/src/installation/index.ts +246 -0
  347. package/src/lsp/client.ts +250 -0
  348. package/src/lsp/index.ts +483 -0
  349. package/src/lsp/language.ts +119 -0
  350. package/src/lsp/server.ts +2046 -0
  351. package/src/mcp/auth.ts +121 -0
  352. package/src/mcp/index.ts +860 -0
  353. package/src/mcp/oauth-callback.ts +198 -0
  354. package/src/mcp/oauth-provider.ts +148 -0
  355. package/src/mobile/auth.ts +97 -0
  356. package/src/mobile/github-repo.ts +185 -0
  357. package/src/patch/index.ts +631 -0
  358. package/src/permission/arity.ts +150 -0
  359. package/src/permission/dbedit.ts +236 -0
  360. package/src/permission/index.ts +210 -0
  361. package/src/permission/next.ts +287 -0
  362. package/src/plugin/codex.ts +493 -0
  363. package/src/plugin/copilot.ts +261 -0
  364. package/src/plugin/index.ts +714 -0
  365. package/src/plugin/install.ts +379 -0
  366. package/src/plugin/meta.ts +165 -0
  367. package/src/plugin/shared.ts +188 -0
  368. package/src/project/bootstrap.ts +35 -0
  369. package/src/project/instance.ts +84 -0
  370. package/src/project/project.ts +373 -0
  371. package/src/project/state.ts +66 -0
  372. package/src/project/vcs.ts +76 -0
  373. package/src/prompt/stash-store.ts +93 -0
  374. package/src/provider/auth.ts +147 -0
  375. package/src/provider/models-macro.ts +22 -0
  376. package/src/provider/models.ts +216 -0
  377. package/src/provider/provider.ts +1483 -0
  378. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  379. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  380. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  381. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  382. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  383. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  384. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  385. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  386. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1732 -0
  387. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  388. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  389. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  390. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  391. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  392. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  393. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  394. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  395. package/src/provider/transform.ts +828 -0
  396. package/src/pty/index.ts +241 -0
  397. package/src/question/index.ts +171 -0
  398. package/src/rag/chunk.ts +43 -0
  399. package/src/rag/embed.ts +179 -0
  400. package/src/rag/index.ts +376 -0
  401. package/src/rag/storage.ts +76 -0
  402. package/src/scheduler/index.ts +61 -0
  403. package/src/server/error.ts +36 -0
  404. package/src/server/event.ts +7 -0
  405. package/src/server/mdns.ts +59 -0
  406. package/src/server/routes/chatbot.ts +205 -0
  407. package/src/server/routes/companion.ts +729 -0
  408. package/src/server/routes/config.ts +92 -0
  409. package/src/server/routes/connectors.ts +121 -0
  410. package/src/server/routes/dbedit.ts +76 -0
  411. package/src/server/routes/experimental.ts +210 -0
  412. package/src/server/routes/file.ts +197 -0
  413. package/src/server/routes/global.ts +135 -0
  414. package/src/server/routes/mcp.ts +225 -0
  415. package/src/server/routes/mobile.ts +2044 -0
  416. package/src/server/routes/permission.ts +68 -0
  417. package/src/server/routes/project.ts +82 -0
  418. package/src/server/routes/provider.ts +235 -0
  419. package/src/server/routes/pty.ts +169 -0
  420. package/src/server/routes/question.ts +98 -0
  421. package/src/server/routes/session.ts +968 -0
  422. package/src/server/routes/tui.ts +379 -0
  423. package/src/server/routes/workspace.ts +104 -0
  424. package/src/server/server.ts +761 -0
  425. package/src/server/ssh.ts +207 -0
  426. package/src/session/auth.ts +402 -0
  427. package/src/session/compaction.ts +253 -0
  428. package/src/session/generate.ts +38 -0
  429. package/src/session/index.ts +598 -0
  430. package/src/session/llm.ts +273 -0
  431. package/src/session/message-v2.ts +836 -0
  432. package/src/session/message.ts +189 -0
  433. package/src/session/processor.ts +408 -0
  434. package/src/session/prompt/anthropic-20250930.txt +165 -0
  435. package/src/session/prompt/anthropic.txt +105 -0
  436. package/src/session/prompt/anthropic_spoof.txt +1 -0
  437. package/src/session/prompt/beast.txt +147 -0
  438. package/src/session/prompt/build-switch.txt +5 -0
  439. package/src/session/prompt/codex_header.txt +79 -0
  440. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  441. package/src/session/prompt/gemini.txt +155 -0
  442. package/src/session/prompt/max-steps.txt +16 -0
  443. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  444. package/src/session/prompt/plan.txt +25 -0
  445. package/src/session/prompt/qwen.txt +108 -0
  446. package/src/session/prompt.ts +1942 -0
  447. package/src/session/retry.ts +90 -0
  448. package/src/session/revert.ts +120 -0
  449. package/src/session/stats.ts +404 -0
  450. package/src/session/status.ts +84 -0
  451. package/src/session/summary.ts +184 -0
  452. package/src/session/system.ts +195 -0
  453. package/src/session/toast.tsx +105 -0
  454. package/src/session/todo.ts +258 -0
  455. package/src/session/uninstall.ts +357 -0
  456. package/src/share/share-next.ts +421 -0
  457. package/src/share/share.ts +92 -0
  458. package/src/shell/shell.ts +65 -0
  459. package/src/skill/index.ts +1 -0
  460. package/src/skill/skill.ts +232 -0
  461. package/src/snapshot/index.ts +297 -0
  462. package/src/storage/storage.ts +227 -0
  463. package/src/tool/apply_patch.ts +288 -0
  464. package/src/tool/apply_patch.txt +33 -0
  465. package/src/tool/bash.ts +252 -0
  466. package/src/tool/bash.txt +115 -0
  467. package/src/tool/batch.ts +175 -0
  468. package/src/tool/batch.txt +24 -0
  469. package/src/tool/codesearch.ts +132 -0
  470. package/src/tool/codesearch.txt +12 -0
  471. package/src/tool/context_collect.ts +152 -0
  472. package/src/tool/context_collect.txt +9 -0
  473. package/src/tool/context_diagnostics.ts +81 -0
  474. package/src/tool/context_diagnostics.txt +5 -0
  475. package/src/tool/context_related.ts +117 -0
  476. package/src/tool/context_related.txt +5 -0
  477. package/src/tool/context_search.ts +108 -0
  478. package/src/tool/context_search.txt +8 -0
  479. package/src/tool/db-diff.ts +434 -0
  480. package/src/tool/db-table.txt +15 -0
  481. package/src/tool/docs_add.ts +50 -0
  482. package/src/tool/docs_add.txt +5 -0
  483. package/src/tool/docs_context.ts +56 -0
  484. package/src/tool/docs_context.txt +4 -0
  485. package/src/tool/docs_gap_report.ts +79 -0
  486. package/src/tool/docs_gap_report.txt +7 -0
  487. package/src/tool/docs_load.ts +41 -0
  488. package/src/tool/docs_load.txt +4 -0
  489. package/src/tool/docs_request.ts +129 -0
  490. package/src/tool/docs_request.txt +7 -0
  491. package/src/tool/docs_search.ts +51 -0
  492. package/src/tool/docs_search.txt +6 -0
  493. package/src/tool/docs_unload.ts +38 -0
  494. package/src/tool/docs_unload.txt +5 -0
  495. package/src/tool/edit.ts +614 -0
  496. package/src/tool/edit.txt +10 -0
  497. package/src/tool/external-directory.ts +32 -0
  498. package/src/tool/generate_image.ts +174 -0
  499. package/src/tool/generate_image.txt +12 -0
  500. package/src/tool/glob.ts +79 -0
  501. package/src/tool/glob.txt +6 -0
  502. package/src/tool/grep.ts +153 -0
  503. package/src/tool/grep.txt +8 -0
  504. package/src/tool/invalid.ts +17 -0
  505. package/src/tool/ls.ts +116 -0
  506. package/src/tool/ls.txt +1 -0
  507. package/src/tool/lsp.ts +96 -0
  508. package/src/tool/lsp.txt +19 -0
  509. package/src/tool/memory_search.ts +141 -0
  510. package/src/tool/memory_search.txt +8 -0
  511. package/src/tool/multiedit.ts +46 -0
  512. package/src/tool/multiedit.txt +41 -0
  513. package/src/tool/plan-enter.txt +14 -0
  514. package/src/tool/plan-exit.txt +13 -0
  515. package/src/tool/plan.ts +130 -0
  516. package/src/tool/question.ts +33 -0
  517. package/src/tool/question.txt +10 -0
  518. package/src/tool/rag_index.ts +77 -0
  519. package/src/tool/rag_index.txt +10 -0
  520. package/src/tool/rag_reset.ts +26 -0
  521. package/src/tool/rag_reset.txt +4 -0
  522. package/src/tool/rag_search.ts +62 -0
  523. package/src/tool/rag_search.txt +6 -0
  524. package/src/tool/rag_status.ts +45 -0
  525. package/src/tool/rag_status.txt +4 -0
  526. package/src/tool/read.ts +203 -0
  527. package/src/tool/read.txt +12 -0
  528. package/src/tool/registry.ts +214 -0
  529. package/src/tool/skill.ts +169 -0
  530. package/src/tool/skill.txt +3 -0
  531. package/src/tool/smart_docs.ts +74 -0
  532. package/src/tool/smart_docs.txt +7 -0
  533. package/src/tool/speak/elevenlabs.ts +201 -0
  534. package/src/tool/speak/openrouter.ts +240 -0
  535. package/src/tool/speak/provider.ts +83 -0
  536. package/src/tool/speak.ts +440 -0
  537. package/src/tool/task.ts +194 -0
  538. package/src/tool/task.txt +60 -0
  539. package/src/tool/todo.ts +53 -0
  540. package/src/tool/todoread.txt +14 -0
  541. package/src/tool/todowrite.txt +167 -0
  542. package/src/tool/tool.ts +87 -0
  543. package/src/tool/tree.ts +218 -0
  544. package/src/tool/tree.txt +8 -0
  545. package/src/tool/truncation.ts +106 -0
  546. package/src/tool/use-connector.ts +47 -0
  547. package/src/tool/voice.ts +188 -0
  548. package/src/tool/webfetch.ts +205 -0
  549. package/src/tool/webfetch.txt +13 -0
  550. package/src/tool/websearch.ts +150 -0
  551. package/src/tool/websearch.txt +14 -0
  552. package/src/tool/write.ts +80 -0
  553. package/src/tool/write.txt +8 -0
  554. package/src/util/archive.ts +16 -0
  555. package/src/util/color.ts +19 -0
  556. package/src/util/context.ts +25 -0
  557. package/src/util/defer.ts +12 -0
  558. package/src/util/error.ts +77 -0
  559. package/src/util/eventloop.ts +20 -0
  560. package/src/util/filesystem.ts +125 -0
  561. package/src/util/flock.ts +329 -0
  562. package/src/util/fn.ts +11 -0
  563. package/src/util/format.ts +20 -0
  564. package/src/util/hash.ts +7 -0
  565. package/src/util/iife.ts +3 -0
  566. package/src/util/keybind.ts +103 -0
  567. package/src/util/lazy.ts +18 -0
  568. package/src/util/locale.ts +81 -0
  569. package/src/util/lock.ts +98 -0
  570. package/src/util/log.ts +180 -0
  571. package/src/util/network.ts +9 -0
  572. package/src/util/process.ts +15 -0
  573. package/src/util/queue.ts +32 -0
  574. package/src/util/record.ts +3 -0
  575. package/src/util/rpc.ts +66 -0
  576. package/src/util/scrap.ts +10 -0
  577. package/src/util/signal.ts +12 -0
  578. package/src/util/timeout.ts +14 -0
  579. package/src/util/token.ts +7 -0
  580. package/src/util/wildcard.ts +56 -0
  581. package/src/workspace/adaptors/index.ts +271 -0
  582. package/src/workspace/adaptors/types.ts +14 -0
  583. package/src/workspace/adaptors/worktree.ts +31 -0
  584. package/src/workspace/config.ts +19 -0
  585. package/src/workspace/index.ts +223 -0
  586. package/src/workspace/session-proxy-middleware.ts +97 -0
  587. package/src/workspace/sse.ts +66 -0
  588. package/src/workspace/workspace-context.ts +23 -0
  589. package/src/workspace/workspace-server/routes.ts +33 -0
  590. package/src/workspace/workspace-server/server.ts +47 -0
  591. package/src/worktree/index.ts +487 -0
  592. package/sst-env.d.ts +10 -0
  593. package/test/benchmark.test.ts +121 -0
  594. package/test/build-optimizations.test.ts +124 -0
  595. package/test/id-benchmark.test.ts +132 -0
  596. package/test/optimizations.test.ts +302 -0
  597. package/test/preload.ts +1 -0
  598. package/test/solidjs-benchmark.test.ts +262 -0
  599. package/test/solidjs-optimizations.test.ts +259 -0
  600. package/test/tui-benchmark.test.ts +230 -0
  601. package/test/wildcard-benchmark.test.ts +180 -0
  602. package/tsconfig.json +26 -0
@@ -0,0 +1,714 @@
1
+ import type { Hooks, PluginInput, Plugin as PluginInstance } from "@nikcli-ai/plugin"
2
+ import { createNikcliClient } from "@nikcli-ai/sdk"
3
+ import os from "os"
4
+ import path from "path"
5
+ import { fileURLToPath } from "url"
6
+ import { NamedError } from "@nikcli-ai/util/error"
7
+ import { retry } from "@nikcli-ai/util/retry"
8
+ import { Bus } from "../bus"
9
+ import { BunProc } from "../bun"
10
+ import { Config } from "../config/config"
11
+ import { resolveCredential } from "../connectors/credentials"
12
+ import { Flag } from "../flag/flag"
13
+ import { Instance } from "../project/instance"
14
+ import { Server } from "../server/server"
15
+ import { Session } from "../session"
16
+ import { Log } from "../util/log"
17
+ import { AsyncQueue } from "../util/queue"
18
+ import { withTimeout } from "../util/timeout"
19
+ import { CodexAuthPlugin } from "./codex"
20
+ import { CopilotAuthPlugin } from "./copilot"
21
+ import { readV1Plugin, readPluginId, resolvePluginId, pluginSource } from "./shared"
22
+ import type { PluginModule } from "@nikcli-ai/plugin"
23
+
24
+ type NotifyChannel = "macos" | "slack" | "discord"
25
+ type NotifyPriority = "low" | "normal" | "high" | "critical"
26
+ type NotifyEventKey = "sessionIdle" | "sessionError" | "permissionAsked" | "questionAsked"
27
+
28
+ type NotifyJob = {
29
+ title: string
30
+ body: string
31
+ priority: NotifyPriority
32
+ source: string
33
+ sessionID?: string
34
+ }
35
+
36
+ type MacIcon = {
37
+ value: string
38
+ remote: boolean
39
+ }
40
+
41
+ type PermissionAsked = {
42
+ sessionID: string
43
+ permission: string
44
+ patterns?: string[]
45
+ }
46
+
47
+ type QuestionAsked = {
48
+ sessionID: string
49
+ questions?: Array<{ header: string; question: string }>
50
+ }
51
+
52
+ type SessionErrorEvent = {
53
+ sessionID?: string
54
+ error?: { name?: string; data?: { message?: string } }
55
+ }
56
+
57
+ type SessionStatusEvent = {
58
+ sessionID: string
59
+ status: { type: "idle" | "busy" | "retry"; attempt?: number; message?: string; next?: number }
60
+ }
61
+
62
+ type NotifyConfig = NonNullable<NonNullable<Config.Info["notifications"]>["notify"]>
63
+ type RateState = { window: number; count: number }
64
+ type BreakerState = { fails: number; openUntil: number }
65
+ type ConnectorEntry = NonNullable<Config.Info["connectors"]>[string]
66
+
67
+ const notifyLog = Log.create({ service: "notify" })
68
+
69
+ const DEFAULT_RATE_WINDOW_MS = 60_000
70
+ const DEFAULT_RATE_MAX = 20
71
+ const DEFAULT_RETRY_ATTEMPTS = 3
72
+ const DEFAULT_RETRY_DELAY = 500
73
+ const DEFAULT_RETRY_FACTOR = 2
74
+ const DEFAULT_RETRY_MAX_DELAY = 10_000
75
+ const DEFAULT_TIMEOUT_MS = 10_000
76
+ const DEFAULT_BREAKER_FAILURES = 3
77
+ const DEFAULT_BREAKER_COOLDOWN_MS = 120_000
78
+ const DEFAULT_IDLE_MIN_MS = 30_000
79
+ const DEFAULT_QUIET_SUPPRESS: NotifyChannel[] = ["macos"]
80
+
81
+ const notifyState = Instance.state(() => ({
82
+ queue: new AsyncQueue<NotifyJob>(),
83
+ started: false,
84
+ rate: new Map<NotifyChannel, RateState>(),
85
+ breaker: new Map<NotifyChannel, BreakerState>(),
86
+ busy: new Map<string, number>(),
87
+ config: undefined as Config.Info | undefined,
88
+ }))
89
+
90
+ function isConnector(entry: ConnectorEntry | undefined): entry is Config.Connector {
91
+ return typeof entry === "object" && entry !== null && "type" in entry
92
+ }
93
+
94
+ function flag(value: boolean | undefined, fallback: boolean) {
95
+ if (value === undefined) return fallback
96
+ return value
97
+ }
98
+
99
+ function settings(config: Config.Info | undefined): NotifyConfig {
100
+ return (config?.notifications?.notify ?? {}) as NotifyConfig
101
+ }
102
+
103
+ function eventOn(config: NotifyConfig | undefined, key: NotifyEventKey, fallback: boolean) {
104
+ const events = config?.events
105
+ if (!events) return fallback
106
+ const value = events[key]
107
+ if (value === undefined) return fallback
108
+ return value
109
+ }
110
+
111
+ function rateConfig(config: NotifyConfig | undefined) {
112
+ const input = config?.rateLimit
113
+ const windowMs = input?.windowMs ?? DEFAULT_RATE_WINDOW_MS
114
+ const maxPerWindow = input?.maxPerWindow ?? DEFAULT_RATE_MAX
115
+ return {
116
+ windowMs: Math.max(1000, windowMs),
117
+ maxPerWindow: Math.max(1, maxPerWindow),
118
+ }
119
+ }
120
+
121
+ function retryConfig(config: NotifyConfig | undefined) {
122
+ const input = config?.retry
123
+ return {
124
+ attempts: input?.attempts ?? DEFAULT_RETRY_ATTEMPTS,
125
+ delay: input?.delay ?? DEFAULT_RETRY_DELAY,
126
+ factor: input?.factor ?? DEFAULT_RETRY_FACTOR,
127
+ maxDelay: input?.maxDelay ?? DEFAULT_RETRY_MAX_DELAY,
128
+ timeoutMs: input?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
129
+ }
130
+ }
131
+
132
+ function breakerConfig(config: NotifyConfig | undefined) {
133
+ const input = config?.breaker
134
+ const failures = input?.failures ?? DEFAULT_BREAKER_FAILURES
135
+ const cooldownMs = input?.cooldownMs ?? DEFAULT_BREAKER_COOLDOWN_MS
136
+ return {
137
+ failures: Math.max(1, failures),
138
+ cooldownMs: Math.max(1000, cooldownMs),
139
+ }
140
+ }
141
+
142
+ function idleMin(config: NotifyConfig | undefined) {
143
+ const value = config?.idleMinMs
144
+ if (value && value > 0) return value
145
+ return DEFAULT_IDLE_MIN_MS
146
+ }
147
+
148
+ function preview(items: string[], max: number) {
149
+ if (items.length === 0) return ""
150
+ if (items.length <= max) return items.join(", ")
151
+ const head = items.slice(0, max).join(", ")
152
+ return `${head} +${items.length - max} more`
153
+ }
154
+
155
+ function formatMessage(title: string, body: string) {
156
+ if (!body) return title
157
+ return `${title}\n${body}`
158
+ }
159
+
160
+ function slackText(title: string, body: string) {
161
+ if (!body) return `*${title}*`
162
+ return `*${title}*\n${body}`
163
+ }
164
+
165
+ function span(ms: number) {
166
+ const total = Math.max(1, Math.round(ms / 1000))
167
+ const mins = Math.floor(total / 60)
168
+ const secs = total % 60
169
+ if (mins === 0) return `${secs}s`
170
+ if (secs === 0) return `${mins}m`
171
+ return `${mins}m ${secs}s`
172
+ }
173
+
174
+ function errorText(error: { name?: string; data?: { message?: string } } | undefined) {
175
+ if (!error) return "Unknown error"
176
+ const data = typeof error === "object" ? error.data : undefined
177
+ if (data && typeof data.message === "string" && data.message.length > 0) return data.message
178
+ const message =
179
+ typeof (error as { message?: string }).message === "string" ? (error as { message?: string }).message : ""
180
+ if (message) return message
181
+ if (typeof error.name === "string" && error.name.length > 0) return error.name
182
+ return "Unknown error"
183
+ }
184
+
185
+ function clock(value: string | undefined) {
186
+ if (!value) return undefined
187
+ const parts = value.split(":")
188
+ if (parts.length !== 2) return undefined
189
+ const hour = Number(parts[0])
190
+ const minute = Number(parts[1])
191
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) return undefined
192
+ if (hour < 0 || hour > 23) return undefined
193
+ if (minute < 0 || minute > 59) return undefined
194
+ return hour * 60 + minute
195
+ }
196
+
197
+ function quiet(config: NotifyConfig | undefined, channel: NotifyChannel, now: Date) {
198
+ const hours = config?.quietHours
199
+ if (!hours) return false
200
+ if (hours.enabled === false) return false
201
+ const start = clock(hours.start)
202
+ const end = clock(hours.end)
203
+ if (start === undefined || end === undefined) return false
204
+ const minutes = now.getHours() * 60 + now.getMinutes()
205
+ const active = start < end ? minutes >= start && minutes < end : minutes >= start || minutes < end
206
+ if (!active) return false
207
+ const suppress = hours.suppress ?? DEFAULT_QUIET_SUPPRESS
208
+ return suppress.includes(channel)
209
+ }
210
+
211
+ function slackReady(config: NotifyConfig | undefined) {
212
+ const slack = config?.slack
213
+ if (!slack) return false
214
+ if (slack.enabled === false) return false
215
+ if (!slack.connector) return false
216
+ const channel = slack.channel ?? Flag.NIKCLI_SLACK_CHANNEL
217
+ if (!channel) return false
218
+ return true
219
+ }
220
+
221
+ function discordReady(config: NotifyConfig | undefined) {
222
+ const discord = config?.discord
223
+ if (!discord) return false
224
+ if (discord.enabled === false) return false
225
+ const webhook = discord.webhook ?? Flag.NIKCLI_DISCORD_WEBHOOK_URL
226
+ if (!webhook) return false
227
+ return true
228
+ }
229
+
230
+ function available(config: NotifyConfig | undefined) {
231
+ const list: NotifyChannel[] = []
232
+ if (config?.macos === true && process.platform === "darwin") list.push("macos")
233
+ if (slackReady(config)) list.push("slack")
234
+ if (discordReady(config)) list.push("discord")
235
+ return list
236
+ }
237
+
238
+ function iconPath(value: string) {
239
+ if (!value) return undefined
240
+ if (value === "~") return os.homedir()
241
+ if (value.startsWith("~/")) return path.join(os.homedir(), value.slice(2))
242
+ if (value.startsWith("file://")) return fileURLToPath(value)
243
+ if (path.isAbsolute(value)) return value
244
+ return undefined
245
+ }
246
+
247
+ function iconMac(config: Config.Info | undefined): MacIcon | undefined {
248
+ const icon = config?.notifications?.icon
249
+ if (!icon) return undefined
250
+ const value = icon.url
251
+ if (!value) return undefined
252
+ if (value.startsWith("http://") || value.startsWith("https://")) {
253
+ return { value, remote: true }
254
+ }
255
+ const local = iconPath(value)
256
+ if (!local) return undefined
257
+ return { value: local, remote: false }
258
+ }
259
+
260
+ function route(priority: NotifyPriority, list: NotifyChannel[]) {
261
+ if (list.length === 0) return []
262
+ const preferred: NotifyChannel[] =
263
+ priority === "low" || priority === "normal" ? ["macos"] : ["macos", "slack", "discord"]
264
+ const chosen = preferred.filter((item) => list.includes(item))
265
+ if (chosen.length > 0) return chosen
266
+ return list
267
+ }
268
+
269
+ function rateAllow(
270
+ data: ReturnType<typeof notifyState>,
271
+ config: NotifyConfig | undefined,
272
+ channel: NotifyChannel,
273
+ priority: NotifyPriority,
274
+ now: number,
275
+ ) {
276
+ if (priority === "high" || priority === "critical") return true
277
+ const limit = rateConfig(config)
278
+ const entry = data.rate.get(channel)
279
+ if (!entry) {
280
+ data.rate.set(channel, { window: now, count: 1 })
281
+ return true
282
+ }
283
+ if (now - entry.window >= limit.windowMs) {
284
+ entry.window = now
285
+ entry.count = 1
286
+ return true
287
+ }
288
+ if (entry.count >= limit.maxPerWindow) return false
289
+ entry.count += 1
290
+ return true
291
+ }
292
+
293
+ function breakerOpen(data: ReturnType<typeof notifyState>, channel: NotifyChannel, now: number) {
294
+ const entry = data.breaker.get(channel)
295
+ if (!entry) return false
296
+ if (entry.openUntil === 0) return false
297
+ if (entry.openUntil <= now) {
298
+ data.breaker.delete(channel)
299
+ return false
300
+ }
301
+ return true
302
+ }
303
+
304
+ function breakerReset(data: ReturnType<typeof notifyState>, channel: NotifyChannel) {
305
+ data.breaker.delete(channel)
306
+ }
307
+
308
+ function breakerFail(
309
+ data: ReturnType<typeof notifyState>,
310
+ config: NotifyConfig | undefined,
311
+ channel: NotifyChannel,
312
+ now: number,
313
+ ) {
314
+ const limit = breakerConfig(config)
315
+ const current = data.breaker.get(channel) ?? { fails: 0, openUntil: 0 }
316
+ const fails = current.fails + 1
317
+ const openUntil = fails >= limit.failures ? now + limit.cooldownMs : current.openUntil
318
+ data.breaker.set(channel, { fails, openUntil })
319
+ }
320
+
321
+ function startQueue(data: ReturnType<typeof notifyState>) {
322
+ if (data.started) return
323
+ data.started = true
324
+ void runQueue(data)
325
+ }
326
+
327
+ function enqueue(data: ReturnType<typeof notifyState>, job: NotifyJob) {
328
+ data.queue.push(job)
329
+ }
330
+
331
+ async function loadConfig(data: ReturnType<typeof notifyState>) {
332
+ if (data.config) return data.config
333
+ const config = await Config.get()
334
+ data.config = config
335
+ return config
336
+ }
337
+
338
+ async function runQueue(data: ReturnType<typeof notifyState>) {
339
+ for await (const job of data.queue) {
340
+ await processJob(data, job).catch((error) => {
341
+ const text = error instanceof Error ? error.message : String(error)
342
+ notifyLog.error("notification processing failed", { error: text, source: job.source })
343
+ })
344
+ }
345
+ }
346
+
347
+ async function processJob(data: ReturnType<typeof notifyState>, job: NotifyJob) {
348
+ const config = await loadConfig(data)
349
+ const cfg = settings(config)
350
+ if (!flag(cfg.enabled, true)) return
351
+ const list = available(cfg)
352
+ if (list.length === 0) return
353
+ const targets = route(job.priority, list)
354
+ if (targets.length === 0) return
355
+ const now = new Date()
356
+ const stamp = now.getTime()
357
+ for (const channel of targets) {
358
+ if (quiet(cfg, channel, now)) continue
359
+ if (!rateAllow(data, cfg, channel, job.priority, stamp)) {
360
+ notifyLog.debug("notification rate limited", { channel, source: job.source })
361
+ continue
362
+ }
363
+ if (breakerOpen(data, channel, stamp)) {
364
+ notifyLog.warn("notification circuit open", { channel, source: job.source })
365
+ continue
366
+ }
367
+ await deliver(config, cfg, channel, job)
368
+ .then((sent) => {
369
+ if (!sent) return false
370
+ breakerReset(data, channel)
371
+ return true
372
+ })
373
+ .catch((error) => {
374
+ breakerFail(data, cfg, channel, stamp)
375
+ const text = error instanceof Error ? error.message : String(error)
376
+ notifyLog.error("notification delivery failed", { channel, source: job.source, error: text })
377
+ return false
378
+ })
379
+ }
380
+ }
381
+
382
+ async function deliver(config: Config.Info, cfg: NotifyConfig, channel: NotifyChannel, job: NotifyJob) {
383
+ if (channel === "macos") return macos(config, job.title, job.body)
384
+ if (channel === "slack") return slack(config, cfg, job)
385
+ if (channel === "discord") return discord(config, cfg, job)
386
+ return false
387
+ }
388
+
389
+ async function macos(config: Config.Info, title: string, body: string) {
390
+ if (process.platform !== "darwin") return false
391
+ const icon = iconMac(config)
392
+ const message = body.length ? body : " "
393
+ const notifier = icon && !icon.remote ? Bun.which("terminal-notifier") : null
394
+ if (icon && notifier && !icon.remote) {
395
+ const result = await Bun.$`${notifier} -title ${title} -message ${message} -contentImage ${icon.value}`
396
+ .nothrow()
397
+ .quiet()
398
+ if (result.exitCode === 0) return true
399
+ }
400
+ if (!icon) {
401
+ const safeTitle = title.replace(/"/g, '\\"')
402
+ const safeBody = body.replace(/"/g, '\\"')
403
+ await Bun.$`osascript -e 'display notification "${safeBody}" with title "${safeTitle}"'`.nothrow().quiet()
404
+ return true
405
+ }
406
+ const script = `
407
+ ObjC.import('Cocoa')
408
+ const title = ${JSON.stringify(title)}
409
+ const body = ${JSON.stringify(body)}
410
+ const image = ${JSON.stringify(icon.value)}
411
+ const remote = ${JSON.stringify(icon.remote)}
412
+ const notification = $.NSUserNotification.alloc.init
413
+ notification.title = title
414
+ notification.informativeText = body
415
+ const url = remote ? $.NSURL.URLWithString(image) : $.NSURL.fileURLWithPath(image)
416
+ const img = $.NSImage.alloc.initWithContentsOfURL(url)
417
+ if (!img) throw new Error('icon load failed')
418
+ notification.contentImage = img
419
+ const center = $.NSUserNotificationCenter.defaultUserNotificationCenter
420
+ center.deliverNotification(notification)
421
+ $.NSThread.sleepForTimeInterval(0.2)
422
+ if (!notification.presented) throw new Error('notification not presented')
423
+ `
424
+ const result = await Bun.$`osascript -l JavaScript -e ${script}`.nothrow().quiet()
425
+ if (result.exitCode === 0) return true
426
+ const safeTitle = title.replace(/"/g, '\\"')
427
+ const safeBody = body.replace(/"/g, '\\"')
428
+ await Bun.$`osascript -e 'display notification "${safeBody}" with title "${safeTitle}"'`.nothrow().quiet()
429
+ return true
430
+ }
431
+
432
+ async function slack(config: Config.Info, cfg: NotifyConfig, job: NotifyJob) {
433
+ const slack = cfg.slack
434
+ if (!slack) return false
435
+ if (slack.enabled === false) return false
436
+ if (!slack.connector) return false
437
+ const channel = slack.channel ?? Flag.NIKCLI_SLACK_CHANNEL
438
+ if (!channel) return false
439
+ const connectors = config.connectors ?? {}
440
+ const entry = connectors[slack.connector]
441
+ if (!isConnector(entry)) return false
442
+ if (entry.enabled === false) return false
443
+ if (entry.type !== "slack") return false
444
+ const credential = await resolveCredential(slack.connector, entry)
445
+ if (!credential) return false
446
+ const api = await import("../connectors/api/slack")
447
+ const retryCfg = retryConfig(cfg)
448
+ const text = slackText(job.title, job.body)
449
+ const action = () => withTimeout(api.SlackApi.sendMessage(credential, channel, text), retryCfg.timeoutMs)
450
+ await retry(action, {
451
+ attempts: retryCfg.attempts,
452
+ delay: retryCfg.delay,
453
+ factor: retryCfg.factor,
454
+ maxDelay: retryCfg.maxDelay,
455
+ })
456
+ return true
457
+ }
458
+
459
+ async function discord(config: Config.Info, cfg: NotifyConfig, job: NotifyJob) {
460
+ const discord = cfg.discord
461
+ if (!discord) return false
462
+ if (discord.enabled === false) return false
463
+ const webhook = discord.webhook ?? Flag.NIKCLI_DISCORD_WEBHOOK_URL
464
+ if (!webhook) return false
465
+ const retryCfg = retryConfig(cfg)
466
+ const message = formatMessage(job.title, job.body)
467
+ const action = async () => {
468
+ const response = await fetch(webhook, {
469
+ method: "POST",
470
+ headers: { "Content-Type": "application/json" },
471
+ body: JSON.stringify({ content: message }),
472
+ })
473
+ if (response.ok) return
474
+ const text = await response.text()
475
+ const detail = text ? `Discord webhook failed: ${text}` : `Discord webhook failed: ${response.status}`
476
+ throw new Error(detail)
477
+ }
478
+ await retry(() => withTimeout(action(), retryCfg.timeoutMs), {
479
+ attempts: retryCfg.attempts,
480
+ delay: retryCfg.delay,
481
+ factor: retryCfg.factor,
482
+ maxDelay: retryCfg.maxDelay,
483
+ })
484
+ return true
485
+ }
486
+
487
+ async function handleEvent(data: ReturnType<typeof notifyState>, event: { type: string; properties: unknown }) {
488
+ // Session status events are frequent and can arrive back-to-back (busy -> idle).
489
+ // Track busy timing before any async work so we don't miss idle notifications.
490
+ if (event.type === "session.status") {
491
+ const info = event.properties as SessionStatusEvent
492
+ const status = info.status
493
+ if (status.type === "busy" || status.type === "retry") {
494
+ if (!data.busy.has(info.sessionID)) data.busy.set(info.sessionID, Date.now())
495
+ return
496
+ }
497
+ if (status.type !== "idle") return
498
+
499
+ const start = data.busy.get(info.sessionID)
500
+ data.busy.delete(info.sessionID)
501
+ if (!start) return
502
+
503
+ const config = await loadConfig(data)
504
+ const cfg = settings(config)
505
+ if (!flag(cfg.enabled, true)) return
506
+ if (!eventOn(cfg, "sessionIdle", true)) return
507
+ const elapsed = Date.now() - start
508
+ if (elapsed < idleMin(cfg)) return
509
+ const infoSession = await Session.get(info.sessionID).catch(() => undefined)
510
+ const title =
511
+ infoSession && !Session.isDefaultTitle(infoSession.title)
512
+ ? `Session ready: ${infoSession.title}`
513
+ : "Session ready"
514
+ enqueue(data, {
515
+ title,
516
+ body: `Completed in ${span(elapsed)}`,
517
+ priority: "normal",
518
+ source: event.type,
519
+ sessionID: info.sessionID,
520
+ })
521
+ return
522
+ }
523
+
524
+ const config = await loadConfig(data)
525
+ const cfg = settings(config)
526
+ if (!flag(cfg.enabled, true)) return
527
+ if (event.type === "permission.asked") {
528
+ if (!eventOn(cfg, "permissionAsked", true)) return
529
+ const info = event.properties as PermissionAsked
530
+ const list = preview(info.patterns ?? [], 3)
531
+ const body = list ? `Permission: ${info.permission}\nPatterns: ${list}` : `Permission: ${info.permission}`
532
+ enqueue(data, {
533
+ title: "Permission required",
534
+ body,
535
+ priority: "high",
536
+ source: event.type,
537
+ sessionID: info.sessionID,
538
+ })
539
+ return
540
+ }
541
+ if (event.type === "question.asked") {
542
+ if (!eventOn(cfg, "questionAsked", true)) return
543
+ const info = event.properties as QuestionAsked
544
+ const first = info.questions?.[0]
545
+ const count = info.questions?.length ?? 0
546
+ const extra = count > 1 ? ` (+${count - 1} more)` : ""
547
+ const body = first ? `${first.header}: ${first.question}${extra}` : "Input required"
548
+ enqueue(data, {
549
+ title: "Input required",
550
+ body,
551
+ priority: "high",
552
+ source: event.type,
553
+ sessionID: info.sessionID,
554
+ })
555
+ return
556
+ }
557
+ if (event.type === "session.error") {
558
+ if (!eventOn(cfg, "sessionError", true)) return
559
+ const info = event.properties as SessionErrorEvent
560
+ const body = errorText(info.error)
561
+ enqueue(data, {
562
+ title: "Session error",
563
+ body,
564
+ priority: "critical",
565
+ source: event.type,
566
+ sessionID: info.sessionID,
567
+ })
568
+ return
569
+ }
570
+ }
571
+
572
+ export async function NotifyPlugin(_input: PluginInput): Promise<Hooks> {
573
+ const data = notifyState()
574
+ startQueue(data)
575
+ return {
576
+ async config(cfg) {
577
+ data.config = cfg as Config.Info
578
+ },
579
+ async event(input) {
580
+ await handleEvent(data, input.event)
581
+ },
582
+ }
583
+ }
584
+
585
+ export namespace Plugin {
586
+ const log = Log.create({ service: "plugin" })
587
+
588
+ const BUILTIN = ["@gitlab/nikcli-gitlab-auth@1.3.2"]
589
+
590
+ // Built-in plugins that are directly imported (not installed from npm)
591
+ const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin, NotifyPlugin]
592
+
593
+ const state = Instance.state(async () => {
594
+ const client = createNikcliClient({
595
+ baseUrl: "http://localhost:4096",
596
+ // @ts-ignore - fetch type incompatibility
597
+ fetch: async (...args) => Server.App().fetch(...args),
598
+ })
599
+ const config = await Config.get()
600
+ const hooks: Hooks[] = []
601
+ const input: PluginInput = {
602
+ client,
603
+ project: Instance.project,
604
+ worktree: Instance.worktree,
605
+ directory: Instance.directory,
606
+ serverUrl: Server.url(),
607
+ $: Bun.$,
608
+ }
609
+
610
+ for (const plugin of INTERNAL_PLUGINS) {
611
+ log.info("loading internal plugin", { name: plugin.name })
612
+ const init = await plugin(input)
613
+ hooks.push(init)
614
+ }
615
+
616
+ const plugins = [...(config.plugin ?? [])]
617
+ if (!Flag.NIKCLI_DISABLE_DEFAULT_PLUGINS) {
618
+ plugins.push(...BUILTIN)
619
+ }
620
+
621
+ for (let plugin of plugins) {
622
+ // ignore old codex plugin since it is supported first party now
623
+ if (plugin.includes("nikcli-openai-codex-auth") || plugin.includes("nikcli-copilot-auth")) continue
624
+ const spec = plugin
625
+ log.info("loading plugin", { path: plugin })
626
+ if (!plugin.startsWith("file://")) {
627
+ const lastAtIndex = plugin.lastIndexOf("@")
628
+ const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin
629
+ const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest"
630
+ const builtin = BUILTIN.some((x) => x.startsWith(pkg + "@"))
631
+ plugin = await BunProc.install(pkg, version).catch((err) => {
632
+ if (!builtin) throw err
633
+
634
+ const message = err instanceof Error ? err.message : String(err)
635
+ log.error("failed to install builtin plugin", {
636
+ pkg,
637
+ version,
638
+ error: message,
639
+ })
640
+ Bus.publish(Session.Event.Error, {
641
+ error: new NamedError.Unknown({
642
+ message: `Failed to install built-in plugin ${pkg}@${version}: ${message}`,
643
+ }).toObject(),
644
+ })
645
+
646
+ return ""
647
+ })
648
+ if (!plugin) continue
649
+ }
650
+ const mod = await import(plugin)
651
+ const v1 = readV1Plugin(mod, spec, "server", "detect")
652
+ if (v1) {
653
+ const source = pluginSource(spec)
654
+ const id = readPluginId(v1.id, spec)
655
+ await resolvePluginId(source, spec, plugin, id)
656
+ hooks.push(await (v1 as PluginModule).server!(input, Config.pluginOptions(spec)))
657
+ } else {
658
+ // Prevent duplicate initialization when plugins export the same function
659
+ // as both a named export and default export (e.g., `export const X` and `export default X`).
660
+ // Object.entries(mod) would return both entries pointing to the same function reference.
661
+ const seen = new Set<PluginInstance>()
662
+ for (const [_name, fn] of Object.entries<PluginInstance>(mod)) {
663
+ if (seen.has(fn)) continue
664
+ seen.add(fn)
665
+ const init = await fn(input)
666
+ hooks.push(init)
667
+ }
668
+ }
669
+ }
670
+
671
+ return {
672
+ hooks,
673
+ input,
674
+ }
675
+ })
676
+
677
+ export async function trigger<
678
+ Name extends Exclude<keyof Required<Hooks>, "auth" | "event" | "tool">,
679
+ Input = Parameters<Required<Hooks>[Name]>[0],
680
+ Output = Parameters<Required<Hooks>[Name]>[1],
681
+ >(name: Name, input: Input, output: Output): Promise<Output> {
682
+ if (!name) return output
683
+ for (const hook of await state().then((x) => x.hooks)) {
684
+ const fn = hook[name]
685
+ if (!fn) continue
686
+ // @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
687
+ // give up.
688
+ // try-counter: 2
689
+ await fn(input, output)
690
+ }
691
+ return output
692
+ }
693
+
694
+ export async function list() {
695
+ return state().then((x) => x.hooks)
696
+ }
697
+
698
+ export async function init() {
699
+ const hooks = await state().then((x) => x.hooks)
700
+ const config = await Config.get()
701
+ for (const hook of hooks) {
702
+ // @ts-expect-error this is because we haven't moved plugin to sdk v2
703
+ await hook.config?.(config)
704
+ }
705
+ Bus.subscribeAll(async (input) => {
706
+ const hooks = await state().then((x) => x.hooks)
707
+ for (const hook of hooks) {
708
+ hook["event"]?.({
709
+ event: input,
710
+ })
711
+ }
712
+ })
713
+ }
714
+ }