@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,381 @@
1
+ /**
2
+ * src/server/auth.mjs
3
+ *
4
+ * v3.6.0 — Bearer-token authentication for the dashboard API.
5
+ *
6
+ * Threat model: the dashboard is a local control surface for an agent
7
+ * platform. When bound to 127.0.0.1 (the default), the only attacker is
8
+ * other local users on the box. When the user opts in to a non-local
9
+ * bind (BIZAR_DASHBOARD_BIND=0.0.0.0 — typical for Tailscale serve), the
10
+ * attack surface expands to the whole tailnet. A static bearer token
11
+ * (auto-generated on first boot, stored at mode 0600) closes the
12
+ * "anyone on the tailnet can hit my dashboard" gap.
13
+ *
14
+ * Token storage:
15
+ * - File: $BIZAR_DASHBOARD_SECRET_PATH or ~/.config/bizar/dashboard-secret
16
+ * - Mode: 0600 (read+write for owner only)
17
+ * - Length: 64 hex chars (32 bytes of entropy)
18
+ *
19
+ * Token delivery:
20
+ * - HTTP routes: Authorization: Bearer <token>
21
+ * - SSE routes: Authorization header OR ?token=<token> query param
22
+ * (EventSource in browsers cannot set custom headers)
23
+ * - WebSocket: ?token=<token> query param on the connection URL
24
+ * (browsers cannot set the Authorization header on WebSocket either)
25
+ *
26
+ * The single public unauthed endpoint is /api/auth/status, which lets
27
+ * the dashboard probe whether auth is required and where to get the
28
+ * token from. /api/auth/reveal (authed) returns the current token for
29
+ * display in the Settings tab. /api/auth/regenerate (authed) mints a
30
+ * fresh token and returns it ONCE in the response — the caller MUST
31
+ * persist it client-side, because the next call without it will 401.
32
+ */
33
+ import {
34
+ existsSync,
35
+ readFileSync,
36
+ writeFileSync,
37
+ mkdirSync,
38
+ unlinkSync,
39
+ chmodSync,
40
+ } from 'node:fs';
41
+ import { join, dirname } from 'node:path';
42
+ import { homedir } from 'node:os';
43
+ import { randomBytes } from 'node:crypto';
44
+ import { isIP } from 'node:net';
45
+
46
+ /** Absolute path to the on-disk token. Override via BIZAR_DASHBOARD_SECRET_PATH. */
47
+ export const SECRET_PATH = process.env.BIZAR_DASHBOARD_SECRET_PATH
48
+ || join(homedir(), '.config', 'bizar', 'dashboard-secret');
49
+
50
+ /**
51
+ * In-process cache of the token. Set on first read so the
52
+ * expensive file I/O only happens once per process. Cleared by
53
+ * /api/auth/regenerate.
54
+ * @type {string | null}
55
+ */
56
+ let cachedToken = null;
57
+
58
+ const EXTRA_ALLOWED_ORIGINS = (process.env.BIZAR_DASHBOARD_ALLOWED_ORIGINS || '')
59
+ .split(',')
60
+ .map((s) => s.trim())
61
+ .filter(Boolean);
62
+
63
+ /**
64
+ * Get the current token, generating one on disk if it doesn't exist.
65
+ *
66
+ * Generation is silent on disk (no token logged there), but the FIRST
67
+ * time we mint a token we ALSO print it to stderr so the operator can
68
+ * see it once. After that the only place to retrieve the token is the
69
+ * Settings tab via /api/auth/reveal (which is itself authed — so only
70
+ * someone who already has the token can read it back).
71
+ *
72
+ * @returns {string} the current bearer token (hex)
73
+ */
74
+ export function getOrCreateSecret() {
75
+ if (cachedToken) return cachedToken;
76
+ if (existsSync(SECRET_PATH)) {
77
+ try {
78
+ ensureSecretFileMode();
79
+ const onDisk = readFileSync(SECRET_PATH, 'utf8').trim();
80
+ if (onDisk) {
81
+ cachedToken = onDisk;
82
+ return cachedToken;
83
+ }
84
+ } catch {
85
+ /* unreadable — fall through to regen */
86
+ }
87
+ }
88
+ // Generate. 32 bytes = 256 bits of entropy → 64 hex chars.
89
+ cachedToken = randomBytes(32).toString('hex');
90
+ try {
91
+ mkdirSync(dirname(SECRET_PATH), { recursive: true });
92
+ writeFileSync(SECRET_PATH, cachedToken + '\n', { mode: 0o600 });
93
+ ensureSecretFileMode();
94
+ } catch (err) {
95
+ // Last-ditch — log to stderr but keep the in-memory token so the
96
+ // process can still serve (and so the operator can see the token
97
+ // in the log even if disk writes are failing).
98
+ console.error(`[dashboard] failed to persist secret to ${SECRET_PATH}: ${err.message}`);
99
+ }
100
+ console.error(`[dashboard] Generated new auth token and saved it to ${SECRET_PATH} (mode 0600)`);
101
+ console.error('[dashboard] Open the dashboard Settings tab from a trusted local session to reveal/copy it.');
102
+ return cachedToken;
103
+ }
104
+
105
+ /**
106
+ * Public accessor used by /api/auth/reveal. Returns the same string
107
+ * as getOrCreateSecret() but never triggers generation if the file
108
+ * is missing (so we can call it from already-authed code without
109
+ * risking a "first-boot then 401" race).
110
+ *
111
+ * @returns {string | null} the current token, or null if not yet
112
+ * generated
113
+ */
114
+ export function getSecret() {
115
+ if (cachedToken) return cachedToken;
116
+ if (existsSync(SECRET_PATH)) {
117
+ try {
118
+ cachedToken = readFileSync(SECRET_PATH, 'utf8').trim() || null;
119
+ } catch {
120
+ cachedToken = null;
121
+ }
122
+ }
123
+ return cachedToken;
124
+ }
125
+
126
+ /**
127
+ * Force-regenerate the token. Deletes the file, clears the cache,
128
+ * and mints a new one. Called by POST /api/auth/regenerate.
129
+ *
130
+ * @returns {string} the new token
131
+ */
132
+ export function regenerateSecret() {
133
+ try {
134
+ unlinkSync(SECRET_PATH);
135
+ } catch {
136
+ /* missing is fine */
137
+ }
138
+ cachedToken = null;
139
+ return getOrCreateSecret();
140
+ }
141
+
142
+ /**
143
+ * Express middleware: reject requests without a valid bearer token.
144
+ *
145
+ * v3.6.1 — Trust loopback connections automatically. The dashboard
146
+ * is a local control surface; the user's own browser talks to it
147
+ * over 127.0.0.1 (or via Tailscale serve, which proxies to 127.0.0.1).
148
+ * Requiring a manual token paste for every browser refresh would be
149
+ * hostile UX. Instead, we authenticate by connection origin:
150
+ *
151
+ * - Loopback (127.0.0.1, ::1, ::ffff:127.0.0.1) → auto-trust. Tailscale
152
+ * serve reaches the dashboard over a local proxy hop, but we only
153
+ * trust that path when the original forwarded client was also
154
+ * loopback. Remote proxy clients must present the bearer token.
155
+ * - Non-loopback → require a valid bearer token. The token can
156
+ * arrive via `Authorization: Bearer <token>` header OR via
157
+ * `?token=<token>` query string (SSE/EventSource can't set
158
+ * custom headers, so the query fallback is required for those).
159
+ *
160
+ * Skips paths in `skipPaths` (prefix match) regardless of origin so
161
+ * /api/auth/status remains reachable.
162
+ *
163
+ * To disable loopback trust and force auth on every connection (e.g.
164
+ * you're exposing the dashboard on a non-loopback bind and want
165
+ * defense-in-depth), set BIZAR_DASHBOARD_REQUIRE_AUTH=1.
166
+ *
167
+ * @param {object} [opts]
168
+ * @param {string[]} [opts.skipPaths] — path prefixes to skip (e.g. ['/auth/status'])
169
+ * @returns {import('express').RequestHandler}
170
+ */
171
+ export function requireAuth({ skipPaths = [] } = {}) {
172
+ return (req, res, next) => {
173
+ const url = req.path || req.url || '';
174
+ if (skipPaths.some((p) => url === p || url.startsWith(p + '/'))) {
175
+ return next();
176
+ }
177
+
178
+ // Trust loopback unless operator explicitly forces auth for all.
179
+ if (!isAuthRequired(req)) {
180
+ return next();
181
+ }
182
+
183
+ const auth = req.headers?.authorization || '';
184
+ let token = '';
185
+ if (typeof auth === 'string' && auth.startsWith('Bearer ')) {
186
+ token = auth.slice(7);
187
+ } else if (req.query && req.query.token) {
188
+ token = String(req.query.token);
189
+ }
190
+
191
+ // Constant-time compare to avoid leaking length / prefix info via
192
+ // timing. String#length and the per-byte XOR loop both run in
193
+ // linear time regardless of match position.
194
+ const expected = getOrCreateSecret();
195
+ if (!token || !timingSafeEqual(token, expected)) {
196
+ res.status(401).json({
197
+ error: 'unauthorized',
198
+ message: 'Invalid or missing auth token',
199
+ });
200
+ return;
201
+ }
202
+ next();
203
+ };
204
+ }
205
+
206
+ /**
207
+ * WebSocket upgrade-time auth check. Returns true if the request
208
+ * carries a valid token via Authorization header or ?token= query,
209
+ * OR if the connection is from loopback (auto-trust).
210
+ *
211
+ * @param {import('http').IncomingMessage} req
212
+ * @returns {boolean}
213
+ */
214
+ export function checkWebSocketAuth(req) {
215
+ // Loopback auto-trust
216
+ if (!isAuthRequired(req)) {
217
+ return true;
218
+ }
219
+ let token = '';
220
+ const auth = req.headers?.authorization || '';
221
+ if (typeof auth === 'string' && auth.startsWith('Bearer ')) {
222
+ token = auth.slice(7);
223
+ }
224
+ if (!token && req.url) {
225
+ try {
226
+ const u = new URL(req.url, 'http://localhost');
227
+ const q = u.searchParams.get('token');
228
+ if (q) token = q;
229
+ } catch {
230
+ /* bad URL — leave token empty */
231
+ }
232
+ }
233
+ const expected = getOrCreateSecret();
234
+ return !!token && timingSafeEqual(token, expected);
235
+ }
236
+
237
+ /**
238
+ * Detect whether an incoming request is from a loopback address.
239
+ * Handles both IPv4 (127.0.0.1, 127.0.0.0/8) and IPv6 (::1) loopback,
240
+ * including the IPv4-mapped IPv6 form (::ffff:127.0.0.1) that Node
241
+ * sometimes produces for v4 connections on dual-stack listeners.
242
+ *
243
+ * Trusts forwarded loopback only when both the direct peer and the first
244
+ * X-Forwarded-For hop are loopback — i.e. a local reverse proxy in front
245
+ * of a local browser. Remote proxy clients do not inherit loopback trust.
246
+ *
247
+ * @param {import('http').IncomingMessage | { ip?: string; socket?: { remoteAddress?: string }; connection?: { remoteAddress?: string }; headers?: Record<string, string|undefined> }} req
248
+ * @returns {boolean}
249
+ */
250
+ export function isLoopback(req) {
251
+ const direct = getDirectPeerAddress(req);
252
+ const forwarded = getForwardedPeerAddress(req);
253
+
254
+ // Direct local connection: no proxy hop, safe to trust.
255
+ if (isLoopbackAddr(direct) && !forwarded) return true;
256
+
257
+ // Local reverse proxy: trust only when the *original* caller was also
258
+ // loopback. This closes the previous auth bypass where a remote client
259
+ // could arrive through a local proxy (for example Tailscale serve) and
260
+ // inherit loopback trust.
261
+ //
262
+ // Tailscale Serve proxies to localhost and authenticates users on
263
+ // the tailnet before forwarding. Trust its XFF when opted in.
264
+ if (isLoopbackAddr(direct) && forwarded) {
265
+ if (isLoopbackAddr(forwarded)) return true;
266
+ if (process.env.BIZAR_DASHBOARD_TRUST_TAILSCALE === '1') {
267
+ if (isTailscaleIp(forwarded)) return true;
268
+ }
269
+ return false;
270
+ }
271
+
272
+ return false;
273
+ }
274
+
275
+ export function isAuthRequired(req) {
276
+ // Explicit operator opt-out: trust everyone, regardless of peer.
277
+ if (process.env.BIZAR_DASHBOARD_REQUIRE_AUTH === '0') return false;
278
+ if (process.env.BIZAR_DASHBOARD_REQUIRE_AUTH === '1') return true;
279
+ return !isLoopback(req);
280
+ }
281
+
282
+ export function getDirectPeerAddress(req) {
283
+ return (
284
+ req?.socket?.remoteAddress ||
285
+ req?.connection?.remoteAddress ||
286
+ req?.ip ||
287
+ ''
288
+ );
289
+ }
290
+
291
+ export function getForwardedPeerAddress(req) {
292
+ const xff = req?.headers?.['x-forwarded-for'];
293
+ if (typeof xff !== 'string') return '';
294
+ return xff.split(',')[0]?.trim() || '';
295
+ }
296
+
297
+ export function isAllowedDashboardOrigin(origin) {
298
+ if (!origin || typeof origin !== 'string') return false;
299
+ const normalized = origin.trim();
300
+ if (!normalized) return false;
301
+ if (EXTRA_ALLOWED_ORIGINS.includes(normalized)) return true;
302
+ try {
303
+ const url = new URL(normalized);
304
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
305
+ return isLoopbackAddr(url.hostname);
306
+ } catch {
307
+ return false;
308
+ }
309
+ }
310
+
311
+ export function isAllowedDashboardOriginForRequest(req) {
312
+ const origin = req?.headers?.origin;
313
+ if (typeof origin !== 'string' || !origin.trim()) return true;
314
+ if (isAllowedDashboardOrigin(origin)) return true;
315
+ if (EXTRA_ALLOWED_ORIGINS.includes(origin.trim())) return true;
316
+ try {
317
+ const originUrl = new URL(origin);
318
+ const hostHeader = String(req?.headers?.host || '').trim();
319
+ const host = stripPort(hostHeader);
320
+ return !!host && originUrl.hostname.toLowerCase() === host.toLowerCase();
321
+ } catch {
322
+ return false;
323
+ }
324
+ }
325
+
326
+ function isLoopbackAddr(addr) {
327
+ if (!addr) return false;
328
+ const normalized = String(addr).replace(/^\[|\]$/g, '').toLowerCase();
329
+ // IPv6 loopback
330
+ if (normalized === '::1' || normalized === 'localhost') return true;
331
+ // IPv4-mapped IPv6 loopback (Node sometimes returns this)
332
+ if (normalized === '::ffff:127.0.0.1' || normalized === '::ffff:7f00:1') return true;
333
+ // IPv4 loopback (entire 127.0.0.0/8)
334
+ if (normalized.startsWith('127.')) return true;
335
+ if (isIP(normalized) === 4) return normalized.startsWith('127.');
336
+ if (isIP(normalized) === 6) return normalized === '::1';
337
+ return false;
338
+ }
339
+
340
+ function isTailscaleIp(addr) {
341
+ if (!addr) return false;
342
+ const s = String(addr).toLowerCase();
343
+ return s.startsWith('100.') || s.startsWith('fd7a:115c:a1e0::');
344
+ }
345
+
346
+ function stripPort(hostHeader) {
347
+ if (!hostHeader) return '';
348
+ if (hostHeader.startsWith('[')) {
349
+ const idx = hostHeader.indexOf(']');
350
+ return idx === -1 ? hostHeader : hostHeader.slice(1, idx);
351
+ }
352
+ return hostHeader.split(':')[0] || '';
353
+ }
354
+
355
+ function ensureSecretFileMode() {
356
+ try {
357
+ chmodSync(SECRET_PATH, 0o600);
358
+ } catch {
359
+ /* best effort */
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Length-aware constant-time string compare. We compute XOR over the
365
+ * longer string's length so the runtime depends on `expected.length`
366
+ * (which is constant per process) and not on the candidate's length
367
+ * or prefix.
368
+ *
369
+ * @param {string} a
370
+ * @param {string} b
371
+ * @returns {boolean}
372
+ */
373
+ function timingSafeEqual(a, b) {
374
+ if (typeof a !== 'string' || typeof b !== 'string') return false;
375
+ const len = Math.max(a.length, b.length);
376
+ let diff = a.length ^ b.length;
377
+ for (let i = 0; i < len; i++) {
378
+ diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
379
+ }
380
+ return diff === 0;
381
+ }