@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
@@ -5,8 +5,8 @@ import { dirname as __acrmDirname } from "node:path";
5
5
  const require2 = __acrmRequire(import.meta.url);
6
6
  const __filename = __acrmPath(import.meta.url);
7
7
  const __dirname = __acrmDirname(__filename);
8
- import { parseArgs } from "node:util";
9
- import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync, chmodSync, renameSync, openSync, writeSync, closeSync, unlinkSync, mkdtempSync, readdirSync, createReadStream } from "node:fs";
8
+ import { format, parseArgs } from "node:util";
9
+ import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync, chmodSync, renameSync, openSync, writeSync, closeSync, unlinkSync, mkdtempSync, rmSync, readdirSync, createReadStream } from "node:fs";
10
10
  import { join, resolve, dirname, basename, extname, sep, normalize, isAbsolute } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { randomBytes, timingSafeEqual } from "node:crypto";
@@ -54,6 +54,13 @@ class CliError extends Error {
54
54
  this.exitCode = exitCode;
55
55
  }
56
56
  }
57
+ function routeConsoleToStderr(target = console, write = (t) => void process.stderr.write(t)) {
58
+ const toStderr = (...args) => write(`${format(...args)}
59
+ `);
60
+ target.log = toStderr;
61
+ target.info = toStderr;
62
+ target.debug = toStderr;
63
+ }
57
64
  function createIo(json) {
58
65
  return {
59
66
  json,
@@ -68,10 +75,20 @@ function printResult(io, data, human) {
68
75
  const OPS_HINTS = {
69
76
  forbidden: "The CRM user the CLI acts as lacks this permission (see `site show` → user, `users list`). Ask an admin, or re-register the site with an admin user (`site add … --user <email>`).",
70
77
  notFound: "Check the id (list commands print them: `records list --object …`, `objects list`, `views list`…).",
71
- validation: "Fix the input: `objects get <object>` lists field ids, types and select options.",
78
+ validation: "Fix the input and retry (`agentcrm help <command>` lists the options; for record fields, `objects get <object>` lists field ids, types and select options).",
72
79
  conflict: "Something with the same key already exists — list it first, then update instead of creating.",
73
80
  invalid: "Check the arguments with `agentcrm help <command>`."
74
81
  };
82
+ function dependencyHint(details) {
83
+ const ids = (key) => Array.isArray(details?.[key]) ? details[key].map(String) : [];
84
+ const blockers = ids("blockers");
85
+ if (blockers.length) return `Blocked by: ${blockers.join(", ")}. Re-run with --cascade (AI tools: "cascade": true) to change ${blockers.length > 1 ? "them" : "it"} too — show the user the plan's \`changed\` list first.`;
86
+ const dependents = ids("dependents");
87
+ if (dependents.length) return `Disable or uninstall ${dependents.join(", ")} first (\`ext disable <id>\` / \`ext uninstall <id>\`), or pass --force to \`ext uninstall\`.`;
88
+ const missing = ids("missing");
89
+ if (missing.length) return `Install the missing module(s) first: ${missing.join(", ")} (\`ext install <zip>\`).`;
90
+ return null;
91
+ }
75
92
  function knownError(error) {
76
93
  if (!(error instanceof Error)) return null;
77
94
  if (error.name === "PassphraseError") {
@@ -84,7 +101,8 @@ function knownError(error) {
84
101
  }
85
102
  if (error.name === "OpsError") {
86
103
  const code = error.code ?? "invalid";
87
- return new CliError(code, error.message, OPS_HINTS[code], code === "validation" || code === "invalid" ? 2 : 1);
104
+ const details = error.details;
105
+ return new CliError(code, error.message, code === "conflict" && dependencyHint(details) || OPS_HINTS[code], code === "validation" || code === "invalid" ? 2 : 1);
88
106
  }
89
107
  if (error.name === "FieldValidationError") {
90
108
  return new CliError("validation", error.message, OPS_HINTS.validation, 2);
@@ -120,10 +138,11 @@ const output = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
120
138
  knownError,
121
139
  printError,
122
140
  printResult,
141
+ routeConsoleToStderr,
123
142
  toCliError
124
143
  }, Symbol.toStringTag, { value: "Module" }));
125
144
  const name = "@fleox-agent-crm/agentcrm";
126
- const version = "0.3.0";
145
+ const version = "0.3.2";
127
146
  const type = "module";
128
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.";
129
148
  const license = "SEE LICENSE IN LICENSE";
@@ -505,8 +524,8 @@ function tmpFolder() {
505
524
  return tmpBase;
506
525
  }
507
526
  async function nodeImportBundle() {
508
- await import("./chunks/runtime-DHo7ZEEO.mjs");
509
- const { createNodeImportBundle } = await import("./chunks/nodeImport-BPCt5uMu.mjs");
527
+ await import("./chunks/runtime-C0Xf0Axi.mjs");
528
+ const { createNodeImportBundle } = await import("./chunks/nodeImport-ylfRDvqt.mjs");
510
529
  return createNodeImportBundle({ runtimeDir: runtimeDir(), tmpDir: tmpFolder() });
511
530
  }
512
531
  function hasEnabledExtensions(store) {
@@ -520,15 +539,9 @@ async function loadSessionExtensions(store, opts = {}) {
520
539
  if (!loadedAny && !opts.force && !hasEnabledExtensions(store)) return null;
521
540
  try {
522
541
  const importBundle = await nodeImportBundle();
523
- const { syncExtensions } = await import("./chunks/loader-C9nm3iwx.mjs");
542
+ const { syncExtensions } = await import("./chunks/loader-MPD9iAvh.mjs");
524
543
  loadedAny = true;
525
- const original = console.error;
526
- console.error = () => void 0;
527
- try {
528
- return await syncExtensions(store, { importBundle });
529
- } finally {
530
- console.error = original;
531
- }
544
+ return await syncExtensions(store, { importBundle, onError: () => void 0 });
532
545
  } catch (err) {
533
546
  return { loaded: [], unloaded: [], failed: [{ id: "*", error: err.message }], skipped: [] };
534
547
  }
@@ -540,7 +553,7 @@ async function loadSessionExtensions(store, opts = {}) {
540
553
  sync,
541
554
  async close() {
542
555
  if (!loadedAny) return;
543
- (await import("./chunks/registry-Ceexawxl.mjs")).deactivateAllExtensions();
556
+ (await import("./chunks/registry-COfJrSmg.mjs")).deactivateAllExtensions();
544
557
  }
545
558
  };
546
559
  }
@@ -595,7 +608,7 @@ async function openStore(runtime, opts) {
595
608
  }
596
609
  if (runtime.backend === "journal") {
597
610
  if (!opts.dataTarget) throw new CliError("internal", "journal store without a data location");
598
- const { buildConnector } = await import("./chunks/hosting-Bme7LxPc.mjs");
611
+ const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
599
612
  const { createJournalStore } = await import("./chunks/index-B-4W0-P4.mjs");
600
613
  const { publicSettings, secrets } = rc.splitSecretSettings(opts.dataTarget.settings);
601
614
  rc.setConnectorSecrets(runtime, secrets);
@@ -612,7 +625,11 @@ async function openStore(runtime, opts) {
612
625
  const token = String(opts.secrets?.token ?? "");
613
626
  if (token) rc.setGitToken(runtime.git, token);
614
627
  }
615
- const { createDataStore } = await import("./chunks/index-CRkw3_iF.mjs");
628
+ if (runtime.backend === "firebase" && opts.account && typeof opts.secrets?.email === "string") {
629
+ const { prepareFirebaseSession: prepareFirebaseSession2 } = await Promise.resolve().then(() => firebaseSession);
630
+ await prepareFirebaseSession2(opts.account, runtime.firebase, opts.secrets.email);
631
+ }
632
+ const { createDataStore } = await import("./chunks/index-rut-KC39.mjs");
616
633
  return createDataStore(runtime, { secrets: opts.secrets ?? {}, passphrase: opts.passphrase ?? null, clientId: cliClientId() });
617
634
  }
618
635
  function siteSecrets(site) {
@@ -626,7 +643,7 @@ function siteSecrets(site) {
626
643
  `Store it (\`agentcrm credentials set ${site.runtime.backend === "firebase" ? "firebase" : site.runtime.git.provider}:<name>\`), then \`agentcrm site credentials ${site.alias} --data-credential <account>\`.`
627
644
  );
628
645
  }
629
- return { secrets: requireCredential(site.credentials.data).values, passphrase };
646
+ return { secrets: requireCredential(site.credentials.data).values, passphrase, account: site.credentials.data };
630
647
  }
631
648
  return { passphrase };
632
649
  }
@@ -663,7 +680,7 @@ async function openSession(alias, opts = {}) {
663
680
  const [{ createOpsContext }, { createPluginRegistry, plugins: defaultPlugins }, { installAutomations }] = await Promise.all([
664
681
  import("./chunks/context-CxFfqS4D.mjs"),
665
682
  import("./chunks/pluginRegistry-DyHtlkcr.mjs"),
666
- import("./chunks/automations-BWrvLqP6.mjs")
683
+ import("./chunks/automations-DwWruxmu.mjs")
667
684
  ]);
668
685
  const own = createPluginRegistry((hook, err) => process.stderr.write(`[agentcrm] ${hook} handler failed: ${String(err?.message ?? err)}
669
686
  `));
@@ -690,14 +707,14 @@ async function openSession(alias, opts = {}) {
690
707
  })();
691
708
  if (target) {
692
709
  const { setStorageConfigSource, registerStorageConnector } = await import("./chunks/registry-DijAKZ24.mjs").then((n) => n.r);
693
- const { buildConnector } = await import("./chunks/hosting-Bme7LxPc.mjs");
710
+ const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
694
711
  if (target.connectorId === "local-folder") registerStorageConnector({ id: "local-folder", create: (s) => buildConnector({ connectorId: "local-folder", settings: s }) });
695
712
  setStorageConfigSource(async () => target);
696
713
  }
697
714
  }
698
715
  {
699
- const forms = await import("./chunks/forms-BKU5F9WF.mjs");
700
- const { buildConnector } = await import("./chunks/hosting-Bme7LxPc.mjs");
716
+ const forms = await import("./chunks/forms-BfuWaQjA.mjs");
717
+ const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
701
718
  forms.setInboxSourceResolver((_ctx, form) => {
702
719
  if (form.inbox.kind !== "s3") return null;
703
720
  const settings = form.inbox.settings ?? {};
@@ -768,6 +785,12 @@ const session = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
768
785
  openStore,
769
786
  withSession
770
787
  }, Symbol.toStringTag, { value: "Module" }));
788
+ function writeOutFile(path, data) {
789
+ const abs = resolve(path);
790
+ mkdirSync(dirname(abs), { recursive: true });
791
+ writeFileSync(abs, data);
792
+ return abs;
793
+ }
771
794
  function usage(message, hint) {
772
795
  throw new CliError("usage", message, hint, 2);
773
796
  }
@@ -912,7 +935,7 @@ function readLocalFile(path) {
912
935
  return { name: basename(full), bytes: new Uint8Array(readFileSync(full)) };
913
936
  }
914
937
  async function uploadLocalFiles(ctx, paths, targets2 = []) {
915
- const { uploadFile } = await import("./chunks/files-q8Xu5bft.mjs");
938
+ const { uploadFile } = await import("./chunks/files-D1daDgY0.mjs");
916
939
  const { guessMime, imageDimensions } = await import("./chunks/files-CjsPJSR6.mjs");
917
940
  const out = [];
918
941
  for (const p of paths) {
@@ -960,7 +983,7 @@ const filesCommand = {
960
983
  switch (action) {
961
984
  case "list":
962
985
  return withSession(io, options, {}, async ({ ctx }) => {
963
- const { listFiles } = await import("./chunks/files-q8Xu5bft.mjs");
986
+ const { listFiles } = await import("./chunks/files-D1daDgY0.mjs");
964
987
  const target = strOpt(options, "record") ?? strOpt(options, "activity") ?? null;
965
988
  const items = listFiles(ctx, target, { includeRelated: boolOpt(options, "related"), kind: strOpt(options, "kind"), search: strOpt(options, "search") });
966
989
  const limit = Math.min(1e3, numOpt(options, "limit") ?? 100);
@@ -975,7 +998,7 @@ ${table(r.files, ["id", "name", "mime", "size", "createdAt"])}`);
975
998
  if (record && activity) usage("Give --record or --activity, not both.");
976
999
  for (const p of args) readLocalFile(p);
977
1000
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
978
- const { attachFile } = await import("./chunks/files-q8Xu5bft.mjs");
1001
+ const { attachFile } = await import("./chunks/files-D1daDgY0.mjs");
979
1002
  const files2 = await uploadLocalFiles(ctx, args, record ? [record] : []);
980
1003
  if (activity) for (const f of files2) await attachFile(ctx, f.id, { kind: "activity", id: activity });
981
1004
  const { requireFile } = await import("./chunks/store-5gqRSKXg.mjs");
@@ -988,17 +1011,17 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
988
1011
  const out = strOpt(options, "out");
989
1012
  if (!out) usage("files get needs --out <path>.");
990
1013
  return withSession(io, options, {}, async ({ ctx }) => {
991
- const { getFile, getFileBytes } = await import("./chunks/files-q8Xu5bft.mjs");
1014
+ const { getFile, getFileBytes } = await import("./chunks/files-D1daDgY0.mjs");
992
1015
  const meta = getFile(ctx, id);
993
1016
  const bytes = await getFileBytes(ctx, id, { thumb: boolOpt(options, "thumb") });
994
- writeFileSync(resolve(out), bytes);
1017
+ writeOutFile(out, bytes);
995
1018
  return { id, name: meta.name, mime: meta.mime, bytes: bytes.length, path: resolve(out) };
996
1019
  }, (r) => `Wrote ${r.bytes} bytes (${r.name}) to ${r.path}`);
997
1020
  }
998
1021
  case "delete": {
999
1022
  const id = positional(args, 0, "file id", U$1.delete);
1000
1023
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1001
- const { getFile, deleteFile } = await import("./chunks/files-q8Xu5bft.mjs");
1024
+ const { getFile, deleteFile } = await import("./chunks/files-D1daDgY0.mjs");
1002
1025
  const meta = fileOut(getFile(ctx, id));
1003
1026
  if (options.yes !== true) return { needsConfirmation: true, plan: { delete: meta } };
1004
1027
  await deleteFile(ctx, id);
@@ -1006,7 +1029,7 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
1006
1029
  }, (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\`).`);
1007
1030
  }
1008
1031
  case "usage":
1009
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/files-q8Xu5bft.mjs")).fileUsage(ctx));
1032
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/files-D1daDgY0.mjs")).fileUsage(ctx));
1010
1033
  default:
1011
1034
  usage(`Unknown files action "${action ?? ""}".`, "Use list, upload, get, delete or usage.");
1012
1035
  }
@@ -1019,7 +1042,7 @@ async function recordImageAction(io, options, args) {
1019
1042
  if (!remove && !path) usage("records image needs an image path (or --remove).");
1020
1043
  const local = remove ? null : readLocalFile(path);
1021
1044
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1022
- const { setRecordImage, clearRecordImage } = await import("./chunks/files-q8Xu5bft.mjs");
1045
+ const { setRecordImage, clearRecordImage } = await import("./chunks/files-D1daDgY0.mjs");
1023
1046
  const field = strOpt(options, "field");
1024
1047
  if (!local) {
1025
1048
  const r = await clearRecordImage(ctx, recordId, field);
@@ -1307,7 +1330,7 @@ const recordsCommand = {
1307
1330
  const filters = parseFilters(multiOpt(options, "filter"), FILTER_OPS);
1308
1331
  const sorts = parseSorts(multiOpt(options, "sort"));
1309
1332
  return withSession(io, options, {}, async ({ ctx }) => {
1310
- const { listRecords } = await import("./chunks/records-DoqiT9xR.mjs");
1333
+ const { listRecords } = await import("./chunks/records-DcHFelq5.mjs");
1311
1334
  const page = listRecords(ctx, objectId, {
1312
1335
  filters,
1313
1336
  sorts,
@@ -1325,7 +1348,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1325
1348
  case "get": {
1326
1349
  const id = positional(args, 0, "record id", U.get);
1327
1350
  return withSession(io, options, {}, async ({ ctx }) => {
1328
- const { getRecordDetails } = await import("./chunks/records-DoqiT9xR.mjs");
1351
+ const { getRecordDetails } = await import("./chunks/records-DcHFelq5.mjs");
1329
1352
  const d = getRecordDetails(ctx, id);
1330
1353
  return { ...await recordOut(ctx, d.record), relations: d.relations, related: d.inverse.filter((i) => i.records.length), activityCount: d.activityCount };
1331
1354
  });
@@ -1336,7 +1359,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1336
1359
  if (!Object.keys(fields).length) usage("records create needs --fields or --set.", `Usage: agentcrm ${U.create}`);
1337
1360
  const owner = strOpt(options, "owner");
1338
1361
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1339
- const { createRecord } = await import("./chunks/records-DoqiT9xR.mjs");
1362
+ const { createRecord } = await import("./chunks/records-DcHFelq5.mjs");
1340
1363
  return recordOut(ctx, await createRecord(ctx, objectId, fields, owner ? { ownerId: owner } : {}));
1341
1364
  }, (r) => `Created ${r.object} ${r.id} (${String(r.label)})`);
1342
1365
  }
@@ -1346,7 +1369,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1346
1369
  const owner = strOpt(options, "owner");
1347
1370
  if (!Object.keys(fields).length && owner === void 0) usage("records update needs --fields, --set or --owner.");
1348
1371
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1349
- const { updateRecord } = await import("./chunks/records-DoqiT9xR.mjs");
1372
+ const { updateRecord } = await import("./chunks/records-DcHFelq5.mjs");
1350
1373
  return recordOut(ctx, await updateRecord(ctx, id, fields, owner !== void 0 ? { ownerId: owner || null } : {}));
1351
1374
  }, (r) => `Updated ${r.id} (${String(r.label)})`);
1352
1375
  }
@@ -1354,7 +1377,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1354
1377
  if (!args.length) usage("records delete needs at least one id.", `Usage: agentcrm ${U.delete}`);
1355
1378
  const hard = boolOpt(options, "hard");
1356
1379
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1357
- const { getRecord, deleteRecord, bulkDelete } = await import("./chunks/records-DoqiT9xR.mjs");
1380
+ const { getRecord, deleteRecord, bulkDelete } = await import("./chunks/records-DcHFelq5.mjs");
1358
1381
  const { recordLabel } = await import("./chunks/fields-B0bitgmr.mjs");
1359
1382
  const { objectOf } = await import("./chunks/store-5gqRSKXg.mjs");
1360
1383
  const targets2 = args.map((id) => getRecord(ctx, id, { includeDeleted: true }));
@@ -1367,7 +1390,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1367
1390
  case "restore": {
1368
1391
  const id = positional(args, 0, "record id", U.restore);
1369
1392
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1370
- const { restoreRecord } = await import("./chunks/records-DoqiT9xR.mjs");
1393
+ const { restoreRecord } = await import("./chunks/records-DcHFelq5.mjs");
1371
1394
  return recordOut(ctx, await restoreRecord(ctx, id));
1372
1395
  });
1373
1396
  }
@@ -1375,7 +1398,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1375
1398
  const query = args.join(" ").trim();
1376
1399
  if (!query) usage("records search needs a query.", `Usage: agentcrm ${U.search}`);
1377
1400
  return withSession(io, options, {}, async ({ ctx }) => {
1378
- const { searchRecords } = await import("./chunks/records-DoqiT9xR.mjs");
1401
+ const { searchRecords } = await import("./chunks/records-DcHFelq5.mjs");
1379
1402
  return searchRecords(ctx, query, { objects: listOpt(options, "object"), limit: Math.min(100, numOpt(options, "limit") ?? 20) });
1380
1403
  });
1381
1404
  }
@@ -1392,7 +1415,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1392
1415
  const [primary, ...dups] = args;
1393
1416
  if (!primary || !dups.length) usage("records merge needs <primaryId> <duplicateId>….", `Usage: agentcrm ${U.merge}`);
1394
1417
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1395
- const { getRecord } = await import("./chunks/records-DoqiT9xR.mjs");
1418
+ const { getRecord } = await import("./chunks/records-DcHFelq5.mjs");
1396
1419
  const all = [primary, ...dups].map((id) => getRecord(ctx, id));
1397
1420
  const plan = { primary: await recordOut(ctx, all[0]), duplicates: await Promise.all(all.slice(1).map((r) => recordOut(ctx, r))) };
1398
1421
  if (options.yes !== true) return { needsConfirmation: true, plan };
@@ -1424,13 +1447,13 @@ const pipelinesCommand = {
1424
1447
  io,
1425
1448
  options,
1426
1449
  {},
1427
- async ({ ctx }) => (await import("./chunks/pipelines-DCdRBNBV.mjs")).listPipelines(ctx, strOpt(options, "object")),
1450
+ async ({ ctx }) => (await import("./chunks/pipelines-BAKpPk1w.mjs")).listPipelines(ctx, strOpt(options, "object")),
1428
1451
  (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")
1429
1452
  );
1430
1453
  }
1431
1454
  if (action === "move") {
1432
1455
  if (!id || !stage.length) usage("pipelines move needs <recordId> <stage>.");
1433
- return withSession(io, options, { lock: true }, async ({ ctx }) => recordOut(ctx, await (await import("./chunks/pipelines-DCdRBNBV.mjs")).moveStage(ctx, id, stage.join(" "), { lostReason: strOpt(options, "lost-reason") })));
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") })));
1434
1457
  }
1435
1458
  usage(`Unknown pipelines action "${action}".`, "Use list or move.");
1436
1459
  }
@@ -1462,7 +1485,7 @@ const viewsCommand = {
1462
1485
  const [action = "list", id] = positionals;
1463
1486
  switch (action) {
1464
1487
  case "list":
1465
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/views-fO_BM5U_.mjs")).listViews(ctx, strOpt(options, "object")), (vs) => table(vs, ["id", "object", "name", "type", "shared"]));
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"]));
1466
1489
  case "create": {
1467
1490
  const type2 = strOpt(options, "type") ?? "table";
1468
1491
  if (type2 !== "table" && type2 !== "kanban") usage("--type must be table or kanban.");
@@ -1477,12 +1500,12 @@ const viewsCommand = {
1477
1500
  ...strOpt(options, "pipeline") ? { pipelineId: strOpt(options, "pipeline") } : {},
1478
1501
  shared: boolOpt(options, "shared")
1479
1502
  };
1480
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/views-fO_BM5U_.mjs")).createView(ctx, input));
1503
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/views-DH1n9Lrt.mjs")).createView(ctx, input));
1481
1504
  }
1482
1505
  case "delete":
1483
1506
  if (!id) usage("views delete needs a view id.");
1484
1507
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1485
- const { getView, deleteView } = await import("./chunks/views-fO_BM5U_.mjs");
1508
+ const { getView, deleteView } = await import("./chunks/views-DH1n9Lrt.mjs");
1486
1509
  const v = getView(ctx, id);
1487
1510
  if (options.yes !== true) return { needsConfirmation: true, plan: { delete: { id: v.id, name: v.name, object: v.object, shared: v.shared } } };
1488
1511
  return deleteView(ctx, id);
@@ -1545,11 +1568,11 @@ const settingsCommand = {
1545
1568
  options: [SITE_OPTION, AS_OPTION, { name: "set", type: "string", description: "key=value (repeatable), e.g. --set staleDealDays=21 --set defaultCurrency=EUR.", multiple: true }, { name: "data", type: "string", description: "JSON object of settings, or @file." }],
1546
1569
  async run({ io, options, positionals }) {
1547
1570
  const [action = "get"] = positionals;
1548
- if (action === "get") return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/settings-BtvR_jdm.mjs")).getSettings(ctx));
1571
+ if (action === "get") return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/settings-Di8nzNEL.mjs")).getSettings(ctx));
1549
1572
  if (action === "set") {
1550
1573
  const patch = fieldsInput(options, "data");
1551
1574
  if (!Object.keys(patch).length) usage("settings set needs --set key=value or --data.");
1552
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/settings-BtvR_jdm.mjs")).updateSettings(ctx, patch));
1575
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/settings-Di8nzNEL.mjs")).updateSettings(ctx, patch));
1553
1576
  }
1554
1577
  usage(`Unknown settings action "${action}".`, "Use get or set.");
1555
1578
  }
@@ -1651,6 +1674,57 @@ function copyToClipboard(text) {
1651
1674
  }
1652
1675
  throw new CliError("no-clipboard", "No clipboard tool found (pbcopy, clip, wl-copy, xclip or xsel).", "Install one, or read the value from ~/.agentcrm/credentials.json yourself.");
1653
1676
  }
1677
+ function sessionFile(account) {
1678
+ return homePath("sessions", `${account.replace(/[^a-z0-9._-]+/gi, "_")}.json`);
1679
+ }
1680
+ function forgetFirebaseSession(account) {
1681
+ const f = sessionFile(account);
1682
+ if (existsSync(f)) rmSync(f, { force: true });
1683
+ }
1684
+ function filePersistence(account, email) {
1685
+ const file2 = sessionFile(account);
1686
+ const saved = readJsonFile(file2, null);
1687
+ const items = saved && saved.email === email ? { ...saved.items } : {};
1688
+ const flush = () => {
1689
+ if (Object.keys(items).length) writeJsonFile(file2, { email, items }, 384);
1690
+ else forgetFirebaseSession(account);
1691
+ };
1692
+ return class FileSessionPersistence {
1693
+ static type = "LOCAL";
1694
+ type = "LOCAL";
1695
+ async _isAvailable() {
1696
+ return true;
1697
+ }
1698
+ async _set(key, value) {
1699
+ items[key] = value;
1700
+ flush();
1701
+ }
1702
+ async _get(key) {
1703
+ return items[key] ?? null;
1704
+ }
1705
+ async _remove(key) {
1706
+ delete items[key];
1707
+ flush();
1708
+ }
1709
+ _addListener() {
1710
+ }
1711
+ _removeListener() {
1712
+ }
1713
+ };
1714
+ }
1715
+ async function prepareFirebaseSession(account, options, email) {
1716
+ const { getFirebaseApp } = await import("./chunks/sdk-CYgNyuoK.mjs");
1717
+ const { initializeAuth } = await import("./chunks/index-Dio0aBWA.mjs").then((n) => n.x);
1718
+ try {
1719
+ initializeAuth(getFirebaseApp(options), { persistence: filePersistence(account, email.trim().toLowerCase()) });
1720
+ } catch {
1721
+ }
1722
+ }
1723
+ const firebaseSession = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1724
+ __proto__: null,
1725
+ forgetFirebaseSession,
1726
+ prepareFirebaseSession
1727
+ }, Symbol.toStringTag, { value: "Module" }));
1654
1728
  const credentialsCommand = {
1655
1729
  name: "credentials",
1656
1730
  usage: "credentials <set|list|remove|copy|verify|types> [<type>:<name>] [--from-env VAR[,VAR]] [--from-gh] [--from-aws-profile <name>] [--generate]",
@@ -1691,6 +1765,7 @@ const credentialsCommand = {
1691
1765
  const spec = CREDENTIAL_TYPES[type2];
1692
1766
  if (action === "remove") {
1693
1767
  const removed = removeCredential(account);
1768
+ forgetFirebaseSession(account);
1694
1769
  printResult(io, { account, removed }, () => removed ? `Removed ${account}.` : `${account} was not stored.`);
1695
1770
  return;
1696
1771
  }
@@ -1705,7 +1780,7 @@ const credentialsCommand = {
1705
1780
  return;
1706
1781
  }
1707
1782
  if (!process.stdin.isTTY) {
1708
- throw new CliError("needs-terminal", "verify reads the value the USER pastes in their own terminal.", `Ask the user to run it themselves (in Claude Code: \`! npx -y @fleox-agent-crm/agentcrm credentials verify ${account}\`).`);
1783
+ throw new CliError("needs-terminal", "verify reads the value the USER pastes in their own terminal.", `Ask the user to run it themselves (in Claude Code: \`! npx -y ${PACKAGE_NAME}@${readVersionInfo().version} credentials verify ${account}\`).`);
1709
1784
  }
1710
1785
  try {
1711
1786
  copyToClipboard("");
@@ -1740,6 +1815,7 @@ const credentialsCommand = {
1740
1815
  for (const field of spec.fields) values[field] = await promptHidden(`${field}: `);
1741
1816
  }
1742
1817
  const saved = setCredential(account, values);
1818
+ forgetFirebaseSession(account);
1743
1819
  printResult(io, { account, type: type2, fields: Object.keys(saved.values) }, () => `Stored ${account} (${Object.keys(saved.values).join(", ")}).`);
1744
1820
  }
1745
1821
  };
@@ -1765,6 +1841,18 @@ function parseJsonValue(raw, name2) {
1765
1841
  usage(`--${name2} is not valid JSON: ${err.message}`, `Example: --${name2} '{"targets":{"owner":50000}}' or --${name2} @config.json`);
1766
1842
  }
1767
1843
  }
1844
+ const MODULE_CONFIG_HINT = "Module configuration is JSON (≤ 256 KB) readable by every CRM user: no secrets (apiKey, token, secret, password…). Module ids: `agentcrm modules list`.";
1845
+ async function withHint(hint, p) {
1846
+ try {
1847
+ return await p;
1848
+ } catch (err) {
1849
+ const e = err;
1850
+ if (e?.name === "OpsError" && (e.code === "validation" || e.code === "notFound" || e.code === "invalid")) {
1851
+ throw new CliError(e.code, e.message, hint, e.code === "notFound" ? 1 : 2);
1852
+ }
1853
+ throw err;
1854
+ }
1855
+ }
1768
1856
  const modulesCommand = {
1769
1857
  name: "modules",
1770
1858
  usage: "modules <list|get|enable|disable|config> …",
@@ -1782,13 +1870,13 @@ const modulesCommand = {
1782
1870
  const apply = options.yes === true;
1783
1871
  switch (action) {
1784
1872
  case "list":
1785
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/modules-CqdVQVJb.mjs")).listModules(ctx), (rows) => table(
1873
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/modules-clWdE-K2.mjs")).listModules(ctx), (rows) => table(
1786
1874
  rows.map((m) => ({ ...m, enabled: m.enabled ? "yes" : "no", required: m.required ? "yes" : "", dependsOn: m.dependsOn.join(",") })),
1787
1875
  ["id", "name", "category", "source", "enabled", "required", "dependsOn"]
1788
1876
  ));
1789
1877
  case "get": {
1790
1878
  const id = positional(positionals, 1, "module id", "modules get <id>");
1791
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/modules-CqdVQVJb.mjs")).getModule(ctx, id));
1879
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/modules-clWdE-K2.mjs")).getModule(ctx, id));
1792
1880
  }
1793
1881
  case "enable":
1794
1882
  case "disable": {
@@ -1796,7 +1884,7 @@ const modulesCommand = {
1796
1884
  const enabled = action === "enable";
1797
1885
  const cascade = boolOpt(options, "cascade");
1798
1886
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
1799
- const { setModuleEnabled } = await import("./chunks/modules-CqdVQVJb.mjs");
1887
+ const { setModuleEnabled } = await import("./chunks/modules-clWdE-K2.mjs");
1800
1888
  if (apply) return setModuleEnabled(ctx, id, enabled, { cascade });
1801
1889
  const { result } = await dryRun(ctx, (copy) => setModuleEnabled(copy, id, enabled, { cascade }));
1802
1890
  return { needsConfirmation: true, plan: result };
@@ -1806,20 +1894,20 @@ Nothing changed — re-run with --yes once the user agrees.` : r.changed.length
1806
1894
  case "config": {
1807
1895
  const sub = positionals[1];
1808
1896
  const id = positional(positionals, 2, "module id", "modules config <get|set> <id>");
1809
- if (sub === "get") return withSession(io, options, {}, async ({ ctx }) => ({ id, config: (await import("./chunks/modules-CqdVQVJb.mjs")).getModuleConfig(ctx, id) ?? null }));
1897
+ if (sub === "get") return withSession(io, options, {}, async ({ ctx }) => ({ id, config: (await import("./chunks/modules-clWdE-K2.mjs")).getModuleConfig(ctx, id) ?? null }));
1810
1898
  if (sub !== "set") usage(`Unknown modules config action "${sub ?? ""}".`, "Use modules config get <id> or modules config set <id> --value <json>.");
1811
1899
  const clear = boolOpt(options, "clear");
1812
1900
  const raw = strOpt(options, "value");
1813
1901
  if (!clear && raw === void 0) usage("modules config set needs --value <json|@file|-> (or --clear).");
1814
1902
  const value = clear ? null : parseJsonValue(raw, "value");
1815
- return withSession(io, options, { lock: apply }, async ({ ctx }) => {
1816
- const mod = await import("./chunks/modules-CqdVQVJb.mjs");
1903
+ return withHint(MODULE_CONFIG_HINT, withSession(io, options, { lock: apply }, async ({ ctx }) => {
1904
+ const mod = await import("./chunks/modules-clWdE-K2.mjs");
1817
1905
  mod.getModule(ctx, id);
1818
1906
  const before = mod.getModuleConfig(ctx, id) ?? null;
1819
1907
  if (apply) return { id, config: await mod.setModuleConfig(ctx, id, value) ?? null, before };
1820
1908
  const { result } = await dryRun(ctx, (copy) => mod.setModuleConfig(copy, id, value));
1821
1909
  return { needsConfirmation: true, plan: { id, before, after: result ?? null } };
1822
- });
1910
+ }));
1823
1911
  }
1824
1912
  default:
1825
1913
  usage(`Unknown modules action "${action}".`, "Use list, get, enable, disable or config.");
@@ -1845,14 +1933,27 @@ function imageDataUrl(file2) {
1845
1933
  function view(a, full) {
1846
1934
  if (full) return { ...a };
1847
1935
  const { logo, favicon, customCss, ...rest } = a;
1848
- const kb2 = (s) => Math.round((s.length - s.indexOf(",") - 1) * 3 / 4 / 1024);
1936
+ const image = (s) => `${s.slice(5, s.indexOf(";"))}, ${formatSize(Math.floor((s.length - s.indexOf(",") - 1) * 3 / 4))}, #${shortHash(s)}`;
1849
1937
  return {
1850
1938
  ...rest,
1851
- logo: logo ? `${logo.slice(5, logo.indexOf(";"))}, ${kb2(logo)} KB` : null,
1852
- favicon: favicon ? `${favicon.slice(5, favicon.indexOf(";"))}, ${kb2(favicon)} KB` : null,
1853
- customCss: customCss ? `${new TextEncoder().encode(customCss).length} bytes` : null
1939
+ logo: logo ? image(logo) : null,
1940
+ favicon: favicon ? image(favicon) : null,
1941
+ customCss: customCss ? `${new TextEncoder().encode(customCss).length} bytes, #${shortHash(customCss)}` : null
1854
1942
  };
1855
1943
  }
1944
+ function formatSize(n) {
1945
+ return n < 1024 ? `${n} B` : `${(n / 1024).toFixed(1)} KB`;
1946
+ }
1947
+ function shortHash(s) {
1948
+ let h = 2166136261;
1949
+ for (let i = 0; i < s.length; i++) {
1950
+ h ^= s.charCodeAt(i);
1951
+ h = Math.imul(h, 16777619);
1952
+ }
1953
+ return (h >>> 0).toString(16).padStart(8, "0");
1954
+ }
1955
+ const APPEARANCE_HINT = "Allowed values: `agentcrm help appearance`; presets and installed themes: `agentcrm appearance presets`.";
1956
+ const withAppearanceHint = (p) => withHint(APPEARANCE_HINT, p);
1856
1957
  function diff(before, after) {
1857
1958
  const out = {};
1858
1959
  for (const k of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
@@ -1918,8 +2019,8 @@ const appearanceCommand = {
1918
2019
  const [action = "get"] = positionals;
1919
2020
  const apply = options.yes === true;
1920
2021
  const full = boolOpt(options, "full");
1921
- const appearance = () => import("./chunks/appearance-BaShelzt.mjs");
1922
- const change = (describe, op) => withSession(io, options, { lock: apply }, async ({ ctx }) => {
2022
+ const appearance = () => import("./chunks/appearance-BloHbiLA.mjs");
2023
+ const change = (describe, op) => withAppearanceHint(withSession(io, options, { lock: apply }, async ({ ctx }) => {
1923
2024
  const before = view((await appearance()).getAppearance(ctx), full);
1924
2025
  if (apply) {
1925
2026
  const after = view(await op(ctx), full);
@@ -1931,7 +2032,7 @@ const appearanceCommand = {
1931
2032
  const changes = r.plan ? r.plan.changes : r.changes ?? {};
1932
2033
  const lines = Object.entries(changes).map(([k, c]) => ` ${k}: ${JSON.stringify(c.from)} → ${JSON.stringify(c.to)}`);
1933
2034
  return [`${r.plan ? "Would apply" : "Applied"}: ${describe}`, ...lines.length ? lines : [" (no visible change)"], ...r.plan ? ["Nothing changed — re-run with --yes once the user agrees."] : []].join("\n");
1934
- });
2035
+ }));
1935
2036
  switch (action) {
1936
2037
  case "get":
1937
2038
  return withSession(io, options, {}, async ({ ctx }) => {
@@ -1986,7 +2087,7 @@ async function scaffold({ io, options, positionals }) {
1986
2087
  if (existsSync(dir) && readdirSync(dir).length > 0 && !boolOpt(options, "force")) {
1987
2088
  throw new CliError("dir-not-empty", `${dir} already exists and is not empty.`, "Pick another --dir (or --force to write into it).", 2);
1988
2089
  }
1989
- const { scaffoldExtension } = await import("./chunks/extScaffold-DqxI7R1e.mjs");
2090
+ const { scaffoldExtension } = await import("./chunks/extScaffold-Aqyjf1nr.mjs");
1990
2091
  const res = scaffoldExtension({ kind, id, dir, name: strOpt(options, "name"), description: strOpt(options, "description"), author: strOpt(options, "author") });
1991
2092
  printResult(io, res, () => [`Created ${kind} "${id}" in ${dir}:`, ...res.files.map((f) => ` ${f}`), "", "Next:", ...res.next.map((n) => ` ${n}`)].join("\n"));
1992
2093
  }
@@ -1994,8 +2095,8 @@ async function check({ io, options, positionals }) {
1994
2095
  const target = positionals[1] ?? ".";
1995
2096
  const kind = strOpt(options, "kind");
1996
2097
  if (kind && kind !== "module" && kind !== "theme") usage("--kind is module or theme.");
1997
- const { readRawPackage } = await import("./chunks/extPackage-BGl5Oqs8.mjs");
1998
- const { checkExtension } = await import("./chunks/extCheck--7lmVvl7.mjs");
2098
+ const { readRawPackage } = await import("./chunks/extPackage-DuZ6GZhp.mjs");
2099
+ const { checkExtension } = await import("./chunks/extCheck-GgHG1dYB.mjs");
1999
2100
  const raw = await readRawPackage(target);
2000
2101
  const report = await checkExtension(raw, { kind, callTools: !boolOpt(options, "no-call") });
2001
2102
  printResult(io, report, () => {
@@ -2024,10 +2125,10 @@ async function check({ io, options, positionals }) {
2024
2125
  }
2025
2126
  async function pack({ io, options, positionals }) {
2026
2127
  const target = positionals[1] ?? ".";
2027
- const { readPackage } = await import("./chunks/extPackage-BGl5Oqs8.mjs");
2128
+ const { readPackage } = await import("./chunks/extPackage-DuZ6GZhp.mjs");
2028
2129
  const { raw, pkg: pkg2 } = await readPackage(target);
2029
2130
  if (raw.sourceType === "zip") usage("ext pack takes a project or an unpacked folder, not a .zip.");
2030
- const { packExtension } = await import("./chunks/package-DWbn3t2R.mjs");
2131
+ const { packExtension } = await import("./chunks/package-B7mI6HGi.mjs");
2031
2132
  const bytes = await packExtension(pkg2);
2032
2133
  const out = resolve(strOpt(options, "out") ?? join(raw.source, `${pkg2.manifest.id}-${pkg2.manifest.version}.zip`));
2033
2134
  mkdirSync(dirname(out), { recursive: true });
@@ -2037,12 +2138,23 @@ async function pack({ io, options, positionals }) {
2037
2138
  }
2038
2139
  async function install({ io, options, positionals }) {
2039
2140
  const target = positional(positionals, 1, "ZIP or folder", "ext install <zip|dir> [--site <alias>] [--yes]");
2040
- const { readPackage } = await import("./chunks/extPackage-BGl5Oqs8.mjs");
2141
+ const { readPackage } = await import("./chunks/extPackage-DuZ6GZhp.mjs");
2041
2142
  const { pkg: pkg2, raw } = await readPackage(target);
2042
2143
  const apply = options.yes === true;
2043
2144
  const disabled = boolOpt(options, "disabled");
2145
+ if (!boolOpt(options, "force")) {
2146
+ const { checkExtension } = await import("./chunks/extCheck-GgHG1dYB.mjs");
2147
+ const report = await checkExtension(raw, { callTools: false });
2148
+ if (!report.valid) {
2149
+ throw new CliError(
2150
+ "invalid-package",
2151
+ `The extension doesn't pass the check: ${report.errors.map((e) => e.message).join(" ")}`,
2152
+ `Nothing was installed. Run \`agentcrm ext check ${target}\` for the full report (--force installs it anyway).`
2153
+ );
2154
+ }
2155
+ }
2044
2156
  return withSession(io, options, { lock: apply }, async (s) => {
2045
- const ext = await import("./chunks/extensions-BP1k60md.mjs");
2157
+ const ext = await import("./chunks/extensions-Bp72uZ9a.mjs");
2046
2158
  const existing = s.store.get("extensions", pkg2.manifest.id);
2047
2159
  const m = pkg2.manifest;
2048
2160
  const plan = {
@@ -2067,7 +2179,7 @@ async function install({ io, options, positionals }) {
2067
2179
  }
2068
2180
  const result = await ext.installExtension(s.ctx, pkg2, disabled ? { enabled: false } : {});
2069
2181
  const report = await s.extensions.sync();
2070
- const { getLoadedExtension } = await import("./chunks/registry-Ceexawxl.mjs");
2182
+ const { getLoadedExtension } = await import("./chunks/registry-COfJrSmg.mjs");
2071
2183
  const loaded = getLoadedExtension(m.id);
2072
2184
  const failure = report?.failed.find((f) => f.id === m.id || f.id === "*");
2073
2185
  if (failure && !loaded) return { ...result, author: m.author ?? null, load: { status: "error", error: failure.error } };
@@ -2078,7 +2190,7 @@ async function install({ io, options, positionals }) {
2078
2190
  ...r.plan.warnings.map((w) => ` note: ${w}`),
2079
2191
  ` ${r.plan.warning}`,
2080
2192
  "Nothing changed — re-run with --yes once the user agrees."
2081
- ].join("\n") : `${r.mode === "upgrade" ? `Upgraded ${r.previousVersion} → ` : "Installed "}${r.id} ${r.version} (${r.enabled ? "enabled" : "disabled"}; load: ${r.load.status}${"error" in r.load ? ` — ${r.load.error}` : ""})${r.warnings.length ? `
2193
+ ].join("\n") : `${r.mode === "upgrade" ? `Upgraded ${r.id} ${r.previousVersion} → ${r.version}` : `Installed ${r.id} ${r.version}`} (${r.enabled ? "enabled" : "disabled"}; load: ${r.load.status}${"error" in r.load ? ` — ${r.load.error}` : ""})${r.warnings.length ? `
2082
2194
  ${r.warnings.join("\n ")}` : ""}`);
2083
2195
  }
2084
2196
  const extCommand = {
@@ -2089,7 +2201,7 @@ const extCommand = {
2089
2201
  { name: "new", usage: "ext new <module|theme> <id> [--dir <path>] [--name <name>] [--description <text>] [--author <name>] [--force]", summary: "Scaffold a standalone extension project (package.json, vite.config.ts, tsconfig + runtime typings, src/index.tsx, README).", writes: true },
2090
2202
  { name: "check", usage: "ext check [<dir|zip>] [--kind module|theme] [--no-call]", summary: "Validate manifest, ids, apiVersion, sizes, imports; load the bundle in a throw-away CRM and report routes, nav, slots, AI tools, automation actions, theme values." },
2091
2203
  { name: "pack", usage: "ext pack [<dir>] [--out <file.zip>]", summary: "ZIP manifest.json + bundle.js (+ style.css) for Settings → Modules → Install or `ext install`.", writes: true },
2092
- { name: "install", usage: "ext install <zip|dir> [--site <alias>] [--disabled] [--yes]", summary: "Install or upgrade an extension on a CRM. Prints the plan (id, version, author, upgrade, size) unless --yes.", writes: true, destructive: true },
2204
+ { name: "install", usage: "ext install <zip|dir> [--site <alias>] [--disabled] [--force] [--yes]", summary: "Install or upgrade an extension on a CRM. The bundle is first loaded in a throw-away CRM (like ext check; --force skips it). Prints the plan (id, version, author, upgrade, size) unless --yes.", writes: true, destructive: true },
2093
2205
  { name: "list", usage: "ext list [--kind module|theme]", summary: "Installed extensions with version, enabled state and load status in this process." },
2094
2206
  { name: "enable", usage: "ext enable <id> [--cascade] [--yes]", summary: "Enable an installed extension (plan unless --yes).", writes: true },
2095
2207
  { name: "disable", usage: "ext disable <id> [--cascade] [--yes]", summary: "Disable it without uninstalling (plan unless --yes).", writes: true },
@@ -2104,7 +2216,7 @@ const extCommand = {
2104
2216
  { name: "name", type: "string", description: "new: display name." },
2105
2217
  { name: "description", type: "string", description: "new: one-sentence description." },
2106
2218
  { name: "author", type: "string", description: "new: author shown in the install plan and Settings → Modules." },
2107
- { name: "force", type: "boolean", description: "new: write into a non-empty folder. uninstall: even if enabled modules depend on it." },
2219
+ { name: "force", type: "boolean", description: "new: write into a non-empty folder. install: skip the trial load (ext check). uninstall: even if enabled modules depend on it." },
2108
2220
  { name: "kind", type: "string", description: "check: expected kind. list: only modules or themes." },
2109
2221
  { name: "no-call", type: "boolean", description: "check: don't smoke-call the AI tools on the demo data." },
2110
2222
  { name: "out", type: "string", description: "pack / export: ZIP path (default: <dir>/<id>-<version>.zip, ./<id>-<version>.zip)." },
@@ -2128,8 +2240,8 @@ const extCommand = {
2128
2240
  const kind = strOpt(options, "kind");
2129
2241
  if (kind && kind !== "module" && kind !== "theme") usage("--kind is module or theme.");
2130
2242
  return withSession(io, options, {}, async (s) => {
2131
- const { listExtensions } = await import("./chunks/extensions-BP1k60md.mjs");
2132
- const { getLoadedExtension } = await import("./chunks/registry-Ceexawxl.mjs");
2243
+ const { listExtensions } = await import("./chunks/extensions-Bp72uZ9a.mjs");
2244
+ const { getLoadedExtension } = await import("./chunks/registry-COfJrSmg.mjs");
2133
2245
  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 } : {} }));
2134
2246
  }, (rows) => table(rows.map((r) => ({ ...r, enabled: r.enabled ? "yes" : "no" })), ["id", "kind", "name", "version", "enabled", "load", "author"]));
2135
2247
  }
@@ -2139,15 +2251,15 @@ const extCommand = {
2139
2251
  const enabled = action === "enable";
2140
2252
  const cascade = boolOpt(options, "cascade");
2141
2253
  return withSession(io, options, { lock: apply }, async (s) => {
2142
- const ext = await import("./chunks/extensions-BP1k60md.mjs");
2254
+ const ext = await import("./chunks/extensions-Bp72uZ9a.mjs");
2143
2255
  const before = ext.getExtension(s.ctx, id);
2144
2256
  if (!apply) {
2145
2257
  const { result } = await dryRun(s.ctx, async (copy) => {
2146
2258
  await ext.setExtensionEnabled(copy, id, enabled, { cascade });
2147
- return (await import("./chunks/modules-CqdVQVJb.mjs")).moduleStates(copy.store);
2259
+ return (await import("./chunks/modules-clWdE-K2.mjs")).moduleStates(copy.store);
2148
2260
  });
2149
2261
  const now = new Map(result.map((m) => [m.id, m.enabled]));
2150
- const changed = (await import("./chunks/modules-CqdVQVJb.mjs")).moduleStates(s.store).filter((m) => now.has(m.id) && now.get(m.id) !== m.enabled).map((m) => ({ id: m.id, enabled: now.get(m.id) }));
2262
+ const changed = (await import("./chunks/modules-clWdE-K2.mjs")).moduleStates(s.store).filter((m) => now.has(m.id) && now.get(m.id) !== m.enabled).map((m) => ({ id: m.id, enabled: now.get(m.id) }));
2151
2263
  if (before.kind === "theme" && before.enabled !== enabled) changed.push({ id, enabled });
2152
2264
  return { needsConfirmation: true, plan: { id, kind: before.kind, from: before.enabled, to: enabled, changed } };
2153
2265
  }
@@ -2161,7 +2273,7 @@ Nothing changed — re-run with --yes once the user agrees.` : `${r.id} is now $
2161
2273
  const id = positional(positionals, 1, "extension id", "ext uninstall <id> [--yes]");
2162
2274
  const force = boolOpt(options, "force");
2163
2275
  return withSession(io, options, { lock: apply }, async (s) => {
2164
- const ext = await import("./chunks/extensions-BP1k60md.mjs");
2276
+ const ext = await import("./chunks/extensions-Bp72uZ9a.mjs");
2165
2277
  const summary = ext.getExtension(s.ctx, id);
2166
2278
  if (!apply) {
2167
2279
  await dryRun(s.ctx, (copy) => ext.uninstallExtension(copy, id, { force }));
@@ -2176,8 +2288,8 @@ Nothing changed — re-run with --yes once the user agrees.` : `Uninstalled ${r.
2176
2288
  case "export": {
2177
2289
  const id = positional(positionals, 1, "extension id", "ext export <id> [--out <file.zip>]");
2178
2290
  return withSession(io, options, {}, async (s) => {
2179
- const { getExtensionPackage } = await import("./chunks/extensions-BP1k60md.mjs");
2180
- const { packExtension } = await import("./chunks/package-DWbn3t2R.mjs");
2291
+ const { getExtensionPackage } = await import("./chunks/extensions-Bp72uZ9a.mjs");
2292
+ const { packExtension } = await import("./chunks/package-B7mI6HGi.mjs");
2181
2293
  const pkg2 = getExtensionPackage(s.ctx, id);
2182
2294
  const bytes = await packExtension(pkg2);
2183
2295
  const out = resolve(strOpt(options, "out") ?? `${id}-${pkg2.manifest.version}.zip`);
@@ -2191,9 +2303,6 @@ Nothing changed — re-run with --yes once the user agrees.` : `Uninstalled ${r.
2191
2303
  }
2192
2304
  }
2193
2305
  };
2194
- function printPlan(io, plan) {
2195
- printResult(io, plan, () => `Would restore ${String(plan.plan.restore)} (${String(plan.plan.mode)}). Re-run with --yes once the user agrees.`);
2196
- }
2197
2306
  function mappingOpt(options) {
2198
2307
  const raw = typeof options.map === "string" ? readTextValue(options.map, "map").trim() : void 0;
2199
2308
  if (!raw) return void 0;
@@ -2242,10 +2351,22 @@ const importCommand = {
2242
2351
  if (!existsSync2(path)) usage(`${path} does not exist.`);
2243
2352
  const bytes = new Uint8Array(readFileSync2(path));
2244
2353
  if (options.yes !== true) {
2245
- printPlan(io, { needsConfirmation: true, plan: { restore: resolve(path), bytes: bytes.length, mode: options.replace === true ? "replace" : "merge" } });
2246
- return;
2354
+ return withSession(
2355
+ io,
2356
+ options,
2357
+ {},
2358
+ async ({ ctx }) => {
2359
+ const plan = await (await import("./chunks/backup-W3CjgkAJ.mjs")).planBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" });
2360
+ return { needsConfirmation: true, plan: { restore: resolve(path), bytes: bytes.length, ...plan } };
2361
+ },
2362
+ (r) => [
2363
+ `Would restore ${r.plan.restore} (${r.plan.mode}): ${r.plan.added} new, ${r.plan.overwritten} overwritten, ${r.plan.deleted} deleted document(s), ${r.plan.files} file(s).`,
2364
+ ...r.plan.warnings.map((w) => `Warning: ${w}`),
2365
+ "Re-run with --yes once the user agrees."
2366
+ ].join("\n")
2367
+ );
2247
2368
  }
2248
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/backup-ClSmxmFZ.mjs")).importBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" }), (r) => `Restored ${r.written} document(s) and ${r.files} file(s)`);
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)`);
2249
2370
  }
2250
2371
  if (kind !== "csv") usage('Only "import csv" and "import zip" are supported.', "Usage: agentcrm import csv <file> --object <id>");
2251
2372
  const source = positional(positionals, 1, "CSV file (or -)", "import csv <file|-> --object <id>");
@@ -2256,7 +2377,7 @@ const importCommand = {
2256
2377
  const apply = options.yes === true && options["dry-run"] !== true;
2257
2378
  const shown = numOpt(options, "rows") ?? 100;
2258
2379
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2259
- const { importCsv } = await import("./chunks/importCsv-DmE_sBch.mjs");
2380
+ const { importCsv } = await import("./chunks/importCsv-LVmMTaWU.mjs");
2260
2381
  const report = await importCsv(ctx, {
2261
2382
  object: objectId,
2262
2383
  csv,
@@ -2305,16 +2426,16 @@ const exportCommand = {
2305
2426
  const file2 = strOpt(options, "out");
2306
2427
  if (!file2) usage("export zip needs --out <file.zip>.");
2307
2428
  return withSession(io, options, {}, async ({ ctx }) => {
2308
- const { exportBackupZip } = await import("./chunks/backup-ClSmxmFZ.mjs");
2429
+ const { exportBackupZip } = await import("./chunks/backup-W3CjgkAJ.mjs");
2309
2430
  const { zip, report } = await exportBackupZip(ctx, { includeFiles: options["no-files"] !== true });
2310
- writeFileSync(resolve(file2), zip);
2431
+ writeOutFile(file2, zip);
2311
2432
  return { file: resolve(file2), zipBytes: zip.length, files: report.files, missing: report.missing, fileBytes: report.bytes };
2312
2433
  }, (r) => `Wrote ${r.zipBytes} bytes (${r.files} file(s)${r.missing.length ? `, ${r.missing.length} missing` : ""}) to ${r.file}`);
2313
2434
  }
2314
2435
  const out = strOpt(options, "out");
2315
2436
  const deliver = (text, extra) => {
2316
2437
  if (out) {
2317
- writeFileSync(resolve(out), text);
2438
+ writeOutFile(out, text);
2318
2439
  return { file: resolve(out), bytes: Buffer.byteLength(text), ...extra };
2319
2440
  }
2320
2441
  return { ...extra, content: text };
@@ -2324,7 +2445,7 @@ const exportCommand = {
2324
2445
  const filters = parseFilters(multiOpt(options, "filter"), FILTER_OPS);
2325
2446
  const sorts = parseSorts(multiOpt(options, "sort"));
2326
2447
  return withSession(io, options, {}, async ({ ctx }) => {
2327
- const { exportCsv } = await import("./chunks/exportCsv-B5hRvkNm.mjs");
2448
+ const { exportCsv } = await import("./chunks/exportCsv-C6e6XEmB.mjs");
2328
2449
  const csv = exportCsv(ctx, objectId, {
2329
2450
  viewId: strOpt(options, "view"),
2330
2451
  filters,
@@ -2333,12 +2454,14 @@ const exportCommand = {
2333
2454
  values: boolOpt(options, "raw") ? "raw" : "labels",
2334
2455
  includeDeleted: boolOpt(options, "deleted")
2335
2456
  });
2336
- return deliver(csv, { object: objectId, rows: Math.max(0, csv.split(/\r?\n/).filter(Boolean).length - 1) });
2457
+ const { default: Papa } = await import("./chunks/papaparse-Df7i1dHr.mjs").then((n) => n.p);
2458
+ const rows = Math.max(0, Papa.parse(csv, { skipEmptyLines: true }).data.length - 1);
2459
+ return deliver(csv, { object: objectId, rows });
2337
2460
  }, (r) => "content" in r ? String(r.content) : `Wrote ${String(r.rows)} row(s) to ${String(r.file)}`);
2338
2461
  }
2339
2462
  if (kind === "json") {
2340
2463
  return withSession(io, options, {}, async ({ ctx }) => {
2341
- const { exportJson } = await import("./chunks/exportCsv-B5hRvkNm.mjs");
2464
+ const { exportJson } = await import("./chunks/exportCsv-C6e6XEmB.mjs");
2342
2465
  const data = exportJson(ctx, { collections: listOpt(options, "collections") });
2343
2466
  const text = JSON.stringify(data, null, 2);
2344
2467
  if (out) return deliver(text, {});
@@ -2374,6 +2497,7 @@ const reportCommand = {
2374
2497
  async run({ io, options, positionals }) {
2375
2498
  const name2 = positional(positionals, 0, "report name", "report <name>");
2376
2499
  const internal = REPORT_ALIASES[name2] ?? name2;
2500
+ if (!Object.values(REPORT_ALIASES).includes(internal)) usage(`Unknown report "${name2}".`, `Reports: ${Object.keys(REPORT_ALIASES).join(", ")}.`);
2377
2501
  const params = {
2378
2502
  ...jsonObjectOpt(options, "params") ?? {},
2379
2503
  ...numOpt(options, "months") !== void 0 ? { months: numOpt(options, "months") } : {},
@@ -2381,7 +2505,7 @@ const reportCommand = {
2381
2505
  ...strOpt(options, "pipeline") ? { pipelineId: strOpt(options, "pipeline") } : {},
2382
2506
  ...strOpt(options, "owner") ? { ownerId: strOpt(options, "owner") } : {}
2383
2507
  };
2384
- return withSession(io, options, {}, async ({ ctx }) => ({ report: name2, data: (await import("./chunks/reports-BB59N6ke.mjs")).runReport(ctx, internal, params) }));
2508
+ return withSession(io, options, {}, async ({ ctx }) => ({ report: name2, data: (await import("./chunks/reports-BACHGccj.mjs")).runReport(ctx, internal, params) }));
2385
2509
  }
2386
2510
  };
2387
2511
  const statsCommand = {
@@ -2391,7 +2515,7 @@ const statsCommand = {
2391
2515
  options: [SITE_OPTION, AS_OPTION],
2392
2516
  async run({ io, options }) {
2393
2517
  return withSession(io, options, {}, async ({ ctx, store }) => {
2394
- const stats = (await import("./chunks/stats-CfeJssSS.mjs")).getStats(ctx);
2518
+ const stats = (await import("./chunks/stats-BfuLWhVV.mjs")).getStats(ctx);
2395
2519
  const journal = store.stats;
2396
2520
  return { ...stats, ...journal ? { journal: await journal.call(store) } : {} };
2397
2521
  }, (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"));
@@ -2405,7 +2529,7 @@ const compactCommand = {
2405
2529
  async run({ io, options }) {
2406
2530
  const apply = options.yes === true;
2407
2531
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2408
- const { compactData } = await import("./chunks/compact-CsFPaAAq.mjs");
2532
+ const { compactData } = await import("./chunks/compact-BGHI92tm.mjs");
2409
2533
  const report = await compactData(ctx, { olderThanDays: numOpt(options, "older-than"), keepRuns: numOpt(options, "keep-runs"), dryRun: !apply });
2410
2534
  return apply ? report : { needsConfirmation: true, plan: report };
2411
2535
  }, (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}`);
@@ -2426,19 +2550,19 @@ const automationsCommand = {
2426
2550
  const [action = "list", id] = positionals;
2427
2551
  switch (action) {
2428
2552
  case "list":
2429
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/automations-BWrvLqP6.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"]));
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"]));
2430
2554
  case "enable":
2431
2555
  case "disable": {
2432
2556
  if (!id) usage(`automations ${action} needs an id.`);
2433
2557
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
2434
- const mod = await import("./chunks/automations-BWrvLqP6.mjs");
2558
+ const mod = await import("./chunks/automations-DwWruxmu.mjs");
2435
2559
  return mod.setAutomationEnabled(ctx, id, action === "enable");
2436
2560
  });
2437
2561
  }
2438
2562
  case "run": {
2439
2563
  const apply = options.yes === true;
2440
2564
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2441
- const { runScheduled } = await import("./chunks/automations-BWrvLqP6.mjs");
2565
+ const { runScheduled } = await import("./chunks/automations-DwWruxmu.mjs");
2442
2566
  const report = await runScheduled(ctx, { dryRun: !apply, automationId: strOpt(options, "id"), force: boolOpt(options, "force") });
2443
2567
  return apply ? report : { needsConfirmation: true, ...report };
2444
2568
  }, (r) => `${"needsConfirmation" in r ? "DRY RUN — " : ""}ran ${r.ran}, skipped ${r.skipped}, errors ${r.errors}`);
@@ -2462,16 +2586,16 @@ const formsCommand = {
2462
2586
  const [action = "list", id] = positionals;
2463
2587
  switch (action) {
2464
2588
  case "list":
2465
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/forms-BKU5F9WF.mjs")).listForms(ctx), (fs) => table(fs, ["id", "name", "object", "publishedPath"]));
2589
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/forms-BfuWaQjA.mjs")).listForms(ctx), (fs) => table(fs, ["id", "name", "object", "publishedPath"]));
2466
2590
  case "publish": {
2467
2591
  if (!id) usage("forms publish needs a form id.");
2468
2592
  return withSession(io, options, { lock: options.yes === true }, async (s) => {
2469
- const forms = await import("./chunks/forms-BKU5F9WF.mjs");
2593
+ const forms = await import("./chunks/forms-BfuWaQjA.mjs");
2470
2594
  const built = await forms.buildFormHtml(s.ctx, id);
2471
2595
  const target = s.hostingTarget();
2472
2596
  if (!target) usage(`${s.site.alias} has no hosting to publish the form to.`);
2473
2597
  if (options.yes !== true) return { needsConfirmation: true, plan: { form: id, path: built.path, hosting: s.site.hosting?.type, bytes: built.html.length } };
2474
- const { buildConnector } = await import("./chunks/hosting-Bme7LxPc.mjs");
2598
+ const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
2475
2599
  const connector = buildConnector(target);
2476
2600
  const siteUrl = typeof s.site.hosting?.settings.siteUrl === "string" ? s.site.hosting.settings.siteUrl.replace(/\/+$/, "") : "";
2477
2601
  return forms.publishForm(s.ctx, id, { put: (input) => connector.put(input), publicUrl: async (path) => siteUrl ? `${siteUrl}/${path}` : connector.publicUrl(path) });
@@ -2480,7 +2604,7 @@ const formsCommand = {
2480
2604
  case "ingest": {
2481
2605
  const apply = options.yes === true;
2482
2606
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2483
- const { ingestInbox } = await import("./chunks/forms-BKU5F9WF.mjs");
2607
+ const { ingestInbox } = await import("./chunks/forms-BfuWaQjA.mjs");
2484
2608
  const report = await ingestInbox(ctx, strOpt(options, "form"), { dryRun: !apply, limit: numOpt(options, "limit") });
2485
2609
  return apply ? report : { needsConfirmation: true, ...report };
2486
2610
  }, (r) => `${"needsConfirmation" in r ? "DRY RUN — " : ""}processed ${r.processed}: ${r.created} created, ${r.updated} updated, ${r.failed} failed`);
@@ -2549,6 +2673,31 @@ async function fetchHostedAdmin(adminUrl, fetchImpl = fetch) {
2549
2673
  return { version: null, dataSchema: null, error: "version.json is not valid JSON" };
2550
2674
  }
2551
2675
  }
2676
+ function localAdminFolder(hosting) {
2677
+ if (!hosting || hosting.connectorId !== "local-folder") return null;
2678
+ const root = typeof hosting.settings.path === "string" ? hosting.settings.path : "";
2679
+ if (!root) return null;
2680
+ return join(root, hosting.folder ?? "admin");
2681
+ }
2682
+ function readLocalAdmin(folder) {
2683
+ for (const name2 of ["version.json", "admin-files.json"]) {
2684
+ const file2 = join(folder, name2);
2685
+ if (!existsSync(file2)) continue;
2686
+ try {
2687
+ const m = JSON.parse(readFileSync(file2, "utf8"));
2688
+ return { version: m.version ?? null, dataSchema: m.dataSchema ?? null };
2689
+ } catch {
2690
+ return { version: null, dataSchema: null, error: `${name2} is not valid JSON` };
2691
+ }
2692
+ }
2693
+ return { version: null, dataSchema: null, error: `no version.json in ${folder}` };
2694
+ }
2695
+ async function siteAdminVersion(site) {
2696
+ if (site.adminUrl) return { ...await fetchHostedAdmin(site.adminUrl), source: "url" };
2697
+ const folder = localAdminFolder(site.hosting);
2698
+ if (folder) return { ...readLocalAdmin(folder), source: "folder" };
2699
+ return { version: null, dataSchema: null, source: "none" };
2700
+ }
2552
2701
  function readChangelog() {
2553
2702
  try {
2554
2703
  const data = JSON.parse(readFileSync(join(packageRoot(), "changelog.json"), "utf8"));
@@ -2591,12 +2740,12 @@ const deployAdminCommand = {
2591
2740
  const sites = options.all === true ? listSites().sites.filter((s) => s.hosting) : [resolveSite(strOpt(options, "site"))];
2592
2741
  if (!sites.length) throw new CliError("no-sites", "No registered CRM has a hosting.");
2593
2742
  installBrowserEnv();
2594
- const { adminOps, applyOps, buildConnector } = await import("./chunks/hosting-Bme7LxPc.mjs");
2743
+ const { adminOps, applyOps, buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
2595
2744
  const { hostingTargetOf: hostingTargetOf2 } = await Promise.resolve().then(() => session);
2596
2745
  const plans = [];
2597
2746
  for (const site of sites) {
2598
2747
  if (!site.hosting) throw new CliError("not-hosted", `${site.alias} has no hosting recorded.`, "Register it with `site add --admin-url …` from a CRM whose admin is hosted, or reinstall with a hosting.");
2599
- const hosted = site.adminUrl ? await fetchHostedAdmin(site.adminUrl) : { version: null, dataSchema: null };
2748
+ const hosted = await siteAdminVersion(site);
2600
2749
  const plan = {
2601
2750
  alias: site.alias,
2602
2751
  adminUrl: site.adminUrl,
@@ -2695,7 +2844,7 @@ async function runDoctor(site, opts = {}) {
2695
2844
  installBrowserEnv();
2696
2845
  try {
2697
2846
  if (site.runtime.backend === "journal" && site.runtime.journal.connectorId === "git-pages") {
2698
- const { probeTarget } = await import("./chunks/install-CpHoUFtL.mjs");
2847
+ const { probeTarget } = await import("./chunks/install-BBTA4nMm.mjs");
2699
2848
  const { dataTargetOf: dataTargetOf2 } = await Promise.resolve().then(() => session);
2700
2849
  const probe = await probeTarget(dataTargetOf2(site), { requireSiteUrl: false, requirePrivate: true });
2701
2850
  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." });
@@ -2712,7 +2861,7 @@ async function runDoctor(site, opts = {}) {
2712
2861
  if (!settingsDoc(store)) missing.push("settings");
2713
2862
  if (!usersOf(store).some((u) => u.role === "admin" && !u.disabled)) missing.push("an active admin user");
2714
2863
  if (missing.length && opts.repair) {
2715
- const { seedCrm } = await import("./chunks/seed-CugPGYZL.mjs");
2864
+ const { seedCrm } = await import("./chunks/seed-6MRmQQBR.mjs");
2716
2865
  repaired = await seedCrm(session$1.system, { admin: site.user ? { name: site.user.split("@")[0], email: site.user } : void 0 });
2717
2866
  add({ id: "dataset", status: "ok", message: `Repaired: seeded ${missing.join(", ")}.` });
2718
2867
  } else {
@@ -2740,26 +2889,40 @@ async function runDoctor(site, opts = {}) {
2740
2889
  add({ id: "data-access", status: "fail", message: e.message, fix: e.hint });
2741
2890
  }
2742
2891
  const adminUrl = site.adminUrl;
2892
+ const adminFolder = adminUrl ? null : localAdminFolder(site.hosting);
2743
2893
  let origin = null;
2744
- if (!adminUrl) {
2894
+ if (!adminUrl && !adminFolder) {
2745
2895
  add({ id: "admin", status: "skip", message: "Admin not hosted (use `agentcrm serve`)." });
2746
2896
  } else {
2747
- origin = new URL(adminUrl).origin;
2748
- add(
2749
- adminUrl.startsWith("https://") || /^http:\/\/(localhost|127\.0\.0\.1)/.test(adminUrl) ? { id: "https", status: "ok", message: "Admin served over HTTPS (or localhost)." } : { id: "https", status: "fail", message: `${adminUrl} is plain HTTP: browsers refuse WebCrypto (signing, encryption) there.`, fix: "Serve the admin over HTTPS (CDN / custom domain with TLS, the bucket's https endpoint, GitHub Pages)." }
2750
- );
2751
- const index = await fetchText(`${adminUrl}index.html`);
2897
+ if (adminUrl) {
2898
+ origin = new URL(adminUrl).origin;
2899
+ add(
2900
+ adminUrl.startsWith("https://") || /^http:\/\/(localhost|127\.0\.0\.1)/.test(adminUrl) ? { id: "https", status: "ok", message: "Admin served over HTTPS (or localhost)." } : { id: "https", status: "fail", message: `${adminUrl} is plain HTTP: browsers refuse WebCrypto (signing, encryption) there.`, fix: "Serve the admin over HTTPS (CDN / custom domain with TLS, the bucket's https endpoint, GitHub Pages)." }
2901
+ );
2902
+ }
2903
+ const read = async (name2) => {
2904
+ if (adminUrl) {
2905
+ const res = await fetchText(`${adminUrl}${name2}`);
2906
+ return { ok: res.ok, text: res.text, error: res.error ?? (res.ok ? void 0 : `HTTP ${res.status}`) };
2907
+ }
2908
+ const file2 = join(adminFolder, name2);
2909
+ if (!existsSync(file2)) return { ok: false, text: "", error: `${file2} not found` };
2910
+ return { ok: true, text: readFileSync(file2, "utf8") };
2911
+ };
2912
+ const index = await read("index.html");
2752
2913
  if (!index.ok) {
2753
- add({ id: "admin", status: "fail", message: `Admin page not served (${index.error ?? `HTTP ${index.status}`}).`, fix: `\`agentcrm deploy-admin --site ${site.alias} --yes\` (Pages hosts take a minute after a commit).` });
2914
+ add({ id: "admin", status: "fail", message: `Admin page not ${adminUrl ? "served" : "found"} (${index.error}).`, fix: `\`agentcrm deploy-admin --site ${site.alias} --yes\`${adminUrl ? " (Pages hosts take a minute after a commit)" : ""}.` });
2754
2915
  } else {
2755
- add({ id: "admin", status: "ok", message: `Admin served at ${adminUrl}.` });
2756
- const hosted = await fetchHostedAdmin(adminUrl);
2916
+ add(
2917
+ adminUrl ? { id: "admin", status: "ok", message: `Admin served at ${adminUrl}.` } : { id: "admin", status: "ok", message: `Admin in the folder ${adminFolder} (no public URL): open it with \`agentcrm serve --dir ${adminFolder}\`.` }
2918
+ );
2919
+ const hosted = adminUrl ? await fetchHostedAdmin(adminUrl) : readLocalAdmin(adminFolder);
2757
2920
  add(
2758
2921
  !hosted.version ? { id: "admin-version", status: "warn", message: "version.json missing next to the admin.", fix: `\`agentcrm deploy-admin --site ${site.alias} --yes\`.` } : compareVersions(hosted.version, info.version) >= 0 ? { id: "admin-version", status: "ok", message: `Admin ${hosted.version} is up to date.` } : { id: "admin-version", status: "warn", message: `Admin ${hosted.version} is older than ${info.version}.`, fix: `Offer \`agentcrm deploy-admin --site ${site.alias}\`.` }
2759
2922
  );
2760
- const cfg = await fetchText(`${adminUrl}config.js`);
2923
+ const cfg = await read("config.js");
2761
2924
  try {
2762
- if (!cfg.ok) throw new Error(cfg.error ?? `HTTP ${cfg.status}`);
2925
+ if (!cfg.ok) throw new Error(cfg.error);
2763
2926
  const runtime = parseConfigJs(cfg.text);
2764
2927
  const secrets = findSecretKeys(runtime);
2765
2928
  if (secrets.length) add({ id: "config-js", status: "fail", message: `config.js publishes secrets: ${secrets.join(", ")}.`, fix: "Revoke those keys NOW, then redeploy the admin (config.js is regenerated without secrets)." });
@@ -2823,7 +2986,7 @@ function readPlan(source) {
2823
2986
  try {
2824
2987
  return JSON.parse(text);
2825
2988
  } catch (err) {
2826
- throw new CliError("invalid-plan", `The plan is not valid JSON: ${err.message}`, void 0, 2);
2989
+ throw new CliError("invalid-plan", `The plan is not valid JSON: ${err.message}`, "Write the plan as one JSON object (`agentcrm options --json` documents every field).", 2);
2827
2990
  }
2828
2991
  }
2829
2992
  const installCommand = {
@@ -2838,10 +3001,10 @@ const installCommand = {
2838
3001
  const planPath = strOpt(options, "plan");
2839
3002
  if (!planPath) usage("install needs --plan <file|->.", "Run `agentcrm options` for the format.");
2840
3003
  installBrowserEnv();
2841
- const { resolvePlan } = await import("./chunks/plan-C0TBrU92.mjs");
3004
+ const { resolvePlan } = await import("./chunks/plan-BmTat6-T.mjs");
2842
3005
  const plan = resolvePlan(readPlan(planPath));
2843
3006
  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.");
2844
- const { checkPlan, runInstall } = await import("./chunks/install-CpHoUFtL.mjs");
3007
+ const { checkPlan, runInstall } = await import("./chunks/install-BBTA4nMm.mjs");
2845
3008
  io.err("Checking access…");
2846
3009
  const checks = await checkPlan(plan);
2847
3010
  const summary = {
@@ -2861,7 +3024,7 @@ const installCommand = {
2861
3024
  throw new CliError(
2862
3025
  "check-failed",
2863
3026
  [!checks.data.ok && `Data: ${checks.data.detail}`, checks.hosting && !checks.hosting.ok && `Hosting: ${checks.hosting.detail}`].filter(Boolean).join(" — "),
2864
- "Fix the access (key / token permissions, bucket / repo names, URL, CORS) and retry the dry run."
3027
+ checks.data.existing && !checks.data.ok ? `Reuse it: the user stores its passphrase (\`agentcrm credentials set ${plan.passphraseAccount ?? `passphrase:${plan.alias}`}\` in their own terminal, or --from-env), then add "encryption": { "credential": "${plan.passphraseAccount ?? `passphrase:${plan.alias}`}" } to the plan's data and retry the dry run. Or pick another folder / root for a new CRM.` : "Fix the access (key / token permissions, bucket / repo names, URL, CORS) and retry the dry run."
2865
3028
  );
2866
3029
  }
2867
3030
  if (options["dry-run"] === true) {
@@ -2887,6 +3050,8 @@ const installCommand = {
2887
3050
  });
2888
3051
  const nextSteps = [];
2889
3052
  if (plan.adminUrl) nextSteps.push(`Open the admin: ${plan.adminUrl}`);
3053
+ else if (localAdminFolder(plan.hosting)) nextSteps.push(`The admin is in ${localAdminFolder(plan.hosting)} (no public URL): open it with \`agentcrm serve --dir ${localAdminFolder(plan.hosting)}\`, or serve that folder over HTTPS.`);
3054
+ else if (plan.hosting) nextSteps.push(`The admin was uploaded to the ${plan.hosting.type} hosting; its public URL is unknown (add siteUrl to the plan's hosting settings next time).`);
2890
3055
  else nextSteps.push("The admin was not uploaded: run `agentcrm serve` to use it on this computer.");
2891
3056
  if (plan.backend === "journal") {
2892
3057
  nextSteps.push(`Each new browser asks once for the data location keys${plan.passphraseAccount ? " and the passphrase" : ""} (they stay in that browser).`);
@@ -3136,9 +3301,10 @@ const versionCommand = {
3136
3301
  }
3137
3302
  };
3138
3303
  async function siteStatus(site, version2, dataSchema) {
3139
- const base = { alias: site.alias, adminUrl: site.adminUrl };
3140
- if (!site.adminUrl) return { ...base, adminVersion: null, state: "not-hosted" };
3141
- const hosted = await fetchHostedAdmin(site.adminUrl);
3304
+ const folder = site.adminUrl ? null : localAdminFolder(site.hosting);
3305
+ const base = { alias: site.alias, adminUrl: site.adminUrl, ...folder ? { adminFolder: folder } : {} };
3306
+ const hosted = await siteAdminVersion(site);
3307
+ if (hosted.source === "none") return { ...base, adminVersion: null, state: "not-hosted" };
3142
3308
  if (!hosted.version) return { ...base, adminVersion: null, state: "unreachable", error: hosted.error };
3143
3309
  if (hosted.version !== site.adminVersion) putSite({ ...site, adminVersion: hosted.version });
3144
3310
  const cmp = compareVersions(hosted.version, version2);
@@ -3199,7 +3365,7 @@ const optionsCommand = {
3199
3365
  printResult(io, { email: rulesEmail, rules, where: "Firebase console → Firestore Database → Rules → paste → Publish (or `firebase deploy --only firestore:rules`)." }, () => rules);
3200
3366
  return;
3201
3367
  }
3202
- const { PLAN_EXAMPLE } = await import("./chunks/plan-C0TBrU92.mjs");
3368
+ const { PLAN_EXAMPLE } = await import("./chunks/plan-BmTat6-T.mjs");
3203
3369
  const data = {
3204
3370
  order: ["data backend", "data location", "admin hosting"],
3205
3371
  backends: DATA_BACKENDS,
@@ -3272,13 +3438,13 @@ const objectsCommand = {
3272
3438
  switch (action) {
3273
3439
  case "list":
3274
3440
  return withSession(io, options, {}, async ({ ctx, store }) => {
3275
- const { listObjects } = await import("./chunks/schema-DlHt2PxI.mjs");
3441
+ const { listObjects } = await import("./chunks/schema-C4lSslXv.mjs");
3276
3442
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3277
3443
  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 }));
3278
3444
  }, (rows) => table(rows, ["id", "labelPlural", "fields", "records", "standard"]));
3279
3445
  case "get": {
3280
3446
  const objectId = positional(positionals, 1, "object id", "objects get <id>");
3281
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/schema-DlHt2PxI.mjs")).describeSchema(ctx, objectId));
3447
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).describeSchema(ctx, objectId)[0]);
3282
3448
  }
3283
3449
  case "create": {
3284
3450
  const data = jsonObjectOpt(options, "data") ?? {};
@@ -3291,7 +3457,7 @@ const objectsCommand = {
3291
3457
  ...strOpt(options, "color") ? { color: strOpt(options, "color") } : {}
3292
3458
  };
3293
3459
  if (!input.labelSingular || !input.labelPlural) usage("objects create needs --singular and --plural (or --data).");
3294
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-DlHt2PxI.mjs")).createObject(ctx, input));
3460
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).createObject(ctx, input));
3295
3461
  }
3296
3462
  case "update": {
3297
3463
  const objectId = positional(positionals, 1, "object id", "objects update <id> …");
@@ -3303,12 +3469,12 @@ const objectsCommand = {
3303
3469
  ...strOpt(options, "label-field") ? { labelField: strOpt(options, "label-field") } : {}
3304
3470
  };
3305
3471
  if (!Object.keys(patch).length) usage("objects update needs --singular, --plural, --icon, --color or --label-field.");
3306
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-DlHt2PxI.mjs")).updateObject(ctx, objectId, patch));
3472
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).updateObject(ctx, objectId, patch));
3307
3473
  }
3308
3474
  case "delete": {
3309
3475
  if (!id) usage("objects delete needs an object id.");
3310
3476
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx, store }) => {
3311
- const { getObject, deleteObject } = await import("./chunks/schema-DlHt2PxI.mjs");
3477
+ const { getObject, deleteObject } = await import("./chunks/schema-C4lSslXv.mjs");
3312
3478
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3313
3479
  const o = getObject(ctx, id);
3314
3480
  const plan = { object: o.id, label: o.labelPlural, standard: o.standard, recordsDeleted: recordsOf(store, o.id, { includeDeleted: true }).length };
@@ -3366,7 +3532,7 @@ const fieldsCommand = {
3366
3532
  ...boolOpt(options, "unique") ? { unique: true } : {},
3367
3533
  ...strOpt(options, "description") ? { description: strOpt(options, "description") } : {}
3368
3534
  };
3369
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-DlHt2PxI.mjs")).createField(ctx, objectId, input));
3535
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).createField(ctx, objectId, input));
3370
3536
  }
3371
3537
  case "update": {
3372
3538
  if (!fieldId) usage("fields update needs a field id.");
@@ -3383,12 +3549,12 @@ const fieldsCommand = {
3383
3549
  ...strOpt(options, "description") !== void 0 ? { description: strOpt(options, "description") } : {}
3384
3550
  };
3385
3551
  if (!Object.keys(patch).length) usage("fields update needs at least one change.");
3386
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-DlHt2PxI.mjs")).updateField(ctx, objectId, fieldId, patch));
3552
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).updateField(ctx, objectId, fieldId, patch));
3387
3553
  }
3388
3554
  case "delete": {
3389
3555
  if (!fieldId) usage("fields delete needs a field id.");
3390
3556
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx, store }) => {
3391
- const { getObject, deleteField } = await import("./chunks/schema-DlHt2PxI.mjs");
3557
+ const { getObject, deleteField } = await import("./chunks/schema-C4lSslXv.mjs");
3392
3558
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3393
3559
  const field = getObject(ctx, objectId).fields.find((f) => f.id === fieldId);
3394
3560
  if (!field) usage(`No field "${fieldId}" on ${objectId}.`, `\`agentcrm objects get ${objectId}\` lists them.`);
@@ -3695,7 +3861,7 @@ const toolsCommand = {
3695
3861
  async run({ io, options, positionals }) {
3696
3862
  const [action = "list"] = positionals;
3697
3863
  if (action === "list") {
3698
- const { listAiTools, AI_TOOLS } = await import("./chunks/tools-Bew8lxKx.mjs");
3864
+ const { listAiTools, AI_TOOLS } = await import("./chunks/tools-iSH-yMpl.mjs");
3699
3865
  const builtin = new Set(AI_TOOLS.map((t) => t.name));
3700
3866
  const shape = () => listAiTools().map((t) => ({ name: t.name, description: t.description, mutating: t.mutating, source: builtin.has(t.name) ? "builtin" : "extension", inputSchema: t.inputSchema }));
3701
3867
  const human = (r) => table(r.tools, ["name", "mutating", "source", "description"]);
@@ -3715,7 +3881,7 @@ const toolsCommand = {
3715
3881
  }
3716
3882
  if (action === "call") {
3717
3883
  const name2 = positional(positionals, 1, "tool name", "tools call <name> --input <json>");
3718
- const { findTool, callTool, listAiTools } = await import("./chunks/tools-Bew8lxKx.mjs");
3884
+ const { findTool, callTool, listAiTools } = await import("./chunks/tools-iSH-yMpl.mjs");
3719
3885
  const input = jsonObjectOpt(options, "input") ?? {};
3720
3886
  const builtin = findTool(name2);
3721
3887
  return withSession(io, options, { lock: builtin ? builtin.mutating : true }, async ({ ctx }) => {
@@ -3737,8 +3903,8 @@ const mcpCommand = {
3737
3903
  const log = (msg) => process.stderr.write(`${msg}
3738
3904
  `);
3739
3905
  for (const w of session2.warnings) log(`[agentcrm mcp] note: ${w}`);
3740
- const { listAiTools } = await import("./chunks/tools-Bew8lxKx.mjs");
3741
- const { createMcpServer, serveStdio } = await import("./chunks/server-ISwlDRCN.mjs");
3906
+ const { listAiTools } = await import("./chunks/tools-iSH-yMpl.mjs");
3907
+ const { createMcpServer, serveStdio } = await import("./chunks/server-DC3t8r30.mjs");
3742
3908
  let lastRefresh = Date.now();
3743
3909
  const server = createMcpServer({
3744
3910
  readOnly,
@@ -3761,7 +3927,10 @@ const mcpCommand = {
3761
3927
  async withWrite(fn) {
3762
3928
  const unlock = lockSite(session2.site.alias);
3763
3929
  try {
3764
- return await fn();
3930
+ const result = await fn();
3931
+ const report = await session2.extensions.sync();
3932
+ for (const f of report?.failed ?? []) log(`[agentcrm mcp] extension ${f.id} failed to load: ${f.error}`);
3933
+ return result;
3765
3934
  } finally {
3766
3935
  unlock();
3767
3936
  }
@@ -3866,7 +4035,11 @@ async function main(argv, io) {
3866
4035
  const json = argv.includes("--json");
3867
4036
  const out = createIo(json);
3868
4037
  try {
3869
- const [name2 = "help", ...rest] = argv.filter((a) => a !== "--json");
4038
+ const args = argv.filter((a) => a !== "--json");
4039
+ const leading = [];
4040
+ while (args[0] && LEADING_GLOBAL_FLAGS.has(args[0])) leading.push(args.shift());
4041
+ const [name2 = "help", ...rest] = args;
4042
+ rest.push(...leading);
3870
4043
  if (name2 === "--help" || name2 === "-h") return await run("help", rest, out) ?? 0;
3871
4044
  if (name2 === "--version" || name2 === "-v") return await run("version", rest, out) ?? 0;
3872
4045
  return await run(name2, rest, out) ?? 0;
@@ -3874,6 +4047,7 @@ async function main(argv, io) {
3874
4047
  return printError(out, error);
3875
4048
  }
3876
4049
  }
4050
+ const LEADING_GLOBAL_FLAGS = new Set(GLOBAL_OPTIONS.filter((o) => o.type === "boolean" && o.name !== "help").map((o) => `--${o.name}`));
3877
4051
  async function run(name2, args, io) {
3878
4052
  const command = findCommand(name2);
3879
4053
  if (!command) {
@@ -3891,13 +4065,21 @@ async function run(name2, args, io) {
3891
4065
  throw new CliError("usage", e.message, `Usage: agentcrm ${command.usage}`, 2);
3892
4066
  }
3893
4067
  if (parsed.values.help && command.name !== "help") return run("help", [command.name], io);
3894
- return command.run({
3895
- io,
3896
- options: parsed.values,
3897
- positionals: parsed.positionals
3898
- });
4068
+ try {
4069
+ return await command.run({
4070
+ io,
4071
+ options: parsed.values,
4072
+ positionals: parsed.positionals
4073
+ });
4074
+ } catch (e) {
4075
+ if (e instanceof CliError && !e.hint && e.exitCode === 2) {
4076
+ throw new CliError(e.code, e.message, `Usage: agentcrm ${command.usage} (details: \`agentcrm help ${command.name}\`).`, e.exitCode);
4077
+ }
4078
+ throw e;
4079
+ }
3899
4080
  }
3900
4081
  installBrowserEnv();
4082
+ routeConsoleToStderr();
3901
4083
  main(process.argv.slice(2)).then(
3902
4084
  (code) => process.exit(code),
3903
4085
  (err) => {
@@ -3913,15 +4095,16 @@ export {
3913
4095
  LOCATIONS as L,
3914
4096
  PACKAGE_NAME as P,
3915
4097
  USER_ROLES as U,
3916
- parseAccount as a,
3917
- requireCredential as b,
3918
- findLocation as c,
3919
- findHosting as d,
3920
- FIELD_TYPES as e,
4098
+ FIELD_TYPES as a,
4099
+ parseAccount as b,
4100
+ requireCredential as c,
4101
+ findLocation as d,
4102
+ findHosting as e,
3921
4103
  findBackend as f,
3922
4104
  getCredential as g,
3923
4105
  openStore as h,
3924
- distRoot as i,
4106
+ installBrowserEnv as i,
4107
+ distRoot as j,
3925
4108
  nodeImportBundle as n,
3926
4109
  ownPackageRoot as o,
3927
4110
  packageRoot as p,