@polderlabs/bizar 3.23.0 → 4.0.0

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 (346) hide show
  1. package/README.md +72 -2
  2. package/bizar-dash/.bizar/activity.log +3 -0
  3. package/bizar-dash/.bizar/graph/.graphify_analysis.json +1540 -0
  4. package/bizar-dash/.bizar/graph/.graphify_labels.json +1 -0
  5. package/bizar-dash/.bizar/graph/GRAPH_REPORT.md +404 -0
  6. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/0661b816358db84dfdb7f10443db8a61152fd20b3fee950caf8fbf32920a1790.json +1 -0
  7. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/16da06d774142a6e5d74829ad982531286a6193ef798de8f593db029e123ec32.json +1 -0
  8. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/27e5697c01254c69c64c5a33896669073b40afb5ba88375102ae77fa5404136d.json +1 -0
  9. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/532997898027254aa4c0932243dc71d2f55888181232b65974961934997dc85b.json +1 -0
  10. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/84e3451a64c7f99f0cd6a88b0748d7426780486f53ab4935b7476b28c08f8293.json +1 -0
  11. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/88e5ea5f3e78fa69353fe9e6a8d1a05391d54605e25600cb125531eb5a6432ff.json +1 -0
  12. package/bizar-dash/.bizar/graph/cache/stat-index.json +1 -0
  13. package/bizar-dash/.bizar/graph/graph.html +307 -0
  14. package/bizar-dash/.bizar/graph/graph.json +40267 -0
  15. package/bizar-dash/.bizar/graph/manifest.json +762 -0
  16. package/bizar-dash/.graphifyignore +40 -0
  17. package/bizar-dash/.obsidian/README.md +14 -0
  18. package/bizar-dash/.obsidian/daily/2026-06-25.md +9 -0
  19. package/bizar-dash/.obsidian/vault.json +7 -0
  20. package/bizar-dash/.od-skills/web-prototype-e1429c1737/SKILL.md +103 -0
  21. package/bizar-dash/.od-skills/web-prototype-e1429c1737/assets/template.html +338 -0
  22. package/bizar-dash/.od-skills/web-prototype-e1429c1737/example.html +81 -0
  23. package/bizar-dash/.od-skills/web-prototype-e1429c1737/open-design.json +172 -0
  24. package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/checklist.md +44 -0
  25. package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/layouts.md +247 -0
  26. package/bizar-dash/CHANGELOG.md +474 -0
  27. package/bizar-dash/DESIGN.md +136 -0
  28. package/bizar-dash/dist/assets/main-B3RgW6FS.js +331 -0
  29. package/bizar-dash/dist/assets/main-B3RgW6FS.js.map +1 -0
  30. package/bizar-dash/dist/assets/main-DlJ8wgLR.css +1 -0
  31. package/bizar-dash/dist/assets/mobile-CsZQAswA.css +1 -0
  32. package/bizar-dash/dist/assets/mobile-DbxIw8BG.js +2 -0
  33. package/bizar-dash/dist/assets/mobile-DbxIw8BG.js.map +1 -0
  34. package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js +354 -0
  35. package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js.map +1 -0
  36. package/bizar-dash/dist/index.html +40 -0
  37. package/bizar-dash/dist/mobile.html +22 -0
  38. package/bizar-dash/plans/debug-plan/comments.json +1 -0
  39. package/bizar-dash/plans/debug-plan/meta.json +10 -0
  40. package/bizar-dash/plans/debug-plan/plan.json +174 -0
  41. package/bizar-dash/scripts/smoke-bg-retry.mjs +246 -0
  42. package/bizar-dash/src/cli/dashboard-ports.mjs +488 -0
  43. package/bizar-dash/src/cli.mjs +522 -0
  44. package/bizar-dash/src/server/activity-log.mjs +174 -0
  45. package/bizar-dash/src/server/agents-store.mjs +465 -0
  46. package/bizar-dash/src/server/api.mjs +138 -0
  47. package/bizar-dash/src/server/artifact-template.html +810 -0
  48. package/bizar-dash/src/server/artifacts-store.mjs +733 -0
  49. package/bizar-dash/src/server/auth.mjs +381 -0
  50. package/bizar-dash/src/server/background-store.mjs +447 -0
  51. package/bizar-dash/src/server/bg-poller.mjs +305 -0
  52. package/bizar-dash/src/server/bg-retry.mjs +574 -0
  53. package/bizar-dash/src/server/browser.mjs +40 -0
  54. package/bizar-dash/src/server/diagnostics-store.mjs +148 -0
  55. package/bizar-dash/src/server/dialog-poller.mjs +105 -0
  56. package/bizar-dash/src/server/dialog-store.mjs +177 -0
  57. package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +583 -0
  58. package/bizar-dash/src/server/lib/path-safe.mjs +283 -0
  59. package/bizar-dash/src/server/memory-git.mjs +249 -0
  60. package/bizar-dash/src/server/memory-schema.mjs +145 -0
  61. package/bizar-dash/src/server/memory-secrets.mjs +84 -0
  62. package/bizar-dash/src/server/memory-store.mjs +424 -0
  63. package/bizar-dash/src/server/mod-security.mjs +398 -0
  64. package/bizar-dash/src/server/mods-loader.mjs +1067 -0
  65. package/bizar-dash/src/server/notifications-store.mjs +243 -0
  66. package/bizar-dash/src/server/obsidian-store.mjs +318 -0
  67. package/bizar-dash/src/server/opencode-runner.mjs +279 -0
  68. package/bizar-dash/src/server/pair-store.mjs +138 -0
  69. package/bizar-dash/src/server/projects-store.mjs +364 -0
  70. package/bizar-dash/src/server/providers-store.mjs +789 -0
  71. package/bizar-dash/src/server/routes/_shared.mjs +415 -0
  72. package/bizar-dash/src/server/routes/activity.mjs +110 -0
  73. package/bizar-dash/src/server/routes/agents.mjs +134 -0
  74. package/bizar-dash/src/server/routes/artifacts.mjs +279 -0
  75. package/bizar-dash/src/server/routes/auth.mjs +69 -0
  76. package/bizar-dash/src/server/routes/background.mjs +165 -0
  77. package/bizar-dash/src/server/routes/chat.mjs +485 -0
  78. package/bizar-dash/src/server/routes/config.mjs +129 -0
  79. package/bizar-dash/src/server/routes/diagnostics.mjs +50 -0
  80. package/bizar-dash/src/server/routes/dialogs.mjs +86 -0
  81. package/bizar-dash/src/server/routes/fs.mjs +429 -0
  82. package/bizar-dash/src/server/routes/history.mjs +78 -0
  83. package/bizar-dash/src/server/routes/memory.mjs +499 -0
  84. package/bizar-dash/src/server/routes/misc.mjs +89 -0
  85. package/bizar-dash/src/server/routes/mods.mjs +359 -0
  86. package/bizar-dash/src/server/routes/notifications.mjs +54 -0
  87. package/bizar-dash/src/server/routes/obsidian.mjs +239 -0
  88. package/bizar-dash/src/server/routes/opencode-sessions.mjs +55 -0
  89. package/bizar-dash/src/server/routes/overview.mjs +91 -0
  90. package/bizar-dash/src/server/routes/pair.mjs +67 -0
  91. package/bizar-dash/src/server/routes/projects.mjs +175 -0
  92. package/bizar-dash/src/server/routes/providers.mjs +47 -0
  93. package/bizar-dash/src/server/routes/schedules.mjs +104 -0
  94. package/bizar-dash/src/server/routes/settings.mjs +51 -0
  95. package/bizar-dash/src/server/routes/skills.mjs +76 -0
  96. package/bizar-dash/src/server/routes/tasks.mjs +468 -0
  97. package/bizar-dash/src/server/routes/themes.mjs +51 -0
  98. package/bizar-dash/src/server/routes-v2/auth.mjs +59 -0
  99. package/bizar-dash/src/server/routes-v2/events.mjs +107 -0
  100. package/bizar-dash/src/server/routes-v2/health.mjs +21 -0
  101. package/bizar-dash/src/server/routes-v2/index.mjs +90 -0
  102. package/bizar-dash/src/server/routes-v2/sessions.mjs +111 -0
  103. package/bizar-dash/src/server/schedules-runner.mjs +369 -0
  104. package/bizar-dash/src/server/schedules-store.mjs +268 -0
  105. package/bizar-dash/src/server/search-store.mjs +220 -0
  106. package/bizar-dash/src/server/serve-info.mjs +684 -0
  107. package/bizar-dash/src/server/server.mjs +796 -0
  108. package/bizar-dash/src/server/settings-store.mjs +45 -0
  109. package/bizar-dash/src/server/skills-store.mjs +300 -0
  110. package/bizar-dash/src/server/state.mjs +437 -0
  111. package/bizar-dash/src/server/tailscale-store.mjs +113 -0
  112. package/bizar-dash/src/server/task-delegator.mjs +824 -0
  113. package/bizar-dash/src/server/tasks-store.mjs +508 -0
  114. package/bizar-dash/src/server/tui.mjs +923 -0
  115. package/bizar-dash/src/server/update-store.mjs +168 -0
  116. package/bizar-dash/src/server/v2-auth-file.mjs +99 -0
  117. package/bizar-dash/src/server/v2-event-bus.mjs +128 -0
  118. package/bizar-dash/src/server/watcher.mjs +81 -0
  119. package/bizar-dash/src/server/yaml.mjs +238 -0
  120. package/bizar-dash/src/web/App.tsx +718 -0
  121. package/bizar-dash/src/web/MobileApp.tsx +408 -0
  122. package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +95 -0
  123. package/bizar-dash/src/web/components/ArtifactListDialog.tsx +55 -0
  124. package/bizar-dash/src/web/components/ArtifactViewer.tsx +143 -0
  125. package/bizar-dash/src/web/components/AuditDialog.tsx +79 -0
  126. package/bizar-dash/src/web/components/BgStatusBadge.tsx +32 -0
  127. package/bizar-dash/src/web/components/Button.tsx +56 -0
  128. package/bizar-dash/src/web/components/CanvasContextMenu.tsx +68 -0
  129. package/bizar-dash/src/web/components/Card.tsx +40 -0
  130. package/bizar-dash/src/web/components/CollapsibleSection.tsx +72 -0
  131. package/bizar-dash/src/web/components/CommandDialog.tsx +66 -0
  132. package/bizar-dash/src/web/components/EmptyState.tsx +30 -0
  133. package/bizar-dash/src/web/components/FileBrowser.tsx +633 -0
  134. package/bizar-dash/src/web/components/HelpDialog.tsx +55 -0
  135. package/bizar-dash/src/web/components/KillConfirmDialog.tsx +88 -0
  136. package/bizar-dash/src/web/components/Modal.tsx +215 -0
  137. package/bizar-dash/src/web/components/Notifications.tsx +241 -0
  138. package/bizar-dash/src/web/components/SearchModal.tsx +224 -0
  139. package/bizar-dash/src/web/components/Sidebar.tsx +66 -0
  140. package/bizar-dash/src/web/components/Spinner.tsx +19 -0
  141. package/bizar-dash/src/web/components/StatusBadge.tsx +25 -0
  142. package/bizar-dash/src/web/components/Tag.tsx +28 -0
  143. package/bizar-dash/src/web/components/Toast.tsx +150 -0
  144. package/bizar-dash/src/web/components/Topbar.tsx +368 -0
  145. package/bizar-dash/src/web/components/VisualPlanDialog.tsx +58 -0
  146. package/bizar-dash/src/web/components/chat/AgentChip.tsx +68 -0
  147. package/bizar-dash/src/web/components/chat/ChatBubble.tsx +60 -0
  148. package/bizar-dash/src/web/components/chat/ChatThread.tsx +88 -0
  149. package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +50 -0
  150. package/bizar-dash/src/web/components/chat/Composer.tsx +164 -0
  151. package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +54 -0
  152. package/bizar-dash/src/web/components/chat/EmptyState.tsx +21 -0
  153. package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +18 -0
  154. package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +32 -0
  155. package/bizar-dash/src/web/components/chat/InfoPanel.tsx +119 -0
  156. package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +19 -0
  157. package/bizar-dash/src/web/components/chat/MessageBubble.tsx +60 -0
  158. package/bizar-dash/src/web/components/chat/SessionList.tsx +153 -0
  159. package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +9 -0
  160. package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +55 -0
  161. package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +36 -0
  162. package/bizar-dash/src/web/components/chat/index.ts +20 -0
  163. package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +18 -0
  164. package/bizar-dash/src/web/components/chat/useChat.ts +219 -0
  165. package/bizar-dash/src/web/components/chat/useSlashCommands.ts +47 -0
  166. package/bizar-dash/src/web/index.html +37 -0
  167. package/bizar-dash/src/web/lib/api.ts +193 -0
  168. package/bizar-dash/src/web/lib/markdown.tsx +59 -0
  169. package/bizar-dash/src/web/lib/types.ts +668 -0
  170. package/bizar-dash/src/web/lib/utils.ts +114 -0
  171. package/bizar-dash/src/web/lib/ws.ts +176 -0
  172. package/bizar-dash/src/web/main.tsx +34 -0
  173. package/bizar-dash/src/web/mobile/MobileApp.tsx +278 -0
  174. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +38 -0
  175. package/bizar-dash/src/web/mobile/MobileTopbar.tsx +115 -0
  176. package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +182 -0
  177. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +64 -0
  178. package/bizar-dash/src/web/mobile/components/MobileModal.tsx +123 -0
  179. package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +273 -0
  180. package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +374 -0
  181. package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +439 -0
  182. package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +143 -0
  183. package/bizar-dash/src/web/mobile/views/MobileChat.tsx +180 -0
  184. package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +175 -0
  185. package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +191 -0
  186. package/bizar-dash/src/web/mobile/views/MobileMods.tsx +149 -0
  187. package/bizar-dash/src/web/mobile/views/MobileMore.tsx +121 -0
  188. package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +137 -0
  189. package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +26 -0
  190. package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +685 -0
  191. package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +172 -0
  192. package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +388 -0
  193. package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +153 -0
  194. package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +564 -0
  195. package/bizar-dash/src/web/mobile.html +20 -0
  196. package/bizar-dash/src/web/mobile.tsx +13 -0
  197. package/bizar-dash/src/web/styles/chat.css +198 -0
  198. package/bizar-dash/src/web/styles/glyphs.css +960 -0
  199. package/bizar-dash/src/web/styles/main.css +8855 -0
  200. package/bizar-dash/src/web/styles/mobile-chat.css +5 -0
  201. package/bizar-dash/src/web/styles/mobile.css +1960 -0
  202. package/bizar-dash/src/web/views/Activity.tsx +1203 -0
  203. package/bizar-dash/src/web/views/Agents.tsx +701 -0
  204. package/bizar-dash/src/web/views/Artifacts.tsx +1480 -0
  205. package/bizar-dash/src/web/views/BackgroundAgents.tsx +450 -0
  206. package/bizar-dash/src/web/views/Chat.tsx +190 -0
  207. package/bizar-dash/src/web/views/Config.tsx +1603 -0
  208. package/bizar-dash/src/web/views/History.tsx +335 -0
  209. package/bizar-dash/src/web/views/ModView.tsx +207 -0
  210. package/bizar-dash/src/web/views/Mods.tsx +693 -0
  211. package/bizar-dash/src/web/views/Overview.tsx +812 -0
  212. package/bizar-dash/src/web/views/Providers.tsx +302 -0
  213. package/bizar-dash/src/web/views/Schedules.tsx +802 -0
  214. package/bizar-dash/src/web/views/Settings.tsx +1787 -0
  215. package/bizar-dash/src/web/views/Skills.tsx +431 -0
  216. package/bizar-dash/src/web/views/Tasks.tsx +1523 -0
  217. package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +613 -0
  218. package/bizar-dash/src/web/views/glyphs/components.tsx +1098 -0
  219. package/bizar-dash/templates/mod/FORMAT.md +76 -0
  220. package/bizar-dash/templates/mod/hello-mod/README.md +19 -0
  221. package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +8 -0
  222. package/bizar-dash/templates/mod/hello-mod/commands/hello.md +6 -0
  223. package/bizar-dash/templates/mod/hello-mod/mod.json +20 -0
  224. package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +9 -0
  225. package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +10 -0
  226. package/bizar-dash/tests/dashboard-ports.test.mjs +138 -0
  227. package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +90 -0
  228. package/bizar-dash/tests/memory-git.test.mjs +138 -0
  229. package/bizar-dash/tests/memory-schema.test.mjs +198 -0
  230. package/bizar-dash/tests/memory-secrets.test.mjs +121 -0
  231. package/bizar-dash/tests/memory-store.test.mjs +390 -0
  232. package/bizar-dash/tests/memory-sync.test.mjs +141 -0
  233. package/bizar-dash/tests/mod-instructions.node.test.mjs +188 -0
  234. package/bizar-dash/tests/mod-security.test.mjs +273 -0
  235. package/bizar-dash/tests/mod-upgrade.node.test.mjs +357 -0
  236. package/bizar-dash/tests/no-agent-browser.node.test.mjs +98 -0
  237. package/bizar-dash/tests/obsidian-back-compat.test.mjs +199 -0
  238. package/bizar-dash/tests/opencode-runner.test.mjs +172 -0
  239. package/bizar-dash/tests/path-safe.test.mjs +108 -0
  240. package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +333 -0
  241. package/bizar-dash/tests/smoke-v2.mjs +212 -0
  242. package/bizar-dash/tests/strip-thinking.test.mjs +175 -0
  243. package/bizar-dash/tests/submit-feedback.test.mjs +353 -0
  244. package/bizar-dash/tests/tmux-wrap.test.mjs +145 -0
  245. package/bizar-dash/tests/yaml.test.mjs +128 -0
  246. package/bizar-dash/tsconfig.json +23 -0
  247. package/bizar-dash/vite.config.ts +27 -0
  248. package/cli/atomic.mjs +73 -0
  249. package/cli/bin.mjs +43 -46
  250. package/cli/copy.mjs +3 -0
  251. package/cli/dev-link.mjs +17 -1
  252. package/cli/init.mjs +135 -1
  253. package/cli/install.mjs +10 -23
  254. package/cli/memory-constants.mjs +59 -0
  255. package/cli/memory.mjs +757 -0
  256. package/cli/service.mjs +5 -6
  257. package/config/skills/obsidian/SKILL.md +213 -65
  258. package/install.sh +48 -18
  259. package/package.json +49 -14
  260. package/packages/sdk/LICENSE +21 -0
  261. package/packages/sdk/README.md +80 -0
  262. package/packages/sdk/src/client.ts +183 -0
  263. package/packages/sdk/src/errors.ts +120 -0
  264. package/packages/sdk/src/events.ts +153 -0
  265. package/packages/sdk/src/index.ts +63 -0
  266. package/packages/sdk/src/types.ts +176 -0
  267. package/packages/sdk/src/version.ts +4 -0
  268. package/packages/sdk/tests/client.test.ts +217 -0
  269. package/packages/sdk/tests/errors.test.ts +108 -0
  270. package/packages/sdk/tests/events.test.ts +139 -0
  271. package/packages/sdk/tests/fixtures/fetch-mock.ts +152 -0
  272. package/packages/sdk/tests/fixtures/sse-mock.ts +30 -0
  273. package/packages/sdk/tsconfig.json +25 -0
  274. package/packages/sdk/vitest.config.ts +9 -0
  275. package/plugins/bizar/LICENSE +21 -0
  276. package/plugins/bizar/README.md +448 -0
  277. package/plugins/bizar/dist/index.js +29901 -0
  278. package/plugins/bizar/index.ts +1484 -0
  279. package/plugins/bizar/scripts/check-forbidden-imports.sh +33 -0
  280. package/plugins/bizar/src/background-state.ts +551 -0
  281. package/plugins/bizar/src/background.ts +1250 -0
  282. package/plugins/bizar/src/commands-impl.ts +464 -0
  283. package/plugins/bizar/src/commands.ts +1127 -0
  284. package/plugins/bizar/src/dashboard-client.ts +233 -0
  285. package/plugins/bizar/src/event-stream.ts +606 -0
  286. package/plugins/bizar/src/fingerprint.ts +120 -0
  287. package/plugins/bizar/src/handoff.ts +79 -0
  288. package/plugins/bizar/src/http-client.ts +467 -0
  289. package/plugins/bizar/src/key-rotation.ts +218 -0
  290. package/plugins/bizar/src/logger.ts +144 -0
  291. package/plugins/bizar/src/loop.ts +176 -0
  292. package/plugins/bizar/src/opencode-runner.ts +390 -0
  293. package/plugins/bizar/src/options.ts +421 -0
  294. package/plugins/bizar/src/plan-fs.ts +323 -0
  295. package/plugins/bizar/src/reasoning-clean.ts +454 -0
  296. package/plugins/bizar/src/report.ts +178 -0
  297. package/plugins/bizar/src/research-prompt.ts +35 -0
  298. package/plugins/bizar/src/serve-info.ts +228 -0
  299. package/plugins/bizar/src/serve.ts +496 -0
  300. package/plugins/bizar/src/settings.ts +349 -0
  301. package/plugins/bizar/src/state.ts +298 -0
  302. package/plugins/bizar/src/tools/bg-collect.ts +104 -0
  303. package/plugins/bizar/src/tools/bg-get-comments.ts +239 -0
  304. package/plugins/bizar/src/tools/bg-kill.ts +87 -0
  305. package/plugins/bizar/src/tools/bg-spawn.ts +401 -0
  306. package/plugins/bizar/src/tools/bg-status.ts +99 -0
  307. package/plugins/bizar/src/tools/plan-action.ts +767 -0
  308. package/plugins/bizar/src/tools/read-glyph-feedback.ts +170 -0
  309. package/plugins/bizar/src/tools/wait-for-feedback.ts +402 -0
  310. package/plugins/bizar/tests/attach-handler-bug.test.ts +169 -0
  311. package/plugins/bizar/tests/background-state.test.ts +277 -0
  312. package/plugins/bizar/tests/background.test.ts +402 -0
  313. package/plugins/bizar/tests/block.test.ts +195 -0
  314. package/plugins/bizar/tests/canonical-key-order.test.ts +75 -0
  315. package/plugins/bizar/tests/commands-impl.test.ts +442 -0
  316. package/plugins/bizar/tests/commands.test.ts +548 -0
  317. package/plugins/bizar/tests/config.test.ts +122 -0
  318. package/plugins/bizar/tests/dashboard-client.test.ts +159 -0
  319. package/plugins/bizar/tests/dispose.test.ts +336 -0
  320. package/plugins/bizar/tests/event-stream.test.ts +409 -0
  321. package/plugins/bizar/tests/event.test.ts +262 -0
  322. package/plugins/bizar/tests/fingerprint.test.ts +162 -0
  323. package/plugins/bizar/tests/http-client.test.ts +404 -0
  324. package/plugins/bizar/tests/init-helpers.test.ts +203 -0
  325. package/plugins/bizar/tests/integration/slash-command.test.ts +348 -0
  326. package/plugins/bizar/tests/integration/tool-routing.test.ts +314 -0
  327. package/plugins/bizar/tests/key-rotation.test.ts +396 -0
  328. package/plugins/bizar/tests/loop.test.ts +397 -0
  329. package/plugins/bizar/tests/options.test.ts +276 -0
  330. package/plugins/bizar/tests/reasoning-clean.test.ts +422 -0
  331. package/plugins/bizar/tests/serve.test.ts +339 -0
  332. package/plugins/bizar/tests/settings.test.ts +351 -0
  333. package/plugins/bizar/tests/stall-think.test.ts +750 -0
  334. package/plugins/bizar/tests/state.test.ts +276 -0
  335. package/plugins/bizar/tests/tools/bg-collect.test.ts +337 -0
  336. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +485 -0
  337. package/plugins/bizar/tests/tools/bg-kill.test.ts +235 -0
  338. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +147 -0
  339. package/plugins/bizar/tests/tools/bg-spawn.test.ts +311 -0
  340. package/plugins/bizar/tests/tools/bg-status.test.ts +217 -0
  341. package/plugins/bizar/tests/tools/opencode-runner.test.ts +115 -0
  342. package/plugins/bizar/tests/tools/plan-action.test.ts +599 -0
  343. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +253 -0
  344. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +390 -0
  345. package/plugins/bizar/tests/update-deadlock.test.ts +145 -0
  346. package/plugins/bizar/tsconfig.json +29 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * src/server/routes/opencode-sessions.mjs
