@fleox-agent-crm/agentcrm 0.4.0 → 0.6.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 (50) hide show
  1. package/README.md +16 -16
  2. package/changelog.json +18 -0
  3. package/dist/admin/admin-files.json +1 -1
  4. package/dist/agent/agentcrm.mjs +188 -76
  5. package/dist/agent/chunks/{activities-Cj_SIOay.mjs → activities-be260oPg.mjs} +1 -1
  6. package/dist/agent/chunks/{automations-DwWruxmu.mjs → automations-gCDYNS88.mjs} +5 -5
  7. package/dist/agent/chunks/{backup-W3CjgkAJ.mjs → backup-DDqTRbuW.mjs} +4 -4
  8. package/dist/agent/chunks/{compact-BGHI92tm.mjs → compact-C_WX0_F5.mjs} +5 -5
  9. package/dist/agent/chunks/{dedupe-BepmsaHv.mjs → dedupe-isAmKHQV.mjs} +1 -1
  10. package/dist/agent/chunks/{exportCsv-C6e6XEmB.mjs → exportCsv-9ZMyqTdH.mjs} +3 -3
  11. package/dist/agent/chunks/{extCheck-GgHG1dYB.mjs → extCheck-BKh_9aLA.mjs} +4 -4
  12. package/dist/agent/chunks/{files-D1daDgY0.mjs → files-Be7DZd1a.mjs} +6 -6
  13. package/dist/agent/chunks/{forms-BfuWaQjA.mjs → forms-Bb0zztBM.mjs} +4 -4
  14. package/dist/agent/chunks/{importCsv-LVmMTaWU.mjs → importCsv-BuSYG4ja.mjs} +3 -3
  15. package/dist/agent/chunks/{install-BLNflFes.mjs → install-D1lgtRIH.mjs} +1 -1
  16. package/dist/agent/chunks/{loader-MPD9iAvh.mjs → loader-Bnu3GnMW.mjs} +14 -14
  17. package/dist/agent/chunks/{migrate-CsClnnN8.mjs → migrate-Cc_8MHZv.mjs} +1 -1
  18. package/dist/agent/chunks/{pipelines-BAKpPk1w.mjs → pipelines-G-c23x-s.mjs} +4 -4
  19. package/dist/agent/chunks/{records-DcHFelq5.mjs → records-BJLSmv0O.mjs} +2 -2
  20. package/dist/agent/chunks/{registry-COfJrSmg.mjs → registry-BpTyK6yU.mjs} +13 -13
  21. package/dist/agent/chunks/{relations-B9TynBmA.mjs → relations-bmUKPT0G.mjs} +4 -4
  22. package/dist/agent/chunks/{reports-BACHGccj.mjs → reports-ogUa41jr.mjs} +5 -5
  23. package/dist/agent/chunks/{runtime-C0Xf0Axi.mjs → runtime-D-hzk6s9.mjs} +20 -20
  24. package/dist/agent/chunks/{schema-C4lSslXv.mjs → schema-CYm_n5BN.mjs} +4 -4
  25. package/dist/agent/chunks/schemaApply-j5hbwPrH.mjs +169 -0
  26. package/dist/agent/chunks/{seed-6MRmQQBR.mjs → seed-Bb26Zsni.mjs} +2 -2
  27. package/dist/agent/chunks/{server-DC3t8r30.mjs → server-BAQwVNtC.mjs} +3 -3
  28. package/dist/agent/chunks/{standardObjects-CheqfDqa.mjs → standardObjects-vrmqIVLH.mjs} +1 -1
  29. package/dist/agent/chunks/{stats-BfuLWhVV.mjs → stats-bInarNLK.mjs} +4 -4
  30. package/dist/agent/chunks/syncRecords-69hcCgOK.mjs +102 -0
  31. package/dist/agent/chunks/{tools-iSH-yMpl.mjs → tools-BHmVIyP_.mjs} +12 -12
  32. package/dist/agent/chunks/{views-DH1n9Lrt.mjs → views-zRdsphZl.mjs} +5 -5
  33. package/dist/version.json +1 -1
  34. package/package.json +1 -1
  35. package/plugin/.claude-plugin/plugin.json +1 -1
  36. package/plugin/skills/agentcrm-admin/SKILL.md +67 -67
  37. package/plugin/skills/agentcrm-admin/references/records.md +5 -5
  38. package/plugin/skills/agentcrm-admin/references/schema.md +19 -19
  39. package/plugin/skills/agentcrm-extensions/SKILL.md +8 -8
  40. package/plugin/skills/agentcrm-extensions/references/module-recipes.md +2 -2
  41. package/plugin/skills/agentcrm-extensions/references/testing.md +14 -14
  42. package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +3 -3
  43. package/plugin/skills/agentcrm-install/SKILL.md +13 -13
  44. package/plugin/skills/agentcrm-install/references/firebase.md +1 -1
  45. package/plugin/skills/agentcrm-install/references/github.md +1 -1
  46. package/plugin/skills/agentcrm-install/references/mcp.md +2 -2
  47. package/plugin/skills/agentcrm-install/references/plans.md +1 -1
  48. package/plugin/skills/agentcrm-install/references/s3.md +1 -1
  49. package/plugin/skills/agentcrm-manage/SKILL.md +15 -15
  50. package/plugin/skills/agentcrm-sales/SKILL.md +18 -18
@@ -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.4.0";
145
+ const version = "0.6.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-D-hzk6s9.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-Bnu3GnMW.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-BpTyK6yU.mjs")).deactivateAllExtensions();
557
557
  }
558
558
  };
559
559
  }
