@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,248 @@
1
+ #!/bin/bash
2
+ # restore-from-backup.sh — executable dead-Mac-mini restore runbook (G16).
3
+ #
4
+ # Rebuilds a Maestro agent's durable state onto replacement hardware from a single
5
+ # backup archive produced by scripts/maintenance/backup-to-cloud.sh. This is the
6
+ # runbook in docs/runbooks/backup-restore.md made EXECUTABLE: instead of a human
7
+ # hand-copying state/, logs/, and config/ from a tarball and trying to remember
8
+ # the re-pair + secret-recovery steps, this script does the mechanical parts and
9
+ # PRINTS the irreducibly-manual ones (secret recovery, Cohort re-pair approval).
10
+ #
11
+ # What it does:
12
+ # 1. Validates the archive (and client-side decrypts it if it is encrypted —
13
+ # .gpg / .age — using a passphrase from $RESTORE_PASSPHRASE).
14
+ # 2. Unpacks into a scratch dir and shows what the archive contains.
15
+ # 3. Restores config/, state/, and logs/ into the agent repo (existing dirs are
16
+ # backed up to <dir>.pre-restore.<ts> first, so a re-run never destroys what
17
+ # it already placed — idempotent).
18
+ # 4. Re-pairs with Cohort (prints the operator-station approval command; mints
19
+ # a FRESH device token rather than restoring the dead one — never restore an
20
+ # identity credential).
21
+ # 5. Prints the remaining manual steps: secret recovery via `maestro secrets
22
+ # sync`, .env lockdown, doctor, resume.
23
+ #
24
+ # Idempotent: safe to re-run. --dry-run lists every step WITHOUT touching the
25
+ # filesystem, decrypting, or pairing. bash -n clean; shellcheck-friendly.
26
+ #
27
+ # Usage:
28
+ # scripts/restore-from-backup.sh --archive <path> [--agent-dir <dir>] [--dry-run]
29
+ # scripts/restore-from-backup.sh --archive backup.tar.gz.gpg --dry-run
30
+ #
31
+ # Env:
32
+ # RESTORE_PASSPHRASE passphrase for an encrypted (.gpg/.age) archive
33
+ # AGENT_DIR override the target agent repo (default: repo root)
34
+ # COHORT_AGENT_ID agent id used in the printed re-pair command
35
+
36
+ set -euo pipefail
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Defaults + arg parsing
40
+ # ---------------------------------------------------------------------------
41
+
42
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
43
+ DEFAULT_AGENT_DIR="$(dirname "$SCRIPT_DIR")"
44
+
45
+ ARCHIVE=""
46
+ AGENT_DIR="${AGENT_DIR:-$DEFAULT_AGENT_DIR}"
47
+ DRY_RUN=0
48
+ TS="$(date -u +"%Y%m%dT%H%M%SZ")"
49
+
50
+ usage() {
51
+ cat <<'USAGE'
52
+ restore-from-backup.sh — dead-Mac-mini restore from a backup archive.
53
+
54
+ --archive <path> backup archive to restore (.tar.gz, .tgz, .tar.gz.gpg, .age)
55
+ --agent-dir <dir> target agent repo (default: this repo's root)
56
+ --dry-run print the plan; touch nothing, decrypt nothing, pair nothing
57
+ -h, --help this help
58
+
59
+ Encrypted archives (.gpg/.age) need RESTORE_PASSPHRASE in the environment.
60
+ USAGE
61
+ }
62
+
63
+ while [ "$#" -gt 0 ]; do
64
+ case "$1" in
65
+ --archive)
66
+ ARCHIVE="${2:-}"
67
+ shift 2
68
+ ;;
69
+ --agent-dir)
70
+ AGENT_DIR="${2:-}"
71
+ shift 2
72
+ ;;
73
+ --dry-run)
74
+ DRY_RUN=1
75
+ shift
76
+ ;;
77
+ -h|--help)
78
+ usage
79
+ exit 0
80
+ ;;
81
+ *)
82
+ echo "ERROR: unknown argument: $1" >&2
83
+ usage >&2
84
+ exit 2
85
+ ;;
86
+ esac
87
+ done
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Output helpers
91
+ # ---------------------------------------------------------------------------
92
+
93
+ step() { echo "[restore] $1"; }
94
+ note() { echo " $1"; }
95
+ run_or_echo() {
96
+ # In dry-run, print the command prefixed with DRY-RUN and do nothing.
97
+ # Otherwise execute it.
98
+ if [ "$DRY_RUN" -eq 1 ]; then
99
+ echo " DRY-RUN: $*"
100
+ else
101
+ "$@"
102
+ fi
103
+ }
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Validate inputs
107
+ # ---------------------------------------------------------------------------
108
+
109
+ if [ -z "$ARCHIVE" ]; then
110
+ echo "ERROR: --archive is required." >&2
111
+ usage >&2
112
+ exit 2
113
+ fi
114
+
115
+ # In a real run the archive must exist; in --dry-run we tolerate a missing path
116
+ # so the operator can rehearse the plan before the archive is downloaded.
117
+ if [ ! -f "$ARCHIVE" ] && [ "$DRY_RUN" -eq 0 ]; then
118
+ echo "ERROR: archive not found: $ARCHIVE" >&2
119
+ exit 1
120
+ fi
121
+
122
+ # Determine whether the archive is encrypted from its extension.
123
+ ENCRYPTED=0
124
+ case "$ARCHIVE" in
125
+ *.gpg|*.age) ENCRYPTED=1 ;;
126
+ *) ENCRYPTED=0 ;;
127
+ esac
128
+
129
+ step "Restore plan for agent dir: $AGENT_DIR"
130
+ note "archive: $ARCHIVE"
131
+ note "encrypted: $([ "$ENCRYPTED" -eq 1 ] && echo yes || echo no)"
132
+ note "mode: $([ "$DRY_RUN" -eq 1 ] && echo DRY-RUN || echo APPLY)"
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # 1. Decrypt (if encrypted)
136
+ # ---------------------------------------------------------------------------
137
+
138
+ SCRATCH=""
139
+ TARBALL="$ARCHIVE"
140
+
141
+ step "Step 1/5: validate + decrypt archive"
142
+ if [ "$ENCRYPTED" -eq 1 ]; then
143
+ note "archive is encrypted — client-side decrypt required"
144
+ if [ "$DRY_RUN" -eq 0 ] && [ -z "${RESTORE_PASSPHRASE:-}" ]; then
145
+ echo "ERROR: encrypted archive but RESTORE_PASSPHRASE is unset." >&2
146
+ exit 1
147
+ fi
148
+ SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/maestro-restore.${TS}.XXXXXX")"
149
+ TARBALL="$SCRATCH/decrypted.tar.gz"
150
+ case "$ARCHIVE" in
151
+ *.gpg)
152
+ run_or_echo gpg --batch --yes --passphrase "${RESTORE_PASSPHRASE:-}" \
153
+ --decrypt --output "$TARBALL" "$ARCHIVE"
154
+ ;;
155
+ *.age)
156
+ run_or_echo age --decrypt --output "$TARBALL" "$ARCHIVE"
157
+ ;;
158
+ esac
159
+ else
160
+ note "archive is plaintext — no decrypt step"
161
+ fi
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # 2. Unpack into scratch + inspect
165
+ # ---------------------------------------------------------------------------
166
+
167
+ step "Step 2/5: unpack archive into a scratch dir and inspect"
168
+ if [ -z "$SCRATCH" ]; then
169
+ SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/maestro-restore.${TS}.XXXXXX" 2>/dev/null || echo "/tmp/maestro-restore.${TS}")"
170
+ fi
171
+ UNPACK="$SCRATCH/unpacked"
172
+ run_or_echo mkdir -p "$UNPACK"
173
+ run_or_echo tar -xzf "$TARBALL" -C "$UNPACK"
174
+ note "inspect before overwriting: ls $UNPACK"
175
+ if [ "$DRY_RUN" -eq 0 ] && [ -d "$UNPACK" ]; then
176
+ ls "$UNPACK" | sed 's/^/ contains: /' || true
177
+ fi
178
+
179
+ # ---------------------------------------------------------------------------
180
+ # 3. Restore config/, state/, logs/ — idempotent (back up existing first)
181
+ # ---------------------------------------------------------------------------
182
+
183
+ step "Step 3/5: restore config/, state/, logs/ into $AGENT_DIR"
184
+ for dir in config state logs; do
185
+ SRC="$UNPACK/$dir"
186
+ DEST="$AGENT_DIR/$dir"
187
+ if [ "$DRY_RUN" -eq 1 ]; then
188
+ note "DRY-RUN: would restore $dir/ (existing $DEST → $DEST.pre-restore.$TS, then copy from archive)"
189
+ continue
190
+ fi
191
+ if [ ! -d "$SRC" ]; then
192
+ note "skip $dir/ — not present in archive"
193
+ continue
194
+ fi
195
+ # Idempotent: move any existing dir aside (timestamped) so a re-run never
196
+ # clobbers a prior restore and the operator can diff/rollback.
197
+ if [ -e "$DEST" ]; then
198
+ run_or_echo mv "$DEST" "$DEST.pre-restore.$TS"
199
+ note "preserved existing $dir/ → $dir.pre-restore.$TS"
200
+ fi
201
+ run_or_echo mkdir -p "$AGENT_DIR"
202
+ run_or_echo cp -R "$SRC" "$DEST"
203
+ note "restored $dir/"
204
+ done
205
+
206
+ # Lock down secret-bearing files if they came back in the archive.
207
+ if [ "$DRY_RUN" -eq 1 ]; then
208
+ note "DRY-RUN: would chmod 600 $AGENT_DIR/.env if present"
209
+ elif [ -f "$AGENT_DIR/.env" ]; then
210
+ run_or_echo chmod 600 "$AGENT_DIR/.env"
211
+ note "locked down .env (0600)"
212
+ fi
213
+
214
+ # ---------------------------------------------------------------------------
215
+ # 4. Re-pair with Cohort (fresh device token — never restore the old one)
216
+ # ---------------------------------------------------------------------------
217
+
218
+ step "Step 4/5: re-establish org-server (Cohort) identity"
219
+ AGENT_ID="${COHORT_AGENT_ID:-<agentId>}"
220
+ note "Do NOT restore the dead device token. Mint a fresh one on the operator station:"
221
+ note " cohort --db \"\$COHORT_DB\" pair approve $AGENT_ID --scopes <scopes>"
222
+ note "Then write the printed token into $AGENT_DIR/.env and chmod 600 .env."
223
+ if [ "$DRY_RUN" -eq 1 ]; then
224
+ note "DRY-RUN: pairing is a manual operator-station action; nothing executed here."
225
+ fi
226
+
227
+ # ---------------------------------------------------------------------------
228
+ # 5. Remaining manual steps
229
+ # ---------------------------------------------------------------------------
230
+
231
+ step "Step 5/5: remaining MANUAL steps (this script cannot do these for you)"
232
+ note "1. Recover secrets: maestro secrets sync"
233
+ note " (re-issue anything unrecoverable at its source; treat theft as full rotation)"
234
+ note "2. Confirm .env: chmod 600 $AGENT_DIR/.env && stat -f '%Sp' $AGENT_DIR/.env (expect -rw-------)"
235
+ note "3. Verify posture: maestro doctor (green before going live; backup freshness should clear)"
236
+ note "4. Resume ops: $AGENT_DIR/scripts/resume-operations.sh"
237
+ note "5. Re-arm backup: maestro init backup-replication --apply (a rebuilt box with no backup is the next dead box)"
238
+
239
+ # ---------------------------------------------------------------------------
240
+ # Cleanup (real runs only; leave scratch for inspection in dry-run is moot —
241
+ # nothing was created)
242
+ # ---------------------------------------------------------------------------
243
+
244
+ if [ "$DRY_RUN" -eq 0 ] && [ -n "$SCRATCH" ] && [ -d "$SCRATCH" ]; then
245
+ rm -rf "$SCRATCH" 2>/dev/null || true
246
+ fi
247
+
248
+ step "Restore $([ "$DRY_RUN" -eq 1 ] && echo "plan complete (dry-run — nothing changed)" || echo "complete — finish the manual steps above")"
@@ -0,0 +1,178 @@
1
+ /**
2
+ * restore-from-backup.test.mjs — coverage for scripts/restore-from-backup.sh.
3
+ *
4
+ * Drives the script with --dry-run over a tmp fixture (a real .tar.gz of a fake
5
+ * config/state/logs tree) and asserts:
6
+ * - `bash -n` is clean (no syntax errors)
7
+ * - --dry-run lists every restore step and the manual follow-ups
8
+ * - --dry-run touches NOTHING on disk (no dirs moved/created in the agent dir)
9
+ * - the required-arg + unknown-arg + help paths exit with the right codes
10
+ * - an encrypted (.gpg) archive surfaces the decrypt step in the plan
11
+ *
12
+ * No live decrypt / pairing happens — --dry-run prints the plan only.
13
+ */
14
+
15
+ import { test } from "node:test";
16
+ import assert from "node:assert/strict";
17
+ import {
18
+ mkdtempSync,
19
+ mkdirSync,
20
+ writeFileSync,
21
+ readdirSync,
22
+ rmSync,
23
+ existsSync,
24
+ } from "node:fs";
25
+ import { tmpdir } from "node:os";
26
+ import { join, resolve, dirname } from "node:path";
27
+ import { fileURLToPath } from "node:url";
28
+ import { spawnSync } from "node:child_process";
29
+
30
+ const __dirname = dirname(fileURLToPath(import.meta.url));
31
+ const SCRIPT = resolve(__dirname, "restore-from-backup.sh");
32
+
33
+ /** Build a tmp dir + a real .tar.gz containing config/, state/, logs/. */
34
+ function makeArchive() {
35
+ const root = mkdtempSync(join(tmpdir(), "restore-test-"));
36
+ const src = join(root, "src");
37
+ for (const d of ["config", "state", "logs"]) {
38
+ mkdirSync(join(src, d), { recursive: true });
39
+ writeFileSync(join(src, d, "marker.txt"), `restored-${d}\n`);
40
+ }
41
+ const archive = join(root, "backup.tar.gz");
42
+ const tar = spawnSync("tar", ["-czf", archive, "-C", src, "config", "state", "logs"], {
43
+ encoding: "utf-8",
44
+ });
45
+ assert.equal(tar.status, 0, `tar failed: ${tar.stderr}`);
46
+ return { root, archive };
47
+ }
48
+
49
+ function run(args, opts = {}) {
50
+ return spawnSync("bash", [SCRIPT, ...args], { encoding: "utf-8", ...opts });
51
+ }
52
+
53
+ function cleanup(p) {
54
+ rmSync(p, { recursive: true, force: true });
55
+ }
56
+
57
+ test("bash -n: the script has no syntax errors", () => {
58
+ const r = spawnSync("bash", ["-n", SCRIPT], { encoding: "utf-8" });
59
+ assert.equal(r.status, 0, `bash -n failed: ${r.stderr}`);
60
+ });
61
+
62
+ test("--dry-run lists every restore step and the manual follow-ups", () => {
63
+ const { root, archive } = makeArchive();
64
+ const agentDir = join(root, "agent");
65
+ mkdirSync(agentDir, { recursive: true });
66
+ try {
67
+ const r = run(["--archive", archive, "--agent-dir", agentDir, "--dry-run"]);
68
+ assert.equal(r.status, 0, `nonzero exit: ${r.stderr}`);
69
+ const out = r.stdout;
70
+
71
+ // The five numbered steps are all present.
72
+ assert.match(out, /Step 1\/5: validate \+ decrypt/);
73
+ assert.match(out, /Step 2\/5: unpack/);
74
+ assert.match(out, /Step 3\/5: restore config\/, state\/, logs\//);
75
+ assert.match(out, /Step 4\/5: re-establish org-server \(Cohort\) identity/);
76
+ assert.match(out, /Step 5\/5: remaining MANUAL steps/);
77
+
78
+ // Each of the three dirs is named in the plan.
79
+ assert.match(out, /would restore config\//);
80
+ assert.match(out, /would restore state\//);
81
+ assert.match(out, /would restore logs\//);
82
+
83
+ // The irreducibly-manual follow-ups: secret recovery + cohort re-pair.
84
+ assert.match(out, /maestro secrets sync/);
85
+ assert.match(out, /cohort .* pair approve/);
86
+ assert.match(out, /resume-operations\.sh/);
87
+ assert.match(out, /init backup-replication/);
88
+
89
+ // Dry-run banner.
90
+ assert.match(out, /dry-run — nothing changed/);
91
+ } finally {
92
+ cleanup(root);
93
+ }
94
+ });
95
+
96
+ test("--dry-run touches NOTHING in the agent dir", () => {
97
+ const { root, archive } = makeArchive();
98
+ const agentDir = join(root, "agent");
99
+ mkdirSync(agentDir, { recursive: true });
100
+ // Seed an existing state/ to prove dry-run does not move it aside.
101
+ mkdirSync(join(agentDir, "state"), { recursive: true });
102
+ writeFileSync(join(agentDir, "state", "pre-existing.txt"), "keep me\n");
103
+ const before = readdirSync(agentDir).sort();
104
+ try {
105
+ const r = run(["--archive", archive, "--agent-dir", agentDir, "--dry-run"]);
106
+ assert.equal(r.status, 0);
107
+ const after = readdirSync(agentDir).sort();
108
+ assert.deepEqual(after, before, "dry-run must not create/move any dir");
109
+ // No .pre-restore.* backup was created.
110
+ assert.ok(
111
+ !after.some((n) => n.includes("pre-restore")),
112
+ "dry-run must not produce a pre-restore backup"
113
+ );
114
+ assert.ok(existsSync(join(agentDir, "state", "pre-existing.txt")), "pre-existing state untouched");
115
+ } finally {
116
+ cleanup(root);
117
+ }
118
+ });
119
+
120
+ test("encrypted (.gpg) archive surfaces the decrypt step in the dry-run plan", () => {
121
+ const root = mkdtempSync(join(tmpdir(), "restore-enc-"));
122
+ const agentDir = join(root, "agent");
123
+ mkdirSync(agentDir, { recursive: true });
124
+ try {
125
+ // A .gpg path need not exist for a dry-run rehearsal.
126
+ const r = run(["--archive", join(root, "backup.tar.gz.gpg"), "--agent-dir", agentDir, "--dry-run"]);
127
+ assert.equal(r.status, 0, r.stderr);
128
+ assert.match(r.stdout, /encrypted: yes/);
129
+ assert.match(r.stdout, /client-side decrypt required/);
130
+ assert.match(r.stdout, /DRY-RUN: gpg .*--decrypt/);
131
+ } finally {
132
+ cleanup(root);
133
+ }
134
+ });
135
+
136
+ test("missing --archive exits 2 with usage", () => {
137
+ const r = run(["--dry-run"]);
138
+ assert.equal(r.status, 2);
139
+ assert.match(r.stderr, /--archive is required/);
140
+ });
141
+
142
+ test("unknown argument exits 2", () => {
143
+ const r = run(["--archive", "x", "--bogus"]);
144
+ assert.equal(r.status, 2);
145
+ assert.match(r.stderr, /unknown argument/);
146
+ });
147
+
148
+ test("--help exits 0 and prints usage", () => {
149
+ const r = run(["--help"]);
150
+ assert.equal(r.status, 0);
151
+ assert.match(r.stdout, /dead-Mac-mini restore/);
152
+ });
153
+
154
+ test("a real (non-dry) restore is idempotent: re-run preserves the prior restore", () => {
155
+ const { root, archive } = makeArchive();
156
+ const agentDir = join(root, "agent");
157
+ mkdirSync(agentDir, { recursive: true });
158
+ try {
159
+ // First restore.
160
+ const r1 = run(["--archive", archive, "--agent-dir", agentDir]);
161
+ assert.equal(r1.status, 0, `first restore failed: ${r1.stderr}`);
162
+ assert.ok(existsSync(join(agentDir, "state", "marker.txt")), "state restored");
163
+ assert.ok(existsSync(join(agentDir, "config", "marker.txt")), "config restored");
164
+ assert.ok(existsSync(join(agentDir, "logs", "marker.txt")), "logs restored");
165
+
166
+ // Second restore must not fail and must preserve the prior dirs aside.
167
+ const r2 = run(["--archive", archive, "--agent-dir", agentDir]);
168
+ assert.equal(r2.status, 0, `second restore failed: ${r2.stderr}`);
169
+ const after = readdirSync(agentDir);
170
+ assert.ok(
171
+ after.some((n) => n.startsWith("state.pre-restore.")),
172
+ "re-run preserved the prior state/ as a timestamped backup"
173
+ );
174
+ assert.ok(existsSync(join(agentDir, "state", "marker.txt")), "state restored again");
175
+ } finally {
176
+ cleanup(root);
177
+ }
178
+ });
@@ -0,0 +1,80 @@
1
+ #!/bin/bash
2
+ # Resume Operations — Restarts Maestro agent after an emergency stop.
3
+ # Usage: ./scripts/resume-operations.sh
4
+ #
5
+ # Reverses emergency-stop.sh:
6
+ # 1. Verifies health check passes.
7
+ # 2. Removes the .emergency-stop flag.
8
+ # 3. Reloads every installed `ai.maestro.<agent>-*` (and legacy
9
+ # `ai.adaptic.<agent>-*`) launchd job.
10
+ #
11
+ # Agent first-name slug resolved from config/agent.json (SOT).
12
+
13
+ set -e
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ AGENT_DIR="$(dirname "$SCRIPT_DIR")"
17
+ LOG_FILE="$AGENT_DIR/logs/operations.log"
18
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
19
+ mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
20
+
21
+ # Resolve agent first-name slug.
22
+ AGENT_FIRST=""
23
+ if [ -f "$AGENT_DIR/config/agent.json" ]; then
24
+ if command -v jq >/dev/null 2>&1; then
25
+ AGENT_FIRST=$(jq -r '.firstName // empty' "$AGENT_DIR/config/agent.json" | tr '[:upper:]' '[:lower:]')
26
+ else
27
+ AGENT_FIRST=$(awk -F'"' '/"firstName"[[:space:]]*:/ { print tolower($4); exit }' "$AGENT_DIR/config/agent.json")
28
+ fi
29
+ fi
30
+ [ -z "$AGENT_FIRST" ] && AGENT_FIRST=$(basename "$AGENT_DIR" | sed 's/-ai$//')
31
+
32
+ LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
33
+ # Match the current ai.maestro.* namespace AND the legacy ai.adaptic.* one
34
+ # (deployed agents whose plists predate the rename). The loops guard with [ -f ].
35
+ PLIST_GLOB="$LAUNCH_AGENTS_DIR/ai.maestro.${AGENT_FIRST}-*.plist $LAUNCH_AGENTS_DIR/ai.adaptic.${AGENT_FIRST}-*.plist"
36
+
37
+ # Check if stop flag exists.
38
+ if [ ! -f "$AGENT_DIR/.emergency-stop" ]; then
39
+ echo "No emergency stop flag found — system is already running."
40
+ # Still verify launchd jobs are loaded; if not, load them.
41
+ loaded=0
42
+ for plist in $PLIST_GLOB; do
43
+ [ -f "$plist" ] || continue
44
+ label=$(basename "$plist" .plist)
45
+ if ! launchctl list | grep -q "$label"; then
46
+ launchctl load "$plist" 2>/dev/null && loaded=$((loaded + 1))
47
+ fi
48
+ done
49
+ [ "$loaded" -gt 0 ] && echo "Loaded $loaded missing launchd job(s)"
50
+ exit 0
51
+ fi
52
+
53
+ echo "[$TIMESTAMP] RESUMING OPERATIONS (agent=$AGENT_FIRST)" | tee -a "$LOG_FILE"
54
+
55
+ # 1. Run health check first.
56
+ echo "Running health check..."
57
+ if ! "$SCRIPT_DIR/healthcheck.sh"; then
58
+ echo "ERROR: Health check failed. Fix issues before resuming."
59
+ exit 1
60
+ fi
61
+
62
+ # 2. Remove stop flag.
63
+ rm "$AGENT_DIR/.emergency-stop"
64
+ echo "[$TIMESTAMP] Stop flag removed" >> "$LOG_FILE"
65
+
66
+ # 3. Reload installed launchd jobs.
67
+ echo "Reloading installed launchd jobs ($PLIST_GLOB)..."
68
+ loaded=0
69
+ for plist in $PLIST_GLOB; do
70
+ if [ -f "$plist" ]; then
71
+ # Unload first in case it was left in a half-loaded state.
72
+ launchctl unload "$plist" 2>/dev/null || true
73
+ launchctl load "$plist" 2>/dev/null || true
74
+ echo "[$TIMESTAMP] Loaded: $(basename "$plist")" >> "$LOG_FILE"
75
+ loaded=$((loaded + 1))
76
+ fi
77
+ done
78
+ echo "[$TIMESTAMP] Loaded $loaded launchd job(s)" >> "$LOG_FILE"
79
+
80
+ echo "[$TIMESTAMP] OPERATIONS RESUMED" | tee -a "$LOG_FILE"
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ """Search Alex's Gmail inbox via IMAP for correspondence matching a query.
3
+
4
+ Usage: python3 search-secondary-inbox.py "<search_query>" [--max N] [--full]
5
+ --max N : Maximum messages to return (default: 20)
6
+ --full : Include full message bodies (default: headers + snippet)
7
+
8
+ Examples:
9
+ python3 search-secondary-inbox.py "from:difc.ae"
10
+ python3 search-secondary-inbox.py "difc fitout" --max 50 --full
11
+ """
12
+ import sys
13
+ import os
14
+ import imaplib
15
+ import email
16
+ from email.header import decode_header
17
+ import json
18
+ from datetime import datetime
19
+
20
+ PRINCIPAL_EMAIL = "alex@example.com"
21
+ MEHRAN_PASSWORD = os.environ.get("MEHRAN_GMAIL_APP_PASSWORD", "")
22
+
23
+ # Load from .env if not in environment
24
+ if not MEHRAN_PASSWORD:
25
+ env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env")
26
+ if os.path.exists(env_path):
27
+ with open(env_path) as f:
28
+ for line in f:
29
+ if line.startswith("MEHRAN_GMAIL_APP_PASSWORD="):
30
+ MEHRAN_PASSWORD = line.strip().split("=", 1)[1].strip()
31
+ break
32
+
33
+ if not MEHRAN_PASSWORD:
34
+ print("ERROR: MEHRAN_GMAIL_APP_PASSWORD not set", file=sys.stderr)
35
+ sys.exit(1)
36
+
37
+
38
+ def decode_header_value(value):
39
+ """Decode email header value handling various encodings."""
40
+ if not value:
41
+ return ""
42
+ decoded_parts = decode_header(value)
43
+ result = []
44
+ for part, encoding in decoded_parts:
45
+ if isinstance(part, bytes):
46
+ result.append(part.decode(encoding or "utf-8", errors="replace"))
47
+ else:
48
+ result.append(str(part))
49
+ return " ".join(result)
50
+
51
+
52
+ def get_body(msg):
53
+ """Extract text body from email message."""
54
+ body = ""
55
+ if msg.is_multipart():
56
+ for part in msg.walk():
57
+ content_type = part.get_content_type()
58
+ content_disposition = str(part.get("Content-Disposition", ""))
59
+ if content_type == "text/plain" and "attachment" not in content_disposition:
60
+ try:
61
+ payload = part.get_payload(decode=True)
62
+ if payload:
63
+ charset = part.get_content_charset() or "utf-8"
64
+ body += payload.decode(charset, errors="replace")
65
+ except Exception:
66
+ pass
67
+ elif content_type == "text/html" and not body and "attachment" not in content_disposition:
68
+ try:
69
+ payload = part.get_payload(decode=True)
70
+ if payload:
71
+ charset = part.get_content_charset() or "utf-8"
72
+ body += payload.decode(charset, errors="replace")
73
+ except Exception:
74
+ pass
75
+ else:
76
+ try:
77
+ payload = msg.get_payload(decode=True)
78
+ if payload:
79
+ charset = msg.get_content_charset() or "utf-8"
80
+ body = payload.decode(charset, errors="replace")
81
+ except Exception:
82
+ pass
83
+ return body
84
+
85
+
86
+ def get_attachments(msg):
87
+ """List attachment filenames and sizes."""
88
+ attachments = []
89
+ if msg.is_multipart():
90
+ for part in msg.walk():
91
+ content_disposition = str(part.get("Content-Disposition", ""))
92
+ if "attachment" in content_disposition or (
93
+ part.get_filename() and part.get_content_type() != "text/plain"
94
+ ):
95
+ filename = decode_header_value(part.get_filename() or "unnamed")
96
+ size = len(part.get_payload(decode=True) or b"")
97
+ attachments.append({"filename": filename, "size": size, "content_type": part.get_content_type()})
98
+ return attachments
99
+
100
+
101
+ def search_inbox(query, max_results=20, full_body=False):
102
+ """Search Alex's Gmail via IMAP."""
103
+ mail = imaplib.IMAP4_SSL("imap.gmail.com")
104
+ mail.login(PRINCIPAL_EMAIL, MEHRAN_PASSWORD)
105
+
106
+ # Use All Mail to search everything
107
+ mail.select('"[Gmail]/All Mail"')
108
+
109
+ # Build IMAP search criteria
110
+ # Gmail supports X-GM-RAW for full Gmail search syntax
111
+ status, message_ids = mail.search(None, f'X-GM-RAW "{query}"')
112
+
113
+ if status != "OK" or not message_ids[0]:
114
+ print(json.dumps({"query": query, "results": [], "total": 0}, indent=2))
115
+ mail.logout()
116
+ return
117
+
118
+ ids = message_ids[0].split()
119
+ total = len(ids)
120
+ # Get most recent N messages (IMAP returns oldest first)
121
+ ids = ids[-max_results:] if len(ids) > max_results else ids
122
+ ids.reverse() # newest first
123
+
124
+ results = []
125
+ for msg_id in ids:
126
+ try:
127
+ if full_body:
128
+ status, data = mail.fetch(msg_id, "(RFC822)")
129
+ else:
130
+ status, data = mail.fetch(msg_id, "(RFC822)")
131
+
132
+ if status != "OK" or not data[0]:
133
+ continue
134
+
135
+ raw_email = data[0][1]
136
+ msg = email.message_from_bytes(raw_email)
137
+
138
+ result = {
139
+ "from": decode_header_value(msg.get("From", "")),
140
+ "to": decode_header_value(msg.get("To", "")),
141
+ "cc": decode_header_value(msg.get("Cc", "")),
142
+ "subject": decode_header_value(msg.get("Subject", "")),
143
+ "date": msg.get("Date", ""),
144
+ "message_id": msg.get("Message-ID", ""),
145
+ "in_reply_to": msg.get("In-Reply-To", ""),
146
+ }
147
+
148
+ body = get_body(msg)
149
+ if full_body:
150
+ result["body"] = body
151
+ else:
152
+ result["snippet"] = body[:500] + ("..." if len(body) > 500 else "")
153
+
154
+ attachments = get_attachments(msg)
155
+ if attachments:
156
+ result["attachments"] = attachments
157
+
158
+ results.append(result)
159
+ except Exception as e:
160
+ results.append({"error": str(e), "msg_id": msg_id.decode()})
161
+
162
+ mail.logout()
163
+
164
+ output = {
165
+ "query": query,
166
+ "total_matches": total,
167
+ "returned": len(results),
168
+ "results": results
169
+ }
170
+ print(json.dumps(output, indent=2, ensure_ascii=False, default=str))
171
+
172
+
173
+ if __name__ == "__main__":
174
+ import argparse
175
+ parser = argparse.ArgumentParser(description="Search Alex's Gmail inbox via IMAP")
176
+ parser.add_argument("query", help="Gmail search query")
177
+ parser.add_argument("--max", type=int, default=20, help="Maximum results (default: 20)")
178
+ parser.add_argument("--full", action="store_true", help="Include full message bodies")
179
+ args = parser.parse_args()
180
+
181
+ search_inbox(args.query, max_results=args.max, full_body=args.full)