@fleox-agent-crm/agentcrm 0.3.0 → 0.3.1

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 (65) hide show
  1. package/README.md +16 -16
  2. package/changelog.json +19 -0
  3. package/dist/admin/admin-files.json +8 -8
  4. package/dist/admin/assets/{chartsImpl-CE2akJat.js → chartsImpl-B45AAqnr.js} +30 -30
  5. package/dist/admin/assets/{firebase-BHSUggRo.js → firebase-C1x_l9P9.js} +2 -2
  6. package/dist/admin/assets/{gitJson-BulGh2KV.js → gitJson-CAUSxl8d.js} +1 -1
  7. package/dist/admin/assets/{index-DidVVK7L.js → index-ChFauO8y.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--rznIkmQ.js} +1 -1
  10. package/dist/admin/assets/{sdk-CnrLnlRo.js → sdk-DgzLACmL.js} +1 -1
  11. package/dist/admin/index.html +2 -2
  12. package/dist/agent/agentcrm.mjs +267 -142
  13. package/dist/agent/chunks/{api-B6OqNVWp.mjs → api-Cz0Ubj-B.mjs} +4 -3
  14. package/dist/agent/chunks/{appearance-BaShelzt.mjs → appearance-BloHbiLA.mjs} +1 -1
  15. package/dist/agent/chunks/{automations-BWrvLqP6.mjs → automations-DwWruxmu.mjs} +3 -3
  16. package/dist/agent/chunks/{backup-ClSmxmFZ.mjs → backup-W3CjgkAJ.mjs} +16 -3
  17. package/dist/agent/chunks/{compact-CsFPaAAq.mjs → compact-BGHI92tm.mjs} +2 -2
  18. package/dist/agent/chunks/{exportCsv-B5hRvkNm.mjs → exportCsv-C6e6XEmB.mjs} +38 -3
  19. package/dist/agent/chunks/{extCheck--7lmVvl7.mjs → extCheck-GgHG1dYB.mjs} +16 -9
  20. package/dist/agent/chunks/{extPackage-BGl5Oqs8.mjs → extPackage-DuZ6GZhp.mjs} +2 -2
  21. package/dist/agent/chunks/{extScaffold-DqxI7R1e.mjs → extScaffold-Aqyjf1nr.mjs} +3 -3
  22. package/dist/agent/chunks/{extensions-BP1k60md.mjs → extensions-Bp72uZ9a.mjs} +19 -6
  23. package/dist/agent/chunks/{featureRegistry-C9nbVShp.mjs → featureRegistry-DSsZbhrh.mjs} +3 -3
  24. package/dist/agent/chunks/{files-q8Xu5bft.mjs → files-D1daDgY0.mjs} +1 -1
  25. package/dist/agent/chunks/{forms-BKU5F9WF.mjs → forms-BfuWaQjA.mjs} +1 -1
  26. package/dist/agent/chunks/{importCsv-DmE_sBch.mjs → importCsv-LVmMTaWU.mjs} +2 -2
  27. package/dist/agent/chunks/{install-CpHoUFtL.mjs → install-CbpD_7gd.mjs} +3 -3
  28. package/dist/agent/chunks/{loader-C9nm3iwx.mjs → loader-MPD9iAvh.mjs} +20 -20
  29. package/dist/agent/chunks/{modules-CqdVQVJb.mjs → modules-clWdE-K2.mjs} +27 -2
  30. package/dist/agent/chunks/{nodeImport-BPCt5uMu.mjs → nodeImport-ylfRDvqt.mjs} +2 -2
  31. package/dist/agent/chunks/{package-DWbn3t2R.mjs → package-B7mI6HGi.mjs} +1 -1
  32. package/dist/agent/chunks/{papaparse-DjBYnQM2.mjs → papaparse-Df7i1dHr.mjs} +31 -7
  33. package/dist/agent/chunks/{pipelines-DCdRBNBV.mjs → pipelines-BAKpPk1w.mjs} +1 -1
  34. package/dist/agent/chunks/{plan-C0TBrU92.mjs → plan-BmTat6-T.mjs} +1 -1
  35. package/dist/agent/chunks/{records-DoqiT9xR.mjs → records-DcHFelq5.mjs} +5 -1
  36. package/dist/agent/chunks/{registry-Ceexawxl.mjs → registry-COfJrSmg.mjs} +18 -18
  37. package/dist/agent/chunks/{reports-BB59N6ke.mjs → reports-BACHGccj.mjs} +3 -3
  38. package/dist/agent/chunks/{runtime-DHo7ZEEO.mjs → runtime-C0Xf0Axi.mjs} +53 -32
  39. package/dist/agent/chunks/{schema-DlHt2PxI.mjs → schema-C4lSslXv.mjs} +10 -4
  40. package/dist/agent/chunks/{schema-7-4-qtSS.mjs → schema-DnLj8dUO.mjs} +10 -0
  41. package/dist/agent/chunks/{seed-CugPGYZL.mjs → seed-6MRmQQBR.mjs} +26 -8
  42. package/dist/agent/chunks/{server-ISwlDRCN.mjs → server-DC3t8r30.mjs} +5 -5
  43. package/dist/agent/chunks/{settings-BtvR_jdm.mjs → settings-Di8nzNEL.mjs} +35 -0
  44. package/dist/agent/chunks/{stats-CfeJssSS.mjs → stats-BfuLWhVV.mjs} +2 -2
  45. package/dist/agent/chunks/{tools-Bew8lxKx.mjs → tools-iSH-yMpl.mjs} +59 -19
  46. package/dist/agent/chunks/{views-fO_BM5U_.mjs → views-DH1n9Lrt.mjs} +1 -1
  47. package/dist/version.json +1 -1
  48. package/package.json +1 -1
  49. package/plugin/.claude-plugin/plugin.json +1 -1
  50. package/plugin/skills/agentcrm-admin/SKILL.md +67 -67
  51. package/plugin/skills/agentcrm-admin/references/appearance.md +1 -1
  52. package/plugin/skills/agentcrm-admin/references/records.md +5 -5
  53. package/plugin/skills/agentcrm-admin/references/schema.md +20 -20
  54. package/plugin/skills/agentcrm-extensions/SKILL.md +9 -9
  55. package/plugin/skills/agentcrm-extensions/references/module-recipes.md +2 -2
  56. package/plugin/skills/agentcrm-extensions/references/testing.md +16 -14
  57. package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +3 -3
  58. package/plugin/skills/agentcrm-install/SKILL.md +13 -13
  59. package/plugin/skills/agentcrm-install/references/firebase.md +1 -1
  60. package/plugin/skills/agentcrm-install/references/github.md +1 -1
  61. package/plugin/skills/agentcrm-install/references/mcp.md +3 -3
  62. package/plugin/skills/agentcrm-install/references/plans.md +1 -1
  63. package/plugin/skills/agentcrm-install/references/s3.md +1 -1
  64. package/plugin/skills/agentcrm-manage/SKILL.md +15 -15
  65. package/plugin/skills/agentcrm-sales/SKILL.md +18 -18
