@adaptic/maestro 1.0.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 (854) hide show
  1. package/.claude/commands/init-agent.md +99 -0
  2. package/.claude/commands/init-maestro.md +565 -0
  3. package/.claude/settings.json +114 -0
  4. package/.env.example +152 -0
  5. package/README.md +491 -0
  6. package/agents/board-prep/agent.md +80 -0
  7. package/agents/browser-operator/agent.md +52 -0
  8. package/agents/calendar-ops/agent.md +50 -0
  9. package/agents/capital-raising/agent.md +69 -0
  10. package/agents/ceo-briefing/agent.md +72 -0
  11. package/agents/communications/agent.md +69 -0
  12. package/agents/competitive-intelligence/agent.md +49 -0
  13. package/agents/corporate-development/agent.md +66 -0
  14. package/agents/decision-log/agent.md +65 -0
  15. package/agents/desktop-operator/agent.md +59 -0
  16. package/agents/engineering-coordination/agent.md +51 -0
  17. package/agents/founder-voice/agent.md +72 -0
  18. package/agents/fund-ops/agent.md +52 -0
  19. package/agents/gmail-operator/agent.md +62 -0
  20. package/agents/hiring-org-design/agent.md +119 -0
  21. package/agents/inbound-dispatcher/agent.md +66 -0
  22. package/agents/legal-structuring/agent.md +65 -0
  23. package/agents/market-research/agent.md +48 -0
  24. package/agents/partnerships/agent.md +59 -0
  25. package/agents/platform-architecture/agent.md +57 -0
  26. package/agents/pmo-execution/agent.md +60 -0
  27. package/agents/product-strategy/agent.md +50 -0
  28. package/agents/regulatory-dfsa/agent.md +96 -0
  29. package/agents/risk-register/agent.md +62 -0
  30. package/agents/rollup-target-sourcing/agent.md +59 -0
  31. package/agents/session-spawner/agent.md +64 -0
  32. package/agents/slack-operator/agent.md +60 -0
  33. package/agents/sophie-chief-of-staff/agent.md +134 -0
  34. package/agents/strategic-planning/agent.md +54 -0
  35. package/agents/whatsapp-operator/agent.md +60 -0
  36. package/agents/workflow-automation/agent.md +61 -0
  37. package/bin/maestro.mjs +388 -0
  38. package/desktop-control/README.md +56 -0
  39. package/desktop-control/app-profiles/gmail.yaml +120 -0
  40. package/desktop-control/app-profiles/slack.yaml +315 -0
  41. package/desktop-control/app-profiles/whatsapp.yaml +107 -0
  42. package/docs/architecture/agent-topology.md +2222 -0
  43. package/docs/architecture/continuous-monitoring.md +221 -0
  44. package/docs/architecture/mcp-capability-map.md +560 -0
  45. package/docs/architecture/system-architecture.md +1273 -0
  46. package/docs/business-synthesis/ADAPTIC-GROUP-FINAL-OWNERSHIP-STRUCTURE.pdf +13667 -10
  47. package/docs/business-synthesis/adaptic-overview.md +296 -0
  48. package/docs/business-synthesis/executive-operating-model.md +261 -0
  49. package/docs/governance/action-approval-model.md +331 -0
  50. package/docs/governance/communications-policy.md +410 -0
  51. package/docs/governance/desktop-control-safety.md +499 -0
  52. package/docs/guides/agent-persona-setup.md +600 -0
  53. package/docs/operating-charter.md +87 -0
  54. package/docs/prompts/board-pack-cover-template.md +37 -0
  55. package/docs/prompts/decision-recommendation-template.md +88 -0
  56. package/docs/prompts/followup-message-template.md +141 -0
  57. package/docs/prompts/investor-letter-template.md +52 -0
  58. package/docs/prompts/morning-brief-template.md +82 -0
  59. package/docs/prompts/presentation-template.md +58 -0
  60. package/docs/prompts/weekly-strategic-memo-template.md +104 -0
  61. package/docs/runbooks/mac-mini-bootstrap.md +404 -0
  62. package/docs/runbooks/perpetual-operations.md +505 -0
  63. package/docs/runbooks/recovery-and-failover.md +588 -0
  64. package/docs/superpowers/plans/2026-04-02-phase0-operational-foundation.md +2550 -0
  65. package/docs/superpowers/plans/2026-04-03-phase1-executive-core.md +1085 -0
  66. package/docs/superpowers/plans/2026-04-03-phase2-people-product-commercial.md +739 -0
  67. package/docs/superpowers/plans/2026-04-05-information-barrier-implementation.md +926 -0
  68. package/docs/superpowers/plans/2026-04-06-session-context-dedup.md +1994 -0
  69. package/docs/superpowers/specs/2026-04-02-phase0-operational-foundation-design.md +842 -0
  70. package/docs/superpowers/specs/2026-04-03-phase1-executive-core-design.md +516 -0
  71. package/docs/superpowers/specs/2026-04-03-phase2-people-product-commercial-design.md +452 -0
  72. package/docs/superpowers/specs/2026-04-03-phase3-4-final-towers-design.md +129 -0
  73. package/docs/superpowers/specs/2026-04-05-information-barrier-design.md +678 -0
  74. package/docs/superpowers/specs/2026-04-05-reactive-daemon-design.md +237 -0
  75. package/docs/superpowers/specs/2026-04-06-session-context-dedup-design.md +369 -0
  76. package/docs/workflows/executive-cadence.md +218 -0
  77. package/ingest/README.md +87 -0
  78. package/mcp/README.md +51 -0
  79. package/package.json +48 -0
  80. package/plugins/maestro-skills/plugin.json +55 -0
  81. package/plugins/maestro-skills/skills/board-deck.md +68 -0
  82. package/plugins/maestro-skills/skills/decision-brief.md +89 -0
  83. package/plugins/maestro-skills/skills/draft-comms.md +84 -0
  84. package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
  85. package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
  86. package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
  87. package/plugins/maestro-skills/skills/morning-brief.md +54 -0
  88. package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
  89. package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
  90. package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
  91. package/plugins/maestro-skills/skills/slack-followup.md +64 -0
  92. package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
  93. package/policies/action-classification.yaml +110 -0
  94. package/policies/information-barriers.yaml +119 -0
  95. package/policies/prompt-injection-defence.yaml +138 -0
  96. package/public/assets/adaptic-icon-dark.png +0 -0
  97. package/public/assets/adaptic-icon-dark.svg +4 -0
  98. package/public/assets/adaptic-icon-light.svg +4 -0
  99. package/public/assets/adaptic-logo-dark.svg +17 -0
  100. package/public/assets/adaptic-logo-light.svg +17 -0
  101. package/scaffold/CLAUDE.md +21 -0
  102. package/scaffold/config/agent.ts +69 -0
  103. package/scaffold/config/agent.ts.example +218 -0
  104. package/schedules/README.md +49 -0
  105. package/schedules/triggers/backlog-executor.md +102 -0
  106. package/schedules/triggers/daily-evening-wrap.md +142 -0
  107. package/schedules/triggers/daily-midday-sweep.md +58 -0
  108. package/schedules/triggers/daily-morning-brief.md +55 -0
  109. package/schedules/triggers/inbox-processor.md +115 -0
  110. package/schedules/triggers/meeting-action-capture.md +60 -0
  111. package/schedules/triggers/meeting-prep.md +69 -0
  112. package/schedules/triggers/quarterly-self-assessment.md +54 -0
  113. package/schedules/triggers/weekly-engineering-health.md +37 -0
  114. package/schedules/triggers/weekly-execution.md +67 -0
  115. package/schedules/triggers/weekly-hiring.md +53 -0
  116. package/schedules/triggers/weekly-priorities.md +38 -0
  117. package/schedules/triggers/weekly-strategic-memo.md +124 -0
  118. package/scripts/__pycache__/disclosure_assessment.cpython-313.pyc +0 -0
  119. package/scripts/__pycache__/disclosure_boundaries.cpython-313.pyc +0 -0
  120. package/scripts/__pycache__/email_quote_thread.cpython-313.pyc +0 -0
  121. package/scripts/__pycache__/email_thread_dedup.cpython-313.pyc +0 -0
  122. package/scripts/__pycache__/mehran-inbox-poller.cpython-313.pyc +0 -0
  123. package/scripts/__pycache__/outbound_dedup.cpython-313.pyc +0 -0
  124. package/scripts/__pycache__/pre-draft-context.cpython-313.pyc +0 -0
  125. package/scripts/__pycache__/pre_draft_lookup.cpython-313.pyc +0 -0
  126. package/scripts/__pycache__/send-email-as-mehran.cpython-313.pyc +0 -0
  127. package/scripts/__pycache__/send-email-threaded.cpython-313.pyc +0 -0
  128. package/scripts/__pycache__/send-email-with-attachment.cpython-313.pyc +0 -0
  129. package/scripts/__pycache__/validate_outbound.cpython-313.pyc +0 -0
  130. package/scripts/airtable-crm-populate.md +99 -0
  131. package/scripts/archive-email.sh +41 -0
  132. package/scripts/comms-monitor.sh +285 -0
  133. package/scripts/configure-whatsapp-sandbox.sh +201 -0
  134. package/scripts/continuous-monitor.sh +86 -0
  135. package/scripts/daemon/classifier.mjs +355 -0
  136. package/scripts/daemon/context-compiler.mjs +490 -0
  137. package/scripts/daemon/dispatcher.mjs +385 -0
  138. package/scripts/daemon/health.mjs +72 -0
  139. package/scripts/daemon/prompt-builder.mjs +426 -0
  140. package/scripts/daemon/responder.mjs +547 -0
  141. package/scripts/daemon/session-lock.mjs +520 -0
  142. package/scripts/daemon/sophie-daemon.mjs +521 -0
  143. package/scripts/daemon/test-context-compiler.mjs +238 -0
  144. package/scripts/daemon/test-integration.mjs +130 -0
  145. package/scripts/daemon/test-session-lock.mjs +215 -0
  146. package/scripts/disclosure_assessment.py +873 -0
  147. package/scripts/disclosure_boundaries.py +562 -0
  148. package/scripts/email-signature-mehran.html +52 -0
  149. package/scripts/email-signature.html +44 -0
  150. package/scripts/email_quote_thread.py +167 -0
  151. package/scripts/email_thread_dedup.py +361 -0
  152. package/scripts/emergency-stop.sh +41 -0
  153. package/scripts/healthcheck.sh +110 -0
  154. package/scripts/hooks/block-mcp-slack-send.sh +7 -0
  155. package/scripts/hooks/post-action-log.sh +17 -0
  156. package/scripts/hooks/pre-send-audit.sh +57 -0
  157. package/scripts/hooks/session-end-log.sh +27 -0
  158. package/scripts/huddle/audio-bridge.mjs +664 -0
  159. package/scripts/huddle/boot-slack-cdp.sh +102 -0
  160. package/scripts/huddle/huddle-controller.mjs +942 -0
  161. package/scripts/huddle/huddle-server.mjs +1059 -0
  162. package/scripts/huddle/launch-slack.sh +232 -0
  163. package/scripts/huddle/node_modules/.package-lock.json +50 -0
  164. package/scripts/huddle/node_modules/@anthropic-ai/sdk/CHANGELOG.md +1677 -0
  165. package/scripts/huddle/node_modules/@anthropic-ai/sdk/LICENSE +8 -0
  166. package/scripts/huddle/node_modules/@anthropic-ai/sdk/README.md +674 -0
  167. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.mts +3 -0
  168. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.mts.map +1 -0
  169. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.ts +3 -0
  170. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.ts.map +1 -0
  171. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.js +226 -0
  172. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.js.map +1 -0
  173. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs +223 -0
  174. package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs.map +1 -0
  175. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.mts +2 -0
  176. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.mts.map +1 -0
  177. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.ts +2 -0
  178. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.ts.map +1 -0
  179. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.js +6 -0
  180. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.js.map +1 -0
  181. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.mjs +2 -0
  182. package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.mjs.map +1 -0
  183. package/scripts/huddle/node_modules/@anthropic-ai/sdk/bin/cli +53 -0
  184. package/scripts/huddle/node_modules/@anthropic-ai/sdk/bin/migration-config.json +7 -0
  185. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.mts +225 -0
  186. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.mts.map +1 -0
  187. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.ts +225 -0
  188. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.ts.map +1 -0
  189. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.js +536 -0
  190. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.js.map +1 -0
  191. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.mjs +531 -0
  192. package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.mjs.map +1 -0
  193. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts +49 -0
  194. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts.map +1 -0
  195. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts +49 -0
  196. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts.map +1 -0
  197. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.js +76 -0
  198. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.js.map +1 -0
  199. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.mjs +72 -0
  200. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.mjs.map +1 -0
  201. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.mts +47 -0
  202. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.mts.map +1 -0
  203. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.ts +47 -0
  204. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.ts.map +1 -0
  205. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.js +114 -0
  206. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.js.map +1 -0
  207. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.mjs +98 -0
  208. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.mjs.map +1 -0
  209. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.mts +63 -0
  210. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.mts.map +1 -0
  211. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.ts +63 -0
  212. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.ts.map +1 -0
  213. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.js +123 -0
  214. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.js.map +1 -0
  215. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.mjs +117 -0
  216. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.mjs.map +1 -0
  217. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.mts +6 -0
  218. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.mts.map +1 -0
  219. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.ts +6 -0
  220. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.ts.map +1 -0
  221. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.js +11 -0
  222. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.js.map +1 -0
  223. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.mjs +7 -0
  224. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.mjs.map +1 -0
  225. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.mts +31 -0
  226. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.mts.map +1 -0
  227. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.ts +31 -0
  228. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.ts.map +1 -0
  229. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.js +282 -0
  230. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.js.map +1 -0
  231. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.mjs +277 -0
  232. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.mjs.map +1 -0
  233. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.mts +3 -0
  234. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.mts.map +1 -0
  235. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.ts +3 -0
  236. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.ts.map +1 -0
  237. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.js +6 -0
  238. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.js.map +1 -0
  239. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.mjs +2 -0
  240. package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.mjs.map +1 -0
  241. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.mts +2 -0
  242. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.mts.map +1 -0
  243. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.ts +2 -0
  244. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.ts.map +1 -0
  245. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.js +6 -0
  246. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.js.map +1 -0
  247. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.mjs +2 -0
  248. package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.mjs.map +1 -0
  249. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.mts +7 -0
  250. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.mts.map +1 -0
  251. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.ts +7 -0
  252. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.ts.map +1 -0
  253. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.js +35 -0
  254. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.js.map +1 -0
  255. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.mjs +8 -0
  256. package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.mjs.map +1 -0
  257. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts +73 -0
  258. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts.map +1 -0
  259. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.ts +73 -0
  260. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.ts.map +1 -0
  261. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.js +4 -0
  262. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.js.map +1 -0
  263. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.mjs +3 -0
  264. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.mjs.map +1 -0
  265. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.mts +5 -0
  266. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.mts.map +1 -0
  267. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.ts +5 -0
  268. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.ts.map +1 -0
  269. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.js +15 -0
  270. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.js.map +1 -0
  271. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.mjs +12 -0
  272. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.mjs.map +1 -0
  273. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts +10 -0
  274. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts.map +1 -0
  275. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.ts +10 -0
  276. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.ts.map +1 -0
  277. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.js +39 -0
  278. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.js.map +1 -0
  279. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.mjs +35 -0
  280. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.mjs.map +1 -0
  281. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts +17 -0
  282. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts.map +1 -0
  283. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.ts +17 -0
  284. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.ts.map +1 -0
  285. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.js +113 -0
  286. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.js.map +1 -0
  287. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.mjs +108 -0
  288. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.mjs.map +1 -0
  289. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.mts +15 -0
  290. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.mts.map +1 -0
  291. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.ts +15 -0
  292. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.ts.map +1 -0
  293. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.js +162 -0
  294. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.js.map +1 -0
  295. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs +157 -0
  296. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs.map +1 -0
  297. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.mts +3 -0
  298. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.mts.map +1 -0
  299. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.ts +3 -0
  300. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.ts.map +1 -0
  301. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.js +41 -0
  302. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.js.map +1 -0
  303. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.mjs +36 -0
  304. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.mjs.map +1 -0
  305. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.mts +22 -0
  306. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.mts.map +1 -0
  307. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.ts +22 -0
  308. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.ts.map +1 -0
  309. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.js +79 -0
  310. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.js.map +1 -0
  311. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.mjs +74 -0
  312. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.mjs.map +1 -0
  313. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.mts +17 -0
  314. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.mts.map +1 -0
  315. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.ts +17 -0
  316. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.ts.map +1 -0
  317. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.js +55 -0
  318. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.js.map +1 -0
  319. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.mjs +51 -0
  320. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.mjs.map +1 -0
  321. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts +34 -0
  322. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts.map +1 -0
  323. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.ts +34 -0
  324. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.ts.map +1 -0
  325. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.js +14 -0
  326. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.js.map +1 -0
  327. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.mjs +10 -0
  328. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.mjs.map +1 -0
  329. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shim-types.d.mts +28 -0
  330. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shim-types.d.ts +28 -0
  331. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.mts +20 -0
  332. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.mts.map +1 -0
  333. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.ts +20 -0
  334. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.ts.map +1 -0
  335. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.js +92 -0
  336. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.js.map +1 -0
  337. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.mjs +85 -0
  338. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.mjs.map +1 -0
  339. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.mts +8 -0
  340. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.mts.map +1 -0
  341. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.ts +8 -0
  342. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.ts.map +1 -0
  343. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.js +38 -0
  344. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.js.map +1 -0
  345. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.mjs +35 -0
  346. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.mjs.map +1 -0
  347. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts +45 -0
  348. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts.map +1 -0
  349. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.ts +45 -0
  350. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.ts.map +1 -0
  351. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.js +96 -0
  352. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.js.map +1 -0
  353. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.mjs +93 -0
  354. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.mjs.map +1 -0
  355. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/tslib.js +81 -0
  356. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/tslib.mjs +17 -0
  357. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.mts +67 -0
  358. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.mts.map +1 -0
  359. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.ts +67 -0
  360. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.ts.map +1 -0
  361. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.js +4 -0
  362. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.js.map +1 -0
  363. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.mjs +3 -0
  364. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.mjs.map +1 -0
  365. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts +42 -0
  366. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts.map +1 -0
  367. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.ts +42 -0
  368. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.ts.map +1 -0
  369. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.js +146 -0
  370. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.js.map +1 -0
  371. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.mjs +136 -0
  372. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.mjs.map +1 -0
  373. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.mts +3 -0
  374. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.mts.map +1 -0
  375. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.ts +3 -0
  376. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.ts.map +1 -0
  377. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.js +38 -0
  378. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.js.map +1 -0
  379. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.mjs +33 -0
  380. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.mjs.map +1 -0
  381. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.mts +4 -0
  382. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.mts.map +1 -0
  383. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.ts +4 -0
  384. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.ts.map +1 -0
  385. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.js +31 -0
  386. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.js.map +1 -0
  387. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.mjs +26 -0
  388. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.mjs.map +1 -0
  389. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.mts +9 -0
  390. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.mts.map +1 -0
  391. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.ts +9 -0
  392. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.ts.map +1 -0
  393. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.js +22 -0
  394. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.js.map +1 -0
  395. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.mjs +18 -0
  396. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.mjs.map +1 -0
  397. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts +37 -0
  398. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts.map +1 -0
  399. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.ts +37 -0
  400. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.ts.map +1 -0
  401. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.js +86 -0
  402. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.js.map +1 -0
  403. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.mjs +80 -0
  404. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.mjs.map +1 -0
  405. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.mts +15 -0
  406. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.mts.map +1 -0
  407. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.ts +15 -0
  408. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.ts.map +1 -0
  409. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.js +58 -0
  410. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.js.map +1 -0
  411. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.mjs +53 -0
  412. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.mjs.map +1 -0
  413. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.mts +2 -0
  414. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.mts.map +1 -0
  415. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.ts +2 -0
  416. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.ts.map +1 -0
  417. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.js +7 -0
  418. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.js.map +1 -0
  419. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs +3 -0
  420. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs.map +1 -0
  421. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.mts +5 -0
  422. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.mts.map +1 -0
  423. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.ts +5 -0
  424. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.ts.map +1 -0
  425. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.js +19 -0
  426. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.js.map +1 -0
  427. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs +15 -0
  428. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs.map +1 -0
  429. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.mts +16 -0
  430. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.mts.map +1 -0
  431. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.ts +16 -0
  432. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.ts.map +1 -0
  433. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.js +109 -0
  434. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.js.map +1 -0
  435. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.mjs +92 -0
  436. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.mjs.map +1 -0
  437. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.mts +7 -0
  438. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.mts.map +1 -0
  439. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.ts +7 -0
  440. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.ts.map +1 -0
  441. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.js +11 -0
  442. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.js.map +1 -0
  443. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.mjs +8 -0
  444. package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.mjs.map +1 -0
  445. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.mts +114 -0
  446. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.mts.map +1 -0
  447. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.ts +114 -0
  448. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.ts.map +1 -0
  449. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.js +553 -0
  450. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.js.map +1 -0
  451. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.mjs +549 -0
  452. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.mjs.map +1 -0
  453. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.mts +114 -0
  454. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.mts.map +1 -0
  455. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts +114 -0
  456. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts.map +1 -0
  457. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.js +553 -0
  458. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.js.map +1 -0
  459. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.mjs +549 -0
  460. package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.mjs.map +1 -0
  461. package/scripts/huddle/node_modules/@anthropic-ai/sdk/package.json +185 -0
  462. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.mts +2 -0
  463. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.mts.map +1 -0
  464. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.ts +2 -0
  465. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.ts.map +1 -0
  466. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.js +6 -0
  467. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.js.map +1 -0
  468. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.mjs +2 -0
  469. package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.mjs.map +1 -0
  470. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.mts +2 -0
  471. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.mts.map +1 -0
  472. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.ts +2 -0
  473. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.ts.map +1 -0
  474. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.js +6 -0
  475. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.js.map +1 -0
  476. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.mjs +2 -0
  477. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.mjs.map +1 -0
  478. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts +61 -0
  479. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts.map +1 -0
  480. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts +61 -0
  481. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts.map +1 -0
  482. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +25 -0
  483. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.js.map +1 -0
  484. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +20 -0
  485. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs.map +1 -0
  486. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts +151 -0
  487. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts.map +1 -0
  488. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.ts +151 -0
  489. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.ts.map +1 -0
  490. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.js +122 -0
  491. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.js.map +1 -0
  492. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +118 -0
  493. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs.map +1 -0
  494. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts +5 -0
  495. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts.map +1 -0
  496. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.ts +5 -0
  497. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.ts.map +1 -0
  498. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.js +13 -0
  499. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.js.map +1 -0
  500. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +6 -0
  501. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs.map +1 -0
  502. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts +343 -0
  503. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts.map +1 -0
  504. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.ts +343 -0
  505. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.ts.map +1 -0
  506. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.js +204 -0
  507. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.js.map +1 -0
  508. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.mjs +200 -0
  509. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.mjs.map +1 -0
  510. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts +3 -0
  511. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts.map +1 -0
  512. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.ts +3 -0
  513. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.ts.map +1 -0
  514. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.js +9 -0
  515. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.js.map +1 -0
  516. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.mjs +4 -0
  517. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.mjs.map +1 -0
  518. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts +1561 -0
  519. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts.map +1 -0
  520. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.ts +1561 -0
  521. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.ts.map +1 -0
  522. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.js +86 -0
  523. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.js.map +1 -0
  524. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.mjs +81 -0
  525. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.mjs.map +1 -0
  526. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.mts +2 -0
  527. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.mts.map +1 -0
  528. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.ts +2 -0
  529. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.ts.map +1 -0
  530. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.js +6 -0
  531. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.js.map +1 -0
  532. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.mjs +3 -0
  533. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.mjs.map +1 -0
  534. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts +74 -0
  535. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts.map +1 -0
  536. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.ts +74 -0
  537. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.ts.map +1 -0
  538. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.js +60 -0
  539. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.js.map +1 -0
  540. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.mjs +56 -0
  541. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.mjs.map +1 -0
  542. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.mts +2 -0
  543. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.mts.map +1 -0
  544. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.ts +2 -0
  545. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.ts.map +1 -0
  546. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.js +6 -0
  547. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.js.map +1 -0
  548. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.mjs +3 -0
  549. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.mjs.map +1 -0
  550. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.mts +183 -0
  551. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.mts.map +1 -0
  552. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.ts +183 -0
  553. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.ts.map +1 -0
  554. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.js +23 -0
  555. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.js.map +1 -0
  556. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.mjs +19 -0
  557. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.mjs.map +1 -0
  558. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.mts +6 -0
  559. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.mts.map +1 -0
  560. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.ts +6 -0
  561. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.ts.map +1 -0
  562. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.js +15 -0
  563. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.js.map +1 -0
  564. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.mjs +7 -0
  565. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.mjs.map +1 -0
  566. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts +304 -0
  567. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts.map +1 -0
  568. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.ts +304 -0
  569. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.ts.map +1 -0
  570. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.js +153 -0
  571. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.js.map +1 -0
  572. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.mjs +149 -0
  573. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.mjs.map +1 -0
  574. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts +3 -0
  575. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts.map +1 -0
  576. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.ts +3 -0
  577. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.ts.map +1 -0
  578. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.js +9 -0
  579. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.js.map +1 -0
  580. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.mjs +4 -0
  581. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.mjs.map +1 -0
  582. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts +1264 -0
  583. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts.map +1 -0
  584. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts +1264 -0
  585. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts.map +1 -0
  586. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.js +72 -0
  587. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.js.map +1 -0
  588. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.mjs +67 -0
  589. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.mjs.map +1 -0
  590. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.mts +2 -0
  591. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.mts.map +1 -0
  592. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.ts +2 -0
  593. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.ts.map +1 -0
  594. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.js +6 -0
  595. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.js.map +1 -0
  596. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.mjs +3 -0
  597. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.mjs.map +1 -0
  598. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.mts +59 -0
  599. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.mts.map +1 -0
  600. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.ts +59 -0
  601. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.ts.map +1 -0
  602. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.js +45 -0
  603. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.js.map +1 -0
  604. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.mjs +41 -0
  605. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.mjs.map +1 -0
  606. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.mts +42 -0
  607. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.mts.map +1 -0
  608. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.ts +42 -0
  609. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.ts.map +1 -0
  610. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.js +4 -0
  611. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.js.map +1 -0
  612. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.mjs +3 -0
  613. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.mjs.map +1 -0
  614. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.mts +2 -0
  615. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.mts.map +1 -0
  616. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.ts +2 -0
  617. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.ts.map +1 -0
  618. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.js +4 -0
  619. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.js.map +1 -0
  620. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.mjs +3 -0
  621. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.mjs.map +1 -0
  622. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.mts +2 -0
  623. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.mts.map +1 -0
  624. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.ts +2 -0
  625. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.ts.map +1 -0
  626. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.js +5 -0
  627. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.js.map +1 -0
  628. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.mjs +2 -0
  629. package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.mjs.map +1 -0
  630. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/_vendor/partial-json-parser/README.md +3 -0
  631. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/_vendor/partial-json-parser/parser.ts +264 -0
  632. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/api-promise.ts +2 -0
  633. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/client.ts +1070 -0
  634. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/README.md +3 -0
  635. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/api-promise.ts +101 -0
  636. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/error.ts +133 -0
  637. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/pagination.ts +201 -0
  638. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/resource.ts +11 -0
  639. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/streaming.ts +331 -0
  640. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/uploads.ts +2 -0
  641. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/error.ts +2 -0
  642. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/index.ts +23 -0
  643. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/README.md +3 -0
  644. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/builtin-types.ts +93 -0
  645. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/constants.ts +12 -0
  646. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/decoders/jsonl.ts +48 -0
  647. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/decoders/line.ts +135 -0
  648. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/detect-platform.ts +196 -0
  649. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/errors.ts +33 -0
  650. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/headers.ts +99 -0
  651. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/parse.ts +84 -0
  652. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/request-options.ts +39 -0
  653. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/shim-types.d.ts +28 -0
  654. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/shims.ts +107 -0
  655. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/stream-utils.ts +32 -0
  656. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/to-file.ts +159 -0
  657. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/types.ts +92 -0
  658. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/uploads.ts +193 -0
  659. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/base64.ts +40 -0
  660. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/bytes.ts +32 -0
  661. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/env.ts +18 -0
  662. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/log.ts +127 -0
  663. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/path.ts +65 -0
  664. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/sleep.ts +3 -0
  665. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/uuid.ts +17 -0
  666. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/values.ts +102 -0
  667. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils.ts +8 -0
  668. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/.keep +4 -0
  669. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/BetaMessageStream.ts +683 -0
  670. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/MessageStream.ts +684 -0
  671. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/pagination.ts +2 -0
  672. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resource.ts +2 -0
  673. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/beta.ts +380 -0
  674. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/files.ts +258 -0
  675. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/index.ts +148 -0
  676. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/batches.ts +502 -0
  677. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/index.ts +135 -0
  678. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/messages.ts +2249 -0
  679. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages.ts +3 -0
  680. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/models.ts +118 -0
  681. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta.ts +3 -0
  682. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/completions.ts +231 -0
  683. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/index.ts +121 -0
  684. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/batches.ts +396 -0
  685. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/index.ts +110 -0
  686. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/messages.ts +1783 -0
  687. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages.ts +3 -0
  688. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/models.ts +103 -0
  689. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/shared.ts +72 -0
  690. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/top-level.ts +3 -0
  691. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources.ts +1 -0
  692. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/streaming.ts +2 -0
  693. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/tsconfig.json +11 -0
  694. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/uploads.ts +2 -0
  695. package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/version.ts +1 -0
  696. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.mts +2 -0
  697. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.mts.map +1 -0
  698. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.ts +2 -0
  699. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.ts.map +1 -0
  700. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.js +6 -0
  701. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.js.map +1 -0
  702. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.mjs +2 -0
  703. package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.mjs.map +1 -0
  704. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.mts +2 -0
  705. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.mts.map +1 -0
  706. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.ts +2 -0
  707. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.ts.map +1 -0
  708. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.js +6 -0
  709. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.js.map +1 -0
  710. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.mjs +2 -0
  711. package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.mjs.map +1 -0
  712. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.mts +2 -0
  713. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.mts.map +1 -0
  714. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.ts +2 -0
  715. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.ts.map +1 -0
  716. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.js +5 -0
  717. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.js.map +1 -0
  718. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.mjs +2 -0
  719. package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.mjs.map +1 -0
  720. package/scripts/huddle/node_modules/dotenv/CHANGELOG.md +520 -0
  721. package/scripts/huddle/node_modules/dotenv/LICENSE +23 -0
  722. package/scripts/huddle/node_modules/dotenv/README-es.md +411 -0
  723. package/scripts/huddle/node_modules/dotenv/README.md +645 -0
  724. package/scripts/huddle/node_modules/dotenv/SECURITY.md +1 -0
  725. package/scripts/huddle/node_modules/dotenv/config.d.ts +1 -0
  726. package/scripts/huddle/node_modules/dotenv/config.js +9 -0
  727. package/scripts/huddle/node_modules/dotenv/lib/cli-options.js +17 -0
  728. package/scripts/huddle/node_modules/dotenv/lib/env-options.js +28 -0
  729. package/scripts/huddle/node_modules/dotenv/lib/main.d.ts +162 -0
  730. package/scripts/huddle/node_modules/dotenv/lib/main.js +386 -0
  731. package/scripts/huddle/node_modules/dotenv/package.json +62 -0
  732. package/scripts/huddle/node_modules/ws/LICENSE +20 -0
  733. package/scripts/huddle/node_modules/ws/README.md +548 -0
  734. package/scripts/huddle/node_modules/ws/browser.js +8 -0
  735. package/scripts/huddle/node_modules/ws/index.js +22 -0
  736. package/scripts/huddle/node_modules/ws/lib/buffer-util.js +131 -0
  737. package/scripts/huddle/node_modules/ws/lib/constants.js +19 -0
  738. package/scripts/huddle/node_modules/ws/lib/event-target.js +292 -0
  739. package/scripts/huddle/node_modules/ws/lib/extension.js +203 -0
  740. package/scripts/huddle/node_modules/ws/lib/limiter.js +55 -0
  741. package/scripts/huddle/node_modules/ws/lib/permessage-deflate.js +528 -0
  742. package/scripts/huddle/node_modules/ws/lib/receiver.js +706 -0
  743. package/scripts/huddle/node_modules/ws/lib/sender.js +602 -0
  744. package/scripts/huddle/node_modules/ws/lib/stream.js +161 -0
  745. package/scripts/huddle/node_modules/ws/lib/subprotocol.js +62 -0
  746. package/scripts/huddle/node_modules/ws/lib/validation.js +152 -0
  747. package/scripts/huddle/node_modules/ws/lib/websocket-server.js +554 -0
  748. package/scripts/huddle/node_modules/ws/lib/websocket.js +1393 -0
  749. package/scripts/huddle/node_modules/ws/package.json +70 -0
  750. package/scripts/huddle/node_modules/ws/wrapper.mjs +21 -0
  751. package/scripts/huddle/package-lock.json +62 -0
  752. package/scripts/huddle/package.json +22 -0
  753. package/scripts/huddle/setup-audio.sh +239 -0
  754. package/scripts/huddle/start-call.mjs +318 -0
  755. package/scripts/huddle/test-pipeline.mjs +263 -0
  756. package/scripts/llm_email_dedup.py +434 -0
  757. package/scripts/local-triggers/install-all.sh +43 -0
  758. package/scripts/local-triggers/plists/ai.adaptic.slack-events-server.plist +45 -0
  759. package/scripts/local-triggers/plists/ai.adaptic.sophie-backlog-executor.plist +21 -0
  760. package/scripts/local-triggers/plists/ai.adaptic.sophie-daemon.plist +32 -0
  761. package/scripts/local-triggers/plists/ai.adaptic.sophie-inbox-processor.plist +21 -0
  762. package/scripts/local-triggers/plists/ai.adaptic.sophie-meeting-action-capture.plist +21 -0
  763. package/scripts/local-triggers/plists/ai.adaptic.sophie-meeting-prep.plist +21 -0
  764. package/scripts/local-triggers/plists/ai.adaptic.sophie-midday-sweep.plist +26 -0
  765. package/scripts/local-triggers/plists/ai.adaptic.sophie-quarterly-self-assessment.plist +62 -0
  766. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-engineering-health.plist +28 -0
  767. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-execution.plist +28 -0
  768. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-hiring.plist +28 -0
  769. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-priorities.plist +28 -0
  770. package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-strategic-memo.plist +28 -0
  771. package/scripts/local-triggers/run-trigger.sh +44 -0
  772. package/scripts/media-generation/README.md +103 -0
  773. package/scripts/media-generation/gemini-image-client.mjs +173 -0
  774. package/scripts/media-generation/generate-assets.mjs +289 -0
  775. package/scripts/media-generation/veo-video-client.mjs +219 -0
  776. package/scripts/mehran-inbox-poller.py +437 -0
  777. package/scripts/outbound-dedup-cleanup.sh +43 -0
  778. package/scripts/outbound-dedup.sh +476 -0
  779. package/scripts/outbound_dedup.py +115 -0
  780. package/scripts/parse-voice-transcript.mjs +486 -0
  781. package/scripts/pdf-generation/README.md +61 -0
  782. package/scripts/pdf-generation/build-document.mjs +230 -0
  783. package/scripts/pdf-generation/templates/board-pack.latex +136 -0
  784. package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
  785. package/scripts/pdf-generation/templates/memo.latex +114 -0
  786. package/scripts/poll-slack-events.sh +33 -0
  787. package/scripts/poller/calendar-poller.mjs +12 -0
  788. package/scripts/poller/gmail-poller.mjs +156 -0
  789. package/scripts/poller/imap-client.mjs +286 -0
  790. package/scripts/poller/index.mjs +73 -0
  791. package/scripts/poller/intra-session-check.mjs +267 -0
  792. package/scripts/poller/mehran-gmail-poller.mjs +98 -0
  793. package/scripts/poller/slack-poller.mjs +716 -0
  794. package/scripts/poller/trigger.mjs +47 -0
  795. package/scripts/poller/utils.mjs +253 -0
  796. package/scripts/poller-launchd/ai.adaptic.sophie-poller.plist +40 -0
  797. package/scripts/poller-launchd/ai.adaptic.sophie-whatsapp-handler.plist +39 -0
  798. package/scripts/poller-launchd/install.sh +38 -0
  799. package/scripts/post-interaction-indexer.py +1598 -0
  800. package/scripts/pre-draft-context.py +994 -0
  801. package/scripts/pre_draft_lookup.py +258 -0
  802. package/scripts/rag-indexer.py +629 -0
  803. package/scripts/resume-operations.sh +40 -0
  804. package/scripts/search-mehran-inbox.py +181 -0
  805. package/scripts/self-optimization/compute-metrics.py +377 -0
  806. package/scripts/send-difc-rfp.sh +98 -0
  807. package/scripts/send-email-as-mehran.py +369 -0
  808. package/scripts/send-email-threaded.py +336 -0
  809. package/scripts/send-email-with-attachment.py +360 -0
  810. package/scripts/send-email.sh +93 -0
  811. package/scripts/send-sms.sh +151 -0
  812. package/scripts/send-whatsapp.sh +261 -0
  813. package/scripts/session-start.sh +106 -0
  814. package/scripts/setup/configure-macos.sh +508 -0
  815. package/scripts/setup/init-agent.sh +450 -0
  816. package/scripts/slack-events-ctl.sh +177 -0
  817. package/scripts/slack-events-server.mjs +989 -0
  818. package/scripts/slack-react.mjs +89 -0
  819. package/scripts/slack-responded.sh +185 -0
  820. package/scripts/slack-send.sh +190 -0
  821. package/scripts/slack-typing.mjs +196 -0
  822. package/scripts/slack-upload-v2.py +95 -0
  823. package/scripts/sms-handler.mjs +436 -0
  824. package/scripts/sophie-inbox-poller.py +406 -0
  825. package/scripts/spawn-session.sh +85 -0
  826. package/scripts/system-verify.sh +171 -0
  827. package/scripts/test-email-thread-dedup.py +239 -0
  828. package/scripts/test-information-barriers.py +484 -0
  829. package/scripts/test-llm-email-dedup.py +251 -0
  830. package/scripts/test-pre-draft-integration.py +203 -0
  831. package/scripts/test-rag-phase2.sh +442 -0
  832. package/scripts/test-rag-search.sh +251 -0
  833. package/scripts/test-voice-parser.mjs +316 -0
  834. package/scripts/user-context-search.py +660 -0
  835. package/scripts/validate-outbound.py +1493 -0
  836. package/scripts/whatsapp-handler.mjs +525 -0
  837. package/teams/desktop-operations.yaml +34 -0
  838. package/teams/executive-office.yaml +27 -0
  839. package/teams/legal-and-regulatory.yaml +24 -0
  840. package/teams/platform-and-engineering.yaml +23 -0
  841. package/teams/strategy-and-growth.yaml +29 -0
  842. package/workflows/continuous/inbound-monitor.yaml +168 -0
  843. package/workflows/daily/applicant-triage.yaml +197 -0
  844. package/workflows/daily/comms-triage.yaml +80 -0
  845. package/workflows/daily/evening-wrap.yaml +65 -0
  846. package/workflows/daily/morning-brief.yaml +147 -0
  847. package/workflows/daily/slack-followup-sweep.yaml +87 -0
  848. package/workflows/event-driven/README.md +50 -0
  849. package/workflows/monthly/board-readiness.yaml +76 -0
  850. package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
  851. package/workflows/session-protocol.md +171 -0
  852. package/workflows/weekly/hiring-review.yaml +169 -0
  853. package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
  854. package/workflows/weekly/strategic-memo.yaml +79 -0
