@cohortapp/agent-sdk 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (731) hide show
  1. package/.claude/commands/init-agent.md +104 -0
  2. package/.claude/commands/init-maestro.md +1187 -0
  3. package/.claude/settings.json +161 -0
  4. package/.env.example +216 -0
  5. package/README.md +632 -0
  6. package/agents/browser-operator/agent.md +52 -0
  7. package/agents/calendar-ops/agent.md +50 -0
  8. package/agents/communications/agent.md +96 -0
  9. package/agents/decision-log/agent.md +65 -0
  10. package/agents/desktop-operator/agent.md +59 -0
  11. package/agents/gmail-operator/agent.md +62 -0
  12. package/agents/inbound-dispatcher/agent.md +66 -0
  13. package/agents/inbox-processor/agent.md +39 -0
  14. package/agents/pmo-execution/agent.md +60 -0
  15. package/agents/session-spawner/agent.md +64 -0
  16. package/agents/slack-operator/agent.md +60 -0
  17. package/agents/whatsapp-operator/agent.md +60 -0
  18. package/agents/workflow-automation/agent.md +61 -0
  19. package/archetypes/altitudes/c-suite.yaml +58 -0
  20. package/archetypes/altitudes/founder.yaml +68 -0
  21. package/archetypes/altitudes/senior-manager.yaml +63 -0
  22. package/archetypes/altitudes/svp.yaml +60 -0
  23. package/archetypes/altitudes/vp.yaml +50 -0
  24. package/archetypes/archetype.schema.json +77 -0
  25. package/archetypes/base.yaml +47 -0
  26. package/archetypes/capabilities/commercial-leader.yaml +159 -0
  27. package/archetypes/capabilities/compliance-officer.yaml +159 -0
  28. package/archetypes/capabilities/executive-operator.yaml +169 -0
  29. package/archetypes/capabilities/finance-leader.yaml +162 -0
  30. package/archetypes/capabilities/operations-leader.yaml +154 -0
  31. package/archetypes/capabilities/product-leader.yaml +148 -0
  32. package/archetypes/capabilities/technical-leader.yaml +146 -0
  33. package/archetypes/functions/commercial-leader.yaml +62 -0
  34. package/archetypes/functions/compliance-officer.yaml +64 -0
  35. package/archetypes/functions/executive-operator.yaml +70 -0
  36. package/archetypes/functions/finance-leader.yaml +70 -0
  37. package/archetypes/functions/operations-leader.yaml +62 -0
  38. package/archetypes/functions/product-leader.yaml +61 -0
  39. package/archetypes/functions/technical-leader.yaml +57 -0
  40. package/bin/cohort-mcp.mjs +81 -0
  41. package/bin/maestro.mjs +3516 -0
  42. package/bin/maestro.test.mjs +1015 -0
  43. package/desktop-control/README.md +56 -0
  44. package/desktop-control/app-profiles/gmail.yaml +120 -0
  45. package/desktop-control/app-profiles/slack.yaml +315 -0
  46. package/desktop-control/app-profiles/whatsapp.yaml +107 -0
  47. package/docs/architecture/agent-topology.md +2239 -0
  48. package/docs/architecture/archetype-agent-factory.md +110 -0
  49. package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
  50. package/docs/architecture/continuous-monitoring.md +221 -0
  51. package/docs/architecture/mcp-capability-map.md +585 -0
  52. package/docs/architecture/system-architecture.md +1272 -0
  53. package/docs/company-context/README.md +40 -0
  54. package/docs/guides/agent-persona-setup.md +600 -0
  55. package/docs/guides/agents-observe-setup.md +64 -0
  56. package/docs/guides/billing-console-keys.md +88 -0
  57. package/docs/guides/ccxray-diagnostics.md +65 -0
  58. package/docs/guides/channel-bus.md +127 -0
  59. package/docs/guides/claude-mem-setup.md +79 -0
  60. package/docs/guides/claude-pace-setup.md +56 -0
  61. package/docs/guides/claudraband-sessions.md +98 -0
  62. package/docs/guides/clawteam-swarm.md +116 -0
  63. package/docs/guides/code-review-graph-setup.md +86 -0
  64. package/docs/guides/email-setup.md +431 -0
  65. package/docs/guides/mac-mini.md +119 -0
  66. package/docs/guides/media-generation-setup.md +349 -0
  67. package/docs/guides/model-routing.md +162 -0
  68. package/docs/guides/observability-otel.md +265 -0
  69. package/docs/guides/org-onboarding.md +132 -0
  70. package/docs/guides/outbound-governance-setup.md +437 -0
  71. package/docs/guides/pdf-generation-setup.md +315 -0
  72. package/docs/guides/poller-daemon-setup.md +563 -0
  73. package/docs/guides/rag-context-setup.md +459 -0
  74. package/docs/guides/self-optimization-pattern.md +82 -0
  75. package/docs/guides/setup-wizard.md +178 -0
  76. package/docs/guides/slack-setup.md +350 -0
  77. package/docs/guides/telegram-setup.md +227 -0
  78. package/docs/guides/twilio-subaccounts-setup.md +223 -0
  79. package/docs/guides/verification.md +128 -0
  80. package/docs/guides/voice-mode.md +188 -0
  81. package/docs/guides/voice-sms-setup.md +698 -0
  82. package/docs/guides/webhook-relay-setup.md +349 -0
  83. package/docs/guides/whatsapp-setup.md +288 -0
  84. package/docs/prompts/board-pack-cover-template.md +36 -0
  85. package/docs/prompts/decision-recommendation-template.md +88 -0
  86. package/docs/prompts/followup-message-template.md +141 -0
  87. package/docs/prompts/investor-letter-template.md +52 -0
  88. package/docs/prompts/morning-brief-template.md +82 -0
  89. package/docs/prompts/presentation-template.md +58 -0
  90. package/docs/prompts/weekly-strategic-memo-template.md +104 -0
  91. package/docs/research/hallucinated-tool-output-investigation.md +151 -0
  92. package/docs/runbooks/backup-restore.md +205 -0
  93. package/docs/runbooks/cohort-cutover.md +129 -0
  94. package/docs/runbooks/fleet-operations.md +200 -0
  95. package/docs/runbooks/incident-response.md +226 -0
  96. package/docs/runbooks/mac-mini-bootstrap.md +431 -0
  97. package/docs/runbooks/perpetual-operations.md +509 -0
  98. package/docs/runbooks/recovery-and-failover.md +260 -0
  99. package/framework-features.json +267 -0
  100. package/ingest/README.md +87 -0
  101. package/lib/action-executor.js +689 -0
  102. package/lib/action-executor.test.mjs +871 -0
  103. package/lib/agent-root.mjs +37 -0
  104. package/lib/archetype.mjs +236 -0
  105. package/lib/archetype.test.mjs +132 -0
  106. package/lib/autonomy.mjs +114 -0
  107. package/lib/autonomy.test.mjs +66 -0
  108. package/lib/backlog.mjs +358 -0
  109. package/lib/backlog.test.mjs +266 -0
  110. package/lib/budget-guard.mjs +279 -0
  111. package/lib/budget-guard.test.mjs +291 -0
  112. package/lib/cadence-bus-schedule.test.mjs +194 -0
  113. package/lib/cadence-bus.mjs +1120 -0
  114. package/lib/cadence-bus.test.mjs +720 -0
  115. package/lib/cadences.mjs +205 -0
  116. package/lib/cadences.test.mjs +125 -0
  117. package/lib/capability.mjs +154 -0
  118. package/lib/capability.test.mjs +78 -0
  119. package/lib/channels/base-adapter.mjs +719 -0
  120. package/lib/channels/base-adapter.test.mjs +590 -0
  121. package/lib/channels/channel.mjs +128 -0
  122. package/lib/channels/channels.test.mjs +371 -0
  123. package/lib/channels/contract.mjs +215 -0
  124. package/lib/channels/contract.test.mjs +137 -0
  125. package/lib/channels/conversation-resolver.mjs +95 -0
  126. package/lib/channels/gmail/adapter.mjs +87 -0
  127. package/lib/channels/inbox-item.mjs +255 -0
  128. package/lib/channels/inbox-item.test.mjs +335 -0
  129. package/lib/channels/index.mjs +94 -0
  130. package/lib/channels/orgmail/adapter.mjs +353 -0
  131. package/lib/channels/orgmail/adapter.test.mjs +311 -0
  132. package/lib/channels/pairing.mjs +363 -0
  133. package/lib/channels/pairing.test.mjs +270 -0
  134. package/lib/channels/registry.mjs +164 -0
  135. package/lib/channels/slack/adapter.mjs +317 -0
  136. package/lib/channels/slack-adapter.test.mjs +212 -0
  137. package/lib/channels/sms/adapter.mjs +43 -0
  138. package/lib/channels/telegram/adapter.mjs +432 -0
  139. package/lib/channels/telegram-adapter.test.mjs +306 -0
  140. package/lib/channels/voice/adapter.mjs +301 -0
  141. package/lib/channels/voice/adapter.test.mjs +278 -0
  142. package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
  143. package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
  144. package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
  145. package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
  146. package/lib/charter.mjs +256 -0
  147. package/lib/charter.test.mjs +89 -0
  148. package/lib/claude-bin.mjs +134 -0
  149. package/lib/claude-bin.test.mjs +75 -0
  150. package/lib/collective/capture.mjs +185 -0
  151. package/lib/collective/capture.test.mjs +121 -0
  152. package/lib/collective/cards.mjs +201 -0
  153. package/lib/collective/cards.test.mjs +114 -0
  154. package/lib/collective/config.mjs +186 -0
  155. package/lib/collective/config.test.mjs +123 -0
  156. package/lib/collective/global-config.mjs +113 -0
  157. package/lib/collective/global-config.test.mjs +75 -0
  158. package/lib/collective/presence.mjs +201 -0
  159. package/lib/collective/presence.test.mjs +95 -0
  160. package/lib/collective/recall.mjs +215 -0
  161. package/lib/collective/recall.test.mjs +116 -0
  162. package/lib/comms/send-gate.mjs +554 -0
  163. package/lib/comms/send-gate.test.mjs +577 -0
  164. package/lib/comms.mjs +67 -0
  165. package/lib/comms.test.mjs +41 -0
  166. package/lib/diagnostics/alerts.mjs +424 -0
  167. package/lib/diagnostics/alerts.test.mjs +318 -0
  168. package/lib/diagnostics/backup-freshness.mjs +188 -0
  169. package/lib/diagnostics/backup-freshness.test.mjs +185 -0
  170. package/lib/diagnostics/counters.mjs +269 -0
  171. package/lib/diagnostics/counters.test.mjs +206 -0
  172. package/lib/diagnostics/events.mjs +188 -0
  173. package/lib/diagnostics/events.test.mjs +290 -0
  174. package/lib/diagnostics/otel.mjs +237 -0
  175. package/lib/diagnostics/otel.test.mjs +196 -0
  176. package/lib/diagnostics/trace.mjs +216 -0
  177. package/lib/diagnostics/trace.test.mjs +251 -0
  178. package/lib/env-compat.mjs +74 -0
  179. package/lib/env-compat.test.mjs +104 -0
  180. package/lib/feature-init.mjs +331 -0
  181. package/lib/fs-atomic.mjs +112 -0
  182. package/lib/fs-atomic.test.mjs +72 -0
  183. package/lib/fs-ownership.mjs +111 -0
  184. package/lib/fs-ownership.test.mjs +158 -0
  185. package/lib/hooks/bus.mjs +347 -0
  186. package/lib/hooks/bus.test.mjs +387 -0
  187. package/lib/index.js +16 -0
  188. package/lib/learning/config.mjs +106 -0
  189. package/lib/learning/config.test.mjs +75 -0
  190. package/lib/learning/counters.mjs +156 -0
  191. package/lib/learning/counters.test.mjs +69 -0
  192. package/lib/learning/curator-consolidate.test.mjs +238 -0
  193. package/lib/learning/curator.mjs +453 -0
  194. package/lib/learning/curator.test.mjs +106 -0
  195. package/lib/learning/log.mjs +40 -0
  196. package/lib/learning/reflect.mjs +534 -0
  197. package/lib/learning/reflect.test.mjs +0 -0
  198. package/lib/learning/session-index.mjs +352 -0
  199. package/lib/learning/session-index.test.mjs +125 -0
  200. package/lib/learning/skill-writer.mjs +474 -0
  201. package/lib/learning/skill-writer.test.mjs +210 -0
  202. package/lib/mcp/server.mjs +328 -0
  203. package/lib/mcp/server.test.mjs +400 -0
  204. package/lib/model-router/auth-profiles.mjs +758 -0
  205. package/lib/model-router/auth-profiles.test.mjs +580 -0
  206. package/lib/model-router/catalog/anthropic.yaml +153 -0
  207. package/lib/model-router/catalog/deepseek.yaml +86 -0
  208. package/lib/model-router/catalog/moonshot.yaml +81 -0
  209. package/lib/model-router/catalog/qwen.yaml +114 -0
  210. package/lib/model-router/catalog.mjs +925 -0
  211. package/lib/model-router/catalog.test.mjs +385 -0
  212. package/lib/model-router/economics.mjs +564 -0
  213. package/lib/model-router/economics.test.mjs +344 -0
  214. package/lib/model-router/failover.mjs +298 -0
  215. package/lib/model-router/failover.test.mjs +439 -0
  216. package/lib/model-router/health.mjs +453 -0
  217. package/lib/model-router/health.test.mjs +338 -0
  218. package/lib/model-router/integration-coverage.test.mjs +829 -0
  219. package/lib/model-router/integration.test.mjs +564 -0
  220. package/lib/model-router/ledger.mjs +402 -0
  221. package/lib/model-router/ledger.test.mjs +382 -0
  222. package/lib/model-router/llm-task.mjs +515 -0
  223. package/lib/model-router/llm-task.test.mjs +392 -0
  224. package/lib/model-router/org-credentials.mjs +260 -0
  225. package/lib/model-router/org-credentials.test.mjs +265 -0
  226. package/lib/model-router/pricing-refresh.mjs +463 -0
  227. package/lib/model-router/pricing-refresh.test.mjs +286 -0
  228. package/lib/model-router/reconcile.mjs +429 -0
  229. package/lib/model-router/reconcile.test.mjs +316 -0
  230. package/lib/model-router/repair.mjs +471 -0
  231. package/lib/model-router/repair.test.mjs +180 -0
  232. package/lib/model-router/resolve.mjs +1206 -0
  233. package/lib/model-router/spawn.mjs +497 -0
  234. package/lib/model-router/spawn.test.mjs +425 -0
  235. package/lib/model-router/taxonomy.mjs +893 -0
  236. package/lib/model-router/taxonomy.test.mjs +410 -0
  237. package/lib/model-router.mjs +677 -0
  238. package/lib/model-router.test.mjs +907 -0
  239. package/lib/org/activity.mjs +211 -0
  240. package/lib/org/activity.test.mjs +134 -0
  241. package/lib/org/approvals.mjs +448 -0
  242. package/lib/org/approvals.test.mjs +216 -0
  243. package/lib/org/awareness.mjs +222 -0
  244. package/lib/org/awareness.test.mjs +159 -0
  245. package/lib/org/board.mjs +229 -0
  246. package/lib/org/board.test.mjs +177 -0
  247. package/lib/org/bootstrap-context.mjs +169 -0
  248. package/lib/org/bootstrap-context.test.mjs +153 -0
  249. package/lib/org/client.mjs +1628 -0
  250. package/lib/org/client.test.mjs +1107 -0
  251. package/lib/org/cohort-client.mjs +67 -0
  252. package/lib/org/cohort-client.test.mjs +126 -0
  253. package/lib/org/cost-sync.mjs +227 -0
  254. package/lib/org/cost-sync.test.mjs +153 -0
  255. package/lib/org/doctor.mjs +212 -0
  256. package/lib/org/doctor.test.mjs +212 -0
  257. package/lib/org/handoff.mjs +293 -0
  258. package/lib/org/handoff.test.mjs +269 -0
  259. package/lib/org/integration-tools.mjs +182 -0
  260. package/lib/org/integration-tools.test.mjs +160 -0
  261. package/lib/org/keys.mjs +131 -0
  262. package/lib/org/keys.test.mjs +92 -0
  263. package/lib/org/knowledge.mjs +463 -0
  264. package/lib/org/knowledge.test.mjs +319 -0
  265. package/lib/org/leases.mjs +335 -0
  266. package/lib/org/leases.test.mjs +235 -0
  267. package/lib/org/mesh-integration.test.mjs +127 -0
  268. package/lib/org/mesh.mjs +459 -0
  269. package/lib/org/mesh.test.mjs +345 -0
  270. package/lib/org/messaging.mjs +503 -0
  271. package/lib/org/messaging.test.mjs +238 -0
  272. package/lib/org/policy.mjs +345 -0
  273. package/lib/org/policy.test.mjs +237 -0
  274. package/lib/org/protocol.checksum +1 -0
  275. package/lib/org/protocol.checksum.test.mjs +90 -0
  276. package/lib/org/protocol.mjs +967 -0
  277. package/lib/org/protocol.test.mjs +264 -0
  278. package/lib/org/registry.mjs +194 -0
  279. package/lib/org/registry.test.mjs +100 -0
  280. package/lib/org/tool-surface-integration.test.mjs +120 -0
  281. package/lib/org/tool-surface.mjs +2535 -0
  282. package/lib/org/tool-surface.test.mjs +589 -0
  283. package/lib/org/ui-parity.mjs +3236 -0
  284. package/lib/org/ui-parity.test.mjs +348 -0
  285. package/lib/org/verify.mjs +176 -0
  286. package/lib/org/verify.test.mjs +194 -0
  287. package/lib/rag/embed.mjs +188 -0
  288. package/lib/rag/indexer.mjs +425 -0
  289. package/lib/rag/rag.test.mjs +505 -0
  290. package/lib/rag/search.mjs +475 -0
  291. package/lib/rate-guard.mjs +246 -0
  292. package/lib/rate-guard.test.mjs +201 -0
  293. package/lib/render.mjs +112 -0
  294. package/lib/render.test.mjs +68 -0
  295. package/lib/resource-governor.mjs +297 -0
  296. package/lib/resource-governor.test.mjs +262 -0
  297. package/lib/scheduling/dynamic-jobs.mjs +675 -0
  298. package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
  299. package/lib/scheduling/jitter.mjs +0 -0
  300. package/lib/scheduling/jitter.test.mjs +140 -0
  301. package/lib/secrets/broker.mjs +315 -0
  302. package/lib/secrets/broker.test.mjs +280 -0
  303. package/lib/secrets/providers.mjs +461 -0
  304. package/lib/secrets/providers.test.mjs +274 -0
  305. package/lib/security/audit-engine.mjs +684 -0
  306. package/lib/security/audit-engine.test.mjs +389 -0
  307. package/lib/security/coerce-args.mjs +552 -0
  308. package/lib/security/coerce-args.test.mjs +281 -0
  309. package/lib/security/dangerous-tools.mjs +97 -0
  310. package/lib/security/dangerous-tools.test.mjs +68 -0
  311. package/lib/security/external-content.mjs +145 -0
  312. package/lib/security/external-content.test.mjs +67 -0
  313. package/lib/security/redact.mjs +592 -0
  314. package/lib/security/redact.test.mjs +441 -0
  315. package/lib/security/secret-equal.mjs +73 -0
  316. package/lib/security/secret-equal.test.mjs +55 -0
  317. package/lib/session-permissions.mjs +101 -0
  318. package/lib/session-permissions.test.mjs +100 -0
  319. package/lib/setup/claude-probe.mjs +74 -0
  320. package/lib/setup/completeness.mjs +175 -0
  321. package/lib/setup/completeness.test.mjs +110 -0
  322. package/lib/setup/context-pack.mjs +173 -0
  323. package/lib/setup/context-pack.test.mjs +89 -0
  324. package/lib/setup/enrich.mjs +277 -0
  325. package/lib/setup/enrich.test.mjs +115 -0
  326. package/lib/setup/enroll-from-cohort.mjs +441 -0
  327. package/lib/setup/enroll-from-cohort.test.mjs +233 -0
  328. package/lib/setup/integration.test.mjs +162 -0
  329. package/lib/setup/io.mjs +360 -0
  330. package/lib/setup/io.test.mjs +77 -0
  331. package/lib/setup/run-generator.mjs +81 -0
  332. package/lib/setup/runner.mjs +244 -0
  333. package/lib/setup/runner.test.mjs +132 -0
  334. package/lib/setup/sections/comms.mjs +173 -0
  335. package/lib/setup/sections/company.mjs +120 -0
  336. package/lib/setup/sections/enrich.mjs +138 -0
  337. package/lib/setup/sections/identity.mjs +182 -0
  338. package/lib/setup/sections/identity.test.mjs +140 -0
  339. package/lib/setup/sections/learning.mjs +153 -0
  340. package/lib/setup/sections/learning.test.mjs +81 -0
  341. package/lib/setup/sections/messaging.mjs +219 -0
  342. package/lib/setup/sections/messaging.test.mjs +127 -0
  343. package/lib/setup/sections/model.mjs +102 -0
  344. package/lib/setup/sections/operating-model.mjs +78 -0
  345. package/lib/setup/sections/org.mjs +475 -0
  346. package/lib/setup/sections/org.test.mjs +313 -0
  347. package/lib/setup/sections/orgmail.mjs +173 -0
  348. package/lib/setup/sections/orgmail.test.mjs +118 -0
  349. package/lib/setup/sections/recovery.mjs +159 -0
  350. package/lib/setup/sections/recovery.test.mjs +98 -0
  351. package/lib/setup/sections/tools.mjs +132 -0
  352. package/lib/setup/sections/verify.mjs +97 -0
  353. package/lib/setup/sot.mjs +205 -0
  354. package/lib/setup/sot.test.mjs +81 -0
  355. package/lib/setup/state.mjs +151 -0
  356. package/lib/setup/state.test.mjs +92 -0
  357. package/lib/singleton.js +229 -0
  358. package/lib/singleton.test.mjs +135 -0
  359. package/lib/telemetry/alerts.mjs +216 -0
  360. package/lib/telemetry/alerts.test.mjs +109 -0
  361. package/lib/telemetry/collect.mjs +512 -0
  362. package/lib/telemetry/collect.test.mjs +202 -0
  363. package/lib/tool-definitions-integration.test.mjs +83 -0
  364. package/lib/tool-definitions.js +738 -0
  365. package/lib/tool-definitions.test.mjs +437 -0
  366. package/lib/util/fetch-timeout.mjs +136 -0
  367. package/lib/util/fetch-timeout.test.mjs +202 -0
  368. package/lib/util/reconnect.mjs +343 -0
  369. package/lib/util/reconnect.test.mjs +369 -0
  370. package/lib/util/unhandled.mjs +205 -0
  371. package/lib/util/unhandled.test.mjs +216 -0
  372. package/lib/voice/context-loader.mjs +466 -0
  373. package/lib/voice/index.mjs +100 -0
  374. package/lib/voice/openai-realtime.mjs +510 -0
  375. package/lib/voice/outbound.mjs +542 -0
  376. package/lib/voice/outbound.test.mjs +69 -0
  377. package/lib/voice/post-call-brief.mjs +428 -0
  378. package/lib/voice/provider.mjs +52 -0
  379. package/lib/voice/session-rotation.mjs +257 -0
  380. package/lib/voice/stt.mjs +161 -0
  381. package/lib/voice/stt.test.mjs +226 -0
  382. package/lib/voice/tool-bridge.mjs +370 -0
  383. package/lib/voice/tts.mjs +104 -0
  384. package/lib/voice/twilio-sip-bridge.mjs +288 -0
  385. package/lib/voice/voice.test.mjs +990 -0
  386. package/mcp/README.md +80 -0
  387. package/package.json +151 -0
  388. package/plugins/maestro-skills/plugin.json +139 -0
  389. package/plugins/maestro-skills/skills/agents-observe.md +110 -0
  390. package/plugins/maestro-skills/skills/board-deck.md +68 -0
  391. package/plugins/maestro-skills/skills/books-close.md +77 -0
  392. package/plugins/maestro-skills/skills/brand-steward.md +121 -0
  393. package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
  394. package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
  395. package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
  396. package/plugins/maestro-skills/skills/claude-pace.md +61 -0
  397. package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
  398. package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
  399. package/plugins/maestro-skills/skills/decision-brief.md +89 -0
  400. package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
  401. package/plugins/maestro-skills/skills/draft-comms.md +84 -0
  402. package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
  403. package/plugins/maestro-skills/skills/files-find.md +65 -0
  404. package/plugins/maestro-skills/skills/generative-ui.md +228 -0
  405. package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
  406. package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
  407. package/plugins/maestro-skills/skills/mail-triage.md +86 -0
  408. package/plugins/maestro-skills/skills/morning-brief.md +54 -0
  409. package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
  410. package/plugins/maestro-skills/skills/org-board.md +133 -0
  411. package/plugins/maestro-skills/skills/org-credential.md +68 -0
  412. package/plugins/maestro-skills/skills/org-recall.md +81 -0
  413. package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
  414. package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
  415. package/plugins/maestro-skills/skills/router-why.md +78 -0
  416. package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
  417. package/plugins/maestro-skills/skills/session-search.md +71 -0
  418. package/plugins/maestro-skills/skills/set-reminder.md +93 -0
  419. package/plugins/maestro-skills/skills/slack-followup.md +64 -0
  420. package/plugins/maestro-skills/skills/team-activity.md +86 -0
  421. package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
  422. package/policies/action-classification.yaml +114 -0
  423. package/policies/ai-disclosure.yaml +294 -0
  424. package/policies/communication-style.md +139 -0
  425. package/policies/information-barriers.yaml +118 -0
  426. package/policies/prompt-injection-defence.yaml +138 -0
  427. package/public/assets/icon-dark.png +0 -0
  428. package/public/assets/icon-dark.svg +9 -0
  429. package/public/assets/icon-light.svg +9 -0
  430. package/public/assets/logo-dark.svg +15 -0
  431. package/public/assets/logo-light.svg +15 -0
  432. package/scaffold/.mcp.json +7 -0
  433. package/scaffold/CLAUDE.md +368 -0
  434. package/scaffold/config/agent.json +55 -0
  435. package/scaffold/config/agent.ts +76 -0
  436. package/scaffold/config/agent.ts.example +89 -0
  437. package/scaffold/config/alerts.yaml +23 -0
  438. package/scaffold/config/allowlist.yaml.example +25 -0
  439. package/scaffold/config/caller-id-map.yaml +46 -0
  440. package/scaffold/config/collective.yaml +49 -0
  441. package/scaffold/config/company.json +20 -0
  442. package/scaffold/config/known-agents.json +6 -0
  443. package/scaffold/config/learning.yaml +55 -0
  444. package/scaffold/config/model-routing.yaml.example +104 -0
  445. package/scaffold/config/org.yaml +25 -0
  446. package/scaffold/config/orgmail.yaml.example +19 -0
  447. package/scaffold/config/recovery.yaml +72 -0
  448. package/scaffold/config/secrets.yaml +27 -0
  449. package/scaffold/config/slack.yaml.example +35 -0
  450. package/scaffold/config/telegram.yaml.example +38 -0
  451. package/scaffold/config/voice.yaml.example +89 -0
  452. package/scaffold/config/whatsapp.yaml.example +39 -0
  453. package/schedules/README.md +49 -0
  454. package/schedules/triggers/backlog-executor.md +102 -0
  455. package/schedules/triggers/brand-steward.md +72 -0
  456. package/schedules/triggers/daily-evening-wrap.md +159 -0
  457. package/schedules/triggers/daily-midday-sweep.md +58 -0
  458. package/schedules/triggers/daily-morning-brief.md +55 -0
  459. package/schedules/triggers/directory-hygiene.md +81 -0
  460. package/schedules/triggers/dynamic-jobs.md +40 -0
  461. package/schedules/triggers/inbox-processor.md +115 -0
  462. package/schedules/triggers/meeting-action-capture.md +60 -0
  463. package/schedules/triggers/meeting-prep.md +69 -0
  464. package/schedules/triggers/messaging-inbound.md +50 -0
  465. package/schedules/triggers/org-pulse.md +24 -0
  466. package/schedules/triggers/quarterly-self-assessment.md +54 -0
  467. package/schedules/triggers/weekly-engineering-health.md +37 -0
  468. package/schedules/triggers/weekly-execution.md +65 -0
  469. package/schedules/triggers/weekly-hiring.md +53 -0
  470. package/schedules/triggers/weekly-priorities.md +38 -0
  471. package/schedules/triggers/weekly-strategic-memo.md +124 -0
  472. package/scripts/archive-email.sh +55 -0
  473. package/scripts/cadence/cadence-status.mjs +36 -0
  474. package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
  475. package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
  476. package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
  477. package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
  478. package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
  479. package/scripts/ci/check-docs-accuracy.mjs +493 -0
  480. package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
  481. package/scripts/ci/check-exports-exist.mjs +140 -0
  482. package/scripts/ci/check-files-exist.mjs +107 -0
  483. package/scripts/ci/check-no-build-artifacts.mjs +111 -0
  484. package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
  485. package/scripts/ci/check-no-confidential.mjs +198 -0
  486. package/scripts/ci/check-no-conflict-markers.mjs +169 -0
  487. package/scripts/ci/check-no-residual-identity.mjs +163 -0
  488. package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
  489. package/scripts/ci/check-tarball-fidelity.mjs +205 -0
  490. package/scripts/ci/check-unresolved-tokens.mjs +83 -0
  491. package/scripts/ci/check.mjs +109 -0
  492. package/scripts/ci/check.test.mjs +194 -0
  493. package/scripts/ci/run-coverage.mjs +82 -0
  494. package/scripts/ci/run-tests.mjs +71 -0
  495. package/scripts/cloud-relay/README.md +59 -0
  496. package/scripts/cloud-relay/index.mjs +233 -0
  497. package/scripts/cloud-relay/package.json +15 -0
  498. package/scripts/cloud-relay/railway.json +13 -0
  499. package/scripts/cloud-relay/voice/README.md +94 -0
  500. package/scripts/cloud-relay/voice/package-lock.json +39 -0
  501. package/scripts/cloud-relay/voice/package.json +16 -0
  502. package/scripts/cloud-relay/voice/railway.json +13 -0
  503. package/scripts/cloud-relay/voice/server.mjs +532 -0
  504. package/scripts/collective/hook-runner.mjs +211 -0
  505. package/scripts/collective/hook-runner.test.mjs +90 -0
  506. package/scripts/collective/org-pulse.mjs +72 -0
  507. package/scripts/collective/org-sync.mjs +61 -0
  508. package/scripts/collective/recall.mjs +45 -0
  509. package/scripts/collective/who.mjs +30 -0
  510. package/scripts/comms-monitor.sh +288 -0
  511. package/scripts/configure-whatsapp-sandbox.sh +201 -0
  512. package/scripts/continuous-monitor.sh +91 -0
  513. package/scripts/cost/fleet-digest.mjs +407 -0
  514. package/scripts/cost/fleet-digest.test.mjs +207 -0
  515. package/scripts/cost/track-claude-usage.mjs +169 -0
  516. package/scripts/daemon/agent-daemon.mjs +989 -0
  517. package/scripts/daemon/agent-daemon.test.mjs +525 -0
  518. package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
  519. package/scripts/daemon/cadence-consumer.mjs +1080 -0
  520. package/scripts/daemon/cadence-consumer.test.mjs +770 -0
  521. package/scripts/daemon/cadence-handlers.mjs +1121 -0
  522. package/scripts/daemon/cadence-handlers.test.mjs +617 -0
  523. package/scripts/daemon/classifier.mjs +704 -0
  524. package/scripts/daemon/classifier.test.mjs +238 -0
  525. package/scripts/daemon/classify-kind.mjs +54 -0
  526. package/scripts/daemon/classify-kind.test.mjs +40 -0
  527. package/scripts/daemon/context-compiler.mjs +605 -0
  528. package/scripts/daemon/context-compiler.test.mjs +300 -0
  529. package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
  530. package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
  531. package/scripts/daemon/dispatcher.mjs +1516 -0
  532. package/scripts/daemon/health.mjs +72 -0
  533. package/scripts/daemon/inbox-deferral.mjs +210 -0
  534. package/scripts/daemon/inbox-deferral.test.mjs +242 -0
  535. package/scripts/daemon/integration.test.mjs +149 -0
  536. package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
  537. package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
  538. package/scripts/daemon/launchd-wrapper.sh +91 -0
  539. package/scripts/daemon/lib/session-router.mjs +274 -0
  540. package/scripts/daemon/lib/session-router.test.mjs +295 -0
  541. package/scripts/daemon/maestro-daemon.mjs +275 -0
  542. package/scripts/daemon/prompt-builder.mjs +685 -0
  543. package/scripts/daemon/prompt-builder.test.mjs +213 -0
  544. package/scripts/daemon/responder.mjs +854 -0
  545. package/scripts/daemon/session-lock.mjs +721 -0
  546. package/scripts/daemon/session-lock.test.mjs +252 -0
  547. package/scripts/daemon/session-outcomes.mjs +640 -0
  548. package/scripts/daemon/session-outcomes.test.mjs +533 -0
  549. package/scripts/daemon/typing-registry.mjs +90 -0
  550. package/scripts/daemon/typing-registry.test.mjs +77 -0
  551. package/scripts/daemon/voice-webhook-server.mjs +804 -0
  552. package/scripts/decisions/capture-decision.mjs +116 -0
  553. package/scripts/disclosure_assessment.py +873 -0
  554. package/scripts/disclosure_boundaries.py +562 -0
  555. package/scripts/email-signature-principal.html +52 -0
  556. package/scripts/email-signature.html +60 -0
  557. package/scripts/email_quote_thread.py +167 -0
  558. package/scripts/email_thread_dedup.py +362 -0
  559. package/scripts/emergency-stop.sh +81 -0
  560. package/scripts/healthcheck.sh +116 -0
  561. package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
  562. package/scripts/hooks/block-mcp-slack-send.sh +7 -0
  563. package/scripts/hooks/post-action-log.sh +126 -0
  564. package/scripts/hooks/pre-send-audit.sh +174 -0
  565. package/scripts/hooks/pre-send-audit.test.mjs +215 -0
  566. package/scripts/hooks/session-end-log.sh +27 -0
  567. package/scripts/hooks/session-start-banner.sh +115 -0
  568. package/scripts/huddle/audio-bridge.mjs +664 -0
  569. package/scripts/huddle/boot-slack-cdp.sh +102 -0
  570. package/scripts/huddle/huddle-controller.mjs +942 -0
  571. package/scripts/huddle/huddle-server.mjs +1229 -0
  572. package/scripts/huddle/launch-slack.sh +232 -0
  573. package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
  574. package/scripts/huddle/package-lock.json +62 -0
  575. package/scripts/huddle/package.json +22 -0
  576. package/scripts/huddle/setup-audio.sh +239 -0
  577. package/scripts/huddle/start-call.mjs +318 -0
  578. package/scripts/huddle/test-pipeline.mjs +263 -0
  579. package/scripts/learning/consolidate-skills.mjs +72 -0
  580. package/scripts/learning/session-search.mjs +125 -0
  581. package/scripts/llm_email_dedup.py +442 -0
  582. package/scripts/local-triggers/generate-plists.sh +432 -0
  583. package/scripts/local-triggers/generate-plists.test.mjs +413 -0
  584. package/scripts/local-triggers/install-all.sh +49 -0
  585. package/scripts/local-triggers/plists/.gitkeep +0 -0
  586. package/scripts/local-triggers/run-trigger.sh +63 -0
  587. package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
  588. package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
  589. package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
  590. package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
  591. package/scripts/maintenance/backup-to-cloud.sh +124 -0
  592. package/scripts/maintenance/health-check.sh +377 -0
  593. package/scripts/media-generation/README.md +105 -0
  594. package/scripts/media-generation/gemini-image-client.mjs +173 -0
  595. package/scripts/media-generation/generate-assets.mjs +289 -0
  596. package/scripts/media-generation/veo-video-client.mjs +219 -0
  597. package/scripts/org/send-orgmail.mjs +227 -0
  598. package/scripts/outbound-dedup-cleanup.sh +43 -0
  599. package/scripts/outbound-dedup.sh +477 -0
  600. package/scripts/outbound_dedup.py +115 -0
  601. package/scripts/parse-voice-transcript.mjs +481 -0
  602. package/scripts/pdf-generation/README.md +63 -0
  603. package/scripts/pdf-generation/build-document.mjs +247 -0
  604. package/scripts/pdf-generation/templates/board-pack.latex +136 -0
  605. package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
  606. package/scripts/pdf-generation/templates/memo.latex +114 -0
  607. package/scripts/poll-slack-events.sh +35 -0
  608. package/scripts/poller/calendar-poller.mjs +12 -0
  609. package/scripts/poller/gmail-poller.mjs +192 -0
  610. package/scripts/poller/imap-client.mjs +289 -0
  611. package/scripts/poller/inbox-scan-poller.mjs +156 -0
  612. package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
  613. package/scripts/poller/index.mjs +73 -0
  614. package/scripts/poller/intra-session-check.mjs +285 -0
  615. package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
  616. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
  617. package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
  618. package/scripts/poller/secondary-gmail-poller.mjs +132 -0
  619. package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
  620. package/scripts/poller/slack-poller.mjs +854 -0
  621. package/scripts/poller/slack-socket-mode.mjs +917 -0
  622. package/scripts/poller/slack-socket-mode.test.mjs +753 -0
  623. package/scripts/poller/trigger.mjs +75 -0
  624. package/scripts/poller/utils.mjs +371 -0
  625. package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
  626. package/scripts/poller/voice-poller.mjs +236 -0
  627. package/scripts/poller-launchd/install.sh +66 -0
  628. package/scripts/poller-launchd/poller.plist.template +40 -0
  629. package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
  630. package/scripts/post-interaction-indexer.py +1598 -0
  631. package/scripts/pre-draft-context.py +994 -0
  632. package/scripts/pre_draft_lookup.py +258 -0
  633. package/scripts/rag/build-index.mjs +47 -0
  634. package/scripts/rag/ingest.mjs +111 -0
  635. package/scripts/rag/search.mjs +119 -0
  636. package/scripts/rag-indexer.py +629 -0
  637. package/scripts/restore-from-backup.sh +248 -0
  638. package/scripts/restore-from-backup.test.mjs +178 -0
  639. package/scripts/resume-operations.sh +80 -0
  640. package/scripts/search-secondary-inbox.py +181 -0
  641. package/scripts/secondary-inbox-poller.py +437 -0
  642. package/scripts/self-optimization/compute-metrics.py +398 -0
  643. package/scripts/send-email-as-principal.py +369 -0
  644. package/scripts/send-email-threaded.py +392 -0
  645. package/scripts/send-email-with-attachment.py +377 -0
  646. package/scripts/send-email.sh +131 -0
  647. package/scripts/send-sms.sh +175 -0
  648. package/scripts/send-whatsapp.sh +292 -0
  649. package/scripts/session-start.sh +106 -0
  650. package/scripts/setup/boot-claude-session.sh +94 -0
  651. package/scripts/setup/configure-macos.sh +674 -0
  652. package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
  653. package/scripts/setup/configure-voice-tunnel.mjs +182 -0
  654. package/scripts/setup/generate-agent-env.mjs +92 -0
  655. package/scripts/setup/generate-agent-package-json.mjs +222 -0
  656. package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
  657. package/scripts/setup/generate-autonomy.mjs +60 -0
  658. package/scripts/setup/generate-backlog.mjs +101 -0
  659. package/scripts/setup/generate-cadences.mjs +92 -0
  660. package/scripts/setup/generate-capability.mjs +162 -0
  661. package/scripts/setup/generate-charter.mjs +76 -0
  662. package/scripts/setup/generate-comms.mjs +59 -0
  663. package/scripts/setup/generate-company.mjs +92 -0
  664. package/scripts/setup/init-agent.sh +547 -0
  665. package/scripts/setup/init-agent.test.mjs +151 -0
  666. package/scripts/setup/init-archetype.mjs +74 -0
  667. package/scripts/setup/init-backup.mjs +54 -0
  668. package/scripts/setup/init-cadence-bus.mjs +60 -0
  669. package/scripts/setup/init-channel-bus.mjs +46 -0
  670. package/scripts/setup/init-cost-tracking.mjs +45 -0
  671. package/scripts/setup/init-decision-capture.mjs +66 -0
  672. package/scripts/setup/init-known-agents.mjs +57 -0
  673. package/scripts/setup/init-learning.mjs +70 -0
  674. package/scripts/setup/init-memory-executive.mjs +45 -0
  675. package/scripts/setup/init-model-router.mjs +124 -0
  676. package/scripts/setup/init-rag.mjs +174 -0
  677. package/scripts/setup/init-session-router.mjs +38 -0
  678. package/scripts/setup/init-slack-socket-mode.mjs +260 -0
  679. package/scripts/setup/init-telegram.mjs +165 -0
  680. package/scripts/setup/init-voice-realtime.mjs +204 -0
  681. package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
  682. package/scripts/setup/install-dev-tools.sh +150 -0
  683. package/scripts/setup/lib/install-plist.mjs +95 -0
  684. package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
  685. package/scripts/setup/render-environment-yaml.mjs +133 -0
  686. package/scripts/slack-events-ctl.sh +177 -0
  687. package/scripts/slack-events-server.mjs +1045 -0
  688. package/scripts/slack-react.mjs +89 -0
  689. package/scripts/slack-responded.sh +232 -0
  690. package/scripts/slack-send.sh +287 -0
  691. package/scripts/slack-typing.mjs +196 -0
  692. package/scripts/slack-upload-v2.py +95 -0
  693. package/scripts/sms-handler.mjs +450 -0
  694. package/scripts/spawn-session.sh +120 -0
  695. package/scripts/sync-protocol.mjs +217 -0
  696. package/scripts/system-verify.sh +184 -0
  697. package/scripts/test-email-thread-dedup.py +239 -0
  698. package/scripts/test-information-barriers.py +484 -0
  699. package/scripts/test-llm-email-dedup.py +251 -0
  700. package/scripts/test-pre-draft-integration.py +203 -0
  701. package/scripts/test-rag-phase2.sh +442 -0
  702. package/scripts/test-rag-search.sh +251 -0
  703. package/scripts/test-voice-parser.mjs +316 -0
  704. package/scripts/user-context-search.py +659 -0
  705. package/scripts/validate_outbound.py +1504 -0
  706. package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
  707. package/scripts/watchdog/force-reboot.sh +157 -0
  708. package/scripts/watchdog/memory-watchdog.sh +473 -0
  709. package/scripts/whatsapp-handler.mjs +538 -0
  710. package/teams/desktop-operations.yaml +34 -0
  711. package/teams/executive-office.yaml +27 -0
  712. package/teams/legal-and-regulatory.yaml +24 -0
  713. package/teams/platform-and-engineering.yaml +23 -0
  714. package/teams/strategy-and-growth.yaml +29 -0
  715. package/workflows/continuous/backlog-executor.yaml +141 -0
  716. package/workflows/continuous/inbound-monitor.yaml +168 -0
  717. package/workflows/daily/applicant-triage.yaml +197 -0
  718. package/workflows/daily/comms-triage.yaml +80 -0
  719. package/workflows/daily/evening-wrap.yaml +105 -0
  720. package/workflows/daily/morning-brief.yaml +164 -0
  721. package/workflows/daily/slack-followup-sweep.yaml +87 -0
  722. package/workflows/event-driven/README.md +50 -0
  723. package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
  724. package/workflows/event-driven/pr-review.yaml +107 -0
  725. package/workflows/monthly/board-readiness.yaml +76 -0
  726. package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
  727. package/workflows/session-protocol.md +171 -0
  728. package/workflows/weekly/engineering-health.yaml +154 -0
  729. package/workflows/weekly/hiring-review.yaml +169 -0
  730. package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
  731. package/workflows/weekly/strategic-memo.yaml +79 -0
