@agent-native/core 0.123.0 → 0.123.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 (263) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/templates-meta.ts +12 -0
  5. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
  6. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
  7. package/corpus/templates/crm/.agents/skills/crm/SKILL.md +205 -0
  8. package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +220 -0
  9. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
  10. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +228 -0
  11. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +178 -0
  12. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
  13. package/corpus/templates/crm/.env.example +5 -0
  14. package/corpus/templates/crm/AGENTS.md +82 -0
  15. package/corpus/templates/crm/CHANGELOG.md +25 -0
  16. package/corpus/templates/crm/DEVELOPING.md +66 -0
  17. package/corpus/templates/crm/README.md +122 -0
  18. package/corpus/templates/crm/_gitignore +12 -0
  19. package/corpus/templates/crm/actions/_crm-action-utils.ts +98 -0
  20. package/corpus/templates/crm/actions/_crm-attribute-utils.ts +219 -0
  21. package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
  22. package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
  23. package/corpus/templates/crm/actions/_crm-list-utils.ts +775 -0
  24. package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
  25. package/corpus/templates/crm/actions/add-crm-record-to-list.ts +164 -0
  26. package/corpus/templates/crm/actions/apply-crm-proposals.ts +291 -0
  27. package/corpus/templates/crm/actions/archive-crm-attribute.ts +69 -0
  28. package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
  29. package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
  30. package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
  31. package/corpus/templates/crm/actions/create-crm-attribute.ts +350 -0
  32. package/corpus/templates/crm/actions/create-crm-list.ts +194 -0
  33. package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
  34. package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
  35. package/corpus/templates/crm/actions/delete-crm-saved-view.ts +68 -0
  36. package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
  37. package/corpus/templates/crm/actions/estimate-crm-enrichment.ts +229 -0
  38. package/corpus/templates/crm/actions/find-crm-duplicates.ts +87 -0
  39. package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
  40. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +35 -0
  41. package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
  42. package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
  43. package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
  44. package/corpus/templates/crm/actions/get-crm-record-page.ts +287 -0
  45. package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
  46. package/corpus/templates/crm/actions/get-crm-workspace.ts +364 -0
  47. package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
  48. package/corpus/templates/crm/actions/list-crm-attributes.ts +71 -0
  49. package/corpus/templates/crm/actions/list-crm-connections.ts +68 -0
  50. package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
  51. package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
  52. package/corpus/templates/crm/actions/list-crm-enrichment-slots.ts +28 -0
  53. package/corpus/templates/crm/actions/list-crm-list-entries.ts +192 -0
  54. package/corpus/templates/crm/actions/list-crm-lists.ts +80 -0
  55. package/corpus/templates/crm/actions/list-crm-proposals.ts +55 -0
  56. package/corpus/templates/crm/actions/list-crm-record-field-history.ts +201 -0
  57. package/corpus/templates/crm/actions/list-crm-record-values.ts +200 -0
  58. package/corpus/templates/crm/actions/list-crm-records.ts +103 -0
  59. package/corpus/templates/crm/actions/list-crm-saved-views.ts +66 -0
  60. package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
  61. package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
  62. package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
  63. package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
  64. package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
  65. package/corpus/templates/crm/actions/manage-crm-attribute-option.ts +272 -0
  66. package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
  67. package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
  68. package/corpus/templates/crm/actions/merge-crm-records.ts +691 -0
  69. package/corpus/templates/crm/actions/navigate.ts +54 -0
  70. package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
  71. package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
  72. package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
  73. package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
  74. package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
  75. package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
  76. package/corpus/templates/crm/actions/remove-crm-list-entry.ts +79 -0
  77. package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
  78. package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
  79. package/corpus/templates/crm/actions/run-crm-attribute-fill.ts +443 -0
  80. package/corpus/templates/crm/actions/run-crm-enrichment.ts +647 -0
  81. package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
  82. package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +265 -0
  83. package/corpus/templates/crm/actions/run.ts +7 -0
  84. package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
  85. package/corpus/templates/crm/actions/save-crm-saved-view.ts +296 -0
  86. package/corpus/templates/crm/actions/sync-crm.ts +146 -0
  87. package/corpus/templates/crm/actions/update-crm-attribute.ts +111 -0
  88. package/corpus/templates/crm/actions/update-crm-list-entry.ts +102 -0
  89. package/corpus/templates/crm/actions/update-crm-list.ts +97 -0
  90. package/corpus/templates/crm/actions/update-crm-record.ts +543 -0
  91. package/corpus/templates/crm/actions/view-screen.ts +324 -0
  92. package/corpus/templates/crm/agent-native.app-skill.json +69 -0
  93. package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
  94. package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +78 -0
  95. package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +220 -0
  96. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +414 -0
  97. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +648 -0
  98. package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +488 -0
  99. package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +232 -0
  100. package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +93 -0
  101. package/corpus/templates/crm/app/components/crm/Surface.tsx +97 -0
  102. package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
  103. package/corpus/templates/crm/app/components/crm/board/CrmBoard.tsx +988 -0
  104. package/corpus/templates/crm/app/components/crm/board/board-model.ts +399 -0
  105. package/corpus/templates/crm/app/components/crm/board/entry-filter.ts +150 -0
  106. package/corpus/templates/crm/app/components/crm/board/view-draft.ts +227 -0
  107. package/corpus/templates/crm/app/components/crm/grid/CrmGrid.tsx +1033 -0
  108. package/corpus/templates/crm/app/components/crm/grid/GridCell.tsx +620 -0
  109. package/corpus/templates/crm/app/components/crm/grid/clipboard.ts +84 -0
  110. package/corpus/templates/crm/app/components/crm/grid/model.ts +304 -0
  111. package/corpus/templates/crm/app/components/crm/grid/navigation.ts +220 -0
  112. package/corpus/templates/crm/app/components/crm/grid/query.ts +214 -0
  113. package/corpus/templates/crm/app/components/crm/record/AttributePanel.tsx +192 -0
  114. package/corpus/templates/crm/app/components/crm/record/FieldHistory.tsx +179 -0
  115. package/corpus/templates/crm/app/components/crm/record/RecordHeader.tsx +103 -0
  116. package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +235 -0
  117. package/corpus/templates/crm/app/components/crm/record/RecordTabs.tsx +206 -0
  118. package/corpus/templates/crm/app/components/crm/record/field-editors.tsx +245 -0
  119. package/corpus/templates/crm/app/components/crm/record/record-data.ts +603 -0
  120. package/corpus/templates/crm/app/components/crm/settings/AdvancedSettings.tsx +49 -0
  121. package/corpus/templates/crm/app/components/crm/settings/AttributeOptionsEditor.tsx +441 -0
  122. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +169 -0
  123. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +935 -0
  124. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +339 -0
  125. package/corpus/templates/crm/app/components/crm/settings/settings-admin.ts +377 -0
  126. package/corpus/templates/crm/app/components/crm/shared/AttributeValueParts.tsx +69 -0
  127. package/corpus/templates/crm/app/components/crm/shared/attribute-value.ts +591 -0
  128. package/corpus/templates/crm/app/components/layout/CrmCommandMenu.tsx +556 -0
  129. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +130 -0
  130. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +407 -0
  131. package/corpus/templates/crm/app/components/layout/command-palette-search.ts +126 -0
  132. package/corpus/templates/crm/app/components/layout/crm-ui-intents.ts +41 -0
  133. package/corpus/templates/crm/app/components/layout/sidebar-collapse.ts +40 -0
  134. package/corpus/templates/crm/app/components/layout/sidebar-lists.ts +109 -0
  135. package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +1 -0
  136. package/corpus/templates/crm/app/components/ui/badge.tsx +1 -0
  137. package/corpus/templates/crm/app/components/ui/button.tsx +1 -0
  138. package/corpus/templates/crm/app/components/ui/card.tsx +1 -0
  139. package/corpus/templates/crm/app/components/ui/checkbox.tsx +1 -0
  140. package/corpus/templates/crm/app/components/ui/dialog.tsx +1 -0
  141. package/corpus/templates/crm/app/components/ui/dropdown-menu.tsx +1 -0
  142. package/corpus/templates/crm/app/components/ui/hover-card.tsx +1 -0
  143. package/corpus/templates/crm/app/components/ui/input.tsx +1 -0
  144. package/corpus/templates/crm/app/components/ui/label.tsx +1 -0
  145. package/corpus/templates/crm/app/components/ui/popover.tsx +1 -0
  146. package/corpus/templates/crm/app/components/ui/select.tsx +1 -0
  147. package/corpus/templates/crm/app/components/ui/sheet.tsx +1 -0
  148. package/corpus/templates/crm/app/components/ui/skeleton.tsx +1 -0
  149. package/corpus/templates/crm/app/components/ui/switch.tsx +1 -0
  150. package/corpus/templates/crm/app/components/ui/table.tsx +1 -0
  151. package/corpus/templates/crm/app/components/ui/tabs.tsx +1 -0
  152. package/corpus/templates/crm/app/components/ui/textarea.tsx +1 -0
  153. package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +17 -0
  154. package/corpus/templates/crm/app/components/ui/tooltip.tsx +1 -0
  155. package/corpus/templates/crm/app/design-system.ts +3 -0
  156. package/corpus/templates/crm/app/entry.client.tsx +19 -0
  157. package/corpus/templates/crm/app/entry.server.tsx +10 -0
  158. package/corpus/templates/crm/app/global.css +167 -0
  159. package/corpus/templates/crm/app/hooks/use-keyboard-shortcuts.ts +206 -0
  160. package/corpus/templates/crm/app/hooks/use-navigation-state.ts +48 -0
  161. package/corpus/templates/crm/app/i18n/en-US.ts +684 -0
  162. package/corpus/templates/crm/app/i18n/index.ts +34 -0
  163. package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
  164. package/corpus/templates/crm/app/lib/navigation.ts +17 -0
  165. package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
  166. package/corpus/templates/crm/app/lib/types.ts +172 -0
  167. package/corpus/templates/crm/app/lib/utils.ts +1 -0
  168. package/corpus/templates/crm/app/root.tsx +93 -0
  169. package/corpus/templates/crm/app/routes/_index.tsx +26 -0
  170. package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
  171. package/corpus/templates/crm/app/routes/agent.tsx +5 -0
  172. package/corpus/templates/crm/app/routes/ask.tsx +53 -0
  173. package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
  174. package/corpus/templates/crm/app/routes/lists.tsx +228 -0
  175. package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
  176. package/corpus/templates/crm/app/routes/people.tsx +41 -0
  177. package/corpus/templates/crm/app/routes/proposals.tsx +388 -0
  178. package/corpus/templates/crm/app/routes/records.$recordId.tsx +53 -0
  179. package/corpus/templates/crm/app/routes/records.tsx +64 -0
  180. package/corpus/templates/crm/app/routes/settings.tsx +118 -0
  181. package/corpus/templates/crm/app/routes/setup.tsx +308 -0
  182. package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
  183. package/corpus/templates/crm/app/routes/views.tsx +683 -0
  184. package/corpus/templates/crm/app/routes.ts +19 -0
  185. package/corpus/templates/crm/app/vite-env.d.ts +6 -0
  186. package/corpus/templates/crm/changelog/2026-07-26-any-list-or-view-can-be-shown-as-a-board-grouped-by-stage-so.md +6 -0
  187. package/corpus/templates/crm/changelog/2026-07-26-crm-now-finds-likely-duplicate-records-and-tells-you-exactly.md +6 -0
  188. package/corpus/templates/crm/changelog/2026-07-26-enrichment-now-shows-what-a-run-will-cost-before-it-starts-g.md +6 -0
  189. package/corpus/templates/crm/changelog/2026-07-26-lists-turn-any-set-of-records-into-a-pipeline-with-its-own-s.md +6 -0
  190. package/corpus/templates/crm/changelog/2026-07-26-moving-a-record-or-list-entry-into-a-stage-that-was-retired-.md +6 -0
  191. package/corpus/templates/crm/changelog/2026-07-26-opening-a-record-now-shows-one-page-with-its-fields-the-list.md +6 -0
  192. package/corpus/templates/crm/changelog/2026-07-26-press-cmd-k-to-jump-to-any-record-list-view-or-setting-and-u.md +6 -0
  193. package/corpus/templates/crm/changelog/2026-07-26-provider-record-edits-now-show-an-exact-before-after-diff-an.md +6 -0
  194. package/corpus/templates/crm/changelog/2026-07-26-record-page-attribute-labels-now-read-in-full-instead-of-bei.md +6 -0
  195. package/corpus/templates/crm/changelog/2026-07-26-records-now-use-typed-fields-currency-dates-ratings-status-s.md +6 -0
  196. package/corpus/templates/crm/changelog/2026-07-26-retyping-a-value-on-the-record-page-now-replaces-it-instead-.md +6 -0
  197. package/corpus/templates/crm/changelog/2026-07-26-settings-now-has-fields-and-lists-tabs-for-adding-fields-edi.md +6 -0
  198. package/corpus/templates/crm/changelog/2026-07-26-the-proposal-review-now-shows-the-field-that-actually-change.md +6 -0
  199. package/corpus/templates/crm/changelog/2026-07-26-the-record-grid-is-now-a-real-spreadsheet-filter-sort-and-pa.md +6 -0
  200. package/corpus/templates/crm/components.json +20 -0
  201. package/corpus/templates/crm/docs/architecture/crm-contract.md +214 -0
  202. package/corpus/templates/crm/docs/features/README.md +55 -0
  203. package/corpus/templates/crm/docs/features/f1-records-and-fields.mdx +52 -0
  204. package/corpus/templates/crm/docs/features/f2-lists-pipelines-and-views.mdx +48 -0
  205. package/corpus/templates/crm/docs/features/f3-work-and-record-quality.mdx +40 -0
  206. package/corpus/templates/crm/docs/features/f4-evidence-and-chat.mdx +47 -0
  207. package/corpus/templates/crm/learnings.defaults.md +5 -0
  208. package/corpus/templates/crm/netlify.toml +11 -0
  209. package/corpus/templates/crm/package.json +61 -0
  210. package/corpus/templates/crm/public/favicon.svg +1 -0
  211. package/corpus/templates/crm/public/icon-180.svg +1 -0
  212. package/corpus/templates/crm/public/manifest.json +11 -0
  213. package/corpus/templates/crm/react-router.config.ts +7 -0
  214. package/corpus/templates/crm/server/crm/adapter.ts +32 -0
  215. package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
  216. package/corpus/templates/crm/server/crm/crm-mirror.ts +721 -0
  217. package/corpus/templates/crm/server/crm/field-policy-attributes.ts +48 -0
  218. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1146 -0
  219. package/corpus/templates/crm/server/crm/native-adapter.ts +1810 -0
  220. package/corpus/templates/crm/server/crm/provider-record-link.ts +264 -0
  221. package/corpus/templates/crm/server/crm/read-through.ts +207 -0
  222. package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
  223. package/corpus/templates/crm/server/db/crm-store.ts +1112 -0
  224. package/corpus/templates/crm/server/db/index.ts +191 -0
  225. package/corpus/templates/crm/server/db/schema.ts +647 -0
  226. package/corpus/templates/crm/server/lib/crm-query.ts +1558 -0
  227. package/corpus/templates/crm/server/lib/dedupe.ts +714 -0
  228. package/corpus/templates/crm/server/lib/enrichment-cost.ts +381 -0
  229. package/corpus/templates/crm/server/lib/enrichment-slots.ts +519 -0
  230. package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
  231. package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
  232. package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +94 -0
  233. package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +39 -0
  234. package/corpus/templates/crm/server/lib/intelligence/summary.ts +19 -0
  235. package/corpus/templates/crm/server/lib/lifecycle.ts +634 -0
  236. package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
  237. package/corpus/templates/crm/server/middleware/auth.ts +4 -0
  238. package/corpus/templates/crm/server/plugins/agent-chat.ts +65 -0
  239. package/corpus/templates/crm/server/plugins/auth.ts +14 -0
  240. package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
  241. package/corpus/templates/crm/server/plugins/db.ts +738 -0
  242. package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
  243. package/corpus/templates/crm/shared/crm-attributes.ts +469 -0
  244. package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
  245. package/corpus/templates/crm/shared/crm-contract.ts +417 -0
  246. package/corpus/templates/crm/shared/crm-navigation.ts +190 -0
  247. package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
  248. package/corpus/templates/crm/ssr-entry.ts +11 -0
  249. package/corpus/templates/crm/tsconfig.json +24 -0
  250. package/corpus/templates/crm/vite.config.ts +15 -0
  251. package/corpus/templates/crm/vitest.config.ts +5 -0
  252. package/corpus/templates/design/app/i18n-data.ts +12 -12
  253. package/corpus/templates/design/changelog/2026-07-26-apply-design-updates-from-a-centered-top-toolbar-without-cov.md +6 -0
  254. package/dist/cli/templates-meta.d.ts.map +1 -1
  255. package/dist/cli/templates-meta.js +12 -0
  256. package/dist/cli/templates-meta.js.map +1 -1
  257. package/dist/collab/routes.d.ts +1 -1
  258. package/dist/observability/routes.d.ts +3 -3
  259. package/dist/resources/handlers.d.ts +1 -1
  260. package/dist/server/realtime-token.d.ts +1 -1
  261. package/dist/server/transcribe-voice.d.ts +1 -1
  262. package/package.json +1 -1
  263. package/src/cli/templates-meta.ts +12 -0
