@polderlabs/bizar 3.23.1 → 4.2.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 (361) hide show
  1. package/README.md +83 -16
  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 +329 -0
  60. package/bizar-dash/src/server/memory-lightrag.mjs +767 -0
  61. package/bizar-dash/src/server/memory-schema.mjs +145 -0
  62. package/bizar-dash/src/server/memory-secrets.mjs +84 -0
  63. package/bizar-dash/src/server/memory-store.mjs +471 -0
  64. package/bizar-dash/src/server/mod-security.mjs +398 -0
  65. package/bizar-dash/src/server/mods-loader.mjs +1067 -0
  66. package/bizar-dash/src/server/notifications-store.mjs +243 -0
  67. package/bizar-dash/src/server/obsidian-store.mjs +318 -0
  68. package/bizar-dash/src/server/opencode-runner.mjs +279 -0
  69. package/bizar-dash/src/server/pair-store.mjs +138 -0
  70. package/bizar-dash/src/server/projects-store.mjs +364 -0
  71. package/bizar-dash/src/server/providers-store.mjs +789 -0
  72. package/bizar-dash/src/server/routes/_shared.mjs +415 -0
  73. package/bizar-dash/src/server/routes/activity.mjs +110 -0
  74. package/bizar-dash/src/server/routes/agents.mjs +134 -0
  75. package/bizar-dash/src/server/routes/artifacts.mjs +279 -0
  76. package/bizar-dash/src/server/routes/auth.mjs +69 -0
  77. package/bizar-dash/src/server/routes/background.mjs +165 -0
  78. package/bizar-dash/src/server/routes/chat.mjs +485 -0
  79. package/bizar-dash/src/server/routes/config.mjs +129 -0
  80. package/bizar-dash/src/server/routes/diagnostics.mjs +50 -0
  81. package/bizar-dash/src/server/routes/dialogs.mjs +86 -0
  82. package/bizar-dash/src/server/routes/fs.mjs +429 -0
  83. package/bizar-dash/src/server/routes/history.mjs +78 -0
  84. package/bizar-dash/src/server/routes/memory.mjs +658 -0
  85. package/bizar-dash/src/server/routes/misc.mjs +89 -0
  86. package/bizar-dash/src/server/routes/mods.mjs +359 -0
  87. package/bizar-dash/src/server/routes/notifications.mjs +54 -0
  88. package/bizar-dash/src/server/routes/obsidian.mjs +239 -0
  89. package/bizar-dash/src/server/routes/opencode-sessions.mjs +55 -0
  90. package/bizar-dash/src/server/routes/overview.mjs +91 -0
  91. package/bizar-dash/src/server/routes/pair.mjs +67 -0
  92. package/bizar-dash/src/server/routes/projects.mjs +175 -0
  93. package/bizar-dash/src/server/routes/providers.mjs +47 -0
  94. package/bizar-dash/src/server/routes/schedules.mjs +104 -0
  95. package/bizar-dash/src/server/routes/settings.mjs +51 -0
  96. package/bizar-dash/src/server/routes/skills.mjs +76 -0
  97. package/bizar-dash/src/server/routes/tasks.mjs +468 -0
  98. package/bizar-dash/src/server/routes/themes.mjs +51 -0
  99. package/bizar-dash/src/server/routes-v2/auth.mjs +59 -0
  100. package/bizar-dash/src/server/routes-v2/events.mjs +107 -0
  101. package/bizar-dash/src/server/routes-v2/health.mjs +21 -0
  102. package/bizar-dash/src/server/routes-v2/index.mjs +90 -0
  103. package/bizar-dash/src/server/routes-v2/sessions.mjs +111 -0
  104. package/bizar-dash/src/server/schedules-runner.mjs +369 -0
  105. package/bizar-dash/src/server/schedules-store.mjs +268 -0
  106. package/bizar-dash/src/server/search-store.mjs +220 -0
  107. package/bizar-dash/src/server/serve-info.mjs +684 -0
  108. package/bizar-dash/src/server/server.mjs +796 -0
  109. package/bizar-dash/src/server/settings-store.mjs +45 -0
  110. package/bizar-dash/src/server/skills-store.mjs +300 -0
  111. package/bizar-dash/src/server/state.mjs +437 -0
  112. package/bizar-dash/src/server/tailscale-store.mjs +113 -0
  113. package/bizar-dash/src/server/task-delegator.mjs +824 -0
  114. package/bizar-dash/src/server/tasks-store.mjs +508 -0
  115. package/bizar-dash/src/server/tui.mjs +923 -0
  116. package/bizar-dash/src/server/update-store.mjs +168 -0
  117. package/bizar-dash/src/server/v2-auth-file.mjs +99 -0
  118. package/bizar-dash/src/server/v2-event-bus.mjs +128 -0
  119. package/bizar-dash/src/server/watcher.mjs +81 -0
  120. package/bizar-dash/src/server/yaml.mjs +238 -0
  121. package/bizar-dash/src/web/App.tsx +718 -0
  122. package/bizar-dash/src/web/MobileApp.tsx +408 -0
  123. package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +95 -0
  124. package/bizar-dash/src/web/components/ArtifactListDialog.tsx +55 -0
  125. package/bizar-dash/src/web/components/ArtifactViewer.tsx +143 -0
  126. package/bizar-dash/src/web/components/AuditDialog.tsx +79 -0
  127. package/bizar-dash/src/web/components/BgStatusBadge.tsx +32 -0
  128. package/bizar-dash/src/web/components/Button.tsx +56 -0
  129. package/bizar-dash/src/web/components/CanvasContextMenu.tsx +68 -0
  130. package/bizar-dash/src/web/components/Card.tsx +40 -0
  131. package/bizar-dash/src/web/components/CollapsibleSection.tsx +72 -0
  132. package/bizar-dash/src/web/components/CommandDialog.tsx +66 -0
  133. package/bizar-dash/src/web/components/EmptyState.tsx +30 -0
  134. package/bizar-dash/src/web/components/FileBrowser.tsx +633 -0
  135. package/bizar-dash/src/web/components/HelpDialog.tsx +55 -0
  136. package/bizar-dash/src/web/components/KillConfirmDialog.tsx +88 -0
  137. package/bizar-dash/src/web/components/Modal.tsx +215 -0
  138. package/bizar-dash/src/web/components/Notifications.tsx +241 -0
  139. package/bizar-dash/src/web/components/SearchModal.tsx +224 -0
  140. package/bizar-dash/src/web/components/Sidebar.tsx +66 -0
  141. package/bizar-dash/src/web/components/Spinner.tsx +19 -0
  142. package/bizar-dash/src/web/components/StatusBadge.tsx +25 -0
  143. package/bizar-dash/src/web/components/Tag.tsx +28 -0
  144. package/bizar-dash/src/web/components/Toast.tsx +150 -0
  145. package/bizar-dash/src/web/components/Topbar.tsx +368 -0
  146. package/bizar-dash/src/web/components/VisualPlanDialog.tsx +58 -0
  147. package/bizar-dash/src/web/components/chat/AgentChip.tsx +68 -0
  148. package/bizar-dash/src/web/components/chat/ChatBubble.tsx +60 -0
  149. package/bizar-dash/src/web/components/chat/ChatThread.tsx +88 -0
  150. package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +50 -0
  151. package/bizar-dash/src/web/components/chat/Composer.tsx +164 -0
  152. package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +54 -0
  153. package/bizar-dash/src/web/components/chat/EmptyState.tsx +21 -0
  154. package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +18 -0
  155. package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +32 -0
  156. package/bizar-dash/src/web/components/chat/InfoPanel.tsx +119 -0
  157. package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +19 -0
  158. package/bizar-dash/src/web/components/chat/MessageBubble.tsx +60 -0
  159. package/bizar-dash/src/web/components/chat/SessionList.tsx +153 -0
  160. package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +9 -0
  161. package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +55 -0
  162. package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +36 -0
  163. package/bizar-dash/src/web/components/chat/index.ts +20 -0
  164. package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +18 -0
  165. package/bizar-dash/src/web/components/chat/useChat.ts +219 -0
  166. package/bizar-dash/src/web/components/chat/useSlashCommands.ts +47 -0
  167. package/bizar-dash/src/web/index.html +37 -0
  168. package/bizar-dash/src/web/lib/api.ts +193 -0
  169. package/bizar-dash/src/web/lib/markdown.tsx +59 -0
  170. package/bizar-dash/src/web/lib/types.ts +668 -0
  171. package/bizar-dash/src/web/lib/utils.ts +114 -0
  172. package/bizar-dash/src/web/lib/ws.ts +176 -0
  173. package/bizar-dash/src/web/main.tsx +34 -0
  174. package/bizar-dash/src/web/mobile/MobileApp.tsx +278 -0
  175. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +38 -0
  176. package/bizar-dash/src/web/mobile/MobileTopbar.tsx +115 -0
  177. package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +182 -0
  178. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +64 -0
  179. package/bizar-dash/src/web/mobile/components/MobileModal.tsx +123 -0
  180. package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +273 -0
  181. package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +374 -0
  182. package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +439 -0
  183. package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +143 -0
  184. package/bizar-dash/src/web/mobile/views/MobileChat.tsx +180 -0
  185. package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +175 -0
  186. package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +191 -0
  187. package/bizar-dash/src/web/mobile/views/MobileMods.tsx +149 -0
  188. package/bizar-dash/src/web/mobile/views/MobileMore.tsx +121 -0
  189. package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +137 -0
  190. package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +26 -0
  191. package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +685 -0
  192. package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +172 -0
  193. package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +388 -0
  194. package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +153 -0
  195. package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +564 -0
  196. package/bizar-dash/src/web/mobile.html +20 -0
  197. package/bizar-dash/src/web/mobile.tsx +13 -0
  198. package/bizar-dash/src/web/styles/chat.css +198 -0
  199. package/bizar-dash/src/web/styles/glyphs.css +960 -0
  200. package/bizar-dash/src/web/styles/main.css +8855 -0
  201. package/bizar-dash/src/web/styles/mobile-chat.css +5 -0
  202. package/bizar-dash/src/web/styles/mobile.css +1960 -0
  203. package/bizar-dash/src/web/views/Activity.tsx +1203 -0
  204. package/bizar-dash/src/web/views/Agents.tsx +701 -0
  205. package/bizar-dash/src/web/views/Artifacts.tsx +1480 -0
  206. package/bizar-dash/src/web/views/BackgroundAgents.tsx +450 -0
  207. package/bizar-dash/src/web/views/Chat.tsx +190 -0
  208. package/bizar-dash/src/web/views/Config.tsx +2063 -0
  209. package/bizar-dash/src/web/views/History.tsx +335 -0
  210. package/bizar-dash/src/web/views/ModView.tsx +207 -0
  211. package/bizar-dash/src/web/views/Mods.tsx +693 -0
  212. package/bizar-dash/src/web/views/Overview.tsx +812 -0
  213. package/bizar-dash/src/web/views/Providers.tsx +302 -0
  214. package/bizar-dash/src/web/views/Schedules.tsx +802 -0
  215. package/bizar-dash/src/web/views/Settings.tsx +1787 -0
  216. package/bizar-dash/src/web/views/Skills.tsx +431 -0
  217. package/bizar-dash/src/web/views/Tasks.tsx +1523 -0
  218. package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +613 -0
  219. package/bizar-dash/src/web/views/glyphs/components.tsx +1098 -0
  220. package/bizar-dash/templates/mod/FORMAT.md +76 -0
  221. package/bizar-dash/templates/mod/hello-mod/README.md +19 -0
  222. package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +8 -0
  223. package/bizar-dash/templates/mod/hello-mod/commands/hello.md +6 -0
  224. package/bizar-dash/templates/mod/hello-mod/mod.json +20 -0
  225. package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +9 -0
  226. package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +10 -0
  227. package/bizar-dash/tests/dashboard-ports.test.mjs +138 -0
  228. package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +90 -0
  229. package/bizar-dash/tests/memory-cli.test.mjs +542 -0
  230. package/bizar-dash/tests/memory-config.test.mjs +422 -0
  231. package/bizar-dash/tests/memory-git.test.mjs +246 -0
  232. package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
  233. package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -0
  234. package/bizar-dash/tests/memory-schema.test.mjs +198 -0
  235. package/bizar-dash/tests/memory-secrets.test.mjs +121 -0
  236. package/bizar-dash/tests/memory-store.test.mjs +390 -0
  237. package/bizar-dash/tests/memory-sync.test.mjs +141 -0
  238. package/bizar-dash/tests/mod-instructions.node.test.mjs +188 -0
  239. package/bizar-dash/tests/mod-security.test.mjs +273 -0
  240. package/bizar-dash/tests/mod-upgrade.node.test.mjs +357 -0
  241. package/bizar-dash/tests/no-agent-browser.node.test.mjs +98 -0
  242. package/bizar-dash/tests/obsidian-back-compat.test.mjs +199 -0
  243. package/bizar-dash/tests/opencode-runner.test.mjs +172 -0
  244. package/bizar-dash/tests/path-safe.test.mjs +108 -0
  245. package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +333 -0
  246. package/bizar-dash/tests/smoke-v2.mjs +212 -0
  247. package/bizar-dash/tests/strip-thinking.test.mjs +175 -0
  248. package/bizar-dash/tests/submit-feedback.test.mjs +353 -0
  249. package/bizar-dash/tests/tmux-wrap.test.mjs +145 -0
  250. package/bizar-dash/tests/yaml.test.mjs +128 -0
  251. package/bizar-dash/tsconfig.json +23 -0
  252. package/bizar-dash/vite.config.ts +27 -0
  253. package/cli/atomic.mjs +73 -0
  254. package/cli/banner.mjs +1 -1
  255. package/cli/bin.mjs +44 -47
  256. package/cli/bootstrap.mjs +1 -1
  257. package/cli/copy.mjs +25 -16
  258. package/cli/dev-link.mjs +17 -1
  259. package/cli/doctor.mjs +4 -4
  260. package/cli/doctor.test.mjs +2 -2
  261. package/cli/init.mjs +135 -1
  262. package/cli/install.mjs +31 -39
  263. package/cli/memory-constants.mjs +59 -0
  264. package/cli/memory.mjs +1436 -0
  265. package/cli/service.mjs +5 -6
  266. package/cli/utils.mjs +6 -3
  267. package/config/AGENTS.md +7 -7
  268. package/config/agents/_shared/AGENT_BASELINE.md +59 -61
  269. package/config/opencode.json +13 -38
  270. package/config/skills/memory-protocol/SKILL.md +105 -0
  271. package/config/skills/obsidian/SKILL.md +270 -65
  272. package/install.sh +59 -19
  273. package/package.json +49 -14
  274. package/packages/sdk/LICENSE +21 -0
  275. package/packages/sdk/README.md +80 -0
  276. package/packages/sdk/src/client.ts +183 -0
  277. package/packages/sdk/src/errors.ts +120 -0
  278. package/packages/sdk/src/events.ts +153 -0
  279. package/packages/sdk/src/index.ts +63 -0
  280. package/packages/sdk/src/types.ts +176 -0
  281. package/packages/sdk/src/version.ts +4 -0
  282. package/packages/sdk/tests/client.test.ts +217 -0
  283. package/packages/sdk/tests/errors.test.ts +108 -0
  284. package/packages/sdk/tests/events.test.ts +139 -0
  285. package/packages/sdk/tests/fixtures/fetch-mock.ts +152 -0
  286. package/packages/sdk/tests/fixtures/sse-mock.ts +30 -0
  287. package/packages/sdk/tsconfig.json +25 -0
  288. package/packages/sdk/vitest.config.ts +9 -0
  289. package/plugins/bizar/LICENSE +21 -0
  290. package/plugins/bizar/README.md +448 -0
  291. package/plugins/bizar/dist/index.js +29901 -0
  292. package/plugins/bizar/index.ts +1491 -0
  293. package/plugins/bizar/scripts/check-forbidden-imports.sh +33 -0
  294. package/plugins/bizar/src/background-state.ts +551 -0
  295. package/plugins/bizar/src/background.ts +1250 -0
  296. package/plugins/bizar/src/commands-impl.ts +464 -0
  297. package/plugins/bizar/src/commands.ts +1168 -0
  298. package/plugins/bizar/src/dashboard-client.ts +233 -0
  299. package/plugins/bizar/src/event-stream.ts +606 -0
  300. package/plugins/bizar/src/fingerprint.ts +120 -0
  301. package/plugins/bizar/src/handoff.ts +79 -0
  302. package/plugins/bizar/src/http-client.ts +467 -0
  303. package/plugins/bizar/src/key-rotation.ts +218 -0
  304. package/plugins/bizar/src/logger.ts +144 -0
  305. package/plugins/bizar/src/loop.ts +176 -0
  306. package/plugins/bizar/src/opencode-runner.ts +390 -0
  307. package/plugins/bizar/src/options.ts +421 -0
  308. package/plugins/bizar/src/plan-fs.ts +323 -0
  309. package/plugins/bizar/src/reasoning-clean.ts +454 -0
  310. package/plugins/bizar/src/report.ts +178 -0
  311. package/plugins/bizar/src/research-prompt.ts +35 -0
  312. package/plugins/bizar/src/serve-info.ts +228 -0
  313. package/plugins/bizar/src/serve.ts +496 -0
  314. package/plugins/bizar/src/settings.ts +349 -0
  315. package/plugins/bizar/src/state.ts +298 -0
  316. package/plugins/bizar/src/tools/bg-collect.ts +104 -0
  317. package/plugins/bizar/src/tools/bg-get-comments.ts +239 -0
  318. package/plugins/bizar/src/tools/bg-kill.ts +87 -0
  319. package/plugins/bizar/src/tools/bg-spawn.ts +401 -0
  320. package/plugins/bizar/src/tools/bg-status.ts +99 -0
  321. package/plugins/bizar/src/tools/open-kb.ts +191 -0
  322. package/plugins/bizar/src/tools/plan-action.ts +767 -0
  323. package/plugins/bizar/src/tools/read-glyph-feedback.ts +170 -0
  324. package/plugins/bizar/src/tools/wait-for-feedback.ts +402 -0
  325. package/plugins/bizar/tests/attach-handler-bug.test.ts +169 -0
  326. package/plugins/bizar/tests/background-state.test.ts +277 -0
  327. package/plugins/bizar/tests/background.test.ts +402 -0
  328. package/plugins/bizar/tests/block.test.ts +195 -0
  329. package/plugins/bizar/tests/canonical-key-order.test.ts +75 -0
  330. package/plugins/bizar/tests/commands-impl.test.ts +442 -0
  331. package/plugins/bizar/tests/commands.test.ts +584 -0
  332. package/plugins/bizar/tests/config.test.ts +122 -0
  333. package/plugins/bizar/tests/dashboard-client.test.ts +159 -0
  334. package/plugins/bizar/tests/dispose.test.ts +336 -0
  335. package/plugins/bizar/tests/event-stream.test.ts +409 -0
  336. package/plugins/bizar/tests/event.test.ts +262 -0
  337. package/plugins/bizar/tests/fingerprint.test.ts +162 -0
  338. package/plugins/bizar/tests/http-client.test.ts +404 -0
  339. package/plugins/bizar/tests/init-helpers.test.ts +203 -0
  340. package/plugins/bizar/tests/integration/slash-command.test.ts +348 -0
  341. package/plugins/bizar/tests/integration/tool-routing.test.ts +314 -0
  342. package/plugins/bizar/tests/key-rotation.test.ts +396 -0
  343. package/plugins/bizar/tests/loop.test.ts +397 -0
  344. package/plugins/bizar/tests/options.test.ts +276 -0
  345. package/plugins/bizar/tests/reasoning-clean.test.ts +422 -0
  346. package/plugins/bizar/tests/serve.test.ts +339 -0
  347. package/plugins/bizar/tests/settings.test.ts +351 -0
  348. package/plugins/bizar/tests/stall-think.test.ts +750 -0
  349. package/plugins/bizar/tests/state.test.ts +276 -0
  350. package/plugins/bizar/tests/tools/bg-collect.test.ts +337 -0
  351. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +485 -0
  352. package/plugins/bizar/tests/tools/bg-kill.test.ts +235 -0
  353. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +147 -0
  354. package/plugins/bizar/tests/tools/bg-spawn.test.ts +311 -0
  355. package/plugins/bizar/tests/tools/bg-status.test.ts +217 -0
  356. package/plugins/bizar/tests/tools/opencode-runner.test.ts +115 -0
  357. package/plugins/bizar/tests/tools/plan-action.test.ts +599 -0
  358. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +253 -0
  359. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +390 -0
  360. package/plugins/bizar/tests/update-deadlock.test.ts +145 -0
  361. package/plugins/bizar/tsconfig.json +29 -0
