@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,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/obsidian.mjs
|
|
3
|
+
*
|
|
4
|
+
* v2 — Backward-compatible rewrite. When `.bizar/memory.json` exists, this
|
|
5
|
+
* router delegates to memory-store.mjs. When memory is not configured, it falls
|
|
6
|
+
* back to the original obsidian-store.mjs behaviour.
|
|
7
|
+
*
|
|
8
|
+
* CRITICAL: Preserve legacy response shapes exactly. Do not change the JSON
|
|
9
|
+
* structure returned by any endpoint.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Router } from 'express';
|
|
13
|
+
import { join, dirname } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const SERVER_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
|
+
|
|
18
|
+
// Lazy imports so circular deps are avoided and we only load memory-store.mjs
|
|
19
|
+
// when it is actually needed.
|
|
20
|
+
let _memoryStore = null;
|
|
21
|
+
let _obsidianStore = null;
|
|
22
|
+
let _wrap = null;
|
|
23
|
+
|
|
24
|
+
async function getMemoryStore() {
|
|
25
|
+
if (!_memoryStore) {
|
|
26
|
+
_memoryStore = await import(`${SERVER_ROOT}/memory-store.mjs`).then((m) => m);
|
|
27
|
+
}
|
|
28
|
+
return _memoryStore;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function getObsidianStore() {
|
|
32
|
+
if (!_obsidianStore) {
|
|
33
|
+
_obsidianStore = await import(`${SERVER_ROOT}/obsidian-store.mjs`).then((m) => m);
|
|
34
|
+
}
|
|
35
|
+
return _obsidianStore;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function getShared() {
|
|
39
|
+
if (!_wrap) {
|
|
40
|
+
const mod = await import('./_shared.mjs').then((m) => m);
|
|
41
|
+
_wrap = mod.wrap;
|
|
42
|
+
}
|
|
43
|
+
return { wrap: _wrap };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns true if the project has opted into the memory service via
|
|
48
|
+
* `.bizar/memory.json`.
|
|
49
|
+
*/
|
|
50
|
+
async function hasMemoryConfig(projectRoot) {
|
|
51
|
+
const { safeReadJSON } = await import(`${SERVER_ROOT}/yaml.mjs`).then((m) => m);
|
|
52
|
+
// Use safeReadJSON from yaml.mjs is expensive — use simpler check
|
|
53
|
+
const { existsSync } = await import('node:fs');
|
|
54
|
+
const configPath = join(projectRoot, '.bizar', 'memory.json');
|
|
55
|
+
return existsSync(configPath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function createObsidianRouter({ projectRoot }) {
|
|
59
|
+
const router = Router();
|
|
60
|
+
const { wrap } = await getShared();
|
|
61
|
+
const memoryEnabled = await hasMemoryConfig(projectRoot);
|
|
62
|
+
|
|
63
|
+
if (memoryEnabled) {
|
|
64
|
+
// ── Memory service is active — delegate to memory-store.mjs ──────────────
|
|
65
|
+
const ms = await getMemoryStore();
|
|
66
|
+
|
|
67
|
+
// GET /api/obsidian — vault stats
|
|
68
|
+
router.get('/obsidian', wrap(async (_req, res) => {
|
|
69
|
+
const { vaultRoot, mode } = ms.resolveVault(projectRoot);
|
|
70
|
+
const { existsSync, readdirSync, statSync, readFileSync } = await import('node:fs');
|
|
71
|
+
if (!existsSync(vaultRoot)) {
|
|
72
|
+
res.json({ exists: false, vaultDir: vaultRoot, noteCount: 0, totalSize: 0 });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const notes = ms.listNotes(projectRoot);
|
|
76
|
+
const totalSize = notes.reduce((acc, n) => acc + n.size, 0);
|
|
77
|
+
const folders = new Set(notes.map((n) => n.relPath.split('/')[0]));
|
|
78
|
+
res.json({
|
|
79
|
+
exists: true,
|
|
80
|
+
vaultDir: vaultRoot,
|
|
81
|
+
noteCount: notes.length,
|
|
82
|
+
totalSize,
|
|
83
|
+
folderCount: folders.size,
|
|
84
|
+
folders: [...folders].sort(),
|
|
85
|
+
lastModified: notes[0]?.mtime || null,
|
|
86
|
+
});
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
// POST /api/obsidian — init vault
|
|
90
|
+
router.post('/obsidian', wrap(async (_req, res) => {
|
|
91
|
+
const result = ms.initVault(projectRoot);
|
|
92
|
+
res.json({ vaultDir: result.vaultRoot, created: result.created });
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
// GET /api/obsidian/notes — legacy shape: { notes: [{ path, relPath, mtime, size }] }
|
|
96
|
+
router.get('/obsidian/notes', wrap(async (_req, res) => {
|
|
97
|
+
const notes = ms.listNotes(projectRoot);
|
|
98
|
+
// Return legacy shape
|
|
99
|
+
const legacyNotes = notes.map((n) => ({
|
|
100
|
+
path: n.relPath,
|
|
101
|
+
relPath: n.relPath,
|
|
102
|
+
mtime: n.mtime,
|
|
103
|
+
size: n.size,
|
|
104
|
+
}));
|
|
105
|
+
res.json({ notes: legacyNotes });
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
// POST /api/obsidian/notes — write note
|
|
109
|
+
// CRITICAL: returns 201 with FULL note shape { relPath, frontmatter, body, raw, mtime, size }
|
|
110
|
+
router.post('/obsidian/notes', wrap(async (req, res) => {
|
|
111
|
+
const { path: relPath, frontmatter, body } = req.body || {};
|
|
112
|
+
if (!relPath || typeof relPath !== 'string') {
|
|
113
|
+
res.status(400).json({ error: 'bad_request', message: 'path is required' });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!relPath.endsWith('.md')) {
|
|
117
|
+
res.status(400).json({ error: 'bad_request', message: 'path must end in .md' });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const note = ms.writeNote(projectRoot, relPath, { frontmatter: frontmatter || {}, body: body || '' });
|
|
122
|
+
// Return FULL note shape (legacy behavior)
|
|
123
|
+
res.status(201).json(note);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
res.status(400).json({ error: 'bad_request', message: err.message });
|
|
126
|
+
}
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
// GET /api/obsidian/notes/:path — single note
|
|
130
|
+
router.get('/obsidian/notes/*', wrap(async (req, res) => {
|
|
131
|
+
const relPath = req.params[0];
|
|
132
|
+
const note = ms.readNote(projectRoot, relPath);
|
|
133
|
+
if (!note) {
|
|
134
|
+
res.status(404).json({ error: 'not_found' });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
res.json(note);
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
// DELETE /api/obsidian/notes/:path
|
|
141
|
+
router.delete('/obsidian/notes/*', wrap(async (req, res) => {
|
|
142
|
+
const relPath = req.params[0];
|
|
143
|
+
const ok = ms.deleteNote(projectRoot, relPath);
|
|
144
|
+
if (!ok) {
|
|
145
|
+
res.status(404).json({ error: 'not_found' });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
res.status(204).end();
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
// GET /api/obsidian/search?q=...
|
|
152
|
+
router.get('/obsidian/search', wrap(async (req, res) => {
|
|
153
|
+
const q = req.query.q || '';
|
|
154
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 25, 100);
|
|
155
|
+
const results = ms.searchVault(projectRoot, q, { limit });
|
|
156
|
+
res.json({ query: q, results });
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
// POST /api/obsidian/index — rebuild index
|
|
160
|
+
router.post('/obsidian/index', wrap(async (_req, res) => {
|
|
161
|
+
// No-op in memory mode (INDEX.md is not maintained)
|
|
162
|
+
res.json({ ok: true, indexPath: null });
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
} else {
|
|
166
|
+
// ── Legacy obsidian-store.mjs behaviour ───────────────────────────────────
|
|
167
|
+
const os = await getObsidianStore();
|
|
168
|
+
|
|
169
|
+
router.get('/obsidian', wrap(async (_req, res) => {
|
|
170
|
+
res.json(os.vaultStats(projectRoot));
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
router.post('/obsidian', wrap(async (_req, res) => {
|
|
174
|
+
const result = os.initObsidianVault(projectRoot);
|
|
175
|
+
res.json(result);
|
|
176
|
+
}));
|
|
177
|
+
|
|
178
|
+
router.get('/obsidian/notes', wrap(async (_req, res) => {
|
|
179
|
+
const notes = os.listVaultNotes(projectRoot);
|
|
180
|
+
res.json({ notes });
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
router.post('/obsidian/notes', wrap(async (req, res) => {
|
|
184
|
+
const { path: relPath, frontmatter, body } = req.body || {};
|
|
185
|
+
if (!relPath || typeof relPath !== 'string') {
|
|
186
|
+
res.status(400).json({ error: 'bad_request', message: 'path is required' });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!relPath.endsWith('.md')) {
|
|
190
|
+
res.status(400).json({ error: 'bad_request', message: 'path must end in .md' });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const note = os.writeVaultNote(projectRoot, relPath, { frontmatter, body });
|
|
195
|
+
res.status(201).json(note);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
res.status(400).json({ error: 'bad_request', message: err.message });
|
|
198
|
+
}
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
router.get('/obsidian/notes/*', wrap(async (req, res) => {
|
|
202
|
+
const relPath = req.params[0];
|
|
203
|
+
const note = os.readVaultNote(projectRoot, relPath);
|
|
204
|
+
if (!note) {
|
|
205
|
+
res.status(404).json({ error: 'not_found' });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
res.json(note);
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
router.delete('/obsidian/notes/*', wrap(async (req, res) => {
|
|
212
|
+
const relPath = req.params[0];
|
|
213
|
+
const ok = os.deleteVaultNote(projectRoot, relPath);
|
|
214
|
+
if (!ok) {
|
|
215
|
+
res.status(404).json({ error: 'not_found' });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
res.status(204).end();
|
|
219
|
+
}));
|
|
220
|
+
|
|
221
|
+
router.get('/obsidian/search', wrap(async (req, res) => {
|
|
222
|
+
const q = req.query.q;
|
|
223
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 25, 100);
|
|
224
|
+
const results = os.searchVault(projectRoot, q, { limit });
|
|
225
|
+
res.json({ query: q, results });
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
router.post('/obsidian/index', wrap(async (_req, res) => {
|
|
229
|
+
const indexPath = os.rebuildIndex(projectRoot);
|
|
230
|
+
if (!indexPath) {
|
|
231
|
+
res.status(404).json({ error: 'not_initialized' });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
res.json({ ok: true, indexPath });
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return router;
|
|
239
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/opencode-sessions.mjs
|
|
3
|
+
*
|
|
4
|
+
* /api/opencode-sessions — reads active sessions from the opencode
|
|
5
|
+
* SQLite database (~/.local/share/opencode/opencode.db) and returns
|
|
6
|
+
* them in a format compatible with the dashboard's session list.
|
|
7
|
+
*
|
|
8
|
+
* Sessions are filtered to non-archived (time_archived IS NULL) and
|
|
9
|
+
* ordered by most recently updated.
|
|
10
|
+
*/
|
|
11
|
+
import { Router } from 'express';
|
|
12
|
+
import Database from 'better-sqlite3';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { wrap } from './_shared.mjs';
|
|
16
|
+
|
|
17
|
+
const DB_PATH = join(homedir(), '.local', 'share', 'opencode', 'opencode.db');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @returns {import('express').Router}
|
|
21
|
+
*/
|
|
22
|
+
export function createOpencodeSessionsRouter() {
|
|
23
|
+
const router = Router();
|
|
24
|
+
|
|
25
|
+
router.get('/opencode-sessions', wrap(async (_req, res) => {
|
|
26
|
+
let db;
|
|
27
|
+
try {
|
|
28
|
+
db = new Database(DB_PATH, { readonly: true, fileMustExist: true });
|
|
29
|
+
const rows = db.prepare(`
|
|
30
|
+
SELECT id, title, project_id, time_created, time_updated
|
|
31
|
+
FROM session
|
|
32
|
+
WHERE time_archived IS NULL
|
|
33
|
+
ORDER BY time_updated DESC
|
|
34
|
+
LIMIT 200
|
|
35
|
+
`).all();
|
|
36
|
+
return res.json({
|
|
37
|
+
sessions: rows.map((r) => ({
|
|
38
|
+
id: r.id,
|
|
39
|
+
title: r.title,
|
|
40
|
+
project: r.project_id,
|
|
41
|
+
created: r.time_created,
|
|
42
|
+
updated: r.time_updated,
|
|
43
|
+
mtime: r.time_updated,
|
|
44
|
+
})),
|
|
45
|
+
});
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// DB might not exist yet, or permissions issue — return empty list
|
|
48
|
+
return res.json({ sessions: [], error: err.message });
|
|
49
|
+
} finally {
|
|
50
|
+
if (db) db.close();
|
|
51
|
+
}
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
return router;
|
|
55
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/overview.mjs
|
|
3
|
+
*
|
|
4
|
+
* /api/snapshot — full snapshot (everything the Overview card needs)
|
|
5
|
+
* /api/overview — overview-only
|
|
6
|
+
* /api/health — { ok: true, ts }
|
|
7
|
+
* /api/restart (POST) — self-respawn
|
|
8
|
+
*/
|
|
9
|
+
import { Router } from 'express';
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { projectsStore } from '../projects-store.mjs';
|
|
13
|
+
import { tasksStore } from '../tasks-store.mjs';
|
|
14
|
+
import { agentsStore } from '../agents-store.mjs';
|
|
15
|
+
import { schedulesStore } from '../schedules-store.mjs';
|
|
16
|
+
import { providersStore, mcpsStore } from '../providers-store.mjs';
|
|
17
|
+
import { modsLoader } from '../mods-loader.mjs';
|
|
18
|
+
import {
|
|
19
|
+
OPENCODE_JSON,
|
|
20
|
+
readSettings,
|
|
21
|
+
safeReadJSON,
|
|
22
|
+
wrap,
|
|
23
|
+
} from './_shared.mjs';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} deps
|
|
27
|
+
* @param {object} deps.state
|
|
28
|
+
* @returns {import('express').Router}
|
|
29
|
+
*/
|
|
30
|
+
export function createOverviewRouter({ state }) {
|
|
31
|
+
const router = Router();
|
|
32
|
+
|
|
33
|
+
router.get('/snapshot', wrap(async (_req, res) => {
|
|
34
|
+
res.json(buildSnapshot());
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
router.get('/overview', wrap(async (_req, res) => {
|
|
38
|
+
res.json(state.getOverview());
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
router.get('/health', (_req, res) => res.json({ ok: true, ts: Date.now() }));
|
|
42
|
+
|
|
43
|
+
// v3.5.3 — Self-respawn: starts a new dashboard process in the
|
|
44
|
+
// background and exits this one. The BIZAR_AUTO_RESPAWN=1 env var
|
|
45
|
+
// marks it as an automated restart (vs a user-initiated one).
|
|
46
|
+
router.post('/restart', (req, res) => {
|
|
47
|
+
res.json({ restarting: true });
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
const child = spawn(
|
|
50
|
+
process.execPath,
|
|
51
|
+
[process.argv[1], ...process.argv.slice(2)],
|
|
52
|
+
{
|
|
53
|
+
detached: true,
|
|
54
|
+
stdio: 'ignore',
|
|
55
|
+
env: { ...process.env, BIZAR_AUTO_RESPAWN: '1' },
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
child.on('error', (err) => {
|
|
59
|
+
console.error('[dashboard] restart spawn failed:', err.message);
|
|
60
|
+
});
|
|
61
|
+
child.unref();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}, 500);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
function buildSnapshot() {
|
|
67
|
+
const cfg = safeReadJSON(OPENCODE_JSON, null);
|
|
68
|
+
const active = projectsStore.active();
|
|
69
|
+
return {
|
|
70
|
+
overview: state.getOverview(),
|
|
71
|
+
agents: agentsStore.list(),
|
|
72
|
+
artifacts: state.getArtifacts(),
|
|
73
|
+
projects: projectsStore.list().projects,
|
|
74
|
+
activeProject: active,
|
|
75
|
+
config: {
|
|
76
|
+
path: OPENCODE_JSON,
|
|
77
|
+
data: cfg,
|
|
78
|
+
raw: cfg === null ? '' : JSON.stringify(cfg, null, 2),
|
|
79
|
+
exists: existsSync(OPENCODE_JSON),
|
|
80
|
+
},
|
|
81
|
+
settings: readSettings(),
|
|
82
|
+
tasks: active ? tasksStore.loadTasks(active.id) : [],
|
|
83
|
+
mods: modsLoader.list(),
|
|
84
|
+
schedules: active ? schedulesStore.list(active.id) : [],
|
|
85
|
+
providers: providersStore.list(),
|
|
86
|
+
mcps: mcpsStore.list(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return router;
|
|
91
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/pair.mjs
|
|
3
|
+
*
|
|
4
|
+
* Companion-app pairing (v3.5.2).
|
|
5
|
+
*
|
|
6
|
+
* /api/pair/start (POST) — mint a short-lived token, return QR payload
|
|
7
|
+
* /api/pair/verify (GET) — companion confirms a token before persisting
|
|
8
|
+
* /api/pair/status (GET) — count of currently active pair tokens
|
|
9
|
+
*
|
|
10
|
+
* The pair token is unrelated to the v3.6.0 dashboard auth token;
|
|
11
|
+
* it's a separate short-lived bearer that's minted on demand for
|
|
12
|
+
* the companion QR scan flow.
|
|
13
|
+
*/
|
|
14
|
+
import { Router } from 'express';
|
|
15
|
+
import { pairStore } from '../pair-store.mjs';
|
|
16
|
+
import { wrap } from './_shared.mjs';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} deps
|
|
20
|
+
* @param {object} deps.state
|
|
21
|
+
* @param {Function} deps.broadcast
|
|
22
|
+
* @returns {import('express').Router}
|
|
23
|
+
*/
|
|
24
|
+
export function createPairRouter({ state, broadcast }) {
|
|
25
|
+
const router = Router();
|
|
26
|
+
|
|
27
|
+
router.post('/pair/start', wrap(async (req, res) => {
|
|
28
|
+
const port = req.app?.get('port') || (req.socket?.server?.address()?.port) || 4321;
|
|
29
|
+
const publicUrl = pairStore.detectPublicUrl(req, port);
|
|
30
|
+
const ttlMs = Math.max(30_000, Math.min(15 * 60 * 1000, Number(req.body?.ttlMs) || 5 * 60 * 1000));
|
|
31
|
+
const entry = pairStore.mint(publicUrl, ttlMs);
|
|
32
|
+
state.appendActivity({ kind: 'pair.start', publicUrl: entry.publicUrl, expiresAt: entry.expiresAt });
|
|
33
|
+
broadcast({ type: 'pair:change', publicUrl: entry.publicUrl, expiresAt: entry.expiresAt });
|
|
34
|
+
res.json({
|
|
35
|
+
token: entry.token,
|
|
36
|
+
qrPayload: entry.qrPayload,
|
|
37
|
+
publicUrl: entry.publicUrl,
|
|
38
|
+
expiresAt: entry.expiresAt,
|
|
39
|
+
});
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Used by the companion right after scanning the QR to confirm the token
|
|
43
|
+
// works before persisting it. Returns { valid: true } on success, 401
|
|
44
|
+
// { error: 'expired' | 'no_token' } on failure.
|
|
45
|
+
router.get('/pair/verify', wrap(async (req, res) => {
|
|
46
|
+
const auth = req.headers.authorization || '';
|
|
47
|
+
if (!auth.startsWith('Bearer ')) {
|
|
48
|
+
res.status(401).json({ error: 'no_token', message: 'missing Authorization: Bearer' });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const token = auth.slice(7).trim();
|
|
52
|
+
const entry = pairStore.verify(token);
|
|
53
|
+
if (!entry) {
|
|
54
|
+
res.status(401).json({ error: 'expired', message: 'pair token invalid or expired' });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
res.json({ valid: true, publicUrl: entry.publicUrl, expiresAt: entry.expiresAt });
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Lightweight introspection — useful for the Settings card and the TUI
|
|
61
|
+
// to show "X tokens active". Always returns { active: <count> }.
|
|
62
|
+
router.get('/pair/status', wrap(async (_req, res) => {
|
|
63
|
+
res.json({ active: pairStore._size() });
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
return router;
|
|
67
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/projects.mjs
|
|
3
|
+
*
|
|
4
|
+
* /api/projects — list
|
|
5
|
+
* /api/projects (POST) — add by path
|
|
6
|
+
* /api/projects/:id/activate (POST) — set active
|
|
7
|
+
* /api/projects/:id (DELETE) — remove
|
|
8
|
+
* /api/projects/refresh (POST) — re-scan projects.json
|
|
9
|
+
* /api/projects/auto-detect (POST) — auto-add the server's cwd
|
|
10
|
+
* /api/projects/active/tasks — tasks for the active project
|
|
11
|
+
* /api/projects/active/schedules — schedules for the active project
|
|
12
|
+
* /api/projects/active/mods — mods for the active project
|
|
13
|
+
* /api/projects/active/state — state.json for the active project
|
|
14
|
+
*/
|
|
15
|
+
import { Router } from 'express';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
import { projectsStore } from '../projects-store.mjs';
|
|
19
|
+
import { tasksStore } from '../tasks-store.mjs';
|
|
20
|
+
import { schedulesStore } from '../schedules-store.mjs';
|
|
21
|
+
import { readActiveProjectId, safeReadJSON, wrap, readSettings } from './_shared.mjs';
|
|
22
|
+
import { buildAllowedRootsFromSettings, resolveSafePath } from '../lib/path-safe.mjs';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {object} deps
|
|
26
|
+
* @param {object} deps.state
|
|
27
|
+
* @param {Function} deps.broadcast
|
|
28
|
+
* @param {string} deps.projectRoot
|
|
29
|
+
* @returns {import('express').Router}
|
|
30
|
+
*/
|
|
31
|
+
export function createProjectsRouter({ state, broadcast, projectRoot }) {
|
|
32
|
+
const router = Router();
|
|
33
|
+
|
|
34
|
+
router.get('/projects', wrap(async (_req, res) => {
|
|
35
|
+
res.json(projectsStore.list());
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
router.post('/projects', wrap(async (req, res) => {
|
|
39
|
+
const path = (req.body?.path || '').trim();
|
|
40
|
+
const name = req.body?.name || null;
|
|
41
|
+
if (!path) {
|
|
42
|
+
res.status(400).json({ error: 'bad_request', message: 'path is required' });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const project = projectsStore.add(path, name);
|
|
46
|
+
state.appendActivity({ kind: 'project.add', id: project.id, path: project.path });
|
|
47
|
+
res.status(201).json(project);
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
router.post('/projects/:id/activate', wrap(async (req, res) => {
|
|
51
|
+
const activated = projectsStore.activate(req.params.id);
|
|
52
|
+
if (!activated) {
|
|
53
|
+
res.status(404).json({ error: 'not_found' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
state.appendActivity({ kind: 'project.activate', id: activated.id });
|
|
57
|
+
broadcast({ type: 'project:change', project: activated });
|
|
58
|
+
res.json(activated);
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
router.delete('/projects/:id', wrap(async (req, res) => {
|
|
62
|
+
projectsStore.remove(req.params.id);
|
|
63
|
+
broadcast({ type: 'project:change' });
|
|
64
|
+
res.status(204).end();
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
router.post('/projects/refresh', wrap(async (_req, res) => {
|
|
68
|
+
res.json(projectsStore.list());
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
// v3.0.4 — Manual auto-detect trigger for the "Use current directory"
|
|
72
|
+
// button on the Overview. Auto-detects the server's `projectRoot` and
|
|
73
|
+
// returns the (possibly updated) registry. Idempotent.
|
|
74
|
+
router.post('/projects/auto-detect', wrap(async (_req, res) => {
|
|
75
|
+
const detected = projectsStore.autoDetect({ cwd: projectRoot });
|
|
76
|
+
if (detected) {
|
|
77
|
+
state.appendActivity({ kind: 'project.auto-detect', id: detected.id, path: detected.path });
|
|
78
|
+
broadcast({ type: 'project:change', project: detected });
|
|
79
|
+
}
|
|
80
|
+
res.json(projectsStore.list());
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
// v3.6.0 — Scan a configured `dashboard.projectsDirectory` for
|
|
84
|
+
// project roots and add any newly-detected ones to the registry.
|
|
85
|
+
// Idempotent: already-registered paths are skipped silently.
|
|
86
|
+
//
|
|
87
|
+
// v3.11.0 — The allow-list now also includes every entry in
|
|
88
|
+
// `dashboard.allowedRoots` (in addition to home and
|
|
89
|
+
// `projectsDirectory`). The configured `projectsDirectory` is
|
|
90
|
+
// re-validated against the full allow-list before scanning — a
|
|
91
|
+
// tampered settings file can't widen the boundary.
|
|
92
|
+
router.post('/projects/scan', wrap(async (_req, res) => {
|
|
93
|
+
const settings = readSettings().data || {};
|
|
94
|
+
const configured = settings.dashboard?.projectsDirectory;
|
|
95
|
+
if (typeof configured !== 'string' || !configured.trim()) {
|
|
96
|
+
res.status(400).json({
|
|
97
|
+
error: 'bad_request',
|
|
98
|
+
message: 'dashboard.projectsDirectory is not configured',
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const allowedRoots = buildAllowedRootsFromSettings({ settings, home: homedir() });
|
|
103
|
+
const safeRoot = resolveSafePath(configured, allowedRoots);
|
|
104
|
+
if (!safeRoot) {
|
|
105
|
+
res.status(403).json({
|
|
106
|
+
error: 'forbidden',
|
|
107
|
+
message: 'projectsDirectory is outside the allowed roots',
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
result = await projectsStore.scanDirectory(safeRoot);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
res.status(500).json({ error: 'scan_failed', message: err?.message || String(err) });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (result.error) {
|
|
119
|
+
// scanDirectory returns `{ error }` on a soft failure (bad
|
|
120
|
+
// root, etc.). Surface as 400 so the client can show it.
|
|
121
|
+
res.status(400).json({ error: 'scan_failed', message: result.error });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Broadcast each newly added project so connected clients refresh.
|
|
125
|
+
for (const p of result.added) {
|
|
126
|
+
state.appendActivity({ kind: 'project.scan', id: p.id, path: p.path });
|
|
127
|
+
broadcast({ type: 'project:change', kind: 'added', project: p });
|
|
128
|
+
}
|
|
129
|
+
res.json({
|
|
130
|
+
added: result.added,
|
|
131
|
+
skipped: result.skipped,
|
|
132
|
+
scanned: result.scanned,
|
|
133
|
+
});
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
// ── /api/projects/active/<entity> ──────────────────────────────────────
|
|
137
|
+
router.get('/projects/active/tasks', wrap(async (_req, res) => {
|
|
138
|
+
const active = projectsStore.active();
|
|
139
|
+
if (!active) {
|
|
140
|
+
res.json({ tasks: [], projectId: null });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
res.json({ tasks: tasksStore.loadTasks(active.id), projectId: active.id });
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
router.get('/projects/active/schedules', wrap(async (_req, res) => {
|
|
147
|
+
const active = projectsStore.active();
|
|
148
|
+
if (!active) {
|
|
149
|
+
res.json({ schedules: [], projectId: null });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
res.json({ schedules: schedulesStore.list(active.id), projectId: active.id });
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
router.get('/projects/active/mods', wrap(async (_req, res) => {
|
|
156
|
+
const active = projectsStore.active();
|
|
157
|
+
if (!active) {
|
|
158
|
+
res.json({ mods: [], projectId: null });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
res.json({ mods: [], projectId: active.id });
|
|
162
|
+
}));
|
|
163
|
+
|
|
164
|
+
router.get('/projects/active/state', wrap(async (_req, res) => {
|
|
165
|
+
const active = projectsStore.active();
|
|
166
|
+
if (!active) {
|
|
167
|
+
res.json({ state: {}, projectId: null });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const stateFile = join(projectsStore.projectDir(active.id), 'state.json');
|
|
171
|
+
res.json({ state: safeReadJSON(stateFile, {}) || {}, projectId: active.id });
|
|
172
|
+
}));
|
|
173
|
+
|
|
174
|
+
return router;
|
|
175
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/providers.mjs
|
|
3
|
+
*
|
|
4
|
+
* /api/providers — aggregated list (opencode.json + agent frontmatter + serve HTTP)
|
|
5
|
+
* /api/providers/active — current default provider + model
|
|
6
|
+
*
|
|
7
|
+
* The dashboard's Overview card uses these to show what the user
|
|
8
|
+
* can run. The /api/config/providers surface (which CRUDs the
|
|
9
|
+
* registry store) lives in config.mjs.
|
|
10
|
+
*/
|
|
11
|
+
import { Router } from 'express';
|
|
12
|
+
import { providersStore } from '../providers-store.mjs';
|
|
13
|
+
import { wrap } from './_shared.mjs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @returns {import('express').Router}
|
|
17
|
+
*/
|
|
18
|
+
export function createProvidersRouter() {
|
|
19
|
+
const router = Router();
|
|
20
|
+
|
|
21
|
+
// Surface-everything endpoint the dashboard uses to populate the
|
|
22
|
+
// Providers card on the Overview tab. Reads from opencode.json +
|
|
23
|
+
// agent frontmatter + (best-effort) the running opencode serve HTTP
|
|
24
|
+
// API, so the list is non-empty even on installs that don't declare
|
|
25
|
+
// a top-level `provider` key in opencode.json.
|
|
26
|
+
router.get('/providers', wrap(async (_req, res) => {
|
|
27
|
+
const providers = await providersStore.listAll();
|
|
28
|
+
res.json({ providers, count: providers.length });
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
// Active default provider + model. null when nothing is configured.
|
|
32
|
+
router.get('/providers/active', wrap(async (_req, res) => {
|
|
33
|
+
const active = await providersStore.getActive();
|
|
34
|
+
res.json(active || { providerId: null, modelId: null, source: null });
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
// v3.16.0 — Auto-detect providers from env + config. Best-effort;
|
|
38
|
+
// a timeout/failure on one provider does not block the others.
|
|
39
|
+
// Query: ?probe=0 to skip the /models probe (faster, less informative).
|
|
40
|
+
router.get('/providers/auto-detect', wrap(async (req, res) => {
|
|
41
|
+
const probe = req.query.probe !== '0';
|
|
42
|
+
const results = await providersStore.autoDetect({ probe });
|
|
43
|
+
res.json({ providers: results, count: results.length });
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
return router;
|
|
47
|
+
}
|