@fleox-agent-crm/agentcrm 0.3.0 → 0.3.2

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 (73) hide show
  1. package/README.md +16 -16
  2. package/changelog.json +30 -0
  3. package/dist/admin/admin-files.json +8 -8
  4. package/dist/admin/assets/{chartsImpl-CE2akJat.js → chartsImpl-qlHW3VOv.js} +30 -30
  5. package/dist/admin/assets/firebase-U38f5kc9.js +2 -0
  6. package/dist/admin/assets/{gitJson-BulGh2KV.js → gitJson-wy2Tytfd.js} +1 -1
  7. package/dist/admin/assets/{index-DidVVK7L.js → index-BpaxrJx9.js} +265 -265
  8. package/dist/admin/assets/{index-hMXwHSvN.css → index-TcjySLVg.css} +1 -1
  9. package/dist/admin/assets/{lucide-react.min-DEDlHCPj.js → lucide-react.min-Djrg16BH.js} +1 -1
  10. package/dist/admin/assets/{sdk-CnrLnlRo.js → sdk-mu08RFtk.js} +1 -1
  11. package/dist/admin/index.html +2 -2
  12. package/dist/agent/agentcrm.mjs +334 -151
  13. package/dist/agent/chunks/{api-B6OqNVWp.mjs → api-Cz0Ubj-B.mjs} +4 -3
  14. package/dist/agent/chunks/api-Dwb4xz1k.mjs +27 -0
  15. package/dist/agent/chunks/{appearance-BaShelzt.mjs → appearance-BloHbiLA.mjs} +1 -1
  16. package/dist/agent/chunks/{automations-BWrvLqP6.mjs → automations-DwWruxmu.mjs} +3 -3
  17. package/dist/agent/chunks/{backup-ClSmxmFZ.mjs → backup-W3CjgkAJ.mjs} +16 -3
  18. package/dist/agent/chunks/{compact-CsFPaAAq.mjs → compact-BGHI92tm.mjs} +2 -2
  19. package/dist/agent/chunks/{exportCsv-B5hRvkNm.mjs → exportCsv-C6e6XEmB.mjs} +38 -3
  20. package/dist/agent/chunks/{extCheck--7lmVvl7.mjs → extCheck-GgHG1dYB.mjs} +16 -9
  21. package/dist/agent/chunks/{extPackage-BGl5Oqs8.mjs → extPackage-DuZ6GZhp.mjs} +2 -2
  22. package/dist/agent/chunks/{extScaffold-DqxI7R1e.mjs → extScaffold-Aqyjf1nr.mjs} +3 -3
  23. package/dist/agent/chunks/{extensions-BP1k60md.mjs → extensions-Bp72uZ9a.mjs} +19 -6
  24. package/dist/agent/chunks/{featureRegistry-C9nbVShp.mjs → featureRegistry-DSsZbhrh.mjs} +3 -3
  25. package/dist/agent/chunks/{files-q8Xu5bft.mjs → files-D1daDgY0.mjs} +1 -1
  26. package/dist/agent/chunks/{firebase-DxGVQV9E.mjs → firebase-BciZWdc6.mjs} +41 -17
  27. package/dist/agent/chunks/{forms-BKU5F9WF.mjs → forms-BfuWaQjA.mjs} +1 -1
  28. package/dist/agent/chunks/{hosting-Bme7LxPc.mjs → hosting-Dnn1k3cV.mjs} +1 -1
  29. package/dist/agent/chunks/{importCsv-DmE_sBch.mjs → importCsv-LVmMTaWU.mjs} +2 -2
  30. package/dist/agent/chunks/{index-DUSdTBkz.mjs → index-CNXq2Yk_.mjs} +1 -24
  31. package/dist/agent/chunks/index-Dio0aBWA.mjs +6920 -0
  32. package/dist/agent/chunks/{index-CRkw3_iF.mjs → index-rut-KC39.mjs} +1 -1
  33. package/dist/agent/chunks/{install-CpHoUFtL.mjs → install-BBTA4nMm.mjs} +16 -7
  34. package/dist/agent/chunks/{loader-C9nm3iwx.mjs → loader-MPD9iAvh.mjs} +20 -20
  35. package/dist/agent/chunks/{modules-CqdVQVJb.mjs → modules-clWdE-K2.mjs} +27 -2
  36. package/dist/agent/chunks/{nodeImport-BPCt5uMu.mjs → nodeImport-ylfRDvqt.mjs} +2 -2
  37. package/dist/agent/chunks/{package-DWbn3t2R.mjs → package-B7mI6HGi.mjs} +1 -1
  38. package/dist/agent/chunks/{papaparse-DjBYnQM2.mjs → papaparse-Df7i1dHr.mjs} +31 -7
  39. package/dist/agent/chunks/{pipelines-DCdRBNBV.mjs → pipelines-BAKpPk1w.mjs} +1 -1
  40. package/dist/agent/chunks/{plan-C0TBrU92.mjs → plan-BmTat6-T.mjs} +1 -1
  41. package/dist/agent/chunks/{records-DoqiT9xR.mjs → records-DcHFelq5.mjs} +5 -1
  42. package/dist/agent/chunks/{registry-Ceexawxl.mjs → registry-COfJrSmg.mjs} +18 -18
  43. package/dist/agent/chunks/{reports-BB59N6ke.mjs → reports-BACHGccj.mjs} +3 -3
  44. package/dist/agent/chunks/{runtime-DHo7ZEEO.mjs → runtime-C0Xf0Axi.mjs} +53 -32
  45. package/dist/agent/chunks/{schema-DlHt2PxI.mjs → schema-C4lSslXv.mjs} +10 -4
  46. package/dist/agent/chunks/{schema-7-4-qtSS.mjs → schema-DnLj8dUO.mjs} +10 -0
  47. package/dist/agent/chunks/{sdk-whvfg786.mjs → sdk-CYgNyuoK.mjs} +132 -7005
  48. package/dist/agent/chunks/{seed-CugPGYZL.mjs → seed-6MRmQQBR.mjs} +26 -8
  49. package/dist/agent/chunks/{server-ISwlDRCN.mjs → server-DC3t8r30.mjs} +5 -5
  50. package/dist/agent/chunks/{settings-BtvR_jdm.mjs → settings-Di8nzNEL.mjs} +35 -0
  51. package/dist/agent/chunks/{stats-CfeJssSS.mjs → stats-BfuLWhVV.mjs} +2 -2
  52. package/dist/agent/chunks/{tools-Bew8lxKx.mjs → tools-iSH-yMpl.mjs} +59 -19
  53. package/dist/agent/chunks/{views-fO_BM5U_.mjs → views-DH1n9Lrt.mjs} +1 -1
  54. package/dist/version.json +1 -1
  55. package/package.json +1 -1
  56. package/plugin/.claude-plugin/plugin.json +1 -1
  57. package/plugin/skills/agentcrm-admin/SKILL.md +67 -67
  58. package/plugin/skills/agentcrm-admin/references/appearance.md +1 -1
  59. package/plugin/skills/agentcrm-admin/references/records.md +5 -5
  60. package/plugin/skills/agentcrm-admin/references/schema.md +20 -20
  61. package/plugin/skills/agentcrm-extensions/SKILL.md +9 -9
  62. package/plugin/skills/agentcrm-extensions/references/module-recipes.md +2 -2
  63. package/plugin/skills/agentcrm-extensions/references/testing.md +16 -14
  64. package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +3 -3
  65. package/plugin/skills/agentcrm-install/SKILL.md +13 -13
  66. package/plugin/skills/agentcrm-install/references/firebase.md +1 -1
  67. package/plugin/skills/agentcrm-install/references/github.md +1 -1
  68. package/plugin/skills/agentcrm-install/references/mcp.md +3 -3
  69. package/plugin/skills/agentcrm-install/references/plans.md +1 -1
  70. package/plugin/skills/agentcrm-install/references/s3.md +1 -1
  71. package/plugin/skills/agentcrm-manage/SKILL.md +15 -15
  72. package/plugin/skills/agentcrm-sales/SKILL.md +18 -18
  73. package/dist/admin/assets/firebase-BHSUggRo.js +0 -2