3
+ *
4
+ * /api/opencode-sessions — reads active sessions from the opencode
5
+ * SQLite database (~/.local/share/opencode/opencode.db) and returns
6
+ * them in a format compatible with the dashboard's session list.
7
+ *
8
+ * Sessions are filtered to non-archived (time_archived IS NULL) and
9
+ * ordered by most recently updated.
10
+ */
11
+ import { Router } from 'express';
12
+ import Database from 'better-sqlite3';
13
+ import { join } from 'node:path';
14
+ import { homedir } from 'node:os';
15
+ import { wrap } from './_shared.mjs';
16
+
17
+ const DB_PATH = join(homedir(), '.local', 'share', 'opencode', 'opencode.db');
18
+
19
+ /**
20
+ * @returns {import('express').Router}
21
+ */
22
+ export function createOpencodeSessionsRouter() {
23
+ const router = Router();
24
+
25
+ router.get('/opencode-sessions', wrap(async (_req, res) => {
26
+ let db;
27
+ try {
28
+ db = new Database(DB_PATH, { readonly: true, fileMustExist: true });
29
+ const rows = db.prepare(`
30
+ SELECT id, title, project_id, time_created, time_updated
31
+ FROM session
32
+ WHERE time_archived IS NULL
33
+ ORDER BY time_updated DESC
34
+ LIMIT 200
35
+ `).all();
36
+ return res.json({
37
+ sessions: rows.map((r) => ({
38
+ id: r.id,
39
+ title: r.title,
40
+ project: r.project_id,
41
+ created: r.time_created,
42
+ updated: r.time_updated,
43
+ mtime: r.time_updated,
44
+ })),
45
+ });
46
+ } catch (err) {
47
+ // DB might not exist yet, or permissions issue — return empty list
48
+ return res.json({ sessions: [], error: err.message });
49
+ } finally {
50
+ if (db) db.close();
51
+ }
52
+ }));
53
+
54
+ return router;
55
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * src/server/routes/overview.mjs
3
+ *
4
+ * /api/snapshot — full snapshot (everything the Overview card needs)
5
+ * /api/overview — overview-only
6
+ * /api/health — { ok: true, ts }
7
+ * /api/restart (POST) — self-respawn
8
+ */
9
+ import { Router } from 'express';
10
+ import { spawn } from 'node:child_process';
11
+ import { existsSync } from 'node:fs';
12
+ import { projectsStore } from '../projects-store.mjs';
13
+ import { tasksStore } from '../tasks-store.mjs';
14
+ import { agentsStore } from '../agents-store.mjs';
15
+ import { schedulesStore } from '../schedules-store.mjs';
16
+ import { providersStore, mcpsStore } from '../providers-store.mjs';
17
+ import { modsLoader } from '../mods-loader.mjs';
18
+ import {
19
+ OPENCODE_JSON,
20
+ readSettings,
21
+ safeReadJSON,
22
+ wrap,
23
+ } from './_shared.mjs';
24
+
25
+ /**
26
+ * @param {object} deps
27
+ * @param {object} deps.state
28
+ * @returns {import('express').Router}
29
+ */
30
+ export function createOverviewRouter({ state }) {
31
+ const router = Router();
32
+
33
+ router.get('/snapshot', wrap(async (_req, res) => {
34
+ res.json(buildSnapshot());
35
+ }));
36
+
37
+ router.get('/overview', wrap(async (_req, res) => {
38
+ res.json(state.getOverview());
39
+ }));
40
+
41
+ router.get('/health', (_req, res) => res.json({ ok: true, ts: Date.now() }));
42
+
43
+ // v3.5.3 — Self-respawn: starts a new dashboard process in the
44
+ // background and exits this one. The BIZAR_AUTO_RESPAWN=1 env var
45
+ // marks it as an automated restart (vs a user-initiated one).
46
+ router.post('/restart', (req, res) => {
47
+ res.json({ restarting: true });
48
+ setTimeout(() => {
49
+ const child = spawn(
50
+ process.execPath,
51
+ [process.argv[1], ...process.argv.slice(2)],
52
+ {
53
+ detached: true,
54
+ stdio: 'ignore',
55
+ env: { ...process.env, BIZAR_AUTO_RESPAWN: '1' },
56
+ },
57
+ );
58
+ child.on('error', (err) => {
59
+ console.error('[dashboard] restart spawn failed:', err.message);
60
+ });
61
+ child.unref();
62
+ process.exit(0);
63
+ }, 500);
64
+ });
65
+
66
+ function buildSnapshot() {
67
+ const cfg = safeReadJSON(OPENCODE_JSON, null);
68
+ const active = projectsStore.active();
69
+ return {
70
+ overview: state.getOverview(),
71
+ agents: agentsStore.list(),
72
+ artifacts: state.getArtifacts(),
73
+ projects: projectsStore.list().projects,
74
+ activeProject: active,
75
+ config: {
76
+ path: OPENCODE_JSON,
77
+ data: cfg,
78
+ raw: cfg === null ? '' : JSON.stringify(cfg, null, 2),
79
+ exists: existsSync(OPENCODE_JSON),
80
+ },
81
+ settings: readSettings(),
82
+ tasks: active ? tasksStore.loadTasks(active.id) : [],
83
+ mods: modsLoader.list(),
84
+ schedules: active ? schedulesStore.list(active.id) : [],
85
+ providers: providersStore.list(),
86
+ mcps: mcpsStore.list(),
87
+ };
88
+ }
89
+
90
+ return router;
91
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * src/server/routes/pair.mjs
3
+ *
4
+ * Companion-app pairing (v3.5.2).
5
+ *
6
+ * /api/pair/start (POST) — mint a short-lived token, return QR payload
7
+ * /api/pair/verify (GET) — companion confirms a token before persisting
8
+ * /api/pair/status (GET) — count of currently active pair tokens
9
+ *
10
+ * The pair token is unrelated to the v3.6.0 dashboard auth token;
11
+ * it's a separate short-lived bearer that's minted on demand for
12
+ * the companion QR scan flow.
13
+ */
14
+ import { Router } from 'express';
15
+ import { pairStore } from '../pair-store.mjs';
16
+ import { wrap } from './_shared.mjs';
17
+
18
+ /**
19
+ * @param {object} deps
20
+ * @param {object} deps.state
21
+ * @param {Function} deps.broadcast
22
+ * @returns {import('express').Router}
23
+ */
24
+ export function createPairRouter({ state, broadcast }) {
25
+ const router = Router();
26
+
27
+ router.post('/pair/start', wrap(async (req, res) => {
28
+ const port = req.app?.get('port') || (req.socket?.server?.address()?.port) || 4321;
29
+ const publicUrl = pairStore.detectPublicUrl(req, port);
30
+ const ttlMs = Math.max(30_000, Math.min(15 * 60 * 1000, Number(req.body?.ttlMs) || 5 * 60 * 1000));
31
+ const entry = pairStore.mint(publicUrl, ttlMs);
32
+ state.appendActivity({ kind: 'pair.start', publicUrl: entry.publicUrl, expiresAt: entry.expiresAt });
33
+ broadcast({ type: 'pair:change', publicUrl: entry.publicUrl, expiresAt: entry.expiresAt });
34
+ res.json({
35
+ token: entry.token,
36
+ qrPayload: entry.qrPayload,
37
+ publicUrl: entry.publicUrl,
38
+ expiresAt: entry.expiresAt,
39
+ });
40
+ }));
41
+
42
+ // Used by the companion right after scanning the QR to confirm the token
43
+ // works before persisting it. Returns { valid: true } on success, 401
44
+ // { error: 'expired' | 'no_token' } on failure.
45
+ router.get('/pair/verify', wrap(async (req, res) => {
46
+ const auth = req.headers.authorization || '';
47
+ if (!auth.startsWith('Bearer ')) {
48
+ res.status(401).json({ error: 'no_token', message: 'missing Authorization: Bearer' });
49
+ return;
50
+ }
51
+ const token = auth.slice(7).trim();
52
+ const entry = pairStore.verify(token);
53
+ if (!entry) {
54
+ res.status(401).json({ error: 'expired', message: 'pair token invalid or expired' });
55
+ return;
56
+ }
57
+ res.json({ valid: true, publicUrl: entry.publicUrl, expiresAt: entry.expiresAt });
58
+ }));
59
+
60
+ // Lightweight introspection — useful for the Settings card and the TUI
61
+ // to show "X tokens active". Always returns { active: <count> }.
62
+ router.get('/pair/status', wrap(async (_req, res) => {
63
+ res.json({ active: pairStore._size() });
64
+ }));
65
+
66
+ return router;
67
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * src/server/routes/projects.mjs
3
+ *
4
+ * /api/projects — list
5
+ * /api/projects (POST) — add by path
6
+ * /api/projects/:id/activate (POST) — set active
7
+ * /api/projects/:id (DELETE) — remove
8
+ * /api/projects/refresh (POST) — re-scan projects.json
9
+ * /api/projects/auto-detect (POST) — auto-add the server's cwd
10
+ * /api/projects/active/tasks — tasks for the active project
11
+ * /api/projects/active/schedules — schedules for the active project
12
+ * /api/projects/active/mods — mods for the active project
13
+ * /api/projects/active/state — state.json for the active project
14
+ */
15
+ import { Router } from 'express';
16
+ import { join } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import { projectsStore } from '../projects-store.mjs';
19
+ import { tasksStore } from '../tasks-store.mjs';
20
+ import { schedulesStore } from '../schedules-store.mjs';
21
+ import { readActiveProjectId, safeReadJSON, wrap, readSettings } from './_shared.mjs';
22
+ import { buildAllowedRootsFromSettings, resolveSafePath } from '../lib/path-safe.mjs';
23
+
24
+ /**
25
+ * @param {object} deps
26
+ * @param {object} deps.state
27
+ * @param {Function} deps.broadcast
28
+ * @param {string} deps.projectRoot
29
+ * @returns {import('express').Router}
30
+ */
31
+ export function createProjectsRouter({ state, broadcast, projectRoot }) {
32
+ const router = Router();
33
+
34
+ router.get('/projects', wrap(async (_req, res) => {
35
+ res.json(projectsStore.list());
36
+ }));
37
+
38
+ router.post('/projects', wrap(async (req, res) => {
39
+ const path = (req.body?.path || '').trim();
40
+ const name = req.body?.name || null;
41
+ if (!path) {
42
+ res.status(400).json({ error: 'bad_request', message: 'path is required' });
43
+ return;
44
+ }
45
+ const project = projectsStore.add(path, name);
46
+ state.appendActivity({ kind: 'project.add', id: project.id, path: project.path });
47
+ res.status(201).json(project);
48
+ }));
49
+
50
+ router.post('/projects/:id/activate', wrap(async (req, res) => {
51
+ const activated = projectsStore.activate(req.params.id);
52
+ if (!activated) {
53
+ res.status(404).json({ error: 'not_found' });
54
+ return;
55
+ }
56
+ state.appendActivity({ kind: 'project.activate', id: activated.id });
57
+ broadcast({ type: 'project:change', project: activated });
58
+ res.json(activated);
59
+ }));
60
+
61
+ router.delete('/projects/:id', wrap(async (req, res) => {
62
+ projectsStore.remove(req.params.id);
63
+ broadcast({ type: 'project:change' });
64
+ res.status(204).end();
65
+ }));
66
+
67
+ router.post('/projects/refresh', wrap(async (_req, res) => {
68
+ res.json(projectsStore.list());
69
+ }));
70
+
71
+ // v3.0.4 — Manual auto-detect trigger for the "Use current directory"
72
+ // button on the Overview. Auto-detects the server's `projectRoot` and
73
+ // returns the (possibly updated) registry. Idempotent.
74
+ router.post('/projects/auto-detect', wrap(async (_req, res) => {
75
+ const detected = projectsStore.autoDetect({ cwd: projectRoot });
76
+ if (detected) {
77
+ state.appendActivity({ kind: 'project.auto-detect', id: detected.id, path: detected.path });
78
+ broadcast({ type: 'project:change', project: detected });
79
+ }
80
+ res.json(projectsStore.list());
81
+ }));
82
+
83
+ // v3.6.0 — Scan a configured `dashboard.projectsDirectory` for
84
+ // project roots and add any newly-detected ones to the registry.
85
+ // Idempotent: already-registered paths are skipped silently.
86
+ //
87
+ // v3.11.0 — The allow-list now also includes every entry in
88
+ // `dashboard.allowedRoots` (in addition to home and
89
+ // `projectsDirectory`). The configured `projectsDirectory` is
90
+ // re-validated against the full allow-list before scanning — a
91
+ // tampered settings file can't widen the boundary.
92
+ router.post('/projects/scan', wrap(async (_req, res) => {
93
+ const settings = readSettings().data || {};
94
+ const configured = settings.dashboard?.projectsDirectory;
95
+ if (typeof configured !== 'string' || !configured.trim()) {
96
+ res.status(400).json({
97
+ error: 'bad_request',
98
+ message: 'dashboard.projectsDirectory is not configured',
99
+ });
100
+ return;
101
+ }
102
+ const allowedRoots = buildAllowedRootsFromSettings({ settings, home: homedir() });
103
+ const safeRoot = resolveSafePath(configured, allowedRoots);
104
+ if (!safeRoot) {
105
+ res.status(403).json({
106
+ error: 'forbidden',
107
+ message: 'projectsDirectory is outside the allowed roots',
108
+ });
109
+ return;
110
+ }
111
+ let result;
112
+ try {
113
+ result = await projectsStore.scanDirectory(safeRoot);
114
+ } catch (err) {
115
+ res.status(500).json({ error: 'scan_failed', message: err?.message || String(err) });
116
+ return;
117
+ }
118
+ if (result.error) {
119
+ // scanDirectory returns `{ error }` on a soft failure (bad
120
+ // root, etc.). Surface as 400 so the client can show it.
121
+ res.status(400).json({ error: 'scan_failed', message: result.error });
122
+ return;
123
+ }
124
+ // Broadcast each newly added project so connected clients refresh.
125
+ for (const p of result.added) {
126
+ state.appendActivity({ kind: 'project.scan', id: p.id, path: p.path });
127
+ broadcast({ type: 'project:change', kind: 'added', project: p });
128
+ }
129
+ res.json({
130
+ added: result.added,
131
+ skipped: result.skipped,
132
+ scanned: result.scanned,
133
+ });
134
+ }));
135
+
136
+ // ── /api/projects/active/<entity> ──────────────────────────────────────
137
+ router.get('/projects/active/tasks', wrap(async (_req, res) => {
138
+ const active = projectsStore.active();
139
+ if (!active) {
140
+ res.json({ tasks: [], projectId: null });
141
+ return;
142
+ }
143
+ res.json({ tasks: tasksStore.loadTasks(active.id), projectId: active.id });
144
+ }));
145
+
146
+ router.get('/projects/active/schedules', wrap(async (_req, res) => {
147
+ const active = projectsStore.active();
148
+ if (!active) {
149
+ res.json({ schedules: [], projectId: null });
150
+ return;
151
+ }
152
+ res.json({ schedules: schedulesStore.list(active.id), projectId: active.id });
153
+ }));
154
+
155
+ router.get('/projects/active/mods', wrap(async (_req, res) => {
156
+ const active = projectsStore.active();
157
+ if (!active) {
158
+ res.json({ mods: [], projectId: null });
159
+ return;
160
+ }
161
+ res.json({ mods: [], projectId: active.id });
162
+ }));
163
+
164
+ router.get('/projects/active/state', wrap(async (_req, res) => {
165
+ const active = projectsStore.active();
166
+ if (!active) {
167
+ res.json({ state: {}, projectId: null });
168
+ return;
169
+ }
170
+ const stateFile = join(projectsStore.projectDir(active.id), 'state.json');
171
+ res.json({ state: safeReadJSON(stateFile, {}) || {}, projectId: active.id });
172
+ }));
173
+
174
+ return router;
175
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * src/server/routes/providers.mjs
3
+ *
4
+ * /api/providers — aggregated list (opencode.json + agent frontmatter + serve HTTP)
5
+ * /api/providers/active — current default provider + model
6
+ *
7
+ * The dashboard's Overview card uses these to show what the user
8
+ * can run. The /api/config/providers surface (which CRUDs the
9
+ * registry store) lives in config.mjs.
10
+ */
11
+ import { Router } from 'express';
12
+ import { providersStore } from '../providers-store.mjs';
13
+ import { wrap } from './_shared.mjs';
14
+
15
+ /**
16
+ * @returns {import('express').Router}
17
+ */
18
+ export function createProvidersRouter() {
19
+ const router = Router();
20
+
21
+ // Surface-everything endpoint the dashboard uses to populate the
22
+ // Providers card on the Overview tab. Reads from opencode.json +
23
+ // agent frontmatter + (best-effort) the running opencode serve HTTP
24
+ // API, so the list is non-empty even on installs that don't declare
25
+ // a top-level `provider` key in opencode.json.
26
+ router.get('/providers', wrap(async (_req, res) => {
27
+ const providers = await providersStore.listAll();
28
+ res.json({ providers, count: providers.length });
29
+ }));
30
+
31
+ // Active default provider + model. null when nothing is configured.
32
+ router.get('/providers/active', wrap(async (_req, res) => {
33
+ const active = await providersStore.getActive();
34
+ res.json(active || { providerId: null, modelId: null, source: null });
35
+ }));
36
+
37
+ // v3.16.0 — Auto-detect providers from env + config. Best-effort;
38
+ // a timeout/failure on one provider does not block the others.
39
+ // Query: ?probe=0 to skip the /models probe (faster, less informative).
40
+ router.get('/providers/auto-detect', wrap(async (req, res) => {
41
+ const probe = req.query.probe !== '0';
42
+ const results = await providersStore.autoDetect({ probe });
43
+ res.json({ providers: results, count: results.length });
44
+ }));
45
+
46
+ return router;
47
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * src/server/routes/schedules.mjs
3
+ *
4
+ * /api/schedules — list
5
+ * /api/schedules (POST) — add
6
+ * /api/schedules/:id (PUT) — full update
7
+ * /api/schedules/:id (PATCH) — partial update (toggle fields)
8
+ * /api/schedules/:id/run (POST) — run once now
9
+ * /api/schedules/:id/trigger (POST) — alias for /run (mobile uses this)
10
+ * /api/schedules/:id (DELETE) — remove
11
+ *
12
+ * PATCH exists because the desktop UI's "toggle enabled" and the mobile
13
+ * UI's edit flow both want to send partial payloads without losing the
14
+ * rest of the schedule.
15
+ */
16
+ import { Router } from 'express';
17
+ import { schedulesStore } from '../schedules-store.mjs';
18
+ import { schedulesRunner } from '../schedules-runner.mjs';
19
+ import { readActiveProjectId, wrap } from './_shared.mjs';
20
+
21
+ /**
22
+ * @param {object} deps
23
+ * @param {Function} deps.broadcast
24
+ * @returns {import('express').Router}
25
+ */
26
+ export function createSchedulesRouter({ broadcast }) {
27
+ const router = Router();
28
+
29
+ router.get('/schedules', wrap(async (req, res) => {
30
+ const projectId = req.query.projectId || readActiveProjectId();
31
+ res.json(schedulesStore.list(projectId || 'default'));
32
+ }));
33
+
34
+ router.post('/schedules', wrap(async (req, res) => {
35
+ const projectId = req.body?.projectId || readActiveProjectId() || 'default';
36
+ const sched = schedulesStore.add(projectId, req.body || {});
37
+ broadcast({ type: 'schedules:change' });
38
+ res.status(201).json(sched);
39
+ }));
40
+
41
+ router.put('/schedules/:id', wrap(async (req, res) => {
42
+ const projectId = req.body?.projectId || readActiveProjectId() || 'default';
43
+ const sched = schedulesStore.update(projectId, req.params.id, req.body || {});
44
+ if (!sched) {
45
+ res.status(404).json({ error: 'not_found' });
46
+ return;
47
+ }
48
+ broadcast({ type: 'schedules:change' });
49
+ res.json(sched);
50
+ }));
51
+
52
+ // v3.9.0 — Partial update. The mobile UI PATCHes a single field at a
53
+ // time (toggle enabled, rename, etc.) so a PUT that required the full
54
+ // shape was impractical. PATCH is semantically correct here.
55
+ router.patch('/schedules/:id', wrap(async (req, res) => {
56
+ const projectId = req.body?.projectId || readActiveProjectId() || 'default';
57
+ const sched = schedulesStore.update(projectId, req.params.id, req.body || {});
58
+ if (!sched) {
59
+ res.status(404).json({ error: 'not_found' });
60
+ return;
61
+ }
62
+ broadcast({ type: 'schedules:change' });
63
+ res.json(sched);
64
+ }));
65
+
66
+ router.post('/schedules/:id/run', wrap(async (req, res) => {
67
+ const projectId = req.body?.projectId || readActiveProjectId() || 'default';
68
+ const sched = schedulesStore.get(projectId, req.params.id);
69
+ if (!sched) {
70
+ res.status(404).json({ error: 'not_found' });
71
+ return;
72
+ }
73
+ const result = await schedulesRunner.runOne(projectId, sched);
74
+ broadcast({ type: 'schedules:change' });
75
+ res.json(result);
76
+ }));
77
+
78
+ // v3.9.0 — Mobile uses /trigger; keep it as an alias of /run so
79
+ // desktop and mobile share the same handler.
80
+ router.post('/schedules/:id/trigger', wrap(async (req, res) => {
81
+ const projectId = req.body?.projectId || readActiveProjectId() || 'default';
82
+ const sched = schedulesStore.get(projectId, req.params.id);
83
+ if (!sched) {
84
+ res.status(404).json({ error: 'not_found' });
85
+ return;
86
+ }
87
+ const result = await schedulesRunner.runOne(projectId, sched);
88
+ broadcast({ type: 'schedules:change' });
89
+ res.json(result);
90
+ }));
91
+
92
+ router.delete('/schedules/:id', wrap(async (req, res) => {
93
+ const projectId = req.query.projectId || readActiveProjectId() || 'default';
94
+ const ok = schedulesStore.remove(projectId, req.params.id);
95
+ if (!ok) {
96
+ res.status(404).json({ error: 'not_found' });
97
+ return;
98
+ }
99
+ broadcast({ type: 'schedules:change' });
100
+ res.status(204).end();
101
+ }));
102
+
103
+ return router;
104
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * src/server/routes/settings.mjs
3
+ *
4
+ * /api/settings — read settings.json
5
+ * /api/settings (PUT) — write (merged with defaults)
6
+ * /api/settings/reset (POST) — reset to defaults
7
+ */
8
+ import { Router } from 'express';
9
+ import { DEFAULT_SETTINGS, readSettings, writeSettings, wrap } from './_shared.mjs';
10
+ import { readPluginOptions, writePluginOptions } from '../settings-store.mjs';
11
+
12
+ /**
13
+ * @param {object} deps
14
+ * @param {object} deps.state
15
+ * @param {Function} deps.broadcast
16
+ * @returns {import('express').Router}
17
+ */
18
+ export function createSettingsRouter({ state, broadcast }) {
19
+ const router = Router();
20
+
21
+ router.get('/settings', wrap(async (_req, res) => {
22
+ res.json(readSettings());
23
+ }));
24
+
25
+ router.put('/settings', wrap(async (req, res) => {
26
+ const updated = writeSettings(req.body || {});
27
+ state.appendActivity({ kind: 'settings.update' });
28
+ broadcast({ type: 'settings:change', settings: updated.data });
29
+ res.json(updated);
30
+ }));
31
+
32
+ router.post('/settings/reset', wrap(async (_req, res) => {
33
+ const updated = writeSettings(DEFAULT_SETTINGS);
34
+ broadcast({ type: 'settings:change', settings: updated.data });
35
+ res.json(updated);
36
+ }));
37
+
38
+ // --- Plugin-options persistence for the Background Agents card ----------
39
+
40
+ router.get('/settings/plugin-options', wrap(async (_req, res) => {
41
+ res.json(readPluginOptions() || {});
42
+ }));
43
+
44
+ router.put('/settings/plugin-options', wrap(async (req, res) => {
45
+ const result = writePluginOptions(req.body || {});
46
+ if (broadcast) broadcast({ type: 'settings:plugin-options:changed' });
47
+ res.json(result);
48
+ }));
49
+
50
+ return router;
51
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * src/server/routes/skills.mjs
3
+ *
4
+ * /api/skills — list (?category=foo to filter)
5
+ * /api/skills/search — fuzzy search by name/description
6
+ * /api/skills/install (POST) — install by name or source URL
7
+ * /api/skills/:id/disable (POST) — disable
8
+ * /api/skills/:id/enable (POST) — enable
9
+ */
10
+ import { Router } from 'express';
11
+ import { skillsStore } from '../skills-store.mjs';
12
+ import { wrap } from './_shared.mjs';
13
+
14
+ /**
15
+ * @param {object} deps
16
+ * @param {Function} deps.broadcast
17
+ * @returns {import('express').Router}
18
+ */
19
+ export function createSkillsRouter({ broadcast }) {
20
+ const router = Router();
21
+
22
+ // v3.3.0 — `?category=foo` filters the list server-side. The
23
+ // frontend uses this to power the collapsible categories view
24
+ // (the old "all in one list" mode is now the default "Show all"
25
+ // toggle in the UI).
26
+ router.get('/skills', wrap(async (req, res) => {
27
+ const skills = await skillsStore.list();
28
+ let out = skills;
29
+ if (req.query.category) {
30
+ const wanted = String(req.query.category).toLowerCase();
31
+ if (wanted !== 'all') {
32
+ out = skills.filter((s) => (s.category || '').toLowerCase() === wanted);
33
+ }
34
+ }
35
+ res.json({ skills: out, categories: skillsStore.CATEGORIES, total: skills.length });
36
+ }));
37
+
38
+ router.get('/skills/search', wrap(async (req, res) => {
39
+ const q = (req.query.q || '').toString();
40
+ const results = await skillsStore.search(q);
41
+ res.json({ results, query: q, categories: skillsStore.CATEGORIES });
42
+ }));
43
+
44
+ router.post('/skills/install', wrap(async (req, res) => {
45
+ const { name, source } = req.body || {};
46
+ if (!name && !source) {
47
+ res.status(400).json({ error: 'bad_request', message: 'name or source is required' });
48
+ return;
49
+ }
50
+ try {
51
+ const result = await skillsStore.install(name, source);
52
+ if (!result.ok) {
53
+ res.status(502).json({ error: 'install_failed', message: result.error || 'skills CLI install failed' });
54
+ return;
55
+ }
56
+ broadcast({ type: 'skills:change' });
57
+ res.status(202).json(result);
58
+ } catch (err) {
59
+ res.status(500).json({ error: 'install_failed', message: err.message });
60
+ }
61
+ }));
62
+
63
+ router.post('/skills/:id/disable', wrap(async (req, res) => {
64
+ const out = skillsStore.disable(req.params.id);
65
+ broadcast({ type: 'skills:change' });
66
+ res.json(out);
67
+ }));
68
+
69
+ router.post('/skills/:id/enable', wrap(async (req, res) => {
70
+ const out = skillsStore.enable(req.params.id);
71
+ broadcast({ type: 'skills:change' });
72
+ res.json(out);
73
+ }));
74
+
75
+ return router;
76
+ }