@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
package/cli/memory.mjs
ADDED
|
@@ -0,0 +1,1436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/memory.mjs
|
|
3
|
+
*
|
|
4
|
+
* `bizar memory` subcommands. Delegates to memory-store.mjs and memory-git.mjs.
|
|
5
|
+
* Supports: init, status, link, unlink, write, pull, commit, push, sync,
|
|
6
|
+
* reindex, conflicts, doctor.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join, dirname } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
// Memory service modules (ESM, shared with bizar-dash)
|
|
16
|
+
const SERVER_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', 'bizar-dash', 'src', 'server');
|
|
17
|
+
const memoryStore = await import(`${SERVER_ROOT}/memory-store.mjs`).then((m) => m);
|
|
18
|
+
const memorySchema = await import(`${SERVER_ROOT}/memory-schema.mjs`).then((m) => m);
|
|
19
|
+
const memorySecrets = await import(`${SERVER_ROOT}/memory-secrets.mjs`).then((m) => m);
|
|
20
|
+
const memoryGit = await import(`${SERVER_ROOT}/memory-git.mjs`).then((m) => m);
|
|
21
|
+
const memoryLightrag = await import(`${SERVER_ROOT}/memory-lightrag.mjs`).then((m) => m);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the project root. Assumes CWD is the project root.
|
|
25
|
+
*/
|
|
26
|
+
function getProjectRoot() {
|
|
27
|
+
return process.cwd();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate a git remote URL. Accepts HTTPS, SSH (scp-style), and SSH URL form.
|
|
32
|
+
* Rejects file://, plain paths, and other schemes.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} url
|
|
35
|
+
* @returns {{ valid: true, kind: 'ssh'|'https'|'ssh-url' } | { valid: false, error: string }}
|
|
36
|
+
*/
|
|
37
|
+
export function validateRemoteUrl(url) {
|
|
38
|
+
if (typeof url !== 'string' || url.length === 0) {
|
|
39
|
+
return { valid: false, error: 'URL must be a non-empty string' };
|
|
40
|
+
}
|
|
41
|
+
const trimmed = url.trim();
|
|
42
|
+
// SSH scp-style: git@host:path
|
|
43
|
+
if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:.+$/.test(trimmed)) {
|
|
44
|
+
return { valid: true, kind: 'ssh' };
|
|
45
|
+
}
|
|
46
|
+
// SSH URL form: ssh://[user@]host[:port]/path
|
|
47
|
+
if (/^ssh:\/\/(?:[a-zA-Z0-9._-]+@)?[a-zA-Z0-9._-]+(?::\d+)?\/.+$/.test(trimmed)) {
|
|
48
|
+
return { valid: true, kind: 'ssh-url' };
|
|
49
|
+
}
|
|
50
|
+
// HTTPS: https://host/path
|
|
51
|
+
if (/^https:\/\/[a-zA-Z0-9._-]+(?::\d+)?\/.+$/.test(trimmed)) {
|
|
52
|
+
return { valid: true, kind: 'https' };
|
|
53
|
+
}
|
|
54
|
+
return { valid: false, error: 'URL must be ssh://, https://, or git@host:path form' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Helper formatters ────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
function success(msg) { console.log(chalk.green('✓'), msg); }
|
|
60
|
+
function info(msg) { console.log(chalk.blue('ℹ'), msg); }
|
|
61
|
+
function warn(msg) { console.log(chalk.yellow('⚠'), msg); }
|
|
62
|
+
function error(msg) { console.error(chalk.red('✗'), msg); }
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Print a key-value row: ` key … value`
|
|
66
|
+
*/
|
|
67
|
+
function kv(key, value) {
|
|
68
|
+
const pad = 20;
|
|
69
|
+
const dots = Math.max(2, pad - key.length);
|
|
70
|
+
console.log(` ${key}${'.'.repeat(dots)} ${value}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Subcommand implementations ───────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `bizar memory write <relpath> [--type …] [--status …] [--confidence …]`
|
|
77
|
+
* `[--tag <tag>]… [--body <body>|--body-file <file>]`
|
|
78
|
+
* `[--title <title>] [--json]`
|
|
79
|
+
*
|
|
80
|
+
* Write a single note to the vault. Validates the path, type, status, and
|
|
81
|
+
* confidence against the schema, builds frontmatter via `defaultFrontmatter`,
|
|
82
|
+
* then delegates to `memoryStore.writeNote`. Designed for agents (and humans)
|
|
83
|
+
* that want to write memory notes without going through the dashboard UI.
|
|
84
|
+
*/
|
|
85
|
+
async function cmdWrite(args) {
|
|
86
|
+
const projectRoot = getProjectRoot();
|
|
87
|
+
const { writeNote, loadConfig } = memoryStore;
|
|
88
|
+
const { defaultFrontmatter, VALID_TYPES, VALID_STATUSES, VALID_CONFIDENCES } = memorySchema;
|
|
89
|
+
|
|
90
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
91
|
+
console.log(`
|
|
92
|
+
Usage: bizar memory write <relpath> [options]
|
|
93
|
+
|
|
94
|
+
Write a single note to the vault. <relpath> must end in .md and is
|
|
95
|
+
resolved relative to the project namespace root (e.g.
|
|
96
|
+
decisions/0001-foo.md), NOT prefixed with the namespace.
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
--type <type> One of: ${VALID_TYPES.join(', ')}
|
|
100
|
+
(default: project_overview)
|
|
101
|
+
--status <status> One of: ${VALID_STATUSES.join(', ')}
|
|
102
|
+
(default: active)
|
|
103
|
+
--confidence <conf> One of: ${VALID_CONFIDENCES.join(', ')}
|
|
104
|
+
(default: verified)
|
|
105
|
+
--tag <tag> Tag to attach (may be passed multiple times)
|
|
106
|
+
--title <title> Frontmatter title field
|
|
107
|
+
--body <text> Note body as a string
|
|
108
|
+
--body-file <path> Path to a file containing the body
|
|
109
|
+
(exactly one of --body / --body-file is allowed)
|
|
110
|
+
--json Emit the full returned note as JSON
|
|
111
|
+
--help, -h Show this help
|
|
112
|
+
|
|
113
|
+
Exactly one of --body or --body-file is required, unless you intend
|
|
114
|
+
to write an empty body (in which case omit both).
|
|
115
|
+
`.trim());
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse positional relpath (first non-flag arg)
|
|
120
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
121
|
+
const relpath = positional[0];
|
|
122
|
+
if (!relpath) {
|
|
123
|
+
error('relpath is required: `bizar memory write <relpath>`');
|
|
124
|
+
info('run `bizar memory write --help` for usage');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
if (!relpath.endsWith('.md')) {
|
|
128
|
+
error(`relpath must end in .md: ${relpath}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Parse flags ──────────────────────────────────────────────────────────
|
|
133
|
+
const optVal = (name) => {
|
|
134
|
+
const i = args.indexOf(name);
|
|
135
|
+
if (i === -1) return undefined;
|
|
136
|
+
const v = args[i + 1];
|
|
137
|
+
if (!v || v.startsWith('-')) return undefined;
|
|
138
|
+
return v;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const type = optVal('--type') ?? 'project_overview';
|
|
142
|
+
if (!VALID_TYPES.includes(type)) {
|
|
143
|
+
error(`invalid --type: '${type}'`);
|
|
144
|
+
info(`must be one of: ${VALID_TYPES.join(', ')}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const status = optVal('--status') ?? 'active';
|
|
149
|
+
if (!VALID_STATUSES.includes(status)) {
|
|
150
|
+
error(`invalid --status: '${status}'`);
|
|
151
|
+
info(`must be one of: ${VALID_STATUSES.join(', ')}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const confidence = optVal('--confidence') ?? 'verified';
|
|
156
|
+
if (!VALID_CONFIDENCES.includes(confidence)) {
|
|
157
|
+
error(`invalid --confidence: '${confidence}'`);
|
|
158
|
+
info(`must be one of: ${VALID_CONFIDENCES.join(', ')}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const title = optVal('--title');
|
|
163
|
+
|
|
164
|
+
// Multi-value --tag
|
|
165
|
+
const tags = [];
|
|
166
|
+
for (let i = 0; i < args.length; i++) {
|
|
167
|
+
if (args[i] === '--tag' && args[i + 1] && !args[i + 1].startsWith('-')) {
|
|
168
|
+
tags.push(args[i + 1]);
|
|
169
|
+
i++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const inlineBody = optVal('--body');
|
|
174
|
+
const bodyFile = optVal('--body-file');
|
|
175
|
+
if (inlineBody !== undefined && bodyFile !== undefined) {
|
|
176
|
+
error('use either --body or --body-file, not both');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let body = '';
|
|
181
|
+
if (bodyFile) {
|
|
182
|
+
try {
|
|
183
|
+
body = readFileSync(bodyFile, 'utf8');
|
|
184
|
+
} catch (err) {
|
|
185
|
+
error(`failed to read --body-file ${bodyFile}: ${err.message}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
} else if (inlineBody !== undefined) {
|
|
189
|
+
body = inlineBody;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Build frontmatter ───────────────────────────────────────────────────
|
|
193
|
+
const { config } = loadConfig(projectRoot);
|
|
194
|
+
const projectId = config.projectId || projectRoot.split('/').pop() || 'unknown';
|
|
195
|
+
|
|
196
|
+
const frontmatter = defaultFrontmatter({
|
|
197
|
+
type,
|
|
198
|
+
project_id: projectId,
|
|
199
|
+
status,
|
|
200
|
+
confidence,
|
|
201
|
+
tags,
|
|
202
|
+
});
|
|
203
|
+
if (title) frontmatter.title = title;
|
|
204
|
+
|
|
205
|
+
// ── Write ───────────────────────────────────────────────────────────────
|
|
206
|
+
let result;
|
|
207
|
+
try {
|
|
208
|
+
result = writeNote(projectRoot, relpath, { frontmatter, body });
|
|
209
|
+
} catch (err) {
|
|
210
|
+
if (err.code === 'SCHEMA_VALIDATION_FAILED') {
|
|
211
|
+
error(`schema validation failed: ${err.message.replace(/^schema validation failed: /, '')}`);
|
|
212
|
+
} else if (err.code === 'SECRET_DETECTED') {
|
|
213
|
+
error(err.message);
|
|
214
|
+
} else {
|
|
215
|
+
error(err.message);
|
|
216
|
+
}
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── Output ──────────────────────────────────────────────────────────────
|
|
221
|
+
if (args.includes('--json')) {
|
|
222
|
+
console.log(JSON.stringify(result, null, 2));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
success(`wrote ${result.relPath}`);
|
|
227
|
+
console.log();
|
|
228
|
+
console.log(chalk.bold(' Note'));
|
|
229
|
+
console.log(' ─────────────────────────────');
|
|
230
|
+
kv('relpath', result.relPath);
|
|
231
|
+
kv('type', String(result.frontmatter.type));
|
|
232
|
+
kv('status', String(result.frontmatter.status));
|
|
233
|
+
kv('confidence', String(result.frontmatter.confidence));
|
|
234
|
+
kv('memory_id', String(result.frontmatter.memory_id));
|
|
235
|
+
if (Array.isArray(result.frontmatter.tags) && result.frontmatter.tags.length > 0) {
|
|
236
|
+
kv('tags', result.frontmatter.tags.join(', '));
|
|
237
|
+
}
|
|
238
|
+
if (title) kv('title', title);
|
|
239
|
+
kv('size', `${result.size} bytes`);
|
|
240
|
+
console.log();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* `bizar memory init`
|
|
245
|
+
* Creates .bizar/memory.json with mode=managed (default).
|
|
246
|
+
* Pass --memory-mode local-only to use local vault storage instead.
|
|
247
|
+
*
|
|
248
|
+
* Flags (added for install.sh bootstrap):
|
|
249
|
+
* --yes Accept all defaults (skip prompts, skip if already initialized)
|
|
250
|
+
* --memory-mode MODE Either "local-only" or "managed" (bypasses --managed alias)
|
|
251
|
+
* --memory-repo-name Repo name / path for managed mode (bypasses --repo alias)
|
|
252
|
+
*/
|
|
253
|
+
async function cmdInit(args) {
|
|
254
|
+
const projectRoot = getProjectRoot();
|
|
255
|
+
const { loadConfig, saveConfig } = memoryStore;
|
|
256
|
+
|
|
257
|
+
const yesFlag = args.includes('--yes');
|
|
258
|
+
|
|
259
|
+
const existing = loadConfig(projectRoot);
|
|
260
|
+
if (existing.exists) {
|
|
261
|
+
if (yesFlag) {
|
|
262
|
+
// Bootstrap mode: skip silently if already initialized
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
warn('memory config already exists — run `bizar memory status` to see current config');
|
|
266
|
+
const mode = existing.config.mode || 'local-only';
|
|
267
|
+
info(`current mode: ${mode}, projectId: ${existing.config.projectId}`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// --memory-mode takes precedence over --managed
|
|
272
|
+
let mode = 'managed';
|
|
273
|
+
const modeIdx = args.indexOf('--memory-mode');
|
|
274
|
+
if (modeIdx !== -1 && args[modeIdx + 1]) {
|
|
275
|
+
mode = args[modeIdx + 1];
|
|
276
|
+
} else if (args.includes('--managed')) {
|
|
277
|
+
mode = 'managed';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let repoName = '';
|
|
281
|
+
|
|
282
|
+
if (mode === 'managed') {
|
|
283
|
+
// --memory-repo-name takes precedence over --repo
|
|
284
|
+
const repoArg = args.indexOf('--memory-repo-name');
|
|
285
|
+
repoName = repoArg !== -1 ? args[repoArg + 1] : '';
|
|
286
|
+
if (!repoName) {
|
|
287
|
+
const legacyRepoArg = args.indexOf('--repo');
|
|
288
|
+
repoName = legacyRepoArg !== -1 ? args[legacyRepoArg + 1] : '';
|
|
289
|
+
}
|
|
290
|
+
if (!repoName) {
|
|
291
|
+
// Ask via inquirer-style prompt (use readline)
|
|
292
|
+
const rl = await import('node:readline').then((m) =>
|
|
293
|
+
m.createInterface({ input: process.stdin, output: process.stdout })
|
|
294
|
+
);
|
|
295
|
+
repoName = await new Promise((resolve) =>
|
|
296
|
+
rl.question(chalk.cyan(' repo name (e.g. my-org/memory): '), resolve)
|
|
297
|
+
);
|
|
298
|
+
rl.close();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const home = homedir();
|
|
303
|
+
const projectId = projectRoot.split('/').pop() || 'project';
|
|
304
|
+
const managedPath = mode === 'managed'
|
|
305
|
+
? join(home, '.local', 'share', 'bizar', 'memory', repoName || 'bizar-memory')
|
|
306
|
+
: null;
|
|
307
|
+
const vaultPath = mode === 'managed' ? managedPath : join(projectRoot, '.obsidian');
|
|
308
|
+
|
|
309
|
+
const config = {
|
|
310
|
+
version: 1,
|
|
311
|
+
backend: 'bizar-local',
|
|
312
|
+
projectId,
|
|
313
|
+
memoryRepo: {
|
|
314
|
+
mode,
|
|
315
|
+
path: vaultPath,
|
|
316
|
+
remote: null,
|
|
317
|
+
branch: 'main',
|
|
318
|
+
namespace: mode === 'managed' ? `projects/${projectId}` : null,
|
|
319
|
+
},
|
|
320
|
+
namespaces: {
|
|
321
|
+
project: `projects/${projectId}`,
|
|
322
|
+
global: 'global/bizar',
|
|
323
|
+
user: `users/${process.env.USER || process.env.USERNAME || 'local'}`,
|
|
324
|
+
},
|
|
325
|
+
lightrag: {
|
|
326
|
+
enabled: false,
|
|
327
|
+
host: '127.0.0.1',
|
|
328
|
+
port: 9621,
|
|
329
|
+
workingDir: join(projectRoot, '.bizar', 'lightrag'),
|
|
330
|
+
},
|
|
331
|
+
git: {
|
|
332
|
+
autoPullOnSessionStart: false,
|
|
333
|
+
autoCommitOnMemoryWrite: false,
|
|
334
|
+
autoPushOnSessionEnd: false,
|
|
335
|
+
commitAuthor: 'Bizar Memory <bizar-memory@local>',
|
|
336
|
+
commitMessageTemplate: `memory(${projectId}): {summary}`,
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const result = saveConfig(projectRoot, config);
|
|
341
|
+
if (!result.ok) {
|
|
342
|
+
error(`failed to write config: ${result.error}`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const { initVault } = memoryStore;
|
|
347
|
+
const vaultResult = initVault(projectRoot);
|
|
348
|
+
success(`initialized memory config`);
|
|
349
|
+
info(`mode: ${mode}`);
|
|
350
|
+
info(`vault: ${vaultResult.vaultRoot}`);
|
|
351
|
+
if (vaultResult.created.length > 0) {
|
|
352
|
+
for (const c of vaultResult.created) info(` created: ${c}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* `bizar memory setup` — configure or reconfigure the memory vault.
|
|
358
|
+
*
|
|
359
|
+
* v4.2.0 — first-class bootstrap entry point. Two flows:
|
|
360
|
+
* - No existing config → behaves like `init` + adds the remote (managed mode).
|
|
361
|
+
* - Existing config → optionally updates the remote URL on a managed vault.
|
|
362
|
+
*
|
|
363
|
+
* Flags:
|
|
364
|
+
* --mode <managed|local-only> Vault mode (default: managed)
|
|
365
|
+
* --remote <url> Git remote URL (required for managed)
|
|
366
|
+
* --repo-name <name> Vault directory name (default: bizar-memory)
|
|
367
|
+
* --non-interactive No prompts; use flags or defaults
|
|
368
|
+
* --yes Same as --non-interactive (backward compat)
|
|
369
|
+
*
|
|
370
|
+
* The remote URL is written to BOTH `memoryRepo.remote` (canonical,
|
|
371
|
+
* read by resolveVault) and the top-level `gitRemote` (read by cmdPush).
|
|
372
|
+
* Both must agree or `bizar memory push` will fail silently.
|
|
373
|
+
*
|
|
374
|
+
* Connectivity is checked via `memoryGit.lsRemote` after `addRemote`. A
|
|
375
|
+
* failure here is informational only — credentials may not be configured
|
|
376
|
+
* yet — and does NOT abort the setup.
|
|
377
|
+
*
|
|
378
|
+
* @param {string[]} argv
|
|
379
|
+
*/
|
|
380
|
+
export async function cmdSetup(argv) {
|
|
381
|
+
const projectRoot = getProjectRoot();
|
|
382
|
+
const { loadConfig, saveConfig, initVault, resolveVault } = memoryStore;
|
|
383
|
+
|
|
384
|
+
// ── Parse flags ──────────────────────────────────────────────────────────
|
|
385
|
+
const optVal = (name) => {
|
|
386
|
+
const i = argv.indexOf(name);
|
|
387
|
+
if (i === -1) return undefined;
|
|
388
|
+
const v = argv[i + 1];
|
|
389
|
+
if (!v || v.startsWith('-')) return undefined;
|
|
390
|
+
return v;
|
|
391
|
+
};
|
|
392
|
+
const hasFlag = (name) => argv.includes(name);
|
|
393
|
+
|
|
394
|
+
const nonInteractive = hasFlag('--non-interactive') || hasFlag('--yes');
|
|
395
|
+
|
|
396
|
+
// --mode (default managed). Accept --memory-mode alias too.
|
|
397
|
+
let mode = 'managed';
|
|
398
|
+
const modeRaw = optVal('--mode') ?? optVal('--memory-mode');
|
|
399
|
+
if (modeRaw) mode = modeRaw;
|
|
400
|
+
else if (hasFlag('--local-only')) mode = 'local-only';
|
|
401
|
+
else if (hasFlag('--managed')) mode = 'managed';
|
|
402
|
+
if (mode !== 'managed' && mode !== 'local-only') {
|
|
403
|
+
error(`invalid --mode: '${mode}' (must be 'managed' or 'local-only')`);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// --remote
|
|
408
|
+
let remote = optVal('--remote');
|
|
409
|
+
if (remote !== undefined) {
|
|
410
|
+
const v = validateRemoteUrl(remote);
|
|
411
|
+
if (!v.valid) {
|
|
412
|
+
error(`invalid --remote URL: ${v.error}`);
|
|
413
|
+
info('examples:');
|
|
414
|
+
info(' --remote git@github.com:user/repo.git');
|
|
415
|
+
info(' --remote ssh://git@github.com/user/repo.git');
|
|
416
|
+
info(' --remote https://github.com/user/repo.git');
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
// Store the trimmed canonical form
|
|
420
|
+
remote = remote.trim();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// --repo-name (or --memory-repo-name, --repo)
|
|
424
|
+
const repoName = optVal('--repo-name') ?? optVal('--memory-repo-name') ?? optVal('--repo') ?? 'bizar-memory';
|
|
425
|
+
|
|
426
|
+
// ── Help ────────────────────────────────────────────────────────────────
|
|
427
|
+
if (hasFlag('--help') || hasFlag('-h')) {
|
|
428
|
+
console.log(`
|
|
429
|
+
Usage: bizar memory setup [--remote <url>] [--mode <mode>] [--non-interactive]
|
|
430
|
+
|
|
431
|
+
Configure or reconfigure this project's memory vault.
|
|
432
|
+
|
|
433
|
+
Options:
|
|
434
|
+
--mode <managed|local-only> Vault mode (default: managed)
|
|
435
|
+
--remote <url> Git remote URL (required for managed)
|
|
436
|
+
Accepts: ssh://, https://, git@host:path
|
|
437
|
+
--repo-name <name> Vault directory name (default: bizar-memory)
|
|
438
|
+
--local-only Shortcut for --mode local-only
|
|
439
|
+
--non-interactive Skip prompts; use flags or defaults
|
|
440
|
+
--yes Alias for --non-interactive
|
|
441
|
+
--help, -h Show this help
|
|
442
|
+
|
|
443
|
+
Examples:
|
|
444
|
+
# Bootstrap: first-time init with a remote
|
|
445
|
+
bizar memory setup --non-interactive \\
|
|
446
|
+
--remote git@github.com:org/bizar-memory.git
|
|
447
|
+
|
|
448
|
+
# Reconfigure the remote on an existing managed vault
|
|
449
|
+
bizar memory setup --non-interactive \\
|
|
450
|
+
--remote https://github.com/org/bizar-memory.git
|
|
451
|
+
|
|
452
|
+
# Convert to local-only (removes remote but keeps notes)
|
|
453
|
+
bizar memory setup --non-interactive --local-only
|
|
454
|
+
`.trim());
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ── Read existing config ────────────────────────────────────────────────
|
|
459
|
+
const { config: existingCfg, exists: configExists } = loadConfig(projectRoot);
|
|
460
|
+
|
|
461
|
+
// Decide whether this is a bootstrap (no config) or reconfigure (config
|
|
462
|
+
// already exists). The branch shape drives the rest of the function.
|
|
463
|
+
const isBootstrap = !configExists;
|
|
464
|
+
|
|
465
|
+
// Reconfigure path: if no --remote was passed AND --mode wasn't changed,
|
|
466
|
+
// just print status and exit. Otherwise apply the change.
|
|
467
|
+
if (!isBootstrap) {
|
|
468
|
+
const existingMode = existingCfg.memoryRepo?.mode || existingCfg.mode || 'local-only';
|
|
469
|
+
const existingRemote = existingCfg.memoryRepo?.remote || existingCfg.gitRemote || '';
|
|
470
|
+
const modeChanged = mode !== existingMode;
|
|
471
|
+
const remoteChanged = remote !== undefined && remote !== existingRemote;
|
|
472
|
+
|
|
473
|
+
if (!modeChanged && !remoteChanged) {
|
|
474
|
+
// Nothing to do — print status and exit
|
|
475
|
+
console.log(chalk.bold('\n Memory setup (no changes)'));
|
|
476
|
+
console.log(' ─────────────────────────────');
|
|
477
|
+
success('already configured');
|
|
478
|
+
kv('mode', existingMode);
|
|
479
|
+
kv('remote', existingRemote || '(none)');
|
|
480
|
+
kv('vault', existingCfg.memoryRepo?.path || existingCfg.repoName || '(unknown)');
|
|
481
|
+
console.log();
|
|
482
|
+
info(`run \`bizar memory setup --remote <url>\` to change the remote`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ── Reconfigure path — apply in place ───────────────────────────────
|
|
487
|
+
const updatedCfg = { ...existingCfg };
|
|
488
|
+
|
|
489
|
+
if (modeChanged && remote === undefined) {
|
|
490
|
+
// Switching modes without a new remote is fine, but warn if going
|
|
491
|
+
// managed without a URL to push to.
|
|
492
|
+
if (mode === 'managed' && !existingRemote) {
|
|
493
|
+
warn('switching to managed mode with no remote configured — push will fail');
|
|
494
|
+
info('pass --remote <url> to set one');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (remote !== undefined) {
|
|
499
|
+
// Dual-write: top-level + memoryRepo.*
|
|
500
|
+
updatedCfg.gitRemote = remote;
|
|
501
|
+
updatedCfg.memoryRepo = {
|
|
502
|
+
...(existingCfg.memoryRepo || {}),
|
|
503
|
+
mode: mode === 'managed' ? 'managed' : (updatedCfg.memoryRepo?.mode || 'local-only'),
|
|
504
|
+
remote,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (modeChanged) {
|
|
508
|
+
updatedCfg.mode = mode;
|
|
509
|
+
if (updatedCfg.memoryRepo) {
|
|
510
|
+
updatedCfg.memoryRepo.mode = mode;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const saveResult = saveConfig(projectRoot, updatedCfg);
|
|
515
|
+
if (!saveResult.ok) {
|
|
516
|
+
error(`failed to save config: ${saveResult.error}`);
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// If we now have a remote on a managed vault, register it.
|
|
521
|
+
if (mode === 'managed' && remote) {
|
|
522
|
+
addRemoteToVault(projectRoot, remote);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
printSetupResult({ mode, remote, vaultPath: updatedCfg.memoryRepo?.path });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ── Bootstrap path (no existing config) ─────────────────────────────────
|
|
530
|
+
|
|
531
|
+
// For managed bootstrap, we need a remote. If missing, prompt or error.
|
|
532
|
+
if (mode === 'managed' && !remote) {
|
|
533
|
+
if (nonInteractive) {
|
|
534
|
+
error('--remote is required for --mode managed in non-interactive setup');
|
|
535
|
+
info('pass --remote <url> (or run without --non-interactive to be prompted)');
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
// Prompt via readline
|
|
539
|
+
const readline = await import('node:readline');
|
|
540
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
541
|
+
const answer = await new Promise((resolve) => {
|
|
542
|
+
rl.question(chalk.cyan(' remote URL (ssh://, https://, or git@host:path): '), resolve);
|
|
543
|
+
});
|
|
544
|
+
rl.close();
|
|
545
|
+
const trimmed = (answer || '').trim();
|
|
546
|
+
if (!trimmed) {
|
|
547
|
+
error('a remote URL is required for managed mode');
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
const v = validateRemoteUrl(trimmed);
|
|
551
|
+
if (!v.valid) {
|
|
552
|
+
error(`invalid remote URL: ${v.error}`);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
remote = trimmed;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Build a config object mirroring cmdInit's shape, with the remote set.
|
|
559
|
+
const home = homedir();
|
|
560
|
+
const projectId = projectRoot.split('/').pop() || 'project';
|
|
561
|
+
const vaultPath = mode === 'managed'
|
|
562
|
+
? join(home, '.local', 'share', 'bizar', 'memory', repoName)
|
|
563
|
+
: join(projectRoot, '.obsidian');
|
|
564
|
+
|
|
565
|
+
const cfg = {
|
|
566
|
+
version: 1,
|
|
567
|
+
backend: 'bizar-local',
|
|
568
|
+
projectId,
|
|
569
|
+
memoryRepo: {
|
|
570
|
+
mode,
|
|
571
|
+
path: vaultPath,
|
|
572
|
+
remote: mode === 'managed' ? (remote || null) : null,
|
|
573
|
+
branch: 'main',
|
|
574
|
+
namespace: mode === 'managed' ? `projects/${projectId}` : null,
|
|
575
|
+
},
|
|
576
|
+
namespaces: {
|
|
577
|
+
project: `projects/${projectId}`,
|
|
578
|
+
global: 'global/bizar',
|
|
579
|
+
user: `users/${process.env.USER || process.env.USERNAME || 'local'}`,
|
|
580
|
+
},
|
|
581
|
+
lightrag: {
|
|
582
|
+
enabled: false,
|
|
583
|
+
host: '127.0.0.1',
|
|
584
|
+
port: 9621,
|
|
585
|
+
workingDir: join(projectRoot, '.bizar', 'lightrag'),
|
|
586
|
+
},
|
|
587
|
+
git: {
|
|
588
|
+
autoPullOnSessionStart: false,
|
|
589
|
+
autoCommitOnMemoryWrite: false,
|
|
590
|
+
autoPushOnSessionEnd: false,
|
|
591
|
+
commitAuthor: 'Bizar Memory <bizar-memory@local>',
|
|
592
|
+
commitMessageTemplate: `memory(${projectId}): {summary}`,
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
// Top-level gitRemote mirrors memoryRepo.remote so cmdPush works.
|
|
597
|
+
if (mode === 'managed' && remote) {
|
|
598
|
+
cfg.gitRemote = remote;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const saveResult = saveConfig(projectRoot, cfg);
|
|
602
|
+
if (!saveResult.ok) {
|
|
603
|
+
error(`failed to write config: ${saveResult.error}`);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Initialize the vault on disk.
|
|
608
|
+
const vaultResult = initVault(projectRoot);
|
|
609
|
+
if (!vaultResult.ok) {
|
|
610
|
+
error(`vault init failed: ${vaultResult.error || 'unknown'}`);
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
if (vaultResult.created.length > 0) {
|
|
614
|
+
for (const c of vaultResult.created) info(` created: ${c}`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// For managed bootstrap with a remote, register it on the new vault.
|
|
618
|
+
if (mode === 'managed' && remote) {
|
|
619
|
+
addRemoteToVault(projectRoot, remote);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
printSetupResult({ mode, remote, vaultPath });
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Register `remote` as `origin` on the vault's git repo, then probe
|
|
627
|
+
* connectivity via `lsRemote`. Both operations are best-effort: we
|
|
628
|
+
* warn on failure but never abort the setup.
|
|
629
|
+
*
|
|
630
|
+
* @param {string} projectRoot
|
|
631
|
+
* @param {string} remote
|
|
632
|
+
*/
|
|
633
|
+
function addRemoteToVault(projectRoot, remote) {
|
|
634
|
+
const { resolveVault } = memoryStore;
|
|
635
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
636
|
+
|
|
637
|
+
// Sanity check: is the vault a git repo? initVault should have ensured
|
|
638
|
+
// this, but double-check before calling git remote.
|
|
639
|
+
const gitDir = join(vaultRoot, '.git');
|
|
640
|
+
if (!existsSync(gitDir)) {
|
|
641
|
+
warn('vault is not a git repo — skipping remote registration');
|
|
642
|
+
info('run `bizar memory init` or `bizar memory sync` to repair');
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (typeof memoryGit.addRemote !== 'function') {
|
|
647
|
+
warn('memoryGit.addRemote is not available in this build');
|
|
648
|
+
info('(waiting for the addRemote/lsRemote stream to merge) — remote URL saved to config');
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const addResult = memoryGit.addRemote(vaultRoot, 'origin', remote);
|
|
653
|
+
if (!addResult || addResult.ok === false) {
|
|
654
|
+
warn(`failed to register remote: ${addResult?.error || 'unknown error'}`);
|
|
655
|
+
info('the remote URL is still saved in .bizar/memory.json');
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (addResult.action === 'unchanged') {
|
|
659
|
+
info(`remote 'origin' already set to ${remote}`);
|
|
660
|
+
} else if (addResult.action === 'updated') {
|
|
661
|
+
success(`updated remote 'origin' → ${remote}`);
|
|
662
|
+
} else {
|
|
663
|
+
success(`registered remote 'origin' → ${remote}`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (typeof memoryGit.lsRemote !== 'function') {
|
|
667
|
+
info('skipping connectivity probe (lsRemote not available yet)');
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const probe = memoryGit.lsRemote(vaultRoot, 'origin', { timeoutMs: 5000 });
|
|
672
|
+
if (typeof probe === 'string' && probe.length > 0) {
|
|
673
|
+
const refCount = probe.split('\n').filter(Boolean).length;
|
|
674
|
+
success(`connectivity ok (${refCount} refs reachable)`);
|
|
675
|
+
} else {
|
|
676
|
+
warn('could not reach remote (auth, network, or unknown host)');
|
|
677
|
+
info('the vault is set up; configure credentials and run `bizar memory pull`');
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Render the standard "setup complete" report using the existing `kv`
|
|
683
|
+
* style. The connectivity line is omitted if not relevant.
|
|
684
|
+
*
|
|
685
|
+
* @param {{ mode: string, remote: string|undefined|null, vaultPath: string }} opts
|
|
686
|
+
*/
|
|
687
|
+
function printSetupResult({ mode, remote, vaultPath }) {
|
|
688
|
+
console.log();
|
|
689
|
+
success('memory setup complete');
|
|
690
|
+
console.log(' ─────────────────────────────');
|
|
691
|
+
kv('mode', mode);
|
|
692
|
+
kv('vault', String(vaultPath || ''));
|
|
693
|
+
if (remote) kv('remote', remote);
|
|
694
|
+
console.log();
|
|
695
|
+
if (!remote) {
|
|
696
|
+
info('local-only mode — notes are stored in this project only');
|
|
697
|
+
info('run `bizar memory setup --remote <url>` to attach a remote later');
|
|
698
|
+
} else {
|
|
699
|
+
info('next steps:');
|
|
700
|
+
info(' 1. `bizar memory pull` to fetch the existing vault (if any)');
|
|
701
|
+
info(' 2. `bizar memory write <relpath>` to start adding notes');
|
|
702
|
+
info(' 3. `bizar memory push` to publish');
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* `bizar memory status`
|
|
708
|
+
*/
|
|
709
|
+
async function cmdStatus(_args) {
|
|
710
|
+
const projectRoot = getProjectRoot();
|
|
711
|
+
const { loadConfig, resolveVault, initVault, listNotes } = memoryStore;
|
|
712
|
+
const { isGitInstalled, status: gitStatus } = memoryGit;
|
|
713
|
+
|
|
714
|
+
const { config, exists } = loadConfig(projectRoot);
|
|
715
|
+
if (!exists) {
|
|
716
|
+
warn('memory not initialized — run `bizar memory init` first');
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const { vaultRoot, mode, projectId, branch } = resolveVault(projectRoot);
|
|
721
|
+
|
|
722
|
+
console.log(chalk.bold('\n Bizar Memory Status'));
|
|
723
|
+
console.log(' ─────────────────────────────');
|
|
724
|
+
kv('mode', mode);
|
|
725
|
+
kv('projectId', projectId);
|
|
726
|
+
kv('vault', vaultRoot);
|
|
727
|
+
kv('branch', branch);
|
|
728
|
+
|
|
729
|
+
// Ensure vault exists
|
|
730
|
+
if (!existsSync(vaultRoot)) {
|
|
731
|
+
warn(`vault directory does not exist — run \`bizar memory init\` or \`bizar memory sync\``);
|
|
732
|
+
} else {
|
|
733
|
+
const notes = listNotes(projectRoot);
|
|
734
|
+
kv('noteCount', String(notes.length));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Git status if managed
|
|
738
|
+
if ((mode === 'managed' || mode === 'linked') && existsSync(vaultRoot)) {
|
|
739
|
+
if (isGitInstalled()) {
|
|
740
|
+
const gs = gitStatus(vaultRoot);
|
|
741
|
+
if (gs.ok) {
|
|
742
|
+
kv('git', `${gs.branch} (${gs.clean ? chalk.green('clean') : chalk.red('dirty')})`);
|
|
743
|
+
if (gs.ahead > 0) kv('ahead', String(gs.ahead));
|
|
744
|
+
if (gs.behind > 0) kv('behind', String(gs.behind));
|
|
745
|
+
} else {
|
|
746
|
+
kv('git', chalk.yellow(`not a git repo`));
|
|
747
|
+
}
|
|
748
|
+
} else {
|
|
749
|
+
kv('git', chalk.yellow(`git not installed`));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// LightRAG status
|
|
754
|
+
try {
|
|
755
|
+
const { isLightRAGRunning, isLightRAGInstalled, resolveLightRAGConfig } = memoryStore;
|
|
756
|
+
const cfg = resolveLightRAGConfig(projectRoot);
|
|
757
|
+
if (!cfg.enabled) {
|
|
758
|
+
kv('lightrag', chalk.gray('disabled'));
|
|
759
|
+
} else if (!(await isLightRAGInstalled())) {
|
|
760
|
+
kv('lightrag', chalk.yellow('not installed — `uv tool install "lightrag-hku[api]"`'));
|
|
761
|
+
} else {
|
|
762
|
+
const running = await isLightRAGRunning(cfg);
|
|
763
|
+
kv('lightrag', running ? chalk.green(`${cfg.host}:${cfg.port} (running)`) : chalk.yellow(`${cfg.host}:${cfg.port} (not running — run \`bizar memory reindex\`)`));
|
|
764
|
+
}
|
|
765
|
+
} catch (err) {
|
|
766
|
+
kv('lightrag', chalk.gray(`status unavailable (${err.message})`));
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Last reindex attempt
|
|
770
|
+
const reindexMarker = join(projectRoot, '.bizar', 'memory-cache', 'last-reindex.json');
|
|
771
|
+
if (existsSync(reindexMarker)) {
|
|
772
|
+
try {
|
|
773
|
+
const { finishedAt, ok, inserted, failed, noteCount } = JSON.parse(readFileSync(reindexMarker, 'utf8'));
|
|
774
|
+
kv('lastReindex', `${new Date(finishedAt).toLocaleString()} — ${ok ? chalk.green(`${inserted}/${noteCount} ok`) : chalk.red(`${failed} failed`)}`);
|
|
775
|
+
} catch { /* ignore */ }
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
console.log();
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* `bizar memory link <path|url>`
|
|
783
|
+
*/
|
|
784
|
+
async function cmdLink(args) {
|
|
785
|
+
const projectRoot = getProjectRoot();
|
|
786
|
+
const { loadConfig, saveConfig, resolveVault } = memoryStore;
|
|
787
|
+
const { clone, isGitInstalled } = memoryGit;
|
|
788
|
+
|
|
789
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
790
|
+
console.log(` Usage: bizar memory link [--force] <path|url>`);
|
|
791
|
+
console.log(` Link this project to a shared memory repo.`);
|
|
792
|
+
console.log(` Use --force to overwrite an existing link.`);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const force = args.includes('--force');
|
|
797
|
+
const { config: existing } = loadConfig(projectRoot);
|
|
798
|
+
|
|
799
|
+
if (existing.mode === 'managed' && !force) {
|
|
800
|
+
error('already linked — use --force to overwrite');
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const target = args.filter((a) => !a.startsWith('--'))[0];
|
|
805
|
+
if (!target) {
|
|
806
|
+
error('specify a path or URL to link to');
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
let repoPath;
|
|
811
|
+
let gitRemote = '';
|
|
812
|
+
|
|
813
|
+
if (target.startsWith('http') || target.startsWith('git')) {
|
|
814
|
+
// Clone URL
|
|
815
|
+
if (!isGitInstalled()) {
|
|
816
|
+
error('git is not installed — cannot clone');
|
|
817
|
+
process.exit(1);
|
|
818
|
+
}
|
|
819
|
+
const repoName = target.split('/').pop().replace(/\.git$/, '');
|
|
820
|
+
repoPath = join(process.env.HOME, '.local', 'share', 'bizar', 'memory', repoName);
|
|
821
|
+
info(`cloning ${target} → ${repoPath}`);
|
|
822
|
+
const result = clone(target, repoPath);
|
|
823
|
+
if (!result.ok) {
|
|
824
|
+
error(`clone failed: ${result.error}`);
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
827
|
+
gitRemote = target;
|
|
828
|
+
} else {
|
|
829
|
+
// Local path — validate it exists
|
|
830
|
+
if (!existsSync(target)) {
|
|
831
|
+
error(`path does not exist: ${target}`);
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
repoPath = target;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const config = {
|
|
838
|
+
...existing,
|
|
839
|
+
mode: 'managed',
|
|
840
|
+
repoName: repoPath,
|
|
841
|
+
gitRemote,
|
|
842
|
+
branch: existing.branch || 'main',
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
const result = saveConfig(projectRoot, config);
|
|
846
|
+
if (!result.ok) {
|
|
847
|
+
error(`failed to save config: ${result.error}`);
|
|
848
|
+
process.exit(1);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
success(`linked to ${repoPath}`);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* `bizar memory unlink`
|
|
856
|
+
*/
|
|
857
|
+
async function cmdUnlink(_args) {
|
|
858
|
+
const projectRoot = getProjectRoot();
|
|
859
|
+
const { loadConfig, saveConfig } = memoryStore;
|
|
860
|
+
|
|
861
|
+
const { config } = loadConfig(projectRoot);
|
|
862
|
+
if (config.mode === 'local-only') {
|
|
863
|
+
warn('not linked — nothing to do');
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const newConfig = { ...config, mode: 'local-only', repoName: '', gitRemote: '' };
|
|
868
|
+
const result = saveConfig(projectRoot, newConfig);
|
|
869
|
+
if (!result.ok) {
|
|
870
|
+
error(`failed to save config: ${result.error}`);
|
|
871
|
+
process.exit(1);
|
|
872
|
+
}
|
|
873
|
+
success('unlinked — reverted to local-only mode');
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* `bizar memory pull`
|
|
878
|
+
*/
|
|
879
|
+
async function cmdPull(_args) {
|
|
880
|
+
const projectRoot = getProjectRoot();
|
|
881
|
+
const { loadConfig, resolveVault } = memoryStore;
|
|
882
|
+
const { pull, isGitInstalled } = memoryGit;
|
|
883
|
+
|
|
884
|
+
const { config } = loadConfig(projectRoot);
|
|
885
|
+
if (config.mode === 'local-only') {
|
|
886
|
+
error('pull only works in managed mode');
|
|
887
|
+
process.exit(1);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (!isGitInstalled()) {
|
|
891
|
+
error('git is not installed');
|
|
892
|
+
process.exit(1);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
896
|
+
info(`pulling into ${vaultRoot}`);
|
|
897
|
+
const result = pull(vaultRoot);
|
|
898
|
+
if (!result.ok) {
|
|
899
|
+
error(`pull failed: ${result.error}`);
|
|
900
|
+
process.exit(1);
|
|
901
|
+
}
|
|
902
|
+
success('pulled successfully');
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* `bizar memory commit [-m msg]`
|
|
907
|
+
*/
|
|
908
|
+
async function cmdCommit(args) {
|
|
909
|
+
const projectRoot = getProjectRoot();
|
|
910
|
+
const { loadConfig, resolveVault } = memoryStore;
|
|
911
|
+
const { commit: gitCommit, addAll, isGitInstalled } = memoryGit;
|
|
912
|
+
|
|
913
|
+
const { config } = loadConfig(projectRoot);
|
|
914
|
+
if (config.mode === 'local-only') {
|
|
915
|
+
error('commit only works in managed mode');
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (!isGitInstalled()) {
|
|
920
|
+
error('git is not installed');
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
925
|
+
|
|
926
|
+
// Stage all
|
|
927
|
+
const addResult = addAll(vaultRoot);
|
|
928
|
+
if (!addResult.ok) {
|
|
929
|
+
error(`git add failed: ${addResult.error}`);
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Message
|
|
934
|
+
const msgIdx = args.indexOf('-m');
|
|
935
|
+
let message;
|
|
936
|
+
if (msgIdx !== -1 && args[msgIdx + 1]) {
|
|
937
|
+
message = args[msgIdx + 1];
|
|
938
|
+
} else {
|
|
939
|
+
const date = new Date().toISOString().replace(/T.*/, '');
|
|
940
|
+
message = `[memory-sync] ${date} vault sync`;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const result = gitCommit(vaultRoot, message);
|
|
944
|
+
if (!result.ok) {
|
|
945
|
+
error(`commit failed: ${result.error}`);
|
|
946
|
+
process.exit(1);
|
|
947
|
+
}
|
|
948
|
+
success(`committed: ${message}`);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* `bizar memory push`
|
|
953
|
+
*/
|
|
954
|
+
async function cmdPush(_args) {
|
|
955
|
+
const projectRoot = getProjectRoot();
|
|
956
|
+
const { loadConfig, resolveVault } = memoryStore;
|
|
957
|
+
const { push, isGitInstalled } = memoryGit;
|
|
958
|
+
|
|
959
|
+
const { config } = loadConfig(projectRoot);
|
|
960
|
+
if (config.mode === 'local-only') {
|
|
961
|
+
error('push only works in managed mode');
|
|
962
|
+
process.exit(1);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (!isGitInstalled()) {
|
|
966
|
+
error('git is not installed');
|
|
967
|
+
process.exit(1);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const { vaultRoot, branch } = resolveVault(projectRoot);
|
|
971
|
+
info(`pushing to ${config.gitRemote || 'origin'}`);
|
|
972
|
+
const result = push(vaultRoot, { remote: config.gitRemote || 'origin', branch });
|
|
973
|
+
if (!result.ok) {
|
|
974
|
+
error(`push failed: ${result.error}`);
|
|
975
|
+
process.exit(1);
|
|
976
|
+
}
|
|
977
|
+
success('pushed successfully');
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* `bizar memory sync`
|
|
982
|
+
* Full sync orchestrator: pull → stage → validate → secret-scan →
|
|
983
|
+
* commit → push (if remote configured).
|
|
984
|
+
*/
|
|
985
|
+
async function cmdSync(args) {
|
|
986
|
+
const projectRoot = getProjectRoot();
|
|
987
|
+
const { loadConfig, resolveVault, validateAll, scanForSecrets, listNotes } = memoryStore;
|
|
988
|
+
const { pull, addAll, commit: gitCommit, push: gitPush, status: gitStatus, acquireLock, isGitInstalled } = memoryGit;
|
|
989
|
+
|
|
990
|
+
const { config } = loadConfig(projectRoot);
|
|
991
|
+
if (config.mode === 'local-only') {
|
|
992
|
+
error('sync only works in managed mode');
|
|
993
|
+
process.exit(1);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (!isGitInstalled()) {
|
|
997
|
+
error('git is not installed');
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
1002
|
+
|
|
1003
|
+
// Acquire lock
|
|
1004
|
+
const lock = acquireLock(vaultRoot);
|
|
1005
|
+
if (lock.error) {
|
|
1006
|
+
error(`vault is locked by another process — try again later`);
|
|
1007
|
+
process.exit(1);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
try {
|
|
1011
|
+
// 1. Pull
|
|
1012
|
+
info('pulling latest changes...');
|
|
1013
|
+
const pullResult = pull(vaultRoot);
|
|
1014
|
+
if (!pullResult.ok) {
|
|
1015
|
+
warn(`pull failed (may be up to date): ${pullResult.error}`);
|
|
1016
|
+
} else {
|
|
1017
|
+
success('pulled');
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// 2. Check status
|
|
1021
|
+
const gs = gitStatus(vaultRoot);
|
|
1022
|
+
if (gs.clean) {
|
|
1023
|
+
info('nothing to sync — vault is clean');
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// 3. Validate changed files
|
|
1028
|
+
const allNotes = listNotes(projectRoot);
|
|
1029
|
+
const changedSet = new Set([...gs.modified, ...gs.untracked]);
|
|
1030
|
+
const invalidNotes = allNotes.filter((n) => !n.schemaValid && changedSet.has(n.relPath));
|
|
1031
|
+
if (invalidNotes.length > 0) {
|
|
1032
|
+
error('schema validation failed for changed notes:');
|
|
1033
|
+
for (const n of invalidNotes) {
|
|
1034
|
+
const { errors } = memorySchema.validateNote(n.frontmatter, n.body);
|
|
1035
|
+
console.error(` ${n.relPath}: ${errors.join('; ')}`);
|
|
1036
|
+
}
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// 4. Secret scan changed files
|
|
1041
|
+
const scanResults = [];
|
|
1042
|
+
for (const relPath of changedSet) {
|
|
1043
|
+
const result = scanForSecrets(projectRoot, relPath);
|
|
1044
|
+
if (!result.safe) {
|
|
1045
|
+
scanResults.push(...result.findings);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const highFindings = scanResults.filter((f) => f.severity === 'HIGH');
|
|
1050
|
+
if (highFindings.length > 0) {
|
|
1051
|
+
error('HIGH-severity secrets detected — blocking commit:');
|
|
1052
|
+
for (const f of highFindings) {
|
|
1053
|
+
console.error(` [${f.id}] line ${f.line}: ${f.snippet}`);
|
|
1054
|
+
}
|
|
1055
|
+
process.exit(1);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (scanResults.length > 0) {
|
|
1059
|
+
warn(`${scanResults.length} MEDIUM-severity finding(s):`);
|
|
1060
|
+
for (const f of scanResults) {
|
|
1061
|
+
warn(` [${f.id}] line ${f.line}: ${f.snippet}`);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// 5. Add all
|
|
1066
|
+
const addResult = addAll(vaultRoot);
|
|
1067
|
+
if (!addResult.ok) {
|
|
1068
|
+
error(`git add failed: ${addResult.error}`);
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// 6. Commit
|
|
1073
|
+
const date = new Date().toISOString().replace(/T.*/, '');
|
|
1074
|
+
const summary = allNotes.length > 0 ? allNotes[0].relPath.slice(0, 60) : 'vault sync';
|
|
1075
|
+
const message = `[memory-sync] ${date} ${summary}`;
|
|
1076
|
+
const commitResult = gitCommit(vaultRoot, message);
|
|
1077
|
+
if (!commitResult.ok) {
|
|
1078
|
+
error(`commit failed: ${commitResult.error}`);
|
|
1079
|
+
process.exit(1);
|
|
1080
|
+
}
|
|
1081
|
+
success(`committed: ${message}`);
|
|
1082
|
+
|
|
1083
|
+
// 7. Push if remote configured
|
|
1084
|
+
if (config.gitRemote) {
|
|
1085
|
+
if (args.includes('--push')) {
|
|
1086
|
+
const pushResult = gitPush(vaultRoot, { remote: config.gitRemote, branch: config.branch || 'main' });
|
|
1087
|
+
if (!pushResult.ok) {
|
|
1088
|
+
error(`push failed: ${pushResult.error}`);
|
|
1089
|
+
process.exit(1);
|
|
1090
|
+
}
|
|
1091
|
+
success('pushed');
|
|
1092
|
+
} else {
|
|
1093
|
+
info('skipping push (pass --push to push after commit)');
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
success('sync complete');
|
|
1098
|
+
} finally {
|
|
1099
|
+
lock.release();
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* `bizar memory reindex`
|
|
1105
|
+
* v4.1.0 — populates the LightRAG server with every note in the vault.
|
|
1106
|
+
*
|
|
1107
|
+
* Steps:
|
|
1108
|
+
* 1. Resolve LightRAG config from `.bizar/memory.json`.
|
|
1109
|
+
* 2. Ensure `lightrag-server` is installed (searches common paths).
|
|
1110
|
+
* 3. Ensure server is running (auto-starts if not).
|
|
1111
|
+
* 4. List all notes from the vault.
|
|
1112
|
+
* 5. Insert each note via `POST /documents` with stable id
|
|
1113
|
+
* `bizar://<projectId>/<relPath>`.
|
|
1114
|
+
* 6. Write marker file with stats.
|
|
1115
|
+
*
|
|
1116
|
+
* The reindex is idempotent — re-running re-inserts the same notes under
|
|
1117
|
+
* the same ids; LightRAG's id-keyed storage handles this gracefully.
|
|
1118
|
+
*/
|
|
1119
|
+
async function cmdReindex(args) {
|
|
1120
|
+
const projectRoot = getProjectRoot();
|
|
1121
|
+
const _atomic = await import('./atomic.mjs').then((m) => m);
|
|
1122
|
+
const cacheDir = join(projectRoot, '.bizar', 'memory-cache');
|
|
1123
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
1124
|
+
|
|
1125
|
+
const { isLightRAGInstalled, reindexVault, resolveLightRAGConfig } = memoryStore;
|
|
1126
|
+
|
|
1127
|
+
// Preflight: is lightrag-server installed?
|
|
1128
|
+
if (!(await isLightRAGInstalled())) {
|
|
1129
|
+
error('lightrag-server not installed');
|
|
1130
|
+
info('install it with: uv tool install "lightrag-hku[api]"');
|
|
1131
|
+
info('then run `bizar memory reindex` again');
|
|
1132
|
+
process.exit(1);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Preflight: is lightrag enabled in config?
|
|
1136
|
+
const config = resolveLightRAGConfig(projectRoot);
|
|
1137
|
+
if (!config.enabled) {
|
|
1138
|
+
warn('lightrag is disabled in .bizar/memory.json (lightrag.enabled = false)');
|
|
1139
|
+
info('set lightrag.enabled = true and run `bizar memory reindex` again');
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
info(`reindexing vault into LightRAG at http://${config.host}:${config.port}…`);
|
|
1144
|
+
const result = await reindexVault(projectRoot, { logger: console });
|
|
1145
|
+
|
|
1146
|
+
if (!result.ok && result.inserted === 0) {
|
|
1147
|
+
error(`reindex failed: ${result.error || 'unknown'}`);
|
|
1148
|
+
if (result.failures?.length > 0) {
|
|
1149
|
+
info(`first failure: ${result.failures[0].relPath} — ${result.failures[0].error}`);
|
|
1150
|
+
}
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (result.started) {
|
|
1155
|
+
success(`started LightRAG server (pid ${result.pid})`);
|
|
1156
|
+
}
|
|
1157
|
+
success(`reindexed ${result.inserted}/${result.noteCount} notes in ${result.durationMs}ms`);
|
|
1158
|
+
if (result.failed > 0) {
|
|
1159
|
+
warn(`${result.failed} notes failed — see marker for details`);
|
|
1160
|
+
}
|
|
1161
|
+
info(`marker: ${result.markerPath}`);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* `bizar memory search <query>`
|
|
1166
|
+
* v4.1.0 — merged lexical + semantic search.
|
|
1167
|
+
*
|
|
1168
|
+
* - Lexical: token-frequency matching against vault notes (instant, free).
|
|
1169
|
+
* - Semantic: LightRAG `/query` with `mode: 'mix'` (combines local entity
|
|
1170
|
+
* graph + global relation graph). Skipped if LightRAG isn't running.
|
|
1171
|
+
*/
|
|
1172
|
+
async function cmdSearch(args) {
|
|
1173
|
+
const query = args.join(' ').trim();
|
|
1174
|
+
if (!query) {
|
|
1175
|
+
error('search requires a query: `bizar memory search <query>`');
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
const projectRoot = getProjectRoot();
|
|
1179
|
+
const { searchVault } = memoryStore;
|
|
1180
|
+
|
|
1181
|
+
// Lexical
|
|
1182
|
+
const lexical = searchVault(projectRoot, query, { limit: 10 });
|
|
1183
|
+
console.log(chalk.bold('\n Lexical results (token-frequency):'));
|
|
1184
|
+
if (lexical.length === 0) {
|
|
1185
|
+
console.log(chalk.dim(' (no matches)'));
|
|
1186
|
+
} else {
|
|
1187
|
+
for (const r of lexical.slice(0, 5)) {
|
|
1188
|
+
console.log(` ${chalk.cyan(r.relPath)} ${chalk.dim(`(score ${r.score})`)}`);
|
|
1189
|
+
console.log(chalk.dim(` ${r.snippet}`));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Semantic (best-effort)
|
|
1194
|
+
console.log(chalk.bold('\n Semantic answer (LightRAG):'));
|
|
1195
|
+
try {
|
|
1196
|
+
const cfg = memoryStore.resolveLightRAGConfig(projectRoot);
|
|
1197
|
+
const semantic = await memoryLightrag.query(cfg, query, { topK: 10 });
|
|
1198
|
+
if (!semantic.ok) {
|
|
1199
|
+
console.log(chalk.dim(` ${semantic.error || 'unavailable'}`));
|
|
1200
|
+
if (semantic.error?.includes('not running')) {
|
|
1201
|
+
console.log(chalk.dim(' tip: run `bizar memory reindex` to start + populate LightRAG'));
|
|
1202
|
+
}
|
|
1203
|
+
} else {
|
|
1204
|
+
const response = semantic.response?.response || semantic.response?.answer || '';
|
|
1205
|
+
if (response) {
|
|
1206
|
+
console.log(` ${response.split('\n').join('\n ')}`);
|
|
1207
|
+
} else {
|
|
1208
|
+
console.log(chalk.dim(' (no answer returned)'));
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
} catch (err) {
|
|
1212
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* `bizar memory conflicts`
|
|
1218
|
+
*/
|
|
1219
|
+
async function cmdConflicts(_args) {
|
|
1220
|
+
const projectRoot = getProjectRoot();
|
|
1221
|
+
const { listNotes, readNote } = memoryStore;
|
|
1222
|
+
|
|
1223
|
+
const notes = listNotes(projectRoot);
|
|
1224
|
+
const conflicts = [];
|
|
1225
|
+
|
|
1226
|
+
// Frontmatter-based conflicts
|
|
1227
|
+
for (const note of notes) {
|
|
1228
|
+
if (note.frontmatter?.status === 'conflict') {
|
|
1229
|
+
conflicts.push({ relPath: note.relPath, reason: 'frontmatter: status=conflict' });
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// Git conflict markers in working tree
|
|
1234
|
+
const { vaultRoot } = memoryStore.resolveVault(projectRoot);
|
|
1235
|
+
const { readFileSync: rf } = await import('node:fs');
|
|
1236
|
+
for (const note of notes) {
|
|
1237
|
+
const filePath = join(vaultRoot, note.relPath);
|
|
1238
|
+
try {
|
|
1239
|
+
const content = rf(filePath, 'utf8');
|
|
1240
|
+
if (/^<{7}\s|^={7}\s|>{7}\s/.test(content)) {
|
|
1241
|
+
conflicts.push({ relPath: note.relPath, reason: 'git conflict markers detected' });
|
|
1242
|
+
}
|
|
1243
|
+
} catch { /* skip */ }
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
if (conflicts.length === 0) {
|
|
1247
|
+
success('no conflicts found');
|
|
1248
|
+
} else {
|
|
1249
|
+
console.log(chalk.red(`\n ${conflicts.length} conflict(s) found:`));
|
|
1250
|
+
for (const c of conflicts) {
|
|
1251
|
+
console.log(` ${c.relPath} — ${c.reason}`);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* `bizar memory doctor`
|
|
1258
|
+
*/
|
|
1259
|
+
async function cmdDoctor(_args) {
|
|
1260
|
+
const projectRoot = getProjectRoot();
|
|
1261
|
+
const { loadConfig, resolveVault, initVault, listNotes, validateAll, scanForSecrets } = memoryStore;
|
|
1262
|
+
const { isGitInstalled, status: gitStatus } = memoryGit;
|
|
1263
|
+
|
|
1264
|
+
console.log(chalk.bold('\n Bizar Memory Doctor'));
|
|
1265
|
+
console.log(' ─────────────────────────────');
|
|
1266
|
+
|
|
1267
|
+
let allPassed = true;
|
|
1268
|
+
|
|
1269
|
+
// Check 1: git installed
|
|
1270
|
+
kv('git binary', isGitInstalled() ? chalk.green('present') : chalk.red('missing'));
|
|
1271
|
+
if (!isGitInstalled()) allPassed = false;
|
|
1272
|
+
|
|
1273
|
+
// Check 2: config exists
|
|
1274
|
+
const { config, exists: configExists } = loadConfig(projectRoot);
|
|
1275
|
+
kv('config file', configExists ? chalk.green('present') : chalk.red('missing'));
|
|
1276
|
+
if (!configExists) allPassed = false;
|
|
1277
|
+
|
|
1278
|
+
// Check 3: vault exists
|
|
1279
|
+
if (configExists) {
|
|
1280
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
1281
|
+
kv('vault dir', existsSync(vaultRoot) ? chalk.green('present') : chalk.red('missing'));
|
|
1282
|
+
if (!existsSync(vaultRoot)) allPassed = false;
|
|
1283
|
+
|
|
1284
|
+
// Check 4: vault is a git repo (if managed)
|
|
1285
|
+
if ((config.mode === 'managed' || config.mode === 'linked') && existsSync(vaultRoot)) {
|
|
1286
|
+
const gs = gitStatus(vaultRoot);
|
|
1287
|
+
kv('git repo', gs.ok ? chalk.green('yes') : chalk.red('no'));
|
|
1288
|
+
if (!gs.ok) allPassed = false;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// Check 5: note count
|
|
1292
|
+
const notes = listNotes(projectRoot);
|
|
1293
|
+
kv('notes', notes.length > 0 ? chalk.green(String(notes.length)) : chalk.yellow('0'));
|
|
1294
|
+
|
|
1295
|
+
// Check 6: schema validation pass rate
|
|
1296
|
+
const validationResults = validateAll(projectRoot);
|
|
1297
|
+
const invalidCount = validationResults.length;
|
|
1298
|
+
if (invalidCount === 0) {
|
|
1299
|
+
kv('schema valid', chalk.green('all notes valid'));
|
|
1300
|
+
} else {
|
|
1301
|
+
kv('schema valid', chalk.red(`${invalidCount} note(s) invalid`));
|
|
1302
|
+
for (const r of validationResults.slice(0, 5)) {
|
|
1303
|
+
console.error(` ${r.relPath}: ${r.errors.join('; ')}`);
|
|
1304
|
+
}
|
|
1305
|
+
allPassed = false;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Check 7: last reindex
|
|
1309
|
+
const reindexMarker = join(projectRoot, '.bizar', 'memory-cache', 'last-reindex.json');
|
|
1310
|
+
if (existsSync(reindexMarker)) {
|
|
1311
|
+
try {
|
|
1312
|
+
const { finishedAt, ok, inserted, failed, noteCount } = JSON.parse(readFileSync(reindexMarker, 'utf8'));
|
|
1313
|
+
const ageMs = Date.now() - new Date(finishedAt).getTime();
|
|
1314
|
+
const ageH = ageMs / 3600000;
|
|
1315
|
+
const summary = `${inserted}/${noteCount} inserted${failed > 0 ? `, ${failed} failed` : ''}`;
|
|
1316
|
+
const status = ok ? chalk.green(summary) : chalk.red(summary);
|
|
1317
|
+
kv('lastReindex', ageH < 24 ? `${status} ${chalk.dim(`(${Math.round(ageH)}h ago)`)}` : `${status} ${chalk.yellow(`(${Math.round(ageH)}h ago)`)}`);
|
|
1318
|
+
} catch { /* ignore */ }
|
|
1319
|
+
} else {
|
|
1320
|
+
kv('lastReindex', chalk.yellow('never — run `bizar memory reindex`'));
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Check 8: LightRAG status
|
|
1324
|
+
try {
|
|
1325
|
+
const { isLightRAGInstalled, isLightRAGRunning, resolveLightRAGConfig } = memoryStore;
|
|
1326
|
+
const cfg = resolveLightRAGConfig(projectRoot);
|
|
1327
|
+
if (!cfg.enabled) {
|
|
1328
|
+
kv('lightrag', chalk.gray('disabled'));
|
|
1329
|
+
} else if (!(await isLightRAGInstalled())) {
|
|
1330
|
+
kv('lightrag', chalk.yellow('not installed — `uv tool install "lightrag-hku[api]"`'));
|
|
1331
|
+
} else {
|
|
1332
|
+
const running = await isLightRAGRunning(cfg);
|
|
1333
|
+
kv('lightrag', running ? chalk.green(`${cfg.host}:${cfg.port} running`) : chalk.yellow(`not running — run \`bizar memory reindex\``));
|
|
1334
|
+
}
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
kv('lightrag', chalk.gray(`status check failed: ${err.message}`));
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
console.log('\n ─────────────────────────────');
|
|
1341
|
+
if (allPassed) {
|
|
1342
|
+
success(chalk.bold('All checks passed'));
|
|
1343
|
+
} else {
|
|
1344
|
+
error(chalk.bold('Some checks failed — run `bizar memory init` or `bizar memory sync`'));
|
|
1345
|
+
}
|
|
1346
|
+
console.log();
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// ─── Dispatcher ──────────────────────────────────────────────────────────────
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* @param {string} subcommand
|
|
1353
|
+
* @param {string[]} args
|
|
1354
|
+
*/
|
|
1355
|
+
export async function runMemory(subcommand, args) {
|
|
1356
|
+
switch (subcommand) {
|
|
1357
|
+
case 'init':
|
|
1358
|
+
await cmdInit(args);
|
|
1359
|
+
break;
|
|
1360
|
+
case 'setup':
|
|
1361
|
+
await cmdSetup(args);
|
|
1362
|
+
break;
|
|
1363
|
+
case 'status':
|
|
1364
|
+
await cmdStatus(args);
|
|
1365
|
+
break;
|
|
1366
|
+
case 'link':
|
|
1367
|
+
await cmdLink(args);
|
|
1368
|
+
break;
|
|
1369
|
+
case 'unlink':
|
|
1370
|
+
await cmdUnlink(args);
|
|
1371
|
+
break;
|
|
1372
|
+
case 'write':
|
|
1373
|
+
await cmdWrite(args);
|
|
1374
|
+
break;
|
|
1375
|
+
case 'pull':
|
|
1376
|
+
await cmdPull(args);
|
|
1377
|
+
break;
|
|
1378
|
+
case 'commit':
|
|
1379
|
+
await cmdCommit(args);
|
|
1380
|
+
break;
|
|
1381
|
+
case 'push':
|
|
1382
|
+
await cmdPush(args);
|
|
1383
|
+
break;
|
|
1384
|
+
case 'sync':
|
|
1385
|
+
await cmdSync(args);
|
|
1386
|
+
break;
|
|
1387
|
+
case 'reindex':
|
|
1388
|
+
await cmdReindex(args);
|
|
1389
|
+
break;
|
|
1390
|
+
case 'search':
|
|
1391
|
+
await cmdSearch(args);
|
|
1392
|
+
break;
|
|
1393
|
+
case 'conflicts':
|
|
1394
|
+
await cmdConflicts(args);
|
|
1395
|
+
break;
|
|
1396
|
+
case 'doctor':
|
|
1397
|
+
await cmdDoctor(args);
|
|
1398
|
+
break;
|
|
1399
|
+
case 'help':
|
|
1400
|
+
case '--help':
|
|
1401
|
+
case '-h':
|
|
1402
|
+
showHelp();
|
|
1403
|
+
break;
|
|
1404
|
+
default:
|
|
1405
|
+
error(`unknown subcommand: ${subcommand}`);
|
|
1406
|
+
console.error(` Run \`bizar memory --help\` for usage.`);
|
|
1407
|
+
process.exit(1);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function showHelp() {
|
|
1412
|
+
console.log(`
|
|
1413
|
+
Bizar Memory Service
|
|
1414
|
+
|
|
1415
|
+
Usage:
|
|
1416
|
+
bizar memory <subcommand> [options]
|
|
1417
|
+
|
|
1418
|
+
Subcommands:
|
|
1419
|
+
init Initialize memory config for this project
|
|
1420
|
+
setup Configure or reconfigure the memory vault (mode + remote)
|
|
1421
|
+
status Show current memory configuration
|
|
1422
|
+
link <path> Link to a shared memory repo (clone if URL)
|
|
1423
|
+
unlink Revert to local-only mode
|
|
1424
|
+
write Write a single note to the vault
|
|
1425
|
+
pull Git pull from the shared repo
|
|
1426
|
+
commit [-m] Git commit staged changes
|
|
1427
|
+
push Git push to the shared repo
|
|
1428
|
+
sync [--push] Full sync: pull → validate → scan → commit → [push]
|
|
1429
|
+
reindex Rebuild search index (STUB — Phase 2)
|
|
1430
|
+
conflicts List notes with conflict markers
|
|
1431
|
+
doctor Run health checks
|
|
1432
|
+
|
|
1433
|
+
Options:
|
|
1434
|
+
--help, -h Show this help
|
|
1435
|
+
`.trim());
|
|
1436
|
+
}
|