@@ -0,0 +1,398 @@
1
+ /**
2
+ * src/server/mod-security.mjs
3
+ *
4
+ * v1.0.0 — Mod security layer for BizarHarness.
5
+ *
6
+ * Threat model: a user installs a mod from a registry (or a third-party
7
+ * GitHub repo) and the mod's `route.mjs` runs in the dashboard's
8
+ * Node process. Without a security layer, the mod has full access to:
9
+ * - The filesystem (read/write any file the user can)
10
+ * - The network (any HTTP/HTTPS request)
11
+ * - Subprocesses (spawn any binary on PATH)
12
+ * - The dashboard's REST API (loopback, no auth required)
13
+ * - The user's environment variables (incl. API keys)
14
+ *
15
+ * This module provides defense in depth:
16
+ *
17
+ * 1. **Permission declarations** — mods MUST declare what they need
18
+ * in `mod.json`. Unknown permissions trigger a warning at load.
19
+ * 2. **Filesystem sandboxing** — mods are given scoped `readFile` /
20
+ * `writeFile` helpers that enforce path prefixes from the mod's
21
+ * declared `fs:read:<path>` / `fs:write:<path>` permissions.
22
+ * Path-traversal attacks (e.g. `../`) are blocked.
23
+ * 3. **Subprocess allowlist** — only whitelisted binaries
24
+ * (`bizar`, `opencode`, `python3`, `graphify`, `git`, `node`,
25
+ * `npm`, `pip`, `pipx`, `uv`, `headroom`) can be spawned. Custom
26
+ * binaries require an explicit `process:spawn:<bin>` permission.
27
+ * 4. **Audit log** — every privileged operation (fs read/write,
28
+ * process spawn, network fetch) is logged to
29
+ * `~/.cache/bizar/logs/mod-audit.log` with timestamp + mod id +
30
+ * action + details.
31
+ * 5. **Integrity hash** — at install time we hash every file in the
32
+ * mod and store the SHA-256 under `_integrity` in mod.json. On
33
+ * every load we recompute the hash; a mismatch triggers a
34
+ * critical warning ("mod contents have been modified outside
35
+ * bizar mod install — review carefully").
36
+ *
37
+ * v1 limitations: a malicious mod could still bypass the helpers and
38
+ * use Node's built-in `fs` / `child_process` directly. v2 will close
39
+ * that gap with a `vm`-based sandbox or worker-thread isolation.
40
+ *
41
+ * For now, the security layer makes the *intent* of mods explicit
42
+ * (declared permissions, audit trail, integrity hash) so users can
43
+ * review what they're installing and detect tampering.
44
+ */
45
+
46
+ import { createHash } from 'node:crypto';
47
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync, statSync } from 'node:fs';
48
+ import { join, dirname, relative, resolve, sep } from 'node:path';
49
+ import { homedir } from 'node:os';
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Defaults
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Binaries that any mod can spawn without an explicit permission.
57
+ * These are core tools in the BizarHarness ecosystem that mods
58
+ * legitimately need. Custom binaries (e.g. `curl`, `wget`) require
59
+ * `process:spawn:<bin>` in the mod's permissions array.
60
+ */
61
+ export const ALLOWED_BINARIES = new Set([
62
+ 'bizar',
63
+ 'opencode',
64
+ 'opencode-ai',
65
+ 'python3',
66
+ 'python',
67
+ 'graphify',
68
+ 'graphifyy',
69
+ 'pip',
70
+ 'pipx',
71
+ 'uv',
72
+ 'node',
73
+ 'npm',
74
+ 'npx',
75
+ 'git',
76
+ 'headroom',
77
+ 'jq',
78
+ ]);
79
+
80
+ /**
81
+ * Default permission set for a brand-new mod. Empty — mods must
82
+ * declare what they need.
83
+ */
84
+ export const DEFAULT_PERMISSIONS = [];
85
+
86
+ /**
87
+ * Paths the dashboard's own runtime depends on. Mods that need
88
+ * to touch these MUST declare the appropriate `fs:read:<path>` /
89
+ * `fs:write:<path>` permission.
90
+ */
91
+ export const SENSITIVE_PATHS = [
92
+ '~/.ssh',
93
+ '~/.aws',
94
+ '~/.config/bizar/auth.json',
95
+ '~/.config/opencode/auth.json',
96
+ '~/.gnupg',
97
+ ];
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Permission parsing
101
+ // ---------------------------------------------------------------------------
102
+
103
+ /**
104
+ * Normalize a list of permission strings from mod.json into a set of
105
+ * canonical permission tokens. Examples:
106
+ *
107
+ * "fs:read:.bizar/graph" -> exact match
108
+ * "fs:read:.bizar/graph/*" -> prefix match (any file under .bizar/graph)
109
+ * "process:spawn:bizar" -> exact match
110
+ * "network:*" -> any network access (v2 only)
111
+ *
112
+ * Invalid permission tokens are returned in a separate `invalid` array
113
+ * so the loader can warn without aborting the load (v1 is permissive).
114
+ */
115
+ export function parsePermissions(perms) {
116
+ const allowed = new Set();
117
+ const invalid = [];
118
+ for (const p of perms || []) {
119
+ if (typeof p !== 'string' || !p.includes(':')) {
120
+ invalid.push(p);
121
+ continue;
122
+ }
123
+ const [category, ...rest] = p.split(':');
124
+ const validCategories = new Set(['fs', 'process', 'network', 'api', 'env']);
125
+ if (!validCategories.has(category)) {
126
+ invalid.push(p);
127
+ continue;
128
+ }
129
+ allowed.add(p);
130
+ }
131
+ return { allowed, invalid };
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // Filesystem sandbox
136
+ // ---------------------------------------------------------------------------
137
+
138
+ /**
139
+ * Validate that `path` is within one of the mod's declared `fs:read` /
140
+ * `fs:write` scopes. Returns a normalized absolute path on success,
141
+ * throws on access denial.
142
+ *
143
+ * Scope syntax:
144
+ * - Exact path: `fs:read:/abs/path/to/file`
145
+ * - Prefix: `fs:read:/abs/path/to/dir/*` (any descendant allowed)
146
+ * - Home-relative: `fs:read:~/.config/bizar/mods` (resolves to home)
147
+ */
148
+ export function authorizeFsPath(perms, kind, requestedPath) {
149
+ // `kind` is the full permission category: 'fs:read' or 'fs:write'.
150
+ // We accept the bare form ('read' / 'write') too for caller convenience.
151
+ const prefix = kind.startsWith('fs:') ? `${kind}:` : `fs:${kind}:`;
152
+ const expanded = requestedPath.startsWith('~/')
153
+ ? join(homedir(), requestedPath.slice(2))
154
+ : resolve(requestedPath);
155
+
156
+ // Reject obvious traversal attempts even before checking perms
157
+ if (expanded.includes('\0')) {
158
+ throw new Error(`fs:${kind}: null bytes are not allowed in paths`);
159
+ }
160
+
161
+ const candidates = [];
162
+ for (const p of perms) {
163
+ if (!p.startsWith(prefix)) continue;
164
+ const scope = p.slice(prefix.length);
165
+ if (scope.endsWith('/*')) {
166
+ candidates.push({ prefix: scope.slice(0, -2), recursive: true });
167
+ } else if (scope.endsWith('/')) {
168
+ // Trailing slash — treat as recursive directory match.
169
+ candidates.push({ prefix: scope.slice(0, -1), recursive: true });
170
+ } else {
171
+ // No suffix — match the directory and everything under it.
172
+ // This is the intuitive behavior for `fs:read:/foo`: "I want
173
+ // access to /foo" usually means "and everything below it". For
174
+ // exact-file access, callers can use a different permission.
175
+ candidates.push({ prefix: scope, recursive: true });
176
+ }
177
+ }
178
+
179
+ for (const c of candidates) {
180
+ const expandedScope = c.prefix.startsWith('~/')
181
+ ? join(homedir(), c.prefix.slice(2))
182
+ : resolve(c.prefix);
183
+ if (c.recursive) {
184
+ if (expanded === expandedScope || expanded.startsWith(expandedScope + sep)) {
185
+ return expanded;
186
+ }
187
+ } else {
188
+ if (expanded === expandedScope) return expanded;
189
+ }
190
+ }
191
+ throw new Error(
192
+ `mod not authorized to ${kind} '${requestedPath}' ` +
193
+ `(declare "fs:${kind}:<path>" in mod.json permissions to allow)`,
194
+ );
195
+ }
196
+
197
+ /**
198
+ * Wrap Node's `fs` module so that readFileSync / writeFileSync /
199
+ * readdirSync / statSync are gated by the mod's declared fs: scopes.
200
+ * Other fs functions (mkdirSync, copyFileSync, etc.) are forwarded
201
+ * with an audit warning but no enforcement — v2 will enforce these too.
202
+ */
203
+ export function sandboxedFs(perms, audit, kind = 'read') {
204
+ const guard = (path) => authorizeFsPath(perms, `${kind}:${path}`.replace(/^read:|^write:/, ''), path);
205
+
206
+ // Strip the `kind:` prefix the authorizeFsPath expects; the caller
207
+ // already filtered by kind.
208
+ const guardRaw = (path, op) => {
209
+ const needed = op === 'read' ? 'read' : 'write';
210
+ const expanded = path.startsWith('~/') ? join(homedir(), path.slice(2)) : resolve(path);
211
+ for (const p of perms) {
212
+ if (!p.startsWith(`fs:${needed}:`)) continue;
213
+ const scope = p.slice(`fs:${needed}:`.length);
214
+ const exp = scope.startsWith('~/') ? join(homedir(), scope.slice(2)) : resolve(scope);
215
+ if (scope.endsWith('/*')) {
216
+ const base = exp;
217
+ if (expanded === base || expanded.startsWith(base + sep)) return expanded;
218
+ } else if (expanded === exp) {
219
+ return expanded;
220
+ }
221
+ }
222
+ throw new Error(`mod not authorized to ${op} '${path}'`);
223
+ };
224
+
225
+ return {
226
+ readFileSync: (path, enc) => {
227
+ const safe = guardRaw(path, 'read');
228
+ audit('fs:read', { path: safe });
229
+ return readFileSync(safe, enc);
230
+ },
231
+ writeFileSync: (path, data, enc) => {
232
+ const safe = guardRaw(path, 'write');
233
+ audit('fs:write', { path: safe });
234
+ mkdirSync(dirname(safe), { recursive: true });
235
+ return writeFileSync(safe, data, enc);
236
+ },
237
+ existsSync: (path) => existsSync(path),
238
+ statSync: (path) => {
239
+ const safe = guardRaw(path, 'read');
240
+ return statSync(safe);
241
+ },
242
+ readdirSync: (path) => {
243
+ const safe = guardRaw(path, 'read');
244
+ return require('node:fs').readdirSync(safe);
245
+ },
246
+ };
247
+ }
248
+
249
+ // ---------------------------------------------------------------------------
250
+ // Subprocess allowlist
251
+ // ---------------------------------------------------------------------------
252
+
253
+ /**
254
+ * Authorize a subprocess spawn. Returns the resolved absolute path of
255
+ * the binary if allowed; throws otherwise. Records the spawn in the
256
+ * audit log.
257
+ *
258
+ * Resolution:
259
+ * 1. If `bin` is a path (contains `/`), it's rejected unless the mod
260
+ * has a matching `process:spawn:<abs-path>` permission.
261
+ * 2. If `bin` is a bare name and is in `ALLOWED_BINARIES`, it's
262
+ * allowed.
263
+ * 3. Otherwise the mod must declare `process:spawn:<bin>`.
264
+ */
265
+ export function authorizeSpawn(perms, audit, bin, args = []) {
266
+ let allow = false;
267
+ if (bin.includes('/') || bin.includes('\\')) {
268
+ // Absolute / relative path — must be explicitly permitted
269
+ allow = perms.has(`process:spawn:${bin}`) || perms.has('process:spawn:*');
270
+ } else if (ALLOWED_BINARIES.has(bin)) {
271
+ allow = true;
272
+ } else {
273
+ allow = perms.has(`process:spawn:${bin}`) || perms.has('process:spawn:*');
274
+ }
275
+ if (!allow) {
276
+ throw new Error(
277
+ `mod not authorized to spawn '${bin}' ` +
278
+ `(declare "process:spawn:${bin}" in mod.json permissions to allow)`,
279
+ );
280
+ }
281
+ audit('process:spawn', { bin, args });
282
+ return true;
283
+ }
284
+
285
+ // ---------------------------------------------------------------------------
286
+ // Audit log
287
+ // ---------------------------------------------------------------------------
288
+
289
+ function auditLogPath() {
290
+ return join(homedir(), '.cache', 'bizar', 'logs', 'mod-audit.log');
291
+ }
292
+
293
+ /**
294
+ * Create an audit writer for a given mod. Logs every privileged action
295
+ * to `~/.cache/bizar/logs/mod-audit.log` as JSON lines.
296
+ */
297
+ export function createAuditWriter(mod) {
298
+ const logPath = auditLogPath();
299
+ mkdirSync(dirname(logPath), { recursive: true });
300
+
301
+ return function audit(action, details = {}) {
302
+ const entry = JSON.stringify({
303
+ ts: new Date().toISOString(),
304
+ mod: mod.id,
305
+ modVersion: mod.version,
306
+ action,
307
+ details,
308
+ });
309
+ try {
310
+ appendFileSync(logPath, entry + '\n');
311
+ } catch {
312
+ // Never let audit logging break a mod.
313
+ }
314
+ };
315
+ }
316
+
317
+ // ---------------------------------------------------------------------------
318
+ // Integrity hash
319
+ // ---------------------------------------------------------------------------
320
+
321
+ /**
322
+ * Walk a mod directory and compute a SHA-256 of (path + content) for
323
+ * every file. The order is deterministic (sorted by relative path).
324
+ */
325
+ export function computeModHash(modPath) {
326
+ const files = [];
327
+ function walk(dir, prefix) {
328
+ let entries;
329
+ try {
330
+ entries = require('node:fs').readdirSync(dir, { withFileTypes: true });
331
+ } catch {
332
+ return;
333
+ }
334
+ for (const e of entries) {
335
+ if (e.name === 'node_modules' || e.name === '.DS_Store') continue;
336
+ const rel = prefix ? `${prefix}/${e.name}` : e.name;
337
+ const full = join(dir, e.name);
338
+ if (e.isDirectory()) walk(full, rel);
339
+ else if (e.isFile()) files.push(rel);
340
+ }
341
+ }
342
+ walk(modPath, '');
343
+ files.sort();
344
+
345
+ const hash = createHash('sha256');
346
+ for (const f of files) {
347
+ hash.update(f + '\0');
348
+ try {
349
+ hash.update(readFileSync(join(modPath, f)));
350
+ } catch {
351
+ hash.update('<unreadable>');
352
+ }
353
+ hash.update('\0');
354
+ }
355
+ return hash.digest('hex');
356
+ }
357
+
358
+ /**
359
+ * Verify the mod's current files match the hash stored in its
360
+ * `mod.json._integrity` field. Returns:
361
+ * { ok: true, hash } — matches
362
+ * { ok: false, hash, expected, reason } — mismatch
363
+ * { ok: 'unsigned', hash } — no _integrity field
364
+ */
365
+ export function verifyModIntegrity(mod) {
366
+ const current = computeModHash(mod.path);
367
+ if (!mod._integrity) return { ok: 'unsigned', hash: current };
368
+ if (mod._integrity === current) return { ok: true, hash: current };
369
+ return {
370
+ ok: false,
371
+ hash: current,
372
+ expected: mod._integrity,
373
+ reason: 'mod contents have been modified outside `bizar mod install`',
374
+ };
375
+ }
376
+
377
+ // ---------------------------------------------------------------------------
378
+ // Per-mod context
379
+ // ---------------------------------------------------------------------------
380
+
381
+ /**
382
+ * Build a security context for one mod. The context exposes the
383
+ * sandboxed fs helpers and an audit writer. Mod route files receive
384
+ * the context as `register({ ctx, security })`.
385
+ */
386
+ export function createModSecurityContext(mod) {
387
+ const { allowed, invalid } = parsePermissions(mod.permissions || []);
388
+ const audit = createAuditWriter(mod);
389
+
390
+ return {
391
+ permissions: allowed,
392
+ invalidPermissions: invalid,
393
+ audit,
394
+ fs: sandboxedFs(allowed, audit, 'read'), // default helpers for read; mod can override
395
+ authorizeSpawn: (bin, args) => authorizeSpawn(allowed, audit, bin, args),
396
+ verifyIntegrity: () => verifyModIntegrity(mod),
397
+ };
398
+ }