@@ -89,9 +89,10 @@ export {
89
89
  disabledFromDoc as d,
90
90
  builtinModule as e,
91
91
  MODULE_CATEGORIES as f,
92
- RUNTIME_MODULES as g,
93
- RUNTIME_DIR as h,
92
+ compareVersions as g,
93
+ RUNTIME_MODULES as h,
94
94
  isCompatibleApiVersion as i,
95
- RUNTIME_GLOBAL as j,
95
+ RUNTIME_DIR as j,
96
+ RUNTIME_GLOBAL as k,
96
97
  modules as m
97
98
  };
@@ -0,0 +1,27 @@
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 DELETE_FIELD = Symbol("deleteField");
8
+ function errorCode(err) {
9
+ return err?.code ?? "";
10
+ }
11
+ function isPermissionDenied(err) {
12
+ const code = errorCode(err);
13
+ return code === "permission-denied" || code === "firestore/permission-denied";
14
+ }
15
+ const FIRESTORE_BATCH_LIMIT = 500;
16
+ function chunk(items, size) {
17
+ const out = [];
18
+ for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
19
+ return out;
20
+ }
21
+ export {
22
+ DELETE_FIELD as D,
23
+ FIRESTORE_BATCH_LIMIT as F,
24
+ chunk as c,
25
+ errorCode as e,
26
+ isPermissionDenied as i
27
+ };
@@ -4,7 +4,7 @@ import { dirname as __acrmDirname } from "node:path";
4
4
  const require2 = __acrmRequire(import.meta.url);
5
5
  const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
- import { m as mixHex, D as DEFAULT_ACCENT, r as resolveAppearance, A as APPEARANCE_DOC_ID, a as DEFAULT_APPEARANCE, v as validateAppearancePatch, b as validateImageDataUrl, c as APPEARANCE_KEYS } from "./schema-7-4-qtSS.mjs";
7
+ import { m as mixHex, D as DEFAULT_ACCENT, r as resolveAppearance, A as APPEARANCE_DOC_ID, a as DEFAULT_APPEARANCE, v as validateAppearancePatch, b as validateImageDataUrl, c as APPEARANCE_KEYS } from "./schema-DnLj8dUO.mjs";
8
8
  import { getExternalTheme } from "./themes-DPZFv3yc.mjs";
9
9
  import { assertCan, OpsError } from "./context-CxFfqS4D.mjs";
10
10
  import { extensionDocOf, extensionDocsOf } from "./store-5gqRSKXg.mjs";
@@ -8,13 +8,13 @@ import { toIsoDate, recordLabel, formatFieldValue, normalizeEmail } from "./fiel
8
8
  import { n as newId } from "./ids-CvnmrMJX.mjs";
9
9
  import { plugins } from "./pluginRegistry-DyHtlkcr.mjs";
10
10
  import { r as resolveField, m as matchesFilter } from "./query-C5nJB_3C.mjs";
11
- import { queryContext, setRecordOwner, updateRecord } from "./records-DoqiT9xR.mjs";
12
- import { getSettings } from "./settings-BtvR_jdm.mjs";
11
+ import { queryContext, setRecordOwner, updateRecord } from "./records-DcHFelq5.mjs";
12
+ import { getSettings } from "./settings-Di8nzNEL.mjs";
13
13
  import { userOf, automationRunsOf, asDoc, requireRecord, requireObject, automationsOf, objectOf, recordsOf, thaw, recordOf, usersOf } from "./store-5gqRSKXg.mjs";
14
14
  import { F as FILTER_OPS } from "../agentcrm.mjs";
