crewswarm 0.8.1-beta
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/.env.example +155 -0
- package/LICENSE +21 -0
- package/README.md +316 -0
- package/apps/dashboard/dist/assets/chat-core-BwSoInmZ.js +1 -0
- package/apps/dashboard/dist/assets/chat-core-BwSoInmZ.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +1 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/components-CSUb80ze.js +1 -0
- package/apps/dashboard/dist/assets/components-CSUb80ze.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js +1 -0
- package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css +1 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-Px49zu76.js +2 -0
- package/apps/dashboard/dist/assets/index-Px49zu76.js.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js +1 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-i3eEixlo.js +1 -0
- package/apps/dashboard/dist/assets/setup-wizard-i3eEixlo.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BThdsdJY.js +1 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BThdsdJY.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-benchmarks-tab-DfCuAClu.js +1 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-eHpOSBhG.js +1 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-eHpOSBhG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-yEegNyO4.js +1 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-yEegNyO4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-C3DYxTwy.js +1 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-C3DYxTwy.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-C59BYFQD.js +1 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-C59BYFQD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-9Ur7pXWA.js +1 -0
- package/apps/dashboard/dist/assets/tab-models-tab-9Ur7pXWA.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-D7mnDelU.js +1 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-D7mnDelU.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-C6h2Mv1K.js +1 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-C6h2Mv1K.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-C0wZvWK3.js +1 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-C0wZvWK3.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DBj_w3bc.js +1 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DBj_w3bc.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-ezeqAjZk.js +1 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-ezeqAjZk.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BYdU2whk.js +1 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BYdU2whk.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-Bg6w9t_p.js +1 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-Bg6w9t_p.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BBV9HB2X.js +1 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BBV9HB2X.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-ChqLlEVs.js +1 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-ChqLlEVs.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-B2UWXenJ.js +1 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-B2UWXenJ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js +1 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-6QSXLJ0i.js +1 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-6QSXLJ0i.js.br +0 -0
- package/apps/dashboard/dist/favicon.png +0 -0
- package/apps/dashboard/dist/index.html +6466 -0
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/dashboard/dist/signup.html +446 -0
- package/apps/dashboard/index.html +6442 -0
- package/apps/dashboard/package.json +15 -0
- package/apps/dashboard/src/app.js +2823 -0
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +1847 -0
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +327 -0
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +208 -0
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +175 -0
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +18 -0
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +220 -0
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +91 -0
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +134 -0
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +127 -0
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +555 -0
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +2085 -0
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +2237 -0
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +229 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +955 -0
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +654 -0
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +175 -0
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +182 -0
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +441 -0
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +185 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +663 -0
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +160 -0
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +202 -0
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +803 -0
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +284 -0
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +173 -0
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +660 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +538 -0
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +390 -0
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +238 -0
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +747 -0
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +249 -0
- package/apps/vibe/.crew/cost.json +17 -0
- package/apps/vibe/.crew/json-parse-metrics.jsonl +22 -0
- package/apps/vibe/.crew/pipeline-metrics.jsonl +22 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +1 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +2 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +5 -0
- package/apps/vibe/.crew/sandbox.json +7 -0
- package/apps/vibe/.crew/session.json +285 -0
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +37 -0
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +12 -0
- package/apps/vibe/.studio-data/project-messages/general.jsonl +54 -0
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +10 -0
- package/apps/vibe/ARCHITECTURE.md +3393 -0
- package/apps/vibe/QUICK-REFERENCE.md +211 -0
- package/apps/vibe/README.md +76 -0
- package/apps/vibe/ROADMAP.md +41 -0
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +35 -0
- package/apps/vibe/VISUAL-GUIDE.md +378 -0
- package/apps/vibe/capture-demo.mjs +160 -0
- package/apps/vibe/capture-vibe-assets.mjs +71 -0
- package/apps/vibe/capture-vibe-video.mjs +260 -0
- package/apps/vibe/check-buttons.js +41 -0
- package/apps/vibe/diagnose.html +106 -0
- package/apps/vibe/fix-buttons.js +103 -0
- package/apps/vibe/index.html +3401 -0
- package/apps/vibe/package-lock.json +920 -0
- package/apps/vibe/package.json +31 -0
- package/apps/vibe/public/favicon.png +0 -0
- package/apps/vibe/scripts/studio-pty-host.py +117 -0
- package/apps/vibe/server.mjs +1835 -0
- package/apps/vibe/src/main.js +2846 -0
- package/apps/vibe/src/register-all-languages.js +98 -0
- package/apps/vibe/start-studio.sh +11 -0
- package/apps/vibe/test/accessibility-tests.js +77 -0
- package/apps/vibe/test/browser-performance-audit.mjs +205 -0
- package/apps/vibe/test/performance-tests.js +120 -0
- package/apps/vibe/test/security-tests.js +213 -0
- package/apps/vibe/tests/e2e.local.mjs +54 -0
- package/apps/vibe/tests/server.smoke.mjs +106 -0
- package/apps/vibe/update_website.mjs +74 -0
- package/apps/vibe/vite.config.js +19 -0
- package/apps/vibe/watch-server.mjs +108 -0
- package/contrib/openclaw-plugin/README.md +199 -0
- package/contrib/openclaw-plugin/index.ts +306 -0
- package/contrib/openclaw-plugin/openclaw.plugin.json +41 -0
- package/contrib/openclaw-plugin/package.json +27 -0
- package/contrib/openclaw-plugin/skills/crewswarm/SKILL.md +88 -0
- package/crew-lead.mjs +649 -0
- package/engines/claude-code.json +36 -0
- package/engines/codex.json +37 -0
- package/engines/crew-cli.json +42 -0
- package/engines/cursor.json +40 -0
- package/engines/docker-sandbox.json +38 -0
- package/engines/gemini-cli.json +75 -0
- package/engines/opencode.json +31 -0
- package/gateway-bridge.mjs +1575 -0
- package/install.sh +738 -0
- package/lib/agent-registry.mjs +232 -0
- package/lib/agents/daemon.mjs +121 -0
- package/lib/agents/dispatch.mjs +225 -0
- package/lib/agents/permissions.mjs +90 -0
- package/lib/agents/platform-formatting.mjs +102 -0
- package/lib/agents/registry.mjs +81 -0
- package/lib/agents/tool-instructions.mjs +257 -0
- package/lib/agents/validation.mjs +75 -0
- package/lib/approval/policy-manager.mjs +221 -0
- package/lib/autoharness/index.mjs +391 -0
- package/lib/bridges/cli-executor.mjs +332 -0
- package/lib/bridges/gateway-ws.mjs +345 -0
- package/lib/bridges/integration.mjs +229 -0
- package/lib/bridges/rag-helper.mjs +90 -0
- package/lib/browser/opencode-passthrough-filter.js +44 -0
- package/lib/browser/passthrough-stderr.js +109 -0
- package/lib/chat/autonomous-mentions.mjs +373 -0
- package/lib/chat/history.mjs +82 -0
- package/lib/chat/mention-routing-intent.mjs +136 -0
- package/lib/chat/participants.mjs +95 -0
- package/lib/chat/project-messages-rag.mjs +265 -0
- package/lib/chat/project-messages.mjs +479 -0
- package/lib/chat/shared-chat-prompt-overlay.mjs +52 -0
- package/lib/chat/thread-binding.mjs +34 -0
- package/lib/chat/unified-history.mjs +223 -0
- package/lib/chat/unified-wrapper.mjs +41 -0
- package/lib/cli-process-tracker.mjs +228 -0
- package/lib/collections/index.mjs +433 -0
- package/lib/contacts/identity-linker.mjs +248 -0
- package/lib/contacts/index.mjs +341 -0
- package/lib/crew-judge/PROMPT.md +93 -0
- package/lib/crew-judge/judge.mjs +260 -0
- package/lib/crew-lead/agent-manager.mjs +125 -0
- package/lib/crew-lead/background.mjs +270 -0
- package/lib/crew-lead/brain.mjs +110 -0
- package/lib/crew-lead/chat-handler.mjs +2603 -0
- package/lib/crew-lead/chat-handler.mjs.bak +1274 -0
- package/lib/crew-lead/classifier.mjs +83 -0
- package/lib/crew-lead/http-server.mjs +4824 -0
- package/lib/crew-lead/intent.mjs +102 -0
- package/lib/crew-lead/interval-manager.mjs +41 -0
- package/lib/crew-lead/llm-caller.mjs +544 -0
- package/lib/crew-lead/prompts.mjs +392 -0
- package/lib/crew-lead/retry-manager.mjs +118 -0
- package/lib/crew-lead/tools.mjs +318 -0
- package/lib/crew-lead/wave-dispatcher.mjs +798 -0
- package/lib/crew-lead/waves-config.json +73 -0
- package/lib/crew-lead/waves-loader.mjs +110 -0
- package/lib/crew-lead/ws-router.mjs +428 -0
- package/lib/dispatch/parsers.mjs +299 -0
- package/lib/domain-planning/detector.mjs +196 -0
- package/lib/domain-planning/prompts/crew-pm-cli.md +96 -0
- package/lib/domain-planning/prompts/crew-pm-core.md +122 -0
- package/lib/domain-planning/prompts/crew-pm-frontend.md +111 -0
- package/lib/engines/crew-cli-sandbox.mjs +422 -0
- package/lib/engines/crew-cli.mjs +155 -0
- package/lib/engines/cursor-launcher.mjs +110 -0
- package/lib/engines/engine-registry.mjs +253 -0
- package/lib/engines/llm-direct.mjs +184 -0
- package/lib/engines/opencode.mjs +256 -0
- package/lib/engines/ouroboros.mjs +114 -0
- package/lib/engines/rt-envelope.mjs +1643 -0
- package/lib/engines/rt-envelope.mjs.backup-current +870 -0
- package/lib/engines/runners.mjs +1367 -0
- package/lib/gemini-cli-passthrough-noise.mjs +37 -0
- package/lib/integrations/code-search.mjs +259 -0
- package/lib/integrations/greptile.mjs +148 -0
- package/lib/integrations/multimodal.mjs +313 -0
- package/lib/integrations/telegram-streaming.mjs +153 -0
- package/lib/integrations/tts.mjs +312 -0
- package/lib/integrations/twitter-links.mjs +294 -0
- package/lib/memory/shared-adapter.mjs +296 -0
- package/lib/pipeline/manager.mjs +539 -0
- package/lib/preferences/extractor.mjs +347 -0
- package/lib/project-dir.mjs +20 -0
- package/lib/runtime/config.mjs +388 -0
- package/lib/runtime/dlq.mjs +170 -0
- package/lib/runtime/log-rotation.mjs +82 -0
- package/lib/runtime/logger.mjs +58 -0
- package/lib/runtime/memory.mjs +421 -0
- package/lib/runtime/paths.mjs +76 -0
- package/lib/runtime/project-dir.mjs +127 -0
- package/lib/runtime/spending.mjs +204 -0
- package/lib/runtime/startup-guard.mjs +291 -0
- package/lib/runtime/task-lease.mjs +234 -0
- package/lib/runtime/telemetry-schema.mjs +208 -0
- package/lib/runtime/telemetry.mjs +101 -0
- package/lib/runtime/utils.mjs +64 -0
- package/lib/skills/index.mjs +265 -0
- package/lib/tools/browser.mjs +135 -0
- package/lib/tools/executor.mjs +913 -0
- package/lib/types.d.ts +57 -0
- package/package.json +106 -0
- package/pm-loop.mjs +1626 -0
- package/prompts/coder-back.md +27 -0
- package/prompts/coder-front.md +27 -0
- package/prompts/coder.md +28 -0
- package/prompts/copywriter.md +17 -0
- package/prompts/fixer.md +39 -0
- package/prompts/frontend.md +23 -0
- package/prompts/github.md +24 -0
- package/prompts/main.md +39 -0
- package/prompts/pm-cli.md +95 -0
- package/prompts/pm-core.md +121 -0
- package/prompts/pm-frontend.md +110 -0
- package/prompts/pm.md +234 -0
- package/prompts/qa.md +44 -0
- package/prompts/security.md +19 -0
- package/scripts/build-crew-chat.sh +28 -0
- package/scripts/build-llms-full.mjs +52 -0
- package/scripts/chatmock-login.sh +16 -0
- package/scripts/chatmock-serve.sh +16 -0
- package/scripts/check-dashboard.mjs +88 -0
- package/scripts/crew-scribe.mjs +326 -0
- package/scripts/dashboard-helpers.mjs +391 -0
- package/scripts/dashboard-validation.mjs +198 -0
- package/scripts/dashboard.mjs +9717 -0
- package/scripts/dlq-replay.mjs +61 -0
- package/scripts/doctor.mjs +196 -0
- package/scripts/file-lock.mjs +186 -0
- package/scripts/fresh-machine-smoke.sh +323 -0
- package/scripts/generate-changelog.mjs +227 -0
- package/scripts/generate-openapi.mjs +334 -0
- package/scripts/health-check.mjs +229 -0
- package/scripts/install-docker.sh +213 -0
- package/scripts/mcp-server.mjs +1625 -0
- package/scripts/opencrew-rt-daemon.mjs +568 -0
- package/scripts/openswitchctl +646 -0
- package/scripts/refactor-configs.mjs +39 -0
- package/scripts/release-check.sh +46 -0
- package/scripts/resolve-node-bin.sh +25 -0
- package/scripts/restart-all-from-repo.sh +329 -0
- package/scripts/restart-crew-lead.sh +98 -0
- package/scripts/restart-dashboard.sh +104 -0
- package/scripts/restart-service.sh +274 -0
- package/scripts/run-accessibility-audit.mjs +356 -0
- package/scripts/run-integration-bounded.mjs +188 -0
- package/scripts/run-scheduled-pipeline.mjs +230 -0
- package/scripts/run.mjs +41 -0
- package/scripts/scan-skills.mjs +79 -0
- package/scripts/setup-firewall.sh +128 -0
- package/scripts/smoke-dispatch.mjs +149 -0
- package/scripts/smoke.sh +163 -0
- package/scripts/start-crew.mjs +328 -0
- package/scripts/start.mjs +146 -0
- package/scripts/swiftbar-restart-service.sh +19 -0
- package/scripts/sync-agents.mjs +152 -0
- package/scripts/sync-prompts.mjs +79 -0
- package/scripts/validate-config.mjs +337 -0
- package/scripts/wow.mjs +89 -0
- package/telegram-bridge.mjs +2421 -0
- package/unified-orchestrator.mjs +519 -0
- package/whatsapp-bridge.mjs +1481 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Collections System — Universal RAG + Metadata Filtering
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing TF-IDF search (crew-cli/src/collections/index.ts)
|
|
5
|
+
* and adds SQLite persistence + structured metadata filtering.
|
|
6
|
+
*
|
|
7
|
+
* Use cases:
|
|
8
|
+
* - crewswarm: projects, documentation, tools, agent memory
|
|
9
|
+
* - GrabLoco: venues, menu items, reviews
|
|
10
|
+
* - Any structured data that needs semantic search + filters
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
14
|
+
import { dirname, join } from 'path';
|
|
15
|
+
import { homedir } from 'os';
|
|
16
|
+
|
|
17
|
+
// Try to import better-sqlite3, but make it optional
|
|
18
|
+
let Database;
|
|
19
|
+
try {
|
|
20
|
+
Database = (await import('better-sqlite3')).default;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.warn('[Collections] better-sqlite3 not available - RAG features disabled:', e.message);
|
|
23
|
+
Database = null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Hash a string into a vector (simple feature hashing for cosine similarity)
|
|
27
|
+
*/
|
|
28
|
+
function toHashedVector(text, dim = 256) {
|
|
29
|
+
const vec = new Float64Array(dim);
|
|
30
|
+
const tokens = text.toLowerCase()
|
|
31
|
+
.replace(/[^a-z0-9\s_-]/g, ' ')
|
|
32
|
+
.split(/\s+/)
|
|
33
|
+
.filter(t => t.length > 1);
|
|
34
|
+
|
|
35
|
+
for (const token of tokens) {
|
|
36
|
+
let h = 2166136261;
|
|
37
|
+
for (let i = 0; i < token.length; i++) {
|
|
38
|
+
h ^= token.charCodeAt(i);
|
|
39
|
+
h = Math.imul(h, 16777619);
|
|
40
|
+
}
|
|
41
|
+
vec[Math.abs(h) % dim] += 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// L2 normalize
|
|
45
|
+
let norm = 0;
|
|
46
|
+
for (let i = 0; i < dim; i++) norm += vec[i] * vec[i];
|
|
47
|
+
norm = Math.sqrt(norm);
|
|
48
|
+
if (norm > 0) {
|
|
49
|
+
for (let i = 0; i < dim; i++) vec[i] /= norm;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return vec;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Cosine similarity between two vectors
|
|
57
|
+
*/
|
|
58
|
+
function cosineSimilarity(a, b) {
|
|
59
|
+
const dim = Math.min(a.length, b.length);
|
|
60
|
+
let dot = 0;
|
|
61
|
+
for (let i = 0; i < dim; i++) {
|
|
62
|
+
dot += a[i] * b[i];
|
|
63
|
+
}
|
|
64
|
+
return dot;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Tokenize text for TF-IDF
|
|
69
|
+
*/
|
|
70
|
+
function tokenize(text) {
|
|
71
|
+
return text.toLowerCase()
|
|
72
|
+
.replace(/[^a-z0-9\s_-]/g, ' ')
|
|
73
|
+
.split(/\s+/)
|
|
74
|
+
.filter(t => t.length > 1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generic Collection with RAG search + metadata filtering
|
|
79
|
+
*/
|
|
80
|
+
export class Collection {
|
|
81
|
+
constructor(dbPath, collectionName) {
|
|
82
|
+
if (!Database) {
|
|
83
|
+
throw new Error('better-sqlite3 not available - install with: npm install better-sqlite3');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Ensure directory exists
|
|
87
|
+
const dir = dirname(dbPath);
|
|
88
|
+
if (!existsSync(dir)) {
|
|
89
|
+
mkdirSync(dir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.db = new Database(dbPath);
|
|
93
|
+
this.name = collectionName;
|
|
94
|
+
this.initSchema();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
initSchema() {
|
|
98
|
+
this.db.exec(`
|
|
99
|
+
-- Generic items table (any structured data)
|
|
100
|
+
CREATE TABLE IF NOT EXISTS collection_items (
|
|
101
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
102
|
+
collection_name TEXT NOT NULL,
|
|
103
|
+
title TEXT NOT NULL,
|
|
104
|
+
content TEXT NOT NULL,
|
|
105
|
+
metadata TEXT,
|
|
106
|
+
tags TEXT,
|
|
107
|
+
created_at INTEGER NOT NULL,
|
|
108
|
+
updated_at INTEGER NOT NULL
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
-- Pre-computed search index chunks
|
|
112
|
+
CREATE TABLE IF NOT EXISTS collection_chunks (
|
|
113
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
114
|
+
item_id INTEGER NOT NULL,
|
|
115
|
+
chunk_text TEXT NOT NULL,
|
|
116
|
+
chunk_vector TEXT NOT NULL,
|
|
117
|
+
FOREIGN KEY(item_id) REFERENCES collection_items(id) ON DELETE CASCADE
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_collection_name ON collection_items(collection_name);
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_item_tags ON collection_items(tags);
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_chunk_item ON collection_chunks(item_id);
|
|
123
|
+
`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Add item to collection
|
|
128
|
+
*/
|
|
129
|
+
add(item) {
|
|
130
|
+
const { title, content, metadata = {}, tags = [] } = item;
|
|
131
|
+
|
|
132
|
+
const result = this.db.prepare(`
|
|
133
|
+
INSERT INTO collection_items (collection_name, title, content, metadata, tags, created_at, updated_at)
|
|
134
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
135
|
+
`).run(
|
|
136
|
+
this.name,
|
|
137
|
+
title,
|
|
138
|
+
content,
|
|
139
|
+
JSON.stringify(metadata),
|
|
140
|
+
JSON.stringify(tags),
|
|
141
|
+
Date.now(),
|
|
142
|
+
Date.now()
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Build search chunks
|
|
146
|
+
this.indexItem(result.lastInsertRowid, content);
|
|
147
|
+
|
|
148
|
+
return result.lastInsertRowid;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Update item
|
|
153
|
+
*/
|
|
154
|
+
update(id, updates) {
|
|
155
|
+
const { title, content, metadata, tags } = updates;
|
|
156
|
+
|
|
157
|
+
const fields = [];
|
|
158
|
+
const params = [];
|
|
159
|
+
|
|
160
|
+
if (title !== undefined) { fields.push('title = ?'); params.push(title); }
|
|
161
|
+
if (content !== undefined) { fields.push('content = ?'); params.push(content); }
|
|
162
|
+
if (metadata !== undefined) { fields.push('metadata = ?'); params.push(JSON.stringify(metadata)); }
|
|
163
|
+
if (tags !== undefined) { fields.push('tags = ?'); params.push(JSON.stringify(tags)); }
|
|
164
|
+
|
|
165
|
+
if (fields.length === 0) return;
|
|
166
|
+
|
|
167
|
+
fields.push('updated_at = ?');
|
|
168
|
+
params.push(Date.now());
|
|
169
|
+
params.push(id);
|
|
170
|
+
|
|
171
|
+
this.db.prepare(`
|
|
172
|
+
UPDATE collection_items SET ${fields.join(', ')} WHERE id = ?
|
|
173
|
+
`).run(...params);
|
|
174
|
+
|
|
175
|
+
// Re-index if content changed
|
|
176
|
+
if (content !== undefined) {
|
|
177
|
+
this.db.prepare('DELETE FROM collection_chunks WHERE item_id = ?').run(id);
|
|
178
|
+
this.indexItem(id, content);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Delete item
|
|
184
|
+
*/
|
|
185
|
+
delete(id) {
|
|
186
|
+
this.db.prepare('DELETE FROM collection_items WHERE id = ?').run(id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get item by ID
|
|
191
|
+
*/
|
|
192
|
+
get(id) {
|
|
193
|
+
const item = this.db.prepare(`
|
|
194
|
+
SELECT * FROM collection_items WHERE id = ? AND collection_name = ?
|
|
195
|
+
`).get(id, this.name);
|
|
196
|
+
|
|
197
|
+
if (!item) return null;
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
...item,
|
|
201
|
+
metadata: JSON.parse(item.metadata || '{}'),
|
|
202
|
+
tags: JSON.parse(item.tags || '[]')
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* List all items (with pagination)
|
|
208
|
+
*/
|
|
209
|
+
list(options = {}) {
|
|
210
|
+
const { limit = 100, offset = 0, orderBy = 'updated_at', order = 'DESC' } = options;
|
|
211
|
+
|
|
212
|
+
const items = this.db.prepare(`
|
|
213
|
+
SELECT * FROM collection_items
|
|
214
|
+
WHERE collection_name = ?
|
|
215
|
+
ORDER BY ${orderBy} ${order}
|
|
216
|
+
LIMIT ? OFFSET ?
|
|
217
|
+
`).all(this.name, limit, offset);
|
|
218
|
+
|
|
219
|
+
return items.map(item => ({
|
|
220
|
+
...item,
|
|
221
|
+
metadata: JSON.parse(item.metadata || '{}'),
|
|
222
|
+
tags: JSON.parse(item.tags || '[]')
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Count items
|
|
228
|
+
*/
|
|
229
|
+
count() {
|
|
230
|
+
return this.db.prepare(`
|
|
231
|
+
SELECT COUNT(*) as count FROM collection_items WHERE collection_name = ?
|
|
232
|
+
`).get(this.name).count;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Search with TF-IDF + metadata filtering
|
|
237
|
+
*/
|
|
238
|
+
search(query, filters = {}, limit = 10) {
|
|
239
|
+
// Step 1: Apply metadata filters to get candidates
|
|
240
|
+
let sql = `SELECT * FROM collection_items WHERE collection_name = ?`;
|
|
241
|
+
const params = [this.name];
|
|
242
|
+
|
|
243
|
+
// Tag filter
|
|
244
|
+
if (filters.tags) {
|
|
245
|
+
const tagList = Array.isArray(filters.tags) ? filters.tags : [filters.tags];
|
|
246
|
+
for (const tag of tagList) {
|
|
247
|
+
sql += ` AND tags LIKE ?`;
|
|
248
|
+
params.push(`%"${tag}"%`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Custom metadata filters
|
|
253
|
+
if (filters.metadata) {
|
|
254
|
+
for (const [key, value] of Object.entries(filters.metadata)) {
|
|
255
|
+
if (Array.isArray(value)) {
|
|
256
|
+
// Array contains check (e.g., dietary_options contains "vegan")
|
|
257
|
+
sql += ` AND json_extract(metadata, ?) LIKE ?`;
|
|
258
|
+
params.push(`$.${key}`, `%${value[0]}%`);
|
|
259
|
+
} else {
|
|
260
|
+
// Exact match
|
|
261
|
+
sql += ` AND json_extract(metadata, ?) = ?`;
|
|
262
|
+
params.push(`$.${key}`, value);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Exclude filters (e.g., exclude_allergens)
|
|
268
|
+
if (filters.exclude) {
|
|
269
|
+
for (const [key, values] of Object.entries(filters.exclude)) {
|
|
270
|
+
const valueList = Array.isArray(values) ? values : [values];
|
|
271
|
+
for (const val of valueList) {
|
|
272
|
+
sql += ` AND (json_extract(metadata, ?) IS NULL OR json_extract(metadata, ?) NOT LIKE ?)`;
|
|
273
|
+
params.push(`$.${key}`, `$.${key}`, `%${val}%`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const candidates = this.db.prepare(sql).all(...params);
|
|
279
|
+
|
|
280
|
+
if (candidates.length === 0) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Step 2: TF-IDF + Vector scoring
|
|
285
|
+
const queryTokens = tokenize(query);
|
|
286
|
+
const queryVector = toHashedVector(query);
|
|
287
|
+
|
|
288
|
+
// Build term index from candidates
|
|
289
|
+
const termIndex = new Map();
|
|
290
|
+
const itemChunks = new Map();
|
|
291
|
+
|
|
292
|
+
for (const item of candidates) {
|
|
293
|
+
const chunks = this.db.prepare(`
|
|
294
|
+
SELECT chunk_text, chunk_vector FROM collection_chunks WHERE item_id = ?
|
|
295
|
+
`).all(item.id);
|
|
296
|
+
|
|
297
|
+
itemChunks.set(item.id, chunks);
|
|
298
|
+
|
|
299
|
+
for (const chunk of chunks) {
|
|
300
|
+
const tokens = tokenize(chunk.chunk_text);
|
|
301
|
+
for (const token of tokens) {
|
|
302
|
+
if (!termIndex.has(token)) termIndex.set(token, []);
|
|
303
|
+
termIndex.get(token).push({ itemId: item.id, chunk });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Score each item
|
|
309
|
+
const scores = new Map();
|
|
310
|
+
const totalChunks = Array.from(itemChunks.values()).reduce((sum, chunks) => sum + chunks.length, 0);
|
|
311
|
+
|
|
312
|
+
for (const token of queryTokens) {
|
|
313
|
+
const matches = termIndex.get(token);
|
|
314
|
+
if (!matches) continue;
|
|
315
|
+
|
|
316
|
+
const idf = Math.log(1 + totalChunks / matches.length);
|
|
317
|
+
|
|
318
|
+
for (const { itemId, chunk } of matches) {
|
|
319
|
+
if (!scores.has(itemId)) {
|
|
320
|
+
scores.set(itemId, { tfidf: 0, vector: 0, bestChunk: chunk.chunk_text });
|
|
321
|
+
}
|
|
322
|
+
scores.get(itemId).tfidf += idf;
|
|
323
|
+
|
|
324
|
+
// Vector similarity
|
|
325
|
+
const chunkVector = new Float64Array(JSON.parse(chunk.chunk_vector));
|
|
326
|
+
const cosine = cosineSimilarity(queryVector, chunkVector);
|
|
327
|
+
scores.get(itemId).vector = Math.max(scores.get(itemId).vector, cosine);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Hybrid scoring: 70% TF-IDF + 30% vector
|
|
332
|
+
const maxTfidf = Math.max(...Array.from(scores.values()).map(s => s.tfidf), 1);
|
|
333
|
+
|
|
334
|
+
const results = Array.from(scores.entries()).map(([itemId, score]) => {
|
|
335
|
+
const item = candidates.find(c => c.id === itemId);
|
|
336
|
+
const tfidfNorm = score.tfidf / maxTfidf;
|
|
337
|
+
const hybridScore = (tfidfNorm * 0.7) + (score.vector * 0.3);
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
...item,
|
|
341
|
+
metadata: JSON.parse(item.metadata || '{}'),
|
|
342
|
+
tags: JSON.parse(item.tags || '[]'),
|
|
343
|
+
score: Math.round(hybridScore * 1000) / 1000,
|
|
344
|
+
matchedText: score.bestChunk.slice(0, 200)
|
|
345
|
+
};
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Sort by score descending
|
|
349
|
+
results.sort((a, b) => b.score - a.score);
|
|
350
|
+
|
|
351
|
+
return results.slice(0, limit);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Re-index all items (rebuild search chunks)
|
|
356
|
+
*/
|
|
357
|
+
reindex() {
|
|
358
|
+
const items = this.list({ limit: 10000 });
|
|
359
|
+
|
|
360
|
+
// Clear existing chunks
|
|
361
|
+
this.db.prepare(`DELETE FROM collection_chunks WHERE item_id IN (
|
|
362
|
+
SELECT id FROM collection_items WHERE collection_name = ?
|
|
363
|
+
)`).run(this.name);
|
|
364
|
+
|
|
365
|
+
// Re-index each item
|
|
366
|
+
for (const item of items) {
|
|
367
|
+
this.indexItem(item.id, item.content);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return items.length;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Internal: Index an item's content for search
|
|
375
|
+
*/
|
|
376
|
+
indexItem(itemId, content) {
|
|
377
|
+
const chunks = this.chunkContent(content);
|
|
378
|
+
|
|
379
|
+
const insert = this.db.prepare(`
|
|
380
|
+
INSERT INTO collection_chunks (item_id, chunk_text, chunk_vector)
|
|
381
|
+
VALUES (?, ?, ?)
|
|
382
|
+
`);
|
|
383
|
+
|
|
384
|
+
for (const chunk of chunks) {
|
|
385
|
+
const vector = toHashedVector(chunk);
|
|
386
|
+
insert.run(itemId, chunk, JSON.stringify(Array.from(vector)));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Internal: Split content into chunks
|
|
392
|
+
*/
|
|
393
|
+
chunkContent(content) {
|
|
394
|
+
// Split on double newlines (paragraphs) or every ~500 chars
|
|
395
|
+
const paragraphs = content.split(/\n\n+/);
|
|
396
|
+
const chunks = [];
|
|
397
|
+
let current = '';
|
|
398
|
+
|
|
399
|
+
for (const para of paragraphs) {
|
|
400
|
+
if (current.length + para.length < 500) {
|
|
401
|
+
current += (current ? '\n\n' : '') + para;
|
|
402
|
+
} else {
|
|
403
|
+
if (current) chunks.push(current.trim());
|
|
404
|
+
current = para;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (current) chunks.push(current.trim());
|
|
409
|
+
|
|
410
|
+
return chunks.filter(c => c.length > 20);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Close database connection
|
|
415
|
+
*/
|
|
416
|
+
close() {
|
|
417
|
+
this.db.close();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get default collections database path
|
|
423
|
+
*/
|
|
424
|
+
export function getCollectionsDbPath() {
|
|
425
|
+
return join(homedir(), '.crewswarm', 'collections.db');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Create a collection (convenience wrapper)
|
|
430
|
+
*/
|
|
431
|
+
export function createCollection(name, dbPath = null) {
|
|
432
|
+
return new Collection(dbPath || getCollectionsDbPath(), name);
|
|
433
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Linker — Link Multiple Platform Identities to a Master Identity
|
|
3
|
+
*
|
|
4
|
+
* Enables unified conversation history across WhatsApp, Telegram, Dashboard, etc.
|
|
5
|
+
* All your platform identities link to one master identity (e.g., "owner").
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
9
|
+
import { dirname, join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
|
|
12
|
+
// Try to import better-sqlite3, but make it optional
|
|
13
|
+
let Database;
|
|
14
|
+
try {
|
|
15
|
+
Database = (await import('better-sqlite3')).default;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.warn('[Identity Linker] better-sqlite3 not available - identity linking disabled');
|
|
18
|
+
Database = null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let _db = null;
|
|
22
|
+
|
|
23
|
+
function getDb() {
|
|
24
|
+
if (!Database) {
|
|
25
|
+
return null; // Silent fail - identity linking disabled
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (_db) return _db;
|
|
29
|
+
|
|
30
|
+
const dbPath = join(homedir(), '.crewswarm', 'contacts.db');
|
|
31
|
+
const dir = dirname(dbPath);
|
|
32
|
+
|
|
33
|
+
if (!existsSync(dir)) {
|
|
34
|
+
mkdirSync(dir, { recursive: true});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_db = new Database(dbPath);
|
|
38
|
+
initSchema(_db);
|
|
39
|
+
return _db;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function initSchema(db) {
|
|
43
|
+
// Schema already created by lib/contacts/index.mjs
|
|
44
|
+
// Just ensure platform_links column exists (added in migration)
|
|
45
|
+
try {
|
|
46
|
+
db.exec(`
|
|
47
|
+
ALTER TABLE contacts ADD COLUMN platform_links TEXT;
|
|
48
|
+
`);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// Column already exists, ignore
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Link multiple platform identities to a master identity
|
|
56
|
+
* @param {string} masterIdentity - "owner", "jeff", etc.
|
|
57
|
+
* @param {Object} links - { dashboard: "owner", telegram: "...", whatsapp: "..." }
|
|
58
|
+
*/
|
|
59
|
+
export function linkIdentities(masterIdentity, links) {
|
|
60
|
+
const db = getDb();
|
|
61
|
+
if (!db) return false; // Identity linking unavailable
|
|
62
|
+
|
|
63
|
+
// Build platform_links object
|
|
64
|
+
const platformLinks = {
|
|
65
|
+
master_identity: masterIdentity,
|
|
66
|
+
...links
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Create or update master identity contact
|
|
70
|
+
const existing = db.prepare('SELECT * FROM contacts WHERE contact_id = ?').get(masterIdentity);
|
|
71
|
+
|
|
72
|
+
if (existing) {
|
|
73
|
+
db.prepare(`
|
|
74
|
+
UPDATE contacts
|
|
75
|
+
SET platform_links = ?, last_seen = ?
|
|
76
|
+
WHERE contact_id = ?
|
|
77
|
+
`).run(
|
|
78
|
+
JSON.stringify(platformLinks),
|
|
79
|
+
Date.now(),
|
|
80
|
+
masterIdentity
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
db.prepare(`
|
|
84
|
+
INSERT INTO contacts (
|
|
85
|
+
contact_id, platform, display_name, platform_links,
|
|
86
|
+
first_seen, last_seen, message_count, preferences, tags
|
|
87
|
+
) VALUES (?, 'unified', ?, ?, ?, ?, 0, '{}', '[]')
|
|
88
|
+
`).run(
|
|
89
|
+
masterIdentity,
|
|
90
|
+
masterIdentity,
|
|
91
|
+
JSON.stringify(platformLinks),
|
|
92
|
+
Date.now(),
|
|
93
|
+
Date.now()
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Update each linked platform identity to point back to master
|
|
98
|
+
for (const [platform, contactId] of Object.entries(links)) {
|
|
99
|
+
if (platform === 'master_identity') continue;
|
|
100
|
+
|
|
101
|
+
const linkedContact = db.prepare('SELECT * FROM contacts WHERE contact_id = ?').get(contactId);
|
|
102
|
+
|
|
103
|
+
if (linkedContact) {
|
|
104
|
+
db.prepare(`
|
|
105
|
+
UPDATE contacts
|
|
106
|
+
SET platform_links = ?, last_seen = ?
|
|
107
|
+
WHERE contact_id = ?
|
|
108
|
+
`).run(
|
|
109
|
+
JSON.stringify({ master_identity: masterIdentity }),
|
|
110
|
+
Date.now(),
|
|
111
|
+
contactId
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
// Create contact if it doesn't exist
|
|
115
|
+
db.prepare(`
|
|
116
|
+
INSERT INTO contacts (
|
|
117
|
+
contact_id, platform, display_name, platform_links,
|
|
118
|
+
first_seen, last_seen, message_count, preferences, tags
|
|
119
|
+
) VALUES (?, ?, ?, ?, ?, ?, 0, '{}', '[]')
|
|
120
|
+
`).run(
|
|
121
|
+
contactId,
|
|
122
|
+
platform,
|
|
123
|
+
contactId,
|
|
124
|
+
JSON.stringify({ master_identity: masterIdentity }),
|
|
125
|
+
Date.now(),
|
|
126
|
+
Date.now()
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return platformLinks;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get master identity for any platform identity
|
|
136
|
+
* @param {string} contactId - Any platform identity
|
|
137
|
+
* @returns {string|null} Master identity or null if not linked
|
|
138
|
+
*/
|
|
139
|
+
export function getMasterIdentity(contactId) {
|
|
140
|
+
const db = getDb();
|
|
141
|
+
if (!db) return null; // Identity linking unavailable
|
|
142
|
+
|
|
143
|
+
const contact = db.prepare('SELECT platform_links FROM contacts WHERE contact_id = ?').get(contactId);
|
|
144
|
+
|
|
145
|
+
if (!contact?.platform_links) return null;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const links = JSON.parse(contact.platform_links);
|
|
149
|
+
return links.master_identity || null;
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get all linked identities for a master identity
|
|
157
|
+
* @param {string} masterIdentity - Master identity
|
|
158
|
+
* @returns {Object} All platform links
|
|
159
|
+
*/
|
|
160
|
+
export function getLinkedIdentities(masterIdentity) {
|
|
161
|
+
const db = getDb();
|
|
162
|
+
if (!db) return {}; // Identity linking unavailable
|
|
163
|
+
|
|
164
|
+
const contact = db.prepare('SELECT platform_links FROM contacts WHERE contact_id = ?').get(masterIdentity);
|
|
165
|
+
|
|
166
|
+
if (!contact?.platform_links) return {};
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(contact.platform_links);
|
|
170
|
+
} catch {
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Unlink a specific platform from master identity
|
|
177
|
+
* @param {string} contactId - Platform identity to unlink
|
|
178
|
+
*/
|
|
179
|
+
export function unlinkIdentity(contactId) {
|
|
180
|
+
const db = getDb();
|
|
181
|
+
if (!db) return false; // Identity linking unavailable
|
|
182
|
+
|
|
183
|
+
db.prepare(`
|
|
184
|
+
UPDATE contacts
|
|
185
|
+
SET platform_links = NULL
|
|
186
|
+
WHERE contact_id = ?
|
|
187
|
+
`).run(contactId);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Unlink all identities from a master identity
|
|
192
|
+
* @param {string} masterIdentity - Master identity
|
|
193
|
+
*/
|
|
194
|
+
export function unlinkAll(masterIdentity) {
|
|
195
|
+
const db = getDb();
|
|
196
|
+
if (!db) return false; // Identity linking unavailable
|
|
197
|
+
|
|
198
|
+
const links = getLinkedIdentities(masterIdentity);
|
|
199
|
+
|
|
200
|
+
// Unlink all platform identities
|
|
201
|
+
for (const contactId of Object.values(links)) {
|
|
202
|
+
if (contactId === masterIdentity) continue;
|
|
203
|
+
unlinkIdentity(contactId);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Unlink master
|
|
207
|
+
unlinkIdentity(masterIdentity);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* List all master identities (users with linked accounts)
|
|
212
|
+
* @returns {Array} List of master identities with their links
|
|
213
|
+
*/
|
|
214
|
+
export function listLinkedIdentities() {
|
|
215
|
+
const db = getDb();
|
|
216
|
+
if (!db) return []; // Identity linking unavailable
|
|
217
|
+
|
|
218
|
+
const contacts = db.prepare(`
|
|
219
|
+
SELECT contact_id, display_name, platform_links
|
|
220
|
+
FROM contacts
|
|
221
|
+
WHERE platform = 'unified' OR (platform_links IS NOT NULL AND platform_links != '{}')
|
|
222
|
+
`).all();
|
|
223
|
+
|
|
224
|
+
return contacts.map(c => ({
|
|
225
|
+
contactId: c.contact_id,
|
|
226
|
+
displayName: c.display_name,
|
|
227
|
+
links: c.platform_links ? JSON.parse(c.platform_links) : {}
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if a contact has unified identity enabled
|
|
233
|
+
* @param {string} contactId - Any platform identity or master identity
|
|
234
|
+
* @returns {boolean}
|
|
235
|
+
*/
|
|
236
|
+
export function hasUnifiedIdentity(contactId) {
|
|
237
|
+
return getMasterIdentity(contactId) !== null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Close database connection
|
|
242
|
+
*/
|
|
243
|
+
export function closeDb() {
|
|
244
|
+
if (_db) {
|
|
245
|
+
_db.close();
|
|
246
|
+
_db = null;
|
|
247
|
+
}
|
|
248
|
+
}
|