@fleox-agent-crm/agentcrm 0.0.0-stage → 0.3.1

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 (129) hide show
  1. package/LICENSE +143 -0
  2. package/README.md +223 -2
  3. package/changelog.json +76 -0
  4. package/dist/admin/admin-files.json +26 -0
  5. package/dist/admin/assets/chartsImpl-B45AAqnr.js +91 -0
  6. package/dist/admin/assets/firebase-C1x_l9P9.js +2 -0
  7. package/dist/admin/assets/gitJson-CAUSxl8d.js +3 -0
  8. package/dist/admin/assets/index-ChFauO8y.js +1181 -0
  9. package/dist/admin/assets/index-TcjySLVg.css +1 -0
  10. package/dist/admin/assets/index.esm-BFc7vwnl.js +400 -0
  11. package/dist/admin/assets/index.esm-CtEQZRb1.js +1651 -0
  12. package/dist/admin/assets/index.esm-Dum0J07z.js +16 -0
  13. package/dist/admin/assets/index.esm2017-EOzKbpUS.js +390 -0
  14. package/dist/admin/assets/lucide-react.min--rznIkmQ.js +6 -0
  15. package/dist/admin/assets/sdk-DgzLACmL.js +1717 -0
  16. package/dist/admin/config.js +3 -0
  17. package/dist/admin/favicon.svg +1 -0
  18. package/dist/admin/index.html +139 -0
  19. package/dist/admin/runtime/agentcrm-runtime.js +104 -0
  20. package/dist/admin/runtime/lucide-react.js +791 -0
  21. package/dist/admin/runtime/react-dom-client.js +17 -0
  22. package/dist/admin/runtime/react-dom.js +20 -0
  23. package/dist/admin/runtime/react-i18next.js +19 -0
  24. package/dist/admin/runtime/react-jsx-runtime.js +17 -0
  25. package/dist/admin/runtime/react.js +22 -0
  26. package/dist/agent/agentcrm.mjs +4057 -0
  27. package/dist/agent/chunks/_commonjsHelpers-ClciMIcB.mjs +14 -0
  28. package/dist/agent/chunks/activities-Cj_SIOay.mjs +227 -0
  29. package/dist/agent/chunks/api-Cz0Ubj-B.mjs +98 -0
  30. package/dist/agent/chunks/appearance-BloHbiLA.mjs +174 -0
  31. package/dist/agent/chunks/automations-DwWruxmu.mjs +698 -0
  32. package/dist/agent/chunks/backup-W3CjgkAJ.mjs +116 -0
  33. package/dist/agent/chunks/batch-CVETiJj5.mjs +142 -0
  34. package/dist/agent/chunks/compact-BGHI92tm.mjs +69 -0
  35. package/dist/agent/chunks/context-CxFfqS4D.mjs +65 -0
  36. package/dist/agent/chunks/core-Dy7vY9H6.mjs +255 -0
  37. package/dist/agent/chunks/dedupe-BepmsaHv.mjs +123 -0
  38. package/dist/agent/chunks/encoding-C9MzdP95.mjs +36 -0
  39. package/dist/agent/chunks/exportCsv-C6e6XEmB.mjs +130 -0
  40. package/dist/agent/chunks/extCheck-GgHG1dYB.mjs +200 -0
  41. package/dist/agent/chunks/extPackage-DuZ6GZhp.mjs +115 -0
  42. package/dist/agent/chunks/extScaffold-Aqyjf1nr.mjs +320 -0
  43. package/dist/agent/chunks/extensionActions-D2HtpQz4.mjs +48 -0
  44. package/dist/agent/chunks/extensions-Bp72uZ9a.mjs +164 -0
  45. package/dist/agent/chunks/featureRegistry-DSsZbhrh.mjs +149 -0
  46. package/dist/agent/chunks/fields-B0bitgmr.mjs +476 -0
  47. package/dist/agent/chunks/files-CjsPJSR6.mjs +158 -0
  48. package/dist/agent/chunks/files-D1daDgY0.mjs +367 -0
  49. package/dist/agent/chunks/firebase-DxGVQV9E.mjs +454 -0
  50. package/dist/agent/chunks/firebaseRules-iori-s7U.mjs +221 -0
  51. package/dist/agent/chunks/forms-BfuWaQjA.mjs +847 -0
  52. package/dist/agent/chunks/gitJson-fjr8mcHe.mjs +528 -0
  53. package/dist/agent/chunks/hosting-Bme7LxPc.mjs +144 -0
  54. package/dist/agent/chunks/http-Bk9ICb7I.mjs +127 -0
  55. package/dist/agent/chunks/ids-CvnmrMJX.mjs +59 -0
  56. package/dist/agent/chunks/ids-u-noYcy5.mjs +14 -0
  57. package/dist/agent/chunks/importCsv-LVmMTaWU.mjs +334 -0
  58. package/dist/agent/chunks/index-B-4W0-P4.mjs +535 -0
  59. package/dist/agent/chunks/index-BM8qeGOb.mjs +2489 -0
  60. package/dist/agent/chunks/index-Bnmav-Oh.mjs +311 -0
  61. package/dist/agent/chunks/index-CRkw3_iF.mjs +147 -0
  62. package/dist/agent/chunks/index-CWrrF1wl.mjs +111 -0
  63. package/dist/agent/chunks/index-DUSdTBkz.mjs +2988 -0
  64. package/dist/agent/chunks/index-RCmo-uld.mjs +26 -0
  65. package/dist/agent/chunks/index-w03ReRs4.mjs +9780 -0
  66. package/dist/agent/chunks/install-CbpD_7gd.mjs +188 -0
  67. package/dist/agent/chunks/jsx-runtime-Ca2uwGDr.mjs +76 -0
  68. package/dist/agent/chunks/loader-MPD9iAvh.mjs +184 -0
  69. package/dist/agent/chunks/lucide-react.min-Bt3gtepY.mjs +64 -0
  70. package/dist/agent/chunks/memory-WNR3VHUJ.mjs +56 -0
  71. package/dist/agent/chunks/migrate-CsClnnN8.mjs +182 -0
  72. package/dist/agent/chunks/modules-clWdE-K2.mjs +202 -0
  73. package/dist/agent/chunks/nodeImport-ylfRDvqt.mjs +38 -0
  74. package/dist/agent/chunks/package-B7mI6HGi.mjs +194 -0
  75. package/dist/agent/chunks/papaparse-Df7i1dHr.mjs +1309 -0
  76. package/dist/agent/chunks/pipelines-BAKpPk1w.mjs +234 -0
  77. package/dist/agent/chunks/plan-BmTat6-T.mjs +254 -0
  78. package/dist/agent/chunks/pluginRegistry-DyHtlkcr.mjs +83 -0
  79. package/dist/agent/chunks/query-C5nJB_3C.mjs +192 -0
  80. package/dist/agent/chunks/records-DcHFelq5.mjs +2195 -0
  81. package/dist/agent/chunks/registry-COfJrSmg.mjs +182 -0
  82. package/dist/agent/chunks/registry-DijAKZ24.mjs +431 -0
  83. package/dist/agent/chunks/relations-B9TynBmA.mjs +191 -0
  84. package/dist/agent/chunks/reports-BACHGccj.mjs +189 -0
  85. package/dist/agent/chunks/runtime-C0Xf0Axi.mjs +14873 -0
  86. package/dist/agent/chunks/runtimeConfig-CCOFg1RM.mjs +250 -0
  87. package/dist/agent/chunks/s3-Dz50-NuA.mjs +903 -0
  88. package/dist/agent/chunks/schema-C4lSslXv.mjs +373 -0
  89. package/dist/agent/chunks/schema-DnLj8dUO.mjs +213 -0
  90. package/dist/agent/chunks/sdk-whvfg786.mjs +43644 -0
  91. package/dist/agent/chunks/seed-6MRmQQBR.mjs +541 -0
  92. package/dist/agent/chunks/server-DC3t8r30.mjs +242 -0
  93. package/dist/agent/chunks/settings-Di8nzNEL.mjs +111 -0
  94. package/dist/agent/chunks/standardObjects-CheqfDqa.mjs +243 -0
  95. package/dist/agent/chunks/stats-BfuLWhVV.mjs +51 -0
  96. package/dist/agent/chunks/store-5gqRSKXg.mjs +122 -0
  97. package/dist/agent/chunks/themes-DPZFv3yc.mjs +43 -0
  98. package/dist/agent/chunks/tools-iSH-yMpl.mjs +884 -0
  99. package/dist/agent/chunks/types-BOy9PUl7.mjs +44 -0
  100. package/dist/agent/chunks/users-DI1BvS0f.mjs +107 -0
  101. package/dist/agent/chunks/utils-DmI8FtXP.mjs +83 -0
  102. package/dist/agent/chunks/views-DH1n9Lrt.mjs +109 -0
  103. package/dist/version.json +7 -0
  104. package/package.json +97 -4
  105. package/plugin/.claude-plugin/plugin.json +17 -0
  106. package/plugin/skills/agentcrm-admin/SKILL.md +191 -0
  107. package/plugin/skills/agentcrm-admin/references/appearance.md +26 -0
  108. package/plugin/skills/agentcrm-admin/references/import.md +16 -0
  109. package/plugin/skills/agentcrm-admin/references/records.md +30 -0
  110. package/plugin/skills/agentcrm-admin/references/schema.md +59 -0
  111. package/plugin/skills/agentcrm-extensions/SKILL.md +93 -0
  112. package/plugin/skills/agentcrm-extensions/references/module-recipes.md +213 -0
  113. package/plugin/skills/agentcrm-extensions/references/runtime-api.md +41 -0
  114. package/plugin/skills/agentcrm-extensions/references/testing.md +78 -0
  115. package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +65 -0
  116. package/plugin/skills/agentcrm-install/SKILL.md +116 -0
  117. package/plugin/skills/agentcrm-install/references/firebase.md +18 -0
  118. package/plugin/skills/agentcrm-install/references/github.md +20 -0
  119. package/plugin/skills/agentcrm-install/references/mcp.md +17 -0
  120. package/plugin/skills/agentcrm-install/references/plans.md +45 -0
  121. package/plugin/skills/agentcrm-install/references/s3.md +24 -0
  122. package/plugin/skills/agentcrm-manage/SKILL.md +108 -0
  123. package/plugin/skills/agentcrm-manage/references/cron.md +11 -0
  124. package/plugin/skills/agentcrm-manage/references/doctor.md +18 -0
  125. package/plugin/skills/agentcrm-sales/SKILL.md +92 -0
  126. package/plugin/skills/agentcrm-sales/references/enrichment.md +13 -0
  127. package/plugin/skills/agentcrm-sales/references/playbooks.md +29 -0
  128. package/plugin/skills/agentcrm-sales/references/weekly-report.md +27 -0
  129. package/types/agentcrm-runtime.d.ts +894 -0