@@ -0,0 +1,288 @@
1
+ #!/bin/bash
2
+ # comms-monitor.sh — Unified communications monitor (Slack + Gmail)
3
+ #
4
+ # 1. Drain Slack events from Railway (channels, @mentions, reactions)
5
+ # 2. Poll ALL DMs + threads via User Token API
6
+ # 3. Poll Gmail (Robin's inbox) via IMAP for new unread emails
7
+ # 4. Write all new items to state/inbox/ for processing
8
+ #
9
+ # Run every 10 seconds via cron/launchd
10
+ set -e
11
+
12
+ # Resolve the agent repo root: AGENT_ROOT env, else the parent of scripts/.
13
+ AGENT_ROOT="${AGENT_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
14
+ [ -f "$AGENT_ROOT/.env" ] && source "$AGENT_ROOT/.env"
15
+ export SLACK_USER_TOKEN
16
+ export GMAIL_APP_PASSWORD
17
+
18
+ INBOX_DIR="$AGENT_ROOT/state/inbox/slack"
19
+ GMAIL_INBOX_DIR="$AGENT_ROOT/state/inbox/gmail"
20
+ mkdir -p "$GMAIL_INBOX_DIR"
21
+ RAILWAY_URL="https://slack-events-server-production.up.railway.app/events"
22
+ # The agent's own Slack user id (self-echo filter) — from env, never hardcoded.
23
+ AGENT_SLACK_ID="${AGENT_SLACK_ID:-${SLACK_SELF_ID:-}}"
24
+ TRACKER_DIR="$AGENT_ROOT/state/slack-thread-tracker"
25
+ mkdir -p "$INBOX_DIR" "$TRACKER_DIR"
26
+
27
+ # ── Part 1: Drain Railway event buffer (channels, mentions, reactions) ──
28
+
29
+ RAILWAY_EVENTS=$(curl -s --max-time 5 "$RAILWAY_URL" 2>/dev/null || echo '{"count":0}')
30
+ RAILWAY_COUNT=$(echo "$RAILWAY_EVENTS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null)
31
+
32
+ if [ "$RAILWAY_COUNT" != "0" ] && [ -n "$RAILWAY_COUNT" ]; then
33
+ echo "$RAILWAY_EVENTS" | python3 -c "
34
+ import sys, json, os
35
+ data = json.load(sys.stdin)
36
+ inbox = '$INBOX_DIR'
37
+ for event in data.get('events', []):
38
+ ts = event.get('received_at', '').replace(':', '-').replace('.', '-')
39
+ etype = event.get('event_type', 'unknown')
40
+ filename = f'{ts}-{etype}.json'
41
+ filepath = os.path.join(inbox, filename)
42
+ with open(filepath, 'w') as f:
43
+ json.dump(event, f, indent=2)
44
+ print(f'[RAILWAY] {etype} -> {filename}')
45
+ "
46
+ fi
47
+
48
+ # ── Part 2 + 3: Scan ALL DMs (top-level + threads) from ALL users ──
49
+ # Discovers every open DM conversation, checks for new messages and unanswered threads
50
+
51
+ ACTIVE_THREADS_FILE="$AGENT_ROOT/state/slack-active-threads.json"
52
+
53
+ python3 -c "
54
+ import json, os, subprocess
55
+
56
+ token = os.environ.get('SLACK_USER_TOKEN', '')
57
+ agent_id = '$AGENT_SLACK_ID'
58
+ inbox = '$INBOX_DIR'
59
+ tracker = '$TRACKER_DIR'
60
+ active_threads_file = '$ACTIVE_THREADS_FILE'
61
+
62
+ if not token:
63
+ print('[ERROR] SLACK_USER_TOKEN not set')
64
+ exit(1)
65
+
66
+ def api(endpoint):
67
+ resp = subprocess.run(
68
+ ['curl', '-s', '-H', f'Authorization: Bearer {token}', endpoint],
69
+ capture_output=True, text=True
70
+ )
71
+ try:
72
+ return json.loads(resp.stdout)
73
+ except:
74
+ return {}
75
+
76
+ # Load active threads
77
+ active_threads = {} # channel -> set of thread_ts
78
+ if os.path.exists(active_threads_file):
79
+ try:
80
+ with open(active_threads_file) as f:
81
+ active_threads = json.load(f)
82
+ except:
83
+ active_threads = {}
84
+
85
+ # Step 1: Get all open DM conversations
86
+ dm_list = api('https://slack.com/api/conversations.list?types=im&limit=100')
87
+ if not dm_list.get('ok'):
88
+ print(f'[ERROR] conversations.list failed: {dm_list.get(\"error\")}')
89
+ exit(1)
90
+
91
+ dm_channels = [c for c in dm_list.get('channels', []) if not c.get('is_archived')]
92
+
93
+ for dm in dm_channels:
94
+ channel_id = dm['id']
95
+ other_user = dm.get('user', '')
96
+
97
+ # Skip DMs with ourselves
98
+ if other_user == agent_id:
99
+ continue
100
+
101
+ # Get recent messages in this DM
102
+ history = api(f'https://slack.com/api/conversations.history?channel={channel_id}&limit=20')
103
+ if not history.get('ok'):
104
+ continue
105
+
106
+ messages = history.get('messages', [])
107
+ channel_threads = active_threads.get(channel_id, [])
108
+
109
+ for msg in messages:
110
+ user = msg.get('user', '')
111
+ ts = msg.get('ts', '')
112
+
113
+ # ── Check top-level messages (new DMs from others) ──
114
+ if user != agent_id and ts:
115
+ tracker_file = os.path.join(tracker, ts)
116
+ if not os.path.exists(tracker_file):
117
+ record = {
118
+ 'received_at': ts,
119
+ 'event_type': 'dm',
120
+ 'slack_event': {
121
+ 'type': 'message',
122
+ 'user': user,
123
+ 'text': msg.get('text', ''),
124
+ 'ts': ts,
125
+ 'channel': channel_id,
126
+ },
127
+ 'source': 'dm_poll',
128
+ }
129
+ filepath = os.path.join(inbox, f'{ts}-dm.json')
130
+ with open(filepath, 'w') as f:
131
+ json.dump(record, f, indent=2)
132
+ with open(tracker_file, 'w') as f:
133
+ f.write(ts)
134
+ print(f'[DM] from {user} in {channel_id} -> {ts}-dm.json')
135
+
136
+ # ── Collect threads to check ──
137
+ if msg.get('reply_count', 0) > 0:
138
+ thread_ts = msg.get('ts', '')
139
+ if thread_ts and thread_ts not in channel_threads:
140
+ channel_threads.append(thread_ts)
141
+
142
+ # ── Check threads for unanswered replies ──
143
+ for thread_ts in channel_threads:
144
+ thread_data = api(f'https://slack.com/api/conversations.replies?channel={channel_id}&ts={thread_ts}&limit=50')
145
+ if not thread_data.get('ok'):
146
+ continue
147
+
148
+ thread_msgs = thread_data.get('messages', [])
149
+ if len(thread_msgs) < 2:
150
+ continue
151
+
152
+ # Load per-thread cursor (timestamp of last processed reply)
153
+ cursor_file = os.path.join(tracker, f'{channel_id}_thread_{thread_ts}_cursor')
154
+ cursor_ts = ''
155
+ if os.path.exists(cursor_file):
156
+ with open(cursor_file) as cf:
157
+ cursor_ts = cf.read().strip()
158
+
159
+ # Process ALL replies newer than cursor, skipping Robin's own messages
160
+ # Skip first message (index 0) as it's the thread parent, already handled as top-level
161
+ newest_processed_ts = cursor_ts
162
+ for reply in thread_msgs[1:]:
163
+ reply_ts = reply.get('ts', '')
164
+ reply_user = reply.get('user', '')
165
+
166
+ # Skip replies at or before the cursor
167
+ if cursor_ts and reply_ts <= cursor_ts:
168
+ continue
169
+
170
+ # Skip Robin's own replies
171
+ if reply_user == agent_id:
172
+ # Still advance cursor past Robin's own messages
173
+ if reply_ts > newest_processed_ts:
174
+ newest_processed_ts = reply_ts
175
+ continue
176
+
177
+ record = {
178
+ 'received_at': reply_ts,
179
+ 'event_type': 'thread_reply',
180
+ 'slack_event': {
181
+ 'type': 'message',
182
+ 'user': reply_user,
183
+ 'text': reply.get('text', ''),
184
+ 'ts': reply_ts,
185
+ 'thread_ts': thread_ts,
186
+ 'channel': channel_id,
187
+ },
188
+ 'source': 'dm_thread_scan',
189
+ }
190
+ filepath = os.path.join(inbox, f'{reply_ts}-thread_reply.json')
191
+ with open(filepath, 'w') as f:
192
+ json.dump(record, f, indent=2)
193
+ print(f'[DM-THREAD] reply from {reply_user} in {channel_id} thread {thread_ts} -> {reply_ts}-thread_reply.json')
194
+
195
+ if reply_ts > newest_processed_ts:
196
+ newest_processed_ts = reply_ts
197
+
198
+ # Update per-thread cursor to the newest reply we processed
199
+ if newest_processed_ts and newest_processed_ts != cursor_ts:
200
+ with open(cursor_file, 'w') as cf:
201
+ cf.write(newest_processed_ts)
202
+
203
+ # Update active threads for this channel (keep last 50)
204
+ active_threads[channel_id] = sorted(set(channel_threads), reverse=True)[:50]
205
+
206
+ # Persist active threads
207
+ with open(active_threads_file, 'w') as f:
208
+ json.dump(active_threads, f)
209
+ "
210
+
211
+ # ── Part 4: Poll Gmail via IMAP for unread emails ──
212
+
213
+ python3 -c "
214
+ import imaplib, email, json, os, hashlib
215
+ from email.header import decode_header
216
+
217
+ gmail_user = os.environ.get('GMAIL_USER', '')
218
+ gmail_pass = os.environ.get('GMAIL_APP_PASSWORD', '')
219
+ inbox_dir = '$GMAIL_INBOX_DIR'
220
+ tracker_dir = '$TRACKER_DIR'
221
+
222
+ if not gmail_pass:
223
+ exit(0)
224
+
225
+ try:
226
+ mail = imaplib.IMAP4_SSL('imap.gmail.com')
227
+ mail.login(gmail_user, gmail_pass)
228
+ mail.select('INBOX')
229
+
230
+ status, data = mail.search(None, 'UNSEEN')
231
+ if status != 'OK' or not data[0]:
232
+ mail.logout()
233
+ exit(0)
234
+
235
+ msg_ids = data[0].split()
236
+
237
+ for mid in msg_ids[-10:]: # Last 10 unread max per cycle
238
+ status, msg_data = mail.fetch(mid, '(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE MESSAGE-ID)])')
239
+ if status != 'OK':
240
+ continue
241
+
242
+ raw = msg_data[0][1]
243
+ msg = email.message_from_bytes(raw)
244
+
245
+ from_addr = msg.get('From', '')
246
+ subject = msg.get('Subject', '')
247
+ date = msg.get('Date', '')
248
+ message_id = msg.get('Message-ID', '')
249
+
250
+ # Decode subject if needed
251
+ if subject:
252
+ decoded = decode_header(subject)
253
+ subject = ''.join(
254
+ part.decode(enc or 'utf-8') if isinstance(part, bytes) else part
255
+ for part, enc in decoded
256
+ )
257
+
258
+ # Dedup via message-id hash
259
+ mid_hash = hashlib.md5((message_id or f'{from_addr}{subject}{date}').encode()).hexdigest()[:16]
260
+ tracker_file = os.path.join(tracker_dir, f'gmail-{mid_hash}')
261
+ if os.path.exists(tracker_file):
262
+ continue
263
+
264
+ record = {
265
+ 'received_at': date,
266
+ 'event_type': 'email',
267
+ 'email': {
268
+ 'from': from_addr,
269
+ 'subject': subject,
270
+ 'date': date,
271
+ 'message_id': message_id,
272
+ },
273
+ 'source': 'gmail_imap',
274
+ }
275
+
276
+ filepath = os.path.join(inbox_dir, f'{mid_hash}-email.json')
277
+ with open(filepath, 'w') as f:
278
+ json.dump(record, f, indent=2)
279
+
280
+ with open(tracker_file, 'w') as f:
281
+ f.write(mid_hash)
282
+
283
+ print(f'[GMAIL] {from_addr} — {subject[:50]}')
284
+
285
+ mail.logout()
286
+ except Exception as e:
287
+ print(f'[GMAIL-ERROR] {e}')
288
+ "
@@ -0,0 +1,201 @@
1
+ #!/bin/bash
2
+ # configure-whatsapp-sandbox.sh — Configure Twilio WhatsApp Sandbox webhook URLs
3
+ #
4
+ # This script queries the current Cloudflare tunnel URL and configures the Twilio
5
+ # WhatsApp Sandbox to route inbound messages to Robin's local whatsapp-handler.mjs.
6
+ #
7
+ # Usage:
8
+ # ./scripts/configure-whatsapp-sandbox.sh # Auto-detect tunnel URL
9
+ # ./scripts/configure-whatsapp-sandbox.sh --url <tunnel-url> # Use specific URL
10
+ # ./scripts/configure-whatsapp-sandbox.sh --serverless # Use Twilio Serverless URL
11
+ # ./scripts/configure-whatsapp-sandbox.sh --status # Show current sandbox config
12
+ #
13
+ # Environment variables (from .env):
14
+ # TWILIO_ACCOUNT_SID — Twilio account SID
15
+ # TWILIO_AUTH_TOKEN — Twilio auth token
16
+ set -e
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
19
+ SOPHIE_AI="$(dirname "$SCRIPT_DIR")"
20
+
21
+ if [ -f "$SOPHIE_AI/.env" ]; then
22
+ source "$SOPHIE_AI/.env"
23
+ fi
24
+
25
+ # Twilio WhatsApp Sandbox API endpoint
26
+ # The sandbox config is managed via the Twilio REST API under the account's messaging sandbox
27
+ SANDBOX_API_URL="https://api.twilio.com/2010-04-01/Accounts/${TWILIO_ACCOUNT_SID}/Sandbox.json"
28
+
29
+ # Default URLs
30
+ SERVERLESS_URL="https://robin-webhooks-2805-production.twil.io/whatsapp-reply"
31
+ TUNNEL_URL=""
32
+ MODE=""
33
+
34
+ # Parse arguments
35
+ while [[ $# -gt 0 ]]; do
36
+ case "$1" in
37
+ --url)
38
+ TUNNEL_URL="$2"
39
+ MODE="tunnel"
40
+ shift 2
41
+ ;;
42
+ --serverless)
43
+ MODE="serverless"
44
+ shift
45
+ ;;
46
+ --status)
47
+ MODE="status"
48
+ shift
49
+ ;;
50
+ -h|--help)
51
+ echo "Usage: configure-whatsapp-sandbox.sh [OPTIONS]"
52
+ echo ""
53
+ echo "Options:"
54
+ echo " --url <url> Use this URL as the webhook base (e.g., https://xyz.trycloudflare.com)"
55
+ echo " --serverless Use Twilio Serverless URL (stable, no tunnel dependency)"
56
+ echo " --status Show current sandbox configuration"
57
+ echo " -h Show this help"
58
+ echo ""
59
+ echo "If no option given, auto-detects Cloudflare tunnel URL."
60
+ exit 0
61
+ ;;
62
+ *)
63
+ echo "ERROR: Unknown argument: $1" >&2
64
+ exit 1
65
+ ;;
66
+ esac
67
+ done
68
+
69
+ # Validate credentials
70
+ if [ -z "$TWILIO_ACCOUNT_SID" ]; then
71
+ echo "ERROR: TWILIO_ACCOUNT_SID not set" >&2
72
+ exit 1
73
+ fi
74
+
75
+ if [ -z "$TWILIO_AUTH_TOKEN" ]; then
76
+ echo "ERROR: TWILIO_AUTH_TOKEN not set" >&2
77
+ exit 1
78
+ fi
79
+
80
+ # Show current sandbox config
81
+ if [ "$MODE" = "status" ]; then
82
+ echo "Fetching current WhatsApp Sandbox configuration..."
83
+ echo ""
84
+
85
+ # The sandbox config is at a different endpoint for WhatsApp
86
+ # Twilio WhatsApp sandbox settings are at:
87
+ # https://api.twilio.com/2010-04-01/Accounts/{SID}/Sandbox.json
88
+ RESPONSE=$(curl -s \
89
+ -u "${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}" \
90
+ "https://api.twilio.com/2010-04-01/Accounts/${TWILIO_ACCOUNT_SID}/Sandbox.json" 2>/dev/null || echo "")
91
+
92
+ if [ -n "$RESPONSE" ]; then
93
+ echo "Sandbox API Response:"
94
+ echo "$RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE"
95
+ else
96
+ echo "No sandbox config returned. The sandbox may need to be activated first in Twilio Console."
97
+ echo ""
98
+ echo "To activate:"
99
+ echo " 1. Go to https://console.twilio.com/us1/develop/sms/try-it-out/whatsapp-learn"
100
+ echo " 2. Follow the on-screen instructions"
101
+ echo " 3. Note the sandbox join keyword"
102
+ fi
103
+ exit 0
104
+ fi
105
+
106
+ # Determine webhook URL
107
+ if [ "$MODE" = "serverless" ]; then
108
+ WEBHOOK_URL="$SERVERLESS_URL"
109
+ STATUS_URL=""
110
+ echo "Using Twilio Serverless URL: $WEBHOOK_URL"
111
+ elif [ -n "$TUNNEL_URL" ]; then
112
+ # Strip trailing slash
113
+ TUNNEL_URL="${TUNNEL_URL%/}"
114
+ WEBHOOK_URL="${TUNNEL_URL}/whatsapp"
115
+ STATUS_URL="${TUNNEL_URL}/whatsapp/status"
116
+ echo "Using provided tunnel URL: $TUNNEL_URL"
117
+ else
118
+ # Auto-detect Cloudflare tunnel URL
119
+ echo "Auto-detecting Cloudflare tunnel URL..."
120
+
121
+ # Try to find the tunnel URL from cloudflared process
122
+ # Quick tunnels print their URL to stderr on startup
123
+ TUNNEL_LOG=$(ps aux | grep cloudflared | grep -v grep | head -1)
124
+
125
+ if [ -z "$TUNNEL_LOG" ]; then
126
+ echo "ERROR: No Cloudflare tunnel detected. Start one with:" >&2
127
+ echo " cloudflared tunnel --url http://localhost:3002" >&2
128
+ echo "" >&2
129
+ echo "Or use --serverless flag to use the Twilio Serverless URL instead." >&2
130
+ exit 1
131
+ fi
132
+
133
+ echo "Cloudflare tunnel process detected."
134
+ echo ""
135
+ echo "NOTE: Quick tunnels (trycloudflare.com) get a random URL on each restart."
136
+ echo "The tunnel URL cannot be reliably auto-detected from the process."
137
+ echo ""
138
+ echo "Please provide the tunnel URL manually:"
139
+ echo " ./scripts/configure-whatsapp-sandbox.sh --url https://xyz.trycloudflare.com"
140
+ echo ""
141
+ echo "Or check the Cloudflare tunnel output for the URL."
142
+ echo "Or use --serverless for a stable URL via Twilio Serverless."
143
+ exit 1
144
+ fi
145
+
146
+ echo ""
147
+ echo "Configuring WhatsApp Sandbox webhooks..."
148
+ echo " Inbound webhook: $WEBHOOK_URL"
149
+ if [ -n "$STATUS_URL" ]; then
150
+ echo " Status callback: $STATUS_URL"
151
+ fi
152
+
153
+ # Update sandbox configuration
154
+ # Note: Twilio's sandbox webhook configuration is typically done via the Console.
155
+ # The REST API approach depends on the sandbox type.
156
+ # For WhatsApp specifically, webhook config is in the Twilio Console under:
157
+ # Messaging > Try it Out > Send a WhatsApp message > Sandbox Settings
158
+ #
159
+ # However, we can try the REST API:
160
+
161
+ echo ""
162
+ echo "========================================"
163
+ echo " MANUAL STEPS REQUIRED"
164
+ echo "========================================"
165
+ echo ""
166
+ echo "Twilio WhatsApp Sandbox webhook URLs must be configured in the Console:"
167
+ echo ""
168
+ echo "1. Go to: https://console.twilio.com/us1/develop/sms/try-it-out/whatsapp-learn"
169
+ echo "2. Scroll to 'Sandbox Configuration' section"
170
+ echo "3. Set these values:"
171
+ echo ""
172
+ echo " WHEN A MESSAGE COMES IN:"
173
+ echo " URL: $WEBHOOK_URL"
174
+ echo " Method: HTTP POST"
175
+ echo ""
176
+ if [ -n "$STATUS_URL" ]; then
177
+ echo " STATUS CALLBACK URL:"
178
+ echo " URL: $STATUS_URL"
179
+ echo " Method: HTTP POST"
180
+ echo ""
181
+ fi
182
+ echo "4. Click Save"
183
+ echo ""
184
+ echo "5. Note the JOIN CODE shown on the page"
185
+ echo " Users must send 'join <code>' to +1 415 523 8886 on WhatsApp"
186
+ echo ""
187
+ echo "========================================"
188
+ echo ""
189
+ echo "Once configured, test with:"
190
+ echo " ./scripts/send-whatsapp.sh --to \"+971585291799\" --body \"WhatsApp sandbox test from Robin\""
191
+
192
+ # Log this action
193
+ LOG_DIR="$SOPHIE_AI/logs/audit"
194
+ mkdir -p "$LOG_DIR"
195
+ LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d)-actions.jsonl"
196
+
197
+ LOG_ENTRY=$(cat <<LOGEOF
198
+ {"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)","agent":"configure-whatsapp-sandbox","action":"sandbox_config_instructions_generated","webhook_url":"${WEBHOOK_URL}","status_url":"${STATUS_URL:-none}","mode":"${MODE}"}
199
+ LOGEOF
200
+ )
201
+ echo "$LOG_ENTRY" >> "$LOG_FILE"
@@ -0,0 +1,91 @@
1
+ #!/bin/bash
2
+ # Continuous Monitoring Launcher
3
+ # Starts the perpetual monitoring loop for all communication channels.
4
+ # Designed to be run as a launchd daemon.
5
+ #
6
+ # Usage: ./scripts/continuous-monitor.sh [--channel slack|gmail|whatsapp|all]
7
+
8
+ set -e
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ AGENT_REPO_DIR="${AGENT_DIR:-$(dirname "$SCRIPT_DIR")}"
12
+ # Load agent identity for the LLM prompt
13
+ if [ -f "$AGENT_REPO_DIR/config/agent.env" ]; then
14
+ # shellcheck disable=SC1091
15
+ source "$AGENT_REPO_DIR/config/agent.env"
16
+ fi
17
+ CHANNEL="${1:---channel all}"
18
+ CHANNEL="${CHANNEL#--channel }"
19
+ LOG_DIR="$AGENT_REPO_DIR/logs/monitor"
20
+ QUEUE_DIR="$AGENT_REPO_DIR/outputs/inbound/queue"
21
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
22
+
23
+ mkdir -p "$LOG_DIR" "$QUEUE_DIR"
24
+
25
+ # Check emergency stop
26
+ check_stop() {
27
+ if [ -f "$AGENT_REPO_DIR/.emergency-stop" ]; then
28
+ echo "[$TIMESTAMP] Monitor halted: emergency stop active" >> "$LOG_DIR/monitor.log"
29
+ exit 0
30
+ fi
31
+ }
32
+
33
+ # Monitor loop for a specific channel
34
+ monitor_channel() {
35
+ local channel=$1
36
+ local interval=$2
37
+ local log_file="$LOG_DIR/$channel.log"
38
+
39
+ echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] Starting $channel monitor (interval: ${interval}s)" >> "$log_file"
40
+
41
+ while true; do
42
+ check_stop
43
+
44
+ echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] Polling $channel..." >> "$log_file"
45
+
46
+ # Use Claude to observe the channel via desktop control
47
+ # This invokes the appropriate operator agent in observe mode
48
+ claude -p "You are the $channel monitoring agent for ${AGENT_FULL_NAME:-the agent} at ${COMPANY_NAME:-the company}.
49
+ OBSERVE ONLY — do not send any messages.
50
+ Check for new messages since the last check.
51
+ If there are new messages, write a YAML summary to: $QUEUE_DIR/${channel}-$(date +%s).yaml
52
+ Include: sender, timestamp, channel/thread, subject/preview, urgency_hint.
53
+ If no new messages, do nothing.
54
+ Check the desktop $channel app using screenshot observation.
55
+ Working directory: $AGENT_REPO_DIR" \
56
+ --output-format text \
57
+ >> "$log_file" 2>&1 || true
58
+
59
+ sleep "$interval"
60
+ done
61
+ }
62
+
63
+ case "$CHANNEL" in
64
+ slack)
65
+ monitor_channel "slack" 60
66
+ ;;
67
+ gmail)
68
+ monitor_channel "gmail" 120
69
+ ;;
70
+ whatsapp)
71
+ monitor_channel "whatsapp" 120
72
+ ;;
73
+ all)
74
+ # Launch all monitors in parallel
75
+ monitor_channel "slack" 60 &
76
+ SLACK_PID=$!
77
+ monitor_channel "gmail" 120 &
78
+ GMAIL_PID=$!
79
+ monitor_channel "whatsapp" 120 &
80
+ WHATSAPP_PID=$!
81
+
82
+ echo "[$TIMESTAMP] All monitors started: slack=$SLACK_PID gmail=$GMAIL_PID whatsapp=$WHATSAPP_PID" >> "$LOG_DIR/monitor.log"
83
+
84
+ # Wait for any to exit (shouldn't happen unless error or stop)
85
+ wait
86
+ ;;
87
+ *)
88
+ echo "Unknown channel: $CHANNEL. Use: slack, gmail, whatsapp, or all"
89
+ exit 1
90
+ ;;
91
+ esac