@@ -8,7 +8,7 @@ import { dateValueToMs, colorAt } from "./fields-B0bitgmr.mjs";
8
8
  import { s as slugId } from "./ids-CvnmrMJX.mjs";
9
9
  import { s as stageOptions, p as pipelineOptions } from "./standardObjects-CheqfDqa.mjs";
10
10
  import { assertCan, OpsError } from "./context-CxFfqS4D.mjs";
11
- import { updateRecord } from "./records-DoqiT9xR.mjs";
11
+ import { updateRecord } from "./records-DcHFelq5.mjs";
12
12
  import { requireRecord, pipelinesOf, requireObject, requirePipeline, recordsOf, asDoc, thaw } from "./store-5gqRSKXg.mjs";
13
13
  import "./pluginRegistry-DyHtlkcr.mjs";
14
14
  import "./query-C5nJB_3C.mjs";
@@ -6,7 +6,7 @@ const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
7
  import { isAbsolute, resolve } from "node:path";
8
8
  import { splitSecretSettings } from "./runtimeConfig-CCOFg1RM.mjs";
9
- import { v as validateAlias, f as findBackend, a as parseAccount, g as getCredential, b as requireCredential, C as CliError, c as findLocation, L as LOCATIONS, d as findHosting, H as HOSTINGS } from "../agentcrm.mjs";
9
+ import { v as validateAlias, f as findBackend, b as parseAccount, g as getCredential, c as requireCredential, C as CliError, d as findLocation, L as LOCATIONS, e as findHosting, H as HOSTINGS } from "../agentcrm.mjs";
10
10
  import "node:util";
11
11
  import "node:fs";
12
12
  import "node:url";
@@ -6,7 +6,7 @@ const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
7
  import { normalizeDomain, recordLabel, validateRecordFields, defaultFieldValues, isEmptyFieldValue, FieldValidationError, normalizeEmail } from "./fields-B0bitgmr.mjs";
8
8
  import { n as newId } from "./ids-CvnmrMJX.mjs";
9
- import { b as applyFilters, a as applySorts, p as paginate } from "./query-C5nJB_3C.mjs";
9
+ import { r as resolveField, b as applyFilters, a as applySorts, p as paginate } from "./query-C5nJB_3C.mjs";
10
10
  import { n as normalizeStandardFields } from "./standardObjects-CheqfDqa.mjs";
11
11
  import { assertCan, OpsError, actorId, emit } from "./context-CxFfqS4D.mjs";
12
12
  import { i as inverseRelations, j as expandRelations, l as labelOf, v as validateRelationValue, c as rewriteReferences, b as fileCleanupMutations } from "./relations-B9TynBmA.mjs";