@@ -0,0 +1,541 @@
1
+ import { createRequire as __acrmRequire } from "node:module";
2
+ import { fileURLToPath as __acrmPath } from "node:url";
3
+ import { dirname as __acrmDirname } from "node:path";
4
+ const require2 = __acrmRequire(import.meta.url);
5
+ const __filename = __acrmPath(import.meta.url);
6
+ const __dirname = __acrmDirname(__filename);
7
+ import { normalizeEmail } from "./fields-B0bitgmr.mjs";
8
+ import { a as seededId } from "./ids-CvnmrMJX.mjs";
9
+ import { l as localizeObjects, S as SCHEMA_MIGRATIONS, a as localizePipeline, b as localizeView } from "./migrate-CsClnnN8.mjs";
10
+ import { a as standardObjects, b as defaultPipeline, c as defaultViews } from "./standardObjects-CheqfDqa.mjs";
11
+ import { assertCan } from "./context-CxFfqS4D.mjs";
12
+ import { sha256Hex } from "./files-CjsPJSR6.mjs";
13
+ import { asDoc, objectOf, usersOf, settingsDoc } from "./store-5gqRSKXg.mjs";
14
+ import { DEFAULT_SETTINGS } from "./settings-Di8nzNEL.mjs";
15
+ import "./pluginRegistry-DyHtlkcr.mjs";
16
+ import "./runtimeConfig-CCOFg1RM.mjs";
17
+ const PALETTES = [
18
+ ["#6366f1", "#8b5cf6"],
19
+ ["#0ea5e9", "#2563eb"],
20
+ ["#10b981", "#0d9488"],
21
+ ["#f59e0b", "#ea580c"],
22
+ ["#ec4899", "#db2777"],
23
+ ["#14b8a6", "#0891b2"],
24
+ ["#8b5cf6", "#c026d3"],
25
+ ["#f97316", "#dc2626"],
26
+ ["#22c55e", "#65a30d"],
27
+ ["#3b82f6", "#7c3aed"]
28
+ ];
29
+ function hash(s) {
30
+ let h = 2166136261;
31
+ for (let i = 0; i < s.length; i++) h = Math.imul(h ^ s.charCodeAt(i), 16777619);
32
+ return h >>> 0;
33
+ }
34
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
35
+ const SKIN = ["#f1c7a5", "#e0ac86", "#c68a62", "#a56b46", "#7d4f33", "#f5d6bf"];
36
+ const HAIR = ["#2b1d14", "#4a3020", "#8a5a2b", "#c9a25c", "#1f2937", "#6b7280", "#7c2d12"];
37
+ const SHIRT = ["#1e3a8a", "#065f46", "#7c3aed", "#be123c", "#0f766e", "#334155", "#b45309", "#0369a1"];
38
+ function demoAvatarSvg(name) {
39
+ const h = hash(name);
40
+ const [a, b] = PALETTES[h % PALETTES.length];
41
+ const skin = SKIN[(h >>> 3) % SKIN.length];
42
+ const hair = HAIR[(h >>> 6) % HAIR.length];
43
+ const shirt = SHIRT[(h >>> 9) % SHIRT.length];
44
+ const style = (h >>> 12) % 3;
45
+ const angle = (h >>> 4) % 360;
46
+ const hairShape = style === 0 ? `<path d="M78 118 C78 62 178 50 180 112 C168 92 120 86 92 104 Z" fill="${hair}"/>` : style === 1 ? `<path d="M70 132 C60 52 196 46 188 132 L176 168 C178 120 168 96 128 92 C90 96 78 120 82 168 Z" fill="${hair}"/>` : `<path d="M84 104 C96 70 160 64 174 102 C150 90 112 90 84 104 Z" fill="${hair}"/><circle cx="128" cy="66" r="22" fill="${hair}"/>`;
47
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
48
+ <defs><linearGradient id="g" gradientTransform="rotate(${angle} .5 .5)"><stop offset="0" stop-color="${a}" stop-opacity=".55"/><stop offset="1" stop-color="${b}" stop-opacity=".85"/></linearGradient></defs>
49
+ <rect width="256" height="256" fill="#e2e8f0"/>
50
+ <rect width="256" height="256" fill="url(#g)"/>
51
+ <path d="M40 256 C44 196 84 172 128 172 C172 172 212 196 216 256 Z" fill="${shirt}"/>
52
+ <rect x="112" y="146" width="32" height="34" rx="12" fill="${skin}"/>
53
+ <path d="M104 176 L128 200 L152 176" fill="none" stroke="#ffffff" stroke-opacity=".5" stroke-width="5" stroke-linecap="round"/>
54
+ <ellipse cx="128" cy="116" rx="46" ry="52" fill="${skin}"/>
55
+ ${hairShape}
56
+ <circle cx="111" cy="120" r="4" fill="#1f2937" fill-opacity=".75"/><circle cx="145" cy="120" r="4" fill="#1f2937" fill-opacity=".75"/>
57
+ <path d="M114 142 Q128 152 142 142" fill="none" stroke="#7c2d12" stroke-opacity=".55" stroke-width="3.5" stroke-linecap="round"/>
58
+ </svg>
59
+ `;
60
+ }
61
+ function demoLogoSvg(name) {
62
+ const h = hash(`logo:${name}`);
63
+ const [a, b] = PALETTES[h % PALETTES.length];
64
+ const shape = h % 4;
65
+ const letter = esc((name.trim()[0] ?? "?").toUpperCase());
66
+ const mark = shape === 0 ? `<circle cx="128" cy="128" r="84" fill="${a}"/><circle cx="160" cy="96" r="40" fill="${b}" fill-opacity=".9"/>` : shape === 1 ? `<polygon points="128,36 220,196 36,196" fill="${a}"/><polygon points="128,92 176,176 80,176" fill="${b}"/>` : shape === 2 ? `<polygon points="128,32 211,80 211,176 128,224 45,176 45,80" fill="${a}"/><polygon points="128,72 176,100 176,156 128,184 80,156 80,100" fill="${b}"/>` : `<rect x="44" y="44" width="168" height="168" rx="40" fill="${a}"/><rect x="92" y="92" width="120" height="120" rx="30" fill="${b}" fill-opacity=".85"/>`;
67
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
68
+ <rect width="256" height="256" rx="48" fill="#f8fafc"/>
69
+ ${mark}
70
+ <text x="128" y="128" dy=".36em" text-anchor="middle" font-family="Inter, system-ui, -apple-system, Segoe UI, sans-serif" font-size="88" font-weight="800" fill="#ffffff">${letter}</text>
71
+ </svg>
72
+ `;
73
+ }
74
+ const CRC_TABLE = (() => {
75
+ const t = new Uint32Array(256);
76
+ for (let n = 0; n < 256; n++) {
77
+ let c = n;
78
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
79
+ t[n] = c >>> 0;
80
+ }
81
+ return t;
82
+ })();
83
+ function crc32(bytes) {
84
+ let c = 4294967295;
85
+ for (const b of bytes) c = CRC_TABLE[(c ^ b) & 255] ^ c >>> 8;
86
+ return (c ^ 4294967295) >>> 0;
87
+ }
88
+ function adler32(bytes) {
89
+ let a = 1;
90
+ let b = 0;
91
+ for (const x of bytes) {
92
+ a = (a + x) % 65521;
93
+ b = (b + a) % 65521;
94
+ }
95
+ return (b << 16 | a) >>> 0;
96
+ }
97
+ function u32(n) {
98
+ return [n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255];
99
+ }
100
+ function concat(parts) {
101
+ const len = parts.reduce((n, p) => n + p.length, 0);
102
+ const out = new Uint8Array(len);
103
+ let o = 0;
104
+ for (const p of parts) {
105
+ out.set(p, o);
106
+ o += p.length;
107
+ }
108
+ return out;
109
+ }
110
+ function chunk(type, data) {
111
+ const td = concat([new TextEncoder().encode(type), data]);
112
+ return concat([u32(data.length), td, u32(crc32(td))]);
113
+ }
114
+ function zlibStored(raw) {
115
+ const blocks = [[120, 1]];
116
+ for (let i = 0; i < raw.length || i === 0; i += 65535) {
117
+ const part = raw.subarray(i, Math.min(raw.length, i + 65535));
118
+ const final = i + 65535 >= raw.length ? 1 : 0;
119
+ blocks.push([final, part.length & 255, part.length >>> 8, ~part.length & 255, ~part.length >>> 8 & 255], part);
120
+ if (final) break;
121
+ }
122
+ blocks.push(u32(adler32(raw)));
123
+ return concat(blocks);
124
+ }
125
+ function encodeIndexedPng(width, height, palette, pixel) {
126
+ const raw = new Uint8Array((width + 1) * height);
127
+ for (let y = 0; y < height; y++) {
128
+ raw[y * (width + 1)] = 0;
129
+ for (let x = 0; x < width; x++) raw[y * (width + 1) + 1 + x] = pixel(x, y);
130
+ }
131
+ const ihdr = concat([u32(width), u32(height), [8, 3, 0, 0, 0]]);
132
+ const plte = new Uint8Array(palette.flat());
133
+ return concat([[137, 80, 78, 71, 13, 10, 26, 10], chunk("IHDR", ihdr), chunk("PLTE", plte), chunk("IDAT", zlibStored(raw)), chunk("IEND", new Uint8Array())]);
134
+ }
135
+ function demoChartPng() {
136
+ const width = 360;
137
+ const height = 200;
138
+ const palette = [
139
+ [255, 255, 255],
140
+ // 0 bg
141
+ [226, 232, 240],
142
+ // 1 grid
143
+ [99, 102, 241],
144
+ // 2 bar
145
+ [16, 185, 129],
146
+ // 3 highlight bar
147
+ [148, 163, 184]
148
+ // 4 axis
149
+ ];
150
+ const values = [42, 58, 51, 77, 69, 94, 88, 120];
151
+ const max = 130;
152
+ const left = 24;
153
+ const bottom = height - 20;
154
+ const bw = 28;
155
+ const gap = (width - left - 16 - values.length * bw) / (values.length - 1);
156
+ const bytes = encodeIndexedPng(width, height, palette, (x, y) => {
157
+ if (y === bottom && x >= left - 4) return 4;
158
+ if (x === left - 4 && y <= bottom && y > 10) return 4;
159
+ for (let i = 0; i < values.length; i++) {
160
+ const x0 = Math.round(left + i * (bw + gap));
161
+ const top = Math.round(bottom - values[i] / max * (bottom - 16));
162
+ if (x >= x0 && x < x0 + bw && y >= top && y < bottom) return i === values.length - 1 ? 3 : 2;
163
+ }
164
+ if (y < bottom && y > 10 && (bottom - y) % 40 === 0) return 1;
165
+ return 0;
166
+ });
167
+ return { bytes, width, height };
168
+ }
169
+ function demoPdf(title, lines) {
170
+ const pdfText = (s) => s.replace(/[\\()]/g, (c) => `\\${c}`).replace(/[^\x20-\x7e]/g, "?");
171
+ const content = [
172
+ "BT /F1 22 Tf 56 760 Td (" + pdfText(title) + ") Tj ET",
173
+ ...lines.map((l, i) => `BT /F1 12 Tf 56 ${720 - i * 20} Td (${pdfText(l)}) Tj ET`),
174
+ "0.39 0.4 0.95 RG 2 w 56 745 m 540 745 l S"
175
+ ].join("\n");
176
+ const objects = [
177
+ "<< /Type /Catalog /Pages 2 0 R >>",
178
+ "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
179
+ "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>",
180
+ "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
181
+ `<< /Length ${content.length} >>
182
+ stream
183
+ ${content}
184
+ endstream`
185
+ ];
186
+ let out = "%PDF-1.4\n";
187
+ const offsets = [];
188
+ objects.forEach((o, i) => {
189
+ offsets.push(out.length);
190
+ out += `${i + 1} 0 obj
191
+ ${o}
192
+ endobj
193
+ `;
194
+ });
195
+ const xref = out.length;
196
+ out += `xref
197
+ 0 ${objects.length + 1}
198
+ 0000000000 65535 f
199
+ ${offsets.map((o) => `${String(o).padStart(10, "0")} 00000 n
200
+ `).join("")}`;
201
+ out += `trailer
202
+ << /Size ${objects.length + 1} /Root 1 0 R >>
203
+ startxref
204
+ ${xref}
205
+ %%EOF
206
+ `;
207
+ return new TextEncoder().encode(out);
208
+ }
209
+ async function seedDemoFiles(ctx, data) {
210
+ const report = { files: 0, avatars: 0, logos: 0, attachments: 0 };
211
+ const files = ctx.store.files;
212
+ if (!files) return report;
213
+ const first = seededId("demo:file:avatar:0");
214
+ if (ctx.store.get("files", first)) return report;
215
+ const now = ctx.now();
216
+ const blobs = [];
217
+ const metas = [];
218
+ const mutations = [];
219
+ const enc = new TextEncoder();
220
+ const add = async (id, name, mime, bytes, targets, dims) => {
221
+ blobs.push({ id, bytes, meta: { name, mime } });
222
+ metas.push({ id, name, mime, size: bytes.length, ...dims ?? {}, sha256: await sha256Hex(bytes), createdAt: now, updatedAt: now, ...data.ownerId ? { createdBy: data.ownerId } : {}, targets });
223
+ };
224
+ const slug = (s) => s.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
225
+ for (const [i, p] of data.people.slice(0, 10).entries()) {
226
+ const id = seededId(`demo:file:avatar:${i}`);
227
+ const name = String(p.fields.name ?? "Person");
228
+ await add(id, `${slug(name)}.svg`, "image/svg+xml", enc.encode(demoAvatarSvg(name)), [{ kind: "record", object: "people", id: p.id }], { width: 256, height: 256 });
229
+ mutations.push({ op: "patch", collection: "records", id: p.id, fields: { "fields.avatar": id } });
230
+ report.avatars++;
231
+ }
232
+ for (const [i, c] of data.companies.slice(0, 8).entries()) {
233
+ const id = seededId(`demo:file:logo:${i}`);
234
+ const name = String(c.fields.name ?? "Company");
235
+ await add(id, `${slug(name)}-logo.svg`, "image/svg+xml", enc.encode(demoLogoSvg(name)), [{ kind: "record", object: "companies", id: c.id }], { width: 256, height: 256 });
236
+ mutations.push({ op: "patch", collection: "records", id: c.id, fields: { "fields.logo": id } });
237
+ report.logos++;
238
+ }
239
+ const note = data.activities.find((a) => a.type === "note");
240
+ if (note) {
241
+ const chart = demoChartPng();
242
+ const chartId = seededId("demo:file:chart");
243
+ const pdfId = seededId("demo:file:pdf");
244
+ await add(chartId, "pipeline-chart.png", "image/png", chart.bytes, [{ kind: "activity", id: note.id }], { width: chart.width, height: chart.height });
245
+ const pdf = demoPdf("Proposal — Annual plan", ["Prepared for the demo workspace.", "Scope: onboarding, annual license, support.", "Total: 24,000 USD / year.", "Valid 30 days."]);
246
+ await add(pdfId, "proposal.pdf", "application/pdf", pdf, [{ kind: "activity", id: note.id }]);
247
+ const body = `${note.body ?? ""}
248
+
249
+ Pipeline trend this quarter:
250
+
251
+ ![pipeline-chart.png](file:${chartId})`.trim();
252
+ mutations.push({ op: "patch", collection: "activities", id: note.id, fields: { attachments: [chartId, pdfId], body } });
253
+ report.attachments += 2;
254
+ }
255
+ if (files.putMany) await files.putMany(blobs);
256
+ else for (const b of blobs) await files.put(b.id, b.bytes, b.meta);
257
+ await ctx.store.write([...metas.map((m) => ({ op: "set", collection: "files", doc: asDoc(m) })), ...mutations], { message: "Add demo files" });
258
+ report.files = metas.length;
259
+ return report;
260
+ }
261
+ const DEFAULT_ADMIN_ID = "owner";
262
+ async function seedCrm(ctx, opts = {}) {
263
+ assertCan(ctx, "schema.write");
264
+ const now = ctx.now();
265
+ const mutations = [];
266
+ const report = { objects: [], pipelines: [], views: [], adminUserId: null, demo: null };
267
+ const currency = (opts.currency ?? DEFAULT_SETTINGS.defaultCurrency).toUpperCase();
268
+ for (const o of localizeObjects(standardObjects(now), opts.locale)) {
269
+ if (objectOf(ctx.store, o.id)) continue;
270
+ const obj = {
271
+ ...o,
272
+ // Fresh install: already at the current schema (ops/migrate.ts).
273
+ migrations: [...SCHEMA_MIGRATIONS],
274
+ fields: o.fields.map((f) => f.type === "currency" ? { ...f, currency } : f)
275
+ };
276
+ mutations.push({ op: "set", collection: "objects", doc: asDoc(obj) });
277
+ report.objects.push(o.id);
278
+ }
279
+ const pipeline = localizePipeline(defaultPipeline(), opts.locale);
280
+ if (!ctx.store.get("pipelines", pipeline.id)) {
281
+ mutations.push({ op: "set", collection: "pipelines", doc: asDoc(pipeline) });
282
+ report.pipelines.push(pipeline.id);
283
+ }
284
+ for (const v of defaultViews().map((view) => localizeView(view, opts.locale))) {
285
+ if (ctx.store.get("views", v.id)) continue;
286
+ mutations.push({ op: "set", collection: "views", doc: asDoc(v) });
287
+ report.views.push(v.id);
288
+ }
289
+ let adminId = null;
290
+ if (opts.admin) {
291
+ const email = normalizeEmail(opts.admin.email);
292
+ const existing = usersOf(ctx.store).find((u) => normalizeEmail(u.email) === email);
293
+ adminId = existing?.id ?? opts.admin.id ?? (usersOf(ctx.store).length === 0 ? DEFAULT_ADMIN_ID : seededId(`user:${email}`));
294
+ if (!existing) {
295
+ const admin = { id: adminId, email, name: opts.admin.name.trim() || email.split("@")[0], role: "admin" };
296
+ mutations.push({ op: "set", collection: "users", doc: asDoc(admin) });
297
+ }
298
+ }
299
+ report.adminUserId = adminId;
300
+ const current = settingsDoc(ctx.store);
301
+ if (!current) {
302
+ const settings = {
303
+ ...DEFAULT_SETTINGS,
304
+ name: opts.name?.trim() || DEFAULT_SETTINGS.name,
305
+ defaultCurrency: currency,
306
+ locale: opts.locale ?? DEFAULT_SETTINGS.locale,
307
+ ...adminId ? { ownerUserId: adminId } : {},
308
+ createdAt: now,
309
+ updatedAt: now
310
+ };
311
+ mutations.push({ op: "set", collection: "settings", doc: asDoc(settings) });
312
+ } else if (adminId && !current.ownerUserId) {
313
+ mutations.push({ op: "patch", collection: "settings", id: "general", fields: { ownerUserId: adminId, updatedAt: now } });
314
+ }
315
+ if (mutations.length) await ctx.store.write(mutations, { message: "Install AgentCRM" });
316
+ if (opts.demo) report.demo = await seedDemoData(ctx, { ownerId: adminId ?? void 0, currency });
317
+ return report;
318
+ }
319
+ function mulberry32(seed) {
320
+ let a = seed >>> 0;
321
+ return () => {
322
+ a = a + 1831565813 >>> 0;
323
+ let t = a;
324
+ t = Math.imul(t ^ t >>> 15, t | 1);
325
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
326
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
327
+ };
328
+ }
329
+ const COMPANIES = [
330
+ ["Acme Robotics", "acmerobotics.com", "manufacturing", 420],
331
+ ["Globex", "globex.io", "software", 1200],
332
+ ["Initech", "initech.com", "software", 230],
333
+ ["Umbrella Health", "umbrellahealth.com", "healthcare", 3100],
334
+ ["Stark Logistics", "starklogistics.com", "other", 850],
335
+ ["Wayne Capital", "waynecapital.com", "finance", 640],
336
+ ["Hooli", "hooli.xyz", "software", 5400],
337
+ ["Pied Piper", "piedpiper.com", "software", 45],
338
+ ["Vandelay Imports", "vandelay.com", "retail", 75],
339
+ ["Soylent Foods", "soylent.co", "retail", 310],
340
+ ["Cyberdyne Systems", "cyberdyne.ai", "software", 980],
341
+ ["Tyrell Corp", "tyrell.com", "manufacturing", 2200],
342
+ ["Massive Dynamic", "massivedynamic.com", "consulting", 1600],
343
+ ["Wonka Industries", "wonka.com", "manufacturing", 520],
344
+ ["Aperture Labs", "aperture.io", "education", 190],
345
+ ["Oscorp", "oscorp.com", "healthcare", 1450],
346
+ ["Nakatomi Trading", "nakatomi.co.jp", "finance", 760],
347
+ ["Gringotts Bank", "gringotts.bank", "finance", 300],
348
+ ["Dunder Mifflin", "dundermifflin.com", "retail", 120],
349
+ ["Prestige Worldwide", "prestigeworldwide.com", "media", 12],
350
+ ["Bluth Company", "bluth.com", "real-estate", 85],
351
+ ["Sterling Cooper", "sterlingcooper.com", "media", 260],
352
+ ["Monarch Solutions", "monarch.solutions", "consulting", 58],
353
+ ["Northwind Traders", "northwind.com", "retail", 410],
354
+ ["Contoso", "contoso.com", "software", 3600],
355
+ ["Fabrikam", "fabrikam.com", "manufacturing", 900],
356
+ ["Lumon Industries", "lumon.com", "other", 1300],
357
+ ["Rekall", "rekall.com", "healthcare", 95],
358
+ ["Blue Sun", "bluesun.com", "media", 700],
359
+ ["Octan Energy", "octan.energy", "other", 2600]
360
+ ];
361
+ const FIRST = ["Ada", "Alan", "Grace", "Linus", "Margaret", "Dennis", "Barbara", "Ken", "Frances", "Tim", "Radia", "Vint", "Hedy", "John", "Katherine", "Guido", "Sophie", "Lucas", "Emma", "Hugo", "Léa", "Nathan", "Chloé", "Louis", "Camille", "Jules", "Inès", "Arthur", "Zoé", "Paul", "Mia", "Noah", "Olivia", "Liam", "Ava", "Ethan", "Isabella", "Mason", "Amelia", "Logan"];
362
+ const LAST = ["Lovelace", "Turing", "Hopper", "Torvalds", "Hamilton", "Ritchie", "Liskov", "Thompson", "Allen", "Berners-Lee", "Perlman", "Cerf", "Lamarr", "McCarthy", "Johnson", "van Rossum", "Martin", "Bernard", "Dubois", "Durand", "Lefebvre", "Moreau", "Laurent", "Simon", "Michel", "Garcia", "Roux", "Fournier", "Girard", "Bonnet", "Smith", "Brown", "Wilson", "Taylor", "Clark", "Lewis", "Walker", "Young", "King", "Wright"];
363
+ const TITLES = ["CEO", "CTO", "VP Sales", "Head of Marketing", "Procurement Manager", "Operations Director", "Product Manager", "Engineering Manager", "CFO", "Office Manager", "Account Executive", "Data Analyst"];
364
+ const CITIES = ["Paris", "London", "New York", "Berlin", "Lyon", "Madrid", "Amsterdam", "Montréal", "San Francisco", "Lisbon", "Brussels", "Toronto"];
365
+ const CITY_COUNTRY = {
366
+ Paris: ["France", "+33 1"],
367
+ London: ["United Kingdom", "+44 20"],
368
+ "New York": ["United States", "+1 212"],
369
+ Berlin: ["Germany", "+49 30"],
370
+ Lyon: ["France", "+33 4"],
371
+ Madrid: ["Spain", "+34 91"],
372
+ Amsterdam: ["Netherlands", "+31 20"],
373
+ Montréal: ["Canada", "+1 514"],
374
+ "San Francisco": ["United States", "+1 415"],
375
+ Lisbon: ["Portugal", "+351 21"],
376
+ Brussels: ["Belgium", "+32 2"],
377
+ Toronto: ["Canada", "+1 416"]
378
+ };
379
+ const DEAL_KINDS = ["Annual license", "Pilot", "Expansion", "Renewal", "Onboarding package", "Enterprise plan", "Support contract", "Consulting retainer"];
380
+ const LIFECYCLE = ["subscriber", "lead", "lead", "mql", "sql", "opportunity", "customer", "customer", "evangelist"];
381
+ const SOURCES = ["website", "referral", "event", "outbound", "partner", "ads", "social"];
382
+ const STAGES = [
383
+ ["new", 8],
384
+ ["qualified", 9],
385
+ ["proposal", 7],
386
+ ["negotiation", 5],
387
+ ["won", 7],
388
+ ["lost", 4]
389
+ ];
390
+ const LOST_REASONS = ["Budget", "Went with a competitor", "No decision", "Timing"];
391
+ const DAY = 864e5;
392
+ async function seedDemoData(ctx, opts = {}) {
393
+ assertCan(ctx, "records.write");
394
+ const rand = mulberry32(42);
395
+ const pick = (xs) => xs[Math.floor(rand() * xs.length)];
396
+ const now = ctx.now();
397
+ const today = new Date(now);
398
+ const isoDay = (offsetDays) => {
399
+ const d2 = new Date(today.getFullYear(), today.getMonth(), today.getDate() + offsetDays);
400
+ return `${d2.getFullYear()}-${String(d2.getMonth() + 1).padStart(2, "0")}-${String(d2.getDate()).padStart(2, "0")}`;
401
+ };
402
+ const mutations = [];
403
+ const firstCompanyId = seededId("demo:company:0");
404
+ if (ctx.store.get("records", firstCompanyId)) return { companies: 0, people: 0, deals: 0, activities: 0, users: 0 };
405
+ const teammates = [
406
+ { id: seededId("demo:user:sam"), email: "sam@example.com", name: "Sam Rivera", role: "member" },
407
+ { id: seededId("demo:user:alex"), email: "alex@example.com", name: "Alex Chen", role: "member" }
408
+ ].filter((u) => !usersOf(ctx.store).some((x) => normalizeEmail(x.email) === u.email));
409
+ for (const u of teammates) mutations.push({ op: "set", collection: "users", doc: asDoc(u) });
410
+ const owners = [opts.ownerId, ...teammates.map((u) => u.id), ...usersOf(ctx.store).map((u) => u.id)].filter((x) => !!x);
411
+ const ownerAt = (i) => owners.length ? owners[i % owners.length] : void 0;
412
+ const stamp = (daysAgo) => now - Math.round(daysAgo * DAY);
413
+ const rec = (id, object, fields, i, daysAgo) => ({
414
+ id,
415
+ object,
416
+ fields,
417
+ ...ownerAt(i) ? { ownerId: ownerAt(i) } : {},
418
+ createdAt: stamp(daysAgo),
419
+ updatedAt: stamp(Math.max(0, daysAgo - rand() * daysAgo)),
420
+ ...opts.ownerId ? { createdBy: opts.ownerId } : {}
421
+ });
422
+ const companies = COMPANIES.map(([name, domain, industry, employees], i) => {
423
+ const annualRevenue = Math.round(employees * (6e4 + rand() * 14e4) / 1e3) * 1e3;
424
+ const city = pick(CITIES);
425
+ rand();
426
+ const [country, dial] = CITY_COUNTRY[city];
427
+ return rec(seededId(`demo:company:${i}`), "companies", {
428
+ name,
429
+ domain: `https://${domain}`,
430
+ industry,
431
+ employees,
432
+ annualRevenue,
433
+ address: { city, country },
434
+ linkedin: `https://www.linkedin.com/company/${domain.split(".")[0]}`,
435
+ phone: `${dial} ${String(10 + i).padStart(2, "0")} ${String(Math.floor(rand() * 90) + 10)} ${String(Math.floor(rand() * 90) + 10)} ${String(Math.floor(rand() * 90) + 10)}`,
436
+ ...rand() < 0.3 ? { tags: [pick(["vip", "partner", "strategic", "smb", "enterprise"])] } : {}
437
+ }, i, 30 + rand() * 300);
438
+ });
439
+ const people = [];
440
+ for (let i = 0; i < 80; i++) {
441
+ const firstName = FIRST[i % FIRST.length];
442
+ const lastName = LAST[(i * 7 + Math.floor(i / FIRST.length)) % LAST.length];
443
+ const company = companies[i % companies.length];
444
+ const domain = String(company.fields.domain).replace(/^https:\/\//, "");
445
+ const slug = `${firstName}.${lastName}`.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^a-z.]/g, "");
446
+ people.push(
447
+ rec(seededId(`demo:person:${i}`), "people", {
448
+ firstName,
449
+ lastName,
450
+ name: `${firstName} ${lastName}`,
451
+ email: `${slug}@${domain}`,
452
+ phone: `+1 555 ${String(100 + i).padStart(3, "0")} ${String(1e3 + Math.floor(rand() * 8999))}`,
453
+ jobTitle: pick(TITLES),
454
+ company: company.id,
455
+ city: pick(CITIES),
456
+ lifecycleStage: pick(LIFECYCLE),
457
+ leadSource: pick(SOURCES),
458
+ ...rand() < 0.2 ? { tags: [pick(["decision-maker", "champion", "newsletter", "event-2024"])] } : {}
459
+ }, i, 10 + rand() * 240)
460
+ );
461
+ }
462
+ const deals = [];
463
+ let d = 0;
464
+ for (const [stage, count] of STAGES) {
465
+ for (let k = 0; k < count; k++, d++) {
466
+ const company = companies[d * 3 % companies.length];
467
+ const contacts = people.filter((p) => p.fields.company === company.id).slice(0, 1 + d % 2);
468
+ const prob = { new: 10, qualified: 25, proposal: 50, negotiation: 75, won: 100, lost: 0 }[stage];
469
+ const closeOffset = stage === "won" || stage === "lost" ? -Math.round(5 + rand() * 170) : Math.round(5 + rand() * 120);
470
+ deals.push(
471
+ rec(seededId(`demo:deal:${d}`), "deals", {
472
+ name: `${company.fields.name} — ${pick(DEAL_KINDS)}`,
473
+ amount: Math.round((2 + rand() * 98) * 1e3),
474
+ stage,
475
+ pipeline: "sales",
476
+ closeDate: isoDay(closeOffset),
477
+ probability: prob,
478
+ company: company.id,
479
+ ...contacts.length ? { contacts: contacts.map((c) => c.id) } : {},
480
+ ...stage === "lost" ? { lostReason: pick(LOST_REASONS) } : {}
481
+ }, d, 5 + rand() * 150)
482
+ );
483
+ }
484
+ }
485
+ const activities = [];
486
+ const titles = {
487
+ note: ["Discovery notes", "Pricing feedback", "Org chart", "Next steps", "Competitor mentioned"],
488
+ call: ["Intro call", "Follow-up call", "Pricing call", "Check-in"],
489
+ email: ["Sent proposal", "Follow-up email", "Shared case study", "Contract sent"],
490
+ meeting: ["Demo", "Kick-off meeting", "Quarterly business review", "On-site visit"],
491
+ task: ["Send proposal", "Schedule demo", "Follow up on contract", "Prepare QBR deck", "Call back", "Update pricing"]
492
+ };
493
+ const types = ["note", "call", "email", "meeting", "task"];
494
+ for (let i = 0; i < 150; i++) {
495
+ const type = types[i % types.length];
496
+ const deal = deals[i % deals.length];
497
+ const company = companies.find((c) => c.id === deal.fields.company);
498
+ const contactId = deal.fields.contacts?.[0];
499
+ const targets = [
500
+ { object: "deals", id: deal.id },
501
+ { object: "companies", id: company.id },
502
+ ...contactId ? [{ object: "people", id: contactId }] : []
503
+ ];
504
+ const daysAgo = rand() * 90;
505
+ const createdAt = stamp(daysAgo);
506
+ const a = {
507
+ id: seededId(`demo:activity:${i}`),
508
+ type,
509
+ title: pick(titles[type]),
510
+ ...type === "note" || rand() < 0.4 ? { body: `${pick(["Talked with", "Met", "Emailed"])} the team at ${company.fields.name}. ${pick(["Interested in the annual plan.", "Needs approval from finance.", "Asked for a security review.", "Comparing with two vendors.", "Wants to start next quarter."])}` } : {},
511
+ targets,
512
+ createdAt,
513
+ updatedAt: createdAt,
514
+ ...opts.ownerId ? { createdBy: ownerAt(i) ?? opts.ownerId } : {}
515
+ };
516
+ if (type === "task") {
517
+ const due = Math.round(-10 + rand() * 30);
518
+ a.dueAt = new Date(today.getFullYear(), today.getMonth(), today.getDate() + due, 9 + i % 8).getTime();
519
+ if (due < -3 && rand() < 0.7) a.doneAt = a.dueAt + DAY / 2;
520
+ if (ownerAt(i)) a.assigneeId = ownerAt(i);
521
+ }
522
+ if (type === "call") a.meta = { durationMin: 5 + Math.floor(rand() * 40) };
523
+ if (type === "meeting") a.meta = { startAt: createdAt, endAt: createdAt + 36e5 };
524
+ activities.push(a);
525
+ }
526
+ for (const r of [...companies, ...people, ...deals]) mutations.push({ op: "set", collection: "records", doc: asDoc(r) });
527
+ for (const a of activities) mutations.push({ op: "set", collection: "activities", doc: asDoc(a) });
528
+ await ctx.store.write(mutations, { message: "Add demo data" });
529
+ let files = 0;
530
+ try {
531
+ files = (await seedDemoFiles(ctx, { companies, people, activities, ownerId: opts.ownerId })).files;
532
+ } catch (err) {
533
+ ctx.log?.(`Demo files skipped: ${err.message}`);
534
+ }
535
+ return { companies: companies.length, people: people.length, deals: deals.length, activities: activities.length, users: teammates.length, files };
536
+ }
537
+ export {
538
+ DEFAULT_ADMIN_ID,
539
+ seedCrm,
540
+ seedDemoData
541
+ };