@datasynx/agentic-crm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +767 -0
  3. package/dist/agent-config-zPvcqu07.js +14 -0
  4. package/dist/agent-config-zPvcqu07.js.map +1 -0
  5. package/dist/approvals-DpjxGHFp.js +67 -0
  6. package/dist/approvals-DpjxGHFp.js.map +1 -0
  7. package/dist/ask-CID3jnuL.js +52 -0
  8. package/dist/ask-CID3jnuL.js.map +1 -0
  9. package/dist/audit-log-DNMY9mUZ.js +49 -0
  10. package/dist/audit-log-DNMY9mUZ.js.map +1 -0
  11. package/dist/auth-CyFuu9X_.js +2 -0
  12. package/dist/auth-DFWwWcYD.js +93 -0
  13. package/dist/auth-DFWwWcYD.js.map +1 -0
  14. package/dist/autofill-Di_-SP7t.js +51 -0
  15. package/dist/autofill-Di_-SP7t.js.map +1 -0
  16. package/dist/backup-CeMk9z86.js +417 -0
  17. package/dist/backup-CeMk9z86.js.map +1 -0
  18. package/dist/backup-f_hC7rBV.js +2 -0
  19. package/dist/calendly-Bft_wwji.js +52 -0
  20. package/dist/calendly-Bft_wwji.js.map +1 -0
  21. package/dist/calendly-D3coO92o.cjs +53 -0
  22. package/dist/calendly-D3coO92o.cjs.map +1 -0
  23. package/dist/chunk-DakpK96I.cjs +43 -0
  24. package/dist/churn-C28IgnAj.js +54 -0
  25. package/dist/churn-C28IgnAj.js.map +1 -0
  26. package/dist/cli.js +4396 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/colors-BG07TZQz.js +11 -0
  29. package/dist/colors-BG07TZQz.js.map +1 -0
  30. package/dist/compliance-B1kk5-YS.js +115 -0
  31. package/dist/compliance-B1kk5-YS.js.map +1 -0
  32. package/dist/compliance-B91zNvCR.cjs +156 -0
  33. package/dist/compliance-B91zNvCR.cjs.map +1 -0
  34. package/dist/compliance-CKSBoQUe.js +118 -0
  35. package/dist/compliance-CKSBoQUe.js.map +1 -0
  36. package/dist/compliance-CujOqAKk.js +2 -0
  37. package/dist/context-builder-BzWAp3Zs.js +96 -0
  38. package/dist/context-builder-BzWAp3Zs.js.map +1 -0
  39. package/dist/context-builder-DlrRcqmJ.js +2 -0
  40. package/dist/conversation-intel-mm7Lhemh.js +72 -0
  41. package/dist/conversation-intel-mm7Lhemh.js.map +1 -0
  42. package/dist/custom-fields-CzNeD3_v.js +2 -0
  43. package/dist/custom-fields-Pl2t9xzp.js +73 -0
  44. package/dist/custom-fields-Pl2t9xzp.js.map +1 -0
  45. package/dist/custom-objects-BHgn1GEX.js +78 -0
  46. package/dist/custom-objects-BHgn1GEX.js.map +1 -0
  47. package/dist/custom-objects-CIFrmQ2V.js +2 -0
  48. package/dist/customer-dir-DIylZ8Q6.js +75 -0
  49. package/dist/customer-dir-DIylZ8Q6.js.map +1 -0
  50. package/dist/daemon/worker.js +207 -0
  51. package/dist/daemon/worker.js.map +1 -0
  52. package/dist/enrichment-3XvgGDfB.js +103 -0
  53. package/dist/enrichment-3XvgGDfB.js.map +1 -0
  54. package/dist/file-lock-B_zi7NQl.js +22 -0
  55. package/dist/file-lock-B_zi7NQl.js.map +1 -0
  56. package/dist/gmail-auth-BP6cJwfw.js +40 -0
  57. package/dist/gmail-auth-BP6cJwfw.js.map +1 -0
  58. package/dist/gmail-auth-DxakCtGm.cjs +44 -0
  59. package/dist/gmail-auth-DxakCtGm.cjs.map +1 -0
  60. package/dist/gmail-auth-OComS92L.js +40 -0
  61. package/dist/gmail-auth-OComS92L.js.map +1 -0
  62. package/dist/gmail-push-watch-DELQFMPk.js +20 -0
  63. package/dist/gmail-push-watch-DELQFMPk.js.map +1 -0
  64. package/dist/gmail-sender-StTpJ9Ub.js +32 -0
  65. package/dist/gmail-sender-StTpJ9Ub.js.map +1 -0
  66. package/dist/gmail-sync-DIaxInDT.js +204 -0
  67. package/dist/gmail-sync-DIaxInDT.js.map +1 -0
  68. package/dist/gmail-sync-hHm9gaWd.cjs +218 -0
  69. package/dist/gmail-sync-hHm9gaWd.cjs.map +1 -0
  70. package/dist/gmail-sync-rQaVqKWd.js +214 -0
  71. package/dist/gmail-sync-rQaVqKWd.js.map +1 -0
  72. package/dist/gmail-webhook-handler-DS7OlRPX.js +3 -0
  73. package/dist/gmail-webhook-handler-e5Od25FX.js +97 -0
  74. package/dist/gmail-webhook-handler-e5Od25FX.js.map +1 -0
  75. package/dist/goal-engine-CUZSpERI.js +2 -0
  76. package/dist/goal-engine-KpBftn4V.js +295 -0
  77. package/dist/goal-engine-KpBftn4V.js.map +1 -0
  78. package/dist/google-drive-sync-DEPcqFca.js +105 -0
  79. package/dist/google-drive-sync-DEPcqFca.js.map +1 -0
  80. package/dist/hybrid-search-BmHttLrR.js +40 -0
  81. package/dist/hybrid-search-BmHttLrR.js.map +1 -0
  82. package/dist/hygiene-DZqfYpFf.js +38 -0
  83. package/dist/hygiene-DZqfYpFf.js.map +1 -0
  84. package/dist/identity-CI6olMNm.js +41 -0
  85. package/dist/identity-CI6olMNm.js.map +1 -0
  86. package/dist/identity-gyfWdrcX.js +2 -0
  87. package/dist/import-hubspot-BaK71U_K.js +588 -0
  88. package/dist/import-hubspot-BaK71U_K.js.map +1 -0
  89. package/dist/index-V8BFaH-b.d.ts +539 -0
  90. package/dist/index-V8BFaH-b.d.ts.map +1 -0
  91. package/dist/index-YqwMd6aQ.d.cts +538 -0
  92. package/dist/index-YqwMd6aQ.d.cts.map +1 -0
  93. package/dist/index.cjs +185 -0
  94. package/dist/index.cjs.map +1 -0
  95. package/dist/index.d.cts +538 -0
  96. package/dist/index.d.cts.map +1 -0
  97. package/dist/index.d.ts +539 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +165 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/interactions-writer-CrPStUll.cjs +77 -0
  102. package/dist/interactions-writer-CrPStUll.cjs.map +1 -0
  103. package/dist/interactions-writer-DO3KcSR3.js +52 -0
  104. package/dist/interactions-writer-DO3KcSR3.js.map +1 -0
  105. package/dist/interactions-writer-SLHnoEeE.js +46 -0
  106. package/dist/interactions-writer-SLHnoEeE.js.map +1 -0
  107. package/dist/interactions-writer-dSPy1XfO.js +2 -0
  108. package/dist/knowledge-base-D0Fh40kc.js +1013 -0
  109. package/dist/knowledge-base-D0Fh40kc.js.map +1 -0
  110. package/dist/lancedb-CCBbpulq.js +2 -0
  111. package/dist/lancedb-rlvWoPwl.js +98 -0
  112. package/dist/lancedb-rlvWoPwl.js.map +1 -0
  113. package/dist/lead-model-BCFzyktm.js +109 -0
  114. package/dist/lead-model-BCFzyktm.js.map +1 -0
  115. package/dist/llm-DEjWcqmW.js +2 -0
  116. package/dist/llm-DvzZqva0.js +372 -0
  117. package/dist/llm-DvzZqva0.js.map +1 -0
  118. package/dist/llm-Z8RIYkpF.js +174 -0
  119. package/dist/llm-Z8RIYkpF.js.map +1 -0
  120. package/dist/llm-iijeXmgq.cjs +198 -0
  121. package/dist/llm-iijeXmgq.cjs.map +1 -0
  122. package/dist/mcp-CdTJWTJf.d.cts +12 -0
  123. package/dist/mcp-CdTJWTJf.d.cts.map +1 -0
  124. package/dist/mcp-CdTJWTJf.d.ts +12 -0
  125. package/dist/mcp-CdTJWTJf.d.ts.map +1 -0
  126. package/dist/mcp.cjs +7464 -0
  127. package/dist/mcp.cjs.map +1 -0
  128. package/dist/mcp.d.cts +12 -0
  129. package/dist/mcp.d.cts.map +1 -0
  130. package/dist/mcp.d.ts +12 -0
  131. package/dist/mcp.d.ts.map +1 -0
  132. package/dist/mcp.js +7448 -0
  133. package/dist/mcp.js.map +1 -0
  134. package/dist/memory-Bb6ky3kb.js +58 -0
  135. package/dist/memory-Bb6ky3kb.js.map +1 -0
  136. package/dist/memory-Cy6-Tbyl.js +2 -0
  137. package/dist/metrics-DH8wHvya.js +26 -0
  138. package/dist/metrics-DH8wHvya.js.map +1 -0
  139. package/dist/microsoft-auth-B8_S45gh.js +17 -0
  140. package/dist/microsoft-auth-B8_S45gh.js.map +1 -0
  141. package/dist/microsoft-calendar-B6MMtUQK.js +67 -0
  142. package/dist/microsoft-calendar-B6MMtUQK.js.map +1 -0
  143. package/dist/microsoft-sync-CpZVoSuq.js +68 -0
  144. package/dist/microsoft-sync-CpZVoSuq.js.map +1 -0
  145. package/dist/nba-3wanmJ0U.js +48 -0
  146. package/dist/nba-3wanmJ0U.js.map +1 -0
  147. package/dist/notification-dispatcher-0vYNngWe.js +97 -0
  148. package/dist/notification-dispatcher-0vYNngWe.js.map +1 -0
  149. package/dist/opportunity-score-BTMOQSTV.js +47 -0
  150. package/dist/opportunity-score-BTMOQSTV.js.map +1 -0
  151. package/dist/pipedrive-client-CdGKpH9b.js +17 -0
  152. package/dist/pipedrive-client-CdGKpH9b.js.map +1 -0
  153. package/dist/pipeline-writer-BqBrYrQc.js +2 -0
  154. package/dist/pipeline-writer-BvVquKIe.js +96 -0
  155. package/dist/pipeline-writer-BvVquKIe.js.map +1 -0
  156. package/dist/pipeline-writer-N2omexxp.cjs +121 -0
  157. package/dist/pipeline-writer-N2omexxp.cjs.map +1 -0
  158. package/dist/pipeline-writer-eufx_0o1.js +102 -0
  159. package/dist/pipeline-writer-eufx_0o1.js.map +1 -0
  160. package/dist/proactive-agent-BgQXw3ac.js +96 -0
  161. package/dist/proactive-agent-BgQXw3ac.js.map +1 -0
  162. package/dist/proactive-worker-BrLHNhjH.js +229 -0
  163. package/dist/proactive-worker-BrLHNhjH.js.map +1 -0
  164. package/dist/push-manager-CdqIIkuh.js +108 -0
  165. package/dist/push-manager-CdqIIkuh.js.map +1 -0
  166. package/dist/push-manager-CowY-0IK.js +2 -0
  167. package/dist/quote-generator-BfwENXzg.js +133 -0
  168. package/dist/quote-generator-BfwENXzg.js.map +1 -0
  169. package/dist/quote-generator-OhSFsi3x.js +2 -0
  170. package/dist/rbac-C7c8tcES.js +2 -0
  171. package/dist/rbac-CTIktZaC.js +91 -0
  172. package/dist/rbac-CTIktZaC.js.map +1 -0
  173. package/dist/relationship-health-odxEoQdJ.js +454 -0
  174. package/dist/relationship-health-odxEoQdJ.js.map +1 -0
  175. package/dist/revenue-simulation-BJdRTEHc.js +2 -0
  176. package/dist/revenue-simulation-Bqf2DLVB.js +251 -0
  177. package/dist/revenue-simulation-Bqf2DLVB.js.map +1 -0
  178. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  179. package/dist/salesforce-client-rhZFa_p5.js +51 -0
  180. package/dist/salesforce-client-rhZFa_p5.js.map +1 -0
  181. package/dist/segments-BqcD5HIl.js +61 -0
  182. package/dist/segments-BqcD5HIl.js.map +1 -0
  183. package/dist/sequence-engine-CCTHEBgi.js +2 -0
  184. package/dist/sequence-engine-J1lTW_in.js +91 -0
  185. package/dist/sequence-engine-J1lTW_in.js.map +1 -0
  186. package/dist/sequence-store-DaaWr0Os.js +221 -0
  187. package/dist/sequence-store-DaaWr0Os.js.map +1 -0
  188. package/dist/server-Dyva03K8.js +4287 -0
  189. package/dist/server-Dyva03K8.js.map +1 -0
  190. package/dist/session-B9AilxOE.js +81 -0
  191. package/dist/session-B9AilxOE.js.map +1 -0
  192. package/dist/session-D0qFkBla.cjs +82 -0
  193. package/dist/session-D0qFkBla.cjs.map +1 -0
  194. package/dist/session-D9ub6Wl1.js +79 -0
  195. package/dist/session-D9ub6Wl1.js.map +1 -0
  196. package/dist/session-mWHA71Lw.js +2 -0
  197. package/dist/session-store-B0QZE8Bx.cjs +697 -0
  198. package/dist/session-store-B0QZE8Bx.cjs.map +1 -0
  199. package/dist/session-store-C8tEvMPw.js +543 -0
  200. package/dist/session-store-C8tEvMPw.js.map +1 -0
  201. package/dist/session-store-CEa39Dxs.js +15 -0
  202. package/dist/session-store-CEa39Dxs.js.map +1 -0
  203. package/dist/sla-engine-5IhTsBUR.js +2 -0
  204. package/dist/sla-engine-BqX-7u-7.js +53 -0
  205. package/dist/sla-engine-BqX-7u-7.js.map +1 -0
  206. package/dist/sop-DkhVChGy.js +2 -0
  207. package/dist/sop-Vp0UPWFW.js +70 -0
  208. package/dist/sop-Vp0UPWFW.js.map +1 -0
  209. package/dist/survey-engine-C06hcQt3.js +2 -0
  210. package/dist/survey-engine-DBjCYqCv.js +147 -0
  211. package/dist/survey-engine-DBjCYqCv.js.map +1 -0
  212. package/dist/sync-state-ChaLbamC.js +33 -0
  213. package/dist/sync-state-ChaLbamC.js.map +1 -0
  214. package/dist/sync-state-CwLSt_1m.js +2 -0
  215. package/dist/ticket-writer-CjqKeIRD.js +2 -0
  216. package/dist/ticket-writer-j2oX_Wal.js +134 -0
  217. package/dist/ticket-writer-j2oX_Wal.js.map +1 -0
  218. package/dist/tone-Bdm5uaht.js +48 -0
  219. package/dist/tone-Bdm5uaht.js.map +1 -0
  220. package/dist/tone-DRKlZgPr.cjs +43 -0
  221. package/dist/tone-DRKlZgPr.cjs.map +1 -0
  222. package/dist/tone-vNb2DAAD.js +39 -0
  223. package/dist/tone-vNb2DAAD.js.map +1 -0
  224. package/dist/transcript-watcher-CL2QUygI.js +132 -0
  225. package/dist/transcript-watcher-CL2QUygI.js.map +1 -0
  226. package/dist/unmatched-transcripts-BsH5bhkU.js +26 -0
  227. package/dist/unmatched-transcripts-BsH5bhkU.js.map +1 -0
  228. package/dist/unmatched-transcripts-D0PrJ9iz.js +2 -0
  229. package/dist/update-deal-BNwPGaTV.js +2 -0
  230. package/dist/update-deal-DKC79skb.js +91 -0
  231. package/dist/update-deal-DKC79skb.js.map +1 -0
  232. package/dist/usage-CClTf5e6.cjs +57 -0
  233. package/dist/usage-CClTf5e6.cjs.map +1 -0
  234. package/dist/usage-D0-TYJkw.js +93 -0
  235. package/dist/usage-D0-TYJkw.js.map +1 -0
  236. package/dist/usage-D0u9a-lV.js +54 -0
  237. package/dist/usage-D0u9a-lV.js.map +1 -0
  238. package/dist/vault-C1D3zScD.js +2 -0
  239. package/dist/vault-DXCg29W-.js +86 -0
  240. package/dist/vault-DXCg29W-.js.map +1 -0
  241. package/dist/webhooks-7EpA05Qr.js +138 -0
  242. package/dist/webhooks-7EpA05Qr.js.map +1 -0
  243. package/dist/webhooks-BO2UAnmn.js +94 -0
  244. package/dist/webhooks-BO2UAnmn.js.map +1 -0
  245. package/dist/webhooks-Xn6zO6kd.cjs +97 -0
  246. package/dist/webhooks-Xn6zO6kd.cjs.map +1 -0
  247. package/dist/write-queue-BDolUxfs.cjs +26 -0
  248. package/dist/write-queue-BDolUxfs.cjs.map +1 -0
  249. package/dist/write-queue-IbsAjUnh.js +21 -0
  250. package/dist/write-queue-IbsAjUnh.js.map +1 -0
  251. package/package.json +142 -0