@@ -0,0 +1,98 @@
1
+ import type { ActionRunContext } from "@agent-native/core/action";
2
+ import {
3
+ getRequestOrgId,
4
+ getRequestUserEmail,
5
+ } from "@agent-native/core/server";
6
+ import { z } from "zod";
7
+
8
+ /**
9
+ * A boolean flag that survives a query string. `z.boolean()` rejects "true" and
10
+ * `z.coerce.boolean()` reads "false" as true, so neither can carry a flag on a
11
+ * `http: { method: "GET" }` action.
12
+ */
13
+ export const queryFlag = z
14
+ .union([z.boolean(), z.enum(["true", "false", "1", "0"])])
15
+ .transform((value) => value === true || value === "true" || value === "1")
16
+ .default(false);
17
+
18
+ export {
19
+ isBoundedCrmValue,
20
+ isSafeCrmMutationFieldName,
21
+ isSafeCrmMutationFields,
22
+ } from "../server/crm/crm-field-firewall.js";
23
+
24
+ export const MAX_CRM_FIELDS_PER_MUTATION = 20;
25
+
26
+ export function requireCrmScope(ctx?: ActionRunContext) {
27
+ const ownerEmail = ctx?.userEmail ?? getRequestUserEmail();
28
+ if (!ownerEmail) throw new Error("Sign in is required to manage CRM data.");
29
+ const orgId = ctx?.orgId ?? getRequestOrgId() ?? null;
30
+ return {
31
+ ownerEmail,
32
+ orgId,
33
+ visibility: orgId ? ("org" as const) : ("private" as const),
34
+ };
35
+ }
36
+
37
+ export function crmInitiatedBy(ctx?: ActionRunContext) {
38
+ if (ctx?.caller === "automation") return "automation" as const;
39
+ return ctx?.caller === "tool" ||
40
+ ctx?.caller === "mcp" ||
41
+ ctx?.caller === "a2a"
42
+ ? ("agent" as const)
43
+ : ("human" as const);
44
+ }
45
+
46
+ export function crmWriteRisk(fieldNames: string[]) {
47
+ const names = fieldNames.map((field) => field.toLowerCase());
48
+ if (names.some((field) => field.includes("owner")))
49
+ return "ownership" as const;
50
+ if (names.some((field) => field === "amount" || field.includes("amount"))) {
51
+ return "amount" as const;
52
+ }
53
+ if (
54
+ names.some((field) => field.includes("stage") || field.includes("pipeline"))
55
+ ) {
56
+ return "stage" as const;
57
+ }
58
+ return "routine" as const;
59
+ }
60
+
61
+ export async function scopedCrmIdempotencyKey(input: {
62
+ ownerEmail: string;
63
+ orgId: string | null;
64
+ recordId: string;
65
+ key: string;
66
+ }): Promise<string> {
67
+ const value = [
68
+ "crm-mutation-v1",
69
+ input.ownerEmail.toLowerCase(),
70
+ input.orgId ?? "",
71
+ input.recordId,
72
+ input.key,
73
+ ].join("\u0000");
74
+ const bytes = new TextEncoder().encode(value);
75
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
76
+ return `crm:v1:${Array.from(new Uint8Array(digest), (byte) =>
77
+ byte.toString(16).padStart(2, "0"),
78
+ ).join("")}`;
79
+ }
80
+
81
+ export function toJson(value: unknown, maxChars: number) {
82
+ const encoded = JSON.stringify(value);
83
+ if (encoded.length > maxChars) {
84
+ throw new Error(`CRM payload exceeds the ${maxChars}-character limit.`);
85
+ }
86
+ return encoded;
87
+ }
88
+
89
+ export function parseJsonRecord(value: string): Record<string, unknown> {
90
+ try {
91
+ const parsed = JSON.parse(value) as unknown;
92
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
93
+ ? (parsed as Record<string, unknown>)
94
+ : {};
95
+ } catch {
96
+ return {};
97
+ }
98
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Shared plumbing for the CRM attribute (typed schema) actions.
3
+ *
4
+ * `crm_field_policies` IS the attribute table — there is no parallel one — so
5
+ * every helper here reads and writes that table and translates it to the
6
+ * `CrmAttributeDefinition` shape the contract publishes.
7
+ */
8
+
9
+ import { accessFilter, assertAccess } from "@agent-native/core/sharing";
10
+ import { and, asc, eq, inArray } from "drizzle-orm";
11
+
12
+ import { getDb, schema } from "../server/db/index.js";
13
+ import { CrmAttributeValueError } from "../server/lib/record-fields.js";
14
+ import {
15
+ ATTRIBUTE_TYPE_SPECS,
16
+ type CrmAttributeType,
17
+ } from "../shared/crm-attributes.js";
18
+ import type {
19
+ CrmAttributeDefinition,
20
+ CrmAttributeOption,
21
+ } from "../shared/crm-contract.js";
22
+
23
+ export type CrmAttributeRow = typeof schema.crmFieldPolicies.$inferSelect;
24
+ export type CrmAttributeOptionRow =
25
+ typeof schema.crmAttributeOptions.$inferSelect;
26
+
27
+ /** Longest attribute slug we will mint; the column is untyped TEXT. */
28
+ const MAX_API_SLUG_LENGTH = 64;
29
+
30
+ // The single `attributeType -> value_type` mapping lives in
31
+ // `shared/crm-attributes.ts` so server-side writers (native adapter, mirror)
32
+ // can use it too without an actions/ -> server/ import.
33
+ export { legacyValueTypeFor } from "../shared/crm-attributes.js";
34
+
35
+ /**
36
+ * Immutable snake_case slug for a title. Minted once at create time: the slug
37
+ * is the `field_name` every stored value row is keyed by, so renaming it would
38
+ * orphan history rather than rename anything.
39
+ */
40
+ export function toApiSlug(title: string): string {
41
+ const slug = title
42
+ .normalize("NFKD")
43
+ .replace(/[\u0300-\u036f]/g, "")
44
+ .toLowerCase()
45
+ .replace(/[^a-z0-9]+/g, "_")
46
+ .replace(/^_+|_+$/g, "")
47
+ .slice(0, MAX_API_SLUG_LENGTH)
48
+ .replace(/_+$/g, "");
49
+ if (!slug) {
50
+ throw new CrmAttributeValueError(
51
+ "crm-attribute-slug",
52
+ `Cannot derive an attribute slug from the title "${title}". Use a title containing letters or digits.`,
53
+ );
54
+ }
55
+ return slug;
56
+ }
57
+
58
+ export function requireAttributeType(type: CrmAttributeType): void {
59
+ if (ATTRIBUTE_TYPE_SPECS[type].systemOnly) {
60
+ throw new CrmAttributeValueError(
61
+ "crm-attribute-system-type",
62
+ `Attribute type "${type}" is created by the system only and cannot be authored. Use a different type.`,
63
+ );
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Stored JSON config, or a typed error. An unreadable blob is NOT silently
69
+ * reported as an empty config — an attribute whose currency code cannot be read
70
+ * must fail loudly, not render as "no currency configured".
71
+ */
72
+ export function parseAttributeJson(
73
+ attributeId: string,
74
+ column: "config_json" | "fill_config_json",
75
+ raw: string,
76
+ ): Record<string, unknown> {
77
+ let parsed: unknown;
78
+ try {
79
+ parsed = JSON.parse(raw);
80
+ } catch {
81
+ throw new CrmAttributeValueError(
82
+ "crm-attribute-config-unreadable",
83
+ `Attribute ${attributeId} has an unreadable ${column}. Repair it before reading or editing this attribute.`,
84
+ );
85
+ }
86
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
87
+ throw new CrmAttributeValueError(
88
+ "crm-attribute-config-unreadable",
89
+ `Attribute ${attributeId} has a non-object ${column}. Repair it before reading or editing this attribute.`,
90
+ );
91
+ }
92
+ return parsed as Record<string, unknown>;
93
+ }
94
+
95
+ export function toAttributeOption(
96
+ row: CrmAttributeOptionRow,
97
+ ): CrmAttributeOption {
98
+ return {
99
+ id: row.id,
100
+ value: row.value,
101
+ title: row.title,
102
+ ...(row.color === null ? {} : { color: row.color }),
103
+ position: row.position,
104
+ archived: row.archived,
105
+ targetDays: row.targetDays,
106
+ celebrate: row.celebrate,
107
+ };
108
+ }
109
+
110
+ export function toAttributeDefinition(
111
+ row: CrmAttributeRow,
112
+ options: CrmAttributeOption[] = [],
113
+ ): CrmAttributeDefinition {
114
+ return {
115
+ id: row.id,
116
+ connectionId: row.connectionId,
117
+ target: row.target,
118
+ // `object_type` is populated for every attribute, including list ones where
119
+ // it mirrors `target_id`; `target_id` is null on rows written by adapters
120
+ // that predate the typed surface.
121
+ targetId: row.targetId ?? row.objectType,
122
+ // Same relationship in reverse: `field_name` is the NOT NULL column the
123
+ // value writer keys on, and the migration seeded `api_slug` from it.
124
+ apiSlug: row.apiSlug ?? row.fieldName,
125
+ label: row.label,
126
+ ...(row.description === null ? {} : { description: row.description }),
127
+ attributeType: row.attributeType,
128
+ multi: row.multi,
129
+ authority: row.authority,
130
+ historyTracked: row.historyTracked,
131
+ uniqueValue: row.uniqueValue,
132
+ archived: row.archived,
133
+ position: row.position,
134
+ inverseAttributeId: row.inverseAttributeId,
135
+ fillMode: row.fillMode,
136
+ fillConfig: parseAttributeJson(
137
+ row.id,
138
+ "fill_config_json",
139
+ row.fillConfigJson,
140
+ ),
141
+ config: parseAttributeJson(row.id, "config_json", row.configJson),
142
+ options,
143
+ storagePolicy: row.storagePolicy,
144
+ sensitive: row.sensitive,
145
+ readable: row.readable,
146
+ createable: row.createable,
147
+ updateable: row.updateable,
148
+ required: row.required,
149
+ };
150
+ }
151
+
152
+ /** Access-scoped options for a set of attributes, keyed by attribute id. */
153
+ export async function loadAttributeOptions(input: {
154
+ attributeIds: string[];
155
+ includeArchived: boolean;
156
+ }): Promise<Map<string, CrmAttributeOption[]>> {
157
+ const byAttribute = new Map<string, CrmAttributeOption[]>();
158
+ if (input.attributeIds.length === 0) return byAttribute;
159
+ const rows = await getDb()
160
+ .select()
161
+ .from(schema.crmAttributeOptions)
162
+ .where(
163
+ and(
164
+ inArray(schema.crmAttributeOptions.attributeId, input.attributeIds),
165
+ ...(input.includeArchived
166
+ ? []
167
+ : [eq(schema.crmAttributeOptions.archived, false)]),
168
+ accessFilter(
169
+ schema.crmAttributeOptions,
170
+ schema.crmAttributeOptionShares,
171
+ ),
172
+ ),
173
+ )
174
+ .orderBy(
175
+ asc(schema.crmAttributeOptions.position),
176
+ asc(schema.crmAttributeOptions.value),
177
+ );
178
+ for (const row of rows) {
179
+ const list = byAttribute.get(row.attributeId) ?? [];
180
+ list.push(toAttributeOption(row));
181
+ byAttribute.set(row.attributeId, list);
182
+ }
183
+ return byAttribute;
184
+ }
185
+
186
+ /**
187
+ * Load one attribute the caller may edit. `assertAccess` is the authority on
188
+ * the decision; the row is re-read through `accessFilter` so a caller can never
189
+ * observe a row the list surface would hide.
190
+ */
191
+ export async function requireEditableAttribute(
192
+ attributeId: string,
193
+ ): Promise<CrmAttributeRow> {
194
+ await assertAccess("crm-field-policy", attributeId, "editor");
195
+ const [row] = await getDb()
196
+ .select()
197
+ .from(schema.crmFieldPolicies)
198
+ .where(
199
+ and(
200
+ eq(schema.crmFieldPolicies.id, attributeId),
201
+ accessFilter(
202
+ schema.crmFieldPolicies,
203
+ schema.crmFieldPolicyShares,
204
+ undefined,
205
+ "editor",
206
+ ),
207
+ ),
208
+ )
209
+ .limit(1);
210
+ if (!row) {
211
+ throw new CrmAttributeValueError(
212
+ "crm-attribute-not-found",
213
+ `CRM attribute ${attributeId} was not found.`,
214
+ );
215
+ }
216
+ return row;
217
+ }
218
+
219
+ export { CrmAttributeValueError };
@@ -0,0 +1,69 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import type { ActionRunContext } from "@agent-native/core/action";
4
+ import type { AccessContext } from "@agent-native/core/sharing";
5
+
6
+ import type { CrmDashboardConfig } from "../server/db/index.js";
7
+
8
+ export const CRM_PIPELINE_DASHBOARD_TITLE = "Pipeline";
9
+ export const CRM_PIPELINE_PROGRAM_NAME = "crm-pipeline-by-stage";
10
+ export const CRM_PIPELINE_PROGRAM_TITLE = "CRM pipeline by stage";
11
+ export const CRM_PIPELINE_REFRESH_TTL_MS = 5 * 60_000;
12
+
13
+ export const CRM_PIPELINE_PROGRAM_COLUMNS = [
14
+ { name: "stage", type: "string" },
15
+ { name: "pipelineValue", type: "number" },
16
+ { name: "opportunities", type: "number" },
17
+ ] as const;
18
+
19
+ export const CRM_PIPELINE_PROGRAM_CODE = `const result = await appAction("get-crm-pipeline-data");
20
+ emit(result.rows, ${JSON.stringify(CRM_PIPELINE_PROGRAM_COLUMNS, null, 2)});`;
21
+
22
+ export function requireDashboardAccess(
23
+ ctx?: ActionRunContext,
24
+ ): AccessContext & { userEmail: string } {
25
+ const userEmail = ctx?.userEmail?.trim().toLowerCase();
26
+ if (!userEmail) {
27
+ throw new Error("CRM dashboards require an authenticated user.");
28
+ }
29
+ return { userEmail, orgId: ctx?.orgId ?? undefined };
30
+ }
31
+
32
+ export function crmPipelineDashboardId(ctx: AccessContext): string {
33
+ const owner = ctx.userEmail?.trim().toLowerCase();
34
+ if (!owner) throw new Error("CRM dashboards require an authenticated user.");
35
+ const scope = `${ctx.orgId ?? "personal"}:${owner}`;
36
+ return `crm-pipeline-${createHash("sha256").update(scope).digest("hex").slice(0, 24)}`;
37
+ }
38
+
39
+ export function createPipelineDashboardConfig(
40
+ programId: string,
41
+ ): CrmDashboardConfig {
42
+ const query = JSON.stringify({ programId });
43
+ return {
44
+ version: 1,
45
+ panels: [
46
+ {
47
+ id: "pipeline-total",
48
+ title: "Pipeline value",
49
+ source: "program",
50
+ query,
51
+ chartType: "metric",
52
+ },
53
+ {
54
+ id: "pipeline-by-stage",
55
+ title: "Pipeline by stage",
56
+ source: "program",
57
+ query,
58
+ chartType: "bar",
59
+ },
60
+ {
61
+ id: "pipeline-table",
62
+ title: "Stage detail",
63
+ source: "program",
64
+ query,
65
+ chartType: "table",
66
+ },
67
+ ],
68
+ };
69
+ }
@@ -0,0 +1,15 @@
1
+ import { initDataPrograms } from "@agent-native/core/data-programs";
2
+
3
+ import { CRM_APP_ID } from "../server/lib/provider-api.js";
4
+ import getCrmPipelineData from "./get-crm-pipeline-data.js";
5
+
6
+ export function getCrmDataProgramActions() {
7
+ return { "get-crm-pipeline-data": getCrmPipelineData };
8
+ }
9
+
10
+ export function initCrmDataPrograms(): void {
11
+ initDataPrograms({
12
+ appId: CRM_APP_ID,
13
+ getActions: getCrmDataProgramActions,
14
+ });
15
+ }