@@ -1933,6 +1933,10 @@ function queryContext(ctx, object) {
1933
1933
  function listRecords(ctx, objectId, opts = {}) {
1934
1934
  assertCan(ctx, "records.read");
1935
1935
  const object = requireObject(ctx.store, objectId);
1936
+ const unknown = [...opts.filters ?? [], ...opts.sorts ?? []].map((f) => f.field).filter((f) => !resolveField(object, f));
1937
+ if (unknown.length) {
1938
+ throw new OpsError("validation", `Unknown field${unknown.length > 1 ? "s" : ""} ${[...new Set(unknown)].map((f) => `"${f}"`).join(", ")} on ${object.labelPlural}. Fields: ${object.fields.map((f) => f.id).join(", ")}, id, ownerId, createdAt, updatedAt.`);
1939
+ }
1936
1940
  let filters = opts.filters ?? [];
1937
1941
  let sorts = opts.sorts ?? [];
1938
1942
  if (opts.viewId) {
@@ -4,18 +4,18 @@ import { dirname as __acrmDirname } from "node:path";
4
4
  const require2 = __acrmRequire(import.meta.url);
5
5
  const __filename = __acrmPath(import.meta.url);
6
6
  const __dirname = __acrmDirname(__filename);
7
- import { i as isCompatibleApiVersion } from "./api-B6OqNVWp.mjs";
8
- import { registerExternalFeature } from "./featureRegistry-C9nbVShp.mjs";
9
- import { registerAiTool } from "./tools-Bew8lxKx.mjs";
7
+ import { i as isCompatibleApiVersion } from "./api-Cz0Ubj-B.mjs";
8
+ import { registerExternalFeature } from "./featureRegistry-DSsZbhrh.mjs";
9
+ import { registerAiTool } from "./tools-iSH-yMpl.mjs";
10
10
  import { registerAutomationAction } from "./extensionActions-D2HtpQz4.mjs";
11
11
  import { plugins } from "./pluginRegistry-DyHtlkcr.mjs";
12
12
  import { registerExternalTheme } from "./themes-DPZFv3yc.mjs";
13
13
  import "./index-Bnmav-Oh.mjs";
14
14
  import "./_commonjsHelpers-ClciMIcB.mjs";
15
15
  import "./index-BM8qeGOb.mjs";
16
- import "./modules-CqdVQVJb.mjs";
16
+ import "./modules-clWdE-K2.mjs";
17
17
  import "./context-CxFfqS4D.mjs";
18
- import "./settings-BtvR_jdm.mjs";
18
+ import "./settings-Di8nzNEL.mjs";
19
19
  import "./store-5gqRSKXg.mjs";
20
20
  import "./runtimeConfig-CCOFg1RM.mjs";
21
21
  import "./fields-B0bitgmr.mjs";
@@ -33,27 +33,27 @@ import "./activities-Cj_SIOay.mjs";
33
33
  import "./ids-CvnmrMJX.mjs";
34
34
  import "./relations-B9TynBmA.mjs";
35
35
  import "./dedupe-BepmsaHv.mjs";
36
- import "./importCsv-DmE_sBch.mjs";
37
- import "./papaparse-DjBYnQM2.mjs";
36
+ import "./importCsv-LVmMTaWU.mjs";
37
+ import "./papaparse-Df7i1dHr.mjs";
38
38
  import "stream";
39
39
  import "./utils-DmI8FtXP.mjs";
40
- import "./records-DoqiT9xR.mjs";
40
+ import "./records-DcHFelq5.mjs";
41
41
  import "./standardObjects-CheqfDqa.mjs";
42
- import "./pipelines-DCdRBNBV.mjs";
43
- import "./reports-BB59N6ke.mjs";
44
- import "./schema-DlHt2PxI.mjs";
45
- import "./automations-BWrvLqP6.mjs";
46
- import "./forms-BKU5F9WF.mjs";
47
- import "./schema-7-4-qtSS.mjs";
48
- import "./appearance-BaShelzt.mjs";
49
- import "./extensions-BP1k60md.mjs";
50
- import "./package-DWbn3t2R.mjs";
42
+ import "./pipelines-BAKpPk1w.mjs";
43
+ import "./reports-BACHGccj.mjs";
44
+ import "./schema-C4lSslXv.mjs";
45
+ import "./automations-DwWruxmu.mjs";
46
+ import "./forms-BfuWaQjA.mjs";
47
+ import "./schema-DnLj8dUO.mjs";
48
+ import "./appearance-BloHbiLA.mjs";
49
+ import "./extensions-Bp72uZ9a.mjs";
50
+ import "./package-B7mI6HGi.mjs";
51
51
  import "./index-w03ReRs4.mjs";
52
52
  import "events";
53
53
  import "buffer";
54
54
  import "util";
55
55
  import "./files-CjsPJSR6.mjs";
56
- import "./files-q8Xu5bft.mjs";
56
+ import "./files-D1daDgY0.mjs";
57
57
  import "./index-RCmo-uld.mjs";
58
58
  import "./http-Bk9ICb7I.mjs";
59
59
  const entries = /* @__PURE__ */ new Map();
@@ -9,8 +9,8 @@ import { L as LIFECYCLE_OPTIONS } from "./standardObjects-CheqfDqa.mjs";
9
9
  import { A as ACTIVITY_TYPES } from "../agentcrm.mjs";
10
10
  import { lastActivityAt } from "./activities-Cj_SIOay.mjs";
11
11
  import { OpsError, assertCan } from "./context-CxFfqS4D.mjs";
12
- import { forecast, pipelineOfRecord } from "./pipelines-DCdRBNBV.mjs";
13
- import { getSettings } from "./settings-BtvR_jdm.mjs";
12
+ import { forecast, pipelineOfRecord } from "./pipelines-BAKpPk1w.mjs";
13
+ import { getSettings } from "./settings-Di8nzNEL.mjs";
14
14
  import { objectOf, recordsOf, pipelinesOf, usersOf, activitiesOf, requirePipeline } from "./store-5gqRSKXg.mjs";
15
15
  import "node:util";
16
16
  import "node:fs";
@@ -24,7 +24,7 @@ import "./ids-CvnmrMJX.mjs";
24
24
  import "./query-C5nJB_3C.mjs";
25
25
  import "./relations-B9TynBmA.mjs";
26
26
  import "./pluginRegistry-DyHtlkcr.mjs";
27
- import "./records-DoqiT9xR.mjs";
27
+ import "./records-DcHFelq5.mjs";
28
28
  import "./runtimeConfig-CCOFg1RM.mjs";
29
29
  const DAY = 864e5;
30
30
  function defaultPipelineId(ctx, pipelineId) {
@@ -8,42 +8,42 @@ import { a as requireReact, r as reactExports, R as React } from "./index-Bnmav-
8
8
  import { j as jsxRuntimeExports, R as ReactJsxRuntime } from "./jsx-runtime-Ca2uwGDr.mjs";
9
9
  import { g as getDefaultExportFromCjs } from "./_commonjsHelpers-ClciMIcB.mjs";
10
10
  import { OpsError, PERMISSIONS, actorId, assertCan, can, createOpsContext, emit } from "./context-CxFfqS4D.mjs";
11
- import { bulkDelete, bulkUpdate, createRecord, deleteRecord, getRecord, getRecordDetails, hardDeleteMutations, listRecords, prepareRecordFields, queryContext, recordPatchMutation, restoreRecord, searchRecords, setRecordOwner, updateRecord } from "./records-DoqiT9xR.mjs";
11
+ import { bulkDelete, bulkUpdate, createRecord, deleteRecord, getRecord, getRecordDetails, hardDeleteMutations, listRecords, prepareRecordFields, queryContext, recordPatchMutation, restoreRecord, searchRecords, setRecordOwner, updateRecord } from "./records-DcHFelq5.mjs";
12
12
  import { j as expandRelations, b as fileCleanupMutations, e as fileFieldIds, g as fileIdsIn, d as fileMergeMutations, f as fileTargetPatch, i as inverseRelations, l as labelOf, k as linkRecords, h as referencedFileIds, m as relatedRecords, r as relationFields, a as relationIds, c as rewriteReferences, s as sameTarget, t as toRef, u as unlinkRecords, v as validateRelationValue } from "./relations-B9TynBmA.mjs";
13
13
  import { addActivity, addNote, completeTask, createTask, deleteActivity, getActivity, lastActivityAt, listActivities, listTasks, timelineScope, updateActivity } from "./activities-Cj_SIOay.mjs";
14
- import { createPipeline, deletePipeline, forecast, getPipeline, listPipelines, moveStage, pipelineOfRecord, syncPipelineOptions, updatePipeline } from "./pipelines-DCdRBNBV.mjs";
15
- import { addOption, changeFieldType, createField, createObject, deleteField, deleteObject, describeSchema, getObject, listObjects, removeOption, reorderFields, updateField, updateObject, updateOption } from "./schema-DlHt2PxI.mjs";
16
- import { createView, deleteView, duplicateView, getView, listViews, runView, updateView } from "./views-fO_BM5U_.mjs";
14
+ import { createPipeline, deletePipeline, forecast, getPipeline, listPipelines, moveStage, pipelineOfRecord, syncPipelineOptions, updatePipeline } from "./pipelines-BAKpPk1w.mjs";
15
+ import { addOption, changeFieldType, createField, createObject, deleteField, deleteObject, describeSchema, getObject, listObjects, removeOption, reorderFields, updateField, updateObject, updateOption } from "./schema-C4lSslXv.mjs";
16
+ import { createView, deleteView, duplicateView, getView, listViews, runView, updateView } from "./views-DH1n9Lrt.mjs";
17
17
  import { createUser, deleteUser, findUserByEmail, getUser, listUsers, setUserRole, updateUser } from "./users-DI1BvS0f.mjs";
18
- import { DEFAULT_SETTINGS, assertNoSecrets, getConfigDoc, getSettings, setConfigDoc, updateSettings } from "./settings-BtvR_jdm.mjs";
19
- import { MAPPING_ID, MAPPING_OWNER, MAPPING_SKIP, importCsv, parseCsv, suggestMapping } from "./importCsv-DmE_sBch.mjs";
20
- import { exportCsv, exportJson, importJson } from "./exportCsv-B5hRvkNm.mjs";
18
+ import { DEFAULT_SETTINGS, SETTINGS_KEYS, assertNoSecrets, getConfigDoc, getSettings, setConfigDoc, updateSettings } from "./settings-Di8nzNEL.mjs";
19
+ import { MAPPING_ID, MAPPING_OWNER, MAPPING_SKIP, importCsv, parseCsv, suggestMapping } from "./importCsv-LVmMTaWU.mjs";
20
+ import { exportCsv, exportJson, importJson, planImportJson } from "./exportCsv-C6e6XEmB.mjs";
21
21
  import { findDuplicates, mergeRecords } from "./dedupe-BepmsaHv.mjs";
22
- import { REPORTS, activityByUser, conversion, leadSources, pipelineByStage, runReport, staleDeals, wonLost } from "./reports-BB59N6ke.mjs";
23
- import { getStats } from "./stats-CfeJssSS.mjs";
24
- import { DEFAULT_ADMIN_ID, seedCrm, seedDemoData } from "./seed-CugPGYZL.mjs";
25
- import { compactData } from "./compact-CsFPaAAq.mjs";
26
- import { ACTION_TYPES, AUTOMATION_TEMPLATES, EVENT_TRIGGERS, MAX_AUTOMATION_DEPTH, MAX_AUTOMATION_RUNS, SCHEDULED_TRIGGERS, TRIGGER_TYPES, automationAssignees, clearAutomationRuns, createAutomation, createAutomationFromTemplate, deleteAutomation, duplicateAutomation, findAutomationTemplate, getAutomation, installAutomations, lastTouchedAt, listAutomationRuns, listAutomations, pruneAutomationRuns, renderTemplate, runAutomationEvent, runScheduled, scheduledCandidates, setAutomationEnabled, setAutomationFetch, templatePlaceholders, testAutomation, triggerMatches, updateAutomation, validateAutomation } from "./automations-BWrvLqP6.mjs";
27
- import { DEFAULT_FIREBASE_INBOX, DEFAULT_FORM_TEXTS, DEFAULT_INBOX_PREFIX, FORM_FIELD_TYPES, FREE_MAIL_DOMAINS, HONEYPOT_NAME, INBOX_KINDS, MIN_FILL_MS, buildFormArtifacts, buildFormHtml, companyDomainOfEmail, connectorInboxSource, createForm, defaultFormPath, deleteForm, escapeHtml, firestoreInboxSource, getForm, ingestInbox, ingestSubmissions, isFormFieldType, listForms, listInbox, memoryInboxSource, parseSubmission, publishForm, resolveInboxSource, setInboxSourceResolver, suggestFormFields, updateForm, validateForm } from "./forms-BKU5F9WF.mjs";
28
- import { disabledObjectIds, getModule, getModuleConfig, isModuleEnabled, listModules, moduleStates, setModuleConfig, setModuleEnabled } from "./modules-CqdVQVJb.mjs";
29
- import { getExtension, getExtensionPackage, installExtension, listExtensions, setExtensionEnabled, summarizeExtension, uninstallExtension } from "./extensions-BP1k60md.mjs";
30
- import { applyPreset, getAppearance, getAppearanceDoc, listPresets, resetAppearance, setAppearance, setLogo } from "./appearance-BaShelzt.mjs";
31
- import { ORPHAN_GRACE_MS, attachFile, clearRecordImage, deleteFile, detachFile, fileUsage, getFile, getFileBytes, listFiles, maxUploadBytes, purgeFiles, recordImageId, renameFile, restoreFile, setRecordImage, uploadFile } from "./files-q8Xu5bft.mjs";
22
+ import { REPORTS, activityByUser, conversion, leadSources, pipelineByStage, runReport, staleDeals, wonLost } from "./reports-BACHGccj.mjs";
23
+ import { getStats } from "./stats-BfuLWhVV.mjs";
24
+ import { DEFAULT_ADMIN_ID, seedCrm, seedDemoData } from "./seed-6MRmQQBR.mjs";
25
+ import { compactData } from "./compact-BGHI92tm.mjs";
26
+ import { ACTION_TYPES, AUTOMATION_TEMPLATES, EVENT_TRIGGERS, MAX_AUTOMATION_DEPTH, MAX_AUTOMATION_RUNS, SCHEDULED_TRIGGERS, TRIGGER_TYPES, automationAssignees, clearAutomationRuns, createAutomation, createAutomationFromTemplate, deleteAutomation, duplicateAutomation, findAutomationTemplate, getAutomation, installAutomations, lastTouchedAt, listAutomationRuns, listAutomations, pruneAutomationRuns, renderTemplate, runAutomationEvent, runScheduled, scheduledCandidates, setAutomationEnabled, setAutomationFetch, templatePlaceholders, testAutomation, triggerMatches, updateAutomation, validateAutomation } from "./automations-DwWruxmu.mjs";
27
+ import { DEFAULT_FIREBASE_INBOX, DEFAULT_FORM_TEXTS, DEFAULT_INBOX_PREFIX, FORM_FIELD_TYPES, FREE_MAIL_DOMAINS, HONEYPOT_NAME, INBOX_KINDS, MIN_FILL_MS, buildFormArtifacts, buildFormHtml, companyDomainOfEmail, connectorInboxSource, createForm, defaultFormPath, deleteForm, escapeHtml, firestoreInboxSource, getForm, ingestInbox, ingestSubmissions, isFormFieldType, listForms, listInbox, memoryInboxSource, parseSubmission, publishForm, resolveInboxSource, setInboxSourceResolver, suggestFormFields, updateForm, validateForm } from "./forms-BfuWaQjA.mjs";
28
+ import { disabledObjectIds, getModule, getModuleConfig, isModuleEnabled, listModules, moduleStates, setModuleConfig, setModuleEnabled } from "./modules-clWdE-K2.mjs";
29
+ import { getExtension, getExtensionPackage, installExtension, listExtensions, setExtensionEnabled, summarizeExtension, uninstallExtension } from "./extensions-Bp72uZ9a.mjs";
30
+ import { applyPreset, getAppearance, getAppearanceDoc, listPresets, resetAppearance, setAppearance, setLogo } from "./appearance-BloHbiLA.mjs";
31
+ import { ORPHAN_GRACE_MS, attachFile, clearRecordImage, deleteFile, detachFile, fileUsage, getFile, getFileBytes, listFiles, maxUploadBytes, purgeFiles, recordImageId, renameFile, restoreFile, setRecordImage, uploadFile } from "./files-D1daDgY0.mjs";
32
32
  import { S as SCHEMA_MIGRATIONS, m as migrateSchema, s as schemaMigrationMutations } from "./migrate-CsClnnN8.mjs";
33
- import { BACKUP_DATA_FILE, BACKUP_FILES_DIR, exportBackupZip, importBackupZip, readBackupZipInfo } from "./backup-ClSmxmFZ.mjs";
33
+ import { BACKUP_DATA_FILE, BACKUP_FILES_DIR, exportBackupZip, importBackupZip, planBackupZip, readBackupZipInfo } from "./backup-W3CjgkAJ.mjs";
34
34
  import { getAutomationAction, listAutomationActions, registerAutomationAction, subscribeAutomationActions } from "./extensionActions-D2HtpQz4.mjs";
35
35
  import { a as intlLocale, b as activeLocale, i as instance, r as registerI18nNamespace } from "./index-BM8qeGOb.mjs";
36
36
  import "./standardObjects-CheqfDqa.mjs";
37
- import { useModuleStates, useModuleEnabled } from "./featureRegistry-C9nbVShp.mjs";
37
+ import { useModuleStates, useModuleEnabled } from "./featureRegistry-DSsZbhrh.mjs";
38
38
  import { a as cn, i as initials, f as formatRelative, b as formatDateTime, d as formatDate } from "./utils-DmI8FtXP.mjs";
39
39
  import { t as toast } from "./http-Bk9ICb7I.mjs";
40
40
  import { isEmptyFieldValue, formatFieldValue, formatAddress, dateValueToMs, formatNumber, formatCurrency, toIsoDate, parseNumber, coerceFieldValue, recordLabel } from "./fields-B0bitgmr.mjs";
41
41
  import { fileKind, isPreviewable, formatBytes } from "./files-CjsPJSR6.mjs";
42
42
  import { plugins } from "./pluginRegistry-DyHtlkcr.mjs";
43
43
  import { n as newId } from "./ids-CvnmrMJX.mjs";
44
- import { registerAiTool } from "./tools-Bew8lxKx.mjs";
45
- import { a as DEFAULT_APPEARANCE } from "./schema-7-4-qtSS.mjs";
46
- import { b as EXTENSION_MIN_API_VERSION, E as EXTENSION_API_VERSION, j as RUNTIME_GLOBAL } from "./api-B6OqNVWp.mjs";
44
+ import { registerAiTool } from "./tools-iSH-yMpl.mjs";
45
+ import { a as DEFAULT_APPEARANCE } from "./schema-DnLj8dUO.mjs";
46
+ import { b as EXTENSION_MIN_API_VERSION, E as EXTENSION_API_VERSION, k as RUNTIME_GLOBAL } from "./api-Cz0Ubj-B.mjs";
47
47
  import "./query-C5nJB_3C.mjs";
48
48
  import "./store-5gqRSKXg.mjs";
49
49
  import "../agentcrm.mjs";
@@ -56,11 +56,11 @@ import "node:os";
56
56
  import "node:child_process";
57
57
  import "node:http";
58
58
  import "./runtimeConfig-CCOFg1RM.mjs";
59
- import "./papaparse-DjBYnQM2.mjs";
59
+ import "./papaparse-Df7i1dHr.mjs";
60
60
  import "stream";
61
61
  import "./types-BOy9PUl7.mjs";
62
62
  import "./index-RCmo-uld.mjs";
63
- import "./package-DWbn3t2R.mjs";
63
+ import "./package-B7mI6HGi.mjs";
64
64
  import "./index-w03ReRs4.mjs";
65
65
  import "events";
66
66
  import "buffer";
@@ -8396,6 +8396,7 @@ const opsModule = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
8396
8396
  REPORTS,
8397
8397
  SCHEDULED_TRIGGERS,
8398
8398
  SCHEMA_MIGRATIONS,
8399
+ SETTINGS_KEYS,
8399
8400
  TRIGGER_TYPES,
8400
8401
  activityByUser,
8401
8402
  actorId,
@@ -8527,6 +8528,8 @@ const opsModule = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
8527
8528
  parseSubmission,
8528
8529
  pipelineByStage,
8529
8530
  pipelineOfRecord,
8531
+ planBackupZip,
8532
+ planImportJson,
8530
8533
  prepareRecordFields,
8531
8534
  pruneAutomationRuns,
8532
8535
  publishForm,
@@ -9706,7 +9709,7 @@ const Button = reactExports.forwardRef(function Button2({ variant = "outline", s
9706
9709
  ),
9707
9710
  ...rest,
9708
9711
  children: [
9709
- hasIcon && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex h-4 w-4 shrink-0 items-center justify-center [&>svg]:h-4 [&>svg]:w-4", children: [
9712
+ hasIcon && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex h-4 w-4 shrink-0 items-center justify-center [&_svg]:h-4 [&_svg]:w-4", children: [
9710
9713
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("inline-flex", loading && "hidden"), children: icon }),
9711
9714
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("inline-flex", !loading && "hidden"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }) })
9712
9715
  ] }),
