@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,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
|
+
}
|