@@ -0,0 +1,1598 @@
1
+ #!/usr/bin/env python3
2
+ """Post-interaction auto-indexer — Memory Enhancement Phase 5.
3
+
4
+ Automatically extracts entities, relationships, and facts from interactions
5
+ and updates memory/indexes/entity-relationships.yaml.
6
+
7
+ Integration points — call this script:
8
+ - After the inbox processor processes each inbox item
9
+ - After the backlog executor completes interaction-heavy tasks
10
+ - During session-end hook as part of session wrap-up
11
+ - Manually for backfill / one-off extraction
12
+
13
+ Usage:
14
+ # Process a specific inbox item (JSON or YAML)
15
+ python3 scripts/post-interaction-indexer.py --input state/inbox/slack/1775230363.978999-dm.json
16
+
17
+ # Process all unprocessed interactions from today
18
+ python3 scripts/post-interaction-indexer.py --scan-today
19
+
20
+ # Dry run — show extractions without writing
21
+ python3 scripts/post-interaction-indexer.py --input <file> --dry-run
22
+
23
+ # Process from stdin (piped JSON)
24
+ echo '{"event_type":"email","email":{"from":"Jane <jane@co.com>"}}' | python3 scripts/post-interaction-indexer.py --stdin
25
+
26
+ Output:
27
+ Logs extracted facts and index updates to stdout.
28
+
29
+ Exit codes:
30
+ 0 = success (updates made or would be made in dry-run)
31
+ 1 = no new facts extracted
32
+ 2 = error
33
+
34
+ Performance target: < 2 seconds per interaction.
35
+
36
+ Created: 2026-04-04 — Memory Enhancement Phase 5
37
+ """
38
+ import argparse
39
+ import json
40
+ import re
41
+ import sys
42
+ import time
43
+ from datetime import datetime, timezone
44
+ from pathlib import Path
45
+
46
+ # ─────────────────────────────────────────────
47
+ # Path resolution
48
+ # ─────────────────────────────────────────────
49
+ SCRIPT_DIR = Path(__file__).resolve().parent
50
+ REPO_ROOT = SCRIPT_DIR.parent
51
+
52
+ ENTITY_INDEX_PATH = REPO_ROOT / "memory" / "indexes" / "entity-relationships.yaml"
53
+ ENTITY_INDEX_CACHE_PATH = REPO_ROOT / "memory" / "indexes" / ".entity-relationships.json"
54
+ INBOX_DIR = REPO_ROOT / "state" / "inbox"
55
+ PROCESSED_DIR = INBOX_DIR / "processed"
56
+
57
+ # ─────────────────────────────────────────────
58
+ # YAML handling — import PyYAML if available
59
+ # ─────────────────────────────────────────────
60
+ _yaml_mod = None
61
+ _yaml_loader = None
62
+ try:
63
+ import yaml as _yaml_mod
64
+ try:
65
+ _yaml_loader = _yaml_mod.CSafeLoader # C extension — 10x faster
66
+ except AttributeError:
67
+ _yaml_loader = None
68
+ except ImportError:
69
+ pass
70
+
71
+
72
+ def _load_yaml(text):
73
+ """Parse YAML text using PyYAML (required for this script)."""
74
+ if _yaml_mod is None:
75
+ raise RuntimeError("PyYAML is required. Install with: pip3 install pyyaml")
76
+ if _yaml_loader is not None:
77
+ return _yaml_mod.load(text, Loader=_yaml_loader) or {}
78
+ return _yaml_mod.safe_load(text) or {}
79
+
80
+
81
+ def _dump_yaml(data):
82
+ """Dump data to YAML string, preserving readability."""
83
+ if _yaml_mod is None:
84
+ raise RuntimeError("PyYAML is required.")
85
+ return _yaml_mod.dump(
86
+ data,
87
+ default_flow_style=False,
88
+ allow_unicode=True,
89
+ sort_keys=False,
90
+ width=120,
91
+ )
92
+
93
+
94
+ # ─────────────────────────────────────────────
95
+ # Entity index loading (with JSON cache for speed)
96
+ # ─────────────────────────────────────────────
97
+ def load_entity_index():
98
+ """Load and return the full entity-relationships.yaml as a dict.
99
+
100
+ Uses a JSON cache (same as pre-draft-context.py) when available,
101
+ falling back to YAML parsing. The JSON cache is 10-50x faster to load.
102
+
103
+ Returns dict with 'entities' (list) and 'relationships' (list).
104
+ """
105
+ if not ENTITY_INDEX_PATH.exists():
106
+ return {
107
+ 'schema_version': 1,
108
+ 'last_updated': datetime.now(timezone.utc).isoformat(),
109
+ 'last_updated_by': 'post-interaction-indexer',
110
+ 'entities': [],
111
+ 'relationships': [],
112
+ }
113
+
114
+ # Try JSON cache first (much faster than YAML parsing)
115
+ if ENTITY_INDEX_CACHE_PATH.exists():
116
+ yaml_mtime = ENTITY_INDEX_PATH.stat().st_mtime
117
+ json_mtime = ENTITY_INDEX_CACHE_PATH.stat().st_mtime
118
+ if json_mtime >= yaml_mtime:
119
+ try:
120
+ data = json.loads(ENTITY_INDEX_CACHE_PATH.read_text(encoding='utf-8'))
121
+ if isinstance(data, dict):
122
+ if 'entities' not in data or data['entities'] is None:
123
+ data['entities'] = []
124
+ if 'relationships' not in data or data['relationships'] is None:
125
+ data['relationships'] = []
126
+ return data
127
+ except Exception:
128
+ pass # Fall through to YAML
129
+
130
+ # Parse YAML (slow path)
131
+ text = ENTITY_INDEX_PATH.read_text(encoding='utf-8')
132
+ data = _load_yaml(text)
133
+ if not isinstance(data, dict):
134
+ data = {'entities': [], 'relationships': []}
135
+
136
+ if 'entities' not in data or data['entities'] is None:
137
+ data['entities'] = []
138
+ if 'relationships' not in data or data['relationships'] is None:
139
+ data['relationships'] = []
140
+
141
+ # Write JSON cache for next time
142
+ try:
143
+ ENTITY_INDEX_CACHE_PATH.write_text(
144
+ json.dumps(data, default=str, ensure_ascii=False),
145
+ encoding='utf-8',
146
+ )
147
+ except Exception:
148
+ pass
149
+
150
+ return data
151
+
152
+
153
+ def build_lookups(data):
154
+ """Build fast lookup dicts from entity index data.
155
+
156
+ Returns dict with:
157
+ by_name: lowercase name/alias -> entity dict
158
+ by_email: lowercase email -> entity dict
159
+ by_id: entity id -> entity dict
160
+ rel_ids: set of relationship ids
161
+ """
162
+ by_name = {}
163
+ by_email = {}
164
+ by_id = {}
165
+
166
+ for entity in data.get('entities', []):
167
+ if not isinstance(entity, dict):
168
+ continue
169
+
170
+ eid = entity.get('id', '')
171
+ if eid:
172
+ by_id[eid] = entity
173
+
174
+ name = entity.get('name', '')
175
+ if name and isinstance(name, str):
176
+ by_name[name.lower()] = entity
177
+ # Also index first-last shorthand
178
+ parts = name.split()
179
+ if len(parts) >= 2:
180
+ short = f"{parts[0]} {parts[-1]}"
181
+ by_name[short.lower()] = entity
182
+ if len(parts) >= 1:
183
+ first = parts[0].lower()
184
+ if first not in by_name:
185
+ by_name[first] = entity
186
+
187
+ aliases = entity.get('aliases', []) or []
188
+ if isinstance(aliases, list):
189
+ for alias in aliases:
190
+ if alias and isinstance(alias, str):
191
+ by_name[alias.lower()] = entity
192
+
193
+ email = entity.get('email', '')
194
+ if email and isinstance(email, str):
195
+ by_email[email.lower()] = entity
196
+
197
+ rel_ids = set()
198
+ for rel in data.get('relationships', []):
199
+ if isinstance(rel, dict) and rel.get('id'):
200
+ rel_ids.add(rel['id'])
201
+
202
+ return {
203
+ 'by_name': by_name,
204
+ 'by_email': by_email,
205
+ 'by_id': by_id,
206
+ 'rel_ids': rel_ids,
207
+ }
208
+
209
+
210
+ # ─────────────────────────────────────────────
211
+ # Input parsing — normalize various formats
212
+ # ─────────────────────────────────────────────
213
+ def parse_input_file(filepath):
214
+ """Parse an inbox item file (JSON or YAML) into a normalized dict.
215
+
216
+ Returns dict with keys:
217
+ source_type: 'slack' | 'email' | 'calendar' | 'meeting' | 'unknown'
218
+ timestamp: ISO timestamp string
219
+ sender_name: str or None
220
+ sender_email: str or None
221
+ sender_slack_id: str or None
222
+ channel: str or None
223
+ subject: str or None
224
+ content: str
225
+ raw: original parsed data
226
+ source_file: str
227
+ """
228
+ path = Path(filepath)
229
+ if not path.exists():
230
+ raise FileNotFoundError(f"Input file not found: {filepath}")
231
+
232
+ text = path.read_text(encoding='utf-8')
233
+ return parse_input_text(text, str(path))
234
+
235
+
236
+ def parse_input_text(text, source_label="stdin"):
237
+ """Parse input text (JSON or YAML) into normalized dict."""
238
+ text = text.strip()
239
+ if not text:
240
+ raise ValueError("Empty input")
241
+
242
+ raw = None
243
+
244
+ # Try JSON first
245
+ if text.startswith('{') or text.startswith('['):
246
+ try:
247
+ raw = json.loads(text)
248
+ except json.JSONDecodeError:
249
+ pass
250
+
251
+ # Try YAML if JSON failed
252
+ if raw is None:
253
+ try:
254
+ raw = _load_yaml(text)
255
+ except Exception:
256
+ pass
257
+
258
+ if raw is None:
259
+ raise ValueError(f"Could not parse input as JSON or YAML: {source_label}")
260
+
261
+ if not isinstance(raw, dict):
262
+ raise ValueError(f"Expected dict, got {type(raw).__name__}: {source_label}")
263
+
264
+ # Normalize based on event_type or service field
265
+ event_type = raw.get('event_type', raw.get('type', 'unknown'))
266
+ # YAML inbox items use 'service' field instead of 'event_type'
267
+ service = raw.get('service', '')
268
+ if event_type == 'unknown' and service:
269
+ if service == 'slack':
270
+ event_type = 'dm' # Treat as DM for fact extraction
271
+ elif service == 'gmail':
272
+ event_type = 'email'
273
+ elif service == 'calendar':
274
+ event_type = 'calendar'
275
+
276
+ result = {
277
+ 'source_type': 'unknown',
278
+ 'timestamp': '',
279
+ 'sender_name': None,
280
+ 'sender_email': None,
281
+ 'sender_slack_id': None,
282
+ 'channel': None,
283
+ 'subject': None,
284
+ 'content': '',
285
+ 'raw': raw,
286
+ 'source_file': source_label,
287
+ }
288
+
289
+ if event_type in ('dm', 'thread_reply', 'channel_message'):
290
+ result['source_type'] = 'slack'
291
+ slack_event = raw.get('slack_event', {})
292
+ result['timestamp'] = raw.get('timestamp', raw.get('received_at', ''))
293
+ result['sender_slack_id'] = slack_event.get('user', raw.get('sender'))
294
+ result['content'] = slack_event.get('text', raw.get('content', ''))
295
+ result['channel'] = slack_event.get('channel', raw.get('channel', ''))
296
+ # For YAML format inbox items
297
+ sender = raw.get('sender', '')
298
+ if sender:
299
+ result['sender_name'] = sender.replace('-', ' ').title()
300
+ # Check for subject in YAML format
301
+ result['subject'] = raw.get('subject')
302
+
303
+ elif event_type == 'email':
304
+ result['source_type'] = 'email'
305
+ email_data = raw.get('email', {})
306
+ result['timestamp'] = email_data.get('date', raw.get('received_at', raw.get('timestamp', '')))
307
+ from_field = email_data.get('from', '')
308
+ result['sender_name'], result['sender_email'] = _parse_email_from(from_field)
309
+ result['subject'] = email_data.get('subject', '')
310
+ result['content'] = email_data.get('body', email_data.get('snippet', raw.get('content', '')))
311
+
312
+ elif event_type in ('calendar', 'meeting'):
313
+ result['source_type'] = event_type
314
+ result['timestamp'] = raw.get('timestamp', raw.get('start_time', raw.get('date', '')))
315
+ result['subject'] = raw.get('subject', raw.get('title', raw.get('summary', '')))
316
+ result['content'] = raw.get('content', raw.get('description', raw.get('notes', '')))
317
+ # Calendar events may have attendees
318
+ attendees = raw.get('attendees', [])
319
+ if attendees and isinstance(attendees, list):
320
+ # Store first attendee as sender for context
321
+ att = attendees[0]
322
+ if isinstance(att, dict):
323
+ result['sender_email'] = att.get('email', '')
324
+ result['sender_name'] = att.get('name', att.get('displayName', ''))
325
+ elif isinstance(att, str):
326
+ if '@' in att:
327
+ result['sender_email'] = att
328
+ else:
329
+ result['sender_name'] = att
330
+
331
+ elif event_type == 'priority_trigger':
332
+ result['source_type'] = 'slack'
333
+ result['timestamp'] = raw.get('timestamp', '')
334
+ sender = raw.get('sender', '')
335
+ if sender:
336
+ result['sender_name'] = sender.replace('-', ' ').title()
337
+ result['channel'] = raw.get('channel', '')
338
+ result['content'] = raw.get('content', '')
339
+ result['subject'] = raw.get('reason', '')
340
+
341
+ return result
342
+
343
+
344
+ def _parse_email_from(from_field):
345
+ """Parse an email From header into (name, email).
346
+
347
+ Examples:
348
+ 'Nima Masroori <nima@adaptic.ai>' -> ('Nima Masroori', 'nima@adaptic.ai')
349
+ '"Zapier" <learn@send.zapier.com>' -> ('Zapier', 'learn@send.zapier.com')
350
+ 'jane@example.com' -> (None, 'jane@example.com')
351
+ """
352
+ if not from_field:
353
+ return None, None
354
+
355
+ # Pattern: "Name" <email> or Name <email>
356
+ match = re.match(r'^"?([^"<]+?)"?\s*<([^>]+)>', from_field.strip())
357
+ if match:
358
+ name = match.group(1).strip().strip('"')
359
+ email = match.group(2).strip()
360
+ # Handle calendar notification format: "user@domain.com (Google Calendar)"
361
+ # The "name" portion is actually an email, and the real email is calendar-noreply
362
+ if '(Google Calendar)' in name or '(Calendar)' in name:
363
+ # This is a calendar notification — extract the real email from the name portion
364
+ real_email_match = re.match(r'([^\s@]+@[^\s@]+\.[^\s@]+)', name)
365
+ if real_email_match:
366
+ return None, real_email_match.group(1)
367
+ return None, email
368
+ return name, email
369
+
370
+ # Bare email
371
+ match = re.match(r'^([^\s@]+@[^\s@]+\.[^\s@]+)$', from_field.strip())
372
+ if match:
373
+ return None, match.group(1)
374
+
375
+ return None, None
376
+
377
+
378
+ # ─────────────────────────────────────────────
379
+ # Fact extraction engine
380
+ # ─────────────────────────────────────────────
381
+ class ExtractedFact:
382
+ """Represents a single fact extracted from an interaction."""
383
+
384
+ def __init__(self, fact_type, data, source_ref, confidence='extracted'):
385
+ self.fact_type = fact_type # 'new_entity', 'new_relationship', 'entity_update'
386
+ self.data = data # dict with the actual data
387
+ self.source_ref = source_ref
388
+ self.confidence = confidence
389
+
390
+ def __repr__(self):
391
+ return f"ExtractedFact({self.fact_type}, {self.data.get('id', self.data.get('name', '?'))})"
392
+
393
+
394
+ def extract_facts(interaction, lookups):
395
+ """Extract new entities, relationships, and updates from an interaction.
396
+
397
+ Args:
398
+ interaction: Normalized interaction dict (from parse_input_text)
399
+ lookups: Entity index lookup dicts (from build_lookups)
400
+
401
+ Returns:
402
+ list of ExtractedFact objects
403
+ """
404
+ facts = []
405
+ now_iso = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
406
+ source_file = interaction.get('source_file', 'unknown')
407
+
408
+ # Build source reference
409
+ source_ref = _build_source_ref(interaction)
410
+
411
+ # --- Extract from email sender ---
412
+ if interaction['source_type'] == 'email':
413
+ sender_name = interaction.get('sender_name')
414
+ sender_email = interaction.get('sender_email')
415
+
416
+ if sender_email and sender_email.lower() not in lookups['by_email']:
417
+ # Skip automated/noreply senders
418
+ if not _is_automated_sender(sender_email, sender_name):
419
+ person_id = _generate_person_id(sender_name, sender_email)
420
+
421
+ # Quality check: only create person entity if name looks real
422
+ if not _is_low_quality_person_name(sender_name):
423
+ entity_data = {
424
+ 'id': person_id,
425
+ 'type': 'person',
426
+ 'name': sender_name or _name_from_email(sender_email),
427
+ 'email': sender_email,
428
+ 'confidence': 'extracted',
429
+ 'first_seen': now_iso,
430
+ 'source': source_ref,
431
+ }
432
+ if sender_name:
433
+ entity_data['aliases'] = [sender_name.split()[0]] if ' ' in sender_name else []
434
+
435
+ # Only add if id not already present
436
+ if person_id not in lookups['by_id']:
437
+ facts.append(ExtractedFact('new_entity', entity_data, source_ref))
438
+
439
+ # Try to extract company from email domain
440
+ company_fact = _extract_company_from_email(sender_email, sender_name, lookups, source_ref, now_iso)
441
+ if company_fact:
442
+ facts.append(company_fact)
443
+
444
+ # Create employment relationship if we can infer company
445
+ emp_fact = _extract_employment_from_email(person_id, sender_email, lookups, source_ref)
446
+ if emp_fact:
447
+ facts.append(emp_fact)
448
+
449
+ elif sender_email and sender_email.lower() in lookups['by_email']:
450
+ # Known entity — check for updates
451
+ existing = lookups['by_email'][sender_email.lower()]
452
+ updates = _check_entity_updates(existing, interaction, source_ref)
453
+ facts.extend(updates)
454
+
455
+ # --- Extract from email subject ---
456
+ subject = interaction.get('subject', '') or ''
457
+ content = interaction.get('content', '') or ''
458
+ combined_text = f"{subject} {content}"
459
+
460
+ # Extract email addresses mentioned in content
461
+ email_facts = _extract_emails_from_text(combined_text, lookups, source_ref, now_iso)
462
+ facts.extend(email_facts)
463
+
464
+ # Extract phone numbers mentioned in content
465
+ phone_facts = _extract_phones_from_text(combined_text, lookups, source_ref)
466
+ facts.extend(phone_facts)
467
+
468
+ # Extract role/title mentions for known entities
469
+ title_facts = _extract_title_updates(combined_text, lookups, source_ref)
470
+ facts.extend(title_facts)
471
+
472
+ return facts
473
+
474
+
475
+ def _build_source_ref(interaction):
476
+ """Build a source reference string from interaction metadata."""
477
+ parts = [interaction.get('source_type', 'unknown')]
478
+ ts = interaction.get('timestamp', '')
479
+ if ts:
480
+ # Normalize timestamp to date portion
481
+ date_match = re.match(r'(\d{4}-\d{2}-\d{2})', str(ts))
482
+ if date_match:
483
+ parts.append(date_match.group(1))
484
+ else:
485
+ parts.append(str(ts)[:20])
486
+
487
+ sender = interaction.get('sender_name') or interaction.get('sender_email') or ''
488
+ if sender:
489
+ parts.append(sender)
490
+
491
+ source_file = interaction.get('source_file', '')
492
+ if source_file:
493
+ # Use just the filename, not full path
494
+ parts.append(Path(source_file).name)
495
+
496
+ return ' | '.join(parts)
497
+
498
+
499
+ def _is_automated_sender(email, name):
500
+ """Check if this sender is an automated system (newsletters, notifications)."""
501
+ if not email:
502
+ return True
503
+
504
+ email_lower = email.lower()
505
+ automated_patterns = [
506
+ 'noreply', 'no-reply', 'donotreply', 'notifications',
507
+ 'mailer-daemon', 'postmaster', 'newsletter', 'updates@',
508
+ 'info@send.', 'learn@send.', 'digest@', 'alerts@',
509
+ 'support@', 'system@', 'admin@', 'bounce',
510
+ 'calendar-notification', 'calendar-server',
511
+ 'invite@', 'notify@', 'team@',
512
+ ]
513
+ for pattern in automated_patterns:
514
+ if pattern in email_lower:
515
+ return True
516
+
517
+ automated_domains = [
518
+ 'sparkpostmail.com', 'sendgrid.net', 'mailchimp.com',
519
+ 'intercom-mail.com', 'mandrillapp.com', 'amazonses.com',
520
+ 'postmarkapp.com', 'mailgun.org', 'railway.app',
521
+ 'calendar.google.com', 'google.com', 'airtable.com',
522
+ 'anthropic.com', 'gamma.app', 'calendly.com',
523
+ 'notion.so', 'linear.app', 'slack.com',
524
+ 'figma.com', 'github.com', 'atlassian.com',
525
+ 'stripe.com', 'twilio.com', 'hubspot.com',
526
+ 'consensus.app', 'superhuman.com',
527
+ ]
528
+ domain = email_lower.split('@')[-1] if '@' in email_lower else ''
529
+ for ad in automated_domains:
530
+ if domain.endswith(ad):
531
+ return True
532
+
533
+ # Skip subdomains of known services (e.g. notify.railway.app, send.calendly.com)
534
+ domain_parts = domain.split('.')
535
+ service_roots = {
536
+ 'railway', 'google', 'airtable', 'anthropic', 'gamma',
537
+ 'calendly', 'notion', 'linear', 'slack', 'figma',
538
+ 'github', 'atlassian', 'stripe', 'twilio', 'hubspot',
539
+ 'consensus', 'superhuman', 'zapier',
540
+ }
541
+ for part in domain_parts:
542
+ if part in service_roots:
543
+ return True
544
+
545
+ # Skip common automated service senders
546
+ if name:
547
+ name_lower = name.lower()
548
+ automated_names = [
549
+ 'zapier', 'slack', 'google', 'github', 'notion', 'linear',
550
+ 'railway', 'airtable', 'anthropic', 'gamma', 'calendly',
551
+ 'google calendar', 'the airtable team', 'stripe',
552
+ ]
553
+ # Check exact match and prefix match
554
+ if name_lower in automated_names:
555
+ return True
556
+ # Check if name contains service-like patterns
557
+ for an in automated_names:
558
+ if an in name_lower:
559
+ return True
560
+ # Names with "from" pattern like "Christian From Consensus"
561
+ if ' from ' in name_lower:
562
+ return True
563
+ # Names that are just a single word and look like a service
564
+ if len(name_lower.split()) == 1 and name_lower in service_roots:
565
+ return True
566
+
567
+ return False
568
+
569
+
570
+ def _is_low_quality_person_name(name):
571
+ """Check if a name looks like it's not a real person name.
572
+
573
+ Filters out single generic words, service names, and names with
574
+ special patterns that indicate non-person senders.
575
+ """
576
+ if not name:
577
+ return True
578
+
579
+ name_stripped = name.strip()
580
+ if not name_stripped:
581
+ return True
582
+
583
+ # Names containing commas with corporate suffixes (e.g. "Anthropic, PBC")
584
+ if re.search(r',\s*(PBC|LLC|Inc|Ltd|Corp|GmbH|SA|BV)', name_stripped, re.IGNORECASE):
585
+ return True
586
+
587
+ # Single-word names that are generic/ambiguous
588
+ words = name_stripped.split()
589
+ if len(words) == 1:
590
+ generic_singles = {
591
+ 'info', 'admin', 'support', 'team', 'hello', 'contact',
592
+ 'speakers', 'events', 'news', 'updates', 'billing',
593
+ 'sales', 'marketing', 'hr', 'legal', 'finance',
594
+ 'ops', 'operations', 'engineering', 'product',
595
+ 'security', 'compliance', 'risk', 'audit',
596
+ }
597
+ if words[0].lower() in generic_singles:
598
+ return True
599
+
600
+ # Names starting with "The" (e.g. "The Airtable Team")
601
+ if name_stripped.lower().startswith('the '):
602
+ return True
603
+
604
+ return False
605
+
606
+
607
+ def _generate_person_id(name, email):
608
+ """Generate a person entity ID from name or email.
609
+
610
+ Follows existing convention: person:firstname-lastname
611
+ """
612
+ if name:
613
+ parts = name.lower().split()
614
+ if len(parts) >= 2:
615
+ slug = f"{parts[0]}-{parts[-1]}"
616
+ elif parts:
617
+ slug = parts[0]
618
+ else:
619
+ slug = email.split('@')[0].replace('.', '-') if email else 'unknown'
620
+ elif email:
621
+ slug = email.split('@')[0].replace('.', '-').replace('_', '-')
622
+ else:
623
+ return 'person:unknown'
624
+
625
+ # Clean slug
626
+ slug = re.sub(r'[^a-z0-9-]', '', slug)
627
+ return f"person:{slug}"
628
+
629
+
630
+ def _generate_company_id(name):
631
+ """Generate a company entity ID from name."""
632
+ slug = name.lower()
633
+ slug = re.sub(r'[^a-z0-9\s-]', '', slug)
634
+ slug = re.sub(r'\s+', '-', slug.strip())
635
+ slug = re.sub(r'-+', '-', slug)
636
+ return f"company:{slug}"
637
+
638
+
639
+ def _name_from_email(email):
640
+ """Derive a display name from an email address."""
641
+ if not email:
642
+ return 'Unknown'
643
+ local = email.split('@')[0]
644
+ # Handle formats: first.last, first_last, firstlast
645
+ parts = re.split(r'[._]', local)
646
+ return ' '.join(p.capitalize() for p in parts if p)
647
+
648
+
649
+ def _extract_company_from_email(email, sender_name, lookups, source_ref, now_iso):
650
+ """Try to extract a company entity from an email domain.
651
+
652
+ Returns an ExtractedFact for a new company, or None.
653
+ """
654
+ if not email or '@' in email is False:
655
+ return None
656
+
657
+ domain = email.lower().split('@')[-1]
658
+
659
+ # Skip common personal/free email providers
660
+ free_providers = {
661
+ 'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
662
+ 'icloud.com', 'me.com', 'aol.com', 'protonmail.com',
663
+ 'live.com', 'msn.com', 'mail.com', 'zoho.com',
664
+ }
665
+ if domain in free_providers:
666
+ return None
667
+
668
+ # Skip Adaptic domains (already known)
669
+ if 'adaptic' in domain:
670
+ return None
671
+
672
+ # Derive company name from domain
673
+ domain_prefix = domain.split('.')[0]
674
+ company_name = domain_prefix.replace('-', ' ').title()
675
+
676
+ # Check if this company domain already maps to a known entity:
677
+ # 1. Exact email domain match on any entity
678
+ # 2. Domain prefix starts with or equals a known company name word (>= 4 chars)
679
+ # 3. Existing entity has this domain in its email
680
+ domain_prefix_lower = domain_prefix.lower()
681
+ for entity in lookups.get('by_id', {}).values():
682
+ if not isinstance(entity, dict):
683
+ continue
684
+ # Check email domain match
685
+ existing_email = entity.get('email', '')
686
+ if existing_email and isinstance(existing_email, str):
687
+ existing_domain = existing_email.lower().split('@')[-1] if '@' in existing_email else ''
688
+ if existing_domain == domain:
689
+ return None # Company already represented
690
+ # Check domain-to-company-name fuzzy match (for known companies only)
691
+ if entity.get('type') == 'company':
692
+ ename = (entity.get('name') or '').lower()
693
+ ename_slug = re.sub(r'[^a-z0-9]', '', ename)
694
+ # Require domain prefix to START with the company name slug
695
+ # or the company name slug to START with the domain prefix
696
+ # This avoids "inc" in "evolveinteriors" false positive
697
+ if len(ename_slug) >= 4 and (
698
+ domain_prefix_lower.startswith(ename_slug) or
699
+ ename_slug.startswith(domain_prefix_lower)
700
+ ):
701
+ return None # Fuzzy match to existing entity
702
+ # Also check each significant word (>= 5 chars to avoid short word collisions)
703
+ ename_words = re.sub(r'[^a-z0-9\s]', '', ename).split()
704
+ for word in ename_words:
705
+ if len(word) >= 5 and domain_prefix_lower.startswith(word):
706
+ return None
707
+ # Also check aliases
708
+ for alias in (entity.get('aliases', []) or []):
709
+ if isinstance(alias, str) and len(alias) >= 4:
710
+ alias_slug = re.sub(r'[^a-z0-9]', '', alias.lower())
711
+ if domain_prefix_lower.startswith(alias_slug) or alias_slug.startswith(domain_prefix_lower):
712
+ return None
713
+
714
+ company_id = _generate_company_id(company_name)
715
+
716
+ # Check if company id already exists
717
+ if company_id in lookups['by_id']:
718
+ return None
719
+ if company_name.lower() in lookups['by_name']:
720
+ return None
721
+
722
+ return ExtractedFact('new_entity', {
723
+ 'id': company_id,
724
+ 'type': 'company',
725
+ 'name': company_name,
726
+ 'domain': domain,
727
+ 'confidence': 'extracted',
728
+ 'first_seen': now_iso,
729
+ 'source': source_ref,
730
+ }, source_ref)
731
+
732
+
733
+ def _extract_employment_from_email(person_id, email, lookups, source_ref):
734
+ """Try to infer an employment relationship from email domain.
735
+
736
+ Returns an ExtractedFact for a new relationship, or None.
737
+ """
738
+ if not email or '@' not in email:
739
+ return None
740
+
741
+ domain = email.lower().split('@')[-1]
742
+
743
+ # Skip free providers
744
+ free_providers = {
745
+ 'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
746
+ 'icloud.com', 'me.com', 'aol.com', 'protonmail.com',
747
+ 'live.com', 'msn.com', 'mail.com', 'zoho.com',
748
+ }
749
+ if domain in free_providers:
750
+ return None
751
+
752
+ # Find company entity matching this domain via multiple strategies
753
+ domain_prefix = domain.split('.')[0].lower()
754
+ company_id = None
755
+ for entity in lookups.get('by_id', {}).values():
756
+ if not isinstance(entity, dict):
757
+ continue
758
+ if entity.get('type') != 'company':
759
+ continue
760
+ # Strategy 1: Exact domain field match
761
+ entity_domain = entity.get('domain', '')
762
+ if entity_domain == domain:
763
+ company_id = entity['id']
764
+ break
765
+ # Strategy 2: Email domain match on any entity email
766
+ existing_email = entity.get('email', '')
767
+ if existing_email and isinstance(existing_email, str):
768
+ if '@' in existing_email and existing_email.lower().split('@')[-1] == domain:
769
+ company_id = entity['id']
770
+ break
771
+ # Strategy 3: Fuzzy match — domain prefix starts with company slug or vice versa
772
+ ename = (entity.get('name') or '').lower()
773
+ ename_slug = re.sub(r'[^a-z0-9]', '', ename)
774
+ if len(ename_slug) >= 4 and (
775
+ domain_prefix.startswith(ename_slug) or
776
+ ename_slug.startswith(domain_prefix)
777
+ ):
778
+ company_id = entity['id']
779
+ break
780
+ # Check significant words (>= 5 chars)
781
+ ename_words = re.sub(r'[^a-z0-9\s]', '', ename).split()
782
+ for word in ename_words:
783
+ if len(word) >= 5 and domain_prefix.startswith(word):
784
+ company_id = entity['id']
785
+ break
786
+ if company_id:
787
+ break
788
+
789
+ # Also check if the company id we'd generate already exists
790
+ if not company_id:
791
+ company_name = domain.split('.')[0].replace('-', ' ').title()
792
+ candidate_id = _generate_company_id(company_name)
793
+ if candidate_id in lookups['by_id']:
794
+ company_id = candidate_id
795
+
796
+ if not company_id:
797
+ return None
798
+
799
+ # Generate relationship id
800
+ person_slug = person_id.replace('person:', '')
801
+ company_slug = company_id.replace('company:', '')
802
+ rel_id = f"rel:{person_slug}-{company_slug}-employed"
803
+
804
+ if rel_id in lookups['rel_ids']:
805
+ return None
806
+
807
+ return ExtractedFact('new_relationship', {
808
+ 'id': rel_id,
809
+ 'subject': person_id,
810
+ 'predicate': 'employed_by',
811
+ 'object': company_id,
812
+ 'status': 'inferred',
813
+ 'confidence': 'extracted',
814
+ 'source': {
815
+ 'type': 'interaction',
816
+ 'ref': source_ref,
817
+ 'confidence': 'medium',
818
+ },
819
+ 'notes': f"Inferred from email domain ({domain}).",
820
+ }, source_ref)
821
+
822
+
823
+ def _check_entity_updates(existing, interaction, source_ref):
824
+ """Check if interaction reveals updated info for a known entity.
825
+
826
+ Only updates fields that are currently empty or adds new info.
827
+ NEVER overwrites verified facts with extracted ones.
828
+
829
+ Returns list of ExtractedFact with type 'entity_update'.
830
+ """
831
+ facts = []
832
+
833
+ # Check if we now know a phone number that was previously missing
834
+ content = interaction.get('content', '') or ''
835
+ subject = interaction.get('subject', '') or ''
836
+ combined = f"{subject} {content}"
837
+
838
+ entity_id = existing.get('id', '')
839
+
840
+ # Look for phone numbers in content that could belong to this sender
841
+ phone_match = re.search(r'(?:phone|mobile|cell|tel|call me)[\s:]*(\+?[\d\s()-]{10,18})', combined, re.IGNORECASE)
842
+ if phone_match and not existing.get('phone') and not existing.get('mobile'):
843
+ phone = re.sub(r'[^\d+]', '', phone_match.group(1))
844
+ if len(phone) >= 10:
845
+ facts.append(ExtractedFact('entity_update', {
846
+ 'entity_id': entity_id,
847
+ 'field': 'phone',
848
+ 'value': phone_match.group(1).strip(),
849
+ 'confidence': 'extracted',
850
+ 'source': source_ref,
851
+ }, source_ref))
852
+
853
+ return facts
854
+
855
+
856
+ def _extract_emails_from_text(text, lookups, source_ref, now_iso):
857
+ """Extract email addresses mentioned in text that are not yet indexed."""
858
+ facts = []
859
+ if not text:
860
+ return facts
861
+
862
+ # Find all email addresses in text
863
+ email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
864
+ found_emails = set(re.findall(email_pattern, text))
865
+
866
+ for email in found_emails:
867
+ email_lower = email.lower()
868
+ if email_lower in lookups['by_email']:
869
+ continue
870
+ if _is_automated_sender(email, None):
871
+ continue
872
+ # Skip Adaptic emails — likely already known or will be added manually
873
+ if 'adaptic' in email_lower:
874
+ continue
875
+
876
+ # Derive name and check quality — skip generic local parts
877
+ derived_name = _name_from_email(email)
878
+ if _is_low_quality_person_name(derived_name):
879
+ continue
880
+
881
+ # Skip if the local part itself is a generic role/function
882
+ local_part = email_lower.split('@')[0]
883
+ generic_locals = {
884
+ 'info', 'admin', 'support', 'team', 'hello', 'contact',
885
+ 'speakers', 'events', 'news', 'updates', 'billing',
886
+ 'sales', 'marketing', 'hr', 'legal', 'finance',
887
+ 'office', 'enquiries', 'inquiries', 'general',
888
+ 'accounts', 'reception', 'helpdesk', 'service',
889
+ }
890
+ if local_part in generic_locals:
891
+ continue
892
+
893
+ person_id = _generate_person_id(None, email)
894
+ if person_id in lookups['by_id']:
895
+ continue
896
+
897
+ facts.append(ExtractedFact('new_entity', {
898
+ 'id': person_id,
899
+ 'type': 'person',
900
+ 'name': _name_from_email(email),
901
+ 'email': email,
902
+ 'confidence': 'extracted',
903
+ 'first_seen': now_iso,
904
+ 'source': source_ref,
905
+ 'notes': 'Auto-extracted from message content. Name derived from email — verify.',
906
+ }, source_ref))
907
+
908
+ return facts
909
+
910
+
911
+ def _extract_phones_from_text(text, lookups, source_ref):
912
+ """Extract phone numbers from text and try to match to known entities."""
913
+ facts = []
914
+ if not text:
915
+ return facts
916
+
917
+ # Find phone patterns: +971 XX XXX XXXX, (XXX) XXX-XXXX, etc.
918
+ phone_pattern = r'\+?\d[\d\s()-]{9,17}\d'
919
+ matches = re.findall(phone_pattern, text)
920
+
921
+ for phone_raw in matches:
922
+ phone_clean = re.sub(r'[^\d+]', '', phone_raw)
923
+ if len(phone_clean) < 10:
924
+ continue
925
+
926
+ # Check if any existing entity already has this phone
927
+ already_known = False
928
+ for entity in lookups['by_id'].values():
929
+ if not isinstance(entity, dict):
930
+ continue
931
+ for field in ('phone', 'mobile'):
932
+ existing_phone = entity.get(field, '')
933
+ if existing_phone:
934
+ existing_clean = re.sub(r'[^\d+]', '', str(existing_phone))
935
+ if existing_clean == phone_clean:
936
+ already_known = True
937
+ break
938
+ if already_known:
939
+ break
940
+
941
+ # We don't create entities just from phone numbers — too ambiguous
942
+ # But we could flag them for manual review if needed
943
+
944
+ return facts
945
+
946
+
947
+ def _extract_title_updates(text, lookups, source_ref):
948
+ """Look for title/role mentions that could update known entities.
949
+
950
+ Patterns: "X, Title at Company" or "X (Title)" in signatures.
951
+ """
952
+ facts = []
953
+ if not text:
954
+ return facts
955
+
956
+ # Common patterns in email signatures / introductions
957
+ # "FirstName LastName, Title" or "FirstName LastName | Title"
958
+ sig_patterns = [
959
+ r'(?:^|\n)\s*([A-Z][a-z]+\s+[A-Z][a-z]+)\s*[,|]\s*([A-Z][A-Za-z\s&/-]+?)(?:\n|$)',
960
+ r'(?:^|\n)\s*([A-Z][a-z]+\s+[A-Z][a-z]+)\s*\n\s*([A-Z][A-Za-z\s&/-]+?)(?:\n|$)',
961
+ ]
962
+
963
+ for pattern in sig_patterns:
964
+ matches = re.finditer(pattern, text)
965
+ for match in matches:
966
+ person_name = match.group(1).strip()
967
+ title = match.group(2).strip()
968
+
969
+ # Must be a known entity
970
+ entity = lookups['by_name'].get(person_name.lower())
971
+ if not entity:
972
+ continue
973
+
974
+ existing_role = entity.get('primary_role', '')
975
+ # Only suggest update if role is empty or meaningfully different
976
+ if existing_role and existing_role.lower().strip() == title.lower().strip():
977
+ continue
978
+ if not title or len(title) < 3 or len(title) > 60:
979
+ continue
980
+
981
+ # Don't overwrite verified roles
982
+ if existing_role:
983
+ continue
984
+
985
+ facts.append(ExtractedFact('entity_update', {
986
+ 'entity_id': entity.get('id', ''),
987
+ 'field': 'primary_role',
988
+ 'value': title,
989
+ 'confidence': 'extracted',
990
+ 'source': source_ref,
991
+ }, source_ref))
992
+
993
+ return facts
994
+
995
+
996
+ # ─────────────────────────────────────────────
997
+ # Index update application
998
+ # ─────────────────────────────────────────────
999
+ def apply_facts(data, facts, lookups, dry_run=False):
1000
+ """Apply extracted facts to the entity index data.
1001
+
1002
+ Follows safety rules:
1003
+ - NEVER removes existing entries
1004
+ - NEVER overrides verified facts with extracted ones
1005
+ - Idempotent — duplicate facts are skipped
1006
+ - New entries marked with confidence: extracted
1007
+
1008
+ Args:
1009
+ data: The full entity index dict (will be mutated)
1010
+ facts: List of ExtractedFact objects
1011
+ lookups: Current lookup dicts (will be updated as we go)
1012
+ dry_run: If True, just report what would happen
1013
+
1014
+ Returns:
1015
+ dict with: entities_added, relationships_added, entities_updated, skipped
1016
+ """
1017
+ stats = {
1018
+ 'entities_added': 0,
1019
+ 'relationships_added': 0,
1020
+ 'entities_updated': 0,
1021
+ 'skipped': 0,
1022
+ 'details': [],
1023
+ }
1024
+
1025
+ for fact in facts:
1026
+ if fact.fact_type == 'new_entity':
1027
+ entity_data = fact.data
1028
+ eid = entity_data.get('id', '')
1029
+
1030
+ # Idempotency check — skip if already exists
1031
+ if eid in lookups['by_id']:
1032
+ stats['skipped'] += 1
1033
+ continue
1034
+
1035
+ email = entity_data.get('email', '')
1036
+ if email and email.lower() in lookups['by_email']:
1037
+ stats['skipped'] += 1
1038
+ continue
1039
+
1040
+ name = entity_data.get('name', '')
1041
+ if name and name.lower() in lookups['by_name']:
1042
+ stats['skipped'] += 1
1043
+ continue
1044
+
1045
+ # Format the entity for YAML (matching existing schema)
1046
+ new_entity = {'id': eid, 'type': entity_data.get('type', 'person')}
1047
+ if entity_data.get('name'):
1048
+ new_entity['name'] = entity_data['name']
1049
+ if entity_data.get('aliases'):
1050
+ new_entity['aliases'] = entity_data['aliases']
1051
+ if entity_data.get('email'):
1052
+ new_entity['email'] = entity_data['email']
1053
+ if entity_data.get('domain'):
1054
+ new_entity['domain'] = entity_data['domain']
1055
+ if entity_data.get('primary_role'):
1056
+ new_entity['primary_role'] = entity_data['primary_role']
1057
+ if entity_data.get('primary_company'):
1058
+ new_entity['primary_company'] = entity_data['primary_company']
1059
+ new_entity['confidence'] = 'extracted'
1060
+ new_entity['first_seen'] = entity_data.get('first_seen', datetime.now(timezone.utc).isoformat())
1061
+ new_entity['extraction_source'] = fact.source_ref
1062
+ if entity_data.get('notes'):
1063
+ new_entity['notes'] = entity_data['notes']
1064
+
1065
+ if not dry_run:
1066
+ data['entities'].append(new_entity)
1067
+
1068
+ # Always update lookups (even in dry-run) to prevent duplicates across files
1069
+ lookups['by_id'][eid] = new_entity
1070
+ if name:
1071
+ lookups['by_name'][name.lower()] = new_entity
1072
+ if email:
1073
+ lookups['by_email'][email.lower()] = new_entity
1074
+
1075
+ stats['entities_added'] += 1
1076
+ stats['details'].append(f"+ Entity: {entity_data.get('name', eid)} ({entity_data.get('type', '?')})")
1077
+
1078
+ elif fact.fact_type == 'new_relationship':
1079
+ rel_data = fact.data
1080
+ rel_id = rel_data.get('id', '')
1081
+
1082
+ # Idempotency check
1083
+ if rel_id in lookups['rel_ids']:
1084
+ stats['skipped'] += 1
1085
+ continue
1086
+
1087
+ new_rel = {
1088
+ 'id': rel_id,
1089
+ 'subject': rel_data.get('subject', ''),
1090
+ 'predicate': rel_data.get('predicate', ''),
1091
+ 'object': rel_data.get('object', ''),
1092
+ 'status': rel_data.get('status', 'inferred'),
1093
+ 'confidence': 'extracted',
1094
+ }
1095
+ if rel_data.get('role'):
1096
+ new_rel['role'] = rel_data['role']
1097
+ if rel_data.get('scope'):
1098
+ new_rel['scope'] = rel_data['scope']
1099
+ if rel_data.get('source'):
1100
+ new_rel['source'] = rel_data['source']
1101
+ if rel_data.get('notes'):
1102
+ new_rel['notes'] = rel_data['notes']
1103
+
1104
+ if not dry_run:
1105
+ data['relationships'].append(new_rel)
1106
+
1107
+ # Always update lookups (even in dry-run) to prevent duplicates
1108
+ lookups['rel_ids'].add(rel_id)
1109
+
1110
+ stats['relationships_added'] += 1
1111
+ stats['details'].append(
1112
+ f"+ Relationship: {rel_data.get('subject', '?')} "
1113
+ f"--[{rel_data.get('predicate', '?')}]--> "
1114
+ f"{rel_data.get('object', '?')}"
1115
+ )
1116
+
1117
+ elif fact.fact_type == 'entity_update':
1118
+ update = fact.data
1119
+ entity_id = update.get('entity_id', '')
1120
+ field = update.get('field', '')
1121
+ value = update.get('value', '')
1122
+
1123
+ entity = lookups['by_id'].get(entity_id)
1124
+ if not entity:
1125
+ stats['skipped'] += 1
1126
+ continue
1127
+
1128
+ # Safety: never overwrite existing verified data
1129
+ existing_value = entity.get(field, '')
1130
+ if existing_value and entity.get('confidence', 'verified') != 'extracted':
1131
+ stats['skipped'] += 1
1132
+ continue
1133
+
1134
+ if not dry_run:
1135
+ entity[field] = value
1136
+ # Find and update in the entities list
1137
+ for i, e in enumerate(data['entities']):
1138
+ if isinstance(e, dict) and e.get('id') == entity_id:
1139
+ data['entities'][i][field] = value
1140
+ break
1141
+
1142
+ stats['entities_updated'] += 1
1143
+ stats['details'].append(f"~ Update: {entity_id}.{field} = {value}")
1144
+
1145
+ else:
1146
+ stats['skipped'] += 1
1147
+
1148
+ return stats
1149
+
1150
+
1151
+ # ─────────────────────────────────────────────
1152
+ # YAML file writing — preserves existing structure
1153
+ # ─────────────────────────────────────────────
1154
+ def write_entity_index(data):
1155
+ """Write the entity index back to YAML, preserving the general structure.
1156
+
1157
+ Uses PyYAML's dump but with careful formatting to match the existing style
1158
+ as closely as possible.
1159
+ """
1160
+ # Update metadata
1161
+ data['last_updated'] = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
1162
+ data['last_updated_by'] = 'post-interaction-indexer'
1163
+
1164
+ # We need to write the YAML preserving comments and structure.
1165
+ # Since PyYAML strips comments, we use a hybrid approach:
1166
+ # 1. Read the existing file
1167
+ # 2. Append new entities and relationships at the end of their sections
1168
+ # 3. For updates, do targeted text replacement
1169
+
1170
+ if not ENTITY_INDEX_PATH.exists():
1171
+ # Fresh write
1172
+ output = _dump_yaml(data)
1173
+ ENTITY_INDEX_PATH.write_text(output, encoding='utf-8')
1174
+ _invalidate_cache()
1175
+ return
1176
+
1177
+ existing_text = ENTITY_INDEX_PATH.read_text(encoding='utf-8')
1178
+
1179
+ # Update last_updated timestamp in-place
1180
+ existing_text = re.sub(
1181
+ r'^last_updated:.*$',
1182
+ f'last_updated: "{data["last_updated"]}"',
1183
+ existing_text,
1184
+ count=1,
1185
+ flags=re.MULTILINE,
1186
+ )
1187
+ existing_text = re.sub(
1188
+ r'^last_updated_by:.*$',
1189
+ f'last_updated_by: {data["last_updated_by"]}',
1190
+ existing_text,
1191
+ count=1,
1192
+ flags=re.MULTILINE,
1193
+ )
1194
+
1195
+ # Find new entities that need to be appended
1196
+ # We identify them by checking which entity IDs are NOT already in the file text
1197
+ new_entities_yaml = []
1198
+ for entity in data.get('entities', []):
1199
+ if not isinstance(entity, dict):
1200
+ continue
1201
+ eid = entity.get('id', '')
1202
+ if not eid:
1203
+ continue
1204
+ # Check if this entity ID already exists in the file
1205
+ if f"id: {eid}" in existing_text:
1206
+ continue
1207
+ # Format as YAML block
1208
+ new_entities_yaml.append(_format_entity_yaml(entity))
1209
+
1210
+ # Find new relationships
1211
+ new_rels_yaml = []
1212
+ for rel in data.get('relationships', []):
1213
+ if not isinstance(rel, dict):
1214
+ continue
1215
+ rid = rel.get('id', '')
1216
+ if not rid:
1217
+ continue
1218
+ if f"id: {rid}" in existing_text:
1219
+ continue
1220
+ new_rels_yaml.append(_format_relationship_yaml(rel))
1221
+
1222
+ # Append new entities before the relationships section
1223
+ if new_entities_yaml:
1224
+ entities_block = '\n'.join(new_entities_yaml)
1225
+ # Find the relationships section marker
1226
+ rel_marker = '# =============================================================================\n# RELATIONSHIPS'
1227
+ if rel_marker in existing_text:
1228
+ existing_text = existing_text.replace(
1229
+ rel_marker,
1230
+ f" # ---------------------------------------------------------------------------\n"
1231
+ f" # Auto-extracted entities (Phase 5 indexer — {data['last_updated']})\n"
1232
+ f" # ---------------------------------------------------------------------------\n"
1233
+ f"{entities_block}\n\n"
1234
+ f"{rel_marker}",
1235
+ )
1236
+ else:
1237
+ # Fallback: append before 'relationships:' line
1238
+ existing_text = re.sub(
1239
+ r'^(relationships:)',
1240
+ f" # Auto-extracted entities\n{entities_block}\n\n\\1",
1241
+ existing_text,
1242
+ count=1,
1243
+ flags=re.MULTILINE,
1244
+ )
1245
+
1246
+ # Append new relationships at the end of the relationships section
1247
+ # (before any subsequent top-level section like commitments:)
1248
+ if new_rels_yaml:
1249
+ rels_block = '\n'.join(new_rels_yaml)
1250
+ rels_insert = (
1251
+ f"\n # ---------------------------------------------------------------------------\n"
1252
+ f" # Auto-extracted relationships (Phase 5 indexer — {data['last_updated']})\n"
1253
+ f" # ---------------------------------------------------------------------------\n"
1254
+ f"{rels_block}\n"
1255
+ )
1256
+
1257
+ # Find the next top-level section after relationships:
1258
+ # Look for patterns like "commitments:", "# ===..." followed by a top-level key
1259
+ # We insert BEFORE the commitments section marker
1260
+ commit_marker = '# =============================================================================\n# COMMITMENTS'
1261
+ if commit_marker in existing_text:
1262
+ existing_text = existing_text.replace(
1263
+ commit_marker,
1264
+ f"{rels_insert}\n{commit_marker}",
1265
+ )
1266
+ else:
1267
+ # Try to find 'commitments:' as a top-level key
1268
+ commitments_match = re.search(r'^commitments:\s*$', existing_text, re.MULTILINE)
1269
+ if commitments_match:
1270
+ # Insert before the commitments section (including its header comments)
1271
+ # Walk backwards from the match to find the section header comments
1272
+ insert_pos = commitments_match.start()
1273
+ # Check for preceding comment block
1274
+ preceding = existing_text[:insert_pos].rstrip()
1275
+ comment_lines = []
1276
+ for line in reversed(preceding.split('\n')):
1277
+ stripped = line.strip()
1278
+ if stripped.startswith('#') or stripped == '':
1279
+ comment_lines.insert(0, line)
1280
+ else:
1281
+ break
1282
+ if comment_lines:
1283
+ # Find the start of the comment block
1284
+ comment_text = '\n'.join(comment_lines)
1285
+ insert_pos = existing_text.rfind(comment_text, 0, commitments_match.start())
1286
+ if insert_pos >= 0:
1287
+ existing_text = (
1288
+ existing_text[:insert_pos] +
1289
+ rels_insert + '\n' +
1290
+ existing_text[insert_pos:]
1291
+ )
1292
+ else:
1293
+ existing_text = (
1294
+ existing_text[:commitments_match.start()] +
1295
+ rels_insert + '\n' +
1296
+ existing_text[commitments_match.start():]
1297
+ )
1298
+ else:
1299
+ existing_text = (
1300
+ existing_text[:commitments_match.start()] +
1301
+ rels_insert + '\n' +
1302
+ existing_text[commitments_match.start():]
1303
+ )
1304
+ else:
1305
+ # No commitments section — append at end
1306
+ if not existing_text.endswith('\n'):
1307
+ existing_text += '\n'
1308
+ existing_text += rels_insert
1309
+
1310
+ ENTITY_INDEX_PATH.write_text(existing_text, encoding='utf-8')
1311
+ _invalidate_cache()
1312
+
1313
+
1314
+ def _format_entity_yaml(entity):
1315
+ """Format a single entity as a YAML block matching the existing style."""
1316
+ lines = []
1317
+ lines.append(f" - id: {entity['id']}")
1318
+ lines.append(f" type: {entity.get('type', 'person')}")
1319
+ if entity.get('name'):
1320
+ lines.append(f" name: {entity['name']}")
1321
+ if entity.get('aliases'):
1322
+ aliases_str = json.dumps(entity['aliases'])
1323
+ lines.append(f" aliases: {aliases_str}")
1324
+ if entity.get('primary_company'):
1325
+ lines.append(f" primary_company: {entity['primary_company']}")
1326
+ if entity.get('primary_role'):
1327
+ lines.append(f" primary_role: {entity['primary_role']}")
1328
+ if entity.get('email'):
1329
+ lines.append(f" email: {entity['email']}")
1330
+ if entity.get('phone'):
1331
+ lines.append(f' phone: "{entity["phone"]}"')
1332
+ if entity.get('timezone'):
1333
+ lines.append(f" timezone: {entity['timezone']}")
1334
+ if entity.get('domain'):
1335
+ lines.append(f" domain: {entity['domain']}")
1336
+ if entity.get('confidence'):
1337
+ lines.append(f" confidence: {entity['confidence']}")
1338
+ if entity.get('first_seen'):
1339
+ lines.append(f' first_seen: "{entity["first_seen"]}"')
1340
+ if entity.get('extraction_source'):
1341
+ lines.append(f' extraction_source: "{entity["extraction_source"]}"')
1342
+ if entity.get('notes'):
1343
+ # Escape quotes in notes
1344
+ notes = str(entity['notes']).replace('"', '\\"')
1345
+ lines.append(f' notes: "{notes}"')
1346
+ lines.append('')
1347
+ return '\n'.join(lines)
1348
+
1349
+
1350
+ def _format_relationship_yaml(rel):
1351
+ """Format a single relationship as a YAML block matching the existing style."""
1352
+ lines = []
1353
+ lines.append(f" - id: {rel['id']}")
1354
+ lines.append(f" subject: {rel.get('subject', '')}")
1355
+ lines.append(f" predicate: {rel.get('predicate', '')}")
1356
+ lines.append(f" object: {rel.get('object', '')}")
1357
+ lines.append(f" status: {rel.get('status', 'inferred')}")
1358
+ if rel.get('role'):
1359
+ lines.append(f" role: {rel['role']}")
1360
+ if rel.get('scope'):
1361
+ lines.append(f' scope: "{rel["scope"]}"')
1362
+ lines.append(f" confidence: {rel.get('confidence', 'extracted')}")
1363
+ if rel.get('source'):
1364
+ src = rel['source']
1365
+ if isinstance(src, dict):
1366
+ lines.append(f" source:")
1367
+ lines.append(f" type: {src.get('type', 'interaction')}")
1368
+ ref = str(src.get('ref', '')).replace('"', '\\"')
1369
+ lines.append(f' ref: "{ref}"')
1370
+ lines.append(f" confidence: {src.get('confidence', 'medium')}")
1371
+ else:
1372
+ src_str = str(src).replace('"', '\\"')
1373
+ lines.append(f' source: "{src_str}"')
1374
+ if rel.get('notes'):
1375
+ notes = str(rel['notes']).replace('"', '\\"')
1376
+ lines.append(f' notes: "{notes}"')
1377
+ lines.append('')
1378
+ return '\n'.join(lines)
1379
+
1380
+
1381
+ def _invalidate_cache():
1382
+ """Remove the JSON cache so it gets rebuilt on next read."""
1383
+ try:
1384
+ if ENTITY_INDEX_CACHE_PATH.exists():
1385
+ ENTITY_INDEX_CACHE_PATH.unlink()
1386
+ except Exception:
1387
+ pass
1388
+
1389
+
1390
+ # ─────────────────────────────────────────────
1391
+ # Scan mode — process today's unprocessed items
1392
+ # ─────────────────────────────────────────────
1393
+ def scan_today(dry_run=False):
1394
+ """Scan all inbox directories for today's items and process them.
1395
+
1396
+ Returns overall stats dict.
1397
+ """
1398
+ today = datetime.now(timezone.utc).strftime('%Y-%m-%d')
1399
+ total_stats = {
1400
+ 'files_scanned': 0,
1401
+ 'files_with_facts': 0,
1402
+ 'entities_added': 0,
1403
+ 'relationships_added': 0,
1404
+ 'entities_updated': 0,
1405
+ 'skipped': 0,
1406
+ 'errors': 0,
1407
+ 'details': [],
1408
+ }
1409
+
1410
+ # Load entity index once
1411
+ data = load_entity_index()
1412
+ lookups = build_lookups(data)
1413
+
1414
+ # Scan each inbox subdirectory
1415
+ inbox_dirs = ['slack', 'gmail', 'calendar', 'internal', 'whatsapp', 'sms']
1416
+ for subdir in inbox_dirs:
1417
+ inbox_path = INBOX_DIR / subdir
1418
+ if not inbox_path.exists():
1419
+ continue
1420
+
1421
+ for item_file in sorted(inbox_path.iterdir()):
1422
+ if item_file.is_dir():
1423
+ continue
1424
+ # Process both .json and .yaml files (including .processed ones)
1425
+ if not (item_file.suffix in ('.json', '.yaml', '.yml') or
1426
+ str(item_file).endswith('.json.processed') or
1427
+ str(item_file).endswith('.yaml.processed') or
1428
+ str(item_file).endswith('.yml.processed')):
1429
+ continue
1430
+
1431
+ total_stats['files_scanned'] += 1
1432
+
1433
+ try:
1434
+ interaction = parse_input_file(item_file)
1435
+ facts = extract_facts(interaction, lookups)
1436
+ if facts:
1437
+ total_stats['files_with_facts'] += 1
1438
+ result = apply_facts(data, facts, lookups, dry_run=dry_run)
1439
+ total_stats['entities_added'] += result['entities_added']
1440
+ total_stats['relationships_added'] += result['relationships_added']
1441
+ total_stats['entities_updated'] += result['entities_updated']
1442
+ total_stats['skipped'] += result['skipped']
1443
+ total_stats['details'].extend(result['details'])
1444
+ except Exception as e:
1445
+ total_stats['errors'] += 1
1446
+ total_stats['details'].append(f"! Error processing {item_file.name}: {e}")
1447
+
1448
+ # Also scan the processed directory
1449
+ if PROCESSED_DIR.exists():
1450
+ for item_file in sorted(PROCESSED_DIR.iterdir()):
1451
+ if item_file.is_dir():
1452
+ continue
1453
+ if not (item_file.suffix in ('.json', '.yaml', '.yml') or
1454
+ str(item_file).endswith('.json.processed')):
1455
+ continue
1456
+
1457
+ total_stats['files_scanned'] += 1
1458
+
1459
+ try:
1460
+ interaction = parse_input_file(item_file)
1461
+ facts = extract_facts(interaction, lookups)
1462
+ if facts:
1463
+ total_stats['files_with_facts'] += 1
1464
+ result = apply_facts(data, facts, lookups, dry_run=dry_run)
1465
+ total_stats['entities_added'] += result['entities_added']
1466
+ total_stats['relationships_added'] += result['relationships_added']
1467
+ total_stats['entities_updated'] += result['entities_updated']
1468
+ total_stats['skipped'] += result['skipped']
1469
+ total_stats['details'].extend(result['details'])
1470
+ except Exception as e:
1471
+ total_stats['errors'] += 1
1472
+ total_stats['details'].append(f"! Error processing {item_file.name}: {e}")
1473
+
1474
+ # Write updates if not dry run
1475
+ if not dry_run and (total_stats['entities_added'] > 0 or
1476
+ total_stats['relationships_added'] > 0 or
1477
+ total_stats['entities_updated'] > 0):
1478
+ write_entity_index(data)
1479
+
1480
+ return total_stats
1481
+
1482
+
1483
+ # ─────────────────────────────────────────────
1484
+ # CLI entry point
1485
+ # ─────────────────────────────────────────────
1486
+ def main():
1487
+ parser = argparse.ArgumentParser(
1488
+ description='Post-interaction auto-indexer — extract entities and relationships from interactions.'
1489
+ )
1490
+ group = parser.add_mutually_exclusive_group(required=True)
1491
+ group.add_argument('--input', dest='input_file',
1492
+ help='Path to an inbox item file (JSON or YAML)')
1493
+ group.add_argument('--scan-today', action='store_true',
1494
+ help='Scan all inbox directories for items to process')
1495
+ group.add_argument('--stdin', action='store_true',
1496
+ help='Read interaction data from stdin')
1497
+
1498
+ parser.add_argument('--dry-run', action='store_true',
1499
+ help='Show what would be extracted without writing to index')
1500
+
1501
+ args = parser.parse_args()
1502
+
1503
+ t0 = time.time()
1504
+
1505
+ try:
1506
+ if args.scan_today:
1507
+ # Scan mode
1508
+ stats = scan_today(dry_run=args.dry_run)
1509
+ elapsed = round((time.time() - t0) * 1000, 1)
1510
+
1511
+ prefix = "[DRY RUN] " if args.dry_run else ""
1512
+ print(f"{prefix}Post-interaction indexer — scan complete")
1513
+ print(f" Files scanned: {stats['files_scanned']}")
1514
+ print(f" Files with new facts: {stats['files_with_facts']}")
1515
+ print(f" Entities added: {stats['entities_added']}")
1516
+ print(f" Relationships added: {stats['relationships_added']}")
1517
+ print(f" Entities updated: {stats['entities_updated']}")
1518
+ print(f" Skipped (dupes): {stats['skipped']}")
1519
+ if stats['errors']:
1520
+ print(f" Errors: {stats['errors']}")
1521
+ print(f" Elapsed: {elapsed}ms")
1522
+
1523
+ if stats['details']:
1524
+ print(f"\n Changes:")
1525
+ for detail in stats['details']:
1526
+ print(f" {detail}")
1527
+
1528
+ if stats['entities_added'] > 0 or stats['relationships_added'] > 0 or stats['entities_updated'] > 0:
1529
+ sys.exit(0)
1530
+ else:
1531
+ sys.exit(1)
1532
+
1533
+ else:
1534
+ # Single item mode
1535
+ if args.stdin:
1536
+ text = sys.stdin.read()
1537
+ interaction = parse_input_text(text, 'stdin')
1538
+ else:
1539
+ interaction = parse_input_file(args.input_file)
1540
+
1541
+ # Load index
1542
+ data = load_entity_index()
1543
+ lookups = build_lookups(data)
1544
+
1545
+ # Extract facts
1546
+ facts = extract_facts(interaction, lookups)
1547
+ elapsed_extract = round((time.time() - t0) * 1000, 1)
1548
+
1549
+ if not facts:
1550
+ prefix = "[DRY RUN] " if args.dry_run else ""
1551
+ print(f"{prefix}No new facts extracted from interaction.")
1552
+ print(f" Source: {interaction.get('source_type', '?')} | "
1553
+ f"Sender: {interaction.get('sender_name') or interaction.get('sender_email') or 'unknown'}")
1554
+ print(f" Elapsed: {elapsed_extract}ms")
1555
+ sys.exit(1)
1556
+
1557
+ # Apply facts
1558
+ stats = apply_facts(data, facts, lookups, dry_run=args.dry_run)
1559
+
1560
+ # Write if not dry run
1561
+ if not args.dry_run and (stats['entities_added'] > 0 or
1562
+ stats['relationships_added'] > 0 or
1563
+ stats['entities_updated'] > 0):
1564
+ write_entity_index(data)
1565
+
1566
+ elapsed_total = round((time.time() - t0) * 1000, 1)
1567
+
1568
+ prefix = "[DRY RUN] " if args.dry_run else ""
1569
+ print(f"{prefix}Post-interaction indexer — extraction complete")
1570
+ print(f" Source: {interaction.get('source_type', '?')} | "
1571
+ f"Sender: {interaction.get('sender_name') or interaction.get('sender_email') or 'unknown'}")
1572
+ print(f" Facts extracted: {len(facts)}")
1573
+ print(f" Entities added: {stats['entities_added']}")
1574
+ print(f" Relationships added: {stats['relationships_added']}")
1575
+ print(f" Entities updated: {stats['entities_updated']}")
1576
+ print(f" Skipped (dupes): {stats['skipped']}")
1577
+ print(f" Elapsed: {elapsed_total}ms")
1578
+
1579
+ if stats['details']:
1580
+ print(f"\n Changes:")
1581
+ for detail in stats['details']:
1582
+ print(f" {detail}")
1583
+
1584
+ if stats['entities_added'] > 0 or stats['relationships_added'] > 0 or stats['entities_updated'] > 0:
1585
+ sys.exit(0)
1586
+ else:
1587
+ sys.exit(1)
1588
+
1589
+ except FileNotFoundError as e:
1590
+ print(f"Error: {e}", file=sys.stderr)
1591
+ sys.exit(2)
1592
+ except Exception as e:
1593
+ print(f"Error: {e}", file=sys.stderr)
1594
+ sys.exit(2)
1595
+
1596
+
1597
+ if __name__ == '__main__':
1598
+ main()