15
15
  import { lastActivityAt, addNote, createTask } from "./activities-Cj_SIOay.mjs";
16
16
  import { OpsError, assertCan } from "./context-CxFfqS4D.mjs";
17
- import { pipelineOfRecord } from "./pipelines-DCdRBNBV.mjs";
17
+ import { pipelineOfRecord } from "./pipelines-BAKpPk1w.mjs";
18
18
  import { getAutomationAction, listAutomationActions } from "./extensionActions-D2HtpQz4.mjs";
19
19
  import { registerAutomationAction, subscribeAutomationActions } from "./extensionActions-D2HtpQz4.mjs";
20
20
  import "./standardObjects-CheqfDqa.mjs";
@@ -7,7 +7,7 @@ const __dirname = __acrmDirname(__filename);
7
7
  import { J as JSZip } from "./index-w03ReRs4.mjs";
8
8
  import { F as FILE_ID_RE } from "./ids-u-noYcy5.mjs";
9
9
  import { assertCan, OpsError } from "./context-CxFfqS4D.mjs";
10
- import { exportJson, importJson } from "./exportCsv-B5hRvkNm.mjs";
10
+ import { exportJson, importJson, planImportJson } from "./exportCsv-C6e6XEmB.mjs";
11
11
  import { filesOf } from "./store-5gqRSKXg.mjs";
12
12
  import "./_commonjsHelpers-ClciMIcB.mjs";
13
13
  import "stream";
@@ -15,11 +15,11 @@ import "events";
15
15
  import "buffer";
16
16
  import "util";
17
17
  import "./pluginRegistry-DyHtlkcr.mjs";
18
- import "./papaparse-DjBYnQM2.mjs";
18
+ import "./papaparse-Df7i1dHr.mjs";
19
19
  import "./fields-B0bitgmr.mjs";
20
20
  import "./query-C5nJB_3C.mjs";
21
21
  import "./types-BOy9PUl7.mjs";
22
- import "./records-DoqiT9xR.mjs";
22
+ import "./records-DcHFelq5.mjs";
23
23
  import "./ids-CvnmrMJX.mjs";
24
24
  import "./standardObjects-CheqfDqa.mjs";
25
25
  import "./relations-B9TynBmA.mjs";
@@ -94,10 +94,23 @@ async function readBackupZipInfo(input) {
94
94
  const files = Object.keys(zip.files).filter((p) => p.startsWith(BACKUP_FILES_DIR) && metaIds.has(p.slice(BACKUP_FILES_DIR.length, -".bin".length))).length;
95
95
  return { data, files };
96
96
  }
97
+ async function planBackupZip(ctx, input, opts = {}) {
98
+ assertCan(ctx, "settings.write");
99
+ let info;
100
+ try {
101
+ info = await readBackupZipInfo(input);
102
+ } catch (err) {
103
+ if (err instanceof SyntaxError) throw new OpsError("validation", `${BACKUP_DATA_FILE} is not valid JSON.`);
104
+ throw err;
105
+ }
106
+ if (info.files && !ctx.store.files) throw new OpsError("invalid", `The "${ctx.store.kind}" backend can't store files: restore the data JSON only.`);
107
+ return { ...planImportJson(ctx, info.data, opts), files: info.files, ...info.data.exportedAt ? { exportedAt: info.data.exportedAt } : {} };
108
+ }
97
109
  export {
98
110
  BACKUP_DATA_FILE,
99
111
  BACKUP_FILES_DIR,
100
112
  exportBackupZip,
101
113
  importBackupZip,
114
+ planBackupZip,
102
115
  readBackupZipInfo
103
116
  };
@@ -6,8 +6,8 @@ const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
7
  import { assertCan } from "./context-CxFfqS4D.mjs";
8
8
  import { b as fileCleanupMutations } from "./relations-B9TynBmA.mjs";
9
- import { purgeFiles } from "./files-q8Xu5bft.mjs";
10
- import { hardDeleteMutations } from "./records-DoqiT9xR.mjs";
9
+ import { purgeFiles } from "./files-D1daDgY0.mjs";
10
+ import { hardDeleteMutations } from "./records-DcHFelq5.mjs";
11
11
  import { recordsOf, activitiesOf, automationRunsOf } from "./store-5gqRSKXg.mjs";
12
12
  import "./pluginRegistry-DyHtlkcr.mjs";
13
13
  import "./fields-B0bitgmr.mjs";
@@ -4,12 +4,12 @@ import { dirname as __acrmDirname } from "node:path";
4
4
  const require2 = __acrmRequire(import.meta.url);
5
5
  const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
- import { P as Papa } from "./papaparse-DjBYnQM2.mjs";
7
+ import { P as Papa } from "./papaparse-Df7i1dHr.mjs";
8
8
  import { formatAddress, formatFieldValue } from "./fields-B0bitgmr.mjs";
9
9
  import { a as applySorts, b as applyFilters, r as resolveField, g as getRecordValue } from "./query-C5nJB_3C.mjs";
10
10
  import { COLLECTIONS } from "./types-BOy9PUl7.mjs";
11
11
  import { assertCan, OpsError } from "./context-CxFfqS4D.mjs";
12
- import { queryContext } from "./records-DoqiT9xR.mjs";
12
+ import { queryContext } from "./records-DcHFelq5.mjs";
13
13
  import { requireObject, recordsOf, userOf } from "./store-5gqRSKXg.mjs";
14
14
  import "./_commonjsHelpers-ClciMIcB.mjs";
15
15
  import "stream";
@@ -62,6 +62,40 @@ function exportJson(ctx, opts = {}) {
62
62
  collections: Object.fromEntries(wanted.map((c) => [c, ctx.store.list(c)]))
63
63
  };
64
64
  }