@@ -9794,7 +9797,7 @@ const Checkbox = reactExports.forwardRef(function Checkbox2({ label, indetermina
9794
9797
  }
9795
9798
  );
9796
9799
  if (!label) return input;
9797
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: cn("inline-flex cursor-pointer items-center gap-2 text-sm text-surface-700 dark:text-surface-200", className), children: [
9800
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: cn("relative inline-flex cursor-pointer items-center gap-2 text-sm text-surface-700 dark:text-surface-200", className), children: [
9798
9801
  input,
9799
9802
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: label })
9800
9803
  ] });
@@ -9821,7 +9824,7 @@ function Switch({ checked, onChange, label, disabled, size = "md", className, id
9821
9824
  }
9822
9825
  );
9823
9826
  if (!label) return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className, children: button });
9824
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: cn("inline-flex cursor-pointer items-center gap-2 text-sm text-surface-700 dark:text-surface-200", className), children: [
9827
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: cn("relative inline-flex cursor-pointer items-center gap-2 text-sm text-surface-700 dark:text-surface-200", className), children: [
9825
9828
  button,
9826
9829
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: label })
9827
9830
  ] });
@@ -9910,8 +9913,23 @@ function Drawer({ open, onClose, title, children, footer, width = "md", side = "
9910
9913
  )
9911
9914
  ] }) });
