@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,562 @@
1
+ #!/usr/bin/env python3
2
+ """Disclosure Boundary Generator — Layer 1 module.
3
+
4
+ Generates dynamic disclosure boundary guidance for a given recipient by:
5
+ 1. Loading the recipient's user profile from memory/profiles/users/{slug}.yaml
6
+ 2. Loading recent provenance records from memory/provenance/index.jsonl
7
+ 3. Cross-referencing provenance against the recipient's information_boundaries
8
+ 4. Returning a structured dict with clearance summary, active conflicts,
9
+ recent sensitive context, and excluded domains active
10
+
11
+ Usage (CLI):
12
+ python3 scripts/disclosure_boundaries.py --recipient casey-morgan
13
+ python3 scripts/disclosure_boundaries.py --recipient alex-chen --hours 24
14
+
15
+ Exit codes:
16
+ 0 = always (profile not found prints to stderr and exits 0)
17
+
18
+ Output: JSON to stdout.
19
+
20
+ Created: 2026-04-05 — Task 5, Layer 1 information barrier system
21
+ """
22
+
23
+ import argparse
24
+ import json
25
+ import os
26
+ import re
27
+ import sys
28
+ from datetime import datetime, timezone, timedelta
29
+ from pathlib import Path
30
+
31
+ # ─── Paths ───────────────────────────────────────────────────────────────────
32
+ SCRIPT_DIR = Path(__file__).resolve().parent
33
+ REPO_ROOT = SCRIPT_DIR.parent
34
+ USER_PROFILES_DIR = REPO_ROOT / "memory" / "profiles" / "users"
35
+ PROVENANCE_INDEX = REPO_ROOT / "memory" / "provenance" / "index.jsonl"
36
+
37
+
38
+ # ─── YAML Parser ─────────────────────────────────────────────────────────────
39
+ # Custom minimal YAML parser — no PyYAML dependency (consistent with repo pattern).
40
+ # Handles: top-level scalars, nested mappings, inline lists [a, b, c],
41
+ # multi-line lists (- item), lists of mappings (- key: val / key2: val2),
42
+ # folded block strings (>), and quoted strings.
43
+
44
+ def _parse_yaml(text: str) -> dict:
45
+ """Parse a YAML text string into a Python dict.
46
+
47
+ Tries PyYAML first (if available), falls back to the custom parser.
48
+ """
49
+ try:
50
+ import yaml as _yaml
51
+ return _yaml.safe_load(text) or {}
52
+ except ImportError:
53
+ pass
54
+
55
+ lines = text.split('\n')
56
+ result, _ = _parse_block(lines, 0, 0)
57
+ return result if isinstance(result, dict) else {}
58
+
59
+
60
+ def _next_content(lines, start):
61
+ """Return (indent, index) for the next non-blank, non-comment line."""
62
+ i = start
63
+ while i < len(lines):
64
+ s = lines[i].lstrip()
65
+ if s and not s.startswith('#'):
66
+ return len(lines[i]) - len(lines[i].lstrip()), i
67
+ i += 1
68
+ return None, None
69
+
70
+
71
+ def _parse_block(lines, start, base_indent):
72
+ """Parse a YAML mapping block. Returns (dict, next_index)."""
73
+ result = {}
74
+ i = start
75
+ while i < len(lines):
76
+ raw = lines[i]
77
+ stripped = raw.lstrip()
78
+
79
+ if not stripped or stripped.startswith('#'):
80
+ i += 1
81
+ continue
82
+
83
+ indent = len(raw) - len(raw.lstrip())
84
+
85
+ if indent < base_indent and i > start:
86
+ break
87
+ if indent > base_indent and i > start:
88
+ break
89
+
90
+ # List item encountered at mapping level — caller handles as value
91
+ if stripped.startswith('- '):
92
+ if not result:
93
+ lst, i = _parse_list(lines, i, indent)
94
+ return lst, i
95
+ break
96
+
97
+ if ':' in stripped and not stripped.startswith('-'):
98
+ colon_pos = stripped.index(':')
99
+ key = stripped[:colon_pos].strip()
100
+ rest = stripped[colon_pos + 1:].strip()
101
+
102
+ # Inline list: key: [a, b, c]
103
+ if rest.startswith('['):
104
+ result[key] = _parse_inline_list(rest)
105
+ i += 1
106
+ continue
107
+
108
+ # Folded/literal block scalar (> or |)
109
+ if rest in ('>', '|', '>-', '|-', '>+', '|+'):
110
+ value, i = _parse_block_scalar(lines, i + 1, indent)
111
+ result[key] = value
112
+ continue
113
+
114
+ if rest:
115
+ result[key] = _yaml_scalar(rest)
116
+ i += 1
117
+ else:
118
+ # Block value — peek to find child indent
119
+ child_indent, child_idx = _next_content(lines, i + 1)
120
+ if child_indent is not None and child_indent > indent:
121
+ child_stripped = lines[child_idx].lstrip()
122
+ if child_stripped.startswith('- '):
123
+ value, i = _parse_list(lines, child_idx, child_indent)
124
+ elif child_stripped.startswith('['):
125
+ # inline list as sole content on next line
126
+ value = _parse_inline_list(child_stripped)
127
+ i = child_idx + 1
128
+ else:
129
+ value, i = _parse_block(lines, child_idx, child_indent)
130
+ result[key] = value
131
+ else:
132
+ result[key] = None
133
+ i += 1
134
+ else:
135
+ i += 1
136
+
137
+ return result, i
138
+
139
+
140
+ def _parse_list(lines, start, base_indent):
141
+ """Parse a YAML sequence block. Returns (list, next_index)."""
142
+ result = []
143
+ i = start
144
+ while i < len(lines):
145
+ raw = lines[i]
146
+ stripped = raw.lstrip()
147
+
148
+ if not stripped or stripped.startswith('#'):
149
+ i += 1
150
+ continue
151
+
152
+ indent = len(raw) - len(raw.lstrip())
153
+ if indent < base_indent:
154
+ break
155
+ if indent > base_indent:
156
+ break
157
+
158
+ if not stripped.startswith('- '):
159
+ # Not a list item — we're done
160
+ break
161
+
162
+ item_content = stripped[2:].strip()
163
+
164
+ # Inline list item: - [a, b, c]
165
+ if item_content.startswith('['):
166
+ result.append(_parse_inline_list(item_content))
167
+ i += 1
168
+ continue
169
+
170
+ # Folded block under a list item
171
+ if item_content in ('>', '|', '>-', '|-', '>+', '|+'):
172
+ value, i = _parse_block_scalar(lines, i + 1, indent)
173
+ result.append(value)
174
+ continue
175
+
176
+ # Mapping-style list item: - key: value
177
+ if ':' in item_content and not item_content.startswith('"') and not item_content.startswith("'"):
178
+ colon_pos = item_content.index(':')
179
+ k = item_content[:colon_pos].strip()
180
+ v = item_content[colon_pos + 1:].strip()
181
+ item_dict = {k: _parse_inline_list(v) if v.startswith('[') else (_yaml_scalar(v) if v else None)}
182
+
183
+ # Continuation keys at deeper indent
184
+ child_indent, child_idx = _next_content(lines, i + 1)
185
+ if child_indent is not None and child_indent > indent:
186
+ child_stripped = lines[child_idx].lstrip()
187
+ if child_stripped.startswith('- '):
188
+ # nested list under dict key — unusual but handle gracefully
189
+ i += 1
190
+ else:
191
+ extra, i = _parse_block(lines, child_idx, child_indent)
192
+ if isinstance(extra, dict):
193
+ item_dict.update(extra)
194
+ else:
195
+ i += 1
196
+
197
+ result.append(item_dict)
198
+ else:
199
+ result.append(_yaml_scalar(item_content))
200
+ i += 1
201
+
202
+ return result, i
203
+
204
+
205
+ def _parse_block_scalar(lines, start, owner_indent):
206
+ """Parse a folded/literal block scalar (lines after > or |).
207
+
208
+ Returns (string, next_index).
209
+ """
210
+ parts = []
211
+ i = start
212
+ while i < len(lines):
213
+ raw = lines[i]
214
+ stripped = raw.lstrip()
215
+ if not stripped:
216
+ i += 1
217
+ continue
218
+ indent = len(raw) - len(raw.lstrip())
219
+ if indent <= owner_indent:
220
+ break
221
+ parts.append(stripped)
222
+ i += 1
223
+ return ' '.join(parts), i
224
+
225
+
226
+ def _parse_inline_list(text: str):
227
+ """Parse an inline YAML list like [a, b, c] or ["x", "y"].
228
+
229
+ Returns a Python list.
230
+ """
231
+ text = text.strip()
232
+ if not (text.startswith('[') and text.endswith(']')):
233
+ return [_yaml_scalar(text)] if text else []
234
+ inner = text[1:-1].strip()
235
+ if not inner:
236
+ return []
237
+ # Split on commas not inside quotes
238
+ items = []
239
+ current = ''
240
+ in_quote = None
241
+ for ch in inner:
242
+ if ch in ('"', "'") and in_quote is None:
243
+ in_quote = ch
244
+ elif ch == in_quote:
245
+ in_quote = None
246
+ elif ch == ',' and in_quote is None:
247
+ items.append(_yaml_scalar(current.strip()))
248
+ current = ''
249
+ continue
250
+ current += ch
251
+ if current.strip():
252
+ items.append(_yaml_scalar(current.strip()))
253
+ return items
254
+
255
+
256
+ def _yaml_scalar(text: str):
257
+ """Convert a YAML scalar string to an appropriate Python type."""
258
+ if text is None:
259
+ return None
260
+ text = text.strip()
261
+ if not text:
262
+ return None
263
+ # Quoted strings
264
+ if (text.startswith('"') and text.endswith('"')) or \
265
+ (text.startswith("'") and text.endswith("'")):
266
+ return text[1:-1]
267
+ # Strip inline comments
268
+ for sep in (' #', '\t#'):
269
+ if sep in text:
270
+ text = text[:text.index(sep)].strip()
271
+ # Boolean
272
+ if text.lower() in ('true', 'yes'):
273
+ return True
274
+ if text.lower() in ('false', 'no'):
275
+ return False
276
+ # Null
277
+ if text.lower() in ('null', '~'):
278
+ return None
279
+ # Integer
280
+ try:
281
+ return int(text)
282
+ except ValueError:
283
+ pass
284
+ # Float
285
+ try:
286
+ return float(text)
287
+ except ValueError:
288
+ pass
289
+ return text
290
+
291
+
292
+ # ─── Profile Loading ──────────────────────────────────────────────────────────
293
+
294
+ def load_profile(slug: str) -> dict | None:
295
+ """Load and parse a user profile YAML. Returns None if not found."""
296
+ path = USER_PROFILES_DIR / f"{slug}.yaml"
297
+ if not path.exists():
298
+ return None
299
+ text = path.read_text(encoding='utf-8')
300
+ return _parse_yaml(text)
301
+
302
+
303
+ # ─── Provenance Loading ───────────────────────────────────────────────────────
304
+
305
+ def load_recent_provenance(hours: int = 48) -> list[dict]:
306
+ """Load provenance records within the last *hours* hours.
307
+
308
+ Returns list of dicts. Empty list if file absent or empty.
309
+ """
310
+ if not PROVENANCE_INDEX.exists():
311
+ return []
312
+
313
+ cutoff = datetime.now(tz=timezone.utc) - timedelta(hours=hours)
314
+ records = []
315
+
316
+ with PROVENANCE_INDEX.open(encoding='utf-8') as f:
317
+ for line in f:
318
+ line = line.strip()
319
+ if not line:
320
+ continue
321
+ try:
322
+ rec = json.loads(line)
323
+ except json.JSONDecodeError:
324
+ continue
325
+
326
+ ts_raw = rec.get('timestamp', '')
327
+ try:
328
+ # Handle ISO 8601 with or without Z suffix
329
+ ts_str = ts_raw.replace('Z', '+00:00')
330
+ ts = datetime.fromisoformat(ts_str)
331
+ if ts.tzinfo is None:
332
+ ts = ts.replace(tzinfo=timezone.utc)
333
+ except (ValueError, AttributeError):
334
+ # Unparseable timestamp — include conservatively
335
+ records.append(rec)
336
+ continue
337
+
338
+ if ts >= cutoff:
339
+ records.append(rec)
340
+
341
+ return records
342
+
343
+
344
+ # ─── Cross-Reference Logic ────────────────────────────────────────────────────
345
+
346
+ def _cross_reference(recipient_slug: str, boundaries: dict, records: list[dict]) -> list[dict]:
347
+ """Flag provenance records that may expose sensitive information to the recipient.
348
+
349
+ A record is flagged when BOTH:
350
+ (a) recipient is NOT in the record's participants list
351
+ (b) the record's matter matches one of the recipient's conflicts
352
+ OR the record's context_domain is in the recipient's excluded_domains
353
+
354
+ Returns list of warning dicts with keys: source, warning.
355
+ """
356
+ conflicts = boundaries.get('conflicts') or []
357
+ excluded_domains = boundaries.get('excluded_domains') or []
358
+
359
+ # Build sets for fast lookup
360
+ conflict_matters = set()
361
+ for c in conflicts:
362
+ if isinstance(c, dict):
363
+ m = c.get('matter')
364
+ if m:
365
+ conflict_matters.add(str(m).lower())
366
+ elif isinstance(c, str):
367
+ conflict_matters.add(c.lower())
368
+
369
+ excluded_set = {str(d).lower() for d in excluded_domains if d}
370
+
371
+ flagged = []
372
+ for rec in records:
373
+ participants = [str(p).lower() for p in (rec.get('participants') or [])]
374
+ if recipient_slug.lower() in participants:
375
+ # Recipient is already a participant — no disclosure risk from this record
376
+ continue
377
+
378
+ matter = str(rec.get('matter') or '').lower()
379
+ domain = str(rec.get('context_domain') or '').lower()
380
+ source_id = rec.get('source_id', 'unknown')
381
+ sensitivity = rec.get('sensitivity_signals') or []
382
+
383
+ reasons = []
384
+ if matter and matter in conflict_matters:
385
+ # Find the nature note for this conflict
386
+ nature = ''
387
+ for c in conflicts:
388
+ if isinstance(c, dict) and str(c.get('matter', '')).lower() == matter:
389
+ nature = c.get('nature', '')
390
+ break
391
+ reasons.append(
392
+ f"matter '{rec.get('matter')}' is a declared conflict"
393
+ + (f" ({nature})" if nature else "")
394
+ )
395
+
396
+ if domain and domain in excluded_set:
397
+ reasons.append(f"context domain '{rec.get('context_domain')}' is in excluded domains")
398
+
399
+ if not reasons:
400
+ continue
401
+
402
+ # Build participant name list for the source description
403
+ participant_names = rec.get('participants') or []
404
+ source_desc = source_id
405
+ if participant_names:
406
+ source_desc += f" (participants: {', '.join(str(p) for p in participant_names)})"
407
+
408
+ warning_parts = [
409
+ f"Recipient {recipient_slug!r} is NOT a participant.",
410
+ *[f"Warning: {r}." for r in reasons],
411
+ ]
412
+ if sensitivity:
413
+ warning_parts.append(f"Sensitivity signals: {', '.join(str(s) for s in sensitivity)}.")
414
+
415
+ flagged.append({
416
+ "source": source_desc,
417
+ "warning": " ".join(warning_parts),
418
+ })
419
+
420
+ return flagged
421
+
422
+
423
+ # ─── Clearance Summary Builder ────────────────────────────────────────────────
424
+
425
+ def _build_clearance_summary(profile: dict, boundaries: dict) -> str:
426
+ """Build a natural-language clearance summary from profile fields."""
427
+ name = profile.get('name') or profile.get('preferred_name') or 'Unknown'
428
+ role = profile.get('role') or ''
429
+ org = profile.get('organisation') or ''
430
+ relationship = profile.get('relationship_to_northwind') or ''
431
+ domains_of_interest = boundaries.get('domains_of_interest') or []
432
+ excluded_domains = boundaries.get('excluded_domains') or []
433
+ conflicts = boundaries.get('conflicts') or []
434
+ party_to_matters = boundaries.get('party_to_matters') or []
435
+
436
+ # Unrestricted access shortcut
437
+ if domains_of_interest == ['*']:
438
+ return (
439
+ f"{name} has unrestricted access — all information domains are permitted. "
440
+ f"Role: {role}. Organisation: {org}."
441
+ ).strip()
442
+
443
+ parts = [f"Clearance summary for {name}."]
444
+ if role:
445
+ parts.append(f"Role: {role}.")
446
+ if org:
447
+ parts.append(f"Organisation: {org}.")
448
+ if relationship:
449
+ parts.append(f"Relationship to Northwind: {relationship}.")
450
+
451
+ if domains_of_interest:
452
+ parts.append(f"Permitted domains: {', '.join(str(d) for d in domains_of_interest)}.")
453
+ else:
454
+ parts.append("No permitted domains declared (default deny).")
455
+
456
+ if excluded_domains:
457
+ parts.append(f"Excluded domains ({len(excluded_domains)}): {', '.join(str(d) for d in excluded_domains)}.")
458
+
459
+ if party_to_matters:
460
+ matters_str = ', '.join(str(m) for m in party_to_matters)
461
+ parts.append(f"Party to matters: {matters_str}.")
462
+
463
+ if conflicts:
464
+ conflict_strs = []
465
+ for c in conflicts:
466
+ if isinstance(c, dict):
467
+ m = c.get('matter', '')
468
+ n = c.get('nature', '')
469
+ conflict_strs.append(f"{m} ({n})" if n else str(m))
470
+ else:
471
+ conflict_strs.append(str(c))
472
+ parts.append(f"Active conflicts ({len(conflicts)}): {'; '.join(conflict_strs)}.")
473
+
474
+ return ' '.join(parts)
475
+
476
+
477
+ # ─── Main Entry Point ─────────────────────────────────────────────────────────
478
+
479
+ def generate_disclosure_boundaries(
480
+ recipient_slug: str,
481
+ recent_context_hours: int = 48,
482
+ ) -> dict | None:
483
+ """Generate disclosure boundary guidance for *recipient_slug*.
484
+
485
+ Returns a dict with keys:
486
+ - recipient_clearance_summary (str)
487
+ - active_conflicts (list)
488
+ - recent_sensitive_context (list of {source, warning} dicts)
489
+ - excluded_domains_active (list)
490
+
491
+ Returns None if no profile is found for the slug.
492
+ """
493
+ profile = load_profile(recipient_slug)
494
+ if profile is None:
495
+ return None
496
+
497
+ boundaries = profile.get('information_boundaries') or {}
498
+ domains_of_interest = boundaries.get('domains_of_interest') or []
499
+
500
+ # Unrestricted recipient — skip provenance cross-reference
501
+ if domains_of_interest == ['*']:
502
+ return {
503
+ "recipient_clearance_summary": _build_clearance_summary(profile, boundaries),
504
+ "active_conflicts": [],
505
+ "recent_sensitive_context": [],
506
+ "excluded_domains_active": [],
507
+ }
508
+
509
+ conflicts = boundaries.get('conflicts') or []
510
+ excluded_domains = boundaries.get('excluded_domains') or []
511
+
512
+ # Load and cross-reference recent provenance
513
+ recent_records = load_recent_provenance(hours=recent_context_hours)
514
+ sensitive_context = _cross_reference(recipient_slug, boundaries, recent_records)
515
+
516
+ return {
517
+ "recipient_clearance_summary": _build_clearance_summary(profile, boundaries),
518
+ "active_conflicts": [
519
+ {
520
+ "matter": c.get('matter') if isinstance(c, dict) else str(c),
521
+ "nature": c.get('nature', '') if isinstance(c, dict) else '',
522
+ }
523
+ for c in conflicts
524
+ ],
525
+ "recent_sensitive_context": sensitive_context,
526
+ "excluded_domains_active": list(excluded_domains),
527
+ }
528
+
529
+
530
+ # ─── CLI ──────────────────────────────────────────────────────────────────────
531
+
532
+ def main():
533
+ parser = argparse.ArgumentParser(
534
+ description="Generate disclosure boundary guidance for a recipient."
535
+ )
536
+ parser.add_argument(
537
+ '--recipient',
538
+ required=True,
539
+ help="Recipient slug (e.g. casey-morgan)",
540
+ )
541
+ parser.add_argument(
542
+ '--hours',
543
+ type=int,
544
+ default=48,
545
+ help="Lookback window in hours for provenance records (default: 48)",
546
+ )
547
+ args = parser.parse_args()
548
+
549
+ result = generate_disclosure_boundaries(args.recipient, recent_context_hours=args.hours)
550
+ if result is None:
551
+ print(
552
+ f"No profile found for recipient: {args.recipient!r}",
553
+ file=sys.stderr,
554
+ )
555
+ sys.exit(0)
556
+
557
+ print(json.dumps(result, indent=2))
558
+ sys.exit(0)
559
+
560
+
561
+ if __name__ == '__main__':
562
+ main()
@@ -0,0 +1,52 @@
1
+ <div dir="ltr">
2
+ <h2 style="color:rgb(0,0,0);vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px;letter-spacing:0.2pt;text-transform:capitalize">
3
+ <font size="2"><br>Alex Chen<br></font>
4
+ <span style="font-weight:normal"><font face="arial, sans-serif" size="2">Chief Executive Officer</font></span>
5
+ </h2>
6
+ <h2 style="color:rgb(0,0,0);vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px;letter-spacing:0.2pt;text-transform:capitalize">
7
+ <div style="color:rgb(34,34,34);font-size:small;font-weight:400;letter-spacing:normal;text-transform:none"><font size="2"><br></font></div>
8
+ <div style="color:rgb(34,34,34);font-size:small;font-weight:400;letter-spacing:normal;text-transform:none">
9
+ <img width="141" height="42" src="https://ci3.googleusercontent.com/mail-sig/AIorK4xNDnPN6a0MjSHV5urb9g11u2CzHIzwdJbwN21quGjzQFdbOdegCq1Wp6lq3NCqsARbi-ooqPfE9E1D" style="margin-right:0px"><br>
10
+ </div>
11
+ </h2>
12
+ <h2 style="color:rgb(34,34,34);vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px">
13
+ <font face="arial, sans-serif" size="1">
14
+ <span style="font-weight:normal">
15
+ <span style="color:rgb(0,0,0);letter-spacing:0.2pt;text-transform:capitalize">
16
+ <br><a href="tel:+971585291799" style="color:rgb(85,85,85);text-transform:none;font-style:inherit;vertical-align:baseline;outline:0px;padding:0px;margin:0px;border:0px" target="_blank">+971 58 529 1799</a>
17
+ </span>
18
+ <span style="letter-spacing:0.2pt;text-transform:capitalize"><font color="#999999">(UAE)</font></span>
19
+ <span style="color:rgb(0,0,0);letter-spacing:0.2pt;text-transform:capitalize"> <br></span>
20
+ </span>
21
+ </font>
22
+ </h2>
23
+ <h2 style="vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px">
24
+ <font color="#555555" face="arial, sans-serif" size="1"><span style="font-weight:400;letter-spacing:0.266667px"><u>+1 646 696 5474</u></span></font>
25
+ <font face="arial, sans-serif" size="1">
26
+ <span style="color:rgb(0,0,0);font-weight:normal;letter-spacing:0.2pt;text-transform:capitalize"> </span>
27
+ <span style="color:rgb(34,34,34);font-weight:normal;letter-spacing:0.2pt;text-transform:capitalize"><font color="#999999">(USA)</font></span>
28
+ <br>
29
+ <font color="#000000"><span style="font-weight:400;letter-spacing:0.266667px;text-transform:capitalize"><br>Level 1, Innovation One, Dubai International Financial Centre, Dubai, UAE</span></font>
30
+ </font>
31
+ </h2>
32
+ <h2 style="color:rgb(0,0,0);vertical-align:baseline;outline:0px;padding:0px;margin:0px 0px 5px;border:0px;line-height:18px;letter-spacing:0.2pt;text-transform:capitalize">
33
+ <div style="color:rgb(34,34,34);font-size:small;font-weight:400;letter-spacing:normal;text-transform:none;line-height:20px"></div>
34
+ <div style="color:rgb(34,34,34);font-weight:400;letter-spacing:normal;text-transform:none;font-size:12.8px">
35
+ <div dir="ltr">
36
+ <div dir="ltr">
37
+ <div dir="ltr">
38
+ <div style="font-size:small">
39
+ <font face="arial, sans-serif"><span style="color:rgb(153,153,153);font-size:x-small">_________________________________________</span><br></font>
40
+ </div>
41
+ </div>
42
+ <div dir="ltr">
43
+ <div style="font-size:small"><font size="1" color="#999999" face="arial, sans-serif"><br></font></div>
44
+ <div style="font-size:small">
45
+ <font size="1" color="#999999"><font face="arial, sans-serif">This email and any files transmitted with it are confidential and intended solely for the use of the individual or entity to whom they are addressed. If you have received this email in error please notify the sender. This message contains confidential information and is intended only for the individual named. If you are not the named addressee you should not disseminate, distribute or copy this email. Please notify the sender immediately by email if you have received this email by mistake and delete this email from your system. If you are not the intended recipient you are notified that disclosing, copying, distributing, or taking any action in reliance on the contents of this information is strictly prohibited.</font></font>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </h2>
52
+ </div>
@@ -0,0 +1,60 @@
1
+ <!--
2
+ Email signature template — Maestro framework
3
+
4
+ This file is rewritten by /init-maestro Sub-agent 4 with the new agent's
5
+ identity. The placeholders below are replaced verbatim:
6
+
7
+ {{AGENT_NAME}} e.g. "Lucas Ferreira"
8
+ {{AGENT_TITLE}} e.g. "VP, Regulatory & Licensing"
9
+ {{AGENT_EMAIL}} e.g. "lucas@example.com"
10
+ {{AGENT_PHONE}} e.g. "+61 478 964 324" (pretty form)
11
+ {{COMPANY_ADDRESS}} e.g. "Level 1, Innovation One, DIFC, Dubai, UAE"
12
+
13
+ If you see this file unchanged in a deployed agent's repo, something went
14
+ wrong with init-maestro Sub-agent 4 — re-run the wizard or rewrite by hand.
15
+ -->
16
+ <div
17
+ style="
18
+ font-family: Arial, Helvetica, sans-serif;
19
+ font-size: 13px;
20
+ color: #333;
21
+ "
22
+ >
23
+ <p style="margin: 0; font-weight: bold; font-size: 14px">{{AGENT_NAME}}</p>
24
+ <p style="margin: 0; color: #666">{{AGENT_TITLE}}</p>
25
+ <br />
26
+ <a href="https://northwind.ai"
27
+ ><img
28
+ src="https://northwind.ai/logo.png"
29
+ alt="Northwind"
30
+ width="120"
31
+ style="display: block; margin: 8px 0"
32
+ /></a>
33
+ <br />
34
+ <p style="margin: 0; font-size: 12px">{{AGENT_EMAIL}}</p>
35
+ <p style="margin: 0; font-size: 12px">{{AGENT_PHONE}}</p>
36
+ <br />
37
+ <p style="margin: 0; font-size: 12px">
38
+ {{COMPANY_ADDRESS}}
39
+ </p>
40
+ <p
41
+ style="
42
+ margin: 16px 0 0 0;
43
+ border-top: 1px solid #ccc;
44
+ padding-top: 8px;
45
+ font-size: 10px;
46
+ color: #999;
47
+ "
48
+ >
49
+ This email and any files transmitted with it are confidential and intended
50
+ solely for the use of the individual or entity to whom they are addressed.
51
+ If you have received this email in error please notify the sender. This
52
+ message contains confidential information and is intended only for the
53
+ individual named. If you are not the named addressee you should not
54
+ disseminate, distribute or copy this email. Please notify the sender
55
+ immediately by email if you have received this email by mistake and delete
56
+ this email from your system. If you are not the intended recipient you are
57
+ notified that disclosing, copying, distributing, or taking any action in
58
+ reliance on the contents of this information is strictly prohibited.
59
+ </p>
60
+ </div>