@fleox-agent-crm/agentcrm 0.3.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +16 -16
  2. package/changelog.json +22 -0
  3. package/dist/admin/admin-files.json +7 -7
  4. package/dist/admin/assets/{chartsImpl-aBuZ1X4W.js → chartsImpl-zjsXDhuy.js} +1 -1
  5. package/dist/admin/assets/firebase-C3h9mF8O.js +2 -0
  6. package/dist/admin/assets/{gitJson-DzX3eZ2P.js → gitJson-nrOYgX0x.js} +1 -1
  7. package/dist/admin/assets/{index-DLy5RpPl.js → index-0ejjkW0z.js} +6 -6
  8. package/dist/admin/assets/{lucide-react.min-BgcmukkH.js → lucide-react.min-CI5dCsSm.js} +1 -1
  9. package/dist/admin/assets/{sdk-DtP9zqdi.js → sdk-mepKXtVl.js} +135 -120
  10. package/dist/admin/index.html +1 -1
  11. package/dist/agent/agentcrm.mjs +159 -74
  12. package/dist/agent/chunks/api-BEl_EKwf.mjs +79 -0
  13. package/dist/agent/chunks/{automations-DwWruxmu.mjs → automations-BqY8EwfJ.mjs} +3 -3
  14. package/dist/agent/chunks/{backup-W3CjgkAJ.mjs → backup-CO0EzdF_.mjs} +3 -3
  15. package/dist/agent/chunks/{compact-BGHI92tm.mjs → compact-qF5t42ko.mjs} +3 -3
  16. package/dist/agent/chunks/{exportCsv-C6e6XEmB.mjs → exportCsv-C-fIHLm1.mjs} +2 -2
  17. package/dist/agent/chunks/{extCheck-GgHG1dYB.mjs → extCheck-BcbUc7eB.mjs} +4 -4
  18. package/dist/agent/chunks/{files-D1daDgY0.mjs → files-C4YONVqQ.mjs} +2 -2
  19. package/dist/agent/chunks/{firebase-DWR0K1OC.mjs → firebase-Bknru2pE.mjs} +230 -53
  20. package/dist/agent/chunks/firebaseCache-sK4jA0ru.mjs +39 -0
  21. package/dist/agent/chunks/{firebaseRules-iori-s7U.mjs → firebaseRules-Jq85MBBN.mjs} +14 -3
  22. package/dist/agent/chunks/{forms-BfuWaQjA.mjs → forms-DJDop3XY.mjs} +2 -2
  23. package/dist/agent/chunks/{hosting-Dnn1k3cV.mjs → hosting-C_UDyMkm.mjs} +1 -1
  24. package/dist/agent/chunks/{importCsv-LVmMTaWU.mjs → importCsv-XjQJ9QIR.mjs} +2 -2
  25. package/dist/agent/chunks/{index-DtCezG4R.mjs → index-DWecFbTi.mjs} +1 -1
  26. package/dist/agent/chunks/{install-Bp-CwDVC.mjs → install-D1lgtRIH.mjs} +4 -4
  27. package/dist/agent/chunks/{loader-MPD9iAvh.mjs → loader-Cv0UJf6b.mjs} +11 -11
  28. package/dist/agent/chunks/{migrate-CsClnnN8.mjs → migrate-Cc_8MHZv.mjs} +1 -1
  29. package/dist/agent/chunks/{pipelines-BAKpPk1w.mjs → pipelines-DiJ2dLqQ.mjs} +3 -3
  30. package/dist/agent/chunks/{records-DcHFelq5.mjs → records-CCTxxMWF.mjs} +1 -1
  31. package/dist/agent/chunks/{registry-COfJrSmg.mjs → registry-ClzNZv9Z.mjs} +10 -10
  32. package/dist/agent/chunks/{reports-BACHGccj.mjs → reports-WlI_A5Nr.mjs} +3 -3
  33. package/dist/agent/chunks/{runtime-C0Xf0Axi.mjs → runtime-BAF5vU5D.mjs} +17 -17
  34. package/dist/agent/chunks/{schema-C4lSslXv.mjs → schema-DOnnH2zL.mjs} +3 -3
  35. package/dist/agent/chunks/schemaApply-DKHipylx.mjs +164 -0
  36. package/dist/agent/chunks/{sdk-CYgNyuoK.mjs → sdk-NYUHv7WG.mjs} +493 -214
  37. package/dist/agent/chunks/{seed-6MRmQQBR.mjs → seed-Bb26Zsni.mjs} +2 -2
  38. package/dist/agent/chunks/{server-DC3t8r30.mjs → server-Bch4TQon.mjs} +3 -3
  39. package/dist/agent/chunks/{standardObjects-CheqfDqa.mjs → standardObjects-vrmqIVLH.mjs} +1 -1
  40. package/dist/agent/chunks/{stats-BfuLWhVV.mjs → stats-DfmDP3tE.mjs} +3 -3
  41. package/dist/agent/chunks/{tools-iSH-yMpl.mjs → tools-CCk24tdh.mjs} +9 -9
  42. package/dist/agent/chunks/{views-DH1n9Lrt.mjs → views-BbhmQO3n.mjs} +4 -4
  43. package/dist/version.json +1 -1
  44. package/package.json +1 -1
  45. package/plugin/.claude-plugin/plugin.json +1 -1
  46. package/plugin/skills/agentcrm-admin/SKILL.md +67 -67
  47. package/plugin/skills/agentcrm-admin/references/records.md +5 -5
  48. package/plugin/skills/agentcrm-admin/references/schema.md +19 -19
  49. package/plugin/skills/agentcrm-extensions/SKILL.md +8 -8
  50. package/plugin/skills/agentcrm-extensions/references/module-recipes.md +2 -2
  51. package/plugin/skills/agentcrm-extensions/references/testing.md +14 -14
  52. package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +3 -3
  53. package/plugin/skills/agentcrm-install/SKILL.md +13 -13
  54. package/plugin/skills/agentcrm-install/references/firebase.md +1 -1
  55. package/plugin/skills/agentcrm-install/references/github.md +1 -1
  56. package/plugin/skills/agentcrm-install/references/mcp.md +2 -2
  57. package/plugin/skills/agentcrm-install/references/plans.md +1 -1
  58. package/plugin/skills/agentcrm-install/references/s3.md +1 -1
  59. package/plugin/skills/agentcrm-manage/SKILL.md +15 -15
  60. package/plugin/skills/agentcrm-sales/SKILL.md +18 -18
  61. package/dist/admin/assets/firebase-BG1GpyBB.js +0 -2
  62. package/dist/agent/chunks/api-Dwb4xz1k.mjs +0 -27
@@ -142,7 +142,7 @@ const output = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
142
142
  toCliError
143
143
  }, Symbol.toStringTag, { value: "Module" }));
144
144
  const name = "@fleox-agent-crm/agentcrm";
145
- const version = "0.3.3";
145
+ const version = "0.5.0";
146
146
  const type = "module";
147
147
  const description = "The static CRM your AI agent can run: a Twenty / HubSpot-style CRM that runs entirely in the browser or headless through the agentcrm CLI, with data on storage you own.";
148
148
  const license = "SEE LICENSE IN LICENSE";
@@ -524,7 +524,7 @@ function tmpFolder() {
524
524
  return tmpBase;
525
525
  }
526
526
  async function nodeImportBundle() {
527
- await import("./chunks/runtime-C0Xf0Axi.mjs");
527
+ await import("./chunks/runtime-BAF5vU5D.mjs");
528
528
  const { createNodeImportBundle } = await import("./chunks/nodeImport-ylfRDvqt.mjs");
529
529
  return createNodeImportBundle({ runtimeDir: runtimeDir(), tmpDir: tmpFolder() });
530
530
  }