9912
9915
  }
9916
+ const PopoverParent = reactExports.createContext(null);
9917
+ function isInsidePopover(target, id) {
9918
+ let el = target instanceof Element ? target : target?.parentElement ?? null;
9919
+ let panel = el?.closest("[data-popover-id]") ?? null;
9920
+ while (panel) {
9921
+ if (panel.getAttribute("data-popover-id") === id) return true;
9922
+ const parent = panel.getAttribute("data-popover-parent");
9923
+ if (!parent) return false;
9924
+ el = document.querySelector(`[data-popover-id="${CSS.escape(parent)}"]`);
9925
+ panel = el;
9926
+ }
9927
+ return false;
9928
+ }
9913
9929
  function Popover({ open, onClose, anchor, children, align = "start", matchWidth, className, offset = 4 }) {
9914
9930
  const panel = reactExports.useRef(null);
9931
+ const id = reactExports.useId();
9932
+ const parentId = reactExports.useContext(PopoverParent);
9915
9933
  const [pos, setPos] = reactExports.useState(null);
9916
9934
  const place = reactExports.useCallback(() => {
9917
9935
  if (!anchor) return;
@@ -9931,11 +9949,12 @@ function Popover({ open, onClose, anchor, children, align = "start", matchWidth,
9931
9949
  if (!open) return;
9932
9950
  const onDown = (e2) => {
9933
9951
  const t2 = e2.target;
9934
- if (panel.current?.contains(t2) || anchor?.contains(t2)) return;
9952
+ if (panel.current?.contains(t2) || anchor?.contains(t2) || isInsidePopover(t2, id)) return;
9935
9953
  onClose();
9936
9954
  };
9937
9955
  const onKey = (e2) => {
9938
9956
  if (e2.key === "Escape") {
9957
+ if (document.querySelector(`[data-popover-parent="${CSS.escape(id)}"]`)) return;
9939
9958
  e2.stopPropagation();
9940
9959
  onClose();
9941
9960
  }
@@ -9953,18 +9972,20 @@ function Popover({ open, onClose, anchor, children, align = "start", matchWidth,
9953
9972
  window.removeEventListener("resize", onMove);
9954
9973
  window.removeEventListener("scroll", onMove, true);
9955
9974
  };
9956
- }, [open, onClose, anchor, place]);
9975
+ }, [open, onClose, anchor, place, id]);
9957
9976
  if (!open || !anchor) return null;
9958
9977
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
9959
9978
  "div",
9960
9979
  {
9961
9980
  ref: panel,
9981
+ "data-popover-id": id,
9982
+ "data-popover-parent": parentId ?? void 0,
9962
9983
  style: { top: pos?.top ?? -9999, left: pos?.left ?? -9999, ...matchWidth && pos ? { minWidth: pos.width } : {} },
9963
9984
  className: cn(
9964
9985
  "fixed z-[60] animate-scale-in overflow-hidden rounded-lg border border-surface-200 bg-white shadow-pop dark:border-surface-700 dark:bg-surface-900",
9965
9986
  className
9966
9987
  ),
9967
- children
9988
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(PopoverParent.Provider, { value: id, children })
9968
9989
  }
9969
9990
  ) });
