@polderlabs/bizar 3.23.0 → 4.0.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 +72 -2
- 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 +249 -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 +424 -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 +499 -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 +1603 -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-git.test.mjs +138 -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/bin.mjs +43 -46
- package/cli/copy.mjs +3 -0
- package/cli/dev-link.mjs +17 -1
- package/cli/init.mjs +135 -1
- package/cli/install.mjs +10 -23
- package/cli/memory-constants.mjs +59 -0
- package/cli/memory.mjs +757 -0
- package/cli/service.mjs +5 -6
- package/config/skills/obsidian/SKILL.md +213 -65
- package/install.sh +48 -18
- 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 +1484 -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 +1127 -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/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 +548 -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,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
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/schedules.mjs
|
|
3
|
+
*
|
|
4
|
+
* /api/schedules — list
|
|
5
|
+
* /api/schedules (POST) — add
|
|
6
|
+
* /api/schedules/:id (PUT) — full update
|
|
7
|
+
* /api/schedules/:id (PATCH) — partial update (toggle fields)
|
|
8
|
+
* /api/schedules/:id/run (POST) — run once now
|
|
9
|
+
* /api/schedules/:id/trigger (POST) — alias for /run (mobile uses this)
|
|
10
|
+
* /api/schedules/:id (DELETE) — remove
|
|
11
|
+
*
|
|
12
|
+
* PATCH exists because the desktop UI's "toggle enabled" and the mobile
|
|
13
|
+
* UI's edit flow both want to send partial payloads without losing the
|
|
14
|
+
* rest of the schedule.
|
|
15
|
+
*/
|
|
16
|
+
import { Router } from 'express';
|
|
17
|
+
import { schedulesStore } from '../schedules-store.mjs';
|
|
18
|
+
import { schedulesRunner } from '../schedules-runner.mjs';
|
|
19
|
+
import { readActiveProjectId, wrap } from './_shared.mjs';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} deps
|
|
23
|
+
* @param {Function} deps.broadcast
|
|
24
|
+
* @returns {import('express').Router}
|
|
25
|
+
*/
|
|
26
|
+
export function createSchedulesRouter({ broadcast }) {
|
|
27
|
+
const router = Router();
|
|
28
|
+
|
|
29
|
+
router.get('/schedules', wrap(async (req, res) => {
|
|
30
|
+
const projectId = req.query.projectId || readActiveProjectId();
|
|
31
|
+
res.json(schedulesStore.list(projectId || 'default'));
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
router.post('/schedules', wrap(async (req, res) => {
|
|
35
|
+
const projectId = req.body?.projectId || readActiveProjectId() || 'default';
|
|
36
|
+
const sched = schedulesStore.add(projectId, req.body || {});
|
|
37
|
+
broadcast({ type: 'schedules:change' });
|
|
38
|
+
res.status(201).json(sched);
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
router.put('/schedules/:id', wrap(async (req, res) => {
|
|
42
|
+
const projectId = req.body?.projectId || readActiveProjectId() || 'default';
|
|
43
|
+
const sched = schedulesStore.update(projectId, req.params.id, req.body || {});
|
|
44
|
+
if (!sched) {
|
|
45
|
+
res.status(404).json({ error: 'not_found' });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
broadcast({ type: 'schedules:change' });
|
|
49
|
+
res.json(sched);
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
// v3.9.0 — Partial update. The mobile UI PATCHes a single field at a
|
|
53
|
+
// time (toggle enabled, rename, etc.) so a PUT that required the full
|
|
54
|
+
// shape was impractical. PATCH is semantically correct here.
|
|
55
|
+
router.patch('/schedules/:id', wrap(async (req, res) => {
|
|
56
|
+
const projectId = req.body?.projectId || readActiveProjectId() || 'default';
|
|
57
|
+
const sched = schedulesStore.update(projectId, req.params.id, req.body || {});
|
|
58
|
+
if (!sched) {
|
|
59
|
+
res.status(404).json({ error: 'not_found' });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
broadcast({ type: 'schedules:change' });
|
|
63
|
+
res.json(sched);
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
router.post('/schedules/:id/run', wrap(async (req, res) => {
|
|
67
|
+
const projectId = req.body?.projectId || readActiveProjectId() || 'default';
|
|
68
|
+
const sched = schedulesStore.get(projectId, req.params.id);
|
|
69
|
+
if (!sched) {
|
|
70
|
+
res.status(404).json({ error: 'not_found' });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const result = await schedulesRunner.runOne(projectId, sched);
|
|
74
|
+
broadcast({ type: 'schedules:change' });
|
|
75
|
+
res.json(result);
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// v3.9.0 — Mobile uses /trigger; keep it as an alias of /run so
|
|
79
|
+
// desktop and mobile share the same handler.
|
|
80
|
+
router.post('/schedules/:id/trigger', wrap(async (req, res) => {
|
|
81
|
+
const projectId = req.body?.projectId || readActiveProjectId() || 'default';
|
|
82
|
+
const sched = schedulesStore.get(projectId, req.params.id);
|
|
83
|
+
if (!sched) {
|
|
84
|
+
res.status(404).json({ error: 'not_found' });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const result = await schedulesRunner.runOne(projectId, sched);
|
|
88
|
+
broadcast({ type: 'schedules:change' });
|
|
89
|
+
res.json(result);
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
router.delete('/schedules/:id', wrap(async (req, res) => {
|
|
93
|
+
const projectId = req.query.projectId || readActiveProjectId() || 'default';
|
|
94
|
+
const ok = schedulesStore.remove(projectId, req.params.id);
|
|
95
|
+
if (!ok) {
|
|
96
|
+
res.status(404).json({ error: 'not_found' });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
broadcast({ type: 'schedules:change' });
|
|
100
|
+
res.status(204).end();
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
return router;
|
|
104
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/settings.mjs
|
|
3
|
+
*
|
|
4
|
+
* /api/settings — read settings.json
|
|
5
|
+
* /api/settings (PUT) — write (merged with defaults)
|
|
6
|
+
* /api/settings/reset (POST) — reset to defaults
|
|
7
|
+
*/
|
|
8
|
+
import { Router } from 'express';
|
|
9
|
+
import { DEFAULT_SETTINGS, readSettings, writeSettings, wrap } from './_shared.mjs';
|
|
10
|
+
import { readPluginOptions, writePluginOptions } from '../settings-store.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} deps
|
|
14
|
+
* @param {object} deps.state
|
|
15
|
+
* @param {Function} deps.broadcast
|
|
16
|
+
* @returns {import('express').Router}
|
|
17
|
+
*/
|
|
18
|
+
export function createSettingsRouter({ state, broadcast }) {
|
|
19
|
+
const router = Router();
|
|
20
|
+
|
|
21
|
+
router.get('/settings', wrap(async (_req, res) => {
|
|
22
|
+
res.json(readSettings());
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
router.put('/settings', wrap(async (req, res) => {
|
|
26
|
+
const updated = writeSettings(req.body || {});
|
|
27
|
+
state.appendActivity({ kind: 'settings.update' });
|
|
28
|
+
broadcast({ type: 'settings:change', settings: updated.data });
|
|
29
|
+
res.json(updated);
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
router.post('/settings/reset', wrap(async (_req, res) => {
|
|
33
|
+
const updated = writeSettings(DEFAULT_SETTINGS);
|
|
34
|
+
broadcast({ type: 'settings:change', settings: updated.data });
|
|
35
|
+
res.json(updated);
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// --- Plugin-options persistence for the Background Agents card ----------
|
|
39
|
+
|
|
40
|
+
router.get('/settings/plugin-options', wrap(async (_req, res) => {
|
|
41
|
+
res.json(readPluginOptions() || {});
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
router.put('/settings/plugin-options', wrap(async (req, res) => {
|
|
45
|
+
const result = writePluginOptions(req.body || {});
|
|
46
|
+
if (broadcast) broadcast({ type: 'settings:plugin-options:changed' });
|
|
47
|
+
res.json(result);
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
return router;
|
|
51
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/skills.mjs
|
|
3
|
+
*
|
|
4
|
+
* /api/skills — list (?category=foo to filter)
|
|
5
|
+
* /api/skills/search — fuzzy search by name/description
|
|
6
|
+
* /api/skills/install (POST) — install by name or source URL
|
|
7
|
+
* /api/skills/:id/disable (POST) — disable
|
|
8
|
+
* /api/skills/:id/enable (POST) — enable
|
|
9
|
+
*/
|
|
10
|
+
import { Router } from 'express';
|
|
11
|
+
import { skillsStore } from '../skills-store.mjs';
|
|
12
|
+
import { wrap } from './_shared.mjs';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} deps
|
|
16
|
+
* @param {Function} deps.broadcast
|
|
17
|
+
* @returns {import('express').Router}
|
|
18
|
+
*/
|
|
19
|
+
export function createSkillsRouter({ broadcast }) {
|
|
20
|
+
const router = Router();
|
|
21
|
+
|
|
22
|
+
// v3.3.0 — `?category=foo` filters the list server-side. The
|
|
23
|
+
// frontend uses this to power the collapsible categories view
|
|
24
|
+
// (the old "all in one list" mode is now the default "Show all"
|
|
25
|
+
// toggle in the UI).
|
|
26
|
+
router.get('/skills', wrap(async (req, res) => {
|
|
27
|
+
const skills = await skillsStore.list();
|
|
28
|
+
let out = skills;
|
|
29
|
+
if (req.query.category) {
|
|
30
|
+
const wanted = String(req.query.category).toLowerCase();
|
|
31
|
+
if (wanted !== 'all') {
|
|
32
|
+
out = skills.filter((s) => (s.category || '').toLowerCase() === wanted);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
res.json({ skills: out, categories: skillsStore.CATEGORIES, total: skills.length });
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
router.get('/skills/search', wrap(async (req, res) => {
|
|
39
|
+
const q = (req.query.q || '').toString();
|
|
40
|
+
const results = await skillsStore.search(q);
|
|
41
|
+
res.json({ results, query: q, categories: skillsStore.CATEGORIES });
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
router.post('/skills/install', wrap(async (req, res) => {
|
|
45
|
+
const { name, source } = req.body || {};
|
|
46
|
+
if (!name && !source) {
|
|
47
|
+
res.status(400).json({ error: 'bad_request', message: 'name or source is required' });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const result = await skillsStore.install(name, source);
|
|
52
|
+
if (!result.ok) {
|
|
53
|
+
res.status(502).json({ error: 'install_failed', message: result.error || 'skills CLI install failed' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
broadcast({ type: 'skills:change' });
|
|
57
|
+
res.status(202).json(result);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
res.status(500).json({ error: 'install_failed', message: err.message });
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
router.post('/skills/:id/disable', wrap(async (req, res) => {
|
|
64
|
+
const out = skillsStore.disable(req.params.id);
|
|
65
|
+
broadcast({ type: 'skills:change' });
|
|
66
|
+
res.json(out);
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
router.post('/skills/:id/enable', wrap(async (req, res) => {
|
|
70
|
+
const out = skillsStore.enable(req.params.id);
|
|
71
|
+
broadcast({ type: 'skills:change' });
|
|
72
|
+
res.json(out);
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
return router;
|
|
76
|
+
}
|