65
+ function planImportJson(ctx, data, opts = {}) {
66
+ assertCan(ctx, "settings.write");
67
+ if (data?.format !== "agentcrm-export") throw new OpsError("validation", "Not an AgentCRM JSON export.");
68
+ const mode = opts.mode ?? "merge";
69
+ const plan = { mode, collections: {}, added: 0, overwritten: 0, deleted: 0, warnings: [] };
70
+ for (const c of COLLECTIONS) {
71
+ const docs = data.collections[c];
72
+ if (!docs) continue;
73
+ const ids = new Set(docs.filter((d) => d && typeof d.id === "string").map((d) => d.id));
74
+ let added = 0;
75
+ let overwritten = 0;
76
+ for (const id of ids) ctx.store.get(c, id) ? overwritten++ : added++;
77
+ const deleted = mode === "replace" ? ctx.store.list(c).filter((d) => !ids.has(d.id)).length : 0;
78
+ plan.collections[c] = { added, overwritten, deleted };
79
+ plan.added += added;
80
+ plan.overwritten += overwritten;
81
+ plan.deleted += deleted;
82
+ }
83
+ const here = ctx.store.get("settings", "general");
84
+ const there = (data.collections.settings ?? []).find((d) => d.id === "general");
85
+ if (here?.name && there?.name && here.name !== there.name) {
86
+ plan.sourceName = there.name;
87
+ plan.warnings.push(`This backup comes from another CRM ("${there.name}"): its settings, users and schema replace the ones with the same ids here (this CRM becomes "${there.name}").`);
88
+ }
89
+ const users = data.collections.users ?? [];
90
+ const replacedUsers = users.filter((u) => {
91
+ const cur = ctx.store.get("users", u.id);
92
+ return cur?.email && u.email && cur.email.toLowerCase() !== u.email.toLowerCase();
93
+ });
94
+ if (replacedUsers.length) {
95
+ plan.warnings.push(`${replacedUsers.length} user(s) of this CRM are replaced by different people with the same id: ${replacedUsers.map((u) => `${String(ctx.store.get("users", u.id).email)} → ${String(u.email)}`).join(", ")}.`);
96
+ }
97
+ return plan;
98
+ }
65
99
  async function importJson(ctx, data, opts = {}) {
66
100
  assertCan(ctx, "settings.write");
67
101
  if (data?.format !== "agentcrm-export") throw new OpsError("validation", "Not an AgentCRM JSON export.");
@@ -91,5 +125,6 @@ async function importJson(ctx, data, opts = {}) {
91
125
  export {
92
126
  exportCsv,
93
127
  exportJson,
94
- importJson
128
+ importJson,
129
+ planImportJson
95
130
  };
@@ -4,8 +4,8 @@ import { dirname as __acrmDirname } from "node:path";
4
4
  const require2 = __acrmRequire(import.meta.url);
5
5
  const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
- import { b as EXTENSION_MIN_API_VERSION, E as EXTENSION_API_VERSION, a as EXTENSION_LIMITS, R as RUNTIME_SPECIFIERS } from "./api-B6OqNVWp.mjs";
8
- import { bundleImports, validateManifest, ExtensionPackageError } from "./package-DWbn3t2R.mjs";
7
+ import { b as EXTENSION_MIN_API_VERSION, E as EXTENSION_API_VERSION, a as EXTENSION_LIMITS, R as RUNTIME_SPECIFIERS } from "./api-Cz0Ubj-B.mjs";
8
+ import { bundleImports, validateManifest, ExtensionPackageError } from "./package-B7mI6HGi.mjs";
9
9
  import { n as nodeImportBundle } from "../agentcrm.mjs";
10
10
  import "./index-w03ReRs4.mjs";
11
11
  import "./_commonjsHelpers-ClciMIcB.mjs";
@@ -77,26 +77,31 @@ async function checkExtension(raw, opts = {}) {
77
77
  }
78
78
  if (manifest?.style && style === void 0) errors.push({ code: "missingStyle", message: 'manifest.json declares "style": "style.css" but there is no style.css.', hint: "Add src/style.css (projects) or style.css next to bundle.js." });
79
79
  if (style !== void 0 && bytes(style) > EXTENSION_LIMITS.style) errors.push({ code: "tooLarge", message: `style.css is larger than ${EXTENSION_LIMITS.style / 1024} KB.` });
80
+ if (style !== void 0) cssWarnings(style, "style.css", warnings);
80
81
  for (const f of raw.ignored) warnings.push({ code: "ignored", message: `${f} is not part of the package (only manifest.json, bundle.js and style.css are).${/style\.css$/.test(f) ? ' Declare "style": "style.css" in manifest.json to ship it.' : ""}` });
81
82
  if (!errors.length && manifest && bundle) await loadInSandbox(report, manifest, { "bundle.js": bundle, ...style ? { "style.css": style } : {} }, opts);
82
83
  report.valid = errors.length === 0;
83
84
  return report;
84
85
  }
86
+ function cssWarnings(css, label, warnings) {
87
+ if (/@import\s+(?:url\(\s*)?["']?\s*(?:https?:)?\/\//i.test(css)) warnings.push({ code: "css", message: `${label} @imports a remote stylesheet: every user's browser would fetch it.`, hint: `Inline the CSS; for fonts set the theme's "font" (the CRM loads Google Fonts itself).` });
88
+ if (/<\/?(?:style|script)/i.test(css)) warnings.push({ code: "css", message: `${label} contains <style> / <script> tags: it must be plain CSS.` });
89
+ }
85
90
  async function loadInSandbox(report, manifest, files, opts) {
86
91
  const importBundle = await nodeImportBundle();
87
92
  const [{ createMemoryStore }, { createOpsContext }, { createPluginRegistry, plugins }, { seedCrm }, { usersOf }, loader, registry, aiTools, actions, features, themes, schema] = await Promise.all([
88
93
  import("./memory-WNR3VHUJ.mjs"),
89
94
  import("./context-CxFfqS4D.mjs"),
90
95
  import("./pluginRegistry-DyHtlkcr.mjs"),
91
- import("./seed-CugPGYZL.mjs"),
96
+ import("./seed-6MRmQQBR.mjs"),
92
97
  import("./store-5gqRSKXg.mjs"),
93
- import("./loader-C9nm3iwx.mjs"),
94
- import("./registry-Ceexawxl.mjs"),
95
- import("./tools-Bew8lxKx.mjs"),
98
+ import("./loader-MPD9iAvh.mjs"),
99
+ import("./registry-COfJrSmg.mjs"),
100
+ import("./tools-iSH-yMpl.mjs"),
96
101
  import("./extensionActions-D2HtpQz4.mjs"),
97
- import("./featureRegistry-C9nbVShp.mjs"),
102
+ import("./featureRegistry-DSsZbhrh.mjs"),
98
103
  import("./themes-DPZFv3yc.mjs"),
99
- import("./schema-7-4-qtSS.mjs").then((n) => n.s)
104
+ import("./schema-DnLj8dUO.mjs").then((n) => n.s)
100
105
  ]);
101
106
  const store = createMemoryStore();
102
107
  await store.ready();
@@ -107,7 +112,7 @@ async function loadInSandbox(report, manifest, files, opts) {
107
112
  const now = Date.now();
108
113
  const doc = { id: manifest.id, kind: manifest.kind, manifest, enabled: true, files, installedAt: now, updatedAt: now };
109
114
  await store.write([{ op: "set", collection: "extensions", doc }]);
110
- const { BUILTIN_MODULES } = await import("./api-B6OqNVWp.mjs").then((n) => n.m);
115
+ const { BUILTIN_MODULES } = await import("./api-Cz0Ubj-B.mjs").then((n) => n.m);
111
116
  const missingDeps = (manifest.dependsOn ?? []).filter((d) => !BUILTIN_MODULES.some((b) => b.id === d));
112
117
  for (const d of missingDeps) report.warnings.push({ code: "dependency", message: `Depends on "${d}", which is not a built-in module: install that extension first on the real CRM.` });
113
118
  const toolsBefore = new Set(aiTools.listAiTools().map((t) => t.name));
@@ -131,6 +136,8 @@ async function loadInSandbox(report, manifest, files, opts) {
131
136
  const { errors } = schema.validateAppearancePatch(theme.appearance);
132
137
  for (const e of errors) report.errors.push({ code: "appearance", message: `Theme appearance: ${e}` });
133
138
  for (const k of ["logo", "favicon", "themeId", "preset"]) if (k in (theme.appearance ?? {})) report.warnings.push({ code: "appearance", message: `Theme appearance sets "${k}" — themes should leave it to the workspace.` });
139
+ const ownCss = (theme.css ?? "").replace(files["style.css"] ?? "", "");
140
+ if (ownCss.trim()) cssWarnings(ownCss, "The theme's css", report.warnings);
134
141
  report.theme = { name: theme.name, appearance: { ...theme.appearance }, css: bytes(theme.css) };
135
142
  return;
136
143
  }
@@ -6,8 +6,8 @@ const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
7
  import { existsSync, statSync, readFileSync } from "node:fs";
8
8
  import { resolve, join } from "node:path";
9
- import { a as EXTENSION_LIMITS } from "./api-B6OqNVWp.mjs";
10
- import { validatePackage, ExtensionPackageError } from "./package-DWbn3t2R.mjs";
9
+ import { a as EXTENSION_LIMITS } from "./api-Cz0Ubj-B.mjs";
10
+ import { validatePackage, ExtensionPackageError } from "./package-B7mI6HGi.mjs";
11
11
  import { C as CliError } from "../agentcrm.mjs";
12
12
  import "./index-w03ReRs4.mjs";
13
13
  import "./_commonjsHelpers-ClciMIcB.mjs";
@@ -6,8 +6,8 @@ const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
7
  import { existsSync, mkdirSync, writeFileSync, copyFileSync } from "node:fs";
8
8
  import { join, dirname } from "node:path";
9
- import { E as EXTENSION_API_VERSION, R as RUNTIME_SPECIFIERS } from "./api-B6OqNVWp.mjs";
10
- import { EXTENSION_ID_RE, RESERVED_EXTENSION_IDS } from "./package-DWbn3t2R.mjs";
9
+ import { E as EXTENSION_API_VERSION, R as RUNTIME_SPECIFIERS } from "./api-Cz0Ubj-B.mjs";
10
+ import { EXTENSION_ID_RE, RESERVED_EXTENSION_IDS } from "./package-B7mI6HGi.mjs";
11
11
  import { C as CliError, r as readVersionInfo, p as packageRoot, o as ownPackageRoot, P as PACKAGE_NAME } from "../agentcrm.mjs";
12
12
  import "./index-w03ReRs4.mjs";
13
13
  import "./_commonjsHelpers-ClciMIcB.mjs";
@@ -243,7 +243,7 @@ Or upload the ZIP in the admin: Settings → Modules → Installed extensions.
243
243
  - \`types/agentcrm-runtime.d.ts\` — the runtime API typings (copied from the CLI; refresh with \`${cli} ext new ${kind} ${id} --dir /tmp/x\` and copy the file).
244
244
  - \`vite.config.ts\` — keeps react, react-i18next, lucide-react and @agent-crm/runtime external (the admin provides them).
245
245
 
246
- Docs: https://github.com/fleox/agentcrm/blob/main/docs/${kind === "module" ? "creating-a-module.md" : "creating-a-theme.md"}
246
+ Docs: https://github.com/fleox/agentcrm/blob/master/docs/${kind === "module" ? "creating-a-module.md" : "creating-a-theme.md"}
247
247
  `;
248
248
  }
249
249
  function scaffoldExtension(opts) {
@@ -4,10 +4,10 @@ import { dirname as __acrmDirname } from "node:path";
4
4
  const require2 = __acrmRequire(import.meta.url);
5
5
  const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
- import { a as EXTENSION_LIMITS, i as isCompatibleApiVersion } from "./api-B6OqNVWp.mjs";
8
- import { validatePackage, ExtensionPackageError } from "./package-DWbn3t2R.mjs";
7
+ import { a as EXTENSION_LIMITS, g as compareVersions, i as isCompatibleApiVersion } from "./api-Cz0Ubj-B.mjs";
8
+ import { validatePackage, ExtensionPackageError } from "./package-B7mI6HGi.mjs";
9
9
  import { assertCan, OpsError } from "./context-CxFfqS4D.mjs";
10
- import { moduleStates, setModuleEnabled } from "./modules-CqdVQVJb.mjs";
10
+ import { moduleStates, setModuleEnabled } from "./modules-clWdE-K2.mjs";
11
11
  import { extensionDocOf, asDoc, extensionDocsOf } from "./store-5gqRSKXg.mjs";
12
12
  import "./index-w03ReRs4.mjs";
13
13
  import "./_commonjsHelpers-ClciMIcB.mjs";
@@ -16,7 +16,7 @@ import "events";
16
16
  import "buffer";
17
17
  import "util";
18
18
  import "./pluginRegistry-DyHtlkcr.mjs";
19
- import "./settings-BtvR_jdm.mjs";
19
+ import "./settings-Di8nzNEL.mjs";
20
20
  import "./runtimeConfig-CCOFg1RM.mjs";
21
21
  const bytes = (s) => s ? new TextEncoder().encode(s).length : 0;
22
22
  function summarizeExtension(doc) {
@@ -90,6 +90,11 @@ async function installExtension(ctx, pkg, opts = {}) {
90
90
  warnings.push(`Installed disabled: it needs ${missing.join(", ")} (${missing.map((d) => states.has(d) ? "disabled" : "not installed").join(", ")}).`);
91
91
  }
92
92
  }
93
+ if (existing) {
94
+ const cmp = compareVersions(manifest.version, existing.manifest.version);
95
+ if (cmp < 0) warnings.push(`Downgrade: ${existing.manifest.version} → ${manifest.version}.`);
96
+ else if (cmp === 0) warnings.push(`Version ${manifest.version} is already installed: its files are replaced (bump "version" for a new release).`);
97
+ }
93
98
  const now = ctx.now();
94
99
  const doc = {
95
100
  id: manifest.id,
@@ -122,11 +127,19 @@ async function setExtensionEnabled(ctx, id, enabled, opts = {}) {
122
127
  await setModuleEnabled(ctx, id, enabled, opts);
123
128
  } else if (doc.enabled !== enabled) {
124
129
  const mutations = [{ op: "patch", collection: "extensions", id, fields: { enabled, updatedAt: ctx.now() } }];
125
- if (!enabled && ctx.store.get("config", "appearance")?.themeId === id) mutations.push({ op: "patch", collection: "config", id: "appearance", fields: { themeId: void 0 } });
130
+ if (!enabled) mutations.push(...clearThemeSelection(ctx, id));
126
131
  await ctx.store.write(mutations, { message: `${enabled ? "Enable" : "Disable"} theme ${id}` });
127
132
  }
128
133
  return summarizeExtension(requireExtension(ctx, id));
129
134
  }
135
+ function clearThemeSelection(ctx, id) {
136
+ const appearance = ctx.store.get("config", "appearance");
137
+ if (!appearance) return [];
138
+ const fields = {};
139
+ if (appearance.themeId === id) fields.themeId = void 0;
140
+ if (appearance.preset === id) fields.preset = void 0;
141
+ return Object.keys(fields).length ? [{ op: "patch", collection: "config", id: "appearance", fields }] : [];
142
+ }
130
143
  async function uninstallExtension(ctx, id, opts = {}) {
131
144
  assertCan(ctx, "extensions.write");
132
145
  const doc = requireExtension(ctx, id);
@@ -135,7 +148,7 @@ async function uninstallExtension(ctx, id, opts = {}) {
135
148
  if (dependents.length) throw new OpsError("conflict", `${dependents.map((d) => `"${d.name}"`).join(", ")} depend${dependents.length > 1 ? "" : "s"} on "${doc.manifest.name}". Disable or uninstall ${dependents.length > 1 ? "them" : "it"} first.`, { dependents: dependents.map((d) => d.id) });
136
149
  }
137
150
  const mutations = [{ op: "delete", collection: "extensions", id }];
138
- if (ctx.store.get("config", "appearance")?.themeId === id) mutations.push({ op: "patch", collection: "config", id: "appearance", fields: { themeId: void 0 } });
151
+ mutations.push(...clearThemeSelection(ctx, id));
139
152
  await ctx.store.write(mutations, { message: `Uninstall extension ${id}` });
140
153
  ctx.log?.(`extension ${id} uninstalled`);
141
154
  return { id, removed: true };
@@ -6,12 +6,12 @@ const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
7
  import { r as reactExports } from "./index-Bnmav-Oh.mjs";
8
8
  import { r as registerI18nNamespace } from "./index-BM8qeGOb.mjs";
9
- import { moduleStates } from "./modules-CqdVQVJb.mjs";
9
+ import { moduleStates } from "./modules-clWdE-K2.mjs";
10
10
  import "./_commonjsHelpers-ClciMIcB.mjs";
11
- import "./api-B6OqNVWp.mjs";
11
+ import "./api-Cz0Ubj-B.mjs";
12
12
  import "./context-CxFfqS4D.mjs";
13
13
  import "./pluginRegistry-DyHtlkcr.mjs";
14
- import "./settings-BtvR_jdm.mjs";
14
+ import "./settings-Di8nzNEL.mjs";
15
15
  import "./store-5gqRSKXg.mjs";
16
16
  import "./runtimeConfig-CCOFg1RM.mjs";
17
17
  let builtins = [];
@@ -11,7 +11,7 @@ import { m as mapLimit } from "./http-Bk9ICb7I.mjs";
11
11
  import { assertCan, OpsError, actorId } from "./context-CxFfqS4D.mjs";
12
12
  import { s as sameTarget, e as fileFieldIds, g as fileIdsIn, f as fileTargetPatch, h as referencedFileIds } from "./relations-B9TynBmA.mjs";
13
13
  import { timelineScope } from "./activities-Cj_SIOay.mjs";
14
- import { updateRecord } from "./records-DoqiT9xR.mjs";
14
+ import { updateRecord } from "./records-DcHFelq5.mjs";
15
15
  import { fileOf, requireFile, filesOf, recordOf, activitiesOf, settingsDoc, asDoc, requireRecord, objectOf, requireObject, recordsOf } from "./store-5gqRSKXg.mjs";
16
16
  import "./index-BM8qeGOb.mjs";
17
17
  import "./pluginRegistry-DyHtlkcr.mjs";
@@ -7,6 +7,7 @@ const __dirname = __acrmDirname(__filename);
7
7
  import "./jsx-runtime-Ca2uwGDr.mjs";
8
8
  import { r as reactExports } from "./index-Bnmav-Oh.mjs";
9
9
  import { c as createStoreCore, p as plainCopy, e as emptySnapshot, n as normalizeDoc } from "./core-Dy7vY9H6.mjs";
10
+ import { i as isPermissionDenied, D as DELETE_FIELD, e as errorCode, c as chunk, F as FIRESTORE_BATCH_LIMIT } from "./api-Dwb4xz1k.mjs";
10
11
  import { w as withFileStore, F as FileStoreUnavailableError } from "./index-RCmo-uld.mjs";
11
12
  import { COLLECTIONS } from "./types-BOy9PUl7.mjs";
12
13
  import { a as assertFileId } from "./ids-u-noYcy5.mjs";
@@ -16,20 +17,6 @@ function registerAuthAdapter(backend, adapter) {
16
17
  adapters.set(backend, adapter);
17
18
  }
18
19
  reactExports.createContext(null);
19
- const DELETE_FIELD = Symbol("deleteField");
20
- function errorCode(err) {
21
- return err?.code ?? "";
22
- }
23
- function isPermissionDenied(err) {
24
- const code = errorCode(err);
25
- return code === "permission-denied" || code === "firestore/permission-denied";
26
- }
27
- const FIRESTORE_BATCH_LIMIT = 500;
28
- function chunk(items, size) {
29
- const out = [];
30
- for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
31
- return out;
32
- }
33
20
  const INVITES_COLLECTION = "invites";
34
21
  const EXTENSION_CHUNKS_COLLECTION = "extensionChunks";
35
22
  const EXTENSION_CHUNK_CHARS = 3e5;
@@ -410,7 +397,7 @@ function firebaseApiFor(config) {
410
397
  const key = config.firebase.projectId ?? "default";
411
398
  let api = apis.get(key);
412
399
  if (!api) {
413
- api = import("./sdk-whvfg786.mjs").then((n) => n.s).then((m) => m.createSdkFirebaseApi(config.firebase));
400
+ api = import("./sdk-CYgNyuoK.mjs").then((m) => m.createSdkFirebaseApi(config.firebase));
414
401
  apis.set(key, api);
415
402
  api.catch(() => apis.delete(key));
416
403
  }
@@ -422,11 +409,46 @@ registerAuthAdapter("firebase", {
422
409
  await activeApi?.auth.signOut();
423
410
  }
424
411
  });
425
- async function createFirebaseStore(config, _opts = {}) {
412
+ function loginOf(secrets) {
413
+ const email = typeof secrets?.email === "string" ? secrets.email.trim() : "";
414
+ const password = typeof secrets?.password === "string" ? secrets.password : "";
415
+ return email && password ? { email, password } : null;
416
+ }
417
+ async function signInWithSecrets(api, secrets) {
418
+ const login = loginOf(secrets);
419
+ if (!login) return null;
420
+ const current = await api.auth.currentUser();
421
+ if (current?.email && current.email.toLowerCase() === login.email.toLowerCase()) return current;
422
+ if (current) await api.auth.signOut();
423
+ return api.auth.signInWithPassword(login.email, login.password);
424
+ }
425
+ async function checkFirebaseAccess(api, secrets, projectId) {
426
+ const login = loginOf(secrets);
427
+ if (!login) return { ok: false, detail: "the Firebase login (email + password) is missing", existing: false };
428
+ let user;
429
+ try {
430
+ user = await signInWithSecrets(api, secrets);
431
+ } catch (err) {
432
+ const code = err.code ?? "";
433
+ return { ok: false, detail: `Firebase refused the login of ${login.email} (${code || err.message}): check the address, the password and that Email/Password sign-in is enabled`, existing: false };
434
+ }
435
+ if (!user?.emailVerified) {
436
+ return { ok: false, detail: `${login.email} is not verified yet: open the verification email Firebase sent to it (the rules refuse unverified addresses), then retry`, existing: false };
437
+ }
438
+ try {
439
+ const settings = await api.getDoc("settings", "general");
440
+ return { ok: true, detail: `Firebase project ${projectId}: signed in as ${login.email}, rules accept it${settings ? " — existing CRM: it is kept, only what's missing is seeded" : ""}`, existing: !!settings };
441
+ } catch (err) {
442
+ if (!isPermissionDenied(err)) throw err;
443
+ return { ok: false, detail: `Firestore refused ${login.email}: publish the AgentCRM rules with this address as bootstrap admin (\`agentcrm options --firebase-rules ${login.email}\`), or invite it`, existing: false };
444
+ }
445
+ }
446
+ async function createFirebaseStore(config, opts = {}) {
426
447
  const cfg = config;
427
448
  if (!cfg.firebase?.apiKey || !cfg.firebase.projectId) throw new Error("The Firebase web config (apiKey, projectId) is missing.");
428
449
  const api = await firebaseApiFor(cfg);
429
450
  activeApi = api;
451
+ await signInWithSecrets(api, opts.secrets);
430
452
  return createFirebaseStoreWith({ api });
431
453
  }
432
454
  export {
@@ -440,6 +462,7 @@ export {
440
462
  STORAGE_FILES_DIR,
441
463
  STORAGE_NOT_ENABLED,
442
464
  assembleExtensionDocs,
465
+ checkFirebaseAccess,
443
466
  chunk,
444
467
  createFirebaseStore,
445
468
  createFirebaseStoreWith,
@@ -450,5 +473,6 @@ export {
450
473
  inviteOps,
451
474
  isFirebaseStore,
452
475
  isPermissionDenied,
453
- mutationOps
476
+ mutationOps,
477
+ signInWithSecrets
454
478
  };
@@ -8,7 +8,7 @@ import { normalizeEmail, isEmptyFieldValue, normalizeDomain, formatFieldValue, r
8
8
  import { n as newId } from "./ids-CvnmrMJX.mjs";
9
9
  import { addActivity } from "./activities-Cj_SIOay.mjs";
10
10
  import { assertCan, OpsError } from "./context-CxFfqS4D.mjs";
11
- import { createRecord, updateRecord, queryContext } from "./records-DoqiT9xR.mjs";
11
+ import { createRecord, updateRecord, queryContext } from "./records-DcHFelq5.mjs";
12
12
  import { formsOf, requireObject, asDoc, objectOf, thaw, userOf, recordsOf, activitiesOf } from "./store-5gqRSKXg.mjs";
13
13
  import "./query-C5nJB_3C.mjs";
14
14
  import "../agentcrm.mjs";
@@ -8,7 +8,7 @@ import { statSync, rmSync, existsSync, unlinkSync, readFileSync, mkdirSync, writ
8
8
  import { resolve, dirname, sep, join, relative } from "node:path";
9
9
  import { buildConfigJsSource } from "./runtimeConfig-CCOFg1RM.mjs";
10
10
  import { a as createGitPagesConnector, b as createS3Connector } from "./s3-Dz50-NuA.mjs";
11
- import { C as CliError, r as readVersionInfo, i as distRoot } from "../agentcrm.mjs";
11
+ import { C as CliError, r as readVersionInfo, j as distRoot } from "../agentcrm.mjs";
12
12
  import { pathToFileURL } from "node:url";
13
13
  import "./index-BM8qeGOb.mjs";
14
14
  import "./http-Bk9ICb7I.mjs";
@@ -4,12 +4,12 @@ import { dirname as __acrmDirname } from "node:path";
4
4
  const require2 = __acrmRequire(import.meta.url);
5
5
  const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
- import { P as Papa } from "./papaparse-DjBYnQM2.mjs";
7
+ import { P as Papa } from "./papaparse-Df7i1dHr.mjs";
8
8
  import { findOption, normalizeEmail, coerceFieldValue, recordLabel, normalizeDomain } from "./fields-B0bitgmr.mjs";
9
9
  import { n as newId } from "./ids-CvnmrMJX.mjs";
10
10
  import { c as camelId } from "./utils-DmI8FtXP.mjs";
11
11
  import { assertCan, OpsError, actorId, emit } from "./context-CxFfqS4D.mjs";
12
- import { prepareRecordFields, recordPatchMutation } from "./records-DoqiT9xR.mjs";
12
+ import { prepareRecordFields, recordPatchMutation } from "./records-DcHFelq5.mjs";
13
13
  import { requireObject, thaw, recordsOf, usersOf, asDoc, objectOf } from "./store-5gqRSKXg.mjs";
14
14
  import "./_commonjsHelpers-ClciMIcB.mjs";
15
15
  import "stream";
@@ -4,30 +4,7 @@ import { dirname as __acrmDirname } from "node:path";
4
4
  const require2 = __acrmRequire(import.meta.url);
5
5
  const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
- import { F as FirebaseError, g as getModularInstance, _ as _getProvider, a as getDefaultEmulatorHostnameAndPort, b as getApp, i as isCloudWorkstation, p as pingServer, u as updateEmulatorBanner, c as createMockUserToken, d as _isFirebaseServerApp, e as _registerComponent, C as Component, r as registerVersion, S as SDK_VERSION } from "./sdk-whvfg786.mjs";
8
- import "util";
9
- import "crypto";
10
- import "process";
11
- import "tls";
12
- import "fs";
13
- import "os";
14
- import "net";
15
- import "events";
16
- import "stream";
17
- import "./_commonjsHelpers-ClciMIcB.mjs";
18
- import "path";
19
- import "http2";
20
- import "http";
21
- import "url";
22
- import "dns";
23
- import "zlib";
24
- import "./firebase-DxGVQV9E.mjs";
25
- import "./jsx-runtime-Ca2uwGDr.mjs";
26
- import "./index-Bnmav-Oh.mjs";
27
- import "./core-Dy7vY9H6.mjs";
28
- import "./types-BOy9PUl7.mjs";
29
- import "./index-RCmo-uld.mjs";
30
- import "./ids-u-noYcy5.mjs";
7
+ import { F as FirebaseError, g as getModularInstance, _ as _getProvider, a as getDefaultEmulatorHostnameAndPort, b as getApp, i as isCloudWorkstation, p as pingServer, u as updateEmulatorBanner, c as createMockUserToken, d as _isFirebaseServerApp, e as _registerComponent, C as Component, r as registerVersion, S as SDK_VERSION } from "./index-Dio0aBWA.mjs";
31
8
  /**
32
9
  * @license
33
10
  * Copyright 2017 Google LLC