@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.
- package/.claude/commands/init-agent.md +99 -0
- package/.claude/commands/init-maestro.md +565 -0
- package/.claude/settings.json +114 -0
- package/.env.example +152 -0
- package/README.md +491 -0
- package/agents/board-prep/agent.md +80 -0
- package/agents/browser-operator/agent.md +52 -0
- package/agents/calendar-ops/agent.md +50 -0
- package/agents/capital-raising/agent.md +69 -0
- package/agents/ceo-briefing/agent.md +72 -0
- package/agents/communications/agent.md +69 -0
- package/agents/competitive-intelligence/agent.md +49 -0
- package/agents/corporate-development/agent.md +66 -0
- package/agents/decision-log/agent.md +65 -0
- package/agents/desktop-operator/agent.md +59 -0
- package/agents/engineering-coordination/agent.md +51 -0
- package/agents/founder-voice/agent.md +72 -0
- package/agents/fund-ops/agent.md +52 -0
- package/agents/gmail-operator/agent.md +62 -0
- package/agents/hiring-org-design/agent.md +119 -0
- package/agents/inbound-dispatcher/agent.md +66 -0
- package/agents/legal-structuring/agent.md +65 -0
- package/agents/market-research/agent.md +48 -0
- package/agents/partnerships/agent.md +59 -0
- package/agents/platform-architecture/agent.md +57 -0
- package/agents/pmo-execution/agent.md +60 -0
- package/agents/product-strategy/agent.md +50 -0
- package/agents/regulatory-dfsa/agent.md +96 -0
- package/agents/risk-register/agent.md +62 -0
- package/agents/rollup-target-sourcing/agent.md +59 -0
- package/agents/session-spawner/agent.md +64 -0
- package/agents/slack-operator/agent.md +60 -0
- package/agents/sophie-chief-of-staff/agent.md +134 -0
- package/agents/strategic-planning/agent.md +54 -0
- package/agents/whatsapp-operator/agent.md +60 -0
- package/agents/workflow-automation/agent.md +61 -0
- package/bin/maestro.mjs +388 -0
- package/desktop-control/README.md +56 -0
- package/desktop-control/app-profiles/gmail.yaml +120 -0
- package/desktop-control/app-profiles/slack.yaml +315 -0
- package/desktop-control/app-profiles/whatsapp.yaml +107 -0
- package/docs/architecture/agent-topology.md +2222 -0
- package/docs/architecture/continuous-monitoring.md +221 -0
- package/docs/architecture/mcp-capability-map.md +560 -0
- package/docs/architecture/system-architecture.md +1273 -0
- package/docs/business-synthesis/ADAPTIC-GROUP-FINAL-OWNERSHIP-STRUCTURE.pdf +13667 -10
- package/docs/business-synthesis/adaptic-overview.md +296 -0
- package/docs/business-synthesis/executive-operating-model.md +261 -0
- package/docs/governance/action-approval-model.md +331 -0
- package/docs/governance/communications-policy.md +410 -0
- package/docs/governance/desktop-control-safety.md +499 -0
- package/docs/guides/agent-persona-setup.md +600 -0
- package/docs/operating-charter.md +87 -0
- package/docs/prompts/board-pack-cover-template.md +37 -0
- package/docs/prompts/decision-recommendation-template.md +88 -0
- package/docs/prompts/followup-message-template.md +141 -0
- package/docs/prompts/investor-letter-template.md +52 -0
- package/docs/prompts/morning-brief-template.md +82 -0
- package/docs/prompts/presentation-template.md +58 -0
- package/docs/prompts/weekly-strategic-memo-template.md +104 -0
- package/docs/runbooks/mac-mini-bootstrap.md +404 -0
- package/docs/runbooks/perpetual-operations.md +505 -0
- package/docs/runbooks/recovery-and-failover.md +588 -0
- package/docs/superpowers/plans/2026-04-02-phase0-operational-foundation.md +2550 -0
- package/docs/superpowers/plans/2026-04-03-phase1-executive-core.md +1085 -0
- package/docs/superpowers/plans/2026-04-03-phase2-people-product-commercial.md +739 -0
- package/docs/superpowers/plans/2026-04-05-information-barrier-implementation.md +926 -0
- package/docs/superpowers/plans/2026-04-06-session-context-dedup.md +1994 -0
- package/docs/superpowers/specs/2026-04-02-phase0-operational-foundation-design.md +842 -0
- package/docs/superpowers/specs/2026-04-03-phase1-executive-core-design.md +516 -0
- package/docs/superpowers/specs/2026-04-03-phase2-people-product-commercial-design.md +452 -0
- package/docs/superpowers/specs/2026-04-03-phase3-4-final-towers-design.md +129 -0
- package/docs/superpowers/specs/2026-04-05-information-barrier-design.md +678 -0
- package/docs/superpowers/specs/2026-04-05-reactive-daemon-design.md +237 -0
- package/docs/superpowers/specs/2026-04-06-session-context-dedup-design.md +369 -0
- package/docs/workflows/executive-cadence.md +218 -0
- package/ingest/README.md +87 -0
- package/mcp/README.md +51 -0
- package/package.json +48 -0
- package/plugins/maestro-skills/plugin.json +55 -0
- package/plugins/maestro-skills/skills/board-deck.md +68 -0
- package/plugins/maestro-skills/skills/decision-brief.md +89 -0
- package/plugins/maestro-skills/skills/draft-comms.md +84 -0
- package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
- package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
- package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
- package/plugins/maestro-skills/skills/morning-brief.md +54 -0
- package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
- package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
- package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
- package/plugins/maestro-skills/skills/slack-followup.md +64 -0
- package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
- package/policies/action-classification.yaml +110 -0
- package/policies/information-barriers.yaml +119 -0
- package/policies/prompt-injection-defence.yaml +138 -0
- package/public/assets/adaptic-icon-dark.png +0 -0
- package/public/assets/adaptic-icon-dark.svg +4 -0
- package/public/assets/adaptic-icon-light.svg +4 -0
- package/public/assets/adaptic-logo-dark.svg +17 -0
- package/public/assets/adaptic-logo-light.svg +17 -0
- package/scaffold/CLAUDE.md +21 -0
- package/scaffold/config/agent.ts +69 -0
- package/scaffold/config/agent.ts.example +218 -0
- package/schedules/README.md +49 -0
- package/schedules/triggers/backlog-executor.md +102 -0
- package/schedules/triggers/daily-evening-wrap.md +142 -0
- package/schedules/triggers/daily-midday-sweep.md +58 -0
- package/schedules/triggers/daily-morning-brief.md +55 -0
- package/schedules/triggers/inbox-processor.md +115 -0
- package/schedules/triggers/meeting-action-capture.md +60 -0
- package/schedules/triggers/meeting-prep.md +69 -0
- package/schedules/triggers/quarterly-self-assessment.md +54 -0
- package/schedules/triggers/weekly-engineering-health.md +37 -0
- package/schedules/triggers/weekly-execution.md +67 -0
- package/schedules/triggers/weekly-hiring.md +53 -0
- package/schedules/triggers/weekly-priorities.md +38 -0
- package/schedules/triggers/weekly-strategic-memo.md +124 -0
- package/scripts/__pycache__/disclosure_assessment.cpython-313.pyc +0 -0
- package/scripts/__pycache__/disclosure_boundaries.cpython-313.pyc +0 -0
- package/scripts/__pycache__/email_quote_thread.cpython-313.pyc +0 -0
- package/scripts/__pycache__/email_thread_dedup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/mehran-inbox-poller.cpython-313.pyc +0 -0
- package/scripts/__pycache__/outbound_dedup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/pre-draft-context.cpython-313.pyc +0 -0
- package/scripts/__pycache__/pre_draft_lookup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/send-email-as-mehran.cpython-313.pyc +0 -0
- package/scripts/__pycache__/send-email-threaded.cpython-313.pyc +0 -0
- package/scripts/__pycache__/send-email-with-attachment.cpython-313.pyc +0 -0
- package/scripts/__pycache__/validate_outbound.cpython-313.pyc +0 -0
- package/scripts/airtable-crm-populate.md +99 -0
- package/scripts/archive-email.sh +41 -0
- package/scripts/comms-monitor.sh +285 -0
- package/scripts/configure-whatsapp-sandbox.sh +201 -0
- package/scripts/continuous-monitor.sh +86 -0
- package/scripts/daemon/classifier.mjs +355 -0
- package/scripts/daemon/context-compiler.mjs +490 -0
- package/scripts/daemon/dispatcher.mjs +385 -0
- package/scripts/daemon/health.mjs +72 -0
- package/scripts/daemon/prompt-builder.mjs +426 -0
- package/scripts/daemon/responder.mjs +547 -0
- package/scripts/daemon/session-lock.mjs +520 -0
- package/scripts/daemon/sophie-daemon.mjs +521 -0
- package/scripts/daemon/test-context-compiler.mjs +238 -0
- package/scripts/daemon/test-integration.mjs +130 -0
- package/scripts/daemon/test-session-lock.mjs +215 -0
- package/scripts/disclosure_assessment.py +873 -0
- package/scripts/disclosure_boundaries.py +562 -0
- package/scripts/email-signature-mehran.html +52 -0
- package/scripts/email-signature.html +44 -0
- package/scripts/email_quote_thread.py +167 -0
- package/scripts/email_thread_dedup.py +361 -0
- package/scripts/emergency-stop.sh +41 -0
- package/scripts/healthcheck.sh +110 -0
- package/scripts/hooks/block-mcp-slack-send.sh +7 -0
- package/scripts/hooks/post-action-log.sh +17 -0
- package/scripts/hooks/pre-send-audit.sh +57 -0
- package/scripts/hooks/session-end-log.sh +27 -0
- package/scripts/huddle/audio-bridge.mjs +664 -0
- package/scripts/huddle/boot-slack-cdp.sh +102 -0
- package/scripts/huddle/huddle-controller.mjs +942 -0
- package/scripts/huddle/huddle-server.mjs +1059 -0
- package/scripts/huddle/launch-slack.sh +232 -0
- package/scripts/huddle/node_modules/.package-lock.json +50 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/CHANGELOG.md +1677 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/LICENSE +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/README.md +674 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.js +226 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs +223 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/_vendor/partial-json-parser/parser.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/api-promise.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/bin/cli +53 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/bin/migration-config.json +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.mts +225 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.ts +225 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.js +536 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.mjs +531 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/client.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts +49 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts +49 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.js +76 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.mjs +72 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/api-promise.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.mts +47 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.ts +47 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.js +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.mjs +98 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/error.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.mts +63 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.ts +63 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.js +123 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.mjs +117 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/pagination.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.mts +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.ts +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.js +11 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.mjs +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/resource.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.mts +31 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.ts +31 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.js +282 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.mjs +277 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/streaming.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/core/uploads.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/error.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.mts +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.ts +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.js +35 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.mjs +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts +73 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.ts +73 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.js +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/builtin-types.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.mts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.ts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.js +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.mjs +12 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/constants.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts +10 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.ts +10 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.js +39 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.mjs +35 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/jsonl.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.ts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.js +113 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.mjs +108 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/decoders/line.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.mts +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.ts +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.js +162 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs +157 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/detect-platform.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.js +41 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.mjs +36 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/errors.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.mts +22 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.ts +22 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.js +79 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.mjs +74 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/headers.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.mts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.ts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.js +55 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.mjs +51 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/parse.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts +34 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.ts +34 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.js +14 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.mjs +10 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/request-options.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shim-types.d.mts +28 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shim-types.d.ts +28 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.mts +20 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.ts +20 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.js +92 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.mjs +85 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/shims.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.mts +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.ts +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.js +38 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.mjs +35 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/stream-utils.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts +45 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.ts +45 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.js +96 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.mjs +93 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/to-file.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/tslib.js +81 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/tslib.mjs +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.mts +67 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.ts +67 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.js +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/types.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts +42 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.ts +42 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.js +146 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.mjs +136 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/uploads.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.js +38 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.mjs +33 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/base64.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.mts +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.ts +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.js +31 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.mjs +26 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/bytes.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.mts +9 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.ts +9 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.js +22 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.mjs +18 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/env.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts +37 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.ts +37 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.js +86 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.mjs +80 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/log.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.mts +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.ts +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.js +58 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.mjs +53 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/path.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.js +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/sleep.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.mts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.ts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.js +19 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.mts +16 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.ts +16 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.js +109 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.mjs +92 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils/values.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.mts +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.ts +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.js +11 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.mjs +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/internal/utils.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.mts +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.ts +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.js +553 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.mjs +549 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/BetaMessageStream.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.mts +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts +114 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.js +553 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.mjs +549 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/lib/MessageStream.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/package.json +185 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/pagination.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resource.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts +61 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts +61 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +25 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +20 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts +151 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.ts +151 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.js +122 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +118 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.ts +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.js +13 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts +343 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.ts +343 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.js +204 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.mjs +200 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/batches.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.js +9 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.mjs +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts +1561 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.ts +1561 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.js +86 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.mjs +81 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages/messages.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/messages.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts +74 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.ts +74 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.js +60 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.mjs +56 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta/models.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/beta.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.mts +183 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.ts +183 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.js +23 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.mjs +19 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/completions.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.mts +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.ts +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.js +15 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.mjs +7 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts +304 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.ts +304 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.js +153 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.mjs +149 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/batches.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.js +9 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.mjs +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/index.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts +1264 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts +1264 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.js +72 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.mjs +67 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages/messages.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/messages.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.mts +59 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.ts +59 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.js +45 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.mjs +41 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/models.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.mts +42 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.ts +42 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.js +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/shared.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.js +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.mjs +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources/top-level.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.js +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/resources.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/_vendor/partial-json-parser/README.md +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/_vendor/partial-json-parser/parser.ts +264 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/api-promise.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/client.ts +1070 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/README.md +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/api-promise.ts +101 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/error.ts +133 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/pagination.ts +201 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/resource.ts +11 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/streaming.ts +331 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/core/uploads.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/error.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/index.ts +23 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/README.md +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/builtin-types.ts +93 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/constants.ts +12 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/decoders/jsonl.ts +48 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/decoders/line.ts +135 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/detect-platform.ts +196 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/errors.ts +33 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/headers.ts +99 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/parse.ts +84 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/request-options.ts +39 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/shim-types.d.ts +28 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/shims.ts +107 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/stream-utils.ts +32 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/to-file.ts +159 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/types.ts +92 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/uploads.ts +193 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/base64.ts +40 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/bytes.ts +32 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/env.ts +18 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/log.ts +127 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/path.ts +65 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/sleep.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/uuid.ts +17 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils/values.ts +102 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/internal/utils.ts +8 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/.keep +4 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/BetaMessageStream.ts +683 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/lib/MessageStream.ts +684 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/pagination.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resource.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/beta.ts +380 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/files.ts +258 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/index.ts +148 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/batches.ts +502 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/index.ts +135 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages/messages.ts +2249 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/messages.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta/models.ts +118 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/beta.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/completions.ts +231 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/index.ts +121 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/batches.ts +396 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/index.ts +110 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages/messages.ts +1783 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/messages.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/models.ts +103 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/shared.ts +72 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources/top-level.ts +3 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/resources.ts +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/streaming.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/tsconfig.json +11 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/uploads.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/src/version.ts +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/streaming.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.js +6 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/uploads.mjs.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.mts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.mts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.ts +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.d.ts.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.js +5 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.js.map +1 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.mjs +2 -0
- package/scripts/huddle/node_modules/@anthropic-ai/sdk/version.mjs.map +1 -0
- package/scripts/huddle/node_modules/dotenv/CHANGELOG.md +520 -0
- package/scripts/huddle/node_modules/dotenv/LICENSE +23 -0
- package/scripts/huddle/node_modules/dotenv/README-es.md +411 -0
- package/scripts/huddle/node_modules/dotenv/README.md +645 -0
- package/scripts/huddle/node_modules/dotenv/SECURITY.md +1 -0
- package/scripts/huddle/node_modules/dotenv/config.d.ts +1 -0
- package/scripts/huddle/node_modules/dotenv/config.js +9 -0
- package/scripts/huddle/node_modules/dotenv/lib/cli-options.js +17 -0
- package/scripts/huddle/node_modules/dotenv/lib/env-options.js +28 -0
- package/scripts/huddle/node_modules/dotenv/lib/main.d.ts +162 -0
- package/scripts/huddle/node_modules/dotenv/lib/main.js +386 -0
- package/scripts/huddle/node_modules/dotenv/package.json +62 -0
- package/scripts/huddle/node_modules/ws/LICENSE +20 -0
- package/scripts/huddle/node_modules/ws/README.md +548 -0
- package/scripts/huddle/node_modules/ws/browser.js +8 -0
- package/scripts/huddle/node_modules/ws/index.js +22 -0
- package/scripts/huddle/node_modules/ws/lib/buffer-util.js +131 -0
- package/scripts/huddle/node_modules/ws/lib/constants.js +19 -0
- package/scripts/huddle/node_modules/ws/lib/event-target.js +292 -0
- package/scripts/huddle/node_modules/ws/lib/extension.js +203 -0
- package/scripts/huddle/node_modules/ws/lib/limiter.js +55 -0
- package/scripts/huddle/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/scripts/huddle/node_modules/ws/lib/receiver.js +706 -0
- package/scripts/huddle/node_modules/ws/lib/sender.js +602 -0
- package/scripts/huddle/node_modules/ws/lib/stream.js +161 -0
- package/scripts/huddle/node_modules/ws/lib/subprotocol.js +62 -0
- package/scripts/huddle/node_modules/ws/lib/validation.js +152 -0
- package/scripts/huddle/node_modules/ws/lib/websocket-server.js +554 -0
- package/scripts/huddle/node_modules/ws/lib/websocket.js +1393 -0
- package/scripts/huddle/node_modules/ws/package.json +70 -0
- package/scripts/huddle/node_modules/ws/wrapper.mjs +21 -0
- package/scripts/huddle/package-lock.json +62 -0
- package/scripts/huddle/package.json +22 -0
- package/scripts/huddle/setup-audio.sh +239 -0
- package/scripts/huddle/start-call.mjs +318 -0
- package/scripts/huddle/test-pipeline.mjs +263 -0
- package/scripts/llm_email_dedup.py +434 -0
- package/scripts/local-triggers/install-all.sh +43 -0
- package/scripts/local-triggers/plists/ai.adaptic.slack-events-server.plist +45 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-backlog-executor.plist +21 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-daemon.plist +32 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-inbox-processor.plist +21 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-meeting-action-capture.plist +21 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-meeting-prep.plist +21 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-midday-sweep.plist +26 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-quarterly-self-assessment.plist +62 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-engineering-health.plist +28 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-execution.plist +28 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-hiring.plist +28 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-priorities.plist +28 -0
- package/scripts/local-triggers/plists/ai.adaptic.sophie-weekly-strategic-memo.plist +28 -0
- package/scripts/local-triggers/run-trigger.sh +44 -0
- package/scripts/media-generation/README.md +103 -0
- package/scripts/media-generation/gemini-image-client.mjs +173 -0
- package/scripts/media-generation/generate-assets.mjs +289 -0
- package/scripts/media-generation/veo-video-client.mjs +219 -0
- package/scripts/mehran-inbox-poller.py +437 -0
- package/scripts/outbound-dedup-cleanup.sh +43 -0
- package/scripts/outbound-dedup.sh +476 -0
- package/scripts/outbound_dedup.py +115 -0
- package/scripts/parse-voice-transcript.mjs +486 -0
- package/scripts/pdf-generation/README.md +61 -0
- package/scripts/pdf-generation/build-document.mjs +230 -0
- package/scripts/pdf-generation/templates/board-pack.latex +136 -0
- package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
- package/scripts/pdf-generation/templates/memo.latex +114 -0
- package/scripts/poll-slack-events.sh +33 -0
- package/scripts/poller/calendar-poller.mjs +12 -0
- package/scripts/poller/gmail-poller.mjs +156 -0
- package/scripts/poller/imap-client.mjs +286 -0
- package/scripts/poller/index.mjs +73 -0
- package/scripts/poller/intra-session-check.mjs +267 -0
- package/scripts/poller/mehran-gmail-poller.mjs +98 -0
- package/scripts/poller/slack-poller.mjs +716 -0
- package/scripts/poller/trigger.mjs +47 -0
- package/scripts/poller/utils.mjs +253 -0
- package/scripts/poller-launchd/ai.adaptic.sophie-poller.plist +40 -0
- package/scripts/poller-launchd/ai.adaptic.sophie-whatsapp-handler.plist +39 -0
- package/scripts/poller-launchd/install.sh +38 -0
- package/scripts/post-interaction-indexer.py +1598 -0
- package/scripts/pre-draft-context.py +994 -0
- package/scripts/pre_draft_lookup.py +258 -0
- package/scripts/rag-indexer.py +629 -0
- package/scripts/resume-operations.sh +40 -0
- package/scripts/search-mehran-inbox.py +181 -0
- package/scripts/self-optimization/compute-metrics.py +377 -0
- package/scripts/send-difc-rfp.sh +98 -0
- package/scripts/send-email-as-mehran.py +369 -0
- package/scripts/send-email-threaded.py +336 -0
- package/scripts/send-email-with-attachment.py +360 -0
- package/scripts/send-email.sh +93 -0
- package/scripts/send-sms.sh +151 -0
- package/scripts/send-whatsapp.sh +261 -0
- package/scripts/session-start.sh +106 -0
- package/scripts/setup/configure-macos.sh +508 -0
- package/scripts/setup/init-agent.sh +450 -0
- package/scripts/slack-events-ctl.sh +177 -0
- package/scripts/slack-events-server.mjs +989 -0
- package/scripts/slack-react.mjs +89 -0
- package/scripts/slack-responded.sh +185 -0
- package/scripts/slack-send.sh +190 -0
- package/scripts/slack-typing.mjs +196 -0
- package/scripts/slack-upload-v2.py +95 -0
- package/scripts/sms-handler.mjs +436 -0
- package/scripts/sophie-inbox-poller.py +406 -0
- package/scripts/spawn-session.sh +85 -0
- package/scripts/system-verify.sh +171 -0
- package/scripts/test-email-thread-dedup.py +239 -0
- package/scripts/test-information-barriers.py +484 -0
- package/scripts/test-llm-email-dedup.py +251 -0
- package/scripts/test-pre-draft-integration.py +203 -0
- package/scripts/test-rag-phase2.sh +442 -0
- package/scripts/test-rag-search.sh +251 -0
- package/scripts/test-voice-parser.mjs +316 -0
- package/scripts/user-context-search.py +660 -0
- package/scripts/validate-outbound.py +1493 -0
- package/scripts/whatsapp-handler.mjs +525 -0
- package/teams/desktop-operations.yaml +34 -0
- package/teams/executive-office.yaml +27 -0
- package/teams/legal-and-regulatory.yaml +24 -0
- package/teams/platform-and-engineering.yaml +23 -0
- package/teams/strategy-and-growth.yaml +29 -0
- package/workflows/continuous/inbound-monitor.yaml +168 -0
- package/workflows/daily/applicant-triage.yaml +197 -0
- package/workflows/daily/comms-triage.yaml +80 -0
- package/workflows/daily/evening-wrap.yaml +65 -0
- package/workflows/daily/morning-brief.yaml +147 -0
- package/workflows/daily/slack-followup-sweep.yaml +87 -0
- package/workflows/event-driven/README.md +50 -0
- package/workflows/monthly/board-readiness.yaml +76 -0
- package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
- package/workflows/session-protocol.md +171 -0
- package/workflows/weekly/hiring-review.yaml +169 -0
- package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
- 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()
|