@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,476 @@
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
+ const OPTION_COLORS = ["gray", "blue", "green", "yellow", "orange", "red", "purple", "pink", "teal", "indigo"];
8
+ function colorAt(index) {
9
+ return OPTION_COLORS[(index % OPTION_COLORS.length + OPTION_COLORS.length) % OPTION_COLORS.length];
10
+ }
11
+ class FieldValidationError extends Error {
12
+ constructor(errors) {
13
+ super(errors.map((e) => e.message).join("; "));
14
+ this.errors = errors;
15
+ this.name = "FieldValidationError";
16
+ }
17
+ }
18
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
19
+ const PHONE_RE = /^\+?[0-9 ().\-/]{5,25}(\s*(x|ext\.?)\s*\d{1,6})?$/i;
20
+ const DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
21
+ const FILE_REF_RE = /^[A-Za-z0-9_-]{1,80}$/;
22
+ const TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "oui", "on", "x", "✓"]);
23
+ const FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "non", "off", ""]);
24
+ function isEmptyValue(value) {
25
+ if (value === void 0 || value === null) return true;
26
+ if (typeof value === "string") return value.trim() === "";
27
+ if (typeof value === "number") return Number.isNaN(value);
28
+ if (Array.isArray(value)) return value.length === 0;
29
+ if (typeof value === "object") return Object.values(value).every((v) => isEmptyValue(v));
30
+ return false;
31
+ }
32
+ function isEmptyFieldValue(field, value) {
33
+ if (field.type === "boolean") return value === void 0 || value === null;
34
+ if (field.type === "rating") return value === void 0 || value === null || value === 0;
35
+ return isEmptyValue(value);
36
+ }
37
+ function findOption(field, raw) {
38
+ if (raw === void 0 || raw === null) return void 0;
39
+ const s = String(raw).trim();
40
+ if (!s) return void 0;
41
+ const options = field.options ?? [];
42
+ const lower = s.toLowerCase();
43
+ return options.find((o) => o.id === s) ?? options.find((o) => o.label.toLowerCase() === lower) ?? options.find((o) => o.id.toLowerCase() === lower);
44
+ }
45
+ function optionLabel(field, id) {
46
+ return findOption(field, id)?.label ?? (id == null ? "" : String(id));
47
+ }
48
+ function splitList(raw) {
49
+ if (Array.isArray(raw)) return raw.flatMap((v) => v == null ? [] : [String(v).trim()]).filter(Boolean);
50
+ if (raw === void 0 || raw === null) return [];
51
+ return String(raw).split(/[,;|\n]/).map((s) => s.trim()).filter(Boolean);
52
+ }
53
+ function parseNumber(raw) {
54
+ if (typeof raw === "number") return Number.isFinite(raw) ? raw : null;
55
+ if (typeof raw !== "string") return null;
56
+ let s = raw.trim().replace(/[\s  ]/g, "").replace(/[€$£¥₹%]|[A-Z]{3}$/gi, "");
57
+ if (!s) return null;
58
+ let mult = 1;
59
+ const suffix = /([kmb])$/i.exec(s);
60
+ if (suffix) {
61
+ mult = { k: 1e3, m: 1e6, b: 1e9 }[suffix[1].toLowerCase()];
62
+ s = s.slice(0, -1);
63
+ }
64
+ const lastComma = s.lastIndexOf(",");
65
+ const lastDot = s.lastIndexOf(".");
66
+ if (lastComma > -1 && lastDot > -1) {
67
+ s = lastComma > lastDot ? s.replace(/\./g, "").replace(",", ".") : s.replace(/,/g, "");
68
+ } else if (lastComma > -1) {
69
+ s = /,\d{3}$/.test(s) && s.split(",").length >= 2 && !/^0,/.test(s) ? s.replace(/,/g, "") : s.replace(",", ".");
70
+ }
71
+ if (!/^[-+]?\d*\.?\d+(e[-+]?\d+)?$/i.test(s)) return null;
72
+ const n = Number(s) * mult;
73
+ return Number.isFinite(n) ? n : null;
74
+ }
75
+ function pad(n, w = 2) {
76
+ return String(n).padStart(w, "0");
77
+ }
78
+ function toIsoDate(d) {
79
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
80
+ }
81
+ function parseDateValue(raw, opts = {}) {
82
+ if (raw instanceof Date) return Number.isNaN(raw.getTime()) ? null : toIsoDate(raw);
83
+ if (typeof raw === "number") return Number.isFinite(raw) ? toIsoDate(new Date(raw)) : null;
84
+ if (typeof raw !== "string") return null;
85
+ const s = raw.trim();
86
+ if (!s) return null;
87
+ const iso = DATE_RE.exec(s);
88
+ if (iso) return validYmd(+iso[1], +iso[2], +iso[3]);
89
+ const slash = /^(\d{1,2})[/.-](\d{1,2})[/.-](\d{2,4})$/.exec(s);
90
+ if (slash) {
91
+ let a = +slash[1];
92
+ let b = +slash[2];
93
+ let y = +slash[3];
94
+ if (y < 100) y += 2e3;
95
+ const dayFirst = a > 12 ? true : b > 12 ? false : opts.dayFirst ?? false;
96
+ if (!dayFirst) [a, b] = [b, a];
97
+ return validYmd(y, b, a);
98
+ }
99
+ const t = Date.parse(s);
100
+ if (!Number.isNaN(t)) {
101
+ const prefix = /^(\d{4})-(\d{2})-(\d{2})T/.exec(s);
102
+ return prefix ? validYmd(+prefix[1], +prefix[2], +prefix[3]) : toIsoDate(new Date(t));
103
+ }
104
+ return null;
105
+ }
106
+ function validYmd(y, m, d) {
107
+ if (m < 1 || m > 12 || d < 1 || d > 31) return null;
108
+ const date = new Date(Date.UTC(y, m - 1, d));
109
+ if (date.getUTCMonth() !== m - 1) return null;
110
+ return `${String(y).padStart(4, "0")}-${pad(m)}-${pad(d)}`;
111
+ }
112
+ function parseDateTimeValue(raw) {
113
+ if (raw instanceof Date) return Number.isNaN(raw.getTime()) ? null : raw.getTime();
114
+ if (typeof raw === "number") return Number.isFinite(raw) ? raw : null;
115
+ if (typeof raw !== "string" || !raw.trim()) return null;
116
+ const s = raw.trim();
117
+ if (/^\d{10,13}$/.test(s)) return Number(s) * (s.length === 10 ? 1e3 : 1);
118
+ const date = parseDateValue(s);
119
+ const t = Date.parse(DATE_RE.test(s) ? `${s}T00:00:00` : s);
120
+ if (!Number.isNaN(t)) return t;
121
+ return date ? Date.parse(`${date}T00:00:00`) : null;
122
+ }
123
+ function dateValueToMs(value) {
124
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
125
+ if (typeof value === "string") {
126
+ const m = DATE_RE.exec(value);
127
+ if (m) return new Date(+m[1], +m[2] - 1, +m[3]).getTime();
128
+ const t = Date.parse(value);
129
+ return Number.isNaN(t) ? null : t;
130
+ }
131
+ if (value instanceof Date) return value.getTime();
132
+ return null;
133
+ }
134
+ function normalizeUrl(raw) {
135
+ const s = raw.trim();
136
+ if (!s) return null;
137
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(s) ? s : `https://${s}`;
138
+ try {
139
+ const u = new URL(withScheme);
140
+ if (!/^https?:$/.test(u.protocol) || !u.hostname.includes(".")) return null;
141
+ return withScheme.replace(/\/+$/, "") === `${u.protocol}//${u.host}` ? `${u.protocol}//${u.host}` : u.href.replace(/\/$/, "");
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+ function normalizeDomain(raw) {
147
+ if (typeof raw !== "string") return "";
148
+ let s = raw.trim().toLowerCase();
149
+ if (!s) return "";
150
+ s = s.replace(/^[a-z][a-z0-9+.-]*:\/\//, "").replace(/^www\./, "");
151
+ s = s.split(/[/?#]/)[0].split("@").pop() ?? "";
152
+ return s.replace(/:\d+$/, "").replace(/\.$/, "");
153
+ }
154
+ function normalizeEmail(raw) {
155
+ return typeof raw === "string" ? raw.trim().toLowerCase() : "";
156
+ }
157
+ const fail = (code, message) => ({ ok: false, code, message });
158
+ const ok = (value) => ({ ok: true, value });
159
+ function coerceFieldValue(field, raw, opts = {}) {
160
+ if (field.type !== "boolean" && (raw === void 0 || raw === null || typeof raw === "string" && raw.trim() === "" || Array.isArray(raw) && raw.length === 0)) {
161
+ return ok(void 0);
162
+ }
163
+ const label = field.label;
164
+ switch (field.type) {
165
+ case "text":
166
+ case "longText": {
167
+ if (typeof raw === "object") return fail("invalidText", `${label}: expected text.`);
168
+ const s = String(raw);
169
+ return ok(field.type === "text" ? s.trim() : s);
170
+ }
171
+ case "email": {
172
+ const s = normalizeEmail(String(raw));
173
+ return EMAIL_RE.test(s) ? ok(s) : fail("invalidEmail", `${label}: "${raw}" is not a valid email address.`);
174
+ }
175
+ case "phone": {
176
+ const s = String(raw).trim();
177
+ return PHONE_RE.test(s) ? ok(s) : fail("invalidPhone", `${label}: "${raw}" is not a valid phone number.`);
178
+ }
179
+ case "url": {
180
+ const s = normalizeUrl(String(raw));
181
+ return s ? ok(s) : fail("invalidUrl", `${label}: "${raw}" is not a valid URL.`);
182
+ }
183
+ case "number":
184
+ case "currency":
185
+ case "percent": {
186
+ const n = parseNumber(raw);
187
+ if (n === null) return fail("invalidNumber", `${label}: "${raw}" is not a number.`);
188
+ if (field.type === "percent" && typeof raw === "number" && raw > 0 && raw < 1 && !Number.isInteger(raw)) {
189
+ return ok(Math.round(raw * 1e4) / 100);
190
+ }
191
+ return ok(n);
192
+ }
193
+ case "rating": {
194
+ const n = parseNumber(raw);
195
+ if (n === null) return fail("invalidNumber", `${label}: "${raw}" is not a rating.`);
196
+ const r = Math.round(n);
197
+ if (r === 0) return ok(void 0);
198
+ return r >= 1 && r <= 5 ? ok(r) : fail("outOfRange", `${label}: rating must be between 1 and 5.`);
199
+ }
200
+ case "date": {
201
+ const d = parseDateValue(raw, opts);
202
+ return d ? ok(d) : fail("invalidDate", `${label}: "${raw}" is not a valid date.`);
203
+ }
204
+ case "datetime": {
205
+ const t = parseDateTimeValue(raw);
206
+ return t !== null ? ok(t) : fail("invalidDate", `${label}: "${raw}" is not a valid date/time.`);
207
+ }
208
+ case "boolean": {
209
+ if (raw === void 0 || raw === null) return ok(void 0);
210
+ if (typeof raw === "boolean") return ok(raw);
211
+ if (typeof raw === "number") return ok(raw !== 0);
212
+ const s = String(raw).trim().toLowerCase();
213
+ if (TRUE_WORDS.has(s)) return ok(true);
214
+ if (FALSE_WORDS.has(s)) return s === "" ? ok(void 0) : ok(false);
215
+ return fail("invalidBoolean", `${label}: "${raw}" is not yes/no.`);
216
+ }
217
+ case "select": {
218
+ const value = Array.isArray(raw) ? raw[0] : raw;
219
+ const opt = findOption(field, value);
220
+ if (opt) return ok(opt.id);
221
+ if (opts.unknownOptions === "keep") return ok(String(value).trim());
222
+ return fail("unknownOption", `${label}: "${value}" is not one of ${(field.options ?? []).map((o) => o.label).join(", ") || "(no options)"}.`);
223
+ }
224
+ case "multiSelect": {
225
+ const out = [];
226
+ for (const part of splitList(raw)) {
227
+ const opt = findOption(field, part);
228
+ if (opt) {
229
+ if (!out.includes(opt.id)) out.push(opt.id);
230
+ } else if (opts.unknownOptions === "keep") {
231
+ if (!out.includes(part)) out.push(part);
232
+ } else {
233
+ return fail("unknownOption", `${label}: "${part}" is not one of ${(field.options ?? []).map((o) => o.label).join(", ")}.`);
234
+ }
235
+ }
236
+ return ok(out.length ? out : void 0);
237
+ }
238
+ case "tags": {
239
+ const seen = /* @__PURE__ */ new Set();
240
+ const out = [];
241
+ for (const t of splitList(raw)) {
242
+ const key = t.toLowerCase();
243
+ if (!seen.has(key)) {
244
+ seen.add(key);
245
+ out.push(t);
246
+ }
247
+ }
248
+ return ok(out.length ? out : void 0);
249
+ }
250
+ case "relation": {
251
+ const many = field.relation?.cardinality === "many";
252
+ const ids = splitList(raw);
253
+ if (ids.some((id) => !/^[\w-]{1,64}$/.test(id))) return fail("invalidRelation", `${label}: expected record id(s).`);
254
+ if (many) return ok(ids.length ? [...new Set(ids)] : void 0);
255
+ if (ids.length > 1) return fail("invalidRelation", `${label}: only one record can be linked.`);
256
+ return ok(ids[0]);
257
+ }
258
+ case "user": {
259
+ const value = Array.isArray(raw) ? raw[0] : raw;
260
+ const s = String(value).trim();
261
+ return /^[\w@.+-]{1,128}$/.test(s) ? ok(s) : fail("invalidUser", `${label}: expected a user id.`);
262
+ }
263
+ case "image": {
264
+ const value = Array.isArray(raw) ? raw[0] : raw;
265
+ const s = String(value ?? "").trim();
266
+ return FILE_REF_RE.test(s) ? ok(s) : fail("invalidFile", `${label}: expected a file id (upload the image first).`);
267
+ }
268
+ case "files": {
269
+ const ids = splitList(raw);
270
+ if (ids.some((id) => !FILE_REF_RE.test(id))) return fail("invalidFile", `${label}: expected file id(s) (upload the files first).`);
271
+ return ok(ids.length ? [...new Set(ids)] : void 0);
272
+ }
273
+ case "address": {
274
+ if (typeof raw === "string") return ok({ street: raw.trim() });
275
+ if (typeof raw !== "object" || Array.isArray(raw)) return fail("invalidAddress", `${label}: expected an address.`);
276
+ const src = raw;
277
+ const out = {};
278
+ for (const k of ["street", "city", "postalCode", "region", "country"]) {
279
+ const v = src[k];
280
+ if (v !== void 0 && v !== null && String(v).trim()) out[k] = String(v).trim();
281
+ }
282
+ return ok(Object.keys(out).length ? out : void 0);
283
+ }
284
+ }
285
+ }
286
+ function validateRecordFields(object, input, opts = {}) {
287
+ const byId = new Map(object.fields.map((f) => [f.id, f]));
288
+ const values = {};
289
+ const errors = [];
290
+ for (const [key, raw] of Object.entries(input)) {
291
+ const field = byId.get(key);
292
+ if (!field) {
293
+ if (opts.unknownFields !== "drop") errors.push({ field: key, code: "unknownField", message: `Unknown field "${key}" on ${object.labelPlural}.` });
294
+ continue;
295
+ }
296
+ const res = coerceFieldValue(field, raw, opts);
297
+ if (!res.ok) errors.push({ field: key, code: res.code, message: res.message });
298
+ else values[key] = res.value;
299
+ }
300
+ for (const field of object.fields) {
301
+ if (!field.required) continue;
302
+ const provided = Object.prototype.hasOwnProperty.call(input, field.id);
303
+ if (opts.partial && !provided) continue;
304
+ if (isEmptyFieldValue(field, values[field.id]) && !errors.some((e) => e.field === field.id)) {
305
+ errors.push({ field: field.id, code: "required", message: `${field.label} is required.` });
306
+ }
307
+ }
308
+ return { ok: errors.length === 0, values, errors };
309
+ }
310
+ function defaultFieldValues(object) {
311
+ const out = {};
312
+ for (const f of object.fields) {
313
+ if (f.defaultValue === void 0) continue;
314
+ const res = coerceFieldValue(f, f.defaultValue);
315
+ if (res.ok && res.value !== void 0) out[f.id] = res.value;
316
+ }
317
+ return out;
318
+ }
319
+ function formatNumber(n, locale = "en-US", opts = {}) {
320
+ try {
321
+ return new Intl.NumberFormat(locale, { maximumFractionDigits: 2, ...opts }).format(n);
322
+ } catch {
323
+ return String(n);
324
+ }
325
+ }
326
+ function formatCurrency(n, currency = "USD", locale = "en-US", compact = false) {
327
+ try {
328
+ return new Intl.NumberFormat(locale, {
329
+ style: "currency",
330
+ currency,
331
+ maximumFractionDigits: compact || Number.isInteger(n) ? 0 : 2,
332
+ ...compact ? { notation: "compact" } : {}
333
+ }).format(n);
334
+ } catch {
335
+ return `${formatNumber(n, locale)} ${currency}`;
336
+ }
337
+ }
338
+ function formatAddress(a) {
339
+ if (!a || typeof a !== "object") return "";
340
+ const cityLine = [a.postalCode, a.city].filter(Boolean).join(" ");
341
+ return [a.street, cityLine, a.region, a.country].filter(Boolean).join(", ");
342
+ }
343
+ function formatFieldValue(field, value, ctx = {}) {
344
+ if (isEmptyFieldValue(field, value)) return "";
345
+ const locale = ctx.locale ?? "en-US";
346
+ switch (field.type) {
347
+ case "number":
348
+ return typeof value === "number" ? formatNumber(value, locale) : String(value);
349
+ case "currency":
350
+ return typeof value === "number" ? formatCurrency(value, field.currency ?? ctx.currency ?? "USD", locale) : String(value);
351
+ case "percent":
352
+ return typeof value === "number" ? `${formatNumber(value, locale)} %`.replace(" %", locale.startsWith("en") ? "%" : " %") : String(value);
353
+ case "rating":
354
+ return "★".repeat(Number(value) || 0);
355
+ case "date": {
356
+ const ms = dateValueToMs(value);
357
+ return ms === null ? String(value) : new Intl.DateTimeFormat(locale, { year: "numeric", month: "short", day: "numeric" }).format(ms);
358
+ }
359
+ case "datetime": {
360
+ const ms = dateValueToMs(value);
361
+ return ms === null ? String(value) : new Intl.DateTimeFormat(locale, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }).format(ms);
362
+ }
363
+ case "boolean":
364
+ return value ? "Yes" : "No";
365
+ case "select":
366
+ return optionLabel(field, value);
367
+ case "multiSelect":
368
+ return (Array.isArray(value) ? value : [value]).map((v) => optionLabel(field, v)).join(", ");
369
+ case "tags":
370
+ return (Array.isArray(value) ? value : [value]).map(String).join(", ");
371
+ case "relation": {
372
+ const ids = Array.isArray(value) ? value : [value];
373
+ const target = field.relation?.object ?? "";
374
+ return ids.map((id) => ctx.recordLabel?.(target, String(id)) ?? String(id)).join(", ");
375
+ }
376
+ case "user":
377
+ return ctx.userName?.(String(value)) ?? String(value);
378
+ case "address":
379
+ return formatAddress(value);
380
+ case "image":
381
+ case "files":
382
+ return (Array.isArray(value) ? value : [value]).map((id) => ctx.fileName?.(String(id)) ?? String(id)).join(", ");
383
+ default:
384
+ return String(value);
385
+ }
386
+ }
387
+ function comparableValue(field, value, ctx = {}) {
388
+ if (value === void 0 || value === null || value === "") return null;
389
+ if (!field) return typeof value === "number" ? value : String(value).toLowerCase();
390
+ switch (field.type) {
391
+ case "number":
392
+ case "currency":
393
+ case "percent":
394
+ case "rating":
395
+ return typeof value === "number" ? value : parseNumber(value);
396
+ case "date":
397
+ case "datetime":
398
+ return dateValueToMs(value);
399
+ case "boolean":
400
+ return value ? 1 : 0;
401
+ case "select": {
402
+ const idx = (field.options ?? []).findIndex((o) => o.id === value);
403
+ return idx === -1 ? String(value).toLowerCase() : idx;
404
+ }
405
+ default: {
406
+ const text = formatFieldValue(field, value, ctx);
407
+ return text ? text.toLowerCase() : null;
408
+ }
409
+ }
410
+ }
411
+ function getField(object, fieldId) {
412
+ return object.fields.find((f) => f.id === fieldId);
413
+ }
414
+ function recordLabel(object, record) {
415
+ if (!object) return record.id;
416
+ const primary = record.fields[object.labelField];
417
+ if (typeof primary === "string" && primary.trim()) return primary.trim();
418
+ const first = record.fields.firstName;
419
+ const last = record.fields.lastName;
420
+ const full = [first, last].filter((v) => typeof v === "string" && v.trim()).join(" ");
421
+ if (full) return full;
422
+ for (const key of ["email", "domain", "title", "subject"]) {
423
+ const v = record.fields[key];
424
+ if (typeof v === "string" && v.trim()) return v.trim();
425
+ }
426
+ return `${object.labelSingular} ${record.id.slice(-4)}`;
427
+ }
428
+ function canConvertType(from, to) {
429
+ if (from === to) return true;
430
+ if (from === "relation" || to === "relation") return false;
431
+ const fileTypes = ["image", "files"];
432
+ if (fileTypes.includes(from) || fileTypes.includes(to)) return fileTypes.includes(from) && fileTypes.includes(to);
433
+ return true;
434
+ }
435
+ function convertValue(from, to, value) {
436
+ if (isEmptyFieldValue(from, value)) return void 0;
437
+ if (from.type === to.type && from.type !== "select" && from.type !== "multiSelect") return value;
438
+ let human = value;
439
+ if (from.type === "select") human = optionLabel(from, value);
440
+ else if (from.type === "multiSelect") human = (Array.isArray(value) ? value : [value]).map((v) => optionLabel(from, v));
441
+ else if (from.type === "address") human = formatAddress(value);
442
+ else if (from.type === "datetime" && to.type === "date") human = typeof value === "number" ? toIsoDate(new Date(value)) : value;
443
+ else if (from.type === "boolean" && (to.type === "text" || to.type === "longText")) human = value ? "Yes" : "No";
444
+ if (to.type === "select" && Array.isArray(human)) human = human[0];
445
+ const res = coerceFieldValue(to, human, { unknownOptions: "fail" });
446
+ return res.ok ? res.value : void 0;
447
+ }
448
+ export {
449
+ FieldValidationError,
450
+ OPTION_COLORS,
451
+ canConvertType,
452
+ coerceFieldValue,
453
+ colorAt,
454
+ comparableValue,
455
+ convertValue,
456
+ dateValueToMs,
457
+ defaultFieldValues,
458
+ findOption,
459
+ formatAddress,
460
+ formatCurrency,
461
+ formatFieldValue,
462
+ formatNumber,
463
+ getField,
464
+ isEmptyFieldValue,
465
+ isEmptyValue,
466
+ normalizeDomain,
467
+ normalizeEmail,
468
+ normalizeUrl,
469
+ optionLabel,
470
+ parseDateTimeValue,
471
+ parseDateValue,
472
+ parseNumber,
473
+ recordLabel,
474
+ toIsoDate,
475
+ validateRecordFields
476
+ };
@@ -0,0 +1,158 @@
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
+ const DEFAULT_MAX_UPLOAD_MB = 25;
8
+ const IMAGE_MAX_DIMENSION = 2560;
9
+ const IMAGE_QUALITY = 0.85;
10
+ const THUMB_SIZE = 320;
11
+ const THUMB_SUFFIX = "_t";
12
+ const thumbIdOf = (id) => `${id}${THUMB_SUFFIX}`;
13
+ const MIME_BY_EXT = {
14
+ png: "image/png",
15
+ jpg: "image/jpeg",
16
+ jpeg: "image/jpeg",
17
+ gif: "image/gif",
18
+ webp: "image/webp",
19
+ avif: "image/avif",
20
+ svg: "image/svg+xml",
21
+ heic: "image/heic",
22
+ bmp: "image/bmp",
23
+ pdf: "application/pdf",
24
+ txt: "text/plain",
25
+ md: "text/markdown",
26
+ csv: "text/csv",
27
+ json: "application/json",
28
+ html: "text/html",
29
+ doc: "application/msword",
30
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
31
+ xls: "application/vnd.ms-excel",
32
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
33
+ ppt: "application/vnd.ms-powerpoint",
34
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
35
+ odt: "application/vnd.oasis.opendocument.text",
36
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
37
+ odp: "application/vnd.oasis.opendocument.presentation",
38
+ rtf: "application/rtf",
39
+ zip: "application/zip",
40
+ gz: "application/gzip",
41
+ mp3: "audio/mpeg",
42
+ wav: "audio/wav",
43
+ m4a: "audio/mp4",
44
+ mp4: "video/mp4",
45
+ mov: "video/quicktime",
46
+ webm: "video/webm",
47
+ eml: "message/rfc822",
48
+ ics: "text/calendar",
49
+ vcf: "text/vcard"
50
+ };
51
+ function extensionOf(name) {
52
+ const m = /\.([A-Za-z0-9]{1,8})$/.exec(name.trim());
53
+ return m ? m[1].toLowerCase() : "";
54
+ }
55
+ function guessMime(name, given) {
56
+ const g = (given ?? "").split(";")[0].trim().toLowerCase();
57
+ if (g && g !== "application/octet-stream") return g;
58
+ return MIME_BY_EXT[extensionOf(name)] ?? "application/octet-stream";
59
+ }
60
+ function isImageMime(mime) {
61
+ return /^image\/(png|jpe?g|gif|webp|avif|bmp|svg\+xml)$/.test(mime);
62
+ }
63
+ function isResizableImage(mime) {
64
+ return /^image\/(png|jpe?g|webp|bmp|avif)$/.test(mime);
65
+ }
66
+ function fileKind(mime, name = "") {
67
+ const m = guessMime(name, mime);
68
+ if (isImageMime(m)) return "image";
69
+ if (m === "application/pdf") return "pdf";
70
+ if (/spreadsheet|excel|csv/.test(m)) return "spreadsheet";
71
+ if (/presentation|powerpoint/.test(m)) return "presentation";
72
+ if (/wordprocessing|msword|opendocument\.text|rtf/.test(m)) return "document";
73
+ if (/zip|gzip|tar|compressed|7z|rar/.test(m)) return "archive";
74
+ if (m.startsWith("audio/")) return "audio";
75
+ if (m.startsWith("video/")) return "video";
76
+ if (m.startsWith("text/") || m === "application/json" || m === "message/rfc822") return "text";
77
+ return "other";
78
+ }
79
+ function isPreviewable(mime) {
80
+ return /^image\/(png|jpe?g|gif|webp|avif|bmp)$/.test(mime) || mime === "application/pdf" || mime === "text/plain" || mime.startsWith("video/") || mime.startsWith("audio/");
81
+ }
82
+ function formatBytes(n, locale = "en-US") {
83
+ if (!Number.isFinite(n) || n < 0) return "";
84
+ const units = ["B", "KB", "MB", "GB"];
85
+ let v = n;
86
+ let i = 0;
87
+ while (v >= 1024 && i < units.length - 1) {
88
+ v /= 1024;
89
+ i++;
90
+ }
91
+ const digits = i === 0 || v >= 100 ? 0 : 1;
92
+ return `${new Intl.NumberFormat(locale, { maximumFractionDigits: digits }).format(v)} ${units[i]}`;
93
+ }
94
+ function sanitizeFileName(name) {
95
+ const base = String(name ?? "").split(/[\\/]/).pop() ?? "";
96
+ const clean = base.replace(/[\u0000-\u001f\u007f<>:"|?*]/g, "").replace(/\s+/g, " ").trim().slice(0, 200);
97
+ return clean || "file";
98
+ }
99
+ async function sha256Hex(bytes) {
100
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
101
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
102
+ }
103
+ function fileImageMarkdown(name, id) {
104
+ return `![${name.replace(/[[\]]/g, "")}](file:${id})`;
105
+ }
106
+ function inlineFileIds(markdown) {
107
+ const out = [];
108
+ for (const m of (markdown ?? "").matchAll(/\]\(file:([A-Za-z0-9_-]{1,80})\)/g)) if (!out.includes(m[1])) out.push(m[1]);
109
+ return out;
110
+ }
111
+ function imageDimensions(bytes) {
112
+ const b = bytes;
113
+ const u16be = (i) => b[i] << 8 | b[i + 1];
114
+ const u16le = (i) => b[i] | b[i + 1] << 8;
115
+ const u32be = (i) => (b[i] << 24 | b[i + 1] << 16 | b[i + 2] << 8 | b[i + 3]) >>> 0;
116
+ if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) return { width: u32be(16), height: u32be(20) };
117
+ if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) return { width: u16le(6), height: u16le(8) };
118
+ if (b.length > 30 && String.fromCharCode(...b.slice(0, 4)) === "RIFF" && String.fromCharCode(...b.slice(8, 12)) === "WEBP") {
119
+ const kind = String.fromCharCode(...b.slice(12, 16));
120
+ if (kind === "VP8X") return { width: 1 + (b[24] | b[25] << 8 | b[26] << 16), height: 1 + (b[27] | b[28] << 8 | b[29] << 16) };
121
+ if (kind === "VP8 ") return { width: u16le(26) & 16383, height: u16le(28) & 16383 };
122
+ if (kind === "VP8L") {
123
+ const bits = b[21] | b[22] << 8 | b[23] << 16 | b[24] << 24;
124
+ return { width: (bits & 16383) + 1, height: (bits >> 14 & 16383) + 1 };
125
+ }
126
+ }
127
+ if (b.length > 4 && b[0] === 255 && b[1] === 216) {
128
+ let i = 2;
129
+ while (i + 9 < b.length) {
130
+ if (b[i] !== 255) return null;
131
+ const marker = b[i + 1];
132
+ const len = u16be(i + 2);
133
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) return { width: u16be(i + 7), height: u16be(i + 5) };
134
+ i += 2 + len;
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+ export {
140
+ DEFAULT_MAX_UPLOAD_MB,
141
+ IMAGE_MAX_DIMENSION,
142
+ IMAGE_QUALITY,
143
+ THUMB_SIZE,
144
+ THUMB_SUFFIX,
145
+ extensionOf,
146
+ fileImageMarkdown,
147
+ fileKind,
148
+ formatBytes,
149
+ guessMime,
150
+ imageDimensions,
151
+ inlineFileIds,
152
+ isImageMime,
153
+ isPreviewable,
154
+ isResizableImage,
155
+ sanitizeFileName,
156
+ sha256Hex,
157
+ thumbIdOf
158
+ };