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,204 @@
1
+ /**
2
+ * Spending caps + token usage accumulator — extracted from gateway-bridge.mjs
3
+ * Inject: initSpending({ resolveConfig, resolveTelegramBridgeConfig })
4
+ */
5
+
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import os from "os";
9
+
10
+ const SPENDING_FILE = path.join(os.homedir(), ".crewswarm", "spending.json");
11
+ const TOKEN_USAGE_FILE = path.join(os.homedir(), ".crewswarm", "token-usage.json");
12
+
13
+ // Lazy path resolvers — in test mode, redirect to /tmp to avoid corrupting live data
14
+ function getSpendingFile() {
15
+ if (process.env.CREWSWARM_TEST_MODE) {
16
+ const dir = path.join(os.tmpdir(), `crewswarm-test-${process.pid}`);
17
+ fs.mkdirSync(dir, { recursive: true });
18
+ return path.join(dir, "spending.json");
19
+ }
20
+ return SPENDING_FILE;
21
+ }
22
+
23
+ function getTokenUsageFile() {
24
+ if (process.env.CREWSWARM_TEST_MODE) {
25
+ const dir = path.join(os.tmpdir(), `crewswarm-test-${process.pid}`);
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ return path.join(dir, "token-usage.json");
28
+ }
29
+ return TOKEN_USAGE_FILE;
30
+ }
31
+
32
+ // Cost per 1M tokens per provider (USD) — input / output / cached
33
+ // Cached pricing: Gemini=free, Anthropic=90% off, Grok=50% off, others=same as input
34
+ const PRICING = {
35
+ groq: { input: 0.05, output: 0.05, cached: 0.025 }, // 50% off
36
+ anthropic: { input: 3.00, output: 15.00, cached: 0.30 }, // 90% off
37
+ openai: { input: 5.00, output: 15.00, cached: 2.50 }, // 50% off
38
+ perplexity: { input: 1.00, output: 1.00, cached: 1.00 }, // no discount
39
+ mistral: { input: 0.70, output: 2.00, cached: 0.70 }, // no discount
40
+ google: { input: 0.075, output: 0.30, cached: 0.00 }, // FREE!
41
+ xai: { input: 5.00, output: 15.00, cached: 2.50 }, // 50% off (Grok)
42
+ deepseek: { input: 0.27, output: 1.10, cached: 0.135 }, // 50% off
43
+ nvidia: { input: 1.00, output: 1.00, cached: 1.00 }, // no discount
44
+ cerebras: { input: 0.10, output: 0.10, cached: 0.10 }, // no discount
45
+ };
46
+
47
+ // Legacy fallback for old code using COST_PER_1M
48
+ const COST_PER_1M = { groq:0.05, anthropic:3.00, openai:5.00, perplexity:1.00, mistral:0.70, google:0.15, xai:2.00, deepseek:0.27, nvidia:1.00, cerebras:0.10 };
49
+
50
+ let _resolveConfig = () => ({});
51
+ let _resolveTelegramBridgeConfig = () => ({});
52
+
53
+ export function initSpending({ resolveConfig, resolveTelegramBridgeConfig } = {}) {
54
+ if (resolveConfig) _resolveConfig = resolveConfig;
55
+ if (resolveTelegramBridgeConfig) _resolveTelegramBridgeConfig = resolveTelegramBridgeConfig;
56
+ }
57
+
58
+ // ── Spending caps ─────────────────────────────────────────────────────────────
59
+
60
+ export function loadSpending() {
61
+ const today = new Date().toISOString().slice(0, 10);
62
+ try {
63
+ const d = JSON.parse(fs.readFileSync(getSpendingFile(), "utf8"));
64
+ if (d.date === today) return d;
65
+ } catch {}
66
+ return { date: today, global: { tokens: 0, costUSD: 0 }, agents: {} };
67
+ }
68
+
69
+ export function saveSpending(s) {
70
+ try {
71
+ const file = getSpendingFile();
72
+ fs.mkdirSync(path.dirname(file), { recursive: true });
73
+ fs.writeFileSync(file, JSON.stringify(s, null, 2));
74
+ } catch {}
75
+ }
76
+
77
+ export function addAgentSpend(agentId, tokens, costUSD) {
78
+ const s = loadSpending();
79
+ s.global.tokens += tokens;
80
+ s.global.costUSD += costUSD;
81
+ if (!s.agents[agentId]) s.agents[agentId] = { tokens: 0, costUSD: 0 };
82
+ s.agents[agentId].tokens += tokens;
83
+ s.agents[agentId].costUSD += costUSD;
84
+ saveSpending(s);
85
+ }
86
+
87
+ export function checkSpendingCap(agentId, providerKey) {
88
+ try {
89
+ const csw = JSON.parse(fs.readFileSync(getCrewswarmConfigPath(), "utf8"));
90
+ const s = loadSpending();
91
+ const gl = csw.globalSpendingCaps || {};
92
+ if (gl.dailyTokenLimit && s.global.tokens >= gl.dailyTokenLimit)
93
+ return { exceeded: true, action: "stop", message: `Global daily token limit ${gl.dailyTokenLimit.toLocaleString()} reached` };
94
+ if (gl.dailyCostLimitUSD && s.global.costUSD >= gl.dailyCostLimitUSD)
95
+ return { exceeded: true, action: "stop", message: `Global daily cost limit $${gl.dailyCostLimitUSD} reached` };
96
+ const agent = (csw.agents || []).find(a => a.id === agentId);
97
+ const agentCap = agent?.spending;
98
+ if (agentCap) {
99
+ const used = s.agents[agentId] || { tokens: 0, costUSD: 0 };
100
+ if (agentCap.dailyTokenLimit && used.tokens >= agentCap.dailyTokenLimit)
101
+ return { exceeded: true, action: agentCap.onExceed || "notify", message: `${agentId} daily token limit ${agentCap.dailyTokenLimit.toLocaleString()} reached` };
102
+ if (agentCap.dailyCostLimitUSD && used.costUSD >= agentCap.dailyCostLimitUSD)
103
+ return { exceeded: true, action: agentCap.onExceed || "notify", message: `${agentId} daily cost limit $${agentCap.dailyCostLimitUSD} reached` };
104
+ }
105
+ } catch {}
106
+ return { exceeded: false };
107
+ }
108
+
109
+ export async function notifyTelegramSpending(message) {
110
+ const cfg = _resolveConfig();
111
+ const tgBridge = _resolveTelegramBridgeConfig();
112
+ const botToken = process.env.TELEGRAM_BOT_TOKEN || cfg?.env?.TELEGRAM_BOT_TOKEN || cfg?.TELEGRAM_BOT_TOKEN || tgBridge.token || "";
113
+ const chatId = process.env.TELEGRAM_CHAT_ID || cfg?.env?.TELEGRAM_CHAT_ID || cfg?.TELEGRAM_CHAT_ID
114
+ || (Array.isArray(tgBridge.allowedChatIds) && tgBridge.allowedChatIds.length ? String(tgBridge.allowedChatIds[0]) : "") || tgBridge.defaultChatId || "";
115
+ const chatIdVal = chatId.trim();
116
+ if (!botToken || !chatIdVal) return;
117
+ await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
118
+ method: "POST", headers: { "Content-Type": "application/json" },
119
+ body: JSON.stringify({ chat_id: chatIdVal, text: `💸 Spending alert: ${message}`, parse_mode: "Markdown" }),
120
+ signal: AbortSignal.timeout(5000),
121
+ }).catch(() => {});
122
+ }
123
+
124
+ // ── Token/cost accumulator ────────────────────────────────────────────────────
125
+
126
+ let _tokenUsage = null;
127
+ export function getTokenUsage() {
128
+ if (!_tokenUsage) {
129
+ try { _tokenUsage = JSON.parse(fs.readFileSync(getTokenUsageFile(), "utf8")); } catch {}
130
+ if (!_tokenUsage) _tokenUsage = { calls: 0, prompt: 0, completion: 0, byModel: {}, sessionStart: new Date().toISOString() };
131
+ }
132
+ return _tokenUsage;
133
+ }
134
+ // Backward-compat: existing code references `tokenUsage` directly
135
+ export const tokenUsage = new Proxy({}, {
136
+ get(_, prop) { return getTokenUsage()[prop]; },
137
+ set(_, prop, val) { getTokenUsage()[prop] = val; return true; },
138
+ });
139
+
140
+ export function recordTokenUsage(modelId, usage, agentId) {
141
+ if (!usage) return;
142
+ const p = Number(usage.prompt_tokens || usage.input_tokens || 0);
143
+ const c = Number(usage.completion_tokens || usage.output_tokens || 0);
144
+ if (!p && !c) return;
145
+ const cached = Number(
146
+ usage.prompt_tokens_details?.cached_tokens // OpenAI / xAI / Groq
147
+ || usage.prompt_cache_hit_tokens // DeepSeek
148
+ || usage.cache_read_input_tokens // Anthropic
149
+ || 0
150
+ );
151
+ if (cached > 0) {
152
+ const pct = p > 0 ? Math.round(cached / p * 100) : 0;
153
+ console.log(`[bridge:${agentId || modelId}] cache hit: ${cached}/${p} tokens cached (${pct}%) — ${modelId}`);
154
+ }
155
+ const today = new Date().toISOString().slice(0, 10);
156
+ tokenUsage.calls++;
157
+ tokenUsage.prompt += p;
158
+ tokenUsage.completion += c;
159
+ if (!tokenUsage.cached) tokenUsage.cached = 0;
160
+ tokenUsage.cached += cached;
161
+ if (!tokenUsage.byModel[modelId]) tokenUsage.byModel[modelId] = { calls: 0, prompt: 0, completion: 0, cached: 0 };
162
+ tokenUsage.byModel[modelId].calls++;
163
+ tokenUsage.byModel[modelId].prompt += p;
164
+ tokenUsage.byModel[modelId].completion += c;
165
+ tokenUsage.byModel[modelId].cached += cached;
166
+ if (!tokenUsage.byDay) tokenUsage.byDay = {};
167
+ if (!tokenUsage.byDay[today]) tokenUsage.byDay[today] = { calls: 0, prompt: 0, completion: 0, cached: 0, byModel: {} };
168
+ tokenUsage.byDay[today].calls++;
169
+ tokenUsage.byDay[today].prompt += p;
170
+ tokenUsage.byDay[today].completion += c;
171
+ tokenUsage.byDay[today].cached += cached;
172
+ if (!tokenUsage.byDay[today].byModel[modelId]) tokenUsage.byDay[today].byModel[modelId] = { calls: 0, prompt: 0, completion: 0, cached: 0 };
173
+ tokenUsage.byDay[today].byModel[modelId].calls++;
174
+ tokenUsage.byDay[today].byModel[modelId].prompt += p;
175
+ tokenUsage.byDay[today].byModel[modelId].completion += c;
176
+ tokenUsage.byDay[today].byModel[modelId].cached += cached;
177
+ if (tokenUsage.calls % 5 === 0) {
178
+ try {
179
+ const file = getTokenUsageFile();
180
+ fs.mkdirSync(path.dirname(file), { recursive: true });
181
+ fs.writeFileSync(file, JSON.stringify(tokenUsage, null, 2));
182
+ } catch {}
183
+ }
184
+ if (agentId) {
185
+ const providerKey = modelId.split("/")[0] || "unknown";
186
+ const pricing = PRICING[providerKey] || { input: 1.0, output: 1.0, cached: 1.0 };
187
+
188
+ // Calculate cost with cache discount
189
+ const uncachedInput = Math.max(0, p - cached);
190
+ const inputCost = (uncachedInput / 1_000_000) * pricing.input;
191
+ const cachedCost = (cached / 1_000_000) * pricing.cached;
192
+ const outputCost = (c / 1_000_000) * pricing.output;
193
+ const costUSD = inputCost + cachedCost + outputCost;
194
+
195
+ // Log cache savings for high cache hit rates
196
+ if (cached > 0 && cached / p > 0.5) {
197
+ const savings = ((uncachedInput / 1_000_000) * pricing.input) - cachedCost;
198
+ console.log(`[spending:${agentId}] cache saved $${savings.toFixed(4)} on ${cached.toLocaleString()} tokens`);
199
+ }
200
+
201
+ const total = p + c; // Total tokens (for cap checking)
202
+ addAgentSpend(agentId, total, costUSD);
203
+ }
204
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Startup guard — Ensures only one instance of a service runs
3
+ * Prevents port conflicts and duplicate processes
4
+ */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import os from "node:os";
8
+ import { execSync } from "node:child_process";
9
+
10
+ function isWritableDir(dir) {
11
+ try {
12
+ fs.mkdirSync(dir, { recursive: true });
13
+ const probe = path.join(dir, `.pid-write-test-${process.pid}`);
14
+ fs.writeFileSync(probe, "ok");
15
+ fs.unlinkSync(probe);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ function resolvePidDir() {
23
+ const configured = process.env.CREWSWARM_PID_DIR;
24
+ const homePidDir = path.join(os.homedir(), ".crewswarm", "pids");
25
+ const tmpPidDir = path.join(
26
+ os.tmpdir(),
27
+ `crewswarm-pids-${process.getuid?.() ?? "user"}`,
28
+ );
29
+
30
+ const candidates = [configured, homePidDir, tmpPidDir].filter(Boolean);
31
+ for (const dir of candidates) {
32
+ if (isWritableDir(dir)) {
33
+ if (dir !== homePidDir) {
34
+ console.warn(`[startup-guard] Using fallback PID directory: ${dir}`);
35
+ }
36
+ return dir;
37
+ }
38
+ }
39
+
40
+ throw new Error(
41
+ `[startup-guard] No writable PID directory found. Tried: ${candidates.join(", ")}`,
42
+ );
43
+ }
44
+
45
+ const PID_DIR = resolvePidDir();
46
+
47
+ /**
48
+ * Check if a process is alive
49
+ */
50
+ function isProcessAlive(pid) {
51
+ try {
52
+ process.kill(pid, 0); // Signal 0 just checks if process exists
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Check if a port is in use
61
+ */
62
+ function isPortInUse(port) {
63
+ try {
64
+ execSync(`lsof -ti :${port}`, {
65
+ encoding: "utf8",
66
+ timeout: 2000,
67
+ stdio: ["pipe", "pipe", "pipe"],
68
+ });
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get PID using port
77
+ */
78
+ function getPidOnPort(port) {
79
+ try {
80
+ const out = execSync(`lsof -ti :${port}`, {
81
+ encoding: "utf8",
82
+ timeout: 2000,
83
+ stdio: ["pipe", "pipe", "pipe"],
84
+ }).trim();
85
+ const pids = out
86
+ .split("\n")
87
+ .filter(Boolean)
88
+ .map((p) => parseInt(p, 10));
89
+ return pids.length > 0 ? pids[0] : null;
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Remove a pid file without crashing startup on permission issues.
97
+ */
98
+ function tryRemovePidFile(pidFile, serviceName, context) {
99
+ try {
100
+ fs.unlinkSync(pidFile);
101
+ return true;
102
+ } catch (err) {
103
+ console.warn(
104
+ `[startup-guard] Failed to remove ${context} PID file for ${serviceName}: ${err.message}`,
105
+ );
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Acquire startup lock for a service
112
+ * Returns: { ok: true, pid } if lock acquired
113
+ * { ok: false, runningPid, message } if already running
114
+ */
115
+ export function acquireStartupLock(serviceName, options = {}) {
116
+ const {
117
+ port = null,
118
+ killStale = true,
119
+ maxRetries = 6,
120
+ pidFile: pidFileOverride = null,
121
+ } = options;
122
+ const pidFile =
123
+ pidFileOverride ?? path.join(PID_DIR, `${serviceName}.pid`);
124
+ if (pidFileOverride) {
125
+ try {
126
+ fs.mkdirSync(path.dirname(pidFile), { recursive: true });
127
+ } catch {
128
+ // write below may still succeed
129
+ }
130
+ }
131
+ const myPid = process.pid;
132
+
133
+ // Check if PID file exists
134
+ if (fs.existsSync(pidFile)) {
135
+ try {
136
+ const savedPid = parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
137
+ if (!Number.isFinite(savedPid) || savedPid < 1) {
138
+ console.log(
139
+ `[startup-guard] Removing invalid PID file for ${serviceName} (not a positive integer)`,
140
+ );
141
+ if (!tryRemovePidFile(pidFile, serviceName, "invalid")) {
142
+ return {
143
+ ok: false,
144
+ message: `Cannot remove invalid PID file for ${serviceName}: ${pidFile}`,
145
+ };
146
+ }
147
+ } else if (isProcessAlive(savedPid)) {
148
+ // Process is alive - check if it's really the right service
149
+ if (port && !isPortInUse(port)) {
150
+ // PID exists but port is free - stale PID file
151
+ console.log(
152
+ `[startup-guard] Stale PID ${savedPid} for ${serviceName} (port ${port} free) - removing`,
153
+ );
154
+ if (!tryRemovePidFile(pidFile, serviceName, "stale")) {
155
+ return {
156
+ ok: false,
157
+ runningPid: savedPid,
158
+ message: `Cannot remove stale PID file for ${serviceName}: ${pidFile}`,
159
+ };
160
+ }
161
+ } else {
162
+ return {
163
+ ok: false,
164
+ runningPid: savedPid,
165
+ message: `${serviceName} already running (pid ${savedPid})${port ? ` on port ${port}` : ""}`,
166
+ };
167
+ }
168
+ } else {
169
+ // Stale PID file (process exited)
170
+ console.log(
171
+ `[startup-guard] Removing stale PID file for ${serviceName} (pid ${savedPid} dead)`,
172
+ );
173
+ if (!tryRemovePidFile(pidFile, serviceName, "stale")) {
174
+ return {
175
+ ok: false,
176
+ runningPid: savedPid,
177
+ message: `Cannot remove stale PID file for ${serviceName}: ${pidFile}`,
178
+ };
179
+ }
180
+ }
181
+ } catch (err) {
182
+ // Corrupted PID file
183
+ console.log(
184
+ `[startup-guard] Removing corrupted PID file for ${serviceName}: ${err.message}`,
185
+ );
186
+ if (!tryRemovePidFile(pidFile, serviceName, "corrupted")) {
187
+ return {
188
+ ok: false,
189
+ message: `Cannot remove corrupted PID file for ${serviceName}: ${pidFile}`,
190
+ };
191
+ }
192
+ }
193
+ }
194
+
195
+ // Check port conflict
196
+ if (port) {
197
+ const portPid = getPidOnPort(port);
198
+ if (portPid && portPid !== myPid) {
199
+ if (killStale) {
200
+ console.log(
201
+ `[startup-guard] Port ${port} occupied by PID ${portPid} - killing stale process`,
202
+ );
203
+ try {
204
+ process.kill(portPid, 9);
205
+ // Wait for port to be released
206
+ for (let i = 0; i < maxRetries; i++) {
207
+ if (!isPortInUse(port)) break;
208
+ const wait = (i + 1) * 1000;
209
+ console.log(
210
+ `[startup-guard] Port ${port} in use — retry ${i + 1}/${maxRetries} in ${wait}ms`,
211
+ );
212
+ execSync(`sleep ${wait / 1000}`, { stdio: "ignore" });
213
+ }
214
+ if (isPortInUse(port)) {
215
+ return {
216
+ ok: false,
217
+ runningPid: portPid,
218
+ message: `Port ${port} still in use after killing stale process ${portPid}`,
219
+ };
220
+ }
221
+ } catch (err) {
222
+ return {
223
+ ok: false,
224
+ runningPid: portPid,
225
+ message: `Failed to kill stale process ${portPid} on port ${port}: ${err.message}`,
226
+ };
227
+ }
228
+ } else {
229
+ return {
230
+ ok: false,
231
+ runningPid: portPid,
232
+ message: `Port ${port} already in use by process ${portPid}`,
233
+ };
234
+ }
235
+ }
236
+ }
237
+
238
+ // Acquire lock by writing PID file
239
+ try {
240
+ fs.writeFileSync(pidFile, String(myPid));
241
+ console.log(
242
+ `[startup-guard] Acquired lock for ${serviceName} (pid ${myPid})${port ? ` on port ${port}` : ""}`,
243
+ );
244
+
245
+ // Clean up PID file on exit
246
+ const cleanup = () => {
247
+ try {
248
+ const current = fs.existsSync(pidFile)
249
+ ? fs.readFileSync(pidFile, "utf8").trim()
250
+ : null;
251
+ if (current === String(myPid)) {
252
+ fs.unlinkSync(pidFile);
253
+ console.log(
254
+ `[startup-guard] Released lock for ${serviceName} (pid ${myPid})`,
255
+ );
256
+ }
257
+ } catch {}
258
+ };
259
+
260
+ process.on("exit", cleanup);
261
+ process.on("SIGINT", () => {
262
+ cleanup();
263
+ process.exit(130);
264
+ });
265
+ process.on("SIGTERM", () => {
266
+ cleanup();
267
+ process.exit(143);
268
+ });
269
+
270
+ return { ok: true, pid: myPid };
271
+ } catch (err) {
272
+ return {
273
+ ok: false,
274
+ message: `Failed to write PID file for ${serviceName}: ${err.message}`,
275
+ };
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Release startup lock (usually automatic via process.on('exit'))
281
+ */
282
+ export function releaseStartupLock(serviceName) {
283
+ const pidFile = path.join(PID_DIR, `${serviceName}.pid`);
284
+ try {
285
+ const savedPid = parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
286
+ if (savedPid === process.pid) {
287
+ fs.unlinkSync(pidFile);
288
+ console.log(`[startup-guard] Released lock for ${serviceName}`);
289
+ }
290
+ } catch {}
291
+ }