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.
Files changed (362) hide show
  1. package/.env.example +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +316 -0
  4. package/apps/dashboard/dist/assets/chat-core-BwSoInmZ.js +1 -0
  5. package/apps/dashboard/dist/assets/chat-core-BwSoInmZ.js.br +0 -0
  6. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +1 -0
  7. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  8. package/apps/dashboard/dist/assets/components-CSUb80ze.js +1 -0
  9. package/apps/dashboard/dist/assets/components-CSUb80ze.js.br +0 -0
  10. package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js +1 -0
  11. package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js.br +0 -0
  12. package/apps/dashboard/dist/assets/index-CF0aJRtC.css +1 -0
  13. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  14. package/apps/dashboard/dist/assets/index-Px49zu76.js +2 -0
  15. package/apps/dashboard/dist/assets/index-Px49zu76.js.br +0 -0
  16. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js +1 -0
  17. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
  18. package/apps/dashboard/dist/assets/setup-wizard-i3eEixlo.js +1 -0
  19. package/apps/dashboard/dist/assets/setup-wizard-i3eEixlo.js.br +0 -0
  20. package/apps/dashboard/dist/assets/tab-agents-tab-BThdsdJY.js +1 -0
  21. package/apps/dashboard/dist/assets/tab-agents-tab-BThdsdJY.js.br +0 -0
  22. package/apps/dashboard/dist/assets/tab-benchmarks-tab-DfCuAClu.js +1 -0
  23. package/apps/dashboard/dist/assets/tab-comms-tab-eHpOSBhG.js +1 -0
  24. package/apps/dashboard/dist/assets/tab-comms-tab-eHpOSBhG.js.br +0 -0
  25. package/apps/dashboard/dist/assets/tab-contacts-tab-yEegNyO4.js +1 -0
  26. package/apps/dashboard/dist/assets/tab-contacts-tab-yEegNyO4.js.br +0 -0
  27. package/apps/dashboard/dist/assets/tab-engines-tab-C3DYxTwy.js +1 -0
  28. package/apps/dashboard/dist/assets/tab-engines-tab-C3DYxTwy.js.br +0 -0
  29. package/apps/dashboard/dist/assets/tab-memory-tab-C59BYFQD.js +1 -0
  30. package/apps/dashboard/dist/assets/tab-memory-tab-C59BYFQD.js.br +0 -0
  31. package/apps/dashboard/dist/assets/tab-models-tab-9Ur7pXWA.js +1 -0
  32. package/apps/dashboard/dist/assets/tab-models-tab-9Ur7pXWA.js.br +0 -0
  33. package/apps/dashboard/dist/assets/tab-pm-loop-tab-D7mnDelU.js +1 -0
  34. package/apps/dashboard/dist/assets/tab-pm-loop-tab-D7mnDelU.js.br +0 -0
  35. package/apps/dashboard/dist/assets/tab-projects-tab-C6h2Mv1K.js +1 -0
  36. package/apps/dashboard/dist/assets/tab-projects-tab-C6h2Mv1K.js.br +0 -0
  37. package/apps/dashboard/dist/assets/tab-prompts-tab-C0wZvWK3.js +1 -0
  38. package/apps/dashboard/dist/assets/tab-prompts-tab-C0wZvWK3.js.br +0 -0
  39. package/apps/dashboard/dist/assets/tab-services-tab-DBj_w3bc.js +1 -0
  40. package/apps/dashboard/dist/assets/tab-services-tab-DBj_w3bc.js.br +0 -0
  41. package/apps/dashboard/dist/assets/tab-settings-tab-ezeqAjZk.js +1 -0
  42. package/apps/dashboard/dist/assets/tab-settings-tab-ezeqAjZk.js.br +0 -0
  43. package/apps/dashboard/dist/assets/tab-skills-tab-BYdU2whk.js +1 -0
  44. package/apps/dashboard/dist/assets/tab-skills-tab-BYdU2whk.js.br +0 -0
  45. package/apps/dashboard/dist/assets/tab-spending-tab-Bg6w9t_p.js +1 -0
  46. package/apps/dashboard/dist/assets/tab-spending-tab-Bg6w9t_p.js.br +0 -0
  47. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BBV9HB2X.js +1 -0
  48. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BBV9HB2X.js.br +0 -0
  49. package/apps/dashboard/dist/assets/tab-swarm-tab-ChqLlEVs.js +1 -0
  50. package/apps/dashboard/dist/assets/tab-swarm-tab-ChqLlEVs.js.br +0 -0
  51. package/apps/dashboard/dist/assets/tab-usage-tab-B2UWXenJ.js +1 -0
  52. package/apps/dashboard/dist/assets/tab-usage-tab-B2UWXenJ.js.br +0 -0
  53. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js +1 -0
  54. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
  55. package/apps/dashboard/dist/assets/tab-workflows-tab-6QSXLJ0i.js +1 -0
  56. package/apps/dashboard/dist/assets/tab-workflows-tab-6QSXLJ0i.js.br +0 -0
  57. package/apps/dashboard/dist/favicon.png +0 -0
  58. package/apps/dashboard/dist/index.html +6466 -0
  59. package/apps/dashboard/dist/index.html.br +0 -0
  60. package/apps/dashboard/dist/index.html.gz +0 -0
  61. package/apps/dashboard/dist/signup.html +446 -0
  62. package/apps/dashboard/index.html +6442 -0
  63. package/apps/dashboard/package.json +15 -0
  64. package/apps/dashboard/src/app.js +2823 -0
  65. package/apps/dashboard/src/app.js.br +0 -0
  66. package/apps/dashboard/src/app.js.gz +0 -0
  67. package/apps/dashboard/src/chat/chat-actions.js +1847 -0
  68. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  69. package/apps/dashboard/src/chat/unified-messages.js +327 -0
  70. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  71. package/apps/dashboard/src/cli-process.js +208 -0
  72. package/apps/dashboard/src/cli-process.js.br +0 -0
  73. package/apps/dashboard/src/cli-process.js.gz +0 -0
  74. package/apps/dashboard/src/components/active-tasks-panel.js +175 -0
  75. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  76. package/apps/dashboard/src/core/api.js +18 -0
  77. package/apps/dashboard/src/core/api.js.br +0 -0
  78. package/apps/dashboard/src/core/dom.js +220 -0
  79. package/apps/dashboard/src/core/dom.js.br +0 -0
  80. package/apps/dashboard/src/core/state.js +91 -0
  81. package/apps/dashboard/src/core/state.js.br +0 -0
  82. package/apps/dashboard/src/core/task-manager.js +134 -0
  83. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  84. package/apps/dashboard/src/orchestration-status.js +127 -0
  85. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  86. package/apps/dashboard/src/setup-wizard.js +555 -0
  87. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  88. package/apps/dashboard/src/styles.css +2085 -0
  89. package/apps/dashboard/src/styles.css.br +0 -0
  90. package/apps/dashboard/src/styles.css.gz +0 -0
  91. package/apps/dashboard/src/tabs/agents-tab.js +2237 -0
  92. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  93. package/apps/dashboard/src/tabs/benchmarks-tab.js +229 -0
  94. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  95. package/apps/dashboard/src/tabs/comms-tab.js +955 -0
  96. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  97. package/apps/dashboard/src/tabs/contacts-tab.js +654 -0
  98. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  99. package/apps/dashboard/src/tabs/engines-tab.js +175 -0
  100. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  101. package/apps/dashboard/src/tabs/memory-tab.js +182 -0
  102. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  103. package/apps/dashboard/src/tabs/models-tab.js +441 -0
  104. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  105. package/apps/dashboard/src/tabs/pm-loop-tab.js +185 -0
  106. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  107. package/apps/dashboard/src/tabs/projects-tab.js +663 -0
  108. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  109. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  110. package/apps/dashboard/src/tabs/prompts-tab.js +160 -0
  111. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  112. package/apps/dashboard/src/tabs/services-tab.js +202 -0
  113. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  114. package/apps/dashboard/src/tabs/settings-tab.js +803 -0
  115. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  116. package/apps/dashboard/src/tabs/skills-tab.js +284 -0
  117. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  118. package/apps/dashboard/src/tabs/spending-tab.js +173 -0
  119. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  120. package/apps/dashboard/src/tabs/swarm-chat-tab.js +660 -0
  121. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  122. package/apps/dashboard/src/tabs/swarm-tab.js +538 -0
  123. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/usage-tab.js +390 -0
  125. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/waves-tab.js +238 -0
  127. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/workflows-tab.js +747 -0
  129. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  130. package/apps/vibe/.crew/agent-memory/pipeline.json +249 -0
  131. package/apps/vibe/.crew/cost.json +17 -0
  132. package/apps/vibe/.crew/json-parse-metrics.jsonl +22 -0
  133. package/apps/vibe/.crew/pipeline-metrics.jsonl +22 -0
  134. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +5 -0
  135. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +5 -0
  136. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +1 -0
  137. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +5 -0
  138. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +5 -0
  139. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +5 -0
  140. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +5 -0
  141. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +5 -0
  142. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +5 -0
  143. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +5 -0
  144. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +5 -0
  145. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +5 -0
  146. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +5 -0
  147. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +5 -0
  148. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +5 -0
  149. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +5 -0
  150. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +5 -0
  151. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +5 -0
  152. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +2 -0
  153. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +5 -0
  154. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +5 -0
  155. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +5 -0
  156. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +5 -0
  157. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +5 -0
  158. package/apps/vibe/.crew/sandbox.json +7 -0
  159. package/apps/vibe/.crew/session.json +285 -0
  160. package/apps/vibe/.crew/training-data.jsonl +0 -0
  161. package/apps/vibe/.github/workflows/studio-quality.yml +37 -0
  162. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +12 -0
  163. package/apps/vibe/.studio-data/project-messages/general.jsonl +54 -0
  164. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +10 -0
  165. package/apps/vibe/ARCHITECTURE.md +3393 -0
  166. package/apps/vibe/QUICK-REFERENCE.md +211 -0
  167. package/apps/vibe/README.md +76 -0
  168. package/apps/vibe/ROADMAP.md +41 -0
  169. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +35 -0
  170. package/apps/vibe/VISUAL-GUIDE.md +378 -0
  171. package/apps/vibe/capture-demo.mjs +160 -0
  172. package/apps/vibe/capture-vibe-assets.mjs +71 -0
  173. package/apps/vibe/capture-vibe-video.mjs +260 -0
  174. package/apps/vibe/check-buttons.js +41 -0
  175. package/apps/vibe/diagnose.html +106 -0
  176. package/apps/vibe/fix-buttons.js +103 -0
  177. package/apps/vibe/index.html +3401 -0
  178. package/apps/vibe/package-lock.json +920 -0
  179. package/apps/vibe/package.json +31 -0
  180. package/apps/vibe/public/favicon.png +0 -0
  181. package/apps/vibe/scripts/studio-pty-host.py +117 -0
  182. package/apps/vibe/server.mjs +1835 -0
  183. package/apps/vibe/src/main.js +2846 -0
  184. package/apps/vibe/src/register-all-languages.js +98 -0
  185. package/apps/vibe/start-studio.sh +11 -0
  186. package/apps/vibe/test/accessibility-tests.js +77 -0
  187. package/apps/vibe/test/browser-performance-audit.mjs +205 -0
  188. package/apps/vibe/test/performance-tests.js +120 -0
  189. package/apps/vibe/test/security-tests.js +213 -0
  190. package/apps/vibe/tests/e2e.local.mjs +54 -0
  191. package/apps/vibe/tests/server.smoke.mjs +106 -0
  192. package/apps/vibe/update_website.mjs +74 -0
  193. package/apps/vibe/vite.config.js +19 -0
  194. package/apps/vibe/watch-server.mjs +108 -0
  195. package/contrib/openclaw-plugin/README.md +199 -0
  196. package/contrib/openclaw-plugin/index.ts +306 -0
  197. package/contrib/openclaw-plugin/openclaw.plugin.json +41 -0
  198. package/contrib/openclaw-plugin/package.json +27 -0
  199. package/contrib/openclaw-plugin/skills/crewswarm/SKILL.md +88 -0
  200. package/crew-lead.mjs +649 -0
  201. package/engines/claude-code.json +36 -0
  202. package/engines/codex.json +37 -0
  203. package/engines/crew-cli.json +42 -0
  204. package/engines/cursor.json +40 -0
  205. package/engines/docker-sandbox.json +38 -0
  206. package/engines/gemini-cli.json +75 -0
  207. package/engines/opencode.json +31 -0
  208. package/gateway-bridge.mjs +1575 -0
  209. package/install.sh +738 -0
  210. package/lib/agent-registry.mjs +232 -0
  211. package/lib/agents/daemon.mjs +121 -0
  212. package/lib/agents/dispatch.mjs +225 -0
  213. package/lib/agents/permissions.mjs +90 -0
  214. package/lib/agents/platform-formatting.mjs +102 -0
  215. package/lib/agents/registry.mjs +81 -0
  216. package/lib/agents/tool-instructions.mjs +257 -0
  217. package/lib/agents/validation.mjs +75 -0
  218. package/lib/approval/policy-manager.mjs +221 -0
  219. package/lib/autoharness/index.mjs +391 -0
  220. package/lib/bridges/cli-executor.mjs +332 -0
  221. package/lib/bridges/gateway-ws.mjs +345 -0
  222. package/lib/bridges/integration.mjs +229 -0
  223. package/lib/bridges/rag-helper.mjs +90 -0
  224. package/lib/browser/opencode-passthrough-filter.js +44 -0
  225. package/lib/browser/passthrough-stderr.js +109 -0
  226. package/lib/chat/autonomous-mentions.mjs +373 -0
  227. package/lib/chat/history.mjs +82 -0
  228. package/lib/chat/mention-routing-intent.mjs +136 -0
  229. package/lib/chat/participants.mjs +95 -0
  230. package/lib/chat/project-messages-rag.mjs +265 -0
  231. package/lib/chat/project-messages.mjs +479 -0
  232. package/lib/chat/shared-chat-prompt-overlay.mjs +52 -0
  233. package/lib/chat/thread-binding.mjs +34 -0
  234. package/lib/chat/unified-history.mjs +223 -0
  235. package/lib/chat/unified-wrapper.mjs +41 -0
  236. package/lib/cli-process-tracker.mjs +228 -0
  237. package/lib/collections/index.mjs +433 -0
  238. package/lib/contacts/identity-linker.mjs +248 -0
  239. package/lib/contacts/index.mjs +341 -0
  240. package/lib/crew-judge/PROMPT.md +93 -0
  241. package/lib/crew-judge/judge.mjs +260 -0
  242. package/lib/crew-lead/agent-manager.mjs +125 -0
  243. package/lib/crew-lead/background.mjs +270 -0
  244. package/lib/crew-lead/brain.mjs +110 -0
  245. package/lib/crew-lead/chat-handler.mjs +2603 -0
  246. package/lib/crew-lead/chat-handler.mjs.bak +1274 -0
  247. package/lib/crew-lead/classifier.mjs +83 -0
  248. package/lib/crew-lead/http-server.mjs +4824 -0
  249. package/lib/crew-lead/intent.mjs +102 -0
  250. package/lib/crew-lead/interval-manager.mjs +41 -0
  251. package/lib/crew-lead/llm-caller.mjs +544 -0
  252. package/lib/crew-lead/prompts.mjs +392 -0
  253. package/lib/crew-lead/retry-manager.mjs +118 -0
  254. package/lib/crew-lead/tools.mjs +318 -0
  255. package/lib/crew-lead/wave-dispatcher.mjs +798 -0
  256. package/lib/crew-lead/waves-config.json +73 -0
  257. package/lib/crew-lead/waves-loader.mjs +110 -0
  258. package/lib/crew-lead/ws-router.mjs +428 -0
  259. package/lib/dispatch/parsers.mjs +299 -0
  260. package/lib/domain-planning/detector.mjs +196 -0
  261. package/lib/domain-planning/prompts/crew-pm-cli.md +96 -0
  262. package/lib/domain-planning/prompts/crew-pm-core.md +122 -0
  263. package/lib/domain-planning/prompts/crew-pm-frontend.md +111 -0
  264. package/lib/engines/crew-cli-sandbox.mjs +422 -0
  265. package/lib/engines/crew-cli.mjs +155 -0
  266. package/lib/engines/cursor-launcher.mjs +110 -0
  267. package/lib/engines/engine-registry.mjs +253 -0
  268. package/lib/engines/llm-direct.mjs +184 -0
  269. package/lib/engines/opencode.mjs +256 -0
  270. package/lib/engines/ouroboros.mjs +114 -0
  271. package/lib/engines/rt-envelope.mjs +1643 -0
  272. package/lib/engines/rt-envelope.mjs.backup-current +870 -0
  273. package/lib/engines/runners.mjs +1367 -0
  274. package/lib/gemini-cli-passthrough-noise.mjs +37 -0
  275. package/lib/integrations/code-search.mjs +259 -0
  276. package/lib/integrations/greptile.mjs +148 -0
  277. package/lib/integrations/multimodal.mjs +313 -0
  278. package/lib/integrations/telegram-streaming.mjs +153 -0
  279. package/lib/integrations/tts.mjs +312 -0
  280. package/lib/integrations/twitter-links.mjs +294 -0
  281. package/lib/memory/shared-adapter.mjs +296 -0
  282. package/lib/pipeline/manager.mjs +539 -0
  283. package/lib/preferences/extractor.mjs +347 -0
  284. package/lib/project-dir.mjs +20 -0
  285. package/lib/runtime/config.mjs +388 -0
  286. package/lib/runtime/dlq.mjs +170 -0
  287. package/lib/runtime/log-rotation.mjs +82 -0
  288. package/lib/runtime/logger.mjs +58 -0
  289. package/lib/runtime/memory.mjs +421 -0
  290. package/lib/runtime/paths.mjs +76 -0
  291. package/lib/runtime/project-dir.mjs +127 -0
  292. package/lib/runtime/spending.mjs +204 -0
  293. package/lib/runtime/startup-guard.mjs +291 -0
  294. package/lib/runtime/task-lease.mjs +234 -0
  295. package/lib/runtime/telemetry-schema.mjs +208 -0
  296. package/lib/runtime/telemetry.mjs +101 -0
  297. package/lib/runtime/utils.mjs +64 -0
  298. package/lib/skills/index.mjs +265 -0
  299. package/lib/tools/browser.mjs +135 -0
  300. package/lib/tools/executor.mjs +913 -0
  301. package/lib/types.d.ts +57 -0
  302. package/package.json +106 -0
  303. package/pm-loop.mjs +1626 -0
  304. package/prompts/coder-back.md +27 -0
  305. package/prompts/coder-front.md +27 -0
  306. package/prompts/coder.md +28 -0
  307. package/prompts/copywriter.md +17 -0
  308. package/prompts/fixer.md +39 -0
  309. package/prompts/frontend.md +23 -0
  310. package/prompts/github.md +24 -0
  311. package/prompts/main.md +39 -0
  312. package/prompts/pm-cli.md +95 -0
  313. package/prompts/pm-core.md +121 -0
  314. package/prompts/pm-frontend.md +110 -0
  315. package/prompts/pm.md +234 -0
  316. package/prompts/qa.md +44 -0
  317. package/prompts/security.md +19 -0
  318. package/scripts/build-crew-chat.sh +28 -0
  319. package/scripts/build-llms-full.mjs +52 -0
  320. package/scripts/chatmock-login.sh +16 -0
  321. package/scripts/chatmock-serve.sh +16 -0
  322. package/scripts/check-dashboard.mjs +88 -0
  323. package/scripts/crew-scribe.mjs +326 -0
  324. package/scripts/dashboard-helpers.mjs +391 -0
  325. package/scripts/dashboard-validation.mjs +198 -0
  326. package/scripts/dashboard.mjs +9717 -0
  327. package/scripts/dlq-replay.mjs +61 -0
  328. package/scripts/doctor.mjs +196 -0
  329. package/scripts/file-lock.mjs +186 -0
  330. package/scripts/fresh-machine-smoke.sh +323 -0
  331. package/scripts/generate-changelog.mjs +227 -0
  332. package/scripts/generate-openapi.mjs +334 -0
  333. package/scripts/health-check.mjs +229 -0
  334. package/scripts/install-docker.sh +213 -0
  335. package/scripts/mcp-server.mjs +1625 -0
  336. package/scripts/opencrew-rt-daemon.mjs +568 -0
  337. package/scripts/openswitchctl +646 -0
  338. package/scripts/refactor-configs.mjs +39 -0
  339. package/scripts/release-check.sh +46 -0
  340. package/scripts/resolve-node-bin.sh +25 -0
  341. package/scripts/restart-all-from-repo.sh +329 -0
  342. package/scripts/restart-crew-lead.sh +98 -0
  343. package/scripts/restart-dashboard.sh +104 -0
  344. package/scripts/restart-service.sh +274 -0
  345. package/scripts/run-accessibility-audit.mjs +356 -0
  346. package/scripts/run-integration-bounded.mjs +188 -0
  347. package/scripts/run-scheduled-pipeline.mjs +230 -0
  348. package/scripts/run.mjs +41 -0
  349. package/scripts/scan-skills.mjs +79 -0
  350. package/scripts/setup-firewall.sh +128 -0
  351. package/scripts/smoke-dispatch.mjs +149 -0
  352. package/scripts/smoke.sh +163 -0
  353. package/scripts/start-crew.mjs +328 -0
  354. package/scripts/start.mjs +146 -0
  355. package/scripts/swiftbar-restart-service.sh +19 -0
  356. package/scripts/sync-agents.mjs +152 -0
  357. package/scripts/sync-prompts.mjs +79 -0
  358. package/scripts/validate-config.mjs +337 -0
  359. package/scripts/wow.mjs +89 -0
  360. package/telegram-bridge.mjs +2421 -0
  361. package/unified-orchestrator.mjs +519 -0
  362. 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
+ }