@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,57 @@
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
+ //#region src/core/usage.ts
7
+ const DEFAULT_PRICING = {
8
+ "claude-haiku-4-5": {
9
+ input: 1,
10
+ output: 5
11
+ },
12
+ default: {
13
+ input: 1,
14
+ output: 5
15
+ }
16
+ };
17
+ function ledgerPath(dataDir) {
18
+ return path.default.join(dataDir, ".agentic", "usage.ndjson");
19
+ }
20
+ function loadPricing(dataDir) {
21
+ const p = path.default.join(dataDir, ".agentic", "llm-pricing.json");
22
+ if (!fs.default.existsSync(p)) return DEFAULT_PRICING;
23
+ try {
24
+ const custom = JSON.parse(fs.default.readFileSync(p, "utf-8"));
25
+ return {
26
+ ...DEFAULT_PRICING,
27
+ ...custom
28
+ };
29
+ } catch {
30
+ return DEFAULT_PRICING;
31
+ }
32
+ }
33
+ function computeCost(pricing, model, inputTokens, outputTokens) {
34
+ const price = pricing[model] ?? pricing["default"];
35
+ const cost = inputTokens / 1e6 * price.input + outputTokens / 1e6 * price.output;
36
+ return Math.round(cost * 1e6) / 1e6;
37
+ }
38
+ function recordUsage(dataDir, u) {
39
+ const entry = {
40
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
41
+ ...u.slug ? { slug: u.slug } : {},
42
+ ...u.tool ? { tool: u.tool } : {},
43
+ model: u.model,
44
+ inputTokens: u.inputTokens,
45
+ outputTokens: u.outputTokens,
46
+ costUsd: computeCost(loadPricing(dataDir), u.model, u.inputTokens, u.outputTokens)
47
+ };
48
+ const p = ledgerPath(dataDir);
49
+ try {
50
+ fs.default.mkdirSync(path.default.dirname(p), { recursive: true });
51
+ fs.default.appendFileSync(p, JSON.stringify(entry) + "\n", "utf-8");
52
+ } catch {}
53
+ }
54
+ //#endregion
55
+ exports.recordUsage = recordUsage;
56
+
57
+ //# sourceMappingURL=usage-CClTf5e6.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-CClTf5e6.cjs","names":[],"sources":["../src/core/usage.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\n/**\n * LLM token-cost observability (domino D3 / F7). Every LLM call can record its\n * token usage + computed cost, attributed per customer/tool, into an append-only\n * NDJSON ledger. Basis for cost transparency and outcome/consumption pricing.\n */\nexport interface UsageEntry {\n timestamp: string;\n slug?: string;\n tool?: string;\n model: string;\n inputTokens: number;\n outputTokens: number;\n costUsd: number;\n}\n\nexport interface PricePerMillion {\n input: number;\n output: number;\n}\n\n// USD per 1M tokens. Override via .agentic/llm-pricing.json. Defaults are\n// conservative Haiku-class estimates — verify against current provider pricing.\nconst DEFAULT_PRICING: Record<string, PricePerMillion> = {\n \"claude-haiku-4-5\": { input: 1.0, output: 5.0 },\n default: { input: 1.0, output: 5.0 },\n};\n\nfunction ledgerPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"usage.ndjson\");\n}\n\nfunction loadPricing(dataDir: string): Record<string, PricePerMillion> {\n const p = path.join(dataDir, \".agentic\", \"llm-pricing.json\");\n if (!fs.existsSync(p)) return DEFAULT_PRICING;\n try {\n const custom = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Record<\n string,\n PricePerMillion\n >;\n return { ...DEFAULT_PRICING, ...custom };\n } catch {\n return DEFAULT_PRICING;\n }\n}\n\nexport function computeCost(\n pricing: Record<string, PricePerMillion>,\n model: string,\n inputTokens: number,\n outputTokens: number\n): number {\n const price = pricing[model] ?? pricing[\"default\"]!;\n const cost = (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output;\n return Math.round(cost * 1_000_000) / 1_000_000;\n}\n\nexport function recordUsage(\n dataDir: string,\n u: { slug?: string; tool?: string; model: string; inputTokens: number; outputTokens: number }\n): void {\n const entry: UsageEntry = {\n timestamp: new Date().toISOString(),\n ...(u.slug ? { slug: u.slug } : {}),\n ...(u.tool ? { tool: u.tool } : {}),\n model: u.model,\n inputTokens: u.inputTokens,\n outputTokens: u.outputTokens,\n costUsd: computeCost(loadPricing(dataDir), u.model, u.inputTokens, u.outputTokens),\n };\n const p = ledgerPath(dataDir);\n try {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.appendFileSync(p, JSON.stringify(entry) + \"\\n\", \"utf-8\");\n } catch {\n /* non-fatal: usage logging must never break an LLM call */\n }\n}\n\nexport function loadUsage(dataDir: string): UsageEntry[] {\n const p = ledgerPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n return (fs.readFileSync(p, \"utf-8\") as string)\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line) as UsageEntry);\n } catch {\n return [];\n }\n}\n\nexport interface UsageAggregate {\n totalInputTokens: number;\n totalOutputTokens: number;\n totalCostUsd: number;\n calls: number;\n bySlug: Record<\n string,\n { inputTokens: number; outputTokens: number; costUsd: number; calls: number }\n >;\n}\n\nexport function aggregateUsage(dataDir: string, opts: { slug?: string } = {}): UsageAggregate {\n const agg: UsageAggregate = {\n totalInputTokens: 0,\n totalOutputTokens: 0,\n totalCostUsd: 0,\n calls: 0,\n bySlug: {},\n };\n for (const e of loadUsage(dataDir)) {\n if (opts.slug && e.slug !== opts.slug) continue;\n agg.totalInputTokens += e.inputTokens;\n agg.totalOutputTokens += e.outputTokens;\n agg.totalCostUsd += e.costUsd;\n agg.calls++;\n const key = e.slug ?? \"(unattributed)\";\n const b = agg.bySlug[key] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0, calls: 0 };\n b.inputTokens += e.inputTokens;\n b.outputTokens += e.outputTokens;\n b.costUsd += e.costUsd;\n b.calls++;\n agg.bySlug[key] = b;\n }\n agg.totalCostUsd = Math.round(agg.totalCostUsd * 1_000_000) / 1_000_000;\n return agg;\n}\n"],"mappings":";;;;;;AAyBA,MAAM,kBAAmD;CACvD,oBAAoB;EAAE,OAAO;EAAK,QAAQ;CAAI;CAC9C,SAAS;EAAE,OAAO;EAAK,QAAQ;CAAI;AACrC;AAEA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAA,QAAK,KAAK,SAAS,YAAY,cAAc;AACtD;AAEA,SAAS,YAAY,SAAkD;CACrE,MAAM,IAAI,KAAA,QAAK,KAAK,SAAS,YAAY,kBAAkB;CAC3D,IAAI,CAAC,GAAA,QAAG,WAAW,CAAC,GAAG,OAAO;CAC9B,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,GAAA,QAAG,aAAa,GAAG,OAAO,CAAW;EAI/D,OAAO;GAAE,GAAG;GAAiB,GAAG;EAAO;CACzC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,YACd,SACA,OACA,aACA,cACQ;CACR,MAAM,QAAQ,QAAQ,UAAU,QAAQ;CACxC,MAAM,OAAQ,cAAc,MAAa,MAAM,QAAS,eAAe,MAAa,MAAM;CAC1F,OAAO,KAAK,MAAM,OAAO,GAAS,IAAI;AACxC;AAEA,SAAgB,YACd,SACA,GACM;CACN,MAAM,QAAoB;EACxB,4BAAW,IAAI,KAAK,GAAE,YAAY;EAClC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,OAAO,EAAE;EACT,aAAa,EAAE;EACf,cAAc,EAAE;EAChB,SAAS,YAAY,YAAY,OAAO,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY;CACnF;CACA,MAAM,IAAI,WAAW,OAAO;CAC5B,IAAI;EACF,GAAA,QAAG,UAAU,KAAA,QAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;EACjD,GAAA,QAAG,eAAe,GAAG,KAAK,UAAU,KAAK,IAAI,MAAM,OAAO;CAC5D,QAAQ,CAER;AACF"}
@@ -0,0 +1,93 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ //#region src/core/usage.ts
4
+ const DEFAULT_PRICING = {
5
+ "claude-haiku-4-5": {
6
+ input: 1,
7
+ output: 5
8
+ },
9
+ default: {
10
+ input: 1,
11
+ output: 5
12
+ }
13
+ };
14
+ function ledgerPath(dataDir) {
15
+ return path.join(dataDir, ".agentic", "usage.ndjson");
16
+ }
17
+ function loadPricing(dataDir) {
18
+ const p = path.join(dataDir, ".agentic", "llm-pricing.json");
19
+ if (!fs.existsSync(p)) return DEFAULT_PRICING;
20
+ try {
21
+ const custom = JSON.parse(fs.readFileSync(p, "utf-8"));
22
+ return {
23
+ ...DEFAULT_PRICING,
24
+ ...custom
25
+ };
26
+ } catch {
27
+ return DEFAULT_PRICING;
28
+ }
29
+ }
30
+ function computeCost(pricing, model, inputTokens, outputTokens) {
31
+ const price = pricing[model] ?? pricing["default"];
32
+ const cost = inputTokens / 1e6 * price.input + outputTokens / 1e6 * price.output;
33
+ return Math.round(cost * 1e6) / 1e6;
34
+ }
35
+ function recordUsage(dataDir, u) {
36
+ const entry = {
37
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
38
+ ...u.slug ? { slug: u.slug } : {},
39
+ ...u.tool ? { tool: u.tool } : {},
40
+ model: u.model,
41
+ inputTokens: u.inputTokens,
42
+ outputTokens: u.outputTokens,
43
+ costUsd: computeCost(loadPricing(dataDir), u.model, u.inputTokens, u.outputTokens)
44
+ };
45
+ const p = ledgerPath(dataDir);
46
+ try {
47
+ fs.mkdirSync(path.dirname(p), { recursive: true });
48
+ fs.appendFileSync(p, JSON.stringify(entry) + "\n", "utf-8");
49
+ } catch {}
50
+ }
51
+ function loadUsage(dataDir) {
52
+ const p = ledgerPath(dataDir);
53
+ if (!fs.existsSync(p)) return [];
54
+ try {
55
+ return fs.readFileSync(p, "utf-8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
56
+ } catch {
57
+ return [];
58
+ }
59
+ }
60
+ function aggregateUsage(dataDir, opts = {}) {
61
+ const agg = {
62
+ totalInputTokens: 0,
63
+ totalOutputTokens: 0,
64
+ totalCostUsd: 0,
65
+ calls: 0,
66
+ bySlug: {}
67
+ };
68
+ for (const e of loadUsage(dataDir)) {
69
+ if (opts.slug && e.slug !== opts.slug) continue;
70
+ agg.totalInputTokens += e.inputTokens;
71
+ agg.totalOutputTokens += e.outputTokens;
72
+ agg.totalCostUsd += e.costUsd;
73
+ agg.calls++;
74
+ const key = e.slug ?? "(unattributed)";
75
+ const b = agg.bySlug[key] ?? {
76
+ inputTokens: 0,
77
+ outputTokens: 0,
78
+ costUsd: 0,
79
+ calls: 0
80
+ };
81
+ b.inputTokens += e.inputTokens;
82
+ b.outputTokens += e.outputTokens;
83
+ b.costUsd += e.costUsd;
84
+ b.calls++;
85
+ agg.bySlug[key] = b;
86
+ }
87
+ agg.totalCostUsd = Math.round(agg.totalCostUsd * 1e6) / 1e6;
88
+ return agg;
89
+ }
90
+ //#endregion
91
+ export { aggregateUsage, recordUsage };
92
+
93
+ //# sourceMappingURL=usage-D0-TYJkw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-D0-TYJkw.js","names":[],"sources":["../src/core/usage.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\n/**\n * LLM token-cost observability (domino D3 / F7). Every LLM call can record its\n * token usage + computed cost, attributed per customer/tool, into an append-only\n * NDJSON ledger. Basis for cost transparency and outcome/consumption pricing.\n */\nexport interface UsageEntry {\n timestamp: string;\n slug?: string;\n tool?: string;\n model: string;\n inputTokens: number;\n outputTokens: number;\n costUsd: number;\n}\n\nexport interface PricePerMillion {\n input: number;\n output: number;\n}\n\n// USD per 1M tokens. Override via .agentic/llm-pricing.json. Defaults are\n// conservative Haiku-class estimates — verify against current provider pricing.\nconst DEFAULT_PRICING: Record<string, PricePerMillion> = {\n \"claude-haiku-4-5\": { input: 1.0, output: 5.0 },\n default: { input: 1.0, output: 5.0 },\n};\n\nfunction ledgerPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"usage.ndjson\");\n}\n\nfunction loadPricing(dataDir: string): Record<string, PricePerMillion> {\n const p = path.join(dataDir, \".agentic\", \"llm-pricing.json\");\n if (!fs.existsSync(p)) return DEFAULT_PRICING;\n try {\n const custom = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Record<\n string,\n PricePerMillion\n >;\n return { ...DEFAULT_PRICING, ...custom };\n } catch {\n return DEFAULT_PRICING;\n }\n}\n\nexport function computeCost(\n pricing: Record<string, PricePerMillion>,\n model: string,\n inputTokens: number,\n outputTokens: number\n): number {\n const price = pricing[model] ?? pricing[\"default\"]!;\n const cost = (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output;\n return Math.round(cost * 1_000_000) / 1_000_000;\n}\n\nexport function recordUsage(\n dataDir: string,\n u: { slug?: string; tool?: string; model: string; inputTokens: number; outputTokens: number }\n): void {\n const entry: UsageEntry = {\n timestamp: new Date().toISOString(),\n ...(u.slug ? { slug: u.slug } : {}),\n ...(u.tool ? { tool: u.tool } : {}),\n model: u.model,\n inputTokens: u.inputTokens,\n outputTokens: u.outputTokens,\n costUsd: computeCost(loadPricing(dataDir), u.model, u.inputTokens, u.outputTokens),\n };\n const p = ledgerPath(dataDir);\n try {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.appendFileSync(p, JSON.stringify(entry) + \"\\n\", \"utf-8\");\n } catch {\n /* non-fatal: usage logging must never break an LLM call */\n }\n}\n\nexport function loadUsage(dataDir: string): UsageEntry[] {\n const p = ledgerPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n return (fs.readFileSync(p, \"utf-8\") as string)\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line) as UsageEntry);\n } catch {\n return [];\n }\n}\n\nexport interface UsageAggregate {\n totalInputTokens: number;\n totalOutputTokens: number;\n totalCostUsd: number;\n calls: number;\n bySlug: Record<\n string,\n { inputTokens: number; outputTokens: number; costUsd: number; calls: number }\n >;\n}\n\nexport function aggregateUsage(dataDir: string, opts: { slug?: string } = {}): UsageAggregate {\n const agg: UsageAggregate = {\n totalInputTokens: 0,\n totalOutputTokens: 0,\n totalCostUsd: 0,\n calls: 0,\n bySlug: {},\n };\n for (const e of loadUsage(dataDir)) {\n if (opts.slug && e.slug !== opts.slug) continue;\n agg.totalInputTokens += e.inputTokens;\n agg.totalOutputTokens += e.outputTokens;\n agg.totalCostUsd += e.costUsd;\n agg.calls++;\n const key = e.slug ?? \"(unattributed)\";\n const b = agg.bySlug[key] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0, calls: 0 };\n b.inputTokens += e.inputTokens;\n b.outputTokens += e.outputTokens;\n b.costUsd += e.costUsd;\n b.calls++;\n agg.bySlug[key] = b;\n }\n agg.totalCostUsd = Math.round(agg.totalCostUsd * 1_000_000) / 1_000_000;\n return agg;\n}\n"],"mappings":";;;AAyBA,MAAM,kBAAmD;CACvD,oBAAoB;EAAE,OAAO;EAAK,QAAQ;CAAI;CAC9C,SAAS;EAAE,OAAO;EAAK,QAAQ;CAAI;AACrC;AAEA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,cAAc;AACtD;AAEA,SAAS,YAAY,SAAkD;CACrE,MAAM,IAAI,KAAK,KAAK,SAAS,YAAY,kBAAkB;CAC3D,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO;CAC9B,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAI/D,OAAO;GAAE,GAAG;GAAiB,GAAG;EAAO;CACzC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,YACd,SACA,OACA,aACA,cACQ;CACR,MAAM,QAAQ,QAAQ,UAAU,QAAQ;CACxC,MAAM,OAAQ,cAAc,MAAa,MAAM,QAAS,eAAe,MAAa,MAAM;CAC1F,OAAO,KAAK,MAAM,OAAO,GAAS,IAAI;AACxC;AAEA,SAAgB,YACd,SACA,GACM;CACN,MAAM,QAAoB;EACxB,4BAAW,IAAI,KAAK,GAAE,YAAY;EAClC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,OAAO,EAAE;EACT,aAAa,EAAE;EACf,cAAc,EAAE;EAChB,SAAS,YAAY,YAAY,OAAO,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY;CACnF;CACA,MAAM,IAAI,WAAW,OAAO;CAC5B,IAAI;EACF,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;EACjD,GAAG,eAAe,GAAG,KAAK,UAAU,KAAK,IAAI,MAAM,OAAO;CAC5D,QAAQ,CAER;AACF;AAEA,SAAgB,UAAU,SAA+B;CACvD,MAAM,IAAI,WAAW,OAAO;CAC5B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,OAAQ,GAAG,aAAa,GAAG,OAAO,EAC/B,MAAM,IAAI,EACV,OAAO,OAAO,EACd,KAAK,SAAS,KAAK,MAAM,IAAI,CAAe;CACjD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAaA,SAAgB,eAAe,SAAiB,OAA0B,CAAC,GAAmB;CAC5F,MAAM,MAAsB;EAC1B,kBAAkB;EAClB,mBAAmB;EACnB,cAAc;EACd,OAAO;EACP,QAAQ,CAAC;CACX;CACA,KAAK,MAAM,KAAK,UAAU,OAAO,GAAG;EAClC,IAAI,KAAK,QAAQ,EAAE,SAAS,KAAK,MAAM;EACvC,IAAI,oBAAoB,EAAE;EAC1B,IAAI,qBAAqB,EAAE;EAC3B,IAAI,gBAAgB,EAAE;EACtB,IAAI;EACJ,MAAM,MAAM,EAAE,QAAQ;EACtB,MAAM,IAAI,IAAI,OAAO,QAAQ;GAAE,aAAa;GAAG,cAAc;GAAG,SAAS;GAAG,OAAO;EAAE;EACrF,EAAE,eAAe,EAAE;EACnB,EAAE,gBAAgB,EAAE;EACpB,EAAE,WAAW,EAAE;EACf,EAAE;EACF,IAAI,OAAO,OAAO;CACpB;CACA,IAAI,eAAe,KAAK,MAAM,IAAI,eAAe,GAAS,IAAI;CAC9D,OAAO;AACT"}
@@ -0,0 +1,54 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ //#region src/core/usage.ts
4
+ const DEFAULT_PRICING = {
5
+ "claude-haiku-4-5": {
6
+ input: 1,
7
+ output: 5
8
+ },
9
+ default: {
10
+ input: 1,
11
+ output: 5
12
+ }
13
+ };
14
+ function ledgerPath(dataDir) {
15
+ return path.join(dataDir, ".agentic", "usage.ndjson");
16
+ }
17
+ function loadPricing(dataDir) {
18
+ const p = path.join(dataDir, ".agentic", "llm-pricing.json");
19
+ if (!fs.existsSync(p)) return DEFAULT_PRICING;
20
+ try {
21
+ const custom = JSON.parse(fs.readFileSync(p, "utf-8"));
22
+ return {
23
+ ...DEFAULT_PRICING,
24
+ ...custom
25
+ };
26
+ } catch {
27
+ return DEFAULT_PRICING;
28
+ }
29
+ }
30
+ function computeCost(pricing, model, inputTokens, outputTokens) {
31
+ const price = pricing[model] ?? pricing["default"];
32
+ const cost = inputTokens / 1e6 * price.input + outputTokens / 1e6 * price.output;
33
+ return Math.round(cost * 1e6) / 1e6;
34
+ }
35
+ function recordUsage(dataDir, u) {
36
+ const entry = {
37
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
38
+ ...u.slug ? { slug: u.slug } : {},
39
+ ...u.tool ? { tool: u.tool } : {},
40
+ model: u.model,
41
+ inputTokens: u.inputTokens,
42
+ outputTokens: u.outputTokens,
43
+ costUsd: computeCost(loadPricing(dataDir), u.model, u.inputTokens, u.outputTokens)
44
+ };
45
+ const p = ledgerPath(dataDir);
46
+ try {
47
+ fs.mkdirSync(path.dirname(p), { recursive: true });
48
+ fs.appendFileSync(p, JSON.stringify(entry) + "\n", "utf-8");
49
+ } catch {}
50
+ }
51
+ //#endregion
52
+ export { recordUsage };
53
+
54
+ //# sourceMappingURL=usage-D0u9a-lV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-D0u9a-lV.js","names":[],"sources":["../src/core/usage.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\n/**\n * LLM token-cost observability (domino D3 / F7). Every LLM call can record its\n * token usage + computed cost, attributed per customer/tool, into an append-only\n * NDJSON ledger. Basis for cost transparency and outcome/consumption pricing.\n */\nexport interface UsageEntry {\n timestamp: string;\n slug?: string;\n tool?: string;\n model: string;\n inputTokens: number;\n outputTokens: number;\n costUsd: number;\n}\n\nexport interface PricePerMillion {\n input: number;\n output: number;\n}\n\n// USD per 1M tokens. Override via .agentic/llm-pricing.json. Defaults are\n// conservative Haiku-class estimates — verify against current provider pricing.\nconst DEFAULT_PRICING: Record<string, PricePerMillion> = {\n \"claude-haiku-4-5\": { input: 1.0, output: 5.0 },\n default: { input: 1.0, output: 5.0 },\n};\n\nfunction ledgerPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"usage.ndjson\");\n}\n\nfunction loadPricing(dataDir: string): Record<string, PricePerMillion> {\n const p = path.join(dataDir, \".agentic\", \"llm-pricing.json\");\n if (!fs.existsSync(p)) return DEFAULT_PRICING;\n try {\n const custom = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Record<\n string,\n PricePerMillion\n >;\n return { ...DEFAULT_PRICING, ...custom };\n } catch {\n return DEFAULT_PRICING;\n }\n}\n\nexport function computeCost(\n pricing: Record<string, PricePerMillion>,\n model: string,\n inputTokens: number,\n outputTokens: number\n): number {\n const price = pricing[model] ?? pricing[\"default\"]!;\n const cost = (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output;\n return Math.round(cost * 1_000_000) / 1_000_000;\n}\n\nexport function recordUsage(\n dataDir: string,\n u: { slug?: string; tool?: string; model: string; inputTokens: number; outputTokens: number }\n): void {\n const entry: UsageEntry = {\n timestamp: new Date().toISOString(),\n ...(u.slug ? { slug: u.slug } : {}),\n ...(u.tool ? { tool: u.tool } : {}),\n model: u.model,\n inputTokens: u.inputTokens,\n outputTokens: u.outputTokens,\n costUsd: computeCost(loadPricing(dataDir), u.model, u.inputTokens, u.outputTokens),\n };\n const p = ledgerPath(dataDir);\n try {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.appendFileSync(p, JSON.stringify(entry) + \"\\n\", \"utf-8\");\n } catch {\n /* non-fatal: usage logging must never break an LLM call */\n }\n}\n\nexport function loadUsage(dataDir: string): UsageEntry[] {\n const p = ledgerPath(dataDir);\n if (!fs.existsSync(p)) return [];\n try {\n return (fs.readFileSync(p, \"utf-8\") as string)\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line) as UsageEntry);\n } catch {\n return [];\n }\n}\n\nexport interface UsageAggregate {\n totalInputTokens: number;\n totalOutputTokens: number;\n totalCostUsd: number;\n calls: number;\n bySlug: Record<\n string,\n { inputTokens: number; outputTokens: number; costUsd: number; calls: number }\n >;\n}\n\nexport function aggregateUsage(dataDir: string, opts: { slug?: string } = {}): UsageAggregate {\n const agg: UsageAggregate = {\n totalInputTokens: 0,\n totalOutputTokens: 0,\n totalCostUsd: 0,\n calls: 0,\n bySlug: {},\n };\n for (const e of loadUsage(dataDir)) {\n if (opts.slug && e.slug !== opts.slug) continue;\n agg.totalInputTokens += e.inputTokens;\n agg.totalOutputTokens += e.outputTokens;\n agg.totalCostUsd += e.costUsd;\n agg.calls++;\n const key = e.slug ?? \"(unattributed)\";\n const b = agg.bySlug[key] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0, calls: 0 };\n b.inputTokens += e.inputTokens;\n b.outputTokens += e.outputTokens;\n b.costUsd += e.costUsd;\n b.calls++;\n agg.bySlug[key] = b;\n }\n agg.totalCostUsd = Math.round(agg.totalCostUsd * 1_000_000) / 1_000_000;\n return agg;\n}\n"],"mappings":";;;AAyBA,MAAM,kBAAmD;CACvD,oBAAoB;EAAE,OAAO;EAAK,QAAQ;CAAI;CAC9C,SAAS;EAAE,OAAO;EAAK,QAAQ;CAAI;AACrC;AAEA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,cAAc;AACtD;AAEA,SAAS,YAAY,SAAkD;CACrE,MAAM,IAAI,KAAK,KAAK,SAAS,YAAY,kBAAkB;CAC3D,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO;CAC9B,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAI/D,OAAO;GAAE,GAAG;GAAiB,GAAG;EAAO;CACzC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,YACd,SACA,OACA,aACA,cACQ;CACR,MAAM,QAAQ,QAAQ,UAAU,QAAQ;CACxC,MAAM,OAAQ,cAAc,MAAa,MAAM,QAAS,eAAe,MAAa,MAAM;CAC1F,OAAO,KAAK,MAAM,OAAO,GAAS,IAAI;AACxC;AAEA,SAAgB,YACd,SACA,GACM;CACN,MAAM,QAAoB;EACxB,4BAAW,IAAI,KAAK,GAAE,YAAY;EAClC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,OAAO,EAAE;EACT,aAAa,EAAE;EACf,cAAc,EAAE;EAChB,SAAS,YAAY,YAAY,OAAO,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY;CACnF;CACA,MAAM,IAAI,WAAW,OAAO;CAC5B,IAAI;EACF,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;EACjD,GAAG,eAAe,GAAG,KAAK,UAAU,KAAK,IAAI,MAAM,OAAO;CAC5D,QAAQ,CAER;AACF"}
@@ -0,0 +1,2 @@
1
+ import { i as removeSecret, n as listSecretKeys, o as setSecret, t as getSecret } from "./vault-DXCg29W-.js";
2
+ export { getSecret, listSecretKeys, removeSecret, setSecret };
@@ -0,0 +1,86 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import crypto from "crypto";
4
+ //#region src/core/encryption.ts
5
+ const ALGORITHM = "aes-256-gcm";
6
+ const KEY_LEN = 32;
7
+ const IV_LEN = 12;
8
+ function deriveKey(secret) {
9
+ return crypto.scryptSync(secret, "dxcrm-salt-v1", KEY_LEN);
10
+ }
11
+ function encryptField(plaintext, key) {
12
+ const iv = crypto.randomBytes(IV_LEN);
13
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
14
+ const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
15
+ const authTag = cipher.getAuthTag();
16
+ return {
17
+ iv: iv.toString("hex"),
18
+ ciphertext: ciphertext.toString("hex"),
19
+ authTag: authTag.toString("hex")
20
+ };
21
+ }
22
+ function decryptField(encrypted, key) {
23
+ const iv = Buffer.from(encrypted.iv, "hex");
24
+ const ciphertext = Buffer.from(encrypted.ciphertext, "hex");
25
+ const authTag = Buffer.from(encrypted.authTag, "hex");
26
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
27
+ decipher.setAuthTag(authTag);
28
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
29
+ }
30
+ function encryptFieldStr(plaintext, secret) {
31
+ return JSON.stringify(encryptField(plaintext, deriveKey(secret)));
32
+ }
33
+ function decryptFieldStr(encryptedJson, secret) {
34
+ return decryptField(JSON.parse(encryptedJson), deriveKey(secret));
35
+ }
36
+ //#endregion
37
+ //#region src/core/vault.ts
38
+ function vaultPath(dataDir) {
39
+ return path.join(dataDir, ".agentic", "vault.enc");
40
+ }
41
+ /** Read + decrypt the vault. Empty vault when the file does not exist yet. */
42
+ function loadVault(dataDir, key) {
43
+ const p = vaultPath(dataDir);
44
+ if (!fs.existsSync(p)) return {};
45
+ const encrypted = fs.readFileSync(p, "utf-8");
46
+ let decrypted;
47
+ try {
48
+ decrypted = decryptFieldStr(encrypted, key);
49
+ } catch {
50
+ throw new Error("Unable to decrypt vault: wrong master key or corrupted vault file.");
51
+ }
52
+ const data = JSON.parse(decrypted);
53
+ return data && typeof data === "object" ? data : {};
54
+ }
55
+ /** Encrypt + write the vault atomically (overwrites the single blob). */
56
+ function saveVault(dataDir, key, data) {
57
+ const p = vaultPath(dataDir);
58
+ fs.mkdirSync(path.dirname(p), { recursive: true });
59
+ fs.writeFileSync(p, encryptFieldStr(JSON.stringify(data), key), "utf-8");
60
+ }
61
+ /** Store (or overwrite) a secret under `name`. */
62
+ function setSecret(dataDir, key, name, value) {
63
+ const data = loadVault(dataDir, key);
64
+ data[name] = value;
65
+ saveVault(dataDir, key, data);
66
+ }
67
+ /** Retrieve a secret; returns undefined when absent. Throws on wrong master key. */
68
+ function getSecret(dataDir, key, name) {
69
+ return loadVault(dataDir, key)[name];
70
+ }
71
+ /** List the names of all stored secrets (values stay encrypted). */
72
+ function listSecretKeys(dataDir, key) {
73
+ return Object.keys(loadVault(dataDir, key));
74
+ }
75
+ /** Remove a secret; returns true when something was deleted. */
76
+ function removeSecret(dataDir, key, name) {
77
+ const data = loadVault(dataDir, key);
78
+ if (!(name in data)) return false;
79
+ delete data[name];
80
+ saveVault(dataDir, key, data);
81
+ return true;
82
+ }
83
+ //#endregion
84
+ export { saveVault as a, removeSecret as i, listSecretKeys as n, setSecret as o, loadVault as r, getSecret as t };
85
+
86
+ //# sourceMappingURL=vault-DXCg29W-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-DXCg29W-.js","names":[],"sources":["../src/core/encryption.ts","../src/core/vault.ts"],"sourcesContent":["import crypto from \"crypto\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst KEY_LEN = 32;\nconst IV_LEN = 12;\n\nexport function deriveKey(secret: string): Buffer {\n return crypto.scryptSync(secret, \"dxcrm-salt-v1\", KEY_LEN);\n}\n\nexport interface EncryptedField {\n iv: string;\n ciphertext: string;\n authTag: string;\n}\n\nexport function encryptField(plaintext: string, key: Buffer): EncryptedField {\n const iv = crypto.randomBytes(IV_LEN);\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\n const ciphertext = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const authTag = cipher.getAuthTag();\n return {\n iv: iv.toString(\"hex\"),\n ciphertext: ciphertext.toString(\"hex\"),\n authTag: authTag.toString(\"hex\"),\n };\n}\n\nexport function decryptField(encrypted: EncryptedField, key: Buffer): string {\n const iv = Buffer.from(encrypted.iv, \"hex\");\n const ciphertext = Buffer.from(encrypted.ciphertext, \"hex\");\n const authTag = Buffer.from(encrypted.authTag, \"hex\");\n const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(authTag);\n return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString(\"utf8\");\n}\n\nexport function encryptFieldStr(plaintext: string, secret: string): string {\n return JSON.stringify(encryptField(plaintext, deriveKey(secret)));\n}\n\nexport function decryptFieldStr(encryptedJson: string, secret: string): string {\n return decryptField(JSON.parse(encryptedJson) as EncryptedField, deriveKey(secret));\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport { encryptFieldStr, decryptFieldStr } from \"./encryption.js\";\n\n/**\n * Local credential vault (domino D12 / F6): a dependency-free, AES-256-GCM\n * encrypted store for secrets the agent must hold (portal passwords, API keys)\n * but the customer markdown must never contain in plaintext. The whole vault is\n * a single encrypted blob at `.agentic/vault.enc`; the master key lives only in\n * the operator's environment (DXCRM_VAULT_KEY) — never on disk, never committed.\n * A GUI is a documented follow-up; this is the headless, scriptable core.\n */\ntype VaultData = Record<string, string>;\n\nfunction vaultPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"vault.enc\");\n}\n\n/** Read + decrypt the vault. Empty vault when the file does not exist yet. */\nexport function loadVault(dataDir: string, key: string): VaultData {\n const p = vaultPath(dataDir);\n if (!fs.existsSync(p)) return {};\n const encrypted = fs.readFileSync(p, \"utf-8\") as string;\n let decrypted: string;\n try {\n decrypted = decryptFieldStr(encrypted, key);\n } catch {\n // AES-GCM auth-tag failure → wrong master key (or a corrupted/tampered file).\n throw new Error(\"Unable to decrypt vault: wrong master key or corrupted vault file.\");\n }\n const data = JSON.parse(decrypted) as VaultData;\n return data && typeof data === \"object\" ? data : {};\n}\n\n/** Encrypt + write the vault atomically (overwrites the single blob). */\nexport function saveVault(dataDir: string, key: string, data: VaultData): void {\n const p = vaultPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, encryptFieldStr(JSON.stringify(data), key), \"utf-8\");\n}\n\n/** Store (or overwrite) a secret under `name`. */\nexport function setSecret(dataDir: string, key: string, name: string, value: string): void {\n const data = loadVault(dataDir, key);\n data[name] = value;\n saveVault(dataDir, key, data);\n}\n\n/** Retrieve a secret; returns undefined when absent. Throws on wrong master key. */\nexport function getSecret(dataDir: string, key: string, name: string): string | undefined {\n return loadVault(dataDir, key)[name];\n}\n\n/** List the names of all stored secrets (values stay encrypted). */\nexport function listSecretKeys(dataDir: string, key: string): string[] {\n return Object.keys(loadVault(dataDir, key));\n}\n\n/** Remove a secret; returns true when something was deleted. */\nexport function removeSecret(dataDir: string, key: string, name: string): boolean {\n const data = loadVault(dataDir, key);\n if (!(name in data)) return false;\n delete data[name];\n saveVault(dataDir, key, data);\n return true;\n}\n"],"mappings":";;;;AAEA,MAAM,YAAY;AAClB,MAAM,UAAU;AAChB,MAAM,SAAS;AAEf,SAAgB,UAAU,QAAwB;CAChD,OAAO,OAAO,WAAW,QAAQ,iBAAiB,OAAO;AAC3D;AAQA,SAAgB,aAAa,WAAmB,KAA6B;CAC3E,MAAM,KAAK,OAAO,YAAY,MAAM;CACpC,MAAM,SAAS,OAAO,eAAe,WAAW,KAAK,EAAE;CACvD,MAAM,aAAa,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;CACnF,MAAM,UAAU,OAAO,WAAW;CAClC,OAAO;EACL,IAAI,GAAG,SAAS,KAAK;EACrB,YAAY,WAAW,SAAS,KAAK;EACrC,SAAS,QAAQ,SAAS,KAAK;CACjC;AACF;AAEA,SAAgB,aAAa,WAA2B,KAAqB;CAC3E,MAAM,KAAK,OAAO,KAAK,UAAU,IAAI,KAAK;CAC1C,MAAM,aAAa,OAAO,KAAK,UAAU,YAAY,KAAK;CAC1D,MAAM,UAAU,OAAO,KAAK,UAAU,SAAS,KAAK;CACpD,MAAM,WAAW,OAAO,iBAAiB,WAAW,KAAK,EAAE;CAC3D,SAAS,WAAW,OAAO;CAC3B,OAAO,OAAO,OAAO,CAAC,SAAS,OAAO,UAAU,GAAG,SAAS,MAAM,CAAC,CAAC,EAAE,SAAS,MAAM;AACvF;AAEA,SAAgB,gBAAgB,WAAmB,QAAwB;CACzE,OAAO,KAAK,UAAU,aAAa,WAAW,UAAU,MAAM,CAAC,CAAC;AAClE;AAEA,SAAgB,gBAAgB,eAAuB,QAAwB;CAC7E,OAAO,aAAa,KAAK,MAAM,aAAa,GAAqB,UAAU,MAAM,CAAC;AACpF;;;AC7BA,SAAS,UAAU,SAAyB;CAC1C,OAAO,KAAK,KAAK,SAAS,YAAY,WAAW;AACnD;;AAGA,SAAgB,UAAU,SAAiB,KAAwB;CACjE,MAAM,IAAI,UAAU,OAAO;CAC3B,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,MAAM,YAAY,GAAG,aAAa,GAAG,OAAO;CAC5C,IAAI;CACJ,IAAI;EACF,YAAY,gBAAgB,WAAW,GAAG;CAC5C,QAAQ;EAEN,MAAM,IAAI,MAAM,oEAAoE;CACtF;CACA,MAAM,OAAO,KAAK,MAAM,SAAS;CACjC,OAAO,QAAQ,OAAO,SAAS,WAAW,OAAO,CAAC;AACpD;;AAGA,SAAgB,UAAU,SAAiB,KAAa,MAAuB;CAC7E,MAAM,IAAI,UAAU,OAAO;CAC3B,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,gBAAgB,KAAK,UAAU,IAAI,GAAG,GAAG,GAAG,OAAO;AACzE;;AAGA,SAAgB,UAAU,SAAiB,KAAa,MAAc,OAAqB;CACzF,MAAM,OAAO,UAAU,SAAS,GAAG;CACnC,KAAK,QAAQ;CACb,UAAU,SAAS,KAAK,IAAI;AAC9B;;AAGA,SAAgB,UAAU,SAAiB,KAAa,MAAkC;CACxF,OAAO,UAAU,SAAS,GAAG,EAAE;AACjC;;AAGA,SAAgB,eAAe,SAAiB,KAAuB;CACrE,OAAO,OAAO,KAAK,UAAU,SAAS,GAAG,CAAC;AAC5C;;AAGA,SAAgB,aAAa,SAAiB,KAAa,MAAuB;CAChF,MAAM,OAAO,UAAU,SAAS,GAAG;CACnC,IAAI,EAAE,QAAQ,OAAO,OAAO;CAC5B,OAAO,KAAK;CACZ,UAAU,SAAS,KAAK,IAAI;CAC5B,OAAO;AACT"}
@@ -0,0 +1,138 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { createHash, createHmac, randomBytes } from "crypto";
4
+ //#region src/core/webhooks.ts
5
+ function subsPath(dataDir) {
6
+ return path.join(dataDir, ".agentic", "webhooks.json");
7
+ }
8
+ function failuresPath(dataDir) {
9
+ return path.join(dataDir, ".agentic", "webhook-failures.json");
10
+ }
11
+ function readJson(p, key) {
12
+ if (!fs.existsSync(p)) return [];
13
+ try {
14
+ const data = JSON.parse(fs.readFileSync(p, "utf-8"));
15
+ return Array.isArray(data[key]) ? data[key] : [];
16
+ } catch {
17
+ return [];
18
+ }
19
+ }
20
+ function writeJson(p, key, items) {
21
+ fs.mkdirSync(path.dirname(p), { recursive: true });
22
+ fs.writeFileSync(p, JSON.stringify({ [key]: items }, null, 2), "utf-8");
23
+ }
24
+ function loadWebhooks(dataDir) {
25
+ return readJson(subsPath(dataDir), "subscriptions");
26
+ }
27
+ function addWebhook(dataDir, url, events, secret) {
28
+ const sub = {
29
+ id: `wh_${randomBytes(5).toString("hex")}`,
30
+ url,
31
+ events,
32
+ ...secret ? { secret } : {},
33
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
34
+ };
35
+ writeJson(subsPath(dataDir), "subscriptions", [...loadWebhooks(dataDir), sub]);
36
+ return sub;
37
+ }
38
+ function removeWebhook(dataDir, id) {
39
+ const subs = loadWebhooks(dataDir);
40
+ const next = subs.filter((s) => s.id !== id);
41
+ if (next.length === subs.length) return false;
42
+ writeJson(subsPath(dataDir), "subscriptions", next);
43
+ return true;
44
+ }
45
+ /** A subscription matches an event by exact name, "*", or a "prefix.*" pattern. */
46
+ function matchSubscriptions(subs, event) {
47
+ return subs.filter((s) => s.events.some((pat) => {
48
+ if (pat === "*" || pat === event) return true;
49
+ if (pat.endsWith(".*")) return event.startsWith(pat.slice(0, -1));
50
+ return false;
51
+ }));
52
+ }
53
+ function signPayload(secret, body) {
54
+ return createHmac("sha256", secret).update(body).digest("hex");
55
+ }
56
+ function loadFailures(dataDir) {
57
+ return readJson(failuresPath(dataDir), "failures");
58
+ }
59
+ async function deliver(sub, event, payload) {
60
+ const body = JSON.stringify({
61
+ event,
62
+ payload,
63
+ deliveredAt: (/* @__PURE__ */ new Date()).toISOString()
64
+ });
65
+ const headers = {
66
+ "Content-Type": "application/json",
67
+ "X-DXCRM-Event": event
68
+ };
69
+ if (sub.secret) headers["X-DXCRM-Signature"] = `sha256=${signPayload(sub.secret, body)}`;
70
+ try {
71
+ const res = await fetch(sub.url, {
72
+ method: "POST",
73
+ headers,
74
+ body
75
+ });
76
+ if (!res.ok) return {
77
+ ok: false,
78
+ error: `HTTP ${res.status}`
79
+ };
80
+ return { ok: true };
81
+ } catch (err) {
82
+ return {
83
+ ok: false,
84
+ error: err.message
85
+ };
86
+ }
87
+ }
88
+ /** Emit an event to all matching subscriptions; queue failures for replay. */
89
+ async function emitEvent(dataDir, event, payload) {
90
+ const matched = matchSubscriptions(loadWebhooks(dataDir), event);
91
+ if (matched.length === 0) return;
92
+ const failures = loadFailures(dataDir);
93
+ for (const sub of matched) {
94
+ const r = await deliver(sub, event, payload);
95
+ if (!r.ok) failures.push({
96
+ id: `whf_${createHash("sha256").update(`${sub.id}:${event}:${Date.now()}`).digest("hex").slice(0, 10)}`,
97
+ subscriptionId: sub.id,
98
+ url: sub.url,
99
+ ...sub.secret ? { secret: sub.secret } : {},
100
+ event,
101
+ payload,
102
+ attempts: 1,
103
+ lastError: r.error ?? "unknown",
104
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString()
105
+ });
106
+ }
107
+ if (failures.length > 0) writeJson(failuresPath(dataDir), "failures", failures);
108
+ }
109
+ /** Re-attempt queued failures; remove on success, increment attempts on failure. */
110
+ async function retryFailures(dataDir) {
111
+ const failures = loadFailures(dataDir);
112
+ const remaining = [];
113
+ let retried = 0;
114
+ for (const f of failures) {
115
+ const r = await deliver({
116
+ id: f.subscriptionId,
117
+ url: f.url,
118
+ events: [f.event],
119
+ ...f.secret ? { secret: f.secret } : {},
120
+ createdAt: f.queuedAt
121
+ }, f.event, f.payload);
122
+ if (r.ok) retried++;
123
+ else remaining.push({
124
+ ...f,
125
+ attempts: f.attempts + 1,
126
+ lastError: r.error ?? "unknown"
127
+ });
128
+ }
129
+ writeJson(failuresPath(dataDir), "failures", remaining);
130
+ return {
131
+ retried,
132
+ stillFailing: remaining.length
133
+ };
134
+ }
135
+ //#endregion
136
+ export { addWebhook, emitEvent, loadWebhooks, removeWebhook, retryFailures };
137
+
138
+ //# sourceMappingURL=webhooks-7EpA05Qr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks-7EpA05Qr.js","names":[],"sources":["../src/core/webhooks.ts"],"sourcesContent":["import { createHash, createHmac, randomBytes } from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\n\n/**\n * Outbound webhooks (event-driven architecture, N5-2). Subscriptions live in\n * .agentic/webhooks.json; failed deliveries are queued in\n * .agentic/webhook-failures.json (replay store) and re-attempted by\n * retryFailures (e.g. from the daemon) — backoff via periodic replay.\n */\nexport interface WebhookSubscription {\n id: string;\n url: string;\n events: string[];\n secret?: string;\n createdAt: string;\n}\n\nexport interface WebhookFailure {\n id: string;\n subscriptionId: string;\n url: string;\n secret?: string;\n event: string;\n payload: unknown;\n attempts: number;\n lastError: string;\n queuedAt: string;\n}\n\nfunction subsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhooks.json\");\n}\nfunction failuresPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhook-failures.json\");\n}\n\nfunction readJson<T>(p: string, key: string): T[] {\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Record<string, T[]>;\n return Array.isArray(data[key]) ? data[key]! : [];\n } catch {\n return [];\n }\n}\nfunction writeJson<T>(p: string, key: string, items: T[]): void {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ [key]: items }, null, 2), \"utf-8\");\n}\n\nexport function loadWebhooks(dataDir: string): WebhookSubscription[] {\n return readJson<WebhookSubscription>(subsPath(dataDir), \"subscriptions\");\n}\n\nexport function addWebhook(\n dataDir: string,\n url: string,\n events: string[],\n secret?: string\n): WebhookSubscription {\n const sub: WebhookSubscription = {\n id: `wh_${randomBytes(5).toString(\"hex\")}`,\n url,\n events,\n ...(secret ? { secret } : {}),\n createdAt: new Date().toISOString(),\n };\n writeJson(subsPath(dataDir), \"subscriptions\", [...loadWebhooks(dataDir), sub]);\n return sub;\n}\n\nexport function removeWebhook(dataDir: string, id: string): boolean {\n const subs = loadWebhooks(dataDir);\n const next = subs.filter((s) => s.id !== id);\n if (next.length === subs.length) return false;\n writeJson(subsPath(dataDir), \"subscriptions\", next);\n return true;\n}\n\n/** A subscription matches an event by exact name, \"*\", or a \"prefix.*\" pattern. */\nexport function matchSubscriptions(\n subs: WebhookSubscription[],\n event: string\n): WebhookSubscription[] {\n return subs.filter((s) =>\n s.events.some((pat) => {\n if (pat === \"*\" || pat === event) return true;\n if (pat.endsWith(\".*\")) return event.startsWith(pat.slice(0, -1));\n return false;\n })\n );\n}\n\nexport function signPayload(secret: string, body: string): string {\n return createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n}\n\nexport function loadFailures(dataDir: string): WebhookFailure[] {\n return readJson<WebhookFailure>(failuresPath(dataDir), \"failures\");\n}\n\nasync function deliver(\n sub: WebhookSubscription,\n event: string,\n payload: unknown\n): Promise<{ ok: boolean; error?: string }> {\n const body = JSON.stringify({ event, payload, deliveredAt: new Date().toISOString() });\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-DXCRM-Event\": event,\n };\n if (sub.secret) headers[\"X-DXCRM-Signature\"] = `sha256=${signPayload(sub.secret, body)}`;\n try {\n const res = (await fetch(sub.url, { method: \"POST\", headers, body })) as {\n ok: boolean;\n status: number;\n };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };\n return { ok: true };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\n/** Emit an event to all matching subscriptions; queue failures for replay. */\nexport async function emitEvent(dataDir: string, event: string, payload: unknown): Promise<void> {\n const matched = matchSubscriptions(loadWebhooks(dataDir), event);\n if (matched.length === 0) return;\n const failures = loadFailures(dataDir);\n for (const sub of matched) {\n const r = await deliver(sub, event, payload);\n if (!r.ok) {\n failures.push({\n id: `whf_${createHash(\"sha256\").update(`${sub.id}:${event}:${Date.now()}`).digest(\"hex\").slice(0, 10)}`,\n subscriptionId: sub.id,\n url: sub.url,\n ...(sub.secret ? { secret: sub.secret } : {}),\n event,\n payload,\n attempts: 1,\n lastError: r.error ?? \"unknown\",\n queuedAt: new Date().toISOString(),\n });\n }\n }\n if (failures.length > 0) writeJson(failuresPath(dataDir), \"failures\", failures);\n}\n\n/** Re-attempt queued failures; remove on success, increment attempts on failure. */\nexport async function retryFailures(\n dataDir: string\n): Promise<{ retried: number; stillFailing: number }> {\n const failures = loadFailures(dataDir);\n const remaining: WebhookFailure[] = [];\n let retried = 0;\n for (const f of failures) {\n const sub: WebhookSubscription = {\n id: f.subscriptionId,\n url: f.url,\n events: [f.event],\n ...(f.secret ? { secret: f.secret } : {}),\n createdAt: f.queuedAt,\n };\n const r = await deliver(sub, f.event, f.payload);\n if (r.ok) retried++;\n else remaining.push({ ...f, attempts: f.attempts + 1, lastError: r.error ?? \"unknown\" });\n }\n writeJson(failuresPath(dataDir), \"failures\", remaining);\n return { retried, stillFailing: remaining.length };\n}\n"],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { createHash, createHmac } from "crypto";
4
+ //#region src/core/webhooks.ts
5
+ function subsPath(dataDir) {
6
+ return path.join(dataDir, ".agentic", "webhooks.json");
7
+ }
8
+ function failuresPath(dataDir) {
9
+ return path.join(dataDir, ".agentic", "webhook-failures.json");
10
+ }
11
+ function readJson(p, key) {
12
+ if (!fs.existsSync(p)) return [];
13
+ try {
14
+ const data = JSON.parse(fs.readFileSync(p, "utf-8"));
15
+ return Array.isArray(data[key]) ? data[key] : [];
16
+ } catch {
17
+ return [];
18
+ }
19
+ }
20
+ function writeJson(p, key, items) {
21
+ fs.mkdirSync(path.dirname(p), { recursive: true });
22
+ fs.writeFileSync(p, JSON.stringify({ [key]: items }, null, 2), "utf-8");
23
+ }
24
+ function loadWebhooks(dataDir) {
25
+ return readJson(subsPath(dataDir), "subscriptions");
26
+ }
27
+ /** A subscription matches an event by exact name, "*", or a "prefix.*" pattern. */
28
+ function matchSubscriptions(subs, event) {
29
+ return subs.filter((s) => s.events.some((pat) => {
30
+ if (pat === "*" || pat === event) return true;
31
+ if (pat.endsWith(".*")) return event.startsWith(pat.slice(0, -1));
32
+ return false;
33
+ }));
34
+ }
35
+ function signPayload(secret, body) {
36
+ return createHmac("sha256", secret).update(body).digest("hex");
37
+ }
38
+ function loadFailures(dataDir) {
39
+ return readJson(failuresPath(dataDir), "failures");
40
+ }
41
+ async function deliver(sub, event, payload) {
42
+ const body = JSON.stringify({
43
+ event,
44
+ payload,
45
+ deliveredAt: (/* @__PURE__ */ new Date()).toISOString()
46
+ });
47
+ const headers = {
48
+ "Content-Type": "application/json",
49
+ "X-DXCRM-Event": event
50
+ };
51
+ if (sub.secret) headers["X-DXCRM-Signature"] = `sha256=${signPayload(sub.secret, body)}`;
52
+ try {
53
+ const res = await fetch(sub.url, {
54
+ method: "POST",
55
+ headers,
56
+ body
57
+ });
58
+ if (!res.ok) return {
59
+ ok: false,
60
+ error: `HTTP ${res.status}`
61
+ };
62
+ return { ok: true };
63
+ } catch (err) {
64
+ return {
65
+ ok: false,
66
+ error: err.message
67
+ };
68
+ }
69
+ }
70
+ /** Emit an event to all matching subscriptions; queue failures for replay. */
71
+ async function emitEvent(dataDir, event, payload) {
72
+ const matched = matchSubscriptions(loadWebhooks(dataDir), event);
73
+ if (matched.length === 0) return;
74
+ const failures = loadFailures(dataDir);
75
+ for (const sub of matched) {
76
+ const r = await deliver(sub, event, payload);
77
+ if (!r.ok) failures.push({
78
+ id: `whf_${createHash("sha256").update(`${sub.id}:${event}:${Date.now()}`).digest("hex").slice(0, 10)}`,
79
+ subscriptionId: sub.id,
80
+ url: sub.url,
81
+ ...sub.secret ? { secret: sub.secret } : {},
82
+ event,
83
+ payload,
84
+ attempts: 1,
85
+ lastError: r.error ?? "unknown",
86
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString()
87
+ });
88
+ }
89
+ if (failures.length > 0) writeJson(failuresPath(dataDir), "failures", failures);
90
+ }
91
+ //#endregion
92
+ export { emitEvent };
93
+
94
+ //# sourceMappingURL=webhooks-BO2UAnmn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks-BO2UAnmn.js","names":[],"sources":["../src/core/webhooks.ts"],"sourcesContent":["import { createHash, createHmac, randomBytes } from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\n\n/**\n * Outbound webhooks (event-driven architecture, N5-2). Subscriptions live in\n * .agentic/webhooks.json; failed deliveries are queued in\n * .agentic/webhook-failures.json (replay store) and re-attempted by\n * retryFailures (e.g. from the daemon) — backoff via periodic replay.\n */\nexport interface WebhookSubscription {\n id: string;\n url: string;\n events: string[];\n secret?: string;\n createdAt: string;\n}\n\nexport interface WebhookFailure {\n id: string;\n subscriptionId: string;\n url: string;\n secret?: string;\n event: string;\n payload: unknown;\n attempts: number;\n lastError: string;\n queuedAt: string;\n}\n\nfunction subsPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhooks.json\");\n}\nfunction failuresPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"webhook-failures.json\");\n}\n\nfunction readJson<T>(p: string, key: string): T[] {\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Record<string, T[]>;\n return Array.isArray(data[key]) ? data[key]! : [];\n } catch {\n return [];\n }\n}\nfunction writeJson<T>(p: string, key: string, items: T[]): void {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ [key]: items }, null, 2), \"utf-8\");\n}\n\nexport function loadWebhooks(dataDir: string): WebhookSubscription[] {\n return readJson<WebhookSubscription>(subsPath(dataDir), \"subscriptions\");\n}\n\nexport function addWebhook(\n dataDir: string,\n url: string,\n events: string[],\n secret?: string\n): WebhookSubscription {\n const sub: WebhookSubscription = {\n id: `wh_${randomBytes(5).toString(\"hex\")}`,\n url,\n events,\n ...(secret ? { secret } : {}),\n createdAt: new Date().toISOString(),\n };\n writeJson(subsPath(dataDir), \"subscriptions\", [...loadWebhooks(dataDir), sub]);\n return sub;\n}\n\nexport function removeWebhook(dataDir: string, id: string): boolean {\n const subs = loadWebhooks(dataDir);\n const next = subs.filter((s) => s.id !== id);\n if (next.length === subs.length) return false;\n writeJson(subsPath(dataDir), \"subscriptions\", next);\n return true;\n}\n\n/** A subscription matches an event by exact name, \"*\", or a \"prefix.*\" pattern. */\nexport function matchSubscriptions(\n subs: WebhookSubscription[],\n event: string\n): WebhookSubscription[] {\n return subs.filter((s) =>\n s.events.some((pat) => {\n if (pat === \"*\" || pat === event) return true;\n if (pat.endsWith(\".*\")) return event.startsWith(pat.slice(0, -1));\n return false;\n })\n );\n}\n\nexport function signPayload(secret: string, body: string): string {\n return createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n}\n\nexport function loadFailures(dataDir: string): WebhookFailure[] {\n return readJson<WebhookFailure>(failuresPath(dataDir), \"failures\");\n}\n\nasync function deliver(\n sub: WebhookSubscription,\n event: string,\n payload: unknown\n): Promise<{ ok: boolean; error?: string }> {\n const body = JSON.stringify({ event, payload, deliveredAt: new Date().toISOString() });\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-DXCRM-Event\": event,\n };\n if (sub.secret) headers[\"X-DXCRM-Signature\"] = `sha256=${signPayload(sub.secret, body)}`;\n try {\n const res = (await fetch(sub.url, { method: \"POST\", headers, body })) as {\n ok: boolean;\n status: number;\n };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };\n return { ok: true };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\n/** Emit an event to all matching subscriptions; queue failures for replay. */\nexport async function emitEvent(dataDir: string, event: string, payload: unknown): Promise<void> {\n const matched = matchSubscriptions(loadWebhooks(dataDir), event);\n if (matched.length === 0) return;\n const failures = loadFailures(dataDir);\n for (const sub of matched) {\n const r = await deliver(sub, event, payload);\n if (!r.ok) {\n failures.push({\n id: `whf_${createHash(\"sha256\").update(`${sub.id}:${event}:${Date.now()}`).digest(\"hex\").slice(0, 10)}`,\n subscriptionId: sub.id,\n url: sub.url,\n ...(sub.secret ? { secret: sub.secret } : {}),\n event,\n payload,\n attempts: 1,\n lastError: r.error ?? \"unknown\",\n queuedAt: new Date().toISOString(),\n });\n }\n }\n if (failures.length > 0) writeJson(failuresPath(dataDir), \"failures\", failures);\n}\n\n/** Re-attempt queued failures; remove on success, increment attempts on failure. */\nexport async function retryFailures(\n dataDir: string\n): Promise<{ retried: number; stillFailing: number }> {\n const failures = loadFailures(dataDir);\n const remaining: WebhookFailure[] = [];\n let retried = 0;\n for (const f of failures) {\n const sub: WebhookSubscription = {\n id: f.subscriptionId,\n url: f.url,\n events: [f.event],\n ...(f.secret ? { secret: f.secret } : {}),\n createdAt: f.queuedAt,\n };\n const r = await deliver(sub, f.event, f.payload);\n if (r.ok) retried++;\n else remaining.push({ ...f, attempts: f.attempts + 1, lastError: r.error ?? \"unknown\" });\n }\n writeJson(failuresPath(dataDir), \"failures\", remaining);\n return { retried, stillFailing: remaining.length };\n}\n"],"mappings":";;;;AA8BA,SAAS,SAAS,SAAyB;CACzC,OAAO,KAAK,KAAK,SAAS,YAAY,eAAe;AACvD;AACA,SAAS,aAAa,SAAyB;CAC7C,OAAO,KAAK,KAAK,SAAS,YAAY,uBAAuB;AAC/D;AAEA,SAAS,SAAY,GAAW,KAAkB;CAChD,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAC7D,OAAO,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,OAAQ,CAAC;CAClD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AACA,SAAS,UAAa,GAAW,KAAa,OAAkB;CAC9D,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,GAAG,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO;AACxE;AAEA,SAAgB,aAAa,SAAwC;CACnE,OAAO,SAA8B,SAAS,OAAO,GAAG,eAAe;AACzE;;AA4BA,SAAgB,mBACd,MACA,OACuB;CACvB,OAAO,KAAK,QAAQ,MAClB,EAAE,OAAO,MAAM,QAAQ;EACrB,IAAI,QAAQ,OAAO,QAAQ,OAAO,OAAO;EACzC,IAAI,IAAI,SAAS,IAAI,GAAG,OAAO,MAAM,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC;EAChE,OAAO;CACT,CAAC,CACH;AACF;AAEA,SAAgB,YAAY,QAAgB,MAAsB;CAChE,OAAO,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAC/D;AAEA,SAAgB,aAAa,SAAmC;CAC9D,OAAO,SAAyB,aAAa,OAAO,GAAG,UAAU;AACnE;AAEA,eAAe,QACb,KACA,OACA,SAC0C;CAC1C,MAAM,OAAO,KAAK,UAAU;EAAE;EAAO;EAAS,8BAAa,IAAI,KAAK,GAAE,YAAY;CAAE,CAAC;CACrF,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB;CACnB;CACA,IAAI,IAAI,QAAQ,QAAQ,uBAAuB,UAAU,YAAY,IAAI,QAAQ,IAAI;CACrF,IAAI;EACF,MAAM,MAAO,MAAM,MAAM,IAAI,KAAK;GAAE,QAAQ;GAAQ;GAAS;EAAK,CAAC;EAInE,IAAI,CAAC,IAAI,IAAI,OAAO;GAAE,IAAI;GAAO,OAAO,QAAQ,IAAI;EAAS;EAC7D,OAAO,EAAE,IAAI,KAAK;CACpB,SAAS,KAAK;EACZ,OAAO;GAAE,IAAI;GAAO,OAAQ,IAAc;EAAQ;CACpD;AACF;;AAGA,eAAsB,UAAU,SAAiB,OAAe,SAAiC;CAC/F,MAAM,UAAU,mBAAmB,aAAa,OAAO,GAAG,KAAK;CAC/D,IAAI,QAAQ,WAAW,GAAG;CAC1B,MAAM,WAAW,aAAa,OAAO;CACrC,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,IAAI,MAAM,QAAQ,KAAK,OAAO,OAAO;EAC3C,IAAI,CAAC,EAAE,IACL,SAAS,KAAK;GACZ,IAAI,OAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,IAAI,GAAG,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;GACpG,gBAAgB,IAAI;GACpB,KAAK,IAAI;GACT,GAAI,IAAI,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;GAC3C;GACA;GACA,UAAU;GACV,WAAW,EAAE,SAAS;GACtB,2BAAU,IAAI,KAAK,GAAE,YAAY;EACnC,CAAC;CAEL;CACA,IAAI,SAAS,SAAS,GAAG,UAAU,aAAa,OAAO,GAAG,YAAY,QAAQ;AAChF"}