@@ -539,7 +539,7 @@ async function loadSessionExtensions(store, opts = {}) {
539
539
  if (!loadedAny && !opts.force && !hasEnabledExtensions(store)) return null;
540
540
  try {
541
541
  const importBundle = await nodeImportBundle();
542
- const { syncExtensions } = await import("./chunks/loader-MPD9iAvh.mjs");
542
+ const { syncExtensions } = await import("./chunks/loader-Cv0UJf6b.mjs");
543
543
  loadedAny = true;
544
544
  return await syncExtensions(store, { importBundle, onError: () => void 0 });
545
545
  } catch (err) {
@@ -553,7 +553,7 @@ async function loadSessionExtensions(store, opts = {}) {
553
553
  sync,
554
554
  async close() {
555
555
  if (!loadedAny) return;
556
- (await import("./chunks/registry-COfJrSmg.mjs")).deactivateAllExtensions();
556
+ (await import("./chunks/registry-ClzNZv9Z.mjs")).deactivateAllExtensions();
557
557
  }
558
558
  };
559
559
  }
@@ -608,7 +608,7 @@ async function openStore(runtime, opts) {
608
608
  }
609
609
  if (runtime.backend === "journal") {
610
610
  if (!opts.dataTarget) throw new CliError("internal", "journal store without a data location");
611
- const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
611
+ const { buildConnector } = await import("./chunks/hosting-C_UDyMkm.mjs");
612
612
  const { createJournalStore } = await import("./chunks/index-B-4W0-P4.mjs");
613
613
  const { publicSettings, secrets } = rc.splitSecretSettings(opts.dataTarget.settings);
614
614
  rc.setConnectorSecrets(runtime, secrets);
@@ -629,8 +629,9 @@ async function openStore(runtime, opts) {
629
629
  const { prepareFirebaseSession: prepareFirebaseSession2 } = await Promise.resolve().then(() => firebaseSession);
630
630
  await prepareFirebaseSession2(opts.account, runtime.firebase, opts.secrets.email);
631
631
  }
632
- const { createDataStore } = await import("./chunks/index-DtCezG4R.mjs");
633
- return createDataStore(runtime, { secrets: opts.secrets ?? {}, passphrase: opts.passphrase ?? null, clientId: cliClientId() });
632
+ const { createDataStore } = await import("./chunks/index-DWecFbTi.mjs");
633
+ const firebaseCache = runtime.backend === "firebase" ? (await import("./chunks/firebaseCache-sK4jA0ru.mjs")).fileFirebaseCache() : void 0;
634
+ return createDataStore(runtime, { secrets: opts.secrets ?? {}, passphrase: opts.passphrase ?? null, clientId: cliClientId(), firebaseCache });
634
635
  }
635
636
  function siteSecrets(site) {
636
637
  const passphrase = site.credentials.passphrase ? requireCredential(site.credentials.passphrase).values.passphrase : null;
@@ -680,7 +681,7 @@ async function openSession(alias, opts = {}) {
680
681
  const [{ createOpsContext }, { createPluginRegistry, plugins: defaultPlugins }, { installAutomations }] = await Promise.all([
681
682
  import("./chunks/context-CxFfqS4D.mjs"),
682
683
  import("./chunks/pluginRegistry-DyHtlkcr.mjs"),
683
- import("./chunks/automations-DwWruxmu.mjs")
684
+ import("./chunks/automations-BqY8EwfJ.mjs")
684
685
  ]);
685
686
  const own = createPluginRegistry((hook, err) => process.stderr.write(`[agentcrm] ${hook} handler failed: ${String(err?.message ?? err)}
686
687
  `));
@@ -694,7 +695,7 @@ async function openSession(alias, opts = {}) {
694
695
  const ctx = createOpsContext(store, user, { plugins, log });
695
696
  const system = createOpsContext(store, null, { plugins, log });
696
697
  if (opts.lock && (!user || user.role === "admin") && store.list("objects").length) {
697
- const { migrateSchema } = await import("./chunks/migrate-CsClnnN8.mjs").then((n) => n.c);
698
+ const { migrateSchema } = await import("./chunks/migrate-Cc_8MHZv.mjs").then((n) => n.c);
698
699
  await migrateSchema(ctx).catch((err) => warnings.push(`Schema migration skipped: ${err.message}`));
699
700
  }
700
701
  if (site.hosting) {
@@ -707,14 +708,14 @@ async function openSession(alias, opts = {}) {
707
708
  })();
708
709
  if (target) {
709
710
  const { setStorageConfigSource, registerStorageConnector } = await import("./chunks/registry-DijAKZ24.mjs").then((n) => n.r);
710
- const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
711
+ const { buildConnector } = await import("./chunks/hosting-C_UDyMkm.mjs");
711
712
  if (target.connectorId === "local-folder") registerStorageConnector({ id: "local-folder", create: (s) => buildConnector({ connectorId: "local-folder", settings: s }) });
712
713
  setStorageConfigSource(async () => target);
713
714
  }
714
715
  }
715
716
  {
716
- const forms = await import("./chunks/forms-BfuWaQjA.mjs");
717
- const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
717
+ const forms = await import("./chunks/forms-DJDop3XY.mjs");
718
+ const { buildConnector } = await import("./chunks/hosting-C_UDyMkm.mjs");
718
719
  forms.setInboxSourceResolver((_ctx, form) => {
719
720
  if (form.inbox.kind !== "s3") return null;
720
721
  const settings = form.inbox.settings ?? {};
@@ -913,6 +914,29 @@ function table(rows, columns) {
913
914
  const SITE_OPTION = { name: "site", type: "string", description: "Site alias (default: $AGENTCRM_SITE, ./agentcrm.json, the only site, or `site use`)." };
914
915
  const AS_OPTION = { name: "as", type: "string", description: "Act as this CRM user (email or id) instead of the site's user." };
915
916
  const YES_OPTION = { name: "yes", type: "boolean", description: "Apply — only after the user confirmed the plan." };
917
+ const util = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
918
+ __proto__: null,
919
+ AS_OPTION,
920
+ SITE_OPTION,
921
+ YES_OPTION,
922
+ boolOpt,
923
+ fieldsInput,
924
+ jsonObjectOpt,
925
+ jsonOpt,
926
+ listOpt,
927
+ multiOpt,
928
+ numOpt,
929
+ parseFilters,
930
+ parseScalar,
931
+ parseSorts,
932
+ positional,
933
+ readTextValue,
934
+ requireStr,
935
+ strOpt,
936
+ table,
937
+ usage,
938
+ writeOutFile
939
+ }, Symbol.toStringTag, { value: "Module" }));
916
940
  function fileOut(f) {
917
941
  return {
918
942
  id: f.id,
@@ -935,7 +959,7 @@ function readLocalFile(path) {
935
959
  return { name: basename(full), bytes: new Uint8Array(readFileSync(full)) };
936
960
  }
937
961
  async function uploadLocalFiles(ctx, paths, targets2 = []) {
938
- const { uploadFile } = await import("./chunks/files-D1daDgY0.mjs");
962
+ const { uploadFile } = await import("./chunks/files-C4YONVqQ.mjs");
939
963
  const { guessMime, imageDimensions } = await import("./chunks/files-CjsPJSR6.mjs");
940
964
  const out = [];
941
965
  for (const p of paths) {
@@ -983,7 +1007,7 @@ const filesCommand = {
983
1007
  switch (action) {
984
1008
  case "list":
985
1009
  return withSession(io, options, {}, async ({ ctx }) => {
986
- const { listFiles } = await import("./chunks/files-D1daDgY0.mjs");
1010
+ const { listFiles } = await import("./chunks/files-C4YONVqQ.mjs");
987
1011
  const target = strOpt(options, "record") ?? strOpt(options, "activity") ?? null;
988
1012
  const items = listFiles(ctx, target, { includeRelated: boolOpt(options, "related"), kind: strOpt(options, "kind"), search: strOpt(options, "search") });
989
1013
  const limit = Math.min(1e3, numOpt(options, "limit") ?? 100);
@@ -998,7 +1022,7 @@ ${table(r.files, ["id", "name", "mime", "size", "createdAt"])}`);
998
1022
  if (record && activity) usage("Give --record or --activity, not both.");
999
1023
  for (const p of args) readLocalFile(p);
1000
1024
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1001
- const { attachFile } = await import("./chunks/files-D1daDgY0.mjs");
1025
+ const { attachFile } = await import("./chunks/files-C4YONVqQ.mjs");
1002
1026
  const files2 = await uploadLocalFiles(ctx, args, record ? [record] : []);
1003
1027
  if (activity) for (const f of files2) await attachFile(ctx, f.id, { kind: "activity", id: activity });
1004
1028
  const { requireFile } = await import("./chunks/store-5gqRSKXg.mjs");
@@ -1011,7 +1035,7 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
1011
1035
  const out = strOpt(options, "out");
1012
1036
  if (!out) usage("files get needs --out <path>.");
1013
1037
  return withSession(io, options, {}, async ({ ctx }) => {
1014
- const { getFile, getFileBytes } = await import("./chunks/files-D1daDgY0.mjs");
1038
+ const { getFile, getFileBytes } = await import("./chunks/files-C4YONVqQ.mjs");
1015
1039
  const meta = getFile(ctx, id);
1016
1040
  const bytes = await getFileBytes(ctx, id, { thumb: boolOpt(options, "thumb") });
1017
1041
  writeOutFile(out, bytes);
@@ -1021,7 +1045,7 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
1021
1045
  case "delete": {
1022
1046
  const id = positional(args, 0, "file id", U$1.delete);
1023
1047
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1024
- const { getFile, deleteFile } = await import("./chunks/files-D1daDgY0.mjs");
1048
+ const { getFile, deleteFile } = await import("./chunks/files-C4YONVqQ.mjs");
1025
1049
  const meta = fileOut(getFile(ctx, id));
1026
1050
  if (options.yes !== true) return { needsConfirmation: true, plan: { delete: meta } };
1027
1051
  await deleteFile(ctx, id);
@@ -1029,7 +1053,7 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
1029
1053
  }, (r) => "plan" in r && r.plan ? `Would delete ${r.plan.delete.name} (${r.plan.delete.id}). Re-run with --yes once the user agrees.` : `Deleted ${"deleted" in r ? r.deleted : id} (bytes purged by \`agentcrm compact\`).`);
1030
1054
  }
1031
1055
  case "usage":
1032
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/files-D1daDgY0.mjs")).fileUsage(ctx));
1056
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/files-C4YONVqQ.mjs")).fileUsage(ctx));
1033
1057
  default:
1034
1058
  usage(`Unknown files action "${action ?? ""}".`, "Use list, upload, get, delete or usage.");
1035
1059
  }
@@ -1042,7 +1066,7 @@ async function recordImageAction(io, options, args) {
1042
1066
  if (!remove && !path) usage("records image needs an image path (or --remove).");
1043
1067
  const local = remove ? null : readLocalFile(path);
1044
1068
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1045
- const { setRecordImage, clearRecordImage } = await import("./chunks/files-D1daDgY0.mjs");
1069
+ const { setRecordImage, clearRecordImage } = await import("./chunks/files-C4YONVqQ.mjs");
1046
1070
  const field = strOpt(options, "field");
1047
1071
  if (!local) {
1048
1072
  const r = await clearRecordImage(ctx, recordId, field);
@@ -1330,7 +1354,7 @@ const recordsCommand = {
1330
1354
  const filters = parseFilters(multiOpt(options, "filter"), FILTER_OPS);
1331
1355
  const sorts = parseSorts(multiOpt(options, "sort"));
1332
1356
  return withSession(io, options, {}, async ({ ctx }) => {
1333
- const { listRecords } = await import("./chunks/records-DcHFelq5.mjs");
1357
+ const { listRecords } = await import("./chunks/records-CCTxxMWF.mjs");
1334
1358
  const page = listRecords(ctx, objectId, {
1335
1359
  filters,
1336
1360
  sorts,
@@ -1348,7 +1372,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1348
1372
  case "get": {
1349
1373
  const id = positional(args, 0, "record id", U.get);
1350
1374
  return withSession(io, options, {}, async ({ ctx }) => {
1351
- const { getRecordDetails } = await import("./chunks/records-DcHFelq5.mjs");
1375
+ const { getRecordDetails } = await import("./chunks/records-CCTxxMWF.mjs");
1352
1376
  const d = getRecordDetails(ctx, id);
1353
1377
  return { ...await recordOut(ctx, d.record), relations: d.relations, related: d.inverse.filter((i) => i.records.length), activityCount: d.activityCount };
1354
1378
  });
@@ -1359,7 +1383,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1359
1383
  if (!Object.keys(fields).length) usage("records create needs --fields or --set.", `Usage: agentcrm ${U.create}`);
1360
1384
  const owner = strOpt(options, "owner");
1361
1385
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1362
- const { createRecord } = await import("./chunks/records-DcHFelq5.mjs");
1386
+ const { createRecord } = await import("./chunks/records-CCTxxMWF.mjs");
1363
1387
  return recordOut(ctx, await createRecord(ctx, objectId, fields, owner ? { ownerId: owner } : {}));
1364
1388
  }, (r) => `Created ${r.object} ${r.id} (${String(r.label)})`);
1365
1389
  }
@@ -1369,7 +1393,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1369
1393
  const owner = strOpt(options, "owner");
1370
1394
  if (!Object.keys(fields).length && owner === void 0) usage("records update needs --fields, --set or --owner.");
1371
1395
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1372
- const { updateRecord } = await import("./chunks/records-DcHFelq5.mjs");
1396
+ const { updateRecord } = await import("./chunks/records-CCTxxMWF.mjs");
1373
1397
  return recordOut(ctx, await updateRecord(ctx, id, fields, owner !== void 0 ? { ownerId: owner || null } : {}));
1374
1398
  }, (r) => `Updated ${r.id} (${String(r.label)})`);
1375
1399
  }
@@ -1377,7 +1401,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1377
1401
  if (!args.length) usage("records delete needs at least one id.", `Usage: agentcrm ${U.delete}`);
1378
1402
  const hard = boolOpt(options, "hard");
1379
1403
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1380
- const { getRecord, deleteRecord, bulkDelete } = await import("./chunks/records-DcHFelq5.mjs");
1404
+ const { getRecord, deleteRecord, bulkDelete } = await import("./chunks/records-CCTxxMWF.mjs");
1381
1405
  const { recordLabel } = await import("./chunks/fields-B0bitgmr.mjs");
1382
1406
  const { objectOf } = await import("./chunks/store-5gqRSKXg.mjs");
1383
1407
  const targets2 = args.map((id) => getRecord(ctx, id, { includeDeleted: true }));
@@ -1390,7 +1414,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1390
1414
  case "restore": {
1391
1415
  const id = positional(args, 0, "record id", U.restore);
1392
1416
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1393
- const { restoreRecord } = await import("./chunks/records-DcHFelq5.mjs");
1417
+ const { restoreRecord } = await import("./chunks/records-CCTxxMWF.mjs");
1394
1418
  return recordOut(ctx, await restoreRecord(ctx, id));
1395
1419
  });
1396
1420
  }
@@ -1398,7 +1422,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1398
1422
  const query = args.join(" ").trim();
1399
1423
  if (!query) usage("records search needs a query.", `Usage: agentcrm ${U.search}`);
1400
1424
  return withSession(io, options, {}, async ({ ctx }) => {
1401
- const { searchRecords } = await import("./chunks/records-DcHFelq5.mjs");
1425
+ const { searchRecords } = await import("./chunks/records-CCTxxMWF.mjs");
1402
1426
  return searchRecords(ctx, query, { objects: listOpt(options, "object"), limit: Math.min(100, numOpt(options, "limit") ?? 20) });
1403
1427
  });
1404
1428
  }
@@ -1415,7 +1439,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1415
1439
  const [primary, ...dups] = args;
1416
1440
  if (!primary || !dups.length) usage("records merge needs <primaryId> <duplicateId>….", `Usage: agentcrm ${U.merge}`);
1417
1441
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1418
- const { getRecord } = await import("./chunks/records-DcHFelq5.mjs");
1442
+ const { getRecord } = await import("./chunks/records-CCTxxMWF.mjs");
1419
1443
  const all = [primary, ...dups].map((id) => getRecord(ctx, id));
1420
1444
  const plan = { primary: await recordOut(ctx, all[0]), duplicates: await Promise.all(all.slice(1).map((r) => recordOut(ctx, r))) };
1421
1445
  if (options.yes !== true) return { needsConfirmation: true, plan };
@@ -1447,13 +1471,13 @@ const pipelinesCommand = {
1447
1471
  io,
1448
1472
  options,
1449
1473
  {},
1450
- async ({ ctx }) => (await import("./chunks/pipelines-BAKpPk1w.mjs")).listPipelines(ctx, strOpt(options, "object")),
1474
+ async ({ ctx }) => (await import("./chunks/pipelines-DiJ2dLqQ.mjs")).listPipelines(ctx, strOpt(options, "object")),
1451
1475
  (ps) => ps.map((p) => `${p.id} (${p.name}, ${p.object}): ${p.stages.map((s) => `${s.label} ${Math.round(s.probability * (s.probability <= 1 ? 100 : 1))}%`).join(" → ")}`).join("\n")
1452
1476
  );
1453
1477
  }
1454
1478
  if (action === "move") {
1455
1479
  if (!id || !stage.length) usage("pipelines move needs <recordId> <stage>.");
1456
- return withSession(io, options, { lock: true }, async ({ ctx }) => recordOut(ctx, await (await import("./chunks/pipelines-BAKpPk1w.mjs")).moveStage(ctx, id, stage.join(" "), { lostReason: strOpt(options, "lost-reason") })));
1480
+ return withSession(io, options, { lock: true }, async ({ ctx }) => recordOut(ctx, await (await import("./chunks/pipelines-DiJ2dLqQ.mjs")).moveStage(ctx, id, stage.join(" "), { lostReason: strOpt(options, "lost-reason") })));
1457
1481
  }
1458
1482
  usage(`Unknown pipelines action "${action}".`, "Use list or move.");
1459
1483
  }
@@ -1485,7 +1509,7 @@ const viewsCommand = {
1485
1509
  const [action = "list", id] = positionals;
1486
1510
  switch (action) {
1487
1511
  case "list":
1488
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/views-DH1n9Lrt.mjs")).listViews(ctx, strOpt(options, "object")), (vs) => table(vs, ["id", "object", "name", "type", "shared"]));
1512
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/views-BbhmQO3n.mjs")).listViews(ctx, strOpt(options, "object")), (vs) => table(vs, ["id", "object", "name", "type", "shared"]));
1489
1513
  case "create": {
1490
1514
  const type2 = strOpt(options, "type") ?? "table";
1491
1515
  if (type2 !== "table" && type2 !== "kanban") usage("--type must be table or kanban.");
@@ -1500,12 +1524,12 @@ const viewsCommand = {
1500
1524
  ...strOpt(options, "pipeline") ? { pipelineId: strOpt(options, "pipeline") } : {},
1501
1525
  shared: boolOpt(options, "shared")
1502
1526
  };
1503
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/views-DH1n9Lrt.mjs")).createView(ctx, input));
1527
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/views-BbhmQO3n.mjs")).createView(ctx, input));
1504
1528
  }
1505
1529
  case "delete":
1506
1530
  if (!id) usage("views delete needs a view id.");
1507
1531
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1508
- const { getView, deleteView } = await import("./chunks/views-DH1n9Lrt.mjs");
1532
+ const { getView, deleteView } = await import("./chunks/views-BbhmQO3n.mjs");
1509
1533
  const v = getView(ctx, id);
1510
1534
  if (options.yes !== true) return { needsConfirmation: true, plan: { delete: { id: v.id, name: v.name, object: v.object, shared: v.shared } } };
1511
1535
  return deleteView(ctx, id);
@@ -1713,7 +1737,7 @@ function filePersistence(account, email) {
1713
1737
  };
1714
1738
  }
1715
1739
  async function prepareFirebaseSession(account, options, email) {
1716
- const { getFirebaseApp } = await import("./chunks/sdk-CYgNyuoK.mjs");
1740
+ const { getFirebaseApp } = await import("./chunks/sdk-NYUHv7WG.mjs");
1717
1741
  const { initializeAuth } = await import("./chunks/index-Dio0aBWA.mjs").then((n) => n.x);
1718
1742
  try {
1719
1743
  initializeAuth(getFirebaseApp(options), { persistence: filePersistence(account, email.trim().toLowerCase()) });
@@ -1832,6 +1856,10 @@ async function dryRun(ctx, work) {
1832
1856
  const copy = createOpsContext(store, ctx.user, { now: ctx.now, plugins: createPluginRegistry(() => void 0) });
1833
1857
  return { result: await work(copy), store };
1834
1858
  }
1859
+ const dryRun$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1860
+ __proto__: null,
1861
+ dryRun
1862
+ }, Symbol.toStringTag, { value: "Module" }));
1835
1863
  const CASCADE_OPTION = { name: "cascade", type: "boolean", description: "enable / disable: also enable the dependencies / disable the dependents." };
1836
1864
  function parseJsonValue(raw, name2) {
1837
1865
  const text = readTextValue(raw, name2);
@@ -2096,7 +2124,7 @@ async function check({ io, options, positionals }) {
2096
2124
  const kind = strOpt(options, "kind");
2097
2125
  if (kind && kind !== "module" && kind !== "theme") usage("--kind is module or theme.");
2098
2126
  const { readRawPackage } = await import("./chunks/extPackage-DuZ6GZhp.mjs");
2099
- const { checkExtension } = await import("./chunks/extCheck-GgHG1dYB.mjs");
2127
+ const { checkExtension } = await import("./chunks/extCheck-BcbUc7eB.mjs");
2100
2128
  const raw = await readRawPackage(target);
2101
2129
  const report = await checkExtension(raw, { kind, callTools: !boolOpt(options, "no-call") });
2102
2130
  printResult(io, report, () => {
@@ -2143,7 +2171,7 @@ async function install({ io, options, positionals }) {
2143
2171
  const apply = options.yes === true;
2144
2172
  const disabled = boolOpt(options, "disabled");
2145
2173
  if (!boolOpt(options, "force")) {
2146
- const { checkExtension } = await import("./chunks/extCheck-GgHG1dYB.mjs");
2174
+ const { checkExtension } = await import("./chunks/extCheck-BcbUc7eB.mjs");
2147
2175
  const report = await checkExtension(raw, { callTools: false });
2148
2176
  if (!report.valid) {
2149
2177
  throw new CliError(
@@ -2179,7 +2207,7 @@ async function install({ io, options, positionals }) {
2179
2207
  }
2180
2208
  const result = await ext.installExtension(s.ctx, pkg2, disabled ? { enabled: false } : {});
2181
2209
  const report = await s.extensions.sync();
2182
- const { getLoadedExtension } = await import("./chunks/registry-COfJrSmg.mjs");
2210
+ const { getLoadedExtension } = await import("./chunks/registry-ClzNZv9Z.mjs");
2183
2211
  const loaded = getLoadedExtension(m.id);
2184
2212
  const failure = report?.failed.find((f) => f.id === m.id || f.id === "*");
2185
2213
  if (failure && !loaded) return { ...result, author: m.author ?? null, load: { status: "error", error: failure.error } };
@@ -2241,7 +2269,7 @@ const extCommand = {
2241
2269
  if (kind && kind !== "module" && kind !== "theme") usage("--kind is module or theme.");
2242
2270
  return withSession(io, options, {}, async (s) => {
2243
2271
  const { listExtensions } = await import("./chunks/extensions-Bp72uZ9a.mjs");
2244
- const { getLoadedExtension } = await import("./chunks/registry-COfJrSmg.mjs");
2272
+ const { getLoadedExtension } = await import("./chunks/registry-ClzNZv9Z.mjs");
2245
2273
  return listExtensions(s.ctx, kind ? { kind } : {}).map((e) => ({ ...e, load: s.extensions.safeMode ? "skipped" : getLoadedExtension(e.id)?.status ?? (e.enabled ? "not loaded" : "disabled"), ...getLoadedExtension(e.id)?.error ? { loadError: getLoadedExtension(e.id)?.error } : {} }));
2246
2274
  }, (rows) => table(rows.map((r) => ({ ...r, enabled: r.enabled ? "yes" : "no" })), ["id", "kind", "name", "version", "enabled", "load", "author"]));
2247
2275
  }
@@ -2356,7 +2384,7 @@ const importCommand = {
2356
2384
  options,
2357
2385
  {},
2358
2386
  async ({ ctx }) => {
2359
- const plan = await (await import("./chunks/backup-W3CjgkAJ.mjs")).planBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" });
2387
+ const plan = await (await import("./chunks/backup-CO0EzdF_.mjs")).planBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" });
2360
2388
  return { needsConfirmation: true, plan: { restore: resolve(path), bytes: bytes.length, ...plan } };
2361
2389
  },
2362
2390
  (r) => [
@@ -2366,7 +2394,7 @@ const importCommand = {
2366
2394
  ].join("\n")
2367
2395
  );
2368
2396
  }
2369
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/backup-W3CjgkAJ.mjs")).importBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" }), (r) => `Restored ${r.written} document(s) and ${r.files} file(s)`);
2397
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/backup-CO0EzdF_.mjs")).importBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" }), (r) => `Restored ${r.written} document(s) and ${r.files} file(s)`);
2370
2398
  }
2371
2399
  if (kind !== "csv") usage('Only "import csv" and "import zip" are supported.', "Usage: agentcrm import csv <file> --object <id>");
2372
2400
  const source = positional(positionals, 1, "CSV file (or -)", "import csv <file|-> --object <id>");
@@ -2377,7 +2405,7 @@ const importCommand = {
2377
2405
  const apply = options.yes === true && options["dry-run"] !== true;
2378
2406
  const shown = numOpt(options, "rows") ?? 100;
2379
2407
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2380
- const { importCsv } = await import("./chunks/importCsv-LVmMTaWU.mjs");
2408
+ const { importCsv } = await import("./chunks/importCsv-XjQJ9QIR.mjs");
2381
2409
  const report = await importCsv(ctx, {
2382
2410
  object: objectId,
2383
2411
  csv,
@@ -2426,7 +2454,7 @@ const exportCommand = {
2426
2454
  const file2 = strOpt(options, "out");
2427
2455
  if (!file2) usage("export zip needs --out <file.zip>.");
2428
2456
  return withSession(io, options, {}, async ({ ctx }) => {
2429
- const { exportBackupZip } = await import("./chunks/backup-W3CjgkAJ.mjs");
2457
+ const { exportBackupZip } = await import("./chunks/backup-CO0EzdF_.mjs");
2430
2458
  const { zip, report } = await exportBackupZip(ctx, { includeFiles: options["no-files"] !== true });
2431
2459
  writeOutFile(file2, zip);
2432
2460
  return { file: resolve(file2), zipBytes: zip.length, files: report.files, missing: report.missing, fileBytes: report.bytes };
@@ -2445,7 +2473,7 @@ const exportCommand = {
2445
2473
  const filters = parseFilters(multiOpt(options, "filter"), FILTER_OPS);
2446
2474
  const sorts = parseSorts(multiOpt(options, "sort"));
2447
2475
  return withSession(io, options, {}, async ({ ctx }) => {
2448
- const { exportCsv } = await import("./chunks/exportCsv-C6e6XEmB.mjs");
2476
+ const { exportCsv } = await import("./chunks/exportCsv-C-fIHLm1.mjs");
2449
2477
  const csv = exportCsv(ctx, objectId, {
2450
2478
  viewId: strOpt(options, "view"),
2451
2479
  filters,
@@ -2461,7 +2489,7 @@ const exportCommand = {
2461
2489
  }
2462
2490
  if (kind === "json") {
2463
2491
  return withSession(io, options, {}, async ({ ctx }) => {
2464
- const { exportJson } = await import("./chunks/exportCsv-C6e6XEmB.mjs");
2492
+ const { exportJson } = await import("./chunks/exportCsv-C-fIHLm1.mjs");
2465
2493
  const data = exportJson(ctx, { collections: listOpt(options, "collections") });
2466
2494
  const text = JSON.stringify(data, null, 2);
2467
2495
  if (out) return deliver(text, {});
@@ -2505,7 +2533,7 @@ const reportCommand = {
2505
2533
  ...strOpt(options, "pipeline") ? { pipelineId: strOpt(options, "pipeline") } : {},
2506
2534
  ...strOpt(options, "owner") ? { ownerId: strOpt(options, "owner") } : {}
2507
2535
  };
2508
- return withSession(io, options, {}, async ({ ctx }) => ({ report: name2, data: (await import("./chunks/reports-BACHGccj.mjs")).runReport(ctx, internal, params) }));
2536
+ return withSession(io, options, {}, async ({ ctx }) => ({ report: name2, data: (await import("./chunks/reports-WlI_A5Nr.mjs")).runReport(ctx, internal, params) }));
2509
2537
  }
2510
2538
  };
2511
2539
  const statsCommand = {
@@ -2515,7 +2543,7 @@ const statsCommand = {
2515
2543
  options: [SITE_OPTION, AS_OPTION],
2516
2544
  async run({ io, options }) {
2517
2545
  return withSession(io, options, {}, async ({ ctx, store }) => {
2518
- const stats = (await import("./chunks/stats-BfuLWhVV.mjs")).getStats(ctx);
2546
+ const stats = (await import("./chunks/stats-DfmDP3tE.mjs")).getStats(ctx);
2519
2547
  const journal = store.stats;
2520
2548
  return { ...stats, ...journal ? { journal: await journal.call(store) } : {} };
2521
2549
  }, (s) => [`backend: ${s.backend}`, ...s.objects.map((o) => ` ${o.label}: ${o.count}${o.deleted ? ` (+${o.deleted} deleted)` : ""}`), `open tasks: ${s.openTasks} (${s.overdueTasks} overdue)`, `users: ${s.users}`].join("\n"));
@@ -2529,7 +2557,7 @@ const compactCommand = {
2529
2557
  async run({ io, options }) {
2530
2558
  const apply = options.yes === true;
2531
2559
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2532
- const { compactData } = await import("./chunks/compact-BGHI92tm.mjs");
2560
+ const { compactData } = await import("./chunks/compact-qF5t42ko.mjs");
2533
2561
  const report = await compactData(ctx, { olderThanDays: numOpt(options, "older-than"), keepRuns: numOpt(options, "keep-runs"), dryRun: !apply });
2534
2562
  return apply ? report : { needsConfirmation: true, plan: report };
2535
2563
  }, (r) => "plan" in r ? `Would purge ${r.plan.purgedRecords.length} record(s), ${r.plan.purgedActivities} activit(ies), ${r.plan.purgedRuns} run(s), ${r.plan.purgedFiles} file(s). Re-run with --yes once the user agrees.` : `Purged ${r.purgedRecords.length} record(s), ${r.purgedFiles} file(s); storage compacted: ${r.storageCompacted}`);
@@ -2550,19 +2578,19 @@ const automationsCommand = {
2550
2578
  const [action = "list", id] = positionals;
2551
2579
  switch (action) {
2552
2580
  case "list":
2553
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/automations-DwWruxmu.mjs")).listAutomations(ctx).map((a) => ({ id: a.id, name: a.name, enabled: a.enabled, object: a.object, trigger: a.trigger, actions: a.actions.map((x) => x.type), lastRunAt: a.lastRunAt ? new Date(a.lastRunAt).toISOString() : null })), (rows) => table(rows, ["id", "name", "enabled", "object", "lastRunAt"]));
2581
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/automations-BqY8EwfJ.mjs")).listAutomations(ctx).map((a) => ({ id: a.id, name: a.name, enabled: a.enabled, object: a.object, trigger: a.trigger, actions: a.actions.map((x) => x.type), lastRunAt: a.lastRunAt ? new Date(a.lastRunAt).toISOString() : null })), (rows) => table(rows, ["id", "name", "enabled", "object", "lastRunAt"]));
2554
2582
  case "enable":
2555
2583
  case "disable": {
2556
2584
  if (!id) usage(`automations ${action} needs an id.`);
2557
2585
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
2558
- const mod = await import("./chunks/automations-DwWruxmu.mjs");
2586
+ const mod = await import("./chunks/automations-BqY8EwfJ.mjs");
2559
2587
  return mod.setAutomationEnabled(ctx, id, action === "enable");
2560
2588
  });
2561
2589
  }
2562
2590
  case "run": {
2563
2591
  const apply = options.yes === true;
2564
2592
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2565
- const { runScheduled } = await import("./chunks/automations-DwWruxmu.mjs");
2593
+ const { runScheduled } = await import("./chunks/automations-BqY8EwfJ.mjs");
2566
2594
  const report = await runScheduled(ctx, { dryRun: !apply, automationId: strOpt(options, "id"), force: boolOpt(options, "force") });
2567
2595
  return apply ? report : { needsConfirmation: true, ...report };
2568
2596
  }, (r) => `${"needsConfirmation" in r ? "DRY RUN — " : ""}ran ${r.ran}, skipped ${r.skipped}, errors ${r.errors}`);
@@ -2586,16 +2614,16 @@ const formsCommand = {
2586
2614
  const [action = "list", id] = positionals;
2587
2615
  switch (action) {
2588
2616
  case "list":
2589
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/forms-BfuWaQjA.mjs")).listForms(ctx), (fs) => table(fs, ["id", "name", "object", "publishedPath"]));
2617
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/forms-DJDop3XY.mjs")).listForms(ctx), (fs) => table(fs, ["id", "name", "object", "publishedPath"]));
2590
2618
  case "publish": {
2591
2619
  if (!id) usage("forms publish needs a form id.");
2592
2620
  return withSession(io, options, { lock: options.yes === true }, async (s) => {
2593
- const forms = await import("./chunks/forms-BfuWaQjA.mjs");
2621
+ const forms = await import("./chunks/forms-DJDop3XY.mjs");
2594
2622
  const built = await forms.buildFormHtml(s.ctx, id);
2595
2623
  const target = s.hostingTarget();
2596
2624
  if (!target) usage(`${s.site.alias} has no hosting to publish the form to.`);
2597
2625
  if (options.yes !== true) return { needsConfirmation: true, plan: { form: id, path: built.path, hosting: s.site.hosting?.type, bytes: built.html.length } };
2598
- const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
2626
+ const { buildConnector } = await import("./chunks/hosting-C_UDyMkm.mjs");
2599
2627
  const connector = buildConnector(target);
2600
2628
  const siteUrl = typeof s.site.hosting?.settings.siteUrl === "string" ? s.site.hosting.settings.siteUrl.replace(/\/+$/, "") : "";
2601
2629
  return forms.publishForm(s.ctx, id, { put: (input) => connector.put(input), publicUrl: async (path) => siteUrl ? `${siteUrl}/${path}` : connector.publicUrl(path) });
@@ -2604,7 +2632,7 @@ const formsCommand = {
2604
2632
  case "ingest": {
2605
2633
  const apply = options.yes === true;
2606
2634
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2607
- const { ingestInbox } = await import("./chunks/forms-BfuWaQjA.mjs");
2635
+ const { ingestInbox } = await import("./chunks/forms-DJDop3XY.mjs");
2608
2636
  const report = await ingestInbox(ctx, strOpt(options, "form"), { dryRun: !apply, limit: numOpt(options, "limit") });
2609
2637
  return apply ? report : { needsConfirmation: true, ...report };
2610
2638
  }, (r) => `${"needsConfirmation" in r ? "DRY RUN — " : ""}processed ${r.processed}: ${r.created} created, ${r.updated} updated, ${r.failed} failed`);
@@ -2740,7 +2768,7 @@ const deployAdminCommand = {
2740
2768
  const sites = options.all === true ? listSites().sites.filter((s) => s.hosting) : [resolveSite(strOpt(options, "site"))];
2741
2769
  if (!sites.length) throw new CliError("no-sites", "No registered CRM has a hosting.");
2742
2770
  installBrowserEnv();
2743
- const { adminOps, applyOps, buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
2771
+ const { adminOps, applyOps, buildConnector } = await import("./chunks/hosting-C_UDyMkm.mjs");
2744
2772
  const { hostingTargetOf: hostingTargetOf2 } = await Promise.resolve().then(() => session);
2745
2773
  const plans = [];
2746
2774
  for (const site of sites) {
@@ -2844,7 +2872,7 @@ async function runDoctor(site, opts = {}) {
2844
2872
  installBrowserEnv();
2845
2873
  try {
2846
2874
  if (site.runtime.backend === "journal" && site.runtime.journal.connectorId === "git-pages") {
2847
- const { probeTarget } = await import("./chunks/install-Bp-CwDVC.mjs");
2875
+ const { probeTarget } = await import("./chunks/install-D1lgtRIH.mjs");
2848
2876
  const { dataTargetOf: dataTargetOf2 } = await Promise.resolve().then(() => session);
2849
2877
  const probe = await probeTarget(dataTargetOf2(site), { requireSiteUrl: false, requirePrivate: true });
2850
2878
  add(probe.ok ? { id: "data-repo", status: "ok", message: probe.detail } : { id: "data-repo", status: "fail", message: probe.detail, fix: "Make the data repository private and check the token's Contents permission." });
@@ -2854,6 +2882,15 @@ async function runDoctor(site, opts = {}) {
2854
2882
  try {
2855
2883
  const store = session$1.store;
2856
2884
  add({ id: "data-access", status: "ok", message: `Dataset opened (${store.kind}${session$1.user ? `, acting as ${session$1.user.email} (${session$1.user.role})` : ""}).` });
2885
+ const { isFirebaseStore } = await import("./chunks/firebase-Bknru2pE.mjs");
2886
+ const load2 = isFirebaseStore(store) ? store.lastLoad() : null;
2887
+ if (load2) {
2888
+ add({
2889
+ id: "firestore-reads",
2890
+ status: "ok",
2891
+ message: load2.mode === "incremental" ? `Loaded from the local cache + changes: ${load2.reads} Firestore reads.` : `Full read: ${load2.reads} Firestore reads (the next commands read only what changed).`
2892
+ });
2893
+ }
2857
2894
  const { usersOf, objectsOf, pipelinesOf, settingsDoc, recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
2858
2895
  const missing = [];
2859
2896
  for (const id of ["companies", "people", "deals"]) if (!objectsOf(store).some((o) => o.id === id)) missing.push(`object ${id}`);
@@ -2861,7 +2898,7 @@ async function runDoctor(site, opts = {}) {
2861
2898
  if (!settingsDoc(store)) missing.push("settings");
2862
2899
  if (!usersOf(store).some((u) => u.role === "admin" && !u.disabled)) missing.push("an active admin user");
2863
2900
  if (missing.length && opts.repair) {
2864
- const { seedCrm } = await import("./chunks/seed-6MRmQQBR.mjs");
2901
+ const { seedCrm } = await import("./chunks/seed-Bb26Zsni.mjs");
2865
2902
  repaired = await seedCrm(session$1.system, { admin: site.user ? { name: site.user.split("@")[0], email: site.user } : void 0 });
2866
2903
  add({ id: "dataset", status: "ok", message: `Repaired: seeded ${missing.join(", ")}.` });
2867
2904
  } else {
@@ -3004,7 +3041,7 @@ const installCommand = {
3004
3041
  const { resolvePlan } = await import("./chunks/plan-BmTat6-T.mjs");
3005
3042
  const plan = resolvePlan(readPlan(planPath));
3006
3043
  if (getSite(plan.alias)) throw new CliError("site-exists", `A site named "${plan.alias}" is already registered.`, "Use another alias, or `agentcrm site forget` it first.");
3007
- const { checkPlan, runInstall } = await import("./chunks/install-Bp-CwDVC.mjs");
3044
+ const { checkPlan, runInstall } = await import("./chunks/install-D1lgtRIH.mjs");
3008
3045
  io.err("Checking access…");
3009
3046
  const checks = await checkPlan(plan);
3010
3047
  const summary = {
@@ -3360,7 +3397,7 @@ const optionsCommand = {
3360
3397
  async run({ io, options }) {
3361
3398
  const rulesEmail = strOpt(options, "firebase-rules");
3362
3399
  if (rulesEmail) {
3363
- const { buildFirestoreRules } = await import("./chunks/firebaseRules-iori-s7U.mjs");
3400
+ const { buildFirestoreRules } = await import("./chunks/firebaseRules-Jq85MBBN.mjs");
3364
3401
  const rules = buildFirestoreRules({ adminEmail: rulesEmail });
3365
3402
  printResult(io, { email: rulesEmail, rules, where: "Firebase console → Firestore Database → Rules → paste → Publish (or `firebase deploy --only firestore:rules`)." }, () => rules);
3366
3403
  return;
@@ -3438,13 +3475,13 @@ const objectsCommand = {
3438
3475
  switch (action) {
3439
3476
  case "list":
3440
3477
  return withSession(io, options, {}, async ({ ctx, store }) => {
3441
- const { listObjects } = await import("./chunks/schema-C4lSslXv.mjs");
3478
+ const { listObjects } = await import("./chunks/schema-DOnnH2zL.mjs");
3442
3479
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3443
3480
  return listObjects(ctx).map((o) => ({ id: o.id, labelSingular: o.labelSingular, labelPlural: o.labelPlural, standard: o.standard, labelField: o.labelField, fields: o.fields.length, records: recordsOf(store, o.id).length }));
3444
3481
  }, (rows) => table(rows, ["id", "labelPlural", "fields", "records", "standard"]));
3445
3482
  case "get": {
3446
3483
  const objectId = positional(positionals, 1, "object id", "objects get <id>");
3447
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).describeSchema(ctx, objectId)[0]);
3484
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/schema-DOnnH2zL.mjs")).describeSchema(ctx, objectId)[0]);
3448
3485
  }
3449
3486
  case "create": {
3450
3487
  const data = jsonObjectOpt(options, "data") ?? {};
@@ -3457,7 +3494,7 @@ const objectsCommand = {
3457
3494
  ...strOpt(options, "color") ? { color: strOpt(options, "color") } : {}
3458
3495
  };
3459
3496
  if (!input.labelSingular || !input.labelPlural) usage("objects create needs --singular and --plural (or --data).");
3460
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).createObject(ctx, input));
3497
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-DOnnH2zL.mjs")).createObject(ctx, input));
3461
3498
  }
3462
3499
  case "update": {
3463
3500
  const objectId = positional(positionals, 1, "object id", "objects update <id> …");
@@ -3469,12 +3506,12 @@ const objectsCommand = {
3469
3506
  ...strOpt(options, "label-field") ? { labelField: strOpt(options, "label-field") } : {}
3470
3507
  };
3471
3508
  if (!Object.keys(patch).length) usage("objects update needs --singular, --plural, --icon, --color or --label-field.");
3472
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).updateObject(ctx, objectId, patch));
3509
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-DOnnH2zL.mjs")).updateObject(ctx, objectId, patch));
3473
3510
  }
3474
3511
  case "delete": {
3475
3512
  if (!id) usage("objects delete needs an object id.");
3476
3513
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx, store }) => {
3477
- const { getObject, deleteObject } = await import("./chunks/schema-C4lSslXv.mjs");
3514
+ const { getObject, deleteObject } = await import("./chunks/schema-DOnnH2zL.mjs");
3478
3515
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3479
3516
  const o = getObject(ctx, id);
3480
3517
  const plan = { object: o.id, label: o.labelPlural, standard: o.standard, recordsDeleted: recordsOf(store, o.id, { includeDeleted: true }).length };
@@ -3532,7 +3569,7 @@ const fieldsCommand = {
3532
3569
  ...boolOpt(options, "unique") ? { unique: true } : {},
3533
3570
  ...strOpt(options, "description") ? { description: strOpt(options, "description") } : {}
3534
3571
  };
3535
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).createField(ctx, objectId, input));
3572
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-DOnnH2zL.mjs")).createField(ctx, objectId, input));
3536
3573
  }
3537
3574
  case "update": {
3538
3575
  if (!fieldId) usage("fields update needs a field id.");
@@ -3549,12 +3586,12 @@ const fieldsCommand = {
3549
3586
  ...strOpt(options, "description") !== void 0 ? { description: strOpt(options, "description") } : {}
3550
3587
  };
3551
3588
  if (!Object.keys(patch).length) usage("fields update needs at least one change.");
3552
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).updateField(ctx, objectId, fieldId, patch));
3589
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-DOnnH2zL.mjs")).updateField(ctx, objectId, fieldId, patch));
3553
3590
  }
3554
3591
  case "delete": {
3555
3592
  if (!fieldId) usage("fields delete needs a field id.");
3556
3593
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx, store }) => {
3557
- const { getObject, deleteField } = await import("./chunks/schema-C4lSslXv.mjs");
3594
+ const { getObject, deleteField } = await import("./chunks/schema-DOnnH2zL.mjs");
3558
3595
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3559
3596
  const field = getObject(ctx, objectId).fields.find((f) => f.id === fieldId);
3560
3597
  if (!field) usage(`No field "${fieldId}" on ${objectId}.`, `\`agentcrm objects get ${objectId}\` lists them.`);
@@ -3568,6 +3605,50 @@ const fieldsCommand = {
3568
3605
  }
3569
3606
  }
3570
3607
  };
3608
+ const schemaCommand = {
3609
+ name: "schema",
3610
+ usage: "schema apply <file|-> [--yes]",
3611
+ summary: "Bring the data model in line with a JSON description ({ objects: [{ id, labelSingular, labelPlural, icon, color, labelField, fields: [{ id, label, type, options, relation… }] }], pipelines: [{ id, name, object, stages }], views: [{ object, name, columns, filters, sorts, groupBy, shared }] }). Creates what is missing and updates what differs; never deletes, never changes a field type (reported as a conflict), never removes a select option or a stage; reports custom fields the description doesn't list. Prints the plan unless --yes.",
3612
+ subcommands: [{ name: "apply", usage: "schema apply <file|-> [--yes]", summary: "Apply a schema description (plan unless --yes).", writes: true }],
3613
+ options: [SITE_OPTION, AS_OPTION, YES_OPTION],
3614
+ async run({ io, options, positionals }) {
3615
+ const [action, source] = positionals;
3616
+ if (action !== "apply") usage(`Unknown schema action "${action ?? ""}".`, "Use schema apply <file|-> [--yes].");
3617
+ if (!source) usage("schema apply needs a file (or - for stdin).", "Example: agentcrm schema apply crm-schema.json");
3618
+ const { readTextValue: readTextValue2 } = await Promise.resolve().then(() => util);
3619
+ const text = readTextValue2(source === "-" ? "-" : `@${source}`, "schema");
3620
+ let raw;
3621
+ try {
3622
+ raw = JSON.parse(text);
3623
+ } catch (err) {
3624
+ usage(`The schema is not valid JSON: ${err.message}`);
3625
+ }
3626
+ const apply = options.yes === true;
3627
+ const { applySchema, parseSchemaSpec } = await import("./chunks/schemaApply-DKHipylx.mjs");
3628
+ const spec = parseSchemaSpec(raw);
3629
+ return withSession(
3630
+ io,
3631
+ options,
3632
+ { lock: apply },
3633
+ async ({ ctx }) => {
3634
+ if (apply) return applySchema(ctx, spec);
3635
+ const { dryRun: dryRun2 } = await Promise.resolve().then(() => dryRun$1);
3636
+ const { result } = await dryRun2(ctx, (copy) => applySchema(copy, spec));
3637
+ return result.changes.length ? { needsConfirmation: true, plan: result } : { ...result, upToDate: true };
3638
+ },
3639
+ (r) => {
3640
+ const report = "needsConfirmation" in r ? r.plan : r;
3641
+ const lines = report.changes.map((c) => ` ${c.action} ${c.kind} ${c.target}${c.changes?.length ? ` (${c.changes.join(", ")})` : ""}`);
3642
+ const extra = [
3643
+ ...report.conflicts.map((c) => ` conflict: ${c}`),
3644
+ ...report.extraFields.length ? [` not in the schema (kept): ${report.extraFields.join(", ")}`] : []
3645
+ ];
3646
+ if ("needsConfirmation" in r) return [`Would change ${report.changes.length} thing(s):`, ...lines, ...extra, "Nothing changed — re-run with --yes once the user agrees."].join("\n");
3647
+ return [report.changes.length ? `Applied ${report.changes.length} change(s):` : "The data model already matches the schema.", ...lines, ...extra].join("\n");
3648
+ }
3649
+ );
3650
+ }
3651
+ };
3571
3652
  const MIME = {
3572
3653
  ".html": "text/html; charset=utf-8",
3573
3654
  ".js": "text/javascript; charset=utf-8",
@@ -3861,7 +3942,7 @@ const toolsCommand = {
3861
3942
  async run({ io, options, positionals }) {
3862
3943
  const [action = "list"] = positionals;
3863
3944
  if (action === "list") {
3864
- const { listAiTools, AI_TOOLS } = await import("./chunks/tools-iSH-yMpl.mjs");
3945
+ const { listAiTools, AI_TOOLS } = await import("./chunks/tools-CCk24tdh.mjs");
3865
3946
  const builtin = new Set(AI_TOOLS.map((t) => t.name));
3866
3947
  const shape = () => listAiTools().map((t) => ({ name: t.name, description: t.description, mutating: t.mutating, source: builtin.has(t.name) ? "builtin" : "extension", inputSchema: t.inputSchema }));
3867
3948
  const human = (r) => table(r.tools, ["name", "mutating", "source", "description"]);
@@ -3881,7 +3962,7 @@ const toolsCommand = {
3881
3962
  }
3882
3963
  if (action === "call") {
3883
3964
  const name2 = positional(positionals, 1, "tool name", "tools call <name> --input <json>");
3884
- const { findTool, callTool, listAiTools } = await import("./chunks/tools-iSH-yMpl.mjs");
3965
+ const { findTool, callTool, listAiTools } = await import("./chunks/tools-CCk24tdh.mjs");
3885
3966
  const input = jsonObjectOpt(options, "input") ?? {};
3886
3967
  const builtin = findTool(name2);
3887
3968
  return withSession(io, options, { lock: builtin ? builtin.mutating : true }, async ({ ctx }) => {
@@ -3903,8 +3984,8 @@ const mcpCommand = {
3903
3984
  const log = (msg) => process.stderr.write(`${msg}
3904
3985
  `);
3905
3986
  for (const w of session2.warnings) log(`[agentcrm mcp] note: ${w}`);
3906
- const { listAiTools } = await import("./chunks/tools-iSH-yMpl.mjs");
3907
- const { createMcpServer, serveStdio } = await import("./chunks/server-DC3t8r30.mjs");
3987
+ const { listAiTools } = await import("./chunks/tools-CCk24tdh.mjs");
3988
+ const { createMcpServer, serveStdio } = await import("./chunks/server-Bch4TQon.mjs");
3908
3989
  let lastRefresh = Date.now();
3909
3990
  const server = createMcpServer({
3910
3991
  readOnly,
@@ -4006,6 +4087,7 @@ const COMMANDS = [
4006
4087
  deployAdminCommand,
4007
4088
  recordsCommand,
4008
4089
  objectsCommand,
4090
+ schemaCommand,
4009
4091
  fieldsCommand,
4010
4092
  activitiesCommand,
4011
4093
  notesCommand,
@@ -4102,14 +4184,17 @@ export {
4102
4184
  findHosting as e,
4103
4185
  findBackend as f,
4104
4186
  getCredential as g,
4105
- openStore as h,
4106
- installBrowserEnv as i,
4107
- distRoot as j,
4187
+ readJsonFile as h,
4188
+ homePath as i,
4189
+ openStore as j,
4190
+ installBrowserEnv as k,
4191
+ distRoot as l,
4108
4192
  nodeImportBundle as n,
4109
4193
  ownPackageRoot as o,
4110
4194
  packageRoot as p,
4111
4195
  readVersionInfo as r,
4112
4196
  setCredential as s,
4113
4197
  toCliError as t,
4114
- validateAlias as v
4198
+ validateAlias as v,
4199
+ writeJsonFile as w
4115
4200
  };