@@ -0,0 +1,103 @@
1
+ import { a as writeMainFacts, i as readMainFacts } from "./customer-dir-DIylZ8Q6.js";
2
+ import { t as getSecret } from "./vault-DXCg29W-.js";
3
+ //#region src/core/enrichment.ts
4
+ const ENRICHABLE = [
5
+ "domain",
6
+ "email",
7
+ "phone",
8
+ "industry"
9
+ ];
10
+ function isEmpty(v) {
11
+ return v === void 0 || v === null || typeof v === "string" && v.trim() === "";
12
+ }
13
+ /** Merge additions into base, only filling fields that are currently empty. */
14
+ function mergeEnrichment(base, additions) {
15
+ const out = { ...base };
16
+ for (const key of ENRICHABLE) {
17
+ const incoming = additions[key];
18
+ if (isEmpty(out[key]) && incoming !== void 0 && incoming.trim() !== "") out[key] = incoming;
19
+ }
20
+ return out;
21
+ }
22
+ const DEFAULT_PROVIDERS = [{
23
+ name: "domain-from-email",
24
+ enrich(input) {
25
+ const email = input.email ?? "";
26
+ const at = email.indexOf("@");
27
+ if (at > 0 && at < email.length - 1) {
28
+ const domain = email.slice(at + 1).trim().toLowerCase();
29
+ if (domain) return { domain };
30
+ }
31
+ return {};
32
+ }
33
+ }];
34
+ /** Run providers in order, merging their output into the input (gaps only). */
35
+ async function runEnrichment(input, providers, ctx) {
36
+ let data = {
37
+ ...input.domain !== void 0 ? { domain: input.domain } : {},
38
+ ...input.email !== void 0 ? { email: input.email } : {},
39
+ ...input.phone !== void 0 ? { phone: input.phone } : {},
40
+ ...input.industry !== void 0 ? { industry: input.industry } : {}
41
+ };
42
+ for (const provider of providers) {
43
+ const additions = await provider.enrich({
44
+ ...data,
45
+ name: input.name
46
+ }, ctx);
47
+ data = mergeEnrichment(data, additions);
48
+ }
49
+ return data;
50
+ }
51
+ function vaultContext(dataDir) {
52
+ const key = process.env["DXCRM_VAULT_KEY"];
53
+ if (!key) return { getSecret: (n) => process.env[n] };
54
+ return { getSecret(name) {
55
+ try {
56
+ return getSecret(dataDir, key, name) ?? process.env[name];
57
+ } catch {
58
+ return process.env[name];
59
+ }
60
+ } };
61
+ }
62
+ /**
63
+ * Enrich one customer: read main_facts, run providers, and (optionally) write
64
+ * the newly-filled fields back. Existing human-entered facts are preserved.
65
+ */
66
+ async function enrichCustomer(dataDir, slug, opts = {}) {
67
+ const facts = await readMainFacts(dataDir, slug);
68
+ const providers = opts.providers ?? DEFAULT_PROVIDERS;
69
+ const ctx = opts.ctx ?? vaultContext(dataDir);
70
+ const before = {
71
+ ...facts.domain !== void 0 ? { domain: facts.domain } : {},
72
+ ...facts.email !== void 0 ? { email: facts.email } : {},
73
+ ...facts.phone !== void 0 ? { phone: facts.phone } : {},
74
+ ...facts.industry !== void 0 ? { industry: facts.industry } : {}
75
+ };
76
+ const merged = await runEnrichment({
77
+ name: facts.name,
78
+ ...before
79
+ }, providers, ctx);
80
+ const applied = {};
81
+ for (const key of ENRICHABLE) {
82
+ const value = merged[key];
83
+ if (isEmpty(before[key]) && value !== void 0 && value.trim() !== "") applied[key] = value;
84
+ }
85
+ let written = false;
86
+ if (opts.write && Object.keys(applied).length > 0) {
87
+ await writeMainFacts(dataDir, slug, {
88
+ ...facts,
89
+ ...applied,
90
+ updated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
91
+ });
92
+ written = true;
93
+ }
94
+ return {
95
+ applied,
96
+ merged,
97
+ written
98
+ };
99
+ }
100
+ //#endregion
101
+ export { enrichCustomer };
102
+
103
+ //# sourceMappingURL=enrichment-3XvgGDfB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrichment-3XvgGDfB.js","names":["vaultGetSecret"],"sources":["../src/core/enrichment.ts"],"sourcesContent":["import { readMainFacts, writeMainFacts } from \"../fs/customer-dir.js\";\nimport { getSecret as vaultGetSecret } from \"./vault.js\";\n\n/**\n * Enrichment layer (domino D15 / C6): a pluggable, vault-backed way to fill in\n * missing customer facts (domain, industry, contact info). The package ships an\n * offline built-in (derive domain from an email) and a provider interface so\n * external data providers can be added as plugins. Provider API keys come from\n * the D12 vault via the context — never from the markdown. Enrichment only fills\n * gaps; it never overwrites human-entered facts.\n */\nexport interface EnrichmentData {\n domain?: string;\n email?: string;\n phone?: string;\n industry?: string;\n}\n\nexport interface EnrichmentContext {\n /** Look up a provider credential from the local vault (or env). */\n getSecret(name: string): string | undefined;\n}\n\nexport interface EnrichmentInput extends EnrichmentData {\n name: string;\n}\n\nexport interface EnrichmentProvider {\n name: string;\n enrich(input: EnrichmentInput, ctx: EnrichmentContext): Promise<EnrichmentData> | EnrichmentData;\n}\n\nconst ENRICHABLE: Array<keyof EnrichmentData> = [\"domain\", \"email\", \"phone\", \"industry\"];\n\nfunction isEmpty(v: unknown): boolean {\n return v === undefined || v === null || (typeof v === \"string\" && v.trim() === \"\");\n}\n\n/** Merge additions into base, only filling fields that are currently empty. */\nexport function mergeEnrichment(base: EnrichmentData, additions: EnrichmentData): EnrichmentData {\n const out: EnrichmentData = { ...base };\n for (const key of ENRICHABLE) {\n const incoming = additions[key];\n if (isEmpty(out[key]) && incoming !== undefined && incoming.trim() !== \"\") out[key] = incoming;\n }\n return out;\n}\n\n/** Built-in, offline provider: derive a company domain from a contact email. */\nexport const domainFromEmailProvider: EnrichmentProvider = {\n name: \"domain-from-email\",\n enrich(input) {\n const email = input.email ?? \"\";\n const at = email.indexOf(\"@\");\n if (at > 0 && at < email.length - 1) {\n const domain = email\n .slice(at + 1)\n .trim()\n .toLowerCase();\n if (domain) return { domain };\n }\n return {};\n },\n};\n\nexport const DEFAULT_PROVIDERS: EnrichmentProvider[] = [domainFromEmailProvider];\n\n/** Run providers in order, merging their output into the input (gaps only). */\nexport async function runEnrichment(\n input: EnrichmentInput,\n providers: EnrichmentProvider[],\n ctx: EnrichmentContext\n): Promise<EnrichmentData> {\n let data: EnrichmentData = {\n ...(input.domain !== undefined ? { domain: input.domain } : {}),\n ...(input.email !== undefined ? { email: input.email } : {}),\n ...(input.phone !== undefined ? { phone: input.phone } : {}),\n ...(input.industry !== undefined ? { industry: input.industry } : {}),\n };\n for (const provider of providers) {\n const additions = await provider.enrich({ ...data, name: input.name }, ctx);\n data = mergeEnrichment(data, additions);\n }\n return data;\n}\n\nexport interface EnrichResult {\n /** Fields newly added by enrichment (i.e. previously empty). */\n applied: EnrichmentData;\n /** The full merged enrichment data. */\n merged: EnrichmentData;\n written: boolean;\n}\n\nfunction vaultContext(dataDir: string): EnrichmentContext {\n const key = process.env[\"DXCRM_VAULT_KEY\"];\n if (!key) return { getSecret: (n) => process.env[n] };\n return {\n getSecret(name: string) {\n try {\n return vaultGetSecret(dataDir, key, name) ?? process.env[name];\n } catch {\n return process.env[name];\n }\n },\n };\n}\n\n/**\n * Enrich one customer: read main_facts, run providers, and (optionally) write\n * the newly-filled fields back. Existing human-entered facts are preserved.\n */\nexport async function enrichCustomer(\n dataDir: string,\n slug: string,\n opts: { providers?: EnrichmentProvider[]; write?: boolean; ctx?: EnrichmentContext } = {}\n): Promise<EnrichResult> {\n const facts = await readMainFacts(dataDir, slug);\n const providers = opts.providers ?? DEFAULT_PROVIDERS;\n const ctx = opts.ctx ?? vaultContext(dataDir);\n\n const before: EnrichmentData = {\n ...(facts.domain !== undefined ? { domain: facts.domain } : {}),\n ...(facts.email !== undefined ? { email: facts.email } : {}),\n ...(facts.phone !== undefined ? { phone: facts.phone } : {}),\n ...(facts.industry !== undefined ? { industry: facts.industry } : {}),\n };\n const merged = await runEnrichment({ name: facts.name, ...before }, providers, ctx);\n\n const applied: EnrichmentData = {};\n for (const key of ENRICHABLE) {\n const value = merged[key];\n if (isEmpty(before[key]) && value !== undefined && value.trim() !== \"\") applied[key] = value;\n }\n\n let written = false;\n if (opts.write && Object.keys(applied).length > 0) {\n await writeMainFacts(dataDir, slug, {\n ...facts,\n ...applied,\n updated: new Date().toISOString().slice(0, 10),\n });\n written = true;\n }\n\n return { applied, merged, written };\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { t as withFileQueue } from "./write-queue-IbsAjUnh.js";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ //#region src/core/file-lock.ts
5
+ async function withJsonFile(filePath, updater) {
6
+ return withFileQueue(filePath, async () => {
7
+ let current = null;
8
+ if (fs.existsSync(filePath)) try {
9
+ current = JSON.parse(fs.readFileSync(filePath, "utf-8"));
10
+ } catch {
11
+ current = null;
12
+ }
13
+ const next = await updater(current);
14
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
15
+ fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf-8");
16
+ return next;
17
+ });
18
+ }
19
+ //#endregion
20
+ export { withJsonFile as t };
21
+
22
+ //# sourceMappingURL=file-lock-B_zi7NQl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock-B_zi7NQl.js","names":[],"sources":["../src/core/file-lock.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { withFileQueue } from \"../fs/write-queue.js\";\n\nexport async function withJsonFile<T>(\n filePath: string,\n updater: (current: T | null) => T | Promise<T>\n): Promise<T> {\n return withFileQueue(filePath, async () => {\n // Read current state\n let current: T | null = null;\n if (fs.existsSync(filePath)) {\n try {\n current = JSON.parse(fs.readFileSync(filePath, \"utf-8\") as string) as T;\n } catch {\n current = null;\n }\n }\n\n // Apply updater — may throw, in which case we do NOT write\n const next = await updater(current);\n\n // Write atomically (within the queue lock)\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(next, null, 2), \"utf-8\");\n\n return next;\n });\n}\n"],"mappings":";;;;AAIA,eAAsB,aACpB,UACA,SACY;CACZ,OAAO,cAAc,UAAU,YAAY;EAEzC,IAAI,UAAoB;EACxB,IAAI,GAAG,WAAW,QAAQ,GACxB,IAAI;GACF,UAAU,KAAK,MAAM,GAAG,aAAa,UAAU,OAAO,CAAW;EACnE,QAAQ;GACN,UAAU;EACZ;EAIF,MAAM,OAAO,MAAM,QAAQ,OAAO;EAGlC,GAAG,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EACxD,GAAG,cAAc,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;EAEjE,OAAO;CACT,CAAC;AACH"}
@@ -0,0 +1,40 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { OAuth2Client } from "google-auth-library";
4
+ import readline from "readline";
5
+ //#region src/sync/gmail-auth.ts
6
+ const SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"];
7
+ async function getGmailAuth(credentialsPath, tokenPath) {
8
+ const credentials = JSON.parse(fs.readFileSync(credentialsPath, "utf-8"));
9
+ const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web;
10
+ const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uris[0]);
11
+ if (fs.existsSync(tokenPath)) {
12
+ const token = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
13
+ oAuth2Client.setCredentials(token);
14
+ return oAuth2Client;
15
+ }
16
+ const authUrl = oAuth2Client.generateAuthUrl({
17
+ access_type: "offline",
18
+ scope: SCOPES
19
+ });
20
+ console.error("Authorize this app by visiting:\n" + authUrl);
21
+ const rl = readline.createInterface({
22
+ input: process.stdin,
23
+ output: process.stdout
24
+ });
25
+ const code = await new Promise((resolve) => {
26
+ rl.question("Enter the code from that page here: ", (c) => {
27
+ rl.close();
28
+ resolve(c);
29
+ });
30
+ });
31
+ const { tokens } = await oAuth2Client.getToken(code);
32
+ oAuth2Client.setCredentials(tokens);
33
+ fs.mkdirSync(path.dirname(tokenPath), { recursive: true });
34
+ fs.writeFileSync(tokenPath, JSON.stringify(tokens));
35
+ return oAuth2Client;
36
+ }
37
+ //#endregion
38
+ export { getGmailAuth };
39
+
40
+ //# sourceMappingURL=gmail-auth-BP6cJwfw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-auth-BP6cJwfw.js","names":[],"sources":["../src/sync/gmail-auth.ts"],"sourcesContent":["// src/sync/gmail-auth.ts\nimport { OAuth2Client, type Credentials } from \"google-auth-library\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport readline from \"readline\";\n\nconst SCOPES = [\"https://www.googleapis.com/auth/gmail.readonly\"];\n\nexport async function getGmailAuth(\n credentialsPath: string,\n tokenPath: string\n): Promise<OAuth2Client> {\n const credentials = JSON.parse(fs.readFileSync(credentialsPath, \"utf-8\")) as {\n installed?: { client_id: string; client_secret: string; redirect_uris: string[] };\n web?: { client_id: string; client_secret: string; redirect_uris: string[] };\n };\n\n const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web!;\n const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uris[0]);\n\n if (fs.existsSync(tokenPath)) {\n const token = JSON.parse(fs.readFileSync(tokenPath, \"utf-8\")) as Credentials;\n oAuth2Client.setCredentials(token);\n return oAuth2Client;\n }\n\n const authUrl = oAuth2Client.generateAuthUrl({ access_type: \"offline\", scope: SCOPES });\n console.error(\"Authorize this app by visiting:\\n\" + authUrl);\n\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const code = await new Promise<string>((resolve) => {\n rl.question(\"Enter the code from that page here: \", (c) => {\n rl.close();\n resolve(c);\n });\n });\n\n const { tokens } = await oAuth2Client.getToken(code);\n oAuth2Client.setCredentials(tokens);\n fs.mkdirSync(path.dirname(tokenPath), { recursive: true });\n fs.writeFileSync(tokenPath, JSON.stringify(tokens));\n\n return oAuth2Client;\n}\n"],"mappings":";;;;;AAMA,MAAM,SAAS,CAAC,gDAAgD;AAEhE,eAAsB,aACpB,iBACA,WACuB;CACvB,MAAM,cAAc,KAAK,MAAM,GAAG,aAAa,iBAAiB,OAAO,CAAC;CAKxE,MAAM,EAAE,WAAW,eAAe,kBAAkB,YAAY,aAAa,YAAY;CACzF,MAAM,eAAe,IAAI,aAAa,WAAW,eAAe,cAAc,EAAE;CAEhF,IAAI,GAAG,WAAW,SAAS,GAAG;EAC5B,MAAM,QAAQ,KAAK,MAAM,GAAG,aAAa,WAAW,OAAO,CAAC;EAC5D,aAAa,eAAe,KAAK;EACjC,OAAO;CACT;CAEA,MAAM,UAAU,aAAa,gBAAgB;EAAE,aAAa;EAAW,OAAO;CAAO,CAAC;CACtF,QAAQ,MAAM,sCAAsC,OAAO;CAE3D,MAAM,KAAK,SAAS,gBAAgB;EAAE,OAAO,QAAQ;EAAO,QAAQ,QAAQ;CAAO,CAAC;CACpF,MAAM,OAAO,MAAM,IAAI,SAAiB,YAAY;EAClD,GAAG,SAAS,yCAAyC,MAAM;GACzD,GAAG,MAAM;GACT,QAAQ,CAAC;EACX,CAAC;CACH,CAAC;CAED,MAAM,EAAE,WAAW,MAAM,aAAa,SAAS,IAAI;CACnD,aAAa,eAAe,MAAM;CAClC,GAAG,UAAU,KAAK,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;CACzD,GAAG,cAAc,WAAW,KAAK,UAAU,MAAM,CAAC;CAElD,OAAO;AACT"}
@@ -0,0 +1,44 @@
1
+ const require_chunk = require("./chunk-DakpK96I.cjs");
2
+ let path = require("path");
3
+ path = require_chunk.__toESM(path, 1);
4
+ let fs = require("fs");
5
+ fs = require_chunk.__toESM(fs, 1);
6
+ let google_auth_library = require("google-auth-library");
7
+ let readline = require("readline");
8
+ readline = require_chunk.__toESM(readline, 1);
9
+ //#region src/sync/gmail-auth.ts
10
+ const SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"];
11
+ async function getGmailAuth(credentialsPath, tokenPath) {
12
+ const credentials = JSON.parse(fs.default.readFileSync(credentialsPath, "utf-8"));
13
+ const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web;
14
+ const oAuth2Client = new google_auth_library.OAuth2Client(client_id, client_secret, redirect_uris[0]);
15
+ if (fs.default.existsSync(tokenPath)) {
16
+ const token = JSON.parse(fs.default.readFileSync(tokenPath, "utf-8"));
17
+ oAuth2Client.setCredentials(token);
18
+ return oAuth2Client;
19
+ }
20
+ const authUrl = oAuth2Client.generateAuthUrl({
21
+ access_type: "offline",
22
+ scope: SCOPES
23
+ });
24
+ console.error("Authorize this app by visiting:\n" + authUrl);
25
+ const rl = readline.default.createInterface({
26
+ input: process.stdin,
27
+ output: process.stdout
28
+ });
29
+ const code = await new Promise((resolve) => {
30
+ rl.question("Enter the code from that page here: ", (c) => {
31
+ rl.close();
32
+ resolve(c);
33
+ });
34
+ });
35
+ const { tokens } = await oAuth2Client.getToken(code);
36
+ oAuth2Client.setCredentials(tokens);
37
+ fs.default.mkdirSync(path.default.dirname(tokenPath), { recursive: true });
38
+ fs.default.writeFileSync(tokenPath, JSON.stringify(tokens));
39
+ return oAuth2Client;
40
+ }
41
+ //#endregion
42
+ exports.getGmailAuth = getGmailAuth;
43
+
44
+ //# sourceMappingURL=gmail-auth-DxakCtGm.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-auth-DxakCtGm.cjs","names":["OAuth2Client"],"sources":["../src/sync/gmail-auth.ts"],"sourcesContent":["// src/sync/gmail-auth.ts\nimport { OAuth2Client, type Credentials } from \"google-auth-library\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport readline from \"readline\";\n\nconst SCOPES = [\"https://www.googleapis.com/auth/gmail.readonly\"];\n\nexport async function getGmailAuth(\n credentialsPath: string,\n tokenPath: string\n): Promise<OAuth2Client> {\n const credentials = JSON.parse(fs.readFileSync(credentialsPath, \"utf-8\")) as {\n installed?: { client_id: string; client_secret: string; redirect_uris: string[] };\n web?: { client_id: string; client_secret: string; redirect_uris: string[] };\n };\n\n const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web!;\n const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uris[0]);\n\n if (fs.existsSync(tokenPath)) {\n const token = JSON.parse(fs.readFileSync(tokenPath, \"utf-8\")) as Credentials;\n oAuth2Client.setCredentials(token);\n return oAuth2Client;\n }\n\n const authUrl = oAuth2Client.generateAuthUrl({ access_type: \"offline\", scope: SCOPES });\n console.error(\"Authorize this app by visiting:\\n\" + authUrl);\n\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const code = await new Promise<string>((resolve) => {\n rl.question(\"Enter the code from that page here: \", (c) => {\n rl.close();\n resolve(c);\n });\n });\n\n const { tokens } = await oAuth2Client.getToken(code);\n oAuth2Client.setCredentials(tokens);\n fs.mkdirSync(path.dirname(tokenPath), { recursive: true });\n fs.writeFileSync(tokenPath, JSON.stringify(tokens));\n\n return oAuth2Client;\n}\n"],"mappings":";;;;;;;;;AAMA,MAAM,SAAS,CAAC,gDAAgD;AAEhE,eAAsB,aACpB,iBACA,WACuB;CACvB,MAAM,cAAc,KAAK,MAAM,GAAA,QAAG,aAAa,iBAAiB,OAAO,CAAC;CAKxE,MAAM,EAAE,WAAW,eAAe,kBAAkB,YAAY,aAAa,YAAY;CACzF,MAAM,eAAe,IAAIA,oBAAAA,aAAa,WAAW,eAAe,cAAc,EAAE;CAEhF,IAAI,GAAA,QAAG,WAAW,SAAS,GAAG;EAC5B,MAAM,QAAQ,KAAK,MAAM,GAAA,QAAG,aAAa,WAAW,OAAO,CAAC;EAC5D,aAAa,eAAe,KAAK;EACjC,OAAO;CACT;CAEA,MAAM,UAAU,aAAa,gBAAgB;EAAE,aAAa;EAAW,OAAO;CAAO,CAAC;CACtF,QAAQ,MAAM,sCAAsC,OAAO;CAE3D,MAAM,KAAK,SAAA,QAAS,gBAAgB;EAAE,OAAO,QAAQ;EAAO,QAAQ,QAAQ;CAAO,CAAC;CACpF,MAAM,OAAO,MAAM,IAAI,SAAiB,YAAY;EAClD,GAAG,SAAS,yCAAyC,MAAM;GACzD,GAAG,MAAM;GACT,QAAQ,CAAC;EACX,CAAC;CACH,CAAC;CAED,MAAM,EAAE,WAAW,MAAM,aAAa,SAAS,IAAI;CACnD,aAAa,eAAe,MAAM;CAClC,GAAA,QAAG,UAAU,KAAA,QAAK,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;CACzD,GAAA,QAAG,cAAc,WAAW,KAAK,UAAU,MAAM,CAAC;CAElD,OAAO;AACT"}
@@ -0,0 +1,40 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import readline from "readline";
4
+ import { OAuth2Client } from "google-auth-library";
5
+ //#region src/sync/gmail-auth.ts
6
+ const SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"];
7
+ async function getGmailAuth(credentialsPath, tokenPath) {
8
+ const credentials = JSON.parse(fs.readFileSync(credentialsPath, "utf-8"));
9
+ const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web;
10
+ const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uris[0]);
11
+ if (fs.existsSync(tokenPath)) {
12
+ const token = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
13
+ oAuth2Client.setCredentials(token);
14
+ return oAuth2Client;
15
+ }
16
+ const authUrl = oAuth2Client.generateAuthUrl({
17
+ access_type: "offline",
18
+ scope: SCOPES
19
+ });
20
+ console.error("Authorize this app by visiting:\n" + authUrl);
21
+ const rl = readline.createInterface({
22
+ input: process.stdin,
23
+ output: process.stdout
24
+ });
25
+ const code = await new Promise((resolve) => {
26
+ rl.question("Enter the code from that page here: ", (c) => {
27
+ rl.close();
28
+ resolve(c);
29
+ });
30
+ });
31
+ const { tokens } = await oAuth2Client.getToken(code);
32
+ oAuth2Client.setCredentials(tokens);
33
+ fs.mkdirSync(path.dirname(tokenPath), { recursive: true });
34
+ fs.writeFileSync(tokenPath, JSON.stringify(tokens));
35
+ return oAuth2Client;
36
+ }
37
+ //#endregion
38
+ export { getGmailAuth };
39
+
40
+ //# sourceMappingURL=gmail-auth-OComS92L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-auth-OComS92L.js","names":[],"sources":["../src/sync/gmail-auth.ts"],"sourcesContent":["// src/sync/gmail-auth.ts\nimport { OAuth2Client, type Credentials } from \"google-auth-library\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport readline from \"readline\";\n\nconst SCOPES = [\"https://www.googleapis.com/auth/gmail.readonly\"];\n\nexport async function getGmailAuth(\n credentialsPath: string,\n tokenPath: string\n): Promise<OAuth2Client> {\n const credentials = JSON.parse(fs.readFileSync(credentialsPath, \"utf-8\")) as {\n installed?: { client_id: string; client_secret: string; redirect_uris: string[] };\n web?: { client_id: string; client_secret: string; redirect_uris: string[] };\n };\n\n const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web!;\n const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uris[0]);\n\n if (fs.existsSync(tokenPath)) {\n const token = JSON.parse(fs.readFileSync(tokenPath, \"utf-8\")) as Credentials;\n oAuth2Client.setCredentials(token);\n return oAuth2Client;\n }\n\n const authUrl = oAuth2Client.generateAuthUrl({ access_type: \"offline\", scope: SCOPES });\n console.error(\"Authorize this app by visiting:\\n\" + authUrl);\n\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const code = await new Promise<string>((resolve) => {\n rl.question(\"Enter the code from that page here: \", (c) => {\n rl.close();\n resolve(c);\n });\n });\n\n const { tokens } = await oAuth2Client.getToken(code);\n oAuth2Client.setCredentials(tokens);\n fs.mkdirSync(path.dirname(tokenPath), { recursive: true });\n fs.writeFileSync(tokenPath, JSON.stringify(tokens));\n\n return oAuth2Client;\n}\n"],"mappings":";;;;;AAMA,MAAM,SAAS,CAAC,gDAAgD;AAEhE,eAAsB,aACpB,iBACA,WACuB;CACvB,MAAM,cAAc,KAAK,MAAM,GAAG,aAAa,iBAAiB,OAAO,CAAC;CAKxE,MAAM,EAAE,WAAW,eAAe,kBAAkB,YAAY,aAAa,YAAY;CACzF,MAAM,eAAe,IAAI,aAAa,WAAW,eAAe,cAAc,EAAE;CAEhF,IAAI,GAAG,WAAW,SAAS,GAAG;EAC5B,MAAM,QAAQ,KAAK,MAAM,GAAG,aAAa,WAAW,OAAO,CAAC;EAC5D,aAAa,eAAe,KAAK;EACjC,OAAO;CACT;CAEA,MAAM,UAAU,aAAa,gBAAgB;EAAE,aAAa;EAAW,OAAO;CAAO,CAAC;CACtF,QAAQ,MAAM,sCAAsC,OAAO;CAE3D,MAAM,KAAK,SAAS,gBAAgB;EAAE,OAAO,QAAQ;EAAO,QAAQ,QAAQ;CAAO,CAAC;CACpF,MAAM,OAAO,MAAM,IAAI,SAAiB,YAAY;EAClD,GAAG,SAAS,yCAAyC,MAAM;GACzD,GAAG,MAAM;GACT,QAAQ,CAAC;EACX,CAAC;CACH,CAAC;CAED,MAAM,EAAE,WAAW,MAAM,aAAa,SAAS,IAAI;CACnD,aAAa,eAAe,MAAM;CAClC,GAAG,UAAU,KAAK,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;CACzD,GAAG,cAAc,WAAW,KAAK,UAAU,MAAM,CAAC;CAElD,OAAO;AACT"}
@@ -0,0 +1,20 @@
1
+ //#region src/sync/gmail-push-watch.ts
2
+ async function registerGmailWatch(accessToken, topicName) {
3
+ const res = await fetch("https://gmail.googleapis.com/gmail/v1/users/me/watch", {
4
+ method: "POST",
5
+ headers: {
6
+ Authorization: `Bearer ${accessToken}`,
7
+ "Content-Type": "application/json"
8
+ },
9
+ body: JSON.stringify({
10
+ topicName,
11
+ labelIds: ["INBOX"]
12
+ })
13
+ });
14
+ if (!res.ok) throw new Error(`Gmail watch registration failed: ${res.status}`);
15
+ return res.json();
16
+ }
17
+ //#endregion
18
+ export { registerGmailWatch };
19
+
20
+ //# sourceMappingURL=gmail-push-watch-DELQFMPk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-push-watch-DELQFMPk.js","names":[],"sources":["../src/sync/gmail-push-watch.ts"],"sourcesContent":["export interface GmailPayload {\n mimeType?: string;\n body?: { data?: string };\n parts?: GmailPayload[];\n}\n\nexport function extractEmailBody(payload: GmailPayload): string {\n // If there's direct body data, decode it\n if (payload.body?.data) {\n return Buffer.from(payload.body.data, \"base64url\").toString(\"utf-8\");\n }\n\n // Search parts for text/plain first\n for (const part of payload.parts ?? []) {\n if (part.mimeType === \"text/plain\" && part.body?.data) {\n return Buffer.from(part.body.data, \"base64url\").toString(\"utf-8\");\n }\n }\n\n // Recurse into multipart parts to find text/plain\n for (const part of payload.parts ?? []) {\n if (part.parts) {\n const found = extractEmailBody(part);\n if (found) return found;\n }\n }\n\n // Fall back to text/html\n for (const part of payload.parts ?? []) {\n if (part.mimeType === \"text/html\" && part.body?.data) {\n return Buffer.from(part.body.data, \"base64url\").toString(\"utf-8\");\n }\n }\n\n // Recurse for html fallback\n for (const part of payload.parts ?? []) {\n if (part.parts) {\n const found = extractEmailBody(part);\n if (found) return found;\n }\n }\n\n return \"\";\n}\n\nexport interface WatchRegistration {\n historyId: string;\n expiration: string;\n}\n\nexport async function registerGmailWatch(\n accessToken: string,\n topicName: string\n): Promise<WatchRegistration> {\n const res = await fetch(\"https://gmail.googleapis.com/gmail/v1/users/me/watch\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ topicName, labelIds: [\"INBOX\"] }),\n });\n if (!res.ok) {\n throw new Error(`Gmail watch registration failed: ${res.status}`);\n }\n return res.json() as Promise<WatchRegistration>;\n}\n\nexport interface HistoryMessage {\n id: string;\n threadId: string;\n}\n\nexport async function fetchNewMessagesFromHistory(\n accessToken: string,\n startHistoryId: string\n): Promise<HistoryMessage[]> {\n const url = `https://gmail.googleapis.com/gmail/v1/users/me/history?startHistoryId=${startHistoryId}&historyTypes=messageAdded`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n if (!res.ok) {\n throw new Error(`Gmail history fetch failed: ${res.status}`);\n }\n const data = (await res.json()) as {\n history?: Array<{\n messagesAdded?: Array<{ message: { id: string; threadId: string } }>;\n }>;\n historyId?: string;\n };\n\n const messages: HistoryMessage[] = [];\n for (const entry of data.history ?? []) {\n for (const added of entry.messagesAdded ?? []) {\n messages.push({ id: added.message.id, threadId: added.message.threadId });\n }\n }\n return messages;\n}\n\nexport async function fetchFullMessage(\n accessToken: string,\n messageId: string\n): Promise<{\n id: string;\n threadId: string;\n subject: string;\n from: string;\n date: string;\n body: string;\n}> {\n const url = `https://gmail.googleapis.com/gmail/v1/users/me/messages/${messageId}?format=full`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n if (!res.ok) {\n throw new Error(`Gmail message fetch failed: ${res.status}`);\n }\n const data = (await res.json()) as {\n id: string;\n threadId: string;\n payload: {\n mimeType?: string;\n headers: Array<{ name: string; value: string }>;\n body?: { data?: string };\n parts?: GmailPayload[];\n };\n };\n\n const headers = data.payload.headers;\n const getHeader = (name: string): string =>\n headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? \"\";\n\n const body = extractEmailBody({\n ...(data.payload.mimeType !== undefined ? { mimeType: data.payload.mimeType } : {}),\n ...(data.payload.body !== undefined ? { body: data.payload.body } : {}),\n ...(data.payload.parts !== undefined ? { parts: data.payload.parts } : {}),\n });\n\n return {\n id: data.id,\n threadId: data.threadId,\n subject: getHeader(\"Subject\"),\n from: getHeader(\"From\"),\n date: getHeader(\"Date\"),\n body,\n };\n}\n"],"mappings":";AAkDA,eAAsB,mBACpB,aACA,WAC4B;CAC5B,MAAM,MAAM,MAAM,MAAM,wDAAwD;EAC9E,QAAQ;EACR,SAAS;GACP,eAAe,UAAU;GACzB,gBAAgB;EAClB;EACA,MAAM,KAAK,UAAU;GAAE;GAAW,UAAU,CAAC,OAAO;EAAE,CAAC;CACzD,CAAC;CACD,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,oCAAoC,IAAI,QAAQ;CAElE,OAAO,IAAI,KAAK;AAClB"}
@@ -0,0 +1,32 @@
1
+ import { gmail } from "@googleapis/gmail";
2
+ //#region src/sync/gmail-sender.ts
3
+ function buildMime(opts) {
4
+ const contentType = opts.isHtml !== false ? "text/html; charset=utf-8" : "text/plain; charset=utf-8";
5
+ return `${[
6
+ `To: ${opts.to}`,
7
+ ...opts.cc && opts.cc.length > 0 ? [`Cc: ${opts.cc.join(", ")}`] : [],
8
+ `Subject: ${opts.subject}`,
9
+ `Content-Type: ${contentType}`,
10
+ `MIME-Version: 1.0`,
11
+ ...opts.replyToMessageId ? [`In-Reply-To: ${opts.replyToMessageId}`, `References: ${opts.replyToMessageId}`] : []
12
+ ].join("\r\n")}\r\n\r\n${opts.body}`;
13
+ }
14
+ async function sendEmail(opts) {
15
+ const gmail$1 = gmail({
16
+ version: "v1",
17
+ auth: opts.auth
18
+ });
19
+ const raw = Buffer.from(buildMime(opts)).toString("base64url");
20
+ const res = await gmail$1.users.messages.send({
21
+ userId: "me",
22
+ requestBody: { raw }
23
+ });
24
+ return {
25
+ messageId: res.data.id ?? "",
26
+ threadId: res.data.threadId ?? ""
27
+ };
28
+ }
29
+ //#endregion
30
+ export { sendEmail };
31
+
32
+ //# sourceMappingURL=gmail-sender-StTpJ9Ub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-sender-StTpJ9Ub.js","names":["gmail","gmailApi"],"sources":["../src/sync/gmail-sender.ts"],"sourcesContent":["import { gmail as gmailApi } from \"@googleapis/gmail\";\nimport type { OAuth2Client } from \"google-auth-library\";\n\nexport interface SendEmailOpts {\n auth: OAuth2Client;\n to: string;\n subject: string;\n body: string; // HTML or plain text\n isHtml?: boolean; // default true\n replyToMessageId?: string;\n cc?: string[];\n}\n\nexport interface SendEmailResult {\n messageId: string;\n threadId: string;\n}\n\nfunction buildMime(opts: SendEmailOpts): string {\n const isHtml = opts.isHtml !== false;\n const contentType = isHtml ? \"text/html; charset=utf-8\" : \"text/plain; charset=utf-8\";\n const lines: string[] = [\n `To: ${opts.to}`,\n ...(opts.cc && opts.cc.length > 0 ? [`Cc: ${opts.cc.join(\", \")}`] : []),\n `Subject: ${opts.subject}`,\n `Content-Type: ${contentType}`,\n `MIME-Version: 1.0`,\n ...(opts.replyToMessageId\n ? [`In-Reply-To: ${opts.replyToMessageId}`, `References: ${opts.replyToMessageId}`]\n : []),\n ];\n return `${lines.join(\"\\r\\n\")}\\r\\n\\r\\n${opts.body}`;\n}\n\nexport async function sendEmail(opts: SendEmailOpts): Promise<SendEmailResult> {\n const gmail = gmailApi({ version: \"v1\", auth: opts.auth });\n const raw = Buffer.from(buildMime(opts)).toString(\"base64url\");\n\n const res = await gmail.users.messages.send({\n userId: \"me\",\n requestBody: { raw },\n });\n\n return {\n messageId: res.data.id ?? \"\",\n threadId: res.data.threadId ?? \"\",\n };\n}\n"],"mappings":";;AAkBA,SAAS,UAAU,MAA6B;CAE9C,MAAM,cADS,KAAK,WAAW,QACF,6BAA6B;CAW1D,OAAO,GAAG;EATR,OAAO,KAAK;EACZ,GAAI,KAAK,MAAM,KAAK,GAAG,SAAS,IAAI,CAAC,OAAO,KAAK,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;EACrE,YAAY,KAAK;EACjB,iBAAiB;EACjB;EACA,GAAI,KAAK,mBACL,CAAC,gBAAgB,KAAK,oBAAoB,eAAe,KAAK,kBAAkB,IAChF,CAAC;CAEO,EAAE,KAAK,MAAM,EAAE,UAAU,KAAK;AAC9C;AAEA,eAAsB,UAAU,MAA+C;CAC7E,MAAMA,UAAQC,MAAS;EAAE,SAAS;EAAM,MAAM,KAAK;CAAK,CAAC;CACzD,MAAM,MAAM,OAAO,KAAK,UAAU,IAAI,CAAC,EAAE,SAAS,WAAW;CAE7D,MAAM,MAAM,MAAMD,QAAM,MAAM,SAAS,KAAK;EAC1C,QAAQ;EACR,aAAa,EAAE,IAAI;CACrB,CAAC;CAED,OAAO;EACL,WAAW,IAAI,KAAK,MAAM;EAC1B,UAAU,IAAI,KAAK,YAAY;CACjC;AACF"}
@@ -0,0 +1,204 @@
1
+ import { t as AgentConfigSchema } from "./agent-config-zPvcqu07.js";
2
+ import { r as readInteractions, t as appendInteraction } from "./interactions-writer-SLHnoEeE.js";
3
+ import { a as summarizeEmail } from "./llm-DvzZqva0.js";
4
+ import path from "path";
5
+ import fs from "fs";
6
+ import https from "https";
7
+ import { gmail } from "@googleapis/gmail";
8
+ //#region src/core/agent-notifier.ts
9
+ function agentConfigPath(dataDir, slug) {
10
+ return path.join(dataDir, ".agentic", "agents", `${slug}.agent.json`);
11
+ }
12
+ function readAgentConfig(dataDir, slug) {
13
+ const p = agentConfigPath(dataDir, slug);
14
+ if (!fs.existsSync(p)) return null;
15
+ try {
16
+ const raw = JSON.parse(fs.readFileSync(p, "utf-8"));
17
+ const result = AgentConfigSchema.safeParse(raw);
18
+ return result.success ? result.data : null;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function writeLastWake(dataDir, slug, config) {
24
+ const p = agentConfigPath(dataDir, slug);
25
+ try {
26
+ const updated = {
27
+ ...config,
28
+ lastWake: (/* @__PURE__ */ new Date()).toISOString()
29
+ };
30
+ fs.writeFileSync(p, JSON.stringify(updated, null, 2), "utf-8");
31
+ } catch {}
32
+ }
33
+ function sendTelegramMessage(token, chatId, text) {
34
+ const body = JSON.stringify({
35
+ chat_id: chatId,
36
+ text,
37
+ parse_mode: "Markdown"
38
+ });
39
+ return new Promise((resolve, reject) => {
40
+ const req = https.request(`https://api.telegram.org/bot${token}/sendMessage`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ "Content-Length": Buffer.byteLength(body)
45
+ }
46
+ }, (res) => {
47
+ res.resume();
48
+ resolve();
49
+ });
50
+ req.on("error", reject);
51
+ req.write(body);
52
+ req.end();
53
+ });
54
+ }
55
+ function buildWakeMessage(slug, subject, summary, nextSteps) {
56
+ return `📧 New email from **${slug}**: ${subject}\n${summary}\n\n💡 Suggested action: ${nextSteps[0] ?? "Follow up within 24h"}`;
57
+ }
58
+ /**
59
+ * Fire-and-forget notification: reads the agent config for `slug`, summarises
60
+ * the inbound email with the LLM, and sends a Telegram message.
61
+ *
62
+ * Silently returns (no throw) when:
63
+ * - no agent config exists for the slug
64
+ * - TELEGRAM_BOT_TOKEN env var is not set
65
+ * - no chat id is available (neither in config nor in TELEGRAM_CHAT_ID env var)
66
+ * - any HTTPS / LLM error occurs
67
+ */
68
+ async function notifyAgentWake(dataDir, slug, context) {
69
+ try {
70
+ const config = readAgentConfig(dataDir, slug);
71
+ if (!config) return;
72
+ const token = process.env["TELEGRAM_BOT_TOKEN"];
73
+ if (!token) return;
74
+ const chatId = config.telegramChatId ?? process.env["TELEGRAM_CHAT_ID"];
75
+ if (!chatId) return;
76
+ const emailSummary = await summarizeEmail(context.subject, context.snippet, context.from);
77
+ await sendTelegramMessage(token, chatId, buildWakeMessage(slug, context.subject, emailSummary.summary, emailSummary.nextSteps));
78
+ writeLastWake(dataDir, slug, config);
79
+ } catch {}
80
+ }
81
+ //#endregion
82
+ //#region src/sync/gmail-sync.ts
83
+ /**
84
+ * Retry a function with exponential backoff on any error.
85
+ * Delays: 1s, 2s, 4s, 8s … (2^attempt seconds), up to maxRetries retries.
86
+ */
87
+ async function retryWithBackoff(fn, maxRetries = 3) {
88
+ let attempt = 0;
89
+ while (true) try {
90
+ return await fn();
91
+ } catch (err) {
92
+ if (attempt >= maxRetries) throw err;
93
+ await sleep(1e3 * Math.pow(2, attempt));
94
+ attempt++;
95
+ }
96
+ }
97
+ async function syncGmail(opts) {
98
+ const gmail$1 = gmail({
99
+ version: "v1",
100
+ auth: opts.auth
101
+ });
102
+ const maxPages = opts.maxPages ?? 5;
103
+ let q = opts.query;
104
+ if (opts.since) {
105
+ const after = Math.floor(opts.since.getTime() / 1e3);
106
+ q += ` after:${after}`;
107
+ }
108
+ const allMessages = [];
109
+ let pageToken = void 0;
110
+ let pagesFetched = 0;
111
+ do {
112
+ const listResp = await gmail$1.users.messages.list({
113
+ userId: "me",
114
+ q,
115
+ maxResults: 200,
116
+ ...pageToken ? { pageToken } : {}
117
+ });
118
+ const pageMessages = listResp.data.messages ?? [];
119
+ allMessages.push(...pageMessages);
120
+ pageToken = listResp.data.nextPageToken ?? void 0;
121
+ pagesFetched++;
122
+ } while (pageToken && pagesFetched < maxPages);
123
+ let existingContent = await readInteractions(opts.dataDir, opts.slug);
124
+ let synced = 0;
125
+ let skipped = 0;
126
+ for (const msg of allMessages) {
127
+ if (!msg.id) continue;
128
+ const source = `gmail://thread/${msg.threadId ?? msg.id}`;
129
+ if (existingContent.includes(source)) {
130
+ skipped++;
131
+ continue;
132
+ }
133
+ await sleep(100);
134
+ let msgData;
135
+ try {
136
+ msgData = (await retryWithBackoff(() => gmail$1.users.messages.get({
137
+ userId: "me",
138
+ id: msg.id,
139
+ format: "metadata",
140
+ metadataHeaders: [
141
+ "Subject",
142
+ "From",
143
+ "Date"
144
+ ]
145
+ }))).data;
146
+ } catch (err) {
147
+ process.stderr.write(`[gmail-sync] Skipping message ${msg.id} after retries: ${err.message}\n`);
148
+ skipped++;
149
+ continue;
150
+ }
151
+ const headers = msgData.payload?.headers ?? [];
152
+ const subject = headers.find((h) => h.name === "Subject")?.value ?? "(no subject)";
153
+ const from = headers.find((h) => h.name === "From")?.value ?? "";
154
+ const dateStr = headers.find((h) => h.name === "Date")?.value;
155
+ const date = dateStr ? new Date(dateStr).toISOString().slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
156
+ const snippet = msgData.snippet ?? "";
157
+ const { summarizeEmail } = await import("./llm-DEjWcqmW.js");
158
+ const emailSummary = await summarizeEmail(subject, snippet, from);
159
+ await appendInteraction(opts.dataDir, opts.slug, {
160
+ date,
161
+ type: "Email",
162
+ direction: detectDirection(from),
163
+ with: from,
164
+ subject,
165
+ summary: emailSummary.summary,
166
+ nextSteps: emailSummary.nextSteps,
167
+ sourceRef: source,
168
+ synced: (/* @__PURE__ */ new Date()).toISOString()
169
+ });
170
+ existingContent += source;
171
+ const { indexInLanceDB } = await import("./lancedb-CCBbpulq.js");
172
+ await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\n${snippet}`, source, {
173
+ date,
174
+ type: "Email"
175
+ }).catch((err) => {
176
+ process.stderr.write(`[gmail-sync] LanceDB index failed: ${err.message}\n`);
177
+ });
178
+ if (agentConfigExists(opts.dataDir, opts.slug)) notifyAgentWake(opts.dataDir, opts.slug, {
179
+ trigger: "email",
180
+ subject,
181
+ from,
182
+ snippet
183
+ }).catch(() => {});
184
+ synced++;
185
+ }
186
+ return {
187
+ synced,
188
+ skipped
189
+ };
190
+ }
191
+ function agentConfigExists(dataDir, slug) {
192
+ const configPath = path.join(dataDir, ".agentic", "agents", `${slug}.agent.json`);
193
+ return fs.existsSync(configPath);
194
+ }
195
+ function detectDirection(_from) {
196
+ return "inbound";
197
+ }
198
+ function sleep(ms) {
199
+ return new Promise((resolve) => setTimeout(resolve, ms));
200
+ }
201
+ //#endregion
202
+ export { syncGmail };
203
+
204
+ //# sourceMappingURL=gmail-sync-DIaxInDT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-sync-DIaxInDT.js","names":["gmail","gmailApi"],"sources":["../src/core/agent-notifier.ts","../src/sync/gmail-sync.ts"],"sourcesContent":["// src/core/agent-notifier.ts\n// Sends a Telegram wake notification when a new inbound email from a customer\n// domain is detected and an agent config exists for that customer slug.\n// All errors are swallowed — this is a notification feature and must never\n// crash the core loop.\n\nimport fs from \"fs\";\nimport https from \"https\";\nimport path from \"path\";\nimport { AgentConfigSchema, type AgentConfig } from \"../schemas/agent-config.js\";\nimport { summarizeEmail } from \"./llm.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface WakeContext {\n trigger: \"email\" | \"calendar\";\n subject: string;\n from: string;\n snippet: string;\n}\n\n// ─── Agent config helpers ─────────────────────────────────────────────────────\n\nfunction agentConfigPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n}\n\nfunction readAgentConfig(dataDir: string, slug: string): AgentConfig | null {\n const p = agentConfigPath(dataDir, slug);\n if (!fs.existsSync(p)) return null;\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n const result = AgentConfigSchema.safeParse(raw);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nfunction writeLastWake(dataDir: string, slug: string, config: AgentConfig): void {\n const p = agentConfigPath(dataDir, slug);\n try {\n const updated: AgentConfig = { ...config, lastWake: new Date().toISOString() };\n fs.writeFileSync(p, JSON.stringify(updated, null, 2), \"utf-8\");\n } catch {\n // non-fatal — just a housekeeping write\n }\n}\n\n// ─── Telegram transport ───────────────────────────────────────────────────────\n\nfunction sendTelegramMessage(token: string, chatId: string, text: string): Promise<void> {\n const body = JSON.stringify({ chat_id: chatId, text, parse_mode: \"Markdown\" });\n return new Promise<void>((resolve, reject) => {\n const req = https.request(\n `https://api.telegram.org/bot${token}/sendMessage`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n },\n },\n (res) => {\n res.resume();\n resolve();\n }\n );\n req.on(\"error\", reject);\n req.write(body);\n req.end();\n });\n}\n\n// ─── Message builder ──────────────────────────────────────────────────────────\n\nfunction buildWakeMessage(\n slug: string,\n subject: string,\n summary: string,\n nextSteps: string[]\n): string {\n const suggestedAction = nextSteps[0] ?? \"Follow up within 24h\";\n return (\n `📧 New email from **${slug}**: ${subject}\\n` +\n `${summary}\\n\\n` +\n `💡 Suggested action: ${suggestedAction}`\n );\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget notification: reads the agent config for `slug`, summarises\n * the inbound email with the LLM, and sends a Telegram message.\n *\n * Silently returns (no throw) when:\n * - no agent config exists for the slug\n * - TELEGRAM_BOT_TOKEN env var is not set\n * - no chat id is available (neither in config nor in TELEGRAM_CHAT_ID env var)\n * - any HTTPS / LLM error occurs\n */\nexport async function notifyAgentWake(\n dataDir: string,\n slug: string,\n context: WakeContext\n): Promise<void> {\n try {\n // 1. Read agent config — bail silently if not found\n const config = readAgentConfig(dataDir, slug);\n if (!config) return;\n\n // 2. Check for Telegram token — bail silently if absent\n const token = process.env[\"TELEGRAM_BOT_TOKEN\"];\n if (!token) return;\n\n // 3. Determine chat id — config takes precedence, fallback to env var\n const chatId = config.telegramChatId ?? process.env[\"TELEGRAM_CHAT_ID\"];\n if (!chatId) return;\n\n // 4. Summarise the email (LLM, with fallback built into summarizeEmail itself)\n const emailSummary = await summarizeEmail(context.subject, context.snippet, context.from);\n\n // 5. Build and send the Telegram message\n const text = buildWakeMessage(\n slug,\n context.subject,\n emailSummary.summary,\n emailSummary.nextSteps\n );\n await sendTelegramMessage(token, chatId, text);\n\n // 6. Update lastWake on success\n writeLastWake(dataDir, slug, config);\n } catch {\n // Swallow all errors — this is a notification feature, never crashes core loop\n }\n}\n","// src/sync/gmail-sync.ts\nimport fs from \"fs\";\nimport path from \"path\";\nimport { gmail as gmailApi, type gmail_v1 } from \"@googleapis/gmail\";\nimport type { OAuth2Client } from \"google-auth-library\";\nimport { readInteractions, appendInteraction } from \"../fs/interactions-writer.js\";\nimport { notifyAgentWake } from \"../core/agent-notifier.js\";\n\ninterface SyncOptions {\n slug: string;\n dataDir: string;\n auth: OAuth2Client;\n query: string;\n since?: Date;\n maxPages?: number;\n}\n\n/**\n * Retry a function with exponential backoff on any error.\n * Delays: 1s, 2s, 4s, 8s … (2^attempt seconds), up to maxRetries retries.\n */\nexport async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n const delayMs = 1000 * Math.pow(2, attempt);\n await sleep(delayMs);\n attempt++;\n }\n }\n}\n\nexport async function syncGmail(opts: SyncOptions): Promise<{ synced: number; skipped: number }> {\n const gmail = gmailApi({ version: \"v1\", auth: opts.auth });\n const maxPages = opts.maxPages ?? 5;\n\n let q = opts.query;\n if (opts.since) {\n const after = Math.floor(opts.since.getTime() / 1000);\n q += ` after:${after}`;\n }\n\n // Collect all message stubs across pages (Task A — pagination)\n const allMessages: Array<{ id?: string | null; threadId?: string | null }> = [];\n let pageToken: string | undefined = undefined;\n let pagesFetched = 0;\n\n do {\n const listResp: { data: gmail_v1.Schema$ListMessagesResponse } =\n await gmail.users.messages.list({\n userId: \"me\",\n q,\n maxResults: 200,\n ...(pageToken ? { pageToken } : {}),\n });\n const pageMessages = listResp.data.messages ?? [];\n allMessages.push(...pageMessages);\n pageToken = listResp.data.nextPageToken ?? undefined;\n pagesFetched++;\n } while (pageToken && pagesFetched < maxPages);\n\n // Read existing interactions once before the loop — avoids O(messages) file reads\n let existingContent = await readInteractions(opts.dataDir, opts.slug);\n\n let synced = 0;\n let skipped = 0;\n\n for (const msg of allMessages) {\n if (!msg.id) continue;\n\n const source = `gmail://thread/${msg.threadId ?? msg.id}`;\n\n if (existingContent.includes(source)) {\n skipped++;\n continue;\n }\n\n // Rate limiting ~10 req/s\n await sleep(100);\n\n // Task B — exponential backoff retry on any error\n let msgData: gmail_v1.Schema$Message;\n try {\n const detail = await retryWithBackoff(() =>\n gmail.users.messages.get({\n userId: \"me\",\n id: msg.id!,\n format: \"metadata\",\n metadataHeaders: [\"Subject\", \"From\", \"Date\"],\n })\n );\n msgData = detail.data;\n } catch (err) {\n process.stderr.write(\n `[gmail-sync] Skipping message ${msg.id} after retries: ${(err as Error).message}\\n`\n );\n skipped++;\n continue;\n }\n\n const headers = msgData.payload?.headers ?? [];\n const subject = headers.find((h) => h.name === \"Subject\")?.value ?? \"(no subject)\";\n const from = headers.find((h) => h.name === \"From\")?.value ?? \"\";\n const dateStr = headers.find((h) => h.name === \"Date\")?.value;\n const date = dateStr\n ? new Date(dateStr).toISOString().slice(0, 10)\n : new Date().toISOString().slice(0, 10);\n const snippet = msgData.snippet ?? \"\";\n\n // LLM summary — non-blocking fallback to raw snippet if no API key or error\n const { summarizeEmail } = await import(\"../core/llm.js\");\n const emailSummary = await summarizeEmail(subject, snippet, from);\n\n await appendInteraction(opts.dataDir, opts.slug, {\n date,\n type: \"Email\",\n direction: detectDirection(from),\n with: from,\n subject,\n summary: emailSummary.summary,\n nextSteps: emailSummary.nextSteps,\n sourceRef: source,\n synced: new Date().toISOString(),\n });\n\n // Append to in-memory string so within-batch duplicates are detected\n existingContent += source;\n\n // Index into LanceDB for semantic search (non-blocking)\n const { indexInLanceDB } = await import(\"../core/lancedb.js\");\n await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\\n${snippet}`, source, {\n date,\n type: \"Email\",\n }).catch((err: unknown) => {\n process.stderr.write(`[gmail-sync] LanceDB index failed: ${(err as Error).message}\\n`);\n });\n\n // Agent wake: notify if an agent config exists for this customer (fire-and-forget)\n if (agentConfigExists(opts.dataDir, opts.slug)) {\n notifyAgentWake(opts.dataDir, opts.slug, {\n trigger: \"email\",\n subject,\n from,\n snippet,\n }).catch(() => {\n // Notification is non-blocking; swallow all errors\n });\n }\n\n synced++;\n }\n\n return { synced, skipped };\n}\n\nfunction agentConfigExists(dataDir: string, slug: string): boolean {\n const configPath = path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n return fs.existsSync(configPath);\n}\n\nfunction detectDirection(_from: string): \"inbound\" | \"outbound\" {\n return \"inbound\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":";;;;;;;;AAuBA,SAAS,gBAAgB,SAAiB,MAAsB;CAC9D,OAAO,KAAK,KAAK,SAAS,YAAY,UAAU,GAAG,KAAK,YAAY;AACtE;AAEA,SAAS,gBAAgB,SAAiB,MAAkC;CAC1E,MAAM,IAAI,gBAAgB,SAAS,IAAI;CACvC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO;CAC9B,IAAI;EACF,MAAM,MAAM,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAC5D,MAAM,SAAS,kBAAkB,UAAU,GAAG;EAC9C,OAAO,OAAO,UAAU,OAAO,OAAO;CACxC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,SAAiB,MAAc,QAA2B;CAC/E,MAAM,IAAI,gBAAgB,SAAS,IAAI;CACvC,IAAI;EACF,MAAM,UAAuB;GAAE,GAAG;GAAQ,2BAAU,IAAI,KAAK,GAAE,YAAY;EAAE;EAC7E,GAAG,cAAc,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,OAAO;CAC/D,QAAQ,CAER;AACF;AAIA,SAAS,oBAAoB,OAAe,QAAgB,MAA6B;CACvF,MAAM,OAAO,KAAK,UAAU;EAAE,SAAS;EAAQ;EAAM,YAAY;CAAW,CAAC;CAC7E,OAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,MAAM,MAAM,QAChB,+BAA+B,MAAM,eACrC;GACE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,kBAAkB,OAAO,WAAW,IAAI;GAC1C;EACF,IACC,QAAQ;GACP,IAAI,OAAO;GACX,QAAQ;EACV,CACF;EACA,IAAI,GAAG,SAAS,MAAM;EACtB,IAAI,MAAM,IAAI;EACd,IAAI,IAAI;CACV,CAAC;AACH;AAIA,SAAS,iBACP,MACA,SACA,SACA,WACQ;CAER,OACE,uBAAuB,KAAK,MAAM,QAAQ,IACvC,QAAQ,2BAHW,UAAU,MAAM;AAM1C;;;;;;;;;;;AAcA,eAAsB,gBACpB,SACA,MACA,SACe;CACf,IAAI;EAEF,MAAM,SAAS,gBAAgB,SAAS,IAAI;EAC5C,IAAI,CAAC,QAAQ;EAGb,MAAM,QAAQ,QAAQ,IAAI;EAC1B,IAAI,CAAC,OAAO;EAGZ,MAAM,SAAS,OAAO,kBAAkB,QAAQ,IAAI;EACpD,IAAI,CAAC,QAAQ;EAGb,MAAM,eAAe,MAAM,eAAe,QAAQ,SAAS,QAAQ,SAAS,QAAQ,IAAI;EASxF,MAAM,oBAAoB,OAAO,QANpB,iBACX,MACA,QAAQ,SACR,aAAa,SACb,aAAa,SAE6B,CAAC;EAG7C,cAAc,SAAS,MAAM,MAAM;CACrC,QAAQ,CAER;AACF;;;;;;;ACpHA,eAAsB,iBAAoB,IAAsB,aAAa,GAAe;CAC1F,IAAI,UAAU;CACd,OAAO,MACL,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,SAAS,KAAK;EACZ,IAAI,WAAW,YAAY,MAAM;EAEjC,MAAM,MADU,MAAO,KAAK,IAAI,GAAG,OAAO,CACvB;EACnB;CACF;AAEJ;AAEA,eAAsB,UAAU,MAAiE;CAC/F,MAAMA,UAAQC,MAAS;EAAE,SAAS;EAAM,MAAM,KAAK;CAAK,CAAC;CACzD,MAAM,WAAW,KAAK,YAAY;CAElC,IAAI,IAAI,KAAK;CACb,IAAI,KAAK,OAAO;EACd,MAAM,QAAQ,KAAK,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAI;EACpD,KAAK,UAAU;CACjB;CAGA,MAAM,cAAuE,CAAC;CAC9E,IAAI,YAAgC,KAAA;CACpC,IAAI,eAAe;CAEnB,GAAG;EACD,MAAM,WACJ,MAAMD,QAAM,MAAM,SAAS,KAAK;GAC9B,QAAQ;GACR;GACA,YAAY;GACZ,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;EACnC,CAAC;EACH,MAAM,eAAe,SAAS,KAAK,YAAY,CAAC;EAChD,YAAY,KAAK,GAAG,YAAY;EAChC,YAAY,SAAS,KAAK,iBAAiB,KAAA;EAC3C;CACF,SAAS,aAAa,eAAe;CAGrC,IAAI,kBAAkB,MAAM,iBAAiB,KAAK,SAAS,KAAK,IAAI;CAEpE,IAAI,SAAS;CACb,IAAI,UAAU;CAEd,KAAK,MAAM,OAAO,aAAa;EAC7B,IAAI,CAAC,IAAI,IAAI;EAEb,MAAM,SAAS,kBAAkB,IAAI,YAAY,IAAI;EAErD,IAAI,gBAAgB,SAAS,MAAM,GAAG;GACpC;GACA;EACF;EAGA,MAAM,MAAM,GAAG;EAGf,IAAI;EACJ,IAAI;GASF,WAAU,MARW,uBACnBA,QAAM,MAAM,SAAS,IAAI;IACvB,QAAQ;IACR,IAAI,IAAI;IACR,QAAQ;IACR,iBAAiB;KAAC;KAAW;KAAQ;IAAM;GAC7C,CAAC,CACH,GACiB;EACnB,SAAS,KAAK;GACZ,QAAQ,OAAO,MACb,iCAAiC,IAAI,GAAG,kBAAmB,IAAc,QAAQ,GACnF;GACA;GACA;EACF;EAEA,MAAM,UAAU,QAAQ,SAAS,WAAW,CAAC;EAC7C,MAAM,UAAU,QAAQ,MAAM,MAAM,EAAE,SAAS,SAAS,GAAG,SAAS;EACpE,MAAM,OAAO,QAAQ,MAAM,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS;EAC9D,MAAM,UAAU,QAAQ,MAAM,MAAM,EAAE,SAAS,MAAM,GAAG;EACxD,MAAM,OAAO,UACT,IAAI,KAAK,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,qBAC3C,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;EACxC,MAAM,UAAU,QAAQ,WAAW;EAGnC,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,MAAM,eAAe,MAAM,eAAe,SAAS,SAAS,IAAI;EAEhE,MAAM,kBAAkB,KAAK,SAAS,KAAK,MAAM;GAC/C;GACA,MAAM;GACN,WAAW,gBAAgB,IAAI;GAC/B,MAAM;GACN;GACA,SAAS,aAAa;GACtB,WAAW,aAAa;GACxB,WAAW;GACX,yBAAQ,IAAI,KAAK,GAAE,YAAY;EACjC,CAAC;EAGD,mBAAmB;EAGnB,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,MAAM,eAAe,KAAK,SAAS,KAAK,MAAM,GAAG,QAAQ,IAAI,WAAW,QAAQ;GAC9E;GACA,MAAM;EACR,CAAC,EAAE,OAAO,QAAiB;GACzB,QAAQ,OAAO,MAAM,sCAAuC,IAAc,QAAQ,GAAG;EACvF,CAAC;EAGD,IAAI,kBAAkB,KAAK,SAAS,KAAK,IAAI,GAC3C,gBAAgB,KAAK,SAAS,KAAK,MAAM;GACvC,SAAS;GACT;GACA;GACA;EACF,CAAC,EAAE,YAAY,CAEf,CAAC;EAGH;CACF;CAEA,OAAO;EAAE;EAAQ;CAAQ;AAC3B;AAEA,SAAS,kBAAkB,SAAiB,MAAuB;CACjE,MAAM,aAAa,KAAK,KAAK,SAAS,YAAY,UAAU,GAAG,KAAK,YAAY;CAChF,OAAO,GAAG,WAAW,UAAU;AACjC;AAEA,SAAS,gBAAgB,OAAuC;CAC9D,OAAO;AACT;AAEA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD"}