@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
@@ -11,7 +11,7 @@ import { a as standardObjects, b as defaultPipeline, c as defaultViews } from ".
11
11
  import { assertCan } from "./context-CxFfqS4D.mjs";
12
12
  import { sha256Hex } from "./files-CjsPJSR6.mjs";
13
13
  import { asDoc, objectOf, usersOf, settingsDoc } from "./store-5gqRSKXg.mjs";
14
- import { DEFAULT_SETTINGS } from "./settings-BtvR_jdm.mjs";
14
+ import { DEFAULT_SETTINGS } from "./settings-Di8nzNEL.mjs";
15
15
  import "./pluginRegistry-DyHtlkcr.mjs";
16
16
  import "./runtimeConfig-CCOFg1RM.mjs";
17
17
  const PALETTES = [
@@ -362,6 +362,20 @@ const FIRST = ["Ada", "Alan", "Grace", "Linus", "Margaret", "Dennis", "Barbara",
362
362
  const LAST = ["Lovelace", "Turing", "Hopper", "Torvalds", "Hamilton", "Ritchie", "Liskov", "Thompson", "Allen", "Berners-Lee", "Perlman", "Cerf", "Lamarr", "McCarthy", "Johnson", "van Rossum", "Martin", "Bernard", "Dubois", "Durand", "Lefebvre", "Moreau", "Laurent", "Simon", "Michel", "Garcia", "Roux", "Fournier", "Girard", "Bonnet", "Smith", "Brown", "Wilson", "Taylor", "Clark", "Lewis", "Walker", "Young", "King", "Wright"];
363
363
  const TITLES = ["CEO", "CTO", "VP Sales", "Head of Marketing", "Procurement Manager", "Operations Director", "Product Manager", "Engineering Manager", "CFO", "Office Manager", "Account Executive", "Data Analyst"];
364
364
  const CITIES = ["Paris", "London", "New York", "Berlin", "Lyon", "Madrid", "Amsterdam", "Montréal", "San Francisco", "Lisbon", "Brussels", "Toronto"];
365
+ const CITY_COUNTRY = {
366
+ Paris: ["France", "+33 1"],
367
+ London: ["United Kingdom", "+44 20"],
368
+ "New York": ["United States", "+1 212"],
369
+ Berlin: ["Germany", "+49 30"],
370
+ Lyon: ["France", "+33 4"],
371
+ Madrid: ["Spain", "+34 91"],
372
+ Amsterdam: ["Netherlands", "+31 20"],
373
+ Montréal: ["Canada", "+1 514"],
374
+ "San Francisco": ["United States", "+1 415"],
375
+ Lisbon: ["Portugal", "+351 21"],
376
+ Brussels: ["Belgium", "+32 2"],
377
+ Toronto: ["Canada", "+1 416"]
378
+ };
365
379
  const DEAL_KINDS = ["Annual license", "Pilot", "Expansion", "Renewal", "Onboarding package", "Enterprise plan", "Support contract", "Consulting retainer"];
366
380
  const LIFECYCLE = ["subscriber", "lead", "lead", "mql", "sql", "opportunity", "customer", "customer", "evangelist"];
367
381
  const SOURCES = ["website", "referral", "event", "outbound", "partner", "ads", "social"];
@@ -405,19 +419,23 @@ async function seedDemoData(ctx, opts = {}) {
405
419
  updatedAt: stamp(Math.max(0, daysAgo - rand() * daysAgo)),
406
420
  ...opts.ownerId ? { createdBy: opts.ownerId } : {}
407
421
  });
408
- const companies = COMPANIES.map(
409
- ([name, domain, industry, employees], i) => rec(seededId(`demo:company:${i}`), "companies", {
422
+ const companies = COMPANIES.map(([name, domain, industry, employees], i) => {
423
+ const annualRevenue = Math.round(employees * (6e4 + rand() * 14e4) / 1e3) * 1e3;
424
+ const city = pick(CITIES);
425
+ rand();
426
+ const [country, dial] = CITY_COUNTRY[city];
427
+ return rec(seededId(`demo:company:${i}`), "companies", {
410
428
  name,
411
429
  domain: `https://${domain}`,
412
430
  industry,
413
431
  employees,
414
- annualRevenue: Math.round(employees * (6e4 + rand() * 14e4) / 1e3) * 1e3,
415
- address: { city: pick(CITIES), country: pick(["France", "United Kingdom", "United States", "Germany", "Canada", "Spain"]) },
432
+ annualRevenue,
433
+ address: { city, country },
416
434
  linkedin: `https://www.linkedin.com/company/${domain.split(".")[0]}`,
417
- phone: `+33 1 ${String(10 + i).padStart(2, "0")} ${String(Math.floor(rand() * 90) + 10)} ${String(Math.floor(rand() * 90) + 10)} ${String(Math.floor(rand() * 90) + 10)}`,
435
+ phone: `${dial} ${String(10 + i).padStart(2, "0")} ${String(Math.floor(rand() * 90) + 10)} ${String(Math.floor(rand() * 90) + 10)} ${String(Math.floor(rand() * 90) + 10)}`,
418
436
  ...rand() < 0.3 ? { tags: [pick(["vip", "partner", "strategic", "smb", "enterprise"])] } : {}
419
- }, i, 30 + rand() * 300)
420
- );
437
+ }, i, 30 + rand() * 300);
438
+ });
421
439
  const people = [];
422
440
  for (let i = 0; i < 80; i++) {
423
441
  const firstName = FIRST[i % FIRST.length];
@@ -97,7 +97,7 @@ function createMcpServer(opts) {
97
97
  try {
98
98
  const run = async () => {
99
99
  const ctx = await opts.backend.context();
100
- const { callTool: call } = await import("./tools-Bew8lxKx.mjs");
100
+ const { callTool: call } = await import("./tools-iSH-yMpl.mjs");
101
101
  return call(ctx, name, args);
102
102
  };
103
103
  const result = tool.mutating ? await opts.backend.withWrite(run) : await run();
@@ -112,15 +112,15 @@ function createMcpServer(opts) {
112
112
  async function readResource(id, uri) {
113
113
  const ctx = await opts.backend.context();
114
114
  let value;
115
- if (uri === "agentcrm://schema") value = (await import("./schema-DlHt2PxI.mjs")).describeSchema(ctx);
115
+ if (uri === "agentcrm://schema") value = (await import("./schema-C4lSslXv.mjs")).describeSchema(ctx);
116
116
  else if (uri === "agentcrm://pipelines") {
117
- const { listPipelines, forecast } = await import("./pipelines-DCdRBNBV.mjs");
117
+ const { listPipelines, forecast } = await import("./pipelines-BAKpPk1w.mjs");
118
118
  value = listPipelines(ctx).map((p) => ({ ...p, forecast: forecast(ctx, { pipelineId: p.id }) }));
119
119
  } else if (uri === "agentcrm://modules") {
120
- const [{ listModules }, { listExtensions }] = await Promise.all([import("./modules-CqdVQVJb.mjs"), import("./extensions-BP1k60md.mjs")]);
120
+ const [{ listModules }, { listExtensions }] = await Promise.all([import("./modules-clWdE-K2.mjs"), import("./extensions-Bp72uZ9a.mjs")]);
121
121
  value = { modules: listModules(ctx), extensions: listExtensions(ctx) };
122
122
  } else if (uri === "agentcrm://appearance") {
123
- const { getAppearance, listPresets } = await import("./appearance-BaShelzt.mjs");
123
+ const { getAppearance, listPresets } = await import("./appearance-BloHbiLA.mjs");
124
124
  const { logo, favicon, customCss, ...rest } = getAppearance(ctx);
125
125
  value = { appearance: { ...rest, hasLogo: !!logo, hasFavicon: !!favicon, customCss: customCss ?? null }, presets: listPresets(ctx).map((p) => ({ id: p.id, name: p.name, source: p.source, description: p.description, ...p.enabled !== void 0 ? { enabled: p.enabled } : {} })) };
126
126
  } else return fail(id, -32002, `Resource not found: ${uri}`, { uri });
@@ -20,8 +20,25 @@ function getSettings(ctx) {
20
20
  assertCan(ctx, "settings.read");
21
21
  return { ...DEFAULT_SETTINGS, ...settingsDoc(ctx.store) ?? {} };
22
22
  }
23
+ const SETTINGS_KEYS = ["name", "defaultCurrency", "locale", "timezone", "weekStartsOn", "staleDealDays", "ownerUserId", "maxUploadMb"];
23
24
  async function updateSettings(ctx, patch) {
24
25
  assertCan(ctx, "settings.write");
26
+ const unknown = Object.keys(patch).filter((k) => !SETTINGS_KEYS.includes(k) && k !== "createdAt" && k !== "updatedAt" && k !== "id");
27
+ if (unknown.length) {
28
+ throw new OpsError("validation", `Unknown setting${unknown.length > 1 ? "s" : ""} ${unknown.map((k) => `"${k}"`).join(", ")}. Settings: ${SETTINGS_KEYS.join(", ")}.`);
29
+ }
30
+ if (patch.name !== void 0 && (typeof patch.name !== "string" || !patch.name.trim())) {
31
+ throw new OpsError("validation", "The CRM name can't be empty.");
32
+ }
33
+ if (patch.weekStartsOn !== void 0 && patch.weekStartsOn !== 0 && patch.weekStartsOn !== 1) {
34
+ throw new OpsError("validation", "Week start must be 0 (Sunday) or 1 (Monday).");
35
+ }
36
+ if (patch.locale !== void 0 && !validLocale(patch.locale)) {
37
+ throw new OpsError("validation", `"${String(patch.locale)}" is not a valid locale (BCP-47, e.g. en-US, fr-FR).`);
38
+ }
39
+ if (patch.timezone !== void 0 && patch.timezone !== null && !validTimeZone(patch.timezone)) {
40
+ throw new OpsError("validation", `"${String(patch.timezone)}" is not a valid time zone (IANA, e.g. Europe/Paris).`);
41
+ }
25
42
  if (patch.defaultCurrency !== void 0 && !/^[A-Z]{3}$/.test(patch.defaultCurrency.toUpperCase())) {
26
43
  throw new OpsError("validation", `"${patch.defaultCurrency}" is not an ISO 4217 currency code.`);
27
44
  }
@@ -43,6 +60,23 @@ async function updateSettings(ctx, patch) {
43
60
  await ctx.store.write([{ op: "set", collection: "settings", doc: asDoc(next) }], { message: "Update settings" });
44
61
  return getSettings(ctx);
45
62
  }
63
+ function validLocale(value) {
64
+ if (typeof value !== "string" || !value) return false;
65
+ try {
66
+ return Intl.getCanonicalLocales(value).length === 1;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+ function validTimeZone(value) {
72
+ if (typeof value !== "string" || !value) return false;
73
+ try {
74
+ new Intl.DateTimeFormat("en-US", { timeZone: value });
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
46
80
  function assertNoSecrets(value, path = "") {
47
81
  if (!value || typeof value !== "object") return;
48
82
  for (const [k, v] of Object.entries(value)) {
@@ -68,6 +102,7 @@ async function setConfigDoc(ctx, id, value) {
68
102
  }
69
103
  export {
70
104
  DEFAULT_SETTINGS,
105
+ SETTINGS_KEYS,
71
106
  assertNoSecrets,
72
107
  getConfigDoc,
73
108
  getSettings,
@@ -6,7 +6,7 @@ const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
7
  import { A as ACTIVITY_TYPES } from "../agentcrm.mjs";
8
8
  import { assertCan } from "./context-CxFfqS4D.mjs";
9
- import { forecast } from "./pipelines-DCdRBNBV.mjs";
9
+ import { forecast } from "./pipelines-BAKpPk1w.mjs";
10
10
  import { activitiesOf, pipelinesOf, usersOf, objectsOf, recordsOf } from "./store-5gqRSKXg.mjs";
11
11
  import "node:util";
12
12
  import "node:fs";
@@ -20,7 +20,7 @@ import "./pluginRegistry-DyHtlkcr.mjs";
20
20
  import "./fields-B0bitgmr.mjs";
21
21
  import "./ids-CvnmrMJX.mjs";
22
22
  import "./standardObjects-CheqfDqa.mjs";
23
- import "./records-DoqiT9xR.mjs";
23
+ import "./records-DcHFelq5.mjs";
24
24
  import "./query-C5nJB_3C.mjs";
25
25
  import "./relations-B9TynBmA.mjs";
26
26
  function getStats(ctx) {
@@ -6,24 +6,24 @@ const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
7
  import { recordLabel, normalizeEmail, formatFieldValue } from "./fields-B0bitgmr.mjs";
8
8
  import { r as resolveField } from "./query-C5nJB_3C.mjs";
9
- import { F as FILTER_OPS, A as ACTIVITY_TYPES, e as FIELD_TYPES } from "../agentcrm.mjs";
9
+ import { F as FILTER_OPS, A as ACTIVITY_TYPES, a as FIELD_TYPES } from "../agentcrm.mjs";
10
10
  import { OpsError } from "./context-CxFfqS4D.mjs";
11
11
  import { listActivities, addNote, addActivity, createTask, completeTask, listTasks } from "./activities-Cj_SIOay.mjs";
12
12
  import { mergeRecords } from "./dedupe-BepmsaHv.mjs";
13
- import { importCsv } from "./importCsv-DmE_sBch.mjs";
14
- import { moveStage, forecast, listPipelines } from "./pipelines-DCdRBNBV.mjs";
15
- import { getRecord, searchRecords, listRecords, getRecordDetails, createRecord, updateRecord, deleteRecord, queryContext } from "./records-DoqiT9xR.mjs";
16
- import { runReport, REPORTS } from "./reports-BB59N6ke.mjs";
17
- import { describeSchema, createField } from "./schema-DlHt2PxI.mjs";
13
+ import { importCsv } from "./importCsv-LVmMTaWU.mjs";
14
+ import { moveStage, forecast, listPipelines } from "./pipelines-BAKpPk1w.mjs";
15
+ import { getRecord, searchRecords, listRecords, getRecordDetails, createRecord, updateRecord, deleteRecord, queryContext } from "./records-DcHFelq5.mjs";
16
+ import { runReport, REPORTS } from "./reports-BACHGccj.mjs";
17
+ import { describeSchema, createField } from "./schema-C4lSslXv.mjs";
18
18
  import { objectOf, recordOf, userOf, activitiesOf, fileOf } from "./store-5gqRSKXg.mjs";
19
- import { ACTION_TYPES, TRIGGER_TYPES, listAutomationRuns, AUTOMATION_TEMPLATES, listAutomations, createAutomationFromTemplate, createAutomation, setAutomationEnabled, testAutomation, runScheduled } from "./automations-BWrvLqP6.mjs";
20
- import { listForms, ingestInbox } from "./forms-BKU5F9WF.mjs";
21
- import { B as BUTTON_STYLES, C as CONTRASTS, d as COLOR_MODES, S as SIDEBAR_STYLES, e as DENSITIES, R as RADII, N as NEUTRALS, F as FONTS } from "./schema-7-4-qtSS.mjs";
22
- import { listPresets, getAppearance, setAppearance, applyPreset } from "./appearance-BaShelzt.mjs";
23
- import { listExtensions, setExtensionEnabled } from "./extensions-BP1k60md.mjs";
24
- import { listModules, setModuleEnabled } from "./modules-CqdVQVJb.mjs";
19
+ import { ACTION_TYPES, TRIGGER_TYPES, listAutomationRuns, AUTOMATION_TEMPLATES, listAutomations, createAutomationFromTemplate, createAutomation, setAutomationEnabled, testAutomation, runScheduled } from "./automations-DwWruxmu.mjs";
20
+ import { listForms, ingestInbox } from "./forms-BfuWaQjA.mjs";
21
+ import { B as BUTTON_STYLES, C as CONTRASTS, d as COLOR_MODES, S as SIDEBAR_STYLES, e as DENSITIES, R as RADII, N as NEUTRALS, F as FONTS } from "./schema-DnLj8dUO.mjs";
22
+ import { listPresets, getAppearance, setAppearance, applyPreset } from "./appearance-BloHbiLA.mjs";
23
+ import { listExtensions, setExtensionEnabled } from "./extensions-Bp72uZ9a.mjs";
24
+ import { listModules, setModuleEnabled } from "./modules-clWdE-K2.mjs";
25
25
  import { inlineFileIds, formatBytes, fileKind } from "./files-CjsPJSR6.mjs";
26
- import { listFiles, fileUsage } from "./files-q8Xu5bft.mjs";
26
+ import { listFiles, fileUsage } from "./files-D1daDgY0.mjs";
27
27
  import { l as labelOf } from "./relations-B9TynBmA.mjs";
28
28
  import "node:util";
29
29
  import "node:fs";
@@ -35,17 +35,17 @@ import "node:child_process";
35
35
  import "node:http";
36
36
  import "./pluginRegistry-DyHtlkcr.mjs";
37
37
  import "./ids-CvnmrMJX.mjs";
38
- import "./papaparse-DjBYnQM2.mjs";
38
+ import "./papaparse-Df7i1dHr.mjs";
39
39
  import "./_commonjsHelpers-ClciMIcB.mjs";
40
40
  import "stream";
41
41
  import "./utils-DmI8FtXP.mjs";
42
42
  import "./standardObjects-CheqfDqa.mjs";
43
- import "./settings-BtvR_jdm.mjs";
43
+ import "./settings-Di8nzNEL.mjs";
44
44
  import "./runtimeConfig-CCOFg1RM.mjs";
45
45
  import "./extensionActions-D2HtpQz4.mjs";
46
46
  import "./themes-DPZFv3yc.mjs";
47
- import "./api-B6OqNVWp.mjs";
48
- import "./package-DWbn3t2R.mjs";
47
+ import "./api-Cz0Ubj-B.mjs";
48
+ import "./package-B7mI6HGi.mjs";
49
49
  import "./index-w03ReRs4.mjs";
50
50
  import "events";
51
51
  import "buffer";
@@ -824,18 +824,58 @@ function subscribeAiTools(listener) {
824
824
  function findTool(name) {
825
825
  return AI_TOOLS.find((t) => t.name === name) ?? extraTools.get(name);
826
826
  }
827
+ function jsonTypeOf(value) {
828
+ if (value === null) return "null";
829
+ if (Array.isArray(value)) return "array";
830
+ return typeof value;
831
+ }
832
+ function checkToolInput(schema, value, path) {
833
+ const types = schema.type === void 0 ? [] : Array.isArray(schema.type) ? schema.type : [schema.type];
834
+ let v = value;
835
+ if (types.length && v !== null && v !== void 0) {
836
+ const actual = jsonTypeOf(v);
837
+ const accepts = (t) => t === actual || t === "integer" && actual === "number" && Number.isInteger(v);
838
+ if (!types.some(accepts)) {
839
+ if ((types.includes("number") || types.includes("integer")) && typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) v = Number(v);
840
+ else if (types.includes("boolean") && (v === "true" || v === "false")) v = v === "true";
841
+ else throw new OpsError("validation", `${path} must be ${types.map((t) => t === "array" || t === "object" || t === "integer" ? `an ${t}` : `a ${t}`).join(" or ")}, got ${actual === "array" || actual === "object" ? `an ${actual}` : `${actual} ${JSON.stringify(v)}`.slice(0, 80)}.`);
842
+ }
843
+ }
844
+ if (v === null || v === void 0) return v;
845
+ if (schema.enum && !schema.enum.includes(v)) {
846
+ throw new OpsError("validation", `${path} must be one of ${schema.enum.map((e) => JSON.stringify(e)).join(", ")}, got ${JSON.stringify(v).slice(0, 80)}.`);
847
+ }
848
+ if (Array.isArray(v) && schema.items) {
849
+ const items = schema.items;
850
+ return v.map((item, i) => checkToolInput(items, item, `${path}[${i}]`));
851
+ }
852
+ if (jsonTypeOf(v) === "object" && (schema.properties || schema.required)) {
853
+ const obj = { ...v };
854
+ for (const key of schema.required ?? []) {
855
+ if (obj[key] === void 0 || obj[key] === null) throw new OpsError("validation", `${path}: missing required "${key}".`);
856
+ }
857
+ for (const [key, sub] of Object.entries(schema.properties ?? {})) {
858
+ if (obj[key] !== void 0) obj[key] = checkToolInput(sub, obj[key], `${path}.${key}`);
859
+ }
860
+ return obj;
861
+ }
862
+ return v;
863
+ }
827
864
  async function callTool(ctx, name, input) {
828
865
  const tool = findTool(name);
829
866
  if (!tool) throw new OpsError("notFound", `Unknown tool "${name}". Tools: ${listAiTools().map((t) => t.name).join(", ")}.`);
830
- const args = input && typeof input === "object" ? input : {};
867
+ if (input !== void 0 && input !== null && jsonTypeOf(input) !== "object") throw new OpsError("validation", `${name}: the input must be a JSON object, got ${jsonTypeOf(input) === "array" ? "an array" : `a ${jsonTypeOf(input)}`}.`);
868
+ const raw = input ?? {};
831
869
  for (const key of tool.inputSchema.required ?? []) {
832
- if (args[key] === void 0 || args[key] === null) throw new OpsError("validation", `${name}: missing required "${key}".`);
870
+ if (raw[key] === void 0 || raw[key] === null) throw new OpsError("validation", `${name}: missing required "${key}".`);
833
871
  }
872
+ const args = checkToolInput({ ...tool.inputSchema, required: [] }, raw, name);
834
873
  return tool.handler(ctx, args);
835
874
  }
836
875
  export {
837
876
  AI_TOOLS,
838
877
  callTool,
878
+ checkToolInput,
839
879
  findTool,
840
880
  listAiTools,
841
881
  registerAiTool,
@@ -7,7 +7,7 @@ const __dirname = __acrmDirname(__filename);
7
7
  import { n as newId } from "./ids-CvnmrMJX.mjs";
8
8
  import { F as FILTER_OPS } from "../agentcrm.mjs";
9
9
  import { OpsError, assertCan, can } from "./context-CxFfqS4D.mjs";
10
- import { listRecords } from "./records-DoqiT9xR.mjs";
10
+ import { listRecords } from "./records-DcHFelq5.mjs";
11
11
  import { requireObject, asDoc, viewsOf, thaw, pipelinesOf } from "./store-5gqRSKXg.mjs";
12
12
  import "node:util";
13
13
  import "node:fs";
package/dist/version.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleox-agent-crm/agentcrm",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "minSkillsVersion": "0.1.0",
5
5
  "dataSchema": 1,
6
6
  "repository": "github:fleox/agentcrm"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleox-agent-crm/agentcrm",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "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.",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcrm",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Skills to install AgentCRM (the static CRM your AI agent can run), manage its sites, run and customise the CRM (modules, appearance, extensions), drive sales workflows and build extension modules and themes.",
5
5
  "author": {
6
6
  "name": "fleox"
@@ -4,7 +4,7 @@ description: Run an AgentCRM like its admin does - find, list, create, update, d
4
4
  license: PolyForm-Noncommercial-1.0.0
5
5
  compatibility: Requires Node.js 20+.
6
6
  metadata:
7
- version: "0.3.0"
7
+ version: "0.3.2"
8
8
  repository: "https://github.com/fleox/agentcrm"
9
9
  allowed-tools: Bash Read Write
10
10
  ---
@@ -15,7 +15,7 @@ The CLI runs the admin's own engine headless: the same validation, roles and aut
15
15
 
16
16
  **Language:** talk to the user in their language; keep commands and files in English.
17
17
 
18
- Every command is `npx -y @fleox-agent-crm/agentcrm@0.3.0 <command> --json`; always pass `--site <alias>` (see `agentcrm-manage` to pick one). Read `error.hint` when `ok` is false. `npx -y @fleox-agent-crm/agentcrm@0.3.0 help records --json` lists any command's actions and options.
18
+ Every command is `npx -y @fleox-agent-crm/agentcrm@0.3.2 <command> --json`; always pass `--site <alias>` (see `agentcrm-manage` to pick one). Read `error.hint` when `ok` is false. `npx -y @fleox-agent-crm/agentcrm@0.3.2 help records --json` lists any command's actions and options.
19
19
 
20
20
  ## Rules
21
21
 
@@ -27,13 +27,13 @@ Every command is `npx -y @fleox-agent-crm/agentcrm@0.3.0 <command> --json`; alwa
27
27
  ## Records
28
28
 
29
29
  ```bash
30
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records list --site acme --object deals --filter stage:in:proposal,negotiation --filter amount:gte:10000 --sort amount:desc --limit 20 --json
31
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records get <id> --site acme --json
32
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records search "globex" --site acme --json
33
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records create --site acme --object people --set firstName=Grace --set lastName=Hopper --set email=grace@globex.com --set company=<companyId> --json
34
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records update <id> --site acme --set jobTitle=CTO --json
35
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records delete <id> --site acme --json
36
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records restore <id> --site acme --json
30
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records list --site acme --object deals --filter stage:in:proposal,negotiation --filter amount:gte:10000 --sort amount:desc --limit 20 --json
31
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records get <id> --site acme --json
32
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records search "globex" --site acme --json
33
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records create --site acme --object people --set firstName=Grace --set lastName=Hopper --set email=grace@globex.com --set company=<companyId> --json
34
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records update <id> --site acme --set jobTitle=CTO --json
35
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records delete <id> --site acme --json
36
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records restore <id> --site acme --json
37
37
  ```
38
38
 
39
39
  - Filters: `--filter field:op:value`, repeatable. Ops: eq, neq, contains, notContains, empty, notEmpty, gt, gte, lt, lte, in, notIn, before, after, withinDays. Dates: `YYYY-MM-DD`, `today`, `+30d`. Also `id`, `ownerId`, `createdAt`, `updatedAt`.
@@ -46,13 +46,13 @@ More examples per object: references/records.md.
46
46
  ## Timeline and tasks
47
47
 
48
48
  ```bash
49
- npx -y @fleox-agent-crm/agentcrm@0.3.0 notes add <recordId> --site acme --body "Met at the expo; wants a demo in June." --json
50
- npx -y @fleox-agent-crm/agentcrm@0.3.0 activities add --site acme --type call --title "Discovery call" --record <personId> --record <dealId> --meta '{"durationMin":25}' --json
51
- npx -y @fleox-agent-crm/agentcrm@0.3.0 activities list --site acme --record <companyId> --related --json
52
- npx -y @fleox-agent-crm/agentcrm@0.3.0 tasks add --site acme --title "Send the proposal" --due 2026-11-10 --assignee me --record <dealId> --json
53
- npx -y @fleox-agent-crm/agentcrm@0.3.0 tasks list --site acme --assignee me --overdue --json
54
- npx -y @fleox-agent-crm/agentcrm@0.3.0 tasks done <taskId> --site acme --json
55
- npx -y @fleox-agent-crm/agentcrm@0.3.0 tasks reassign <taskId> --site acme --assignee sam@acme.com --json
49
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 notes add <recordId> --site acme --body "Met at the expo; wants a demo in June." --json
50
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 activities add --site acme --type call --title "Discovery call" --record <personId> --record <dealId> --meta '{"durationMin":25}' --json
51
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 activities list --site acme --record <companyId> --related --json
52
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 tasks add --site acme --title "Send the proposal" --due 2026-11-10 --assignee me --record <dealId> --json
53
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 tasks list --site acme --assignee me --overdue --json
54
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 tasks done <taskId> --site acme --json
55
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 tasks reassign <taskId> --site acme --assignee sam@acme.com --json
56
56
  ```
57
57
 
58
58
  Activity types: note, task, call, email, meeting. `--body @notes.md` reads a file.
@@ -62,18 +62,18 @@ Activity types: note, task, call, email, meeting. `--body @notes.md` reads a fil
62
62
  People have a **photo** (`avatar` field) and companies a **logo** (`logo`), shown next to the name everywhere; custom objects get one by adding an `image` field (the first becomes the object's image). Files (any type: images, PDFs, Office documents…) attach to a record or to an activity (note, task, call, email, meeting).
63
63
 
64
64
  ```bash
65
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records image <personId> ./ada.jpg --site acme --json
66
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records image <companyId> ./logo.svg --site acme --json
67
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records image <personId> --remove --site acme --json
68
- npx -y @fleox-agent-crm/agentcrm@0.3.0 notes add <recordId> --body "Signed contract attached." --attach ./contract.pdf --site acme --json
69
- npx -y @fleox-agent-crm/agentcrm@0.3.0 tasks add --title "Review the quote" --record <dealId> --attach ./quote.xlsx --attach ./scope.pdf --site acme --json
70
- npx -y @fleox-agent-crm/agentcrm@0.3.0 activities add --type meeting --title "Workshop" --record <companyId> --attach ./whiteboard.png --site acme --json
71
- npx -y @fleox-agent-crm/agentcrm@0.3.0 files upload ./brochure.pdf ./photo.png --record <companyId> --site acme --json
72
- npx -y @fleox-agent-crm/agentcrm@0.3.0 files upload ./minutes.docx --activity <noteId> --site acme --json
73
- npx -y @fleox-agent-crm/agentcrm@0.3.0 files list --record <companyId> --related --site acme --json
74
- npx -y @fleox-agent-crm/agentcrm@0.3.0 files get <fileId> --out ./contract.pdf --site acme --json
75
- npx -y @fleox-agent-crm/agentcrm@0.3.0 files delete <fileId> --site acme --json
76
- npx -y @fleox-agent-crm/agentcrm@0.3.0 files usage --site acme --json
65
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records image <personId> ./ada.jpg --site acme --json
66
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records image <companyId> ./logo.svg --site acme --json
67
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records image <personId> --remove --site acme --json
68
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 notes add <recordId> --body "Signed contract attached." --attach ./contract.pdf --site acme --json
69
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 tasks add --title "Review the quote" --record <dealId> --attach ./quote.xlsx --attach ./scope.pdf --site acme --json
70
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 activities add --type meeting --title "Workshop" --record <companyId> --attach ./whiteboard.png --site acme --json
71
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 files upload ./brochure.pdf ./photo.png --record <companyId> --site acme --json
72
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 files upload ./minutes.docx --activity <noteId> --site acme --json
73
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 files list --record <companyId> --related --site acme --json
74
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 files get <fileId> --out ./contract.pdf --site acme --json
75
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 files delete <fileId> --site acme --json
76
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 files usage --site acme --json
77
77
  ```
78
78
 
79
79
  - Files live next to the data and are **encrypted with the same passphrase** on the journal backend; IndexedDB in local mode; the data repo (`files/`) for git-json (warn: history keeps them); Firebase Storage for Firebase (Storage must be enabled, see docs/files.md).
@@ -86,9 +86,9 @@ npx -y @fleox-agent-crm/agentcrm@0.3.0 files usage --site acme --json
86
86
  ## Pipelines
87
87
 
88
88
  ```bash
89
- npx -y @fleox-agent-crm/agentcrm@0.3.0 pipelines list --site acme --json
90
- npx -y @fleox-agent-crm/agentcrm@0.3.0 pipelines move <dealId> Negotiation --site acme --json
91
- npx -y @fleox-agent-crm/agentcrm@0.3.0 pipelines move <dealId> Lost --lost-reason "Budget" --site acme --json
89
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 pipelines list --site acme --json
90
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 pipelines move <dealId> Negotiation --site acme --json
91
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 pipelines move <dealId> Lost --lost-reason "Budget" --site acme --json
92
92
  ```
93
93
 
94
94
  Moving sets the deal's probability to the stage's. Adding / renaming stages is done in the admin (Settings → Data model).
@@ -102,10 +102,10 @@ See references/schema.md for objects, fields (types, options, relations), views,
102
102
  Built-in modules: dashboard, deals (deals object + pipelines), reports, forms, tasks, automations, integrations (email and calendar), assistant (in-admin AI); records, search, settings, setup and customize are required. Installed extension modules are listed with `source: "extension"`. Disabling hides pages, navigation, widgets and owned objects for every user; it never deletes data, and enabling again brings everything back.
103
103
 
104
104
  ```bash
105
- npx -y @fleox-agent-crm/agentcrm@0.3.0 modules list --site acme --json
106
- npx -y @fleox-agent-crm/agentcrm@0.3.0 modules disable forms --site acme --json
107
- npx -y @fleox-agent-crm/agentcrm@0.3.0 modules disable forms --site acme --yes --json
108
- npx -y @fleox-agent-crm/agentcrm@0.3.0 modules enable deals --site acme --cascade --yes --json
105
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 modules list --site acme --json
106
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 modules disable forms --site acme --json
107
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 modules disable forms --site acme --yes --json
108
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 modules enable deals --site acme --cascade --yes --json
109
109
  ```
110
110
 
111
111
  - When the user says "we only manage contacts" or "hide what we don't use": list the modules, propose which to turn off (with what each hides), and apply only after they agree.
@@ -117,14 +117,14 @@ npx -y @fleox-agent-crm/agentcrm@0.3.0 modules enable deals --site acme --cascad
117
117
  One accent color drives the whole palette (a 50 to 950 scale is generated). Applies live to every user.
118
118
 
119
119
  ```bash
120
- npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance get --site acme --json
121
- npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance presets --site acme --json
122
- npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance set --site acme --accent "#0f766e" --neutral stone --radius lg --json
123
- npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance preset ocean --site acme --yes --json
124
- npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance logo ./logo.svg --site acme --yes --json
125
- npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance favicon ./favicon.png --site acme --yes --json
126
- npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance css ./brand.css --site acme --yes --json
127
- npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance reset --site acme --json
120
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 appearance get --site acme --json
121
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 appearance presets --site acme --json
122
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 appearance set --site acme --accent "#0f766e" --neutral stone --radius lg --json
123
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 appearance preset ocean --site acme --yes --json
124
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 appearance logo ./logo.svg --site acme --yes --json
125
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 appearance favicon ./favicon.png --site acme --yes --json
126
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 appearance css ./brand.css --site acme --yes --json
127
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 appearance reset --site acme --json
128
128
  ```
129
129
 
130
130
  - **Brand from a hex:** `appearance set --accent <hex>`, then pick `--neutral` by temperature (blue / teal brands: slate; warm brands: stone; otherwise zinc), `--buttons accent` for a colorful brand. **From a logo:** read the dominant non-gray color of the SVG (its `fill`) or the image, propose it as the accent, and upload the logo itself with `appearance logo`.
@@ -134,9 +134,9 @@ npx -y @fleox-agent-crm/agentcrm@0.3.0 appearance reset --site acme --json
134
134
  ## Extensions (installed modules and themes)
135
135
 
136
136
  ```bash
137
- npx -y @fleox-agent-crm/agentcrm@0.3.0 ext list --site acme --json
138
- npx -y @fleox-agent-crm/agentcrm@0.3.0 ext disable sales-goals --site acme --yes --json
139
- npx -y @fleox-agent-crm/agentcrm@0.3.0 ext enable sales-goals --site acme --yes --json
137
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 ext list --site acme --json
138
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 ext disable sales-goals --site acme --yes --json
139
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 ext enable sales-goals --site acme --yes --json
140
140
  ```
141
141
 
142
142
  Installing, building or debugging extensions: the `agentcrm-extensions` skill. Installing runs third-party code with admin rights: always show the plan and ask. If the admin misbehaves after an extension change, `ext disable <id> --yes`, or open the admin with `?safe=1`; CLI commands accept `--safe` to skip extensions.
@@ -144,11 +144,11 @@ Installing, building or debugging extensions: the `agentcrm-extensions` skill. I
144
144
  ## Import and export
145
145
 
146
146
  ```bash
147
- npx -y @fleox-agent-crm/agentcrm@0.3.0 import csv contacts.csv --site acme --object people --json
148
- npx -y @fleox-agent-crm/agentcrm@0.3.0 import csv contacts.csv --site acme --object people --yes --json
149
- npx -y @fleox-agent-crm/agentcrm@0.3.0 export csv --site acme --object companies --out companies.csv --json
150
- npx -y @fleox-agent-crm/agentcrm@0.3.0 export json --site acme --out backup.json --json
151
- npx -y @fleox-agent-crm/agentcrm@0.3.0 export zip --site acme --out backup.zip --json
147
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 import csv contacts.csv --site acme --object people --json
148
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 import csv contacts.csv --site acme --object people --yes --json
149
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 export csv --site acme --object companies --out companies.csv --json
150
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 export json --site acme --out backup.json --json
151
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 export zip --site acme --out backup.zip --json
152
152
  ```
153
153
 
154
154
  The import is a DRY RUN until `--yes`: read the report (mapping, per-row action, errors, new select options, companies to create) with the user before applying. Mapping, dedupe and coercion rules: references/import.md.
@@ -156,25 +156,25 @@ The import is a DRY RUN until `--yes`: read the report (mapping, per-row action,
156
156
  ## Reports and stats
157
157
 
158
158
  ```bash
159
- npx -y @fleox-agent-crm/agentcrm@0.3.0 report pipeline --site acme --json
160
- npx -y @fleox-agent-crm/agentcrm@0.3.0 report forecast --site acme --json
161
- npx -y @fleox-agent-crm/agentcrm@0.3.0 report won-lost --site acme --months 6 --json
162
- npx -y @fleox-agent-crm/agentcrm@0.3.0 report activity --site acme --days 30 --json
163
- npx -y @fleox-agent-crm/agentcrm@0.3.0 report lead-sources --site acme --json
164
- npx -y @fleox-agent-crm/agentcrm@0.3.0 report stale --site acme --days 21 --json
165
- npx -y @fleox-agent-crm/agentcrm@0.3.0 report conversion --site acme --json
166
- npx -y @fleox-agent-crm/agentcrm@0.3.0 stats --site acme --json
159
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 report pipeline --site acme --json
160
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 report forecast --site acme --json
161
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 report won-lost --site acme --months 6 --json
162
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 report activity --site acme --days 30 --json
163
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 report lead-sources --site acme --json
164
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 report stale --site acme --days 21 --json
165
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 report conversion --site acme --json
166
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 stats --site acme --json
167
167
  ```
168
168
 
169
169
  ## Automations and lead forms
170
170
 
171
171
  ```bash
172
- npx -y @fleox-agent-crm/agentcrm@0.3.0 automations list --site acme --json
173
- npx -y @fleox-agent-crm/agentcrm@0.3.0 automations disable <id> --site acme --json
174
- npx -y @fleox-agent-crm/agentcrm@0.3.0 automations run --site acme --json
175
- npx -y @fleox-agent-crm/agentcrm@0.3.0 forms list --site acme --json
176
- npx -y @fleox-agent-crm/agentcrm@0.3.0 forms publish <formId> --site acme --json
177
- npx -y @fleox-agent-crm/agentcrm@0.3.0 forms ingest --site acme --json
172
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 automations list --site acme --json
173
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 automations disable <id> --site acme --json
174
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 automations run --site acme --json
175
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 forms list --site acme --json
176
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 forms publish <formId> --site acme --json
177
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 forms ingest --site acme --json
178
178
  ```
179
179
 
180
180
  Event automations (record created, field changed, stage changed) fire on every write, from the CLI too. Building automations and forms is done in the admin; the CLI enables / disables, runs the scheduled ones and ingests form submissions. For a cron, see the `agentcrm-manage` skill.
@@ -184,8 +184,8 @@ Event automations (record created, field changed, stage changed) fire on every w
184
184
  The same tools the MCP server and the in-admin assistant expose, including the tools of enabled extension modules (`source: "extension"`) the customisation tools (`list_modules`, `set_module_enabled`, `get_appearance`, `set_appearance`, `apply_appearance_preset`, `list_extensions`, `set_extension_enabled`) and the file tools (`list_files`, `describe_attachments`):
185
185
 
186
186
  ```bash
187
- npx -y @fleox-agent-crm/agentcrm@0.3.0 tools list --site acme --json
188
- npx -y @fleox-agent-crm/agentcrm@0.3.0 tools call pipeline_summary --site acme --input '{}' --json
187
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 tools list --site acme --json
188
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 tools call pipeline_summary --site acme --input '{}' --json
189
189
  ```
190
190
 
191
191
  Useful when a tool returns exactly what you need in one call (`get_record` with relations and latest activities, `pipeline_summary`).
@@ -18,7 +18,7 @@
18
18
  Changing a visual value marks the preset `custom`. Other actions:
19
19
 
20
20
  - `appearance preset <id>`: default, ocean, forest, sunset, graphite, rose, high-contrast, or a theme extension id. Keeps logo, favicon, custom CSS and the default mode.
21
- - `appearance logo <file>` / `appearance favicon <file>`: PNG, JPEG, GIF, WebP, SVG or ICO, at most 200 KB; `--remove` deletes it.
21
+ - `appearance logo <file>` / `appearance favicon <file>`: PNG, JPEG, GIF, WebP, SVG or ICO, at most 200 KB (SVGs with scripts or event handlers are refused); `--remove` deletes it.
22
22
  - `appearance css <file|->`: plain CSS (no `<style>` / `<script>`), at most 50 KB, loaded last; `--remove` deletes it. Style through the CSS variables (`--acc-50` to `--acc-950`, `--srf-50` to `--srf-950`, `--primary`, `--radius-scale`, `--font-sans`, `--row-h`…) as `rgb(var(--acc-600))`.
23
23
  - `appearance reset`: back to defaults, keeping logo and favicon (`--branding` removes them too).
24
24
  - `appearance get --full` includes the logo / favicon data URLs and the custom CSS.
@@ -12,17 +12,17 @@ Always confirm with `objects get <object>`: admins add fields and options.
12
12
 
13
13
  ```bash
14
14
  # A company and its contact
15
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records create --site acme --object companies --set name="Globex" --set domain=globex.com --set industry=software --json
16
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records create --site acme --object people --fields @grace.json --json
15
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records create --site acme --object companies --set name="Globex" --set domain=globex.com --set industry=software --json
16
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records create --site acme --object people --fields @grace.json --json
17
17
 
18
18
  # A deal linked to both
19
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records create --site acme --object deals --fields '{"name":"Globex pilot","amount":12000,"closeDate":"2026-12-15","company":"<companyId>","contacts":["<personId>"]}' --json
19
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records create --site acme --object deals --fields '{"name":"Globex pilot","amount":12000,"closeDate":"2026-12-15","company":"<companyId>","contacts":["<personId>"]}' --json
20
20
 
21
21
  # Leads created this week, without an owner
22
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records list --site acme --object people --filter lifecycleStage:eq:lead --filter createdAt:withinDays:-7 --filter ownerId:empty --json
22
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records list --site acme --object people --filter lifecycleStage:eq:lead --filter createdAt:withinDays:-7 --filter ownerId:empty --json
23
23
 
24
24
  # Assign an owner
25
- npx -y @fleox-agent-crm/agentcrm@0.3.0 records update <id> --site acme --owner <userId> --json
25
+ npx -y @fleox-agent-crm/agentcrm@0.3.2 records update <id> --site acme --owner <userId> --json
26
26
  ```
27
27
 
28
28
  `records get` returns `relations` (records this one points to) and `related` (records pointing to it, e.g. a company's people and deals) plus `activityCount`.