9970
9991
  }
@@ -13541,11 +13562,11 @@ function createDOMPurify() {
13541
13562
  const originalDocument = document2;
13542
13563
  const currentScript = originalDocument.currentScript;
13543
13564
  window2.DocumentFragment;
13544
- const HTMLTemplateElement = window2.HTMLTemplateElement, Node = window2.Node, Element = window2.Element, NodeFilter = window2.NodeFilter;
13565
+ const HTMLTemplateElement = window2.HTMLTemplateElement, Node = window2.Node, Element2 = window2.Element, NodeFilter = window2.NodeFilter;
13545
13566
  window2.NamedNodeMap === void 0 && (window2.NamedNodeMap || window2.MozNamedAttrMap);
13546
13567
  window2.HTMLFormElement;
13547
13568
  const DOMParser = window2.DOMParser, trustedTypes = window2.trustedTypes;
13548
- const ElementPrototype = Element.prototype;
13569
+ const ElementPrototype = Element2.prototype;
13549
13570
  const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
13550
13571
  const remove = lookupGetter(ElementPrototype, "remove");
13551
13572
  const removeAttributeNode = lookupGetter(ElementPrototype, "removeAttributeNode");
@@ -8,10 +8,10 @@ import { c as camelId } from "./utils-DmI8FtXP.mjs";
8
8
  import { colorAt, canConvertType, isEmptyFieldValue, convertValue } from "./fields-B0bitgmr.mjs";
9
9
  import { s as slugId } from "./ids-CvnmrMJX.mjs";
10
10
  import { d as defaultViewFor } from "./standardObjects-CheqfDqa.mjs";
11
- import { e as FIELD_TYPES } from "../agentcrm.mjs";
11
+ import { a as FIELD_TYPES } from "../agentcrm.mjs";
12
12
  import { assertCan, OpsError } from "./context-CxFfqS4D.mjs";
13
- import { hardDeleteMutations } from "./records-DoqiT9xR.mjs";
14
- import { requireObject, objectsOf, pipelinesOf, recordsOf, asDoc, viewsOf, thaw } from "./store-5gqRSKXg.mjs";
13
+ import { hardDeleteMutations } from "./records-DcHFelq5.mjs";
14
+ import { requireObject, objectsOf, pipelinesOf, recordsOf, viewsOf, asDoc, thaw } from "./store-5gqRSKXg.mjs";
15
15
  import "node:util";
16
16
  import "node:fs";
17
17
  import "node:path";
@@ -183,9 +183,15 @@ async function createField(ctx, objectId, input) {
183
183
  const object = requireObject(ctx.store, objectId);
184
184
  const field = buildField(ctx, object, input);
185
185
  const imageField = object.imageField ?? (field.type === "image" ? field.id : void 0);
186
- await ctx.store.write([saveObject(ctx, { ...object, ...imageField ? { imageField } : {}, fields: [...object.fields, field] })], { message: `Add field ${field.label}` });
186
+ const mutations = [saveObject(ctx, { ...object, ...imageField ? { imageField } : {}, fields: [...object.fields, field] })];
187
+ const defaultView = viewsOf(ctx.store, objectId).find((v) => v.id === defaultViewFor(object).id);
188
+ if (defaultView && defaultView.type === "table" && !field.hidden && field.type !== "image" && !defaultView.columns.includes(field.id) && defaultView.columns.length < DEFAULT_VIEW_MAX_COLUMNS) {
189
+ mutations.push({ op: "patch", collection: "views", id: defaultView.id, fields: { columns: [...defaultView.columns, field.id] } });
190
+ }
191
+ await ctx.store.write(mutations, { message: `Add field ${field.label}` });
187
192
  return field;
188
193
  }
194
+ const DEFAULT_VIEW_MAX_COLUMNS = 7;
189
195
  function requireField(object, fieldId) {
190
196
  const f = object.fields.find((x) => x.id === fieldId);
191
197
  if (!f) throw new OpsError("notFound", `Field "${fieldId}" not found on ${object.labelPlural}.`);
@@ -87,6 +87,15 @@ const ENUMS = {
87
87
  };
88
88
  const IMAGE_RE = /^data:image\/(png|jpeg|gif|webp|svg\+xml|x-icon|vnd\.microsoft\.icon);base64,[A-Za-z0-9+/]+=*$/;
89
89
  const ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
90
+ function svgHasActiveContent(dataUrl) {
91
+ let text;
92
+ try {
93
+ text = atob(dataUrl.slice(dataUrl.indexOf(",") + 1));
94
+ } catch {
95
+ return true;
96
+ }
97
+ return /<script[\s>/]|<foreignObject[\s>/]|\son[a-z]+\s*=|javascript:/i.test(text);
98
+ }
90
99
  function dataUrlBytes(url) {
91
100
  const b64 = url.slice(url.indexOf(",") + 1);
92
101
  const pad = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
@@ -96,6 +105,7 @@ function validateImageDataUrl(value, label = "image") {
96
105
  if (typeof value !== "string" || !IMAGE_RE.test(value)) return `The ${label} must be a base64 image data URL (PNG, JPEG, GIF, WebP, SVG or ICO).`;
97
106
  const bytes = dataUrlBytes(value);
98
107
  if (bytes > MAX_IMAGE_BYTES) return `The ${label} is ${Math.round(bytes / 1024)} KB — the limit is ${MAX_IMAGE_BYTES / 1024} KB.`;
108
+ if (/^data:image\/svg\+xml/i.test(value) && svgHasActiveContent(value)) return `The ${label} SVG contains scripts or event handlers — export it as a plain SVG (or a PNG).`;
99
109
  return null;
100
110
  }
101
111
  function validateAppearancePatch(patch) {
@@ -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) {