@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.
- package/README.md +83 -16
- package/bizar-dash/.bizar/activity.log +3 -0
- package/bizar-dash/.bizar/graph/.graphify_analysis.json +1540 -0
- package/bizar-dash/.bizar/graph/.graphify_labels.json +1 -0
- package/bizar-dash/.bizar/graph/GRAPH_REPORT.md +404 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/0661b816358db84dfdb7f10443db8a61152fd20b3fee950caf8fbf32920a1790.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/16da06d774142a6e5d74829ad982531286a6193ef798de8f593db029e123ec32.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/27e5697c01254c69c64c5a33896669073b40afb5ba88375102ae77fa5404136d.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/532997898027254aa4c0932243dc71d2f55888181232b65974961934997dc85b.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/84e3451a64c7f99f0cd6a88b0748d7426780486f53ab4935b7476b28c08f8293.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/88e5ea5f3e78fa69353fe9e6a8d1a05391d54605e25600cb125531eb5a6432ff.json +1 -0
- package/bizar-dash/.bizar/graph/cache/stat-index.json +1 -0
- package/bizar-dash/.bizar/graph/graph.html +307 -0
- package/bizar-dash/.bizar/graph/graph.json +40267 -0
- package/bizar-dash/.bizar/graph/manifest.json +762 -0
- package/bizar-dash/.graphifyignore +40 -0
- package/bizar-dash/.obsidian/README.md +14 -0
- package/bizar-dash/.obsidian/daily/2026-06-25.md +9 -0
- package/bizar-dash/.obsidian/vault.json +7 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/SKILL.md +103 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/assets/template.html +338 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/example.html +81 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/open-design.json +172 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/checklist.md +44 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/layouts.md +247 -0
- package/bizar-dash/CHANGELOG.md +474 -0
- package/bizar-dash/DESIGN.md +136 -0
- package/bizar-dash/dist/assets/main-B3RgW6FS.js +331 -0
- package/bizar-dash/dist/assets/main-B3RgW6FS.js.map +1 -0
- package/bizar-dash/dist/assets/main-DlJ8wgLR.css +1 -0
- package/bizar-dash/dist/assets/mobile-CsZQAswA.css +1 -0
- package/bizar-dash/dist/assets/mobile-DbxIw8BG.js +2 -0
- package/bizar-dash/dist/assets/mobile-DbxIw8BG.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js +354 -0
- package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js.map +1 -0
- package/bizar-dash/dist/index.html +40 -0
- package/bizar-dash/dist/mobile.html +22 -0
- package/bizar-dash/plans/debug-plan/comments.json +1 -0
- package/bizar-dash/plans/debug-plan/meta.json +10 -0
- package/bizar-dash/plans/debug-plan/plan.json +174 -0
- package/bizar-dash/scripts/smoke-bg-retry.mjs +246 -0
- package/bizar-dash/src/cli/dashboard-ports.mjs +488 -0
- package/bizar-dash/src/cli.mjs +522 -0
- package/bizar-dash/src/server/activity-log.mjs +174 -0
- package/bizar-dash/src/server/agents-store.mjs +465 -0
- package/bizar-dash/src/server/api.mjs +138 -0
- package/bizar-dash/src/server/artifact-template.html +810 -0
- package/bizar-dash/src/server/artifacts-store.mjs +733 -0
- package/bizar-dash/src/server/auth.mjs +381 -0
- package/bizar-dash/src/server/background-store.mjs +447 -0
- package/bizar-dash/src/server/bg-poller.mjs +305 -0
- package/bizar-dash/src/server/bg-retry.mjs +574 -0
- package/bizar-dash/src/server/browser.mjs +40 -0
- package/bizar-dash/src/server/diagnostics-store.mjs +148 -0
- package/bizar-dash/src/server/dialog-poller.mjs +105 -0
- package/bizar-dash/src/server/dialog-store.mjs +177 -0
- package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +583 -0
- package/bizar-dash/src/server/lib/path-safe.mjs +283 -0
- package/bizar-dash/src/server/memory-git.mjs +329 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +767 -0
- package/bizar-dash/src/server/memory-schema.mjs +145 -0
- package/bizar-dash/src/server/memory-secrets.mjs +84 -0
- package/bizar-dash/src/server/memory-store.mjs +471 -0
- package/bizar-dash/src/server/mod-security.mjs +398 -0
- package/bizar-dash/src/server/mods-loader.mjs +1067 -0
- package/bizar-dash/src/server/notifications-store.mjs +243 -0
- package/bizar-dash/src/server/obsidian-store.mjs +318 -0
- package/bizar-dash/src/server/opencode-runner.mjs +279 -0
- package/bizar-dash/src/server/pair-store.mjs +138 -0
- package/bizar-dash/src/server/projects-store.mjs +364 -0
- package/bizar-dash/src/server/providers-store.mjs +789 -0
- package/bizar-dash/src/server/routes/_shared.mjs +415 -0
- package/bizar-dash/src/server/routes/activity.mjs +110 -0
- package/bizar-dash/src/server/routes/agents.mjs +134 -0
- package/bizar-dash/src/server/routes/artifacts.mjs +279 -0
- package/bizar-dash/src/server/routes/auth.mjs +69 -0
- package/bizar-dash/src/server/routes/background.mjs +165 -0
- package/bizar-dash/src/server/routes/chat.mjs +485 -0
- package/bizar-dash/src/server/routes/config.mjs +129 -0
- package/bizar-dash/src/server/routes/diagnostics.mjs +50 -0
- package/bizar-dash/src/server/routes/dialogs.mjs +86 -0
- package/bizar-dash/src/server/routes/fs.mjs +429 -0
- package/bizar-dash/src/server/routes/history.mjs +78 -0
- package/bizar-dash/src/server/routes/memory.mjs +658 -0
- package/bizar-dash/src/server/routes/misc.mjs +89 -0
- package/bizar-dash/src/server/routes/mods.mjs +359 -0
- package/bizar-dash/src/server/routes/notifications.mjs +54 -0
- package/bizar-dash/src/server/routes/obsidian.mjs +239 -0
- package/bizar-dash/src/server/routes/opencode-sessions.mjs +55 -0
- package/bizar-dash/src/server/routes/overview.mjs +91 -0
- package/bizar-dash/src/server/routes/pair.mjs +67 -0
- package/bizar-dash/src/server/routes/projects.mjs +175 -0
- package/bizar-dash/src/server/routes/providers.mjs +47 -0
- package/bizar-dash/src/server/routes/schedules.mjs +104 -0
- package/bizar-dash/src/server/routes/settings.mjs +51 -0
- package/bizar-dash/src/server/routes/skills.mjs +76 -0
- package/bizar-dash/src/server/routes/tasks.mjs +468 -0
- package/bizar-dash/src/server/routes/themes.mjs +51 -0
- package/bizar-dash/src/server/routes-v2/auth.mjs +59 -0
- package/bizar-dash/src/server/routes-v2/events.mjs +107 -0
- package/bizar-dash/src/server/routes-v2/health.mjs +21 -0
- package/bizar-dash/src/server/routes-v2/index.mjs +90 -0
- package/bizar-dash/src/server/routes-v2/sessions.mjs +111 -0
- package/bizar-dash/src/server/schedules-runner.mjs +369 -0
- package/bizar-dash/src/server/schedules-store.mjs +268 -0
- package/bizar-dash/src/server/search-store.mjs +220 -0
- package/bizar-dash/src/server/serve-info.mjs +684 -0
- package/bizar-dash/src/server/server.mjs +796 -0
- package/bizar-dash/src/server/settings-store.mjs +45 -0
- package/bizar-dash/src/server/skills-store.mjs +300 -0
- package/bizar-dash/src/server/state.mjs +437 -0
- package/bizar-dash/src/server/tailscale-store.mjs +113 -0
- package/bizar-dash/src/server/task-delegator.mjs +824 -0
- package/bizar-dash/src/server/tasks-store.mjs +508 -0
- package/bizar-dash/src/server/tui.mjs +923 -0
- package/bizar-dash/src/server/update-store.mjs +168 -0
- package/bizar-dash/src/server/v2-auth-file.mjs +99 -0
- package/bizar-dash/src/server/v2-event-bus.mjs +128 -0
- package/bizar-dash/src/server/watcher.mjs +81 -0
- package/bizar-dash/src/server/yaml.mjs +238 -0
- package/bizar-dash/src/web/App.tsx +718 -0
- package/bizar-dash/src/web/MobileApp.tsx +408 -0
- package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +95 -0
- package/bizar-dash/src/web/components/ArtifactListDialog.tsx +55 -0
- package/bizar-dash/src/web/components/ArtifactViewer.tsx +143 -0
- package/bizar-dash/src/web/components/AuditDialog.tsx +79 -0
- package/bizar-dash/src/web/components/BgStatusBadge.tsx +32 -0
- package/bizar-dash/src/web/components/Button.tsx +56 -0
- package/bizar-dash/src/web/components/CanvasContextMenu.tsx +68 -0
- package/bizar-dash/src/web/components/Card.tsx +40 -0
- package/bizar-dash/src/web/components/CollapsibleSection.tsx +72 -0
- package/bizar-dash/src/web/components/CommandDialog.tsx +66 -0
- package/bizar-dash/src/web/components/EmptyState.tsx +30 -0
- package/bizar-dash/src/web/components/FileBrowser.tsx +633 -0
- package/bizar-dash/src/web/components/HelpDialog.tsx +55 -0
- package/bizar-dash/src/web/components/KillConfirmDialog.tsx +88 -0
- package/bizar-dash/src/web/components/Modal.tsx +215 -0
- package/bizar-dash/src/web/components/Notifications.tsx +241 -0
- package/bizar-dash/src/web/components/SearchModal.tsx +224 -0
- package/bizar-dash/src/web/components/Sidebar.tsx +66 -0
- package/bizar-dash/src/web/components/Spinner.tsx +19 -0
- package/bizar-dash/src/web/components/StatusBadge.tsx +25 -0
- package/bizar-dash/src/web/components/Tag.tsx +28 -0
- package/bizar-dash/src/web/components/Toast.tsx +150 -0
- package/bizar-dash/src/web/components/Topbar.tsx +368 -0
- package/bizar-dash/src/web/components/VisualPlanDialog.tsx +58 -0
- package/bizar-dash/src/web/components/chat/AgentChip.tsx +68 -0
- package/bizar-dash/src/web/components/chat/ChatBubble.tsx +60 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +88 -0
- package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +50 -0
- package/bizar-dash/src/web/components/chat/Composer.tsx +164 -0
- package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +54 -0
- package/bizar-dash/src/web/components/chat/EmptyState.tsx +21 -0
- package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +18 -0
- package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +32 -0
- package/bizar-dash/src/web/components/chat/InfoPanel.tsx +119 -0
- package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +19 -0
- package/bizar-dash/src/web/components/chat/MessageBubble.tsx +60 -0
- package/bizar-dash/src/web/components/chat/SessionList.tsx +153 -0
- package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +9 -0
- package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +55 -0
- package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +36 -0
- package/bizar-dash/src/web/components/chat/index.ts +20 -0
- package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +18 -0
- package/bizar-dash/src/web/components/chat/useChat.ts +219 -0
- package/bizar-dash/src/web/components/chat/useSlashCommands.ts +47 -0
- package/bizar-dash/src/web/index.html +37 -0
- package/bizar-dash/src/web/lib/api.ts +193 -0
- package/bizar-dash/src/web/lib/markdown.tsx +59 -0
- package/bizar-dash/src/web/lib/types.ts +668 -0
- package/bizar-dash/src/web/lib/utils.ts +114 -0
- package/bizar-dash/src/web/lib/ws.ts +176 -0
- package/bizar-dash/src/web/main.tsx +34 -0
- package/bizar-dash/src/web/mobile/MobileApp.tsx +278 -0
- package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +38 -0
- package/bizar-dash/src/web/mobile/MobileTopbar.tsx +115 -0
- package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +182 -0
- package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +64 -0
- package/bizar-dash/src/web/mobile/components/MobileModal.tsx +123 -0
- package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +273 -0
- package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +374 -0
- package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +439 -0
- package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +143 -0
- package/bizar-dash/src/web/mobile/views/MobileChat.tsx +180 -0
- package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +175 -0
- package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +191 -0
- package/bizar-dash/src/web/mobile/views/MobileMods.tsx +149 -0
- package/bizar-dash/src/web/mobile/views/MobileMore.tsx +121 -0
- package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +137 -0
- package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +26 -0
- package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +685 -0
- package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +172 -0
- package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +388 -0
- package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +153 -0
- package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +564 -0
- package/bizar-dash/src/web/mobile.html +20 -0
- package/bizar-dash/src/web/mobile.tsx +13 -0
- package/bizar-dash/src/web/styles/chat.css +198 -0
- package/bizar-dash/src/web/styles/glyphs.css +960 -0
- package/bizar-dash/src/web/styles/main.css +8855 -0
- package/bizar-dash/src/web/styles/mobile-chat.css +5 -0
- package/bizar-dash/src/web/styles/mobile.css +1960 -0
- package/bizar-dash/src/web/views/Activity.tsx +1203 -0
- package/bizar-dash/src/web/views/Agents.tsx +701 -0
- package/bizar-dash/src/web/views/Artifacts.tsx +1480 -0
- package/bizar-dash/src/web/views/BackgroundAgents.tsx +450 -0
- package/bizar-dash/src/web/views/Chat.tsx +190 -0
- package/bizar-dash/src/web/views/Config.tsx +2063 -0
- package/bizar-dash/src/web/views/History.tsx +335 -0
- package/bizar-dash/src/web/views/ModView.tsx +207 -0
- package/bizar-dash/src/web/views/Mods.tsx +693 -0
- package/bizar-dash/src/web/views/Overview.tsx +812 -0
- package/bizar-dash/src/web/views/Providers.tsx +302 -0
- package/bizar-dash/src/web/views/Schedules.tsx +802 -0
- package/bizar-dash/src/web/views/Settings.tsx +1787 -0
- package/bizar-dash/src/web/views/Skills.tsx +431 -0
- package/bizar-dash/src/web/views/Tasks.tsx +1523 -0
- package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +613 -0
- package/bizar-dash/src/web/views/glyphs/components.tsx +1098 -0
- package/bizar-dash/templates/mod/FORMAT.md +76 -0
- package/bizar-dash/templates/mod/hello-mod/README.md +19 -0
- package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +8 -0
- package/bizar-dash/templates/mod/hello-mod/commands/hello.md +6 -0
- package/bizar-dash/templates/mod/hello-mod/mod.json +20 -0
- package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +9 -0
- package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +10 -0
- package/bizar-dash/tests/dashboard-ports.test.mjs +138 -0
- package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +90 -0
- package/bizar-dash/tests/memory-cli.test.mjs +542 -0
- package/bizar-dash/tests/memory-config.test.mjs +422 -0
- package/bizar-dash/tests/memory-git.test.mjs +246 -0
- package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
- package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -0
- package/bizar-dash/tests/memory-schema.test.mjs +198 -0
- package/bizar-dash/tests/memory-secrets.test.mjs +121 -0
- package/bizar-dash/tests/memory-store.test.mjs +390 -0
- package/bizar-dash/tests/memory-sync.test.mjs +141 -0
- package/bizar-dash/tests/mod-instructions.node.test.mjs +188 -0
- package/bizar-dash/tests/mod-security.test.mjs +273 -0
- package/bizar-dash/tests/mod-upgrade.node.test.mjs +357 -0
- package/bizar-dash/tests/no-agent-browser.node.test.mjs +98 -0
- package/bizar-dash/tests/obsidian-back-compat.test.mjs +199 -0
- package/bizar-dash/tests/opencode-runner.test.mjs +172 -0
- package/bizar-dash/tests/path-safe.test.mjs +108 -0
- package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +333 -0
- package/bizar-dash/tests/smoke-v2.mjs +212 -0
- package/bizar-dash/tests/strip-thinking.test.mjs +175 -0
- package/bizar-dash/tests/submit-feedback.test.mjs +353 -0
- package/bizar-dash/tests/tmux-wrap.test.mjs +145 -0
- package/bizar-dash/tests/yaml.test.mjs +128 -0
- package/bizar-dash/tsconfig.json +23 -0
- package/bizar-dash/vite.config.ts +27 -0
- package/cli/atomic.mjs +73 -0
- package/cli/banner.mjs +1 -1
- package/cli/bin.mjs +44 -47
- package/cli/bootstrap.mjs +1 -1
- package/cli/copy.mjs +25 -16
- package/cli/dev-link.mjs +17 -1
- package/cli/doctor.mjs +4 -4
- package/cli/doctor.test.mjs +2 -2
- package/cli/init.mjs +135 -1
- package/cli/install.mjs +31 -39
- package/cli/memory-constants.mjs +59 -0
- package/cli/memory.mjs +1436 -0
- package/cli/service.mjs +5 -6
- package/cli/utils.mjs +6 -3
- package/config/AGENTS.md +7 -7
- package/config/agents/_shared/AGENT_BASELINE.md +59 -61
- package/config/opencode.json +13 -38
- package/config/skills/memory-protocol/SKILL.md +105 -0
- package/config/skills/obsidian/SKILL.md +270 -65
- package/install.sh +59 -19
- package/package.json +49 -14
- package/packages/sdk/LICENSE +21 -0
- package/packages/sdk/README.md +80 -0
- package/packages/sdk/src/client.ts +183 -0
- package/packages/sdk/src/errors.ts +120 -0
- package/packages/sdk/src/events.ts +153 -0
- package/packages/sdk/src/index.ts +63 -0
- package/packages/sdk/src/types.ts +176 -0
- package/packages/sdk/src/version.ts +4 -0
- package/packages/sdk/tests/client.test.ts +217 -0
- package/packages/sdk/tests/errors.test.ts +108 -0
- package/packages/sdk/tests/events.test.ts +139 -0
- package/packages/sdk/tests/fixtures/fetch-mock.ts +152 -0
- package/packages/sdk/tests/fixtures/sse-mock.ts +30 -0
- package/packages/sdk/tsconfig.json +25 -0
- package/packages/sdk/vitest.config.ts +9 -0
- package/plugins/bizar/LICENSE +21 -0
- package/plugins/bizar/README.md +448 -0
- package/plugins/bizar/dist/index.js +29901 -0
- package/plugins/bizar/index.ts +1491 -0
- package/plugins/bizar/scripts/check-forbidden-imports.sh +33 -0
- package/plugins/bizar/src/background-state.ts +551 -0
- package/plugins/bizar/src/background.ts +1250 -0
- package/plugins/bizar/src/commands-impl.ts +464 -0
- package/plugins/bizar/src/commands.ts +1168 -0
- package/plugins/bizar/src/dashboard-client.ts +233 -0
- package/plugins/bizar/src/event-stream.ts +606 -0
- package/plugins/bizar/src/fingerprint.ts +120 -0
- package/plugins/bizar/src/handoff.ts +79 -0
- package/plugins/bizar/src/http-client.ts +467 -0
- package/plugins/bizar/src/key-rotation.ts +218 -0
- package/plugins/bizar/src/logger.ts +144 -0
- package/plugins/bizar/src/loop.ts +176 -0
- package/plugins/bizar/src/opencode-runner.ts +390 -0
- package/plugins/bizar/src/options.ts +421 -0
- package/plugins/bizar/src/plan-fs.ts +323 -0
- package/plugins/bizar/src/reasoning-clean.ts +454 -0
- package/plugins/bizar/src/report.ts +178 -0
- package/plugins/bizar/src/research-prompt.ts +35 -0
- package/plugins/bizar/src/serve-info.ts +228 -0
- package/plugins/bizar/src/serve.ts +496 -0
- package/plugins/bizar/src/settings.ts +349 -0
- package/plugins/bizar/src/state.ts +298 -0
- package/plugins/bizar/src/tools/bg-collect.ts +104 -0
- package/plugins/bizar/src/tools/bg-get-comments.ts +239 -0
- package/plugins/bizar/src/tools/bg-kill.ts +87 -0
- package/plugins/bizar/src/tools/bg-spawn.ts +401 -0
- package/plugins/bizar/src/tools/bg-status.ts +99 -0
- package/plugins/bizar/src/tools/open-kb.ts +191 -0
- package/plugins/bizar/src/tools/plan-action.ts +767 -0
- package/plugins/bizar/src/tools/read-glyph-feedback.ts +170 -0
- package/plugins/bizar/src/tools/wait-for-feedback.ts +402 -0
- package/plugins/bizar/tests/attach-handler-bug.test.ts +169 -0
- package/plugins/bizar/tests/background-state.test.ts +277 -0
- package/plugins/bizar/tests/background.test.ts +402 -0
- package/plugins/bizar/tests/block.test.ts +195 -0
- package/plugins/bizar/tests/canonical-key-order.test.ts +75 -0
- package/plugins/bizar/tests/commands-impl.test.ts +442 -0
- package/plugins/bizar/tests/commands.test.ts +584 -0
- package/plugins/bizar/tests/config.test.ts +122 -0
- package/plugins/bizar/tests/dashboard-client.test.ts +159 -0
- package/plugins/bizar/tests/dispose.test.ts +336 -0
- package/plugins/bizar/tests/event-stream.test.ts +409 -0
- package/plugins/bizar/tests/event.test.ts +262 -0
- package/plugins/bizar/tests/fingerprint.test.ts +162 -0
- package/plugins/bizar/tests/http-client.test.ts +404 -0
- package/plugins/bizar/tests/init-helpers.test.ts +203 -0
- package/plugins/bizar/tests/integration/slash-command.test.ts +348 -0
- package/plugins/bizar/tests/integration/tool-routing.test.ts +314 -0
- package/plugins/bizar/tests/key-rotation.test.ts +396 -0
- package/plugins/bizar/tests/loop.test.ts +397 -0
- package/plugins/bizar/tests/options.test.ts +276 -0
- package/plugins/bizar/tests/reasoning-clean.test.ts +422 -0
- package/plugins/bizar/tests/serve.test.ts +339 -0
- package/plugins/bizar/tests/settings.test.ts +351 -0
- package/plugins/bizar/tests/stall-think.test.ts +750 -0
- package/plugins/bizar/tests/state.test.ts +276 -0
- package/plugins/bizar/tests/tools/bg-collect.test.ts +337 -0
- package/plugins/bizar/tests/tools/bg-get-comments.test.ts +485 -0
- package/plugins/bizar/tests/tools/bg-kill.test.ts +235 -0
- package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +147 -0
- package/plugins/bizar/tests/tools/bg-spawn.test.ts +311 -0
- package/plugins/bizar/tests/tools/bg-status.test.ts +217 -0
- package/plugins/bizar/tests/tools/opencode-runner.test.ts +115 -0
- package/plugins/bizar/tests/tools/plan-action.test.ts +599 -0
- package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +253 -0
- package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +390 -0
- package/plugins/bizar/tests/update-deadlock.test.ts +145 -0
- 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
|
+
}
|