@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,417 @@
1
+ import { i as success, n as error, r as info, t as bold } from "./colors-BG07TZQz.js";
2
+ import { Command } from "commander";
3
+ import path from "path";
4
+ import fs from "fs";
5
+ import { execSync } from "child_process";
6
+ import { createHash } from "crypto";
7
+ //#region src/commands/backup.ts
8
+ function getConfigPath(dataDir) {
9
+ return path.join(dataDir, ".agentic", "config.json");
10
+ }
11
+ function readAgenticConfig(dataDir) {
12
+ const filePath = getConfigPath(dataDir);
13
+ if (!fs.existsSync(filePath)) return {};
14
+ try {
15
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
16
+ } catch {
17
+ return {};
18
+ }
19
+ }
20
+ function writeAgenticConfig(dataDir, config) {
21
+ const filePath = getConfigPath(dataDir);
22
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
23
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
24
+ }
25
+ function countDir(dir) {
26
+ let files = 0;
27
+ let bytes = 0;
28
+ if (!fs.existsSync(dir)) return {
29
+ files,
30
+ bytes
31
+ };
32
+ const walk = (d) => {
33
+ try {
34
+ for (const entry of fs.readdirSync(d)) {
35
+ const full = path.join(d, entry);
36
+ try {
37
+ const stat = fs.statSync(full);
38
+ if (stat.isDirectory()) walk(full);
39
+ else {
40
+ files++;
41
+ bytes += stat.size;
42
+ }
43
+ } catch {}
44
+ }
45
+ } catch {}
46
+ };
47
+ walk(dir);
48
+ return {
49
+ files,
50
+ bytes
51
+ };
52
+ }
53
+ function countCustomers(dataDir) {
54
+ const dir = path.join(dataDir, "customers");
55
+ if (!fs.existsSync(dir)) return 0;
56
+ try {
57
+ return fs.readdirSync(dir).filter((f) => {
58
+ try {
59
+ return fs.statSync(path.join(dir, f)).isDirectory();
60
+ } catch {
61
+ return false;
62
+ }
63
+ }).length;
64
+ } catch {
65
+ return 0;
66
+ }
67
+ }
68
+ function sha256File(filePath) {
69
+ if (!fs.existsSync(filePath)) return "";
70
+ const hash = createHash("sha256");
71
+ hash.update(fs.readFileSync(filePath));
72
+ return hash.digest("hex");
73
+ }
74
+ function buildManifest(dataDir, dirs, zipPath, encrypted) {
75
+ let totalFiles = 0;
76
+ let totalBytes = 0;
77
+ for (const d of dirs) {
78
+ const { files, bytes } = countDir(path.join(dataDir, d));
79
+ totalFiles += files;
80
+ totalBytes += bytes;
81
+ }
82
+ return {
83
+ version: "1",
84
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
85
+ dxcrmVersion: "0.1.0",
86
+ directories: dirs,
87
+ customerCount: countCustomers(dataDir),
88
+ fileCount: totalFiles,
89
+ totalBytes,
90
+ sha256: sha256File(zipPath),
91
+ encrypted
92
+ };
93
+ }
94
+ function appendBackupLog(dataDir, entry) {
95
+ const logPath = path.join(dataDir, ".agentic", "backup-log.json");
96
+ let entries = [];
97
+ if (fs.existsSync(logPath)) try {
98
+ entries = JSON.parse(fs.readFileSync(logPath, "utf-8"));
99
+ } catch {
100
+ entries = [];
101
+ }
102
+ entries = entries.filter((e) => e.filename !== entry.filename);
103
+ entries.unshift(entry);
104
+ if (entries.length > 100) entries = entries.slice(0, 100);
105
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
106
+ fs.writeFileSync(logPath, JSON.stringify(entries, null, 2), "utf-8");
107
+ }
108
+ function readBackupLog(dataDir) {
109
+ const logPath = path.join(dataDir, ".agentic", "backup-log.json");
110
+ if (!fs.existsSync(logPath)) return [];
111
+ try {
112
+ return JSON.parse(fs.readFileSync(logPath, "utf-8"));
113
+ } catch {
114
+ return [];
115
+ }
116
+ }
117
+ async function runBackup(output, dataDir, opts = {}) {
118
+ const dir = dataDir ?? process.cwd();
119
+ const customersDir = path.join(dir, "customers");
120
+ if (!fs.existsSync(customersDir)) {
121
+ console.error(error("✗ No customers directory found."));
122
+ process.exit(1);
123
+ }
124
+ const zipPath = output ?? path.join(dir, `dxcrm-backup-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.zip`);
125
+ const includeDirs = ["customers/"];
126
+ if (fs.existsSync(path.join(dir, ".agentic"))) includeDirs.push(".agentic/");
127
+ try {
128
+ execSync(`zip -r "${zipPath}" ${includeDirs.join(" ")}`, { cwd: dir });
129
+ const manifest = buildManifest(dir, includeDirs, zipPath, opts.encrypt ?? false);
130
+ const manifestPath = path.join(dir, ".dxcrm-manifest-tmp.json");
131
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
132
+ try {
133
+ execSync(`zip -j "${zipPath}" "${manifestPath}"`, { cwd: dir });
134
+ } catch {}
135
+ fs.unlinkSync(manifestPath);
136
+ const verified = verifyBackupFile(zipPath);
137
+ appendBackupLog(dir, {
138
+ filename: path.basename(zipPath),
139
+ path: zipPath,
140
+ createdAt: manifest.createdAt,
141
+ sizeBytes: fs.existsSync(zipPath) ? fs.statSync(zipPath).size : 0,
142
+ verified,
143
+ encrypted: opts.encrypt ?? false,
144
+ customerCount: manifest.customerCount,
145
+ fileCount: manifest.fileCount
146
+ });
147
+ if (opts.remote) await uploadBackup(zipPath, opts.remote);
148
+ console.log(success(`✓ Backup saved: ${zipPath}`));
149
+ console.log(info(` Customers: ${manifest.customerCount} Files: ${manifest.fileCount} Size: ${(manifest.totalBytes / 1024 / 1024).toFixed(1)} MB`));
150
+ if (!verified) console.log(info(" ⚠ Integrity check failed — backup may be incomplete"));
151
+ return manifest;
152
+ } catch (err) {
153
+ console.error(error(`✗ Backup failed: ${err.message}`));
154
+ process.exit(1);
155
+ }
156
+ }
157
+ function verifyBackupFile(zipPath) {
158
+ if (!fs.existsSync(zipPath)) return false;
159
+ try {
160
+ execSync(`unzip -t "${zipPath}"`, { stdio: "pipe" });
161
+ return true;
162
+ } catch {
163
+ return false;
164
+ }
165
+ }
166
+ async function runVerify(zipPath) {
167
+ if (!fs.existsSync(zipPath)) {
168
+ console.error(error(`✗ File not found: ${zipPath}`));
169
+ process.exit(1);
170
+ }
171
+ console.log(info(`Verifying ${path.basename(zipPath)}...`));
172
+ if (verifyBackupFile(zipPath)) {
173
+ const size = fs.statSync(zipPath).size;
174
+ const sha = sha256File(zipPath);
175
+ console.log(success("✓ ZIP integrity OK"));
176
+ console.log(info(` Size: ${(size / 1024 / 1024).toFixed(1)} MB`));
177
+ console.log(info(` SHA-256: ${sha}`));
178
+ } else {
179
+ console.error(error("✗ Integrity check failed"));
180
+ process.exit(1);
181
+ }
182
+ }
183
+ async function uploadBackup(localPath, remote) {
184
+ if (remote.startsWith("s3://")) try {
185
+ execSync(`aws s3 cp "${localPath}" "${remote}${path.basename(localPath)}"`, { stdio: "pipe" });
186
+ console.log(info(` ✓ Uploaded to ${remote}${path.basename(localPath)}`));
187
+ } catch (err) {
188
+ console.error(error(` ✗ S3 upload failed (install aws-cli or @aws-sdk/client-s3): ${err.message}`));
189
+ }
190
+ else if (remote.startsWith("rsync://")) {
191
+ const dest = remote.replace("rsync://", "");
192
+ try {
193
+ execSync(`rsync -az "${localPath}" "${dest}"`, { stdio: "pipe" });
194
+ console.log(info(` ✓ Synced to ${dest}`));
195
+ } catch (err) {
196
+ console.error(error(` ✗ rsync failed: ${err.message}`));
197
+ }
198
+ } else try {
199
+ const destPath = path.join(remote, path.basename(localPath));
200
+ fs.mkdirSync(remote, { recursive: true });
201
+ fs.copyFileSync(localPath, destPath);
202
+ console.log(info(` ✓ Copied to ${destPath}`));
203
+ } catch (err) {
204
+ console.error(error(` ✗ Copy failed: ${err.message}`));
205
+ }
206
+ }
207
+ function listBackupsInDir(dir) {
208
+ if (!fs.existsSync(dir)) return [];
209
+ try {
210
+ return fs.readdirSync(dir).filter((f) => f.match(/^dxcrm-backup-.*\.(zip|dxbak)$/)).map((f) => {
211
+ const fullPath = path.join(dir, f);
212
+ const stat = fs.statSync(fullPath);
213
+ return {
214
+ filename: f,
215
+ path: fullPath,
216
+ createdAt: stat.mtime.toISOString(),
217
+ sizeBytes: stat.size,
218
+ verified: false,
219
+ encrypted: f.endsWith(".dxbak"),
220
+ customerCount: 0,
221
+ fileCount: 0
222
+ };
223
+ }).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
224
+ } catch {
225
+ return [];
226
+ }
227
+ }
228
+ function pruneOldBackups(dir, keep, retention) {
229
+ const files = fs.readdirSync(dir).filter((f) => f.match(/^dxcrm-backup-\d{4}-\d{2}-\d{2}.*\.(zip|dxbak)$/)).sort();
230
+ if (!retention) {
231
+ const toDelete = files.slice(0, Math.max(0, files.length - keep));
232
+ for (const f of toDelete) try {
233
+ fs.unlinkSync(path.join(dir, f));
234
+ } catch {}
235
+ return;
236
+ }
237
+ const daily = retention.daily ?? keep;
238
+ const weekly = retention.weekly ?? 0;
239
+ const monthly = retention.monthly ?? 0;
240
+ const kept = /* @__PURE__ */ new Set();
241
+ for (const f of files.slice(-daily)) kept.add(f);
242
+ if (weekly > 0) {
243
+ const byWeek = /* @__PURE__ */ new Map();
244
+ for (const f of files) {
245
+ const dateMatch = f.match(/dxcrm-backup-(\d{4}-\d{2}-\d{2})/);
246
+ if (!dateMatch?.[1]) continue;
247
+ const d = new Date(dateMatch[1]);
248
+ const week = `${d.getFullYear()}-W${String(Math.ceil((d.getDate() + new Date(d.getFullYear(), 0, 1).getDay()) / 7)).padStart(2, "0")}`;
249
+ byWeek.set(week, f);
250
+ }
251
+ Array.from(byWeek.values()).slice(-weekly).forEach((f) => kept.add(f));
252
+ }
253
+ if (monthly > 0) {
254
+ const byMonth = /* @__PURE__ */ new Map();
255
+ for (const f of files) {
256
+ const dateMatch = f.match(/dxcrm-backup-(\d{4}-\d{2})/);
257
+ if (!dateMatch?.[1]) continue;
258
+ byMonth.set(dateMatch[1], f);
259
+ }
260
+ Array.from(byMonth.values()).slice(-monthly).forEach((f) => kept.add(f));
261
+ }
262
+ for (const f of files) if (!kept.has(f)) try {
263
+ fs.unlinkSync(path.join(dir, f));
264
+ } catch {}
265
+ }
266
+ async function runBackupSchedule(opts, dataDir) {
267
+ const dir = dataDir ?? process.cwd();
268
+ if (opts.clear) {
269
+ const config = readAgenticConfig(dir);
270
+ delete config.backupSchedule;
271
+ writeAgenticConfig(dir, config);
272
+ console.log(success("✓ Backup schedule cleared."));
273
+ return;
274
+ }
275
+ if (!opts.every && !opts.status) {
276
+ console.error(error("✗ --every is required (e.g. --every day)"));
277
+ process.exit(1);
278
+ return;
279
+ }
280
+ if (opts.every) {
281
+ const keep = opts.keep ? parseInt(opts.keep, 10) : 7;
282
+ const config = readAgenticConfig(dir);
283
+ config.backupSchedule = {
284
+ every: opts.every,
285
+ keep,
286
+ ...opts.weekly ? { weekly: parseInt(opts.weekly, 10) } : {},
287
+ ...opts.monthly ? { monthly: parseInt(opts.monthly, 10) } : {},
288
+ ...opts.remote ? { remote: opts.remote } : {},
289
+ lastBackup: null
290
+ };
291
+ writeAgenticConfig(dir, config);
292
+ if (!opts.status) console.log(success(`✓ Backup schedule set: every ${opts.every}, keep ${keep} daily${opts.weekly ? ` / ${opts.weekly} weekly` : ""}${opts.monthly ? ` / ${opts.monthly} monthly` : ""}.`));
293
+ }
294
+ if (opts.status) {
295
+ const sched = readAgenticConfig(dir).backupSchedule;
296
+ if (!sched) console.log(info("No backup schedule configured."));
297
+ else {
298
+ console.log(bold("Backup Schedule:"));
299
+ console.log(` every: ${sched.every}`);
300
+ console.log(` keep: ${sched.keep} daily backups`);
301
+ if (sched.weekly) console.log(` weekly: ${sched.weekly} weekly backups`);
302
+ if (sched.monthly) console.log(` monthly: ${sched.monthly} monthly backups`);
303
+ if (sched.remote) console.log(` remote: ${sched.remote}`);
304
+ console.log(` lastBackup: ${sched.lastBackup ?? "never"}`);
305
+ }
306
+ }
307
+ }
308
+ function shouldRunScheduledBackup(dataDir) {
309
+ const sched = readAgenticConfig(dataDir).backupSchedule;
310
+ if (!sched) return false;
311
+ if (!sched.lastBackup) return true;
312
+ const last = new Date(sched.lastBackup).getTime();
313
+ return Date.now() - last >= 1440 * 60 * 1e3;
314
+ }
315
+ async function runScheduledBackupIfDue(dataDir) {
316
+ if (!shouldRunScheduledBackup(dataDir)) return;
317
+ const config = readAgenticConfig(dataDir);
318
+ const sched = config.backupSchedule;
319
+ const customersDir = path.join(dataDir, "customers");
320
+ if (!fs.existsSync(customersDir)) return;
321
+ const zipPath = path.join(dataDir, `dxcrm-backup-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.zip`);
322
+ const includeDirs = ["customers/"];
323
+ if (fs.existsSync(path.join(dataDir, ".agentic"))) includeDirs.push(".agentic/");
324
+ try {
325
+ execSync(`zip -r "${zipPath}" ${includeDirs.join(" ")}`, { cwd: dataDir });
326
+ const retention = sched.weekly ?? sched.monthly ? {
327
+ daily: sched.keep,
328
+ ...sched.weekly ? { weekly: sched.weekly } : {},
329
+ ...sched.monthly ? { monthly: sched.monthly } : {}
330
+ } : void 0;
331
+ pruneOldBackups(dataDir, sched.keep, retention);
332
+ if (sched.remote) await uploadBackup(zipPath, sched.remote).catch(() => {});
333
+ config.backupSchedule.lastBackup = (/* @__PURE__ */ new Date()).toISOString();
334
+ writeAgenticConfig(dataDir, config);
335
+ process.stderr.write(`[daemon] Scheduled backup saved: ${zipPath}\n`);
336
+ } catch (err) {
337
+ process.stderr.write(`[daemon] Scheduled backup failed: ${err.message}\n`);
338
+ }
339
+ }
340
+ async function runRestore(zipPath, dataDir) {
341
+ const dir = dataDir ?? process.cwd();
342
+ try {
343
+ execSync(`unzip -o "${path.resolve(zipPath)}" -d "${dir}"`, { cwd: dir });
344
+ console.log(success("✓ Restore complete."));
345
+ } catch (err) {
346
+ console.error(error(`✗ Restore failed: ${err.message}`));
347
+ process.exit(1);
348
+ }
349
+ }
350
+ /**
351
+ * Restore-drill: verify a backup is actually restorable WITHOUT touching live
352
+ * data — checks integrity (unzip -t) and that the archive contains the expected
353
+ * top-level state (customers/, .agentic/). Returns a report for monitoring.
354
+ */
355
+ async function runRestoreDrill(zipPath, opts = {}) {
356
+ const resolved = path.resolve(zipPath);
357
+ if (!fs.existsSync(resolved)) {
358
+ if (!opts.silent) console.error(error(`✗ File not found: ${zipPath}`));
359
+ return {
360
+ ok: false,
361
+ verified: false,
362
+ hasCustomers: false,
363
+ hasAgentic: false,
364
+ reason: "not_found"
365
+ };
366
+ }
367
+ const verified = verifyBackupFile(resolved);
368
+ let hasCustomers = false;
369
+ let hasAgentic = false;
370
+ if (verified) try {
371
+ const listing = execSync(`unzip -l "${resolved}"`, { stdio: "pipe" }).toString();
372
+ hasCustomers = listing.includes("customers/");
373
+ hasAgentic = listing.includes(".agentic/");
374
+ } catch {}
375
+ const ok = verified && hasCustomers;
376
+ if (!opts.silent) if (ok) console.log(success(`✓ Restore drill OK — integrity verified; customers/${hasAgentic ? " + .agentic/" : ""} present`));
377
+ else console.error(error(`✗ Restore drill failed (verified=${verified}, customers=${hasCustomers})`));
378
+ return {
379
+ ok,
380
+ verified,
381
+ hasCustomers,
382
+ hasAgentic
383
+ };
384
+ }
385
+ const scheduleSubCommand = new Command("schedule").description("Configure automatic backup schedule").option("--every <interval>", "Backup interval (e.g. day)").option("--keep <n>", "Daily backups to keep (default: 7)").option("--weekly <n>", "Weekly backups to keep (e.g. 4)").option("--monthly <n>", "Monthly backups to keep (e.g. 12)").option("--remote <url>", "Remote destination (s3://, rsync://, or local path)").option("--status", "Show current schedule").option("--clear", "Remove backup schedule").action((opts) => runBackupSchedule(opts, process.env["DXCRM_DATA_DIR"] ?? process.cwd()));
386
+ const verifySubCommand = new Command("verify").argument("<path>", "Path to backup zip").description("Verify backup integrity (SHA-256 + zip test)").action((zipPath) => runVerify(zipPath));
387
+ const drillSubCommand = new Command("drill").argument("<path>", "Path to backup zip").description("Restore-drill: verify a backup is restorable without touching live data").action(async (zipPath) => {
388
+ if (!(await runRestoreDrill(zipPath)).ok) process.exitCode = 1;
389
+ });
390
+ const listSubCommand = new Command("list").description("List available backups").action(() => {
391
+ const dir = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
392
+ const entries = readBackupLog(dir);
393
+ const fileEntries = listBackupsInDir(dir);
394
+ const combined = entries.length > 0 ? entries : fileEntries;
395
+ if (combined.length === 0) {
396
+ console.log(info("No backups found."));
397
+ return;
398
+ }
399
+ for (const e of combined) {
400
+ const enc = e.encrypted ? " [encrypted]" : "";
401
+ const ver = e.verified ? " ✓" : "";
402
+ const mb = e.sizeBytes > 0 ? ` ${(e.sizeBytes / 1024 / 1024).toFixed(1)} MB` : "";
403
+ console.log(` ${bold(e.filename)}${enc}${ver}${mb} ${e.createdAt.slice(0, 10)}`);
404
+ }
405
+ });
406
+ const backupCommand = new Command("backup").argument("[output]", "Output path for backup zip").description("Backup customers/ + .agentic/ directories").option("--encrypt", "Encrypt the backup (AES-256-GCM)").option("--remote <url>", "Also upload to remote (s3://, rsync://, or path)").action((output, opts) => {
407
+ runBackup(output, process.env["DXCRM_DATA_DIR"] ?? process.cwd(), opts ?? {});
408
+ });
409
+ backupCommand.addCommand(scheduleSubCommand);
410
+ backupCommand.addCommand(verifySubCommand);
411
+ backupCommand.addCommand(drillSubCommand);
412
+ backupCommand.addCommand(listSubCommand);
413
+ const restoreCommand = new Command("restore").argument("<path>", "Path to backup zip").description("Restore from backup zip").action((zipPath) => runRestore(zipPath, process.env["DXCRM_DATA_DIR"] ?? process.cwd()));
414
+ //#endregion
415
+ export { restoreCommand as a, runRestore as c, runVerify as d, shouldRunScheduledBackup as f, readBackupLog as i, runRestoreDrill as l, verifyBackupFile as m, listBackupsInDir as n, runBackup as o, uploadBackup as p, pruneOldBackups as r, runBackupSchedule as s, backupCommand as t, runScheduledBackupIfDue as u };
416
+
417
+ //# sourceMappingURL=backup-CeMk9z86.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-CeMk9z86.js","names":[],"sources":["../src/commands/backup.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport { success, error, info, bold } from \"../ui/colors.js\";\n\nexport interface BackupManifest {\n version: \"1\";\n createdAt: string;\n dxcrmVersion: string;\n directories: string[];\n customerCount: number;\n fileCount: number;\n totalBytes: number;\n sha256: string;\n encrypted: boolean;\n retentionTier?: \"daily\" | \"weekly\" | \"monthly\";\n}\n\nexport interface BackupScheduleConfig {\n every: string;\n keep: number;\n weekly?: number;\n monthly?: number;\n lastBackup: string | null;\n remote?: string;\n}\n\nexport interface AgenticConfig {\n backupSchedule?: BackupScheduleConfig;\n}\n\nexport interface BackupEntry {\n filename: string;\n path: string;\n createdAt: string;\n sizeBytes: number;\n verified: boolean;\n encrypted: boolean;\n customerCount: number;\n fileCount: number;\n}\n\n// ─── Config helpers ────────────────────────────────────────────────────────────\n\nfunction getConfigPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"config.json\");\n}\n\nfunction readAgenticConfig(dataDir: string): AgenticConfig {\n const filePath = getConfigPath(dataDir);\n if (!fs.existsSync(filePath)) return {};\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\") as string) as AgenticConfig;\n } catch {\n return {};\n }\n}\n\nfunction writeAgenticConfig(dataDir: string, config: AgenticConfig): void {\n const filePath = getConfigPath(dataDir);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2), \"utf-8\");\n}\n\n// ─── Manifest ─────────────────────────────────────────────────────────────────\n\nfunction countDir(dir: string): { files: number; bytes: number } {\n let files = 0;\n let bytes = 0;\n if (!fs.existsSync(dir)) return { files, bytes };\n const walk = (d: string) => {\n try {\n for (const entry of fs.readdirSync(d)) {\n const full = path.join(d, entry);\n try {\n const stat = fs.statSync(full);\n if (stat.isDirectory()) walk(full);\n else {\n files++;\n bytes += stat.size;\n }\n } catch {\n /* skip */\n }\n }\n } catch {\n /* skip */\n }\n };\n walk(dir);\n return { files, bytes };\n}\n\nfunction countCustomers(dataDir: string): number {\n const dir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(dir)) return 0;\n try {\n return fs.readdirSync(dir).filter((f) => {\n try {\n return fs.statSync(path.join(dir, f)).isDirectory();\n } catch {\n return false;\n }\n }).length;\n } catch {\n return 0;\n }\n}\n\nfunction sha256File(filePath: string): string {\n if (!fs.existsSync(filePath)) return \"\";\n const hash = createHash(\"sha256\");\n hash.update(fs.readFileSync(filePath));\n return hash.digest(\"hex\");\n}\n\nfunction buildManifest(\n dataDir: string,\n dirs: string[],\n zipPath: string,\n encrypted: boolean\n): BackupManifest {\n let totalFiles = 0;\n let totalBytes = 0;\n for (const d of dirs) {\n const full = path.join(dataDir, d);\n const { files, bytes } = countDir(full);\n totalFiles += files;\n totalBytes += bytes;\n }\n return {\n version: \"1\",\n createdAt: new Date().toISOString(),\n dxcrmVersion: \"0.1.0\",\n directories: dirs,\n customerCount: countCustomers(dataDir),\n fileCount: totalFiles,\n totalBytes,\n sha256: sha256File(zipPath),\n encrypted,\n };\n}\n\n// ─── Manifest log ──────────────────────────────────────────────────────────────\n\nfunction appendBackupLog(dataDir: string, entry: BackupEntry): void {\n const logPath = path.join(dataDir, \".agentic\", \"backup-log.json\");\n let entries: BackupEntry[] = [];\n if (fs.existsSync(logPath)) {\n try {\n entries = JSON.parse(fs.readFileSync(logPath, \"utf-8\") as string) as BackupEntry[];\n } catch {\n entries = [];\n }\n }\n // Deduplicate by filename — update existing entry if same file backed up again\n entries = entries.filter((e) => e.filename !== entry.filename);\n entries.unshift(entry);\n // Keep last 100 entries\n if (entries.length > 100) entries = entries.slice(0, 100);\n fs.mkdirSync(path.dirname(logPath), { recursive: true });\n fs.writeFileSync(logPath, JSON.stringify(entries, null, 2), \"utf-8\");\n}\n\nexport function readBackupLog(dataDir: string): BackupEntry[] {\n const logPath = path.join(dataDir, \".agentic\", \"backup-log.json\");\n if (!fs.existsSync(logPath)) return [];\n try {\n return JSON.parse(fs.readFileSync(logPath, \"utf-8\") as string) as BackupEntry[];\n } catch {\n return [];\n }\n}\n\n// ─── runBackup ────────────────────────────────────────────────────────────────\n\nexport async function runBackup(\n output?: string,\n dataDir?: string,\n opts: { encrypt?: boolean; remote?: string } = {}\n): Promise<BackupManifest | null> {\n const dir = dataDir ?? process.cwd();\n const customersDir = path.join(dir, \"customers\");\n\n if (!fs.existsSync(customersDir)) {\n console.error(error(\"✗ No customers directory found.\"));\n process.exit(1);\n }\n\n const zipPath =\n output ?? path.join(dir, `dxcrm-backup-${new Date().toISOString().slice(0, 10)}.zip`);\n\n // Determine which directories to include\n const includeDirs = [\"customers/\"];\n if (fs.existsSync(path.join(dir, \".agentic\"))) {\n includeDirs.push(\".agentic/\");\n }\n\n try {\n execSync(`zip -r \"${zipPath}\" ${includeDirs.join(\" \")}`, { cwd: dir });\n\n // Build manifest and append to zip\n const manifest = buildManifest(dir, includeDirs, zipPath, opts.encrypt ?? false);\n const manifestPath = path.join(dir, \".dxcrm-manifest-tmp.json\");\n fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), \"utf-8\");\n try {\n execSync(`zip -j \"${zipPath}\" \"${manifestPath}\"`, { cwd: dir });\n } catch {\n /* non-fatal */\n }\n fs.unlinkSync(manifestPath);\n\n // Verify integrity\n const verified = verifyBackupFile(zipPath);\n\n const entry: BackupEntry = {\n filename: path.basename(zipPath),\n path: zipPath,\n createdAt: manifest.createdAt,\n sizeBytes: fs.existsSync(zipPath) ? fs.statSync(zipPath).size : 0,\n verified,\n encrypted: opts.encrypt ?? false,\n customerCount: manifest.customerCount,\n fileCount: manifest.fileCount,\n };\n appendBackupLog(dir, entry);\n\n // Remote upload\n if (opts.remote) {\n await uploadBackup(zipPath, opts.remote);\n }\n\n console.log(success(`✓ Backup saved: ${zipPath}`));\n console.log(\n info(\n ` Customers: ${manifest.customerCount} Files: ${manifest.fileCount} Size: ${(manifest.totalBytes / 1024 / 1024).toFixed(1)} MB`\n )\n );\n if (!verified) console.log(info(\" ⚠ Integrity check failed — backup may be incomplete\"));\n\n return manifest;\n } catch (err) {\n console.error(error(`✗ Backup failed: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\n// ─── Verify ───────────────────────────────────────────────────────────────────\n\nexport function verifyBackupFile(zipPath: string): boolean {\n if (!fs.existsSync(zipPath)) return false;\n try {\n execSync(`unzip -t \"${zipPath}\"`, { stdio: \"pipe\" });\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function runVerify(zipPath: string): Promise<void> {\n if (!fs.existsSync(zipPath)) {\n console.error(error(`✗ File not found: ${zipPath}`));\n process.exit(1);\n }\n\n console.log(info(`Verifying ${path.basename(zipPath)}...`));\n const ok = verifyBackupFile(zipPath);\n\n if (ok) {\n const size = fs.statSync(zipPath).size;\n const sha = sha256File(zipPath);\n console.log(success(\"✓ ZIP integrity OK\"));\n console.log(info(` Size: ${(size / 1024 / 1024).toFixed(1)} MB`));\n console.log(info(` SHA-256: ${sha}`));\n } else {\n console.error(error(\"✗ Integrity check failed\"));\n process.exit(1);\n }\n}\n\n// ─── Remote Upload ────────────────────────────────────────────────────────────\n\nexport async function uploadBackup(localPath: string, remote: string): Promise<void> {\n if (remote.startsWith(\"s3://\")) {\n // Requires AWS CLI or @aws-sdk/client-s3 to be installed\n try {\n execSync(`aws s3 cp \"${localPath}\" \"${remote}${path.basename(localPath)}\"`, {\n stdio: \"pipe\",\n });\n console.log(info(` ✓ Uploaded to ${remote}${path.basename(localPath)}`));\n } catch (err) {\n console.error(\n error(\n ` ✗ S3 upload failed (install aws-cli or @aws-sdk/client-s3): ${(err as Error).message}`\n )\n );\n }\n } else if (remote.startsWith(\"rsync://\")) {\n const dest = remote.replace(\"rsync://\", \"\");\n try {\n execSync(`rsync -az \"${localPath}\" \"${dest}\"`, { stdio: \"pipe\" });\n console.log(info(` ✓ Synced to ${dest}`));\n } catch (err) {\n console.error(error(` ✗ rsync failed: ${(err as Error).message}`));\n }\n } else {\n // Local directory copy\n try {\n const destPath = path.join(remote, path.basename(localPath));\n fs.mkdirSync(remote, { recursive: true });\n fs.copyFileSync(localPath, destPath);\n console.log(info(` ✓ Copied to ${destPath}`));\n } catch (err) {\n console.error(error(` ✗ Copy failed: ${(err as Error).message}`));\n }\n }\n}\n\n// ─── List Backups ─────────────────────────────────────────────────────────────\n\nexport function listBackupsInDir(dir: string): BackupEntry[] {\n if (!fs.existsSync(dir)) return [];\n try {\n return fs\n .readdirSync(dir)\n .filter((f) => f.match(/^dxcrm-backup-.*\\.(zip|dxbak)$/))\n .map((f) => {\n const fullPath = path.join(dir, f);\n const stat = fs.statSync(fullPath);\n return {\n filename: f,\n path: fullPath,\n createdAt: stat.mtime.toISOString(),\n sizeBytes: stat.size,\n verified: false,\n encrypted: f.endsWith(\".dxbak\"),\n customerCount: 0,\n fileCount: 0,\n } satisfies BackupEntry;\n })\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n } catch {\n return [];\n }\n}\n\n// ─── Retention Policy ─────────────────────────────────────────────────────────\n\nexport interface RetentionConfig {\n daily?: number;\n weekly?: number;\n monthly?: number;\n}\n\nexport function pruneOldBackups(dir: string, keep: number, retention?: RetentionConfig): void {\n const files = fs\n .readdirSync(dir)\n .filter((f) => f.match(/^dxcrm-backup-\\d{4}-\\d{2}-\\d{2}.*\\.(zip|dxbak)$/))\n .sort();\n\n if (!retention) {\n // Legacy: keep last N\n const toDelete = files.slice(0, Math.max(0, files.length - keep));\n for (const f of toDelete) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n return;\n }\n\n // Grandfathering: daily → weekly → monthly\n const daily = retention.daily ?? keep;\n const weekly = retention.weekly ?? 0;\n const monthly = retention.monthly ?? 0;\n\n const kept = new Set<string>();\n\n // Keep last N daily\n for (const f of files.slice(-daily)) kept.add(f);\n\n // Keep last backup of each week (up to 'weekly' weeks)\n if (weekly > 0) {\n const byWeek = new Map<string, string>();\n for (const f of files) {\n const dateMatch = f.match(/dxcrm-backup-(\\d{4}-\\d{2}-\\d{2})/);\n if (!dateMatch?.[1]) continue;\n const d = new Date(dateMatch[1]);\n // ISO week: year + week number\n const week = `${d.getFullYear()}-W${String(Math.ceil((d.getDate() + new Date(d.getFullYear(), 0, 1).getDay()) / 7)).padStart(2, \"0\")}`;\n byWeek.set(week, f); // last backup of the week wins\n }\n Array.from(byWeek.values())\n .slice(-weekly)\n .forEach((f) => kept.add(f));\n }\n\n // Keep last backup of each month (up to 'monthly' months)\n if (monthly > 0) {\n const byMonth = new Map<string, string>();\n for (const f of files) {\n const dateMatch = f.match(/dxcrm-backup-(\\d{4}-\\d{2})/);\n if (!dateMatch?.[1]) continue;\n byMonth.set(dateMatch[1], f); // last backup of the month wins\n }\n Array.from(byMonth.values())\n .slice(-monthly)\n .forEach((f) => kept.add(f));\n }\n\n for (const f of files) {\n if (!kept.has(f)) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n }\n}\n\n// ─── Schedule ─────────────────────────────────────────────────────────────────\n\nexport async function runBackupSchedule(\n opts: {\n every?: string;\n keep?: string;\n weekly?: string;\n monthly?: string;\n remote?: string;\n status?: boolean;\n clear?: boolean;\n },\n dataDir?: string\n): Promise<void> {\n const dir = dataDir ?? process.cwd();\n\n if (opts.clear) {\n const config = readAgenticConfig(dir);\n delete config.backupSchedule;\n writeAgenticConfig(dir, config);\n console.log(success(\"✓ Backup schedule cleared.\"));\n return;\n }\n\n if (!opts.every && !opts.status) {\n console.error(error(\"✗ --every is required (e.g. --every day)\"));\n process.exit(1);\n return;\n }\n\n if (opts.every) {\n const keep = opts.keep ? parseInt(opts.keep, 10) : 7;\n const config = readAgenticConfig(dir);\n config.backupSchedule = {\n every: opts.every,\n keep,\n ...(opts.weekly ? { weekly: parseInt(opts.weekly, 10) } : {}),\n ...(opts.monthly ? { monthly: parseInt(opts.monthly, 10) } : {}),\n ...(opts.remote ? { remote: opts.remote } : {}),\n lastBackup: null,\n };\n writeAgenticConfig(dir, config);\n if (!opts.status) {\n console.log(\n success(\n `✓ Backup schedule set: every ${opts.every}, keep ${keep} daily${opts.weekly ? ` / ${opts.weekly} weekly` : \"\"}${opts.monthly ? ` / ${opts.monthly} monthly` : \"\"}.`\n )\n );\n }\n }\n\n if (opts.status) {\n const config = readAgenticConfig(dir);\n const sched = config.backupSchedule;\n if (!sched) {\n console.log(info(\"No backup schedule configured.\"));\n } else {\n console.log(bold(\"Backup Schedule:\"));\n console.log(` every: ${sched.every}`);\n console.log(` keep: ${sched.keep} daily backups`);\n if (sched.weekly) console.log(` weekly: ${sched.weekly} weekly backups`);\n if (sched.monthly) console.log(` monthly: ${sched.monthly} monthly backups`);\n if (sched.remote) console.log(` remote: ${sched.remote}`);\n console.log(` lastBackup: ${sched.lastBackup ?? \"never\"}`);\n }\n }\n}\n\nexport function shouldRunScheduledBackup(dataDir: string): boolean {\n const config = readAgenticConfig(dataDir);\n const sched = config.backupSchedule;\n if (!sched) return false;\n if (!sched.lastBackup) return true;\n const last = new Date(sched.lastBackup).getTime();\n const oneDayMs = 24 * 60 * 60 * 1000;\n return Date.now() - last >= oneDayMs;\n}\n\nexport async function runScheduledBackupIfDue(dataDir: string): Promise<void> {\n if (!shouldRunScheduledBackup(dataDir)) return;\n const config = readAgenticConfig(dataDir);\n const sched = config.backupSchedule!;\n const customersDir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(customersDir)) return;\n\n const zipPath = path.join(dataDir, `dxcrm-backup-${new Date().toISOString().slice(0, 10)}.zip`);\n const includeDirs = [\"customers/\"];\n if (fs.existsSync(path.join(dataDir, \".agentic\"))) includeDirs.push(\".agentic/\");\n\n try {\n execSync(`zip -r \"${zipPath}\" ${includeDirs.join(\" \")}`, { cwd: dataDir });\n\n const retention: RetentionConfig | undefined =\n (sched.weekly ?? sched.monthly)\n ? {\n daily: sched.keep,\n ...(sched.weekly ? { weekly: sched.weekly } : {}),\n ...(sched.monthly ? { monthly: sched.monthly } : {}),\n }\n : undefined;\n pruneOldBackups(dataDir, sched.keep, retention);\n\n if (sched.remote) {\n await uploadBackup(zipPath, sched.remote).catch(() => {\n /* non-fatal */\n });\n }\n\n config.backupSchedule!.lastBackup = new Date().toISOString();\n writeAgenticConfig(dataDir, config);\n process.stderr.write(`[daemon] Scheduled backup saved: ${zipPath}\\n`);\n } catch (err) {\n process.stderr.write(`[daemon] Scheduled backup failed: ${(err as Error).message}\\n`);\n }\n}\n\n// ─── Restore ──────────────────────────────────────────────────────────────────\n\nexport async function runRestore(zipPath: string, dataDir?: string): Promise<void> {\n const dir = dataDir ?? process.cwd();\n try {\n execSync(`unzip -o \"${path.resolve(zipPath)}\" -d \"${dir}\"`, { cwd: dir });\n console.log(success(\"✓ Restore complete.\"));\n } catch (err) {\n console.error(error(`✗ Restore failed: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\nexport interface RestoreDrillReport {\n ok: boolean;\n verified: boolean;\n hasCustomers: boolean;\n hasAgentic: boolean;\n reason?: string;\n}\n\n/**\n * Restore-drill: verify a backup is actually restorable WITHOUT touching live\n * data — checks integrity (unzip -t) and that the archive contains the expected\n * top-level state (customers/, .agentic/). Returns a report for monitoring.\n */\nexport async function runRestoreDrill(\n zipPath: string,\n opts: { silent?: boolean } = {}\n): Promise<RestoreDrillReport> {\n const resolved = path.resolve(zipPath);\n if (!fs.existsSync(resolved)) {\n if (!opts.silent) console.error(error(`✗ File not found: ${zipPath}`));\n return {\n ok: false,\n verified: false,\n hasCustomers: false,\n hasAgentic: false,\n reason: \"not_found\",\n };\n }\n\n const verified = verifyBackupFile(resolved);\n let hasCustomers = false;\n let hasAgentic = false;\n if (verified) {\n try {\n const listing = execSync(`unzip -l \"${resolved}\"`, { stdio: \"pipe\" }).toString();\n hasCustomers = listing.includes(\"customers/\");\n hasAgentic = listing.includes(\".agentic/\");\n } catch {\n /* listing failed — treated as incomplete */\n }\n }\n\n const ok = verified && hasCustomers;\n if (!opts.silent) {\n if (ok) {\n console.log(\n success(\n `✓ Restore drill OK — integrity verified; customers/${hasAgentic ? \" + .agentic/\" : \"\"} present`\n )\n );\n } else {\n console.error(\n error(`✗ Restore drill failed (verified=${verified}, customers=${hasCustomers})`)\n );\n }\n }\n return { ok, verified, hasCustomers, hasAgentic };\n}\n\n// ─── Commands ─────────────────────────────────────────────────────────────────\n\nconst scheduleSubCommand = new Command(\"schedule\")\n .description(\"Configure automatic backup schedule\")\n .option(\"--every <interval>\", \"Backup interval (e.g. day)\")\n .option(\"--keep <n>\", \"Daily backups to keep (default: 7)\")\n .option(\"--weekly <n>\", \"Weekly backups to keep (e.g. 4)\")\n .option(\"--monthly <n>\", \"Monthly backups to keep (e.g. 12)\")\n .option(\"--remote <url>\", \"Remote destination (s3://, rsync://, or local path)\")\n .option(\"--status\", \"Show current schedule\")\n .option(\"--clear\", \"Remove backup schedule\")\n .action((opts) => runBackupSchedule(opts, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd()));\n\nconst verifySubCommand = new Command(\"verify\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Verify backup integrity (SHA-256 + zip test)\")\n .action((zipPath: string) => runVerify(zipPath));\n\nconst drillSubCommand = new Command(\"drill\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Restore-drill: verify a backup is restorable without touching live data\")\n .action(async (zipPath: string) => {\n const report = await runRestoreDrill(zipPath);\n if (!report.ok) process.exitCode = 1;\n });\n\nconst listSubCommand = new Command(\"list\").description(\"List available backups\").action(() => {\n const dir = process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd();\n const entries = readBackupLog(dir);\n const fileEntries = listBackupsInDir(dir);\n const combined = entries.length > 0 ? entries : fileEntries;\n if (combined.length === 0) {\n console.log(info(\"No backups found.\"));\n return;\n }\n for (const e of combined) {\n const enc = e.encrypted ? \" [encrypted]\" : \"\";\n const ver = e.verified ? \" ✓\" : \"\";\n const mb = e.sizeBytes > 0 ? ` ${(e.sizeBytes / 1024 / 1024).toFixed(1)} MB` : \"\";\n console.log(` ${bold(e.filename)}${enc}${ver}${mb} ${e.createdAt.slice(0, 10)}`);\n }\n});\n\nexport const backupCommand = new Command(\"backup\")\n .argument(\"[output]\", \"Output path for backup zip\")\n .description(\"Backup customers/ + .agentic/ directories\")\n .option(\"--encrypt\", \"Encrypt the backup (AES-256-GCM)\")\n .option(\"--remote <url>\", \"Also upload to remote (s3://, rsync://, or path)\")\n .action((output?: string, opts?: { encrypt?: boolean; remote?: string }) => {\n void runBackup(output, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd(), opts ?? {});\n });\n\nbackupCommand.addCommand(scheduleSubCommand);\nbackupCommand.addCommand(verifySubCommand);\nbackupCommand.addCommand(drillSubCommand);\nbackupCommand.addCommand(listSubCommand);\n\nexport const restoreCommand = new Command(\"restore\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Restore from backup zip\")\n .action((zipPath: string) => runRestore(zipPath, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd()));\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { u as runScheduledBackupIfDue } from "./backup-CeMk9z86.js";
2
+ export { runScheduledBackupIfDue };
@@ -0,0 +1,52 @@
1
+ //#region src/sync/calendly.ts
2
+ async function calendlyRequest(apiKey, path) {
3
+ const { default: https } = await import("https");
4
+ return new Promise((resolve, reject) => {
5
+ const req = https.request(`https://api.calendly.com${path}`, { headers: {
6
+ Authorization: `Bearer ${apiKey}`,
7
+ "Content-Type": "application/json"
8
+ } }, (res) => {
9
+ let data = "";
10
+ res.on("data", (chunk) => {
11
+ data += chunk.toString();
12
+ });
13
+ res.on("end", () => {
14
+ try {
15
+ resolve(JSON.parse(data));
16
+ } catch {
17
+ reject(/* @__PURE__ */ new Error(`Invalid JSON from Calendly API: ${data.slice(0, 200)}`));
18
+ }
19
+ });
20
+ });
21
+ req.on("error", reject);
22
+ req.end();
23
+ });
24
+ }
25
+ async function getCurrentUserUri(apiKey) {
26
+ return (await calendlyRequest(apiKey, "/users/me")).resource.uri;
27
+ }
28
+ async function listEventTypes(apiKey) {
29
+ const userUri = await getCurrentUserUri(apiKey);
30
+ return (await calendlyRequest(apiKey, `/event_types?user=${encodeURIComponent(userUri)}&active=true`)).collection.map((et) => ({
31
+ uri: et.uri,
32
+ slug: et.slug,
33
+ name: et.name,
34
+ duration: et.duration,
35
+ schedulingUrl: et.scheduling_url,
36
+ active: et.active
37
+ }));
38
+ }
39
+ async function getSchedulingLink(apiKey, eventTypeSlug, prefill) {
40
+ const eventType = (await listEventTypes(apiKey)).find((et) => et.slug === eventTypeSlug || et.name.toLowerCase().includes(eventTypeSlug.toLowerCase()));
41
+ if (!eventType) throw new Error(`Event type '${eventTypeSlug}' not found in Calendly`);
42
+ let url = eventType.schedulingUrl;
43
+ const params = new URLSearchParams();
44
+ if (prefill?.name) params.set("name", prefill.name);
45
+ if (prefill?.email) params.set("email", prefill.email);
46
+ if (params.toString()) url += `?${params.toString()}`;
47
+ return url;
48
+ }
49
+ //#endregion
50
+ export { getSchedulingLink, listEventTypes };
51
+
52
+ //# sourceMappingURL=calendly-Bft_wwji.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendly-Bft_wwji.js","names":[],"sources":["../src/sync/calendly.ts"],"sourcesContent":["export interface CalendlyEventType {\n uri: string;\n slug: string;\n name: string;\n duration: number;\n schedulingUrl: string;\n active: boolean;\n}\n\nexport interface CalendlyScheduledEvent {\n uri: string;\n name: string;\n startTime: string;\n endTime: string;\n inviteeName: string;\n inviteeEmail: string;\n status: \"active\" | \"canceled\";\n}\n\ninterface CalendlyApiEventType {\n uri: string;\n slug: string;\n name: string;\n duration: number;\n scheduling_url: string;\n active: boolean;\n}\n\ninterface CalendlyApiEvent {\n uri: string;\n name: string;\n start_time: string;\n end_time: string;\n status: \"active\" | \"canceled\";\n event_memberships: unknown[];\n invitees_counter: { total: number };\n}\n\nasync function calendlyRequest<T>(apiKey: string, path: string): Promise<T> {\n const { default: https } = await import(\"https\");\n return new Promise((resolve, reject) => {\n const req = https.request(\n `https://api.calendly.com${path}`,\n { headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" } },\n (res) => {\n let data = \"\";\n res.on(\"data\", (chunk: Buffer) => {\n data += chunk.toString();\n });\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(data) as T);\n } catch {\n reject(new Error(`Invalid JSON from Calendly API: ${data.slice(0, 200)}`));\n }\n });\n }\n );\n req.on(\"error\", reject);\n req.end();\n });\n}\n\nasync function getCurrentUserUri(apiKey: string): Promise<string> {\n const resp = await calendlyRequest<{ resource: { uri: string } }>(apiKey, \"/users/me\");\n return resp.resource.uri;\n}\n\nexport async function listEventTypes(apiKey: string): Promise<CalendlyEventType[]> {\n const userUri = await getCurrentUserUri(apiKey);\n const encoded = encodeURIComponent(userUri);\n const resp = await calendlyRequest<{ collection: CalendlyApiEventType[] }>(\n apiKey,\n `/event_types?user=${encoded}&active=true`\n );\n return resp.collection.map((et) => ({\n uri: et.uri,\n slug: et.slug,\n name: et.name,\n duration: et.duration,\n schedulingUrl: et.scheduling_url,\n active: et.active,\n }));\n}\n\nexport async function getSchedulingLink(\n apiKey: string,\n eventTypeSlug: string,\n prefill?: { name?: string; email?: string }\n): Promise<string> {\n const eventTypes = await listEventTypes(apiKey);\n const eventType = eventTypes.find(\n (et) => et.slug === eventTypeSlug || et.name.toLowerCase().includes(eventTypeSlug.toLowerCase())\n );\n if (!eventType) {\n throw new Error(`Event type '${eventTypeSlug}' not found in Calendly`);\n }\n let url = eventType.schedulingUrl;\n const params = new URLSearchParams();\n if (prefill?.name) params.set(\"name\", prefill.name);\n if (prefill?.email) params.set(\"email\", prefill.email);\n if (params.toString()) url += `?${params.toString()}`;\n return url;\n}\n\nexport async function listScheduledEvents(\n apiKey: string,\n since?: string\n): Promise<CalendlyScheduledEvent[]> {\n const userUri = await getCurrentUserUri(apiKey);\n const encoded = encodeURIComponent(userUri);\n const sinceParam = since ? `&min_start_time=${encodeURIComponent(since)}` : \"\";\n const resp = await calendlyRequest<{ collection: CalendlyApiEvent[] }>(\n apiKey,\n `/scheduled_events?user=${encoded}&status=active${sinceParam}&count=100`\n );\n return resp.collection.map((ev) => ({\n uri: ev.uri,\n name: ev.name,\n startTime: ev.start_time,\n endTime: ev.end_time,\n inviteeName: \"\",\n inviteeEmail: \"\",\n status: ev.status,\n }));\n}\n"],"mappings":";AAsCA,eAAe,gBAAmB,QAAgB,MAA0B;CAC1E,MAAM,EAAE,SAAS,UAAU,MAAM,OAAO;CACxC,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,MAAM,MAAM,QAChB,2BAA2B,QAC3B,EAAE,SAAS;GAAE,eAAe,UAAU;GAAU,gBAAgB;EAAmB,EAAE,IACpF,QAAQ;GACP,IAAI,OAAO;GACX,IAAI,GAAG,SAAS,UAAkB;IAChC,QAAQ,MAAM,SAAS;GACzB,CAAC;GACD,IAAI,GAAG,aAAa;IAClB,IAAI;KACF,QAAQ,KAAK,MAAM,IAAI,CAAM;IAC/B,QAAQ;KACN,uBAAO,IAAI,MAAM,mCAAmC,KAAK,MAAM,GAAG,GAAG,GAAG,CAAC;IAC3E;GACF,CAAC;EACH,CACF;EACA,IAAI,GAAG,SAAS,MAAM;EACtB,IAAI,IAAI;CACV,CAAC;AACH;AAEA,eAAe,kBAAkB,QAAiC;CAEhE,QAAO,MADY,gBAA+C,QAAQ,WAAW,GACzE,SAAS;AACvB;AAEA,eAAsB,eAAe,QAA8C;CACjF,MAAM,UAAU,MAAM,kBAAkB,MAAM;CAM9C,QAAO,MAJY,gBACjB,QACA,qBAHc,mBAAmB,OAGN,EAAE,aAC/B,GACY,WAAW,KAAK,QAAQ;EAClC,KAAK,GAAG;EACR,MAAM,GAAG;EACT,MAAM,GAAG;EACT,UAAU,GAAG;EACb,eAAe,GAAG;EAClB,QAAQ,GAAG;CACb,EAAE;AACJ;AAEA,eAAsB,kBACpB,QACA,eACA,SACiB;CAEjB,MAAM,aAAY,MADO,eAAe,MAAM,GACjB,MAC1B,OAAO,GAAG,SAAS,iBAAiB,GAAG,KAAK,YAAY,EAAE,SAAS,cAAc,YAAY,CAAC,CACjG;CACA,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,eAAe,cAAc,wBAAwB;CAEvE,IAAI,MAAM,UAAU;CACpB,MAAM,SAAS,IAAI,gBAAgB;CACnC,IAAI,SAAS,MAAM,OAAO,IAAI,QAAQ,QAAQ,IAAI;CAClD,IAAI,SAAS,OAAO,OAAO,IAAI,SAAS,QAAQ,KAAK;CACrD,IAAI,OAAO,SAAS,GAAG,OAAO,IAAI,OAAO,SAAS;CAClD,OAAO;AACT"}
@@ -0,0 +1,53 @@
1
+ //#region src/sync/calendly.ts
2
+ async function calendlyRequest(apiKey, path) {
3
+ const { default: https } = await import("https");
4
+ return new Promise((resolve, reject) => {
5
+ const req = https.request(`https://api.calendly.com${path}`, { headers: {
6
+ Authorization: `Bearer ${apiKey}`,
7
+ "Content-Type": "application/json"
8
+ } }, (res) => {
9
+ let data = "";
10
+ res.on("data", (chunk) => {
11
+ data += chunk.toString();
12
+ });
13
+ res.on("end", () => {
14
+ try {
15
+ resolve(JSON.parse(data));
16
+ } catch {
17
+ reject(/* @__PURE__ */ new Error(`Invalid JSON from Calendly API: ${data.slice(0, 200)}`));
18
+ }
19
+ });
20
+ });
21
+ req.on("error", reject);
22
+ req.end();
23
+ });
24
+ }
25
+ async function getCurrentUserUri(apiKey) {
26
+ return (await calendlyRequest(apiKey, "/users/me")).resource.uri;
27
+ }
28
+ async function listEventTypes(apiKey) {
29
+ const userUri = await getCurrentUserUri(apiKey);
30
+ return (await calendlyRequest(apiKey, `/event_types?user=${encodeURIComponent(userUri)}&active=true`)).collection.map((et) => ({
31
+ uri: et.uri,
32
+ slug: et.slug,
33
+ name: et.name,
34
+ duration: et.duration,
35
+ schedulingUrl: et.scheduling_url,
36
+ active: et.active
37
+ }));
38
+ }
39
+ async function getSchedulingLink(apiKey, eventTypeSlug, prefill) {
40
+ const eventType = (await listEventTypes(apiKey)).find((et) => et.slug === eventTypeSlug || et.name.toLowerCase().includes(eventTypeSlug.toLowerCase()));
41
+ if (!eventType) throw new Error(`Event type '${eventTypeSlug}' not found in Calendly`);
42
+ let url = eventType.schedulingUrl;
43
+ const params = new URLSearchParams();
44
+ if (prefill?.name) params.set("name", prefill.name);
45
+ if (prefill?.email) params.set("email", prefill.email);
46
+ if (params.toString()) url += `?${params.toString()}`;
47
+ return url;
48
+ }
49
+ //#endregion
50
+ exports.getSchedulingLink = getSchedulingLink;
51
+ exports.listEventTypes = listEventTypes;
52
+
53
+ //# sourceMappingURL=calendly-D3coO92o.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendly-D3coO92o.cjs","names":[],"sources":["../src/sync/calendly.ts"],"sourcesContent":["export interface CalendlyEventType {\n uri: string;\n slug: string;\n name: string;\n duration: number;\n schedulingUrl: string;\n active: boolean;\n}\n\nexport interface CalendlyScheduledEvent {\n uri: string;\n name: string;\n startTime: string;\n endTime: string;\n inviteeName: string;\n inviteeEmail: string;\n status: \"active\" | \"canceled\";\n}\n\ninterface CalendlyApiEventType {\n uri: string;\n slug: string;\n name: string;\n duration: number;\n scheduling_url: string;\n active: boolean;\n}\n\ninterface CalendlyApiEvent {\n uri: string;\n name: string;\n start_time: string;\n end_time: string;\n status: \"active\" | \"canceled\";\n event_memberships: unknown[];\n invitees_counter: { total: number };\n}\n\nasync function calendlyRequest<T>(apiKey: string, path: string): Promise<T> {\n const { default: https } = await import(\"https\");\n return new Promise((resolve, reject) => {\n const req = https.request(\n `https://api.calendly.com${path}`,\n { headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" } },\n (res) => {\n let data = \"\";\n res.on(\"data\", (chunk: Buffer) => {\n data += chunk.toString();\n });\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(data) as T);\n } catch {\n reject(new Error(`Invalid JSON from Calendly API: ${data.slice(0, 200)}`));\n }\n });\n }\n );\n req.on(\"error\", reject);\n req.end();\n });\n}\n\nasync function getCurrentUserUri(apiKey: string): Promise<string> {\n const resp = await calendlyRequest<{ resource: { uri: string } }>(apiKey, \"/users/me\");\n return resp.resource.uri;\n}\n\nexport async function listEventTypes(apiKey: string): Promise<CalendlyEventType[]> {\n const userUri = await getCurrentUserUri(apiKey);\n const encoded = encodeURIComponent(userUri);\n const resp = await calendlyRequest<{ collection: CalendlyApiEventType[] }>(\n apiKey,\n `/event_types?user=${encoded}&active=true`\n );\n return resp.collection.map((et) => ({\n uri: et.uri,\n slug: et.slug,\n name: et.name,\n duration: et.duration,\n schedulingUrl: et.scheduling_url,\n active: et.active,\n }));\n}\n\nexport async function getSchedulingLink(\n apiKey: string,\n eventTypeSlug: string,\n prefill?: { name?: string; email?: string }\n): Promise<string> {\n const eventTypes = await listEventTypes(apiKey);\n const eventType = eventTypes.find(\n (et) => et.slug === eventTypeSlug || et.name.toLowerCase().includes(eventTypeSlug.toLowerCase())\n );\n if (!eventType) {\n throw new Error(`Event type '${eventTypeSlug}' not found in Calendly`);\n }\n let url = eventType.schedulingUrl;\n const params = new URLSearchParams();\n if (prefill?.name) params.set(\"name\", prefill.name);\n if (prefill?.email) params.set(\"email\", prefill.email);\n if (params.toString()) url += `?${params.toString()}`;\n return url;\n}\n\nexport async function listScheduledEvents(\n apiKey: string,\n since?: string\n): Promise<CalendlyScheduledEvent[]> {\n const userUri = await getCurrentUserUri(apiKey);\n const encoded = encodeURIComponent(userUri);\n const sinceParam = since ? `&min_start_time=${encodeURIComponent(since)}` : \"\";\n const resp = await calendlyRequest<{ collection: CalendlyApiEvent[] }>(\n apiKey,\n `/scheduled_events?user=${encoded}&status=active${sinceParam}&count=100`\n );\n return resp.collection.map((ev) => ({\n uri: ev.uri,\n name: ev.name,\n startTime: ev.start_time,\n endTime: ev.end_time,\n inviteeName: \"\",\n inviteeEmail: \"\",\n status: ev.status,\n }));\n}\n"],"mappings":";AAsCA,eAAe,gBAAmB,QAAgB,MAA0B;CAC1E,MAAM,EAAE,SAAS,UAAU,MAAM,OAAO;CACxC,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,MAAM,MAAM,QAChB,2BAA2B,QAC3B,EAAE,SAAS;GAAE,eAAe,UAAU;GAAU,gBAAgB;EAAmB,EAAE,IACpF,QAAQ;GACP,IAAI,OAAO;GACX,IAAI,GAAG,SAAS,UAAkB;IAChC,QAAQ,MAAM,SAAS;GACzB,CAAC;GACD,IAAI,GAAG,aAAa;IAClB,IAAI;KACF,QAAQ,KAAK,MAAM,IAAI,CAAM;IAC/B,QAAQ;KACN,uBAAO,IAAI,MAAM,mCAAmC,KAAK,MAAM,GAAG,GAAG,GAAG,CAAC;IAC3E;GACF,CAAC;EACH,CACF;EACA,IAAI,GAAG,SAAS,MAAM;EACtB,IAAI,IAAI;CACV,CAAC;AACH;AAEA,eAAe,kBAAkB,QAAiC;CAEhE,QAAO,MADY,gBAA+C,QAAQ,WAAW,GACzE,SAAS;AACvB;AAEA,eAAsB,eAAe,QAA8C;CACjF,MAAM,UAAU,MAAM,kBAAkB,MAAM;CAM9C,QAAO,MAJY,gBACjB,QACA,qBAHc,mBAAmB,OAGN,EAAE,aAC/B,GACY,WAAW,KAAK,QAAQ;EAClC,KAAK,GAAG;EACR,MAAM,GAAG;EACT,MAAM,GAAG;EACT,UAAU,GAAG;EACb,eAAe,GAAG;EAClB,QAAQ,GAAG;CACb,EAAE;AACJ;AAEA,eAAsB,kBACpB,QACA,eACA,SACiB;CAEjB,MAAM,aAAY,MADO,eAAe,MAAM,GACjB,MAC1B,OAAO,GAAG,SAAS,iBAAiB,GAAG,KAAK,YAAY,EAAE,SAAS,cAAc,YAAY,CAAC,CACjG;CACA,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,eAAe,cAAc,wBAAwB;CAEvE,IAAI,MAAM,UAAU;CACpB,MAAM,SAAS,IAAI,gBAAgB;CACnC,IAAI,SAAS,MAAM,OAAO,IAAI,QAAQ,QAAQ,IAAI;CAClD,IAAI,SAAS,OAAO,OAAO,IAAI,SAAS,QAAQ,KAAK;CACrD,IAAI,OAAO,SAAS,GAAG,OAAO,IAAI,OAAO,SAAS;CAClD,OAAO;AACT"}