@@ -681,7 +681,7 @@ async function openSession(alias, opts = {}) {
681
681
  const [{ createOpsContext }, { createPluginRegistry, plugins: defaultPlugins }, { installAutomations }] = await Promise.all([
682
682
  import("./chunks/context-CxFfqS4D.mjs"),
683
683
  import("./chunks/pluginRegistry-DyHtlkcr.mjs"),
684
- import("./chunks/automations-DwWruxmu.mjs")
684
+ import("./chunks/automations-gCDYNS88.mjs")
685
685
  ]);
686
686
  const own = createPluginRegistry((hook, err) => process.stderr.write(`[agentcrm] ${hook} handler failed: ${String(err?.message ?? err)}
687
687
  `));
@@ -695,7 +695,7 @@ async function openSession(alias, opts = {}) {
695
695
  const ctx = createOpsContext(store, user, { plugins, log });
696
696
  const system = createOpsContext(store, null, { plugins, log });
697
697
  if (opts.lock && (!user || user.role === "admin") && store.list("objects").length) {
698
- 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);
699
699
  await migrateSchema(ctx).catch((err) => warnings.push(`Schema migration skipped: ${err.message}`));
700
700
  }
701
701
  if (site.hosting) {
@@ -714,7 +714,7 @@ async function openSession(alias, opts = {}) {
714
714
  }
715
715
  }
716
716
  {
717
- const forms = await import("./chunks/forms-BfuWaQjA.mjs");
717
+ const forms = await import("./chunks/forms-Bb0zztBM.mjs");
718
718
  const { buildConnector } = await import("./chunks/hosting-C_UDyMkm.mjs");
719
719
  forms.setInboxSourceResolver((_ctx, form) => {
720
720
  if (form.inbox.kind !== "s3") return null;
@@ -914,6 +914,29 @@ function table(rows, columns) {
914
914
  const SITE_OPTION = { name: "site", type: "string", description: "Site alias (default: $AGENTCRM_SITE, ./agentcrm.json, the only site, or `site use`)." };
915
915
  const AS_OPTION = { name: "as", type: "string", description: "Act as this CRM user (email or id) instead of the site's user." };
916
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" }));
917
940
  function fileOut(f) {
918
941
  return {
919
942
  id: f.id,
@@ -936,7 +959,7 @@ function readLocalFile(path) {
936
959
  return { name: basename(full), bytes: new Uint8Array(readFileSync(full)) };
937
960
  }
938
961
  async function uploadLocalFiles(ctx, paths, targets2 = []) {
939
- const { uploadFile } = await import("./chunks/files-D1daDgY0.mjs");
962
+ const { uploadFile } = await import("./chunks/files-Be7DZd1a.mjs");
940
963
  const { guessMime, imageDimensions } = await import("./chunks/files-CjsPJSR6.mjs");
941
964
  const out = [];
942
965
  for (const p of paths) {
@@ -984,7 +1007,7 @@ const filesCommand = {
984
1007
  switch (action) {
985
1008
  case "list":
986
1009
  return withSession(io, options, {}, async ({ ctx }) => {
987
- const { listFiles } = await import("./chunks/files-D1daDgY0.mjs");
1010
+ const { listFiles } = await import("./chunks/files-Be7DZd1a.mjs");
988
1011
  const target = strOpt(options, "record") ?? strOpt(options, "activity") ?? null;
989
1012
  const items = listFiles(ctx, target, { includeRelated: boolOpt(options, "related"), kind: strOpt(options, "kind"), search: strOpt(options, "search") });
990
1013
  const limit = Math.min(1e3, numOpt(options, "limit") ?? 100);
@@ -999,7 +1022,7 @@ ${table(r.files, ["id", "name", "mime", "size", "createdAt"])}`);
999
1022
  if (record && activity) usage("Give --record or --activity, not both.");
1000
1023
  for (const p of args) readLocalFile(p);
1001
1024
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1002
- const { attachFile } = await import("./chunks/files-D1daDgY0.mjs");
1025
+ const { attachFile } = await import("./chunks/files-Be7DZd1a.mjs");
1003
1026
  const files2 = await uploadLocalFiles(ctx, args, record ? [record] : []);
1004
1027
  if (activity) for (const f of files2) await attachFile(ctx, f.id, { kind: "activity", id: activity });
1005
1028
  const { requireFile } = await import("./chunks/store-5gqRSKXg.mjs");
@@ -1012,7 +1035,7 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
1012
1035
  const out = strOpt(options, "out");
1013
1036
  if (!out) usage("files get needs --out <path>.");
1014
1037
  return withSession(io, options, {}, async ({ ctx }) => {
1015
- const { getFile, getFileBytes } = await import("./chunks/files-D1daDgY0.mjs");
1038
+ const { getFile, getFileBytes } = await import("./chunks/files-Be7DZd1a.mjs");
1016
1039
  const meta = getFile(ctx, id);
1017
1040
  const bytes = await getFileBytes(ctx, id, { thumb: boolOpt(options, "thumb") });
1018
1041
  writeOutFile(out, bytes);
@@ -1022,7 +1045,7 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
1022
1045
  case "delete": {
1023
1046
  const id = positional(args, 0, "file id", U$1.delete);
1024
1047
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1025
- const { getFile, deleteFile } = await import("./chunks/files-D1daDgY0.mjs");
1048
+ const { getFile, deleteFile } = await import("./chunks/files-Be7DZd1a.mjs");
1026
1049
  const meta = fileOut(getFile(ctx, id));
1027
1050
  if (options.yes !== true) return { needsConfirmation: true, plan: { delete: meta } };
1028
1051
  await deleteFile(ctx, id);
@@ -1030,7 +1053,7 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
1030
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\`).`);
1031
1054
  }
1032
1055
  case "usage":
1033
- 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-Be7DZd1a.mjs")).fileUsage(ctx));
1034
1057
  default:
1035
1058
  usage(`Unknown files action "${action ?? ""}".`, "Use list, upload, get, delete or usage.");
1036
1059
  }
@@ -1043,7 +1066,7 @@ async function recordImageAction(io, options, args) {
1043
1066
  if (!remove && !path) usage("records image needs an image path (or --remove).");
1044
1067
  const local = remove ? null : readLocalFile(path);
1045
1068
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1046
- const { setRecordImage, clearRecordImage } = await import("./chunks/files-D1daDgY0.mjs");
1069
+ const { setRecordImage, clearRecordImage } = await import("./chunks/files-Be7DZd1a.mjs");
1047
1070
  const field = strOpt(options, "field");
1048
1071
  if (!local) {
1049
1072
  const r = await clearRecordImage(ctx, recordId, field);
@@ -1137,7 +1160,7 @@ const activitiesCommand = {
1137
1160
  const recs = multiOpt(options, "record");
1138
1161
  if (!recs.length && type2 !== "task") usage("activities add needs --record <id> (repeatable).");
1139
1162
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1140
- const { addActivity } = await import("./chunks/activities-Cj_SIOay.mjs");
1163
+ const { addActivity } = await import("./chunks/activities-be260oPg.mjs");
1141
1164
  return activityOut(
1142
1165
  await addActivity(ctx, {
1143
1166
  type: type2,
@@ -1154,7 +1177,7 @@ const activitiesCommand = {
1154
1177
  }
1155
1178
  case "list":
1156
1179
  return withSession(io, options, {}, async ({ ctx }) => {
1157
- const { listActivities } = await import("./chunks/activities-Cj_SIOay.mjs");
1180
+ const { listActivities } = await import("./chunks/activities-be260oPg.mjs");
1158
1181
  const types = listOpt(options, "type");
1159
1182
  const page = listActivities(ctx, { target: multiOpt(options, "record")[0], types, includeRelated: boolOpt(options, "related"), limit: Math.min(500, numOpt(options, "limit") ?? 50) });
1160
1183
  return { total: page.total, activities: page.items.map(activityOut) };
@@ -1162,7 +1185,7 @@ const activitiesCommand = {
1162
1185
  ${table(r.activities, ["id", "type", "title", "createdAt"])}`);
1163
1186
  case "done": {
1164
1187
  if (!id) usage("activities done needs an id.");
1165
- return withSession(io, options, { lock: true }, async ({ ctx }) => activityOut(await (await import("./chunks/activities-Cj_SIOay.mjs")).completeTask(ctx, id, !boolOpt(options, "undo"))));
1188
+ return withSession(io, options, { lock: true }, async ({ ctx }) => activityOut(await (await import("./chunks/activities-be260oPg.mjs")).completeTask(ctx, id, !boolOpt(options, "undo"))));
1166
1189
  }
1167
1190
  default:
1168
1191
  usage(`Unknown activities action "${action ?? ""}".`, "Use add, list or done.");
@@ -1183,7 +1206,7 @@ const notesCommand = {
1183
1206
  if (!text?.trim()) usage("notes add needs --body.");
1184
1207
  const files2 = attachPaths(options);
1185
1208
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1186
- const ops = await import("./chunks/activities-Cj_SIOay.mjs");
1209
+ const ops = await import("./chunks/activities-be260oPg.mjs");
1187
1210
  const { recordOf } = await import("./chunks/store-5gqRSKXg.mjs");
1188
1211
  const r = recordOf(ctx.store, id);
1189
1212
  if (!files2.length || !r) return activityOut(await ops.addNote(ctx, id, text, strOpt(options, "title")));
@@ -1226,7 +1249,7 @@ const tasksCommand = {
1226
1249
  switch (action) {
1227
1250
  case "list":
1228
1251
  return withSession(io, options, {}, async ({ ctx }) => {
1229
- const { listTasks } = await import("./chunks/activities-Cj_SIOay.mjs");
1252
+ const { listTasks } = await import("./chunks/activities-be260oPg.mjs");
1230
1253
  const status = strOpt(options, "status") ?? "open";
1231
1254
  if (!["open", "done", "all"].includes(status)) usage("--status must be open, done or all.");
1232
1255
  const page = listTasks(ctx, { assigneeId: await assignee(ctx), target: multiOpt(options, "record")[0], status, overdue: boolOpt(options, "overdue"), dueWithinDays: numOpt(options, "due-within"), limit: Math.min(500, numOpt(options, "limit") ?? 50) });
@@ -1237,17 +1260,17 @@ ${table(r.tasks, ["id", "title", "dueAt", "assigneeId"])}`);
1237
1260
  const title = requireStr(options, "title");
1238
1261
  const files2 = attachPaths(options);
1239
1262
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1240
- const { addActivity } = await import("./chunks/activities-Cj_SIOay.mjs");
1263
+ const { addActivity } = await import("./chunks/activities-be260oPg.mjs");
1241
1264
  return activityOut(await addActivity(ctx, { type: "task", title, body: body(options), dueAt: strOpt(options, "due"), assigneeId: await assignee(ctx) ?? ctx.user?.id, targets: targets(multiOpt(options, "record")), attachments: await attachments(ctx, files2) }));
1242
1265
  });
1243
1266
  }
1244
1267
  case "done":
1245
1268
  if (!id) usage("tasks done needs a task id.");
1246
- return withSession(io, options, { lock: true }, async ({ ctx }) => activityOut(await (await import("./chunks/activities-Cj_SIOay.mjs")).completeTask(ctx, id, !boolOpt(options, "undo"))));
1269
+ return withSession(io, options, { lock: true }, async ({ ctx }) => activityOut(await (await import("./chunks/activities-be260oPg.mjs")).completeTask(ctx, id, !boolOpt(options, "undo"))));
1247
1270
  case "reassign":
1248
1271
  if (!id) usage("tasks reassign needs a task id.");
1249
1272
  requireStr(options, "assignee");
1250
- return withSession(io, options, { lock: true }, async ({ ctx }) => activityOut(await (await import("./chunks/activities-Cj_SIOay.mjs")).updateActivity(ctx, id, { assigneeId: await assignee(ctx) ?? null })));
1273
+ return withSession(io, options, { lock: true }, async ({ ctx }) => activityOut(await (await import("./chunks/activities-be260oPg.mjs")).updateActivity(ctx, id, { assigneeId: await assignee(ctx) ?? null })));
1251
1274
  default:
1252
1275
  usage(`Unknown tasks action "${action ?? ""}".`, "Use list, add, done or reassign.");
1253
1276
  }
@@ -1278,11 +1301,12 @@ const U = {
1278
1301
  search: "records search <query> [--object <id>]… [--limit n]",
1279
1302
  duplicates: "records duplicates --object <id> [--by email,domain,name,phone]",
1280
1303
  merge: "records merge <primaryId> <duplicateId>… [--yes]",
1281
- image: "records image <recordId> <path> | --remove [--field <imageFieldId>]"
1304
+ image: "records image <recordId> <path> | --remove [--field <imageFieldId>]",
1305
+ sync: "records sync --object <id> --key <fieldId> --rows <json|@file|-> [--archive-field <fieldId>] [--ref-key <fieldId>] [--yes]"
1282
1306
  };
1283
1307
  const recordsCommand = {
1284
1308
  name: "records",
1285
- usage: "records <list|get|create|update|delete|restore|search|duplicates|merge|image> …",
1309
+ usage: "records <list|get|create|update|delete|restore|search|duplicates|merge|image|sync> …",
1286
1310
  summary: "Records of any object (companies, people, deals, custom objects): list with filters / sorts / pagination, get with relations and timeline counts, create, update (only the given fields), soft delete / restore, full-text search, find and merge duplicates.",
1287
1311
  subcommands: [
1288
1312
  { name: "list", usage: U.list, summary: `List records. Filter ops: ${FILTER_OPS.join(", ")}. Values: in/notIn comma-separated; dates YYYY-MM-DD, today, +30d.` },
@@ -1294,7 +1318,13 @@ const recordsCommand = {
1294
1318
  { name: "search", usage: U.search, summary: "Full-text search (names, emails, domains, phones)." },
1295
1319
  { name: "duplicates", usage: U.duplicates, summary: "Groups of likely duplicates (same email / domain / name / phone)." },
1296
1320
  { name: "merge", usage: U.merge, summary: "Merge duplicates into the primary (empty fields filled, relations and activities moved). Plan unless --yes.", writes: true, destructive: true },
1297
- { name: "image", usage: U.image, summary: "Set the record's avatar / logo from a local image (the object's image field), or --remove it.", writes: true }
1321
+ { name: "image", usage: U.image, summary: "Set the record's avatar / logo from a local image (the object's image field), or --remove it.", writes: true },
1322
+ {
1323
+ name: "sync",
1324
+ usage: U.sync,
1325
+ summary: 'Mirror an external source into one object: rows [{ key, fields }] matched on the --key field, created / updated only where a value differs; relation values reference other mirrored records by their key ({ "$key": "12" } or { "$key": ["12","13"] }); records whose key is missing get --archive-field = true (never deleted). No automation fires. Plan unless --yes.',
1326
+ writes: true
1327
+ }
1298
1328
  ],
1299
1329
  options: [
1300
1330
  SITE_OPTION,
@@ -1315,7 +1345,11 @@ const recordsCommand = {
1315
1345
  { name: "hard", type: "boolean", description: "delete: remove immediately (no restore)." },
1316
1346
  { name: "by", type: "string", description: "duplicates: email,domain,name,phone." },
1317
1347
  { name: "remove", type: "boolean", description: "image: remove the record's image." },
1318
- { name: "field", type: "string", description: "image: image field id (default: the object's image field)." }
1348
+ { name: "field", type: "string", description: "image: image field id (default: the object's image field)." },
1349
+ { name: "key", type: "string", description: "sync: field holding the source id (match key)." },
1350
+ { name: "rows", type: "string", description: "sync: rows as a JSON array [{ key, fields }], @file.json or - (stdin)." },
1351
+ { name: "archive-field", type: "string", description: "sync: boolean field set to true on records whose key is missing from the rows." },
1352
+ { name: "ref-key", type: "string", description: 'sync: key field of related objects for { "$key": … } (default: --key).' }
1319
1353
  ],
1320
1354
  async run({ io, options, positionals }) {
1321
1355
  const [action, ...args] = positionals;
@@ -1331,7 +1365,7 @@ const recordsCommand = {
1331
1365
  const filters = parseFilters(multiOpt(options, "filter"), FILTER_OPS);
1332
1366
  const sorts = parseSorts(multiOpt(options, "sort"));
1333
1367
  return withSession(io, options, {}, async ({ ctx }) => {
1334
- const { listRecords } = await import("./chunks/records-DcHFelq5.mjs");
1368
+ const { listRecords } = await import("./chunks/records-BJLSmv0O.mjs");
1335
1369
  const page = listRecords(ctx, objectId, {
1336
1370
  filters,
1337
1371
  sorts,
@@ -1349,7 +1383,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1349
1383
  case "get": {
1350
1384
  const id = positional(args, 0, "record id", U.get);
1351
1385
  return withSession(io, options, {}, async ({ ctx }) => {
1352
- const { getRecordDetails } = await import("./chunks/records-DcHFelq5.mjs");
1386
+ const { getRecordDetails } = await import("./chunks/records-BJLSmv0O.mjs");
1353
1387
  const d = getRecordDetails(ctx, id);
1354
1388
  return { ...await recordOut(ctx, d.record), relations: d.relations, related: d.inverse.filter((i) => i.records.length), activityCount: d.activityCount };
1355
1389
  });
@@ -1360,7 +1394,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1360
1394
  if (!Object.keys(fields).length) usage("records create needs --fields or --set.", `Usage: agentcrm ${U.create}`);
1361
1395
  const owner = strOpt(options, "owner");
1362
1396
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1363
- const { createRecord } = await import("./chunks/records-DcHFelq5.mjs");
1397
+ const { createRecord } = await import("./chunks/records-BJLSmv0O.mjs");
1364
1398
  return recordOut(ctx, await createRecord(ctx, objectId, fields, owner ? { ownerId: owner } : {}));
1365
1399
  }, (r) => `Created ${r.object} ${r.id} (${String(r.label)})`);
1366
1400
  }
@@ -1370,7 +1404,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1370
1404
  const owner = strOpt(options, "owner");
1371
1405
  if (!Object.keys(fields).length && owner === void 0) usage("records update needs --fields, --set or --owner.");
1372
1406
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1373
- const { updateRecord } = await import("./chunks/records-DcHFelq5.mjs");
1407
+ const { updateRecord } = await import("./chunks/records-BJLSmv0O.mjs");
1374
1408
  return recordOut(ctx, await updateRecord(ctx, id, fields, owner !== void 0 ? { ownerId: owner || null } : {}));
1375
1409
  }, (r) => `Updated ${r.id} (${String(r.label)})`);
1376
1410
  }
@@ -1378,7 +1412,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1378
1412
  if (!args.length) usage("records delete needs at least one id.", `Usage: agentcrm ${U.delete}`);
1379
1413
  const hard = boolOpt(options, "hard");
1380
1414
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1381
- const { getRecord, deleteRecord, bulkDelete } = await import("./chunks/records-DcHFelq5.mjs");
1415
+ const { getRecord, deleteRecord, bulkDelete } = await import("./chunks/records-BJLSmv0O.mjs");
1382
1416
  const { recordLabel } = await import("./chunks/fields-B0bitgmr.mjs");
1383
1417
  const { objectOf } = await import("./chunks/store-5gqRSKXg.mjs");
1384
1418
  const targets2 = args.map((id) => getRecord(ctx, id, { includeDeleted: true }));
@@ -1391,7 +1425,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1391
1425
  case "restore": {
1392
1426
  const id = positional(args, 0, "record id", U.restore);
1393
1427
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
1394
- const { restoreRecord } = await import("./chunks/records-DcHFelq5.mjs");
1428
+ const { restoreRecord } = await import("./chunks/records-BJLSmv0O.mjs");
1395
1429
  return recordOut(ctx, await restoreRecord(ctx, id));
1396
1430
  });
1397
1431
  }
@@ -1399,14 +1433,14 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1399
1433
  const query = args.join(" ").trim();
1400
1434
  if (!query) usage("records search needs a query.", `Usage: agentcrm ${U.search}`);
1401
1435
  return withSession(io, options, {}, async ({ ctx }) => {
1402
- const { searchRecords } = await import("./chunks/records-DcHFelq5.mjs");
1436
+ const { searchRecords } = await import("./chunks/records-BJLSmv0O.mjs");
1403
1437
  return searchRecords(ctx, query, { objects: listOpt(options, "object"), limit: Math.min(100, numOpt(options, "limit") ?? 20) });
1404
1438
  });
1405
1439
  }
1406
1440
  case "duplicates": {
1407
1441
  const objectId = object();
1408
1442
  return withSession(io, options, {}, async ({ ctx }) => {
1409
- const { findDuplicates } = await import("./chunks/dedupe-BepmsaHv.mjs");
1443
+ const { findDuplicates } = await import("./chunks/dedupe-isAmKHQV.mjs");
1410
1444
  const by = listOpt(options, "by");
1411
1445
  const groups = findDuplicates(ctx, objectId, by ? { by } : {});
1412
1446
  return { object: objectId, groups: groups.length, duplicates: groups };
@@ -1416,19 +1450,48 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
1416
1450
  const [primary, ...dups] = args;
1417
1451
  if (!primary || !dups.length) usage("records merge needs <primaryId> <duplicateId>….", `Usage: agentcrm ${U.merge}`);
1418
1452
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1419
- const { getRecord } = await import("./chunks/records-DcHFelq5.mjs");
1453
+ const { getRecord } = await import("./chunks/records-BJLSmv0O.mjs");
1420
1454
  const all = [primary, ...dups].map((id) => getRecord(ctx, id));
1421
1455
  const plan = { primary: await recordOut(ctx, all[0]), duplicates: await Promise.all(all.slice(1).map((r) => recordOut(ctx, r))) };
1422
1456
  if (options.yes !== true) return { needsConfirmation: true, plan };
1423
- const { mergeRecords } = await import("./chunks/dedupe-BepmsaHv.mjs");
1457
+ const { mergeRecords } = await import("./chunks/dedupe-isAmKHQV.mjs");
1424
1458
  const res = await mergeRecords(ctx, primary, dups);
1425
1459
  return { record: await recordOut(ctx, res.record), merged: res.merged, movedActivities: res.movedActivities };
1426
1460
  }, (r) => "plan" in r && r.plan ? `Would merge ${r.plan.duplicates.length} duplicate(s) into ${r.plan.primary.id}. Re-run with --yes once the user agrees.` : "merged" in r ? `Merged ${r.merged.length} into ${r.record.id}` : "");
1427
1461
  }
1428
1462
  case "image":
1429
1463
  return (await Promise.resolve().then(() => files)).recordImageAction(io, options, args);
1464
+ case "sync": {
1465
+ const objectId = Array.isArray(options.object) ? options.object[0] : options.object;
1466
+ const key = strOpt(options, "key");
1467
+ const rowsRaw = strOpt(options, "rows");
1468
+ if (!objectId || !key || !rowsRaw) usage("records sync needs --object, --key and --rows.", `Usage: agentcrm ${U.sync}`);
1469
+ const { readTextValue: readTextValue2 } = await Promise.resolve().then(() => util);
1470
+ let rows;
1471
+ try {
1472
+ rows = JSON.parse(readTextValue2(rowsRaw, "rows"));
1473
+ } catch (err) {
1474
+ usage(`--rows is not valid JSON: ${err.message}`);
1475
+ }
1476
+ if (!Array.isArray(rows)) usage("--rows must be a JSON array of { key, fields }.");
1477
+ const spec = { object: objectId, key, rows, archiveField: strOpt(options, "archive-field"), refKey: strOpt(options, "ref-key") };
1478
+ const apply = options.yes === true;
1479
+ return withSession(io, options, { lock: apply }, async ({ ctx }) => {
1480
+ const { syncRecords } = await import("./chunks/syncRecords-69hcCgOK.mjs");
1481
+ if (apply) return syncRecords(ctx, spec);
1482
+ const { dryRun: dryRun2 } = await Promise.resolve().then(() => dryRun$1);
1483
+ const { result } = await dryRun2(ctx, (copy) => syncRecords(copy, spec));
1484
+ const pending = result.created + result.updated + result.archived;
1485
+ return pending ? { needsConfirmation: true, plan: result } : result;
1486
+ }, (r) => {
1487
+ const s = "needsConfirmation" in r ? r.plan : r;
1488
+ const line = `${s.object}: ${s.rows} rows → ${s.created} new, ${s.updated} updated, ${s.unchanged} unchanged, ${s.archived} archived${s.unresolved ? `, ${s.unresolved} unresolved references` : ""}${s.errors.length ? `, ${s.errors.length} errors (first: ${s.errors[0].key}: ${s.errors[0].message})` : ""}`;
1489
+ return "needsConfirmation" in r ? `${line}
1490
+ Nothing changed — re-run with --yes once the user agrees.` : line;
1491
+ });
1492
+ }
1430
1493
  default:
1431
- usage(`Unknown records action "${action ?? ""}".`, "Use list, get, create, update, delete, restore, search, duplicates, merge or image.");
1494
+ usage(`Unknown records action "${action ?? ""}".`, "Use list, get, create, update, delete, restore, search, duplicates, merge, image or sync.");
1432
1495
  }
1433
1496
  }
1434
1497
  };
@@ -1448,13 +1511,13 @@ const pipelinesCommand = {
1448
1511
  io,
1449
1512
  options,
1450
1513
  {},
1451
- async ({ ctx }) => (await import("./chunks/pipelines-BAKpPk1w.mjs")).listPipelines(ctx, strOpt(options, "object")),
1514
+ async ({ ctx }) => (await import("./chunks/pipelines-G-c23x-s.mjs")).listPipelines(ctx, strOpt(options, "object")),
1452
1515
  (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")
1453
1516
  );
1454
1517
  }
1455
1518
  if (action === "move") {
1456
1519
  if (!id || !stage.length) usage("pipelines move needs <recordId> <stage>.");
1457
- 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") })));
1520
+ return withSession(io, options, { lock: true }, async ({ ctx }) => recordOut(ctx, await (await import("./chunks/pipelines-G-c23x-s.mjs")).moveStage(ctx, id, stage.join(" "), { lostReason: strOpt(options, "lost-reason") })));
1458
1521
  }
1459
1522
  usage(`Unknown pipelines action "${action}".`, "Use list or move.");
1460
1523
  }
@@ -1486,7 +1549,7 @@ const viewsCommand = {
1486
1549
  const [action = "list", id] = positionals;
1487
1550
  switch (action) {
1488
1551
  case "list":
1489
- 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"]));
1552
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/views-zRdsphZl.mjs")).listViews(ctx, strOpt(options, "object")), (vs) => table(vs, ["id", "object", "name", "type", "shared"]));
1490
1553
  case "create": {
1491
1554
  const type2 = strOpt(options, "type") ?? "table";
1492
1555
  if (type2 !== "table" && type2 !== "kanban") usage("--type must be table or kanban.");
@@ -1501,12 +1564,12 @@ const viewsCommand = {
1501
1564
  ...strOpt(options, "pipeline") ? { pipelineId: strOpt(options, "pipeline") } : {},
1502
1565
  shared: boolOpt(options, "shared")
1503
1566
  };
1504
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/views-DH1n9Lrt.mjs")).createView(ctx, input));
1567
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/views-zRdsphZl.mjs")).createView(ctx, input));
1505
1568
  }
1506
1569
  case "delete":
1507
1570
  if (!id) usage("views delete needs a view id.");
1508
1571
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
1509
- const { getView, deleteView } = await import("./chunks/views-DH1n9Lrt.mjs");
1572
+ const { getView, deleteView } = await import("./chunks/views-zRdsphZl.mjs");
1510
1573
  const v = getView(ctx, id);
1511
1574
  if (options.yes !== true) return { needsConfirmation: true, plan: { delete: { id: v.id, name: v.name, object: v.object, shared: v.shared } } };
1512
1575
  return deleteView(ctx, id);
@@ -1833,6 +1896,10 @@ async function dryRun(ctx, work) {
1833
1896
  const copy = createOpsContext(store, ctx.user, { now: ctx.now, plugins: createPluginRegistry(() => void 0) });
1834
1897
  return { result: await work(copy), store };
1835
1898
  }
1899
+ const dryRun$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1900
+ __proto__: null,
1901
+ dryRun
1902
+ }, Symbol.toStringTag, { value: "Module" }));
1836
1903
  const CASCADE_OPTION = { name: "cascade", type: "boolean", description: "enable / disable: also enable the dependencies / disable the dependents." };
1837
1904
  function parseJsonValue(raw, name2) {
1838
1905
  const text = readTextValue(raw, name2);
@@ -2097,7 +2164,7 @@ async function check({ io, options, positionals }) {
2097
2164
  const kind = strOpt(options, "kind");
2098
2165
  if (kind && kind !== "module" && kind !== "theme") usage("--kind is module or theme.");
2099
2166
  const { readRawPackage } = await import("./chunks/extPackage-DuZ6GZhp.mjs");
2100
- const { checkExtension } = await import("./chunks/extCheck-GgHG1dYB.mjs");
2167
+ const { checkExtension } = await import("./chunks/extCheck-BKh_9aLA.mjs");
2101
2168
  const raw = await readRawPackage(target);
2102
2169
  const report = await checkExtension(raw, { kind, callTools: !boolOpt(options, "no-call") });
2103
2170
  printResult(io, report, () => {
@@ -2144,7 +2211,7 @@ async function install({ io, options, positionals }) {
2144
2211
  const apply = options.yes === true;
2145
2212
  const disabled = boolOpt(options, "disabled");
2146
2213
  if (!boolOpt(options, "force")) {
2147
- const { checkExtension } = await import("./chunks/extCheck-GgHG1dYB.mjs");
2214
+ const { checkExtension } = await import("./chunks/extCheck-BKh_9aLA.mjs");
2148
2215
  const report = await checkExtension(raw, { callTools: false });
2149
2216
  if (!report.valid) {
2150
2217
  throw new CliError(
@@ -2180,7 +2247,7 @@ async function install({ io, options, positionals }) {
2180
2247
  }
2181
2248
  const result = await ext.installExtension(s.ctx, pkg2, disabled ? { enabled: false } : {});
2182
2249
  const report = await s.extensions.sync();
2183
- const { getLoadedExtension } = await import("./chunks/registry-COfJrSmg.mjs");
2250
+ const { getLoadedExtension } = await import("./chunks/registry-BpTyK6yU.mjs");
2184
2251
  const loaded = getLoadedExtension(m.id);
2185
2252
  const failure = report?.failed.find((f) => f.id === m.id || f.id === "*");
2186
2253
  if (failure && !loaded) return { ...result, author: m.author ?? null, load: { status: "error", error: failure.error } };
@@ -2242,7 +2309,7 @@ const extCommand = {
2242
2309
  if (kind && kind !== "module" && kind !== "theme") usage("--kind is module or theme.");
2243
2310
  return withSession(io, options, {}, async (s) => {
2244
2311
  const { listExtensions } = await import("./chunks/extensions-Bp72uZ9a.mjs");
2245
- const { getLoadedExtension } = await import("./chunks/registry-COfJrSmg.mjs");
2312
+ const { getLoadedExtension } = await import("./chunks/registry-BpTyK6yU.mjs");
2246
2313
  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 } : {} }));
2247
2314
  }, (rows) => table(rows.map((r) => ({ ...r, enabled: r.enabled ? "yes" : "no" })), ["id", "kind", "name", "version", "enabled", "load", "author"]));
2248
2315
  }
@@ -2357,7 +2424,7 @@ const importCommand = {
2357
2424
  options,
2358
2425
  {},
2359
2426
  async ({ ctx }) => {
2360
- const plan = await (await import("./chunks/backup-W3CjgkAJ.mjs")).planBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" });
2427
+ const plan = await (await import("./chunks/backup-DDqTRbuW.mjs")).planBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" });
2361
2428
  return { needsConfirmation: true, plan: { restore: resolve(path), bytes: bytes.length, ...plan } };
2362
2429
  },
2363
2430
  (r) => [
@@ -2367,7 +2434,7 @@ const importCommand = {
2367
2434
  ].join("\n")
2368
2435
  );
2369
2436
  }
2370
- 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)`);
2437
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/backup-DDqTRbuW.mjs")).importBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" }), (r) => `Restored ${r.written} document(s) and ${r.files} file(s)`);
2371
2438
  }
2372
2439
  if (kind !== "csv") usage('Only "import csv" and "import zip" are supported.', "Usage: agentcrm import csv <file> --object <id>");
2373
2440
  const source = positional(positionals, 1, "CSV file (or -)", "import csv <file|-> --object <id>");
@@ -2378,7 +2445,7 @@ const importCommand = {
2378
2445
  const apply = options.yes === true && options["dry-run"] !== true;
2379
2446
  const shown = numOpt(options, "rows") ?? 100;
2380
2447
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2381
- const { importCsv } = await import("./chunks/importCsv-LVmMTaWU.mjs");
2448
+ const { importCsv } = await import("./chunks/importCsv-BuSYG4ja.mjs");
2382
2449
  const report = await importCsv(ctx, {
2383
2450
  object: objectId,
2384
2451
  csv,
@@ -2427,7 +2494,7 @@ const exportCommand = {
2427
2494
  const file2 = strOpt(options, "out");
2428
2495
  if (!file2) usage("export zip needs --out <file.zip>.");
2429
2496
  return withSession(io, options, {}, async ({ ctx }) => {
2430
- const { exportBackupZip } = await import("./chunks/backup-W3CjgkAJ.mjs");
2497
+ const { exportBackupZip } = await import("./chunks/backup-DDqTRbuW.mjs");
2431
2498
  const { zip, report } = await exportBackupZip(ctx, { includeFiles: options["no-files"] !== true });
2432
2499
  writeOutFile(file2, zip);
2433
2500
  return { file: resolve(file2), zipBytes: zip.length, files: report.files, missing: report.missing, fileBytes: report.bytes };
@@ -2446,7 +2513,7 @@ const exportCommand = {
2446
2513
  const filters = parseFilters(multiOpt(options, "filter"), FILTER_OPS);
2447
2514
  const sorts = parseSorts(multiOpt(options, "sort"));
2448
2515
  return withSession(io, options, {}, async ({ ctx }) => {
2449
- const { exportCsv } = await import("./chunks/exportCsv-C6e6XEmB.mjs");
2516
+ const { exportCsv } = await import("./chunks/exportCsv-9ZMyqTdH.mjs");
2450
2517
  const csv = exportCsv(ctx, objectId, {
2451
2518
  viewId: strOpt(options, "view"),
2452
2519
  filters,
@@ -2462,7 +2529,7 @@ const exportCommand = {
2462
2529
  }
2463
2530
  if (kind === "json") {
2464
2531
  return withSession(io, options, {}, async ({ ctx }) => {
2465
- const { exportJson } = await import("./chunks/exportCsv-C6e6XEmB.mjs");
2532
+ const { exportJson } = await import("./chunks/exportCsv-9ZMyqTdH.mjs");
2466
2533
  const data = exportJson(ctx, { collections: listOpt(options, "collections") });
2467
2534
  const text = JSON.stringify(data, null, 2);
2468
2535
  if (out) return deliver(text, {});
@@ -2506,7 +2573,7 @@ const reportCommand = {
2506
2573
  ...strOpt(options, "pipeline") ? { pipelineId: strOpt(options, "pipeline") } : {},
2507
2574
  ...strOpt(options, "owner") ? { ownerId: strOpt(options, "owner") } : {}
2508
2575
  };
2509
- return withSession(io, options, {}, async ({ ctx }) => ({ report: name2, data: (await import("./chunks/reports-BACHGccj.mjs")).runReport(ctx, internal, params) }));
2576
+ return withSession(io, options, {}, async ({ ctx }) => ({ report: name2, data: (await import("./chunks/reports-ogUa41jr.mjs")).runReport(ctx, internal, params) }));
2510
2577
  }
2511
2578
  };
2512
2579
  const statsCommand = {
@@ -2516,7 +2583,7 @@ const statsCommand = {
2516
2583
  options: [SITE_OPTION, AS_OPTION],
2517
2584
  async run({ io, options }) {
2518
2585
  return withSession(io, options, {}, async ({ ctx, store }) => {
2519
- const stats = (await import("./chunks/stats-BfuLWhVV.mjs")).getStats(ctx);
2586
+ const stats = (await import("./chunks/stats-bInarNLK.mjs")).getStats(ctx);
2520
2587
  const journal = store.stats;
2521
2588
  return { ...stats, ...journal ? { journal: await journal.call(store) } : {} };
2522
2589
  }, (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"));
@@ -2530,7 +2597,7 @@ const compactCommand = {
2530
2597
  async run({ io, options }) {
2531
2598
  const apply = options.yes === true;
2532
2599
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2533
- const { compactData } = await import("./chunks/compact-BGHI92tm.mjs");
2600
+ const { compactData } = await import("./chunks/compact-C_WX0_F5.mjs");
2534
2601
  const report = await compactData(ctx, { olderThanDays: numOpt(options, "older-than"), keepRuns: numOpt(options, "keep-runs"), dryRun: !apply });
2535
2602
  return apply ? report : { needsConfirmation: true, plan: report };
2536
2603
  }, (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}`);
@@ -2551,19 +2618,19 @@ const automationsCommand = {
2551
2618
  const [action = "list", id] = positionals;
2552
2619
  switch (action) {
2553
2620
  case "list":
2554
- 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"]));
2621
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/automations-gCDYNS88.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"]));
2555
2622
  case "enable":
2556
2623
  case "disable": {
2557
2624
  if (!id) usage(`automations ${action} needs an id.`);
2558
2625
  return withSession(io, options, { lock: true }, async ({ ctx }) => {
2559
- const mod = await import("./chunks/automations-DwWruxmu.mjs");
2626
+ const mod = await import("./chunks/automations-gCDYNS88.mjs");
2560
2627
  return mod.setAutomationEnabled(ctx, id, action === "enable");
2561
2628
  });
2562
2629
  }
2563
2630
  case "run": {
2564
2631
  const apply = options.yes === true;
2565
2632
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2566
- const { runScheduled } = await import("./chunks/automations-DwWruxmu.mjs");
2633
+ const { runScheduled } = await import("./chunks/automations-gCDYNS88.mjs");
2567
2634
  const report = await runScheduled(ctx, { dryRun: !apply, automationId: strOpt(options, "id"), force: boolOpt(options, "force") });
2568
2635
  return apply ? report : { needsConfirmation: true, ...report };
2569
2636
  }, (r) => `${"needsConfirmation" in r ? "DRY RUN — " : ""}ran ${r.ran}, skipped ${r.skipped}, errors ${r.errors}`);
@@ -2587,11 +2654,11 @@ const formsCommand = {
2587
2654
  const [action = "list", id] = positionals;
2588
2655
  switch (action) {
2589
2656
  case "list":
2590
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/forms-BfuWaQjA.mjs")).listForms(ctx), (fs) => table(fs, ["id", "name", "object", "publishedPath"]));
2657
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/forms-Bb0zztBM.mjs")).listForms(ctx), (fs) => table(fs, ["id", "name", "object", "publishedPath"]));
2591
2658
  case "publish": {
2592
2659
  if (!id) usage("forms publish needs a form id.");
2593
2660
  return withSession(io, options, { lock: options.yes === true }, async (s) => {
2594
- const forms = await import("./chunks/forms-BfuWaQjA.mjs");
2661
+ const forms = await import("./chunks/forms-Bb0zztBM.mjs");
2595
2662
  const built = await forms.buildFormHtml(s.ctx, id);
2596
2663
  const target = s.hostingTarget();
2597
2664
  if (!target) usage(`${s.site.alias} has no hosting to publish the form to.`);
@@ -2605,7 +2672,7 @@ const formsCommand = {
2605
2672
  case "ingest": {
2606
2673
  const apply = options.yes === true;
2607
2674
  return withSession(io, options, { lock: apply }, async ({ ctx }) => {
2608
- const { ingestInbox } = await import("./chunks/forms-BfuWaQjA.mjs");
2675
+ const { ingestInbox } = await import("./chunks/forms-Bb0zztBM.mjs");
2609
2676
  const report = await ingestInbox(ctx, strOpt(options, "form"), { dryRun: !apply, limit: numOpt(options, "limit") });
2610
2677
  return apply ? report : { needsConfirmation: true, ...report };
2611
2678
  }, (r) => `${"needsConfirmation" in r ? "DRY RUN — " : ""}processed ${r.processed}: ${r.created} created, ${r.updated} updated, ${r.failed} failed`);
@@ -2845,7 +2912,7 @@ async function runDoctor(site, opts = {}) {
2845
2912
  installBrowserEnv();
2846
2913
  try {
2847
2914
  if (site.runtime.backend === "journal" && site.runtime.journal.connectorId === "git-pages") {
2848
- const { probeTarget } = await import("./chunks/install-BLNflFes.mjs");
2915
+ const { probeTarget } = await import("./chunks/install-D1lgtRIH.mjs");
2849
2916
  const { dataTargetOf: dataTargetOf2 } = await Promise.resolve().then(() => session);
2850
2917
  const probe = await probeTarget(dataTargetOf2(site), { requireSiteUrl: false, requirePrivate: true });
2851
2918
  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." });
@@ -2871,7 +2938,7 @@ async function runDoctor(site, opts = {}) {
2871
2938
  if (!settingsDoc(store)) missing.push("settings");
2872
2939
  if (!usersOf(store).some((u) => u.role === "admin" && !u.disabled)) missing.push("an active admin user");
2873
2940
  if (missing.length && opts.repair) {
2874
- const { seedCrm } = await import("./chunks/seed-6MRmQQBR.mjs");
2941
+ const { seedCrm } = await import("./chunks/seed-Bb26Zsni.mjs");
2875
2942
  repaired = await seedCrm(session$1.system, { admin: site.user ? { name: site.user.split("@")[0], email: site.user } : void 0 });
2876
2943
  add({ id: "dataset", status: "ok", message: `Repaired: seeded ${missing.join(", ")}.` });
2877
2944
  } else {
@@ -3014,7 +3081,7 @@ const installCommand = {
3014
3081
  const { resolvePlan } = await import("./chunks/plan-BmTat6-T.mjs");
3015
3082
  const plan = resolvePlan(readPlan(planPath));
3016
3083
  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.");
3017
- const { checkPlan, runInstall } = await import("./chunks/install-BLNflFes.mjs");
3084
+ const { checkPlan, runInstall } = await import("./chunks/install-D1lgtRIH.mjs");
3018
3085
  io.err("Checking access…");
3019
3086
  const checks = await checkPlan(plan);
3020
3087
  const summary = {
@@ -3448,13 +3515,13 @@ const objectsCommand = {
3448
3515
  switch (action) {
3449
3516
  case "list":
3450
3517
  return withSession(io, options, {}, async ({ ctx, store }) => {
3451
- const { listObjects } = await import("./chunks/schema-C4lSslXv.mjs");
3518
+ const { listObjects } = await import("./chunks/schema-CYm_n5BN.mjs");
3452
3519
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3453
3520
  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 }));
3454
3521
  }, (rows) => table(rows, ["id", "labelPlural", "fields", "records", "standard"]));
3455
3522
  case "get": {
3456
3523
  const objectId = positional(positionals, 1, "object id", "objects get <id>");
3457
- return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).describeSchema(ctx, objectId)[0]);
3524
+ return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/schema-CYm_n5BN.mjs")).describeSchema(ctx, objectId)[0]);
3458
3525
  }
3459
3526
  case "create": {
3460
3527
  const data = jsonObjectOpt(options, "data") ?? {};
@@ -3467,7 +3534,7 @@ const objectsCommand = {
3467
3534
  ...strOpt(options, "color") ? { color: strOpt(options, "color") } : {}
3468
3535
  };
3469
3536
  if (!input.labelSingular || !input.labelPlural) usage("objects create needs --singular and --plural (or --data).");
3470
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).createObject(ctx, input));
3537
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-CYm_n5BN.mjs")).createObject(ctx, input));
3471
3538
  }
3472
3539
  case "update": {
3473
3540
  const objectId = positional(positionals, 1, "object id", "objects update <id> …");
@@ -3479,12 +3546,12 @@ const objectsCommand = {
3479
3546
  ...strOpt(options, "label-field") ? { labelField: strOpt(options, "label-field") } : {}
3480
3547
  };
3481
3548
  if (!Object.keys(patch).length) usage("objects update needs --singular, --plural, --icon, --color or --label-field.");
3482
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).updateObject(ctx, objectId, patch));
3549
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-CYm_n5BN.mjs")).updateObject(ctx, objectId, patch));
3483
3550
  }
3484
3551
  case "delete": {
3485
3552
  if (!id) usage("objects delete needs an object id.");
3486
3553
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx, store }) => {
3487
- const { getObject, deleteObject } = await import("./chunks/schema-C4lSslXv.mjs");
3554
+ const { getObject, deleteObject } = await import("./chunks/schema-CYm_n5BN.mjs");
3488
3555
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3489
3556
  const o = getObject(ctx, id);
3490
3557
  const plan = { object: o.id, label: o.labelPlural, standard: o.standard, recordsDeleted: recordsOf(store, o.id, { includeDeleted: true }).length };
@@ -3542,7 +3609,7 @@ const fieldsCommand = {
3542
3609
  ...boolOpt(options, "unique") ? { unique: true } : {},
3543
3610
  ...strOpt(options, "description") ? { description: strOpt(options, "description") } : {}
3544
3611
  };
3545
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).createField(ctx, objectId, input));
3612
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-CYm_n5BN.mjs")).createField(ctx, objectId, input));
3546
3613
  }
3547
3614
  case "update": {
3548
3615
  if (!fieldId) usage("fields update needs a field id.");
@@ -3559,12 +3626,12 @@ const fieldsCommand = {
3559
3626
  ...strOpt(options, "description") !== void 0 ? { description: strOpt(options, "description") } : {}
3560
3627
  };
3561
3628
  if (!Object.keys(patch).length) usage("fields update needs at least one change.");
3562
- return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).updateField(ctx, objectId, fieldId, patch));
3629
+ return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-CYm_n5BN.mjs")).updateField(ctx, objectId, fieldId, patch));
3563
3630
  }
3564
3631
  case "delete": {
3565
3632
  if (!fieldId) usage("fields delete needs a field id.");
3566
3633
  return withSession(io, options, { lock: options.yes === true }, async ({ ctx, store }) => {
3567
- const { getObject, deleteField } = await import("./chunks/schema-C4lSslXv.mjs");
3634
+ const { getObject, deleteField } = await import("./chunks/schema-CYm_n5BN.mjs");
3568
3635
  const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
3569
3636
  const field = getObject(ctx, objectId).fields.find((f) => f.id === fieldId);
3570
3637
  if (!field) usage(`No field "${fieldId}" on ${objectId}.`, `\`agentcrm objects get ${objectId}\` lists them.`);
@@ -3578,6 +3645,50 @@ const fieldsCommand = {
3578
3645
  }
3579
3646
  }
3580
3647
  };
3648
+ const schemaCommand = {
3649
+ name: "schema",
3650
+ usage: "schema apply <file|-> [--yes]",
3651
+ 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.",
3652
+ subcommands: [{ name: "apply", usage: "schema apply <file|-> [--yes]", summary: "Apply a schema description (plan unless --yes).", writes: true }],
3653
+ options: [SITE_OPTION, AS_OPTION, YES_OPTION],
3654
+ async run({ io, options, positionals }) {
3655
+ const [action, source] = positionals;
3656
+ if (action !== "apply") usage(`Unknown schema action "${action ?? ""}".`, "Use schema apply <file|-> [--yes].");
3657
+ if (!source) usage("schema apply needs a file (or - for stdin).", "Example: agentcrm schema apply crm-schema.json");
3658
+ const { readTextValue: readTextValue2 } = await Promise.resolve().then(() => util);
3659
+ const text = readTextValue2(source === "-" ? "-" : `@${source}`, "schema");
3660
+ let raw;
3661
+ try {
3662
+ raw = JSON.parse(text);
3663
+ } catch (err) {
3664
+ usage(`The schema is not valid JSON: ${err.message}`);
3665
+ }
3666
+ const apply = options.yes === true;
3667
+ const { applySchema, parseSchemaSpec } = await import("./chunks/schemaApply-j5hbwPrH.mjs");
3668
+ const spec = parseSchemaSpec(raw);
3669
+ return withSession(
3670
+ io,
3671
+ options,
3672
+ { lock: apply },
3673
+ async ({ ctx }) => {
3674
+ if (apply) return applySchema(ctx, spec);
3675
+ const { dryRun: dryRun2 } = await Promise.resolve().then(() => dryRun$1);
3676
+ const { result } = await dryRun2(ctx, (copy) => applySchema(copy, spec));
3677
+ return result.changes.length ? { needsConfirmation: true, plan: result } : { ...result, upToDate: true };
3678
+ },
3679
+ (r) => {
3680
+ const report = "needsConfirmation" in r ? r.plan : r;
3681
+ const lines = report.changes.map((c) => ` ${c.action} ${c.kind} ${c.target}${c.changes?.length ? ` (${c.changes.join(", ")})` : ""}`);
3682
+ const extra = [
3683
+ ...report.conflicts.map((c) => ` conflict: ${c}`),
3684
+ ...report.extraFields.length ? [` not in the schema (kept): ${report.extraFields.join(", ")}`] : []
3685
+ ];
3686
+ 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");
3687
+ return [report.changes.length ? `Applied ${report.changes.length} change(s):` : "The data model already matches the schema.", ...lines, ...extra].join("\n");
3688
+ }
3689
+ );
3690
+ }
3691
+ };
3581
3692
  const MIME = {
3582
3693
  ".html": "text/html; charset=utf-8",
3583
3694
  ".js": "text/javascript; charset=utf-8",
@@ -3871,7 +3982,7 @@ const toolsCommand = {
3871
3982
  async run({ io, options, positionals }) {
3872
3983
  const [action = "list"] = positionals;
3873
3984
  if (action === "list") {
3874
- const { listAiTools, AI_TOOLS } = await import("./chunks/tools-iSH-yMpl.mjs");
3985
+ const { listAiTools, AI_TOOLS } = await import("./chunks/tools-BHmVIyP_.mjs");
3875
3986
  const builtin = new Set(AI_TOOLS.map((t) => t.name));
3876
3987
  const shape = () => listAiTools().map((t) => ({ name: t.name, description: t.description, mutating: t.mutating, source: builtin.has(t.name) ? "builtin" : "extension", inputSchema: t.inputSchema }));
3877
3988
  const human = (r) => table(r.tools, ["name", "mutating", "source", "description"]);
@@ -3891,7 +4002,7 @@ const toolsCommand = {
3891
4002
  }
3892
4003
  if (action === "call") {
3893
4004
  const name2 = positional(positionals, 1, "tool name", "tools call <name> --input <json>");
3894
- const { findTool, callTool, listAiTools } = await import("./chunks/tools-iSH-yMpl.mjs");
4005
+ const { findTool, callTool, listAiTools } = await import("./chunks/tools-BHmVIyP_.mjs");
3895
4006
  const input = jsonObjectOpt(options, "input") ?? {};
3896
4007
  const builtin = findTool(name2);
3897
4008
  return withSession(io, options, { lock: builtin ? builtin.mutating : true }, async ({ ctx }) => {
@@ -3913,8 +4024,8 @@ const mcpCommand = {
3913
4024
  const log = (msg) => process.stderr.write(`${msg}
3914
4025
  `);
3915
4026
  for (const w of session2.warnings) log(`[agentcrm mcp] note: ${w}`);
3916
- const { listAiTools } = await import("./chunks/tools-iSH-yMpl.mjs");
3917
- const { createMcpServer, serveStdio } = await import("./chunks/server-DC3t8r30.mjs");
4027
+ const { listAiTools } = await import("./chunks/tools-BHmVIyP_.mjs");
4028
+ const { createMcpServer, serveStdio } = await import("./chunks/server-BAQwVNtC.mjs");
3918
4029
  let lastRefresh = Date.now();
3919
4030
  const server = createMcpServer({
3920
4031
  readOnly,
@@ -4016,6 +4127,7 @@ const COMMANDS = [
4016
4127
  deployAdminCommand,
4017
4128
  recordsCommand,
4018
4129
  objectsCommand,
4130
+ schemaCommand,
4019
4131
  fieldsCommand,
4020
4132
  activitiesCommand,
4021
4133
  notesCommand,