@agent-native/core 0.123.0 → 0.123.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 (334) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +24 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/templates-meta.ts +12 -0
  5. package/corpus/core/src/deploy/build.ts +8 -0
  6. package/corpus/core/src/server/builder-browser.ts +76 -0
  7. package/corpus/core/src/server/core-routes-plugin.ts +15 -5
  8. package/corpus/templates/assets/app/global.css +0 -4
  9. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +195 -167
  10. package/corpus/templates/assets/changelog/2026-07-23-drafts-preview-panel-now-fills-the-full-width-with-a-large-p.md +6 -0
  11. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
  12. package/corpus/templates/content/actions/_builder-cms-write-client.ts +55 -9
  13. package/corpus/templates/content/actions/_database-source-utils.ts +756 -182
  14. package/corpus/templates/content/actions/change-content-database-source-role.ts +21 -20
  15. package/corpus/templates/content/actions/execute-builder-source-execution.ts +49 -8
  16. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +42 -9
  17. package/corpus/templates/content/actions/prepare-builder-source-review.ts +88 -90
  18. package/corpus/templates/content/actions/preview-builder-source-review.ts +4 -3
  19. package/corpus/templates/content/actions/refresh-content-database-source.ts +36 -9
  20. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +28 -24
  21. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +62 -12
  22. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +91 -16
  23. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +15 -1
  24. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +234 -44
  25. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +194 -16
  26. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +95 -42
  27. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +4 -2
  28. package/corpus/templates/content/app/components/editor/extensions/CodeBlockNode.tsx +10 -166
  29. package/corpus/templates/content/app/components/editor/extensions/VideoBlock.tsx +32 -8
  30. package/corpus/templates/content/app/components/editor/extensions/VideoNode.ts +14 -0
  31. package/corpus/templates/content/app/components/editor/registrySlashItems.ts +5 -1
  32. package/corpus/templates/content/app/hooks/use-content-database.ts +59 -2
  33. package/corpus/templates/content/app/hooks/use-documents.ts +18 -26
  34. package/corpus/templates/content/app/root.tsx +1 -6
  35. package/corpus/templates/content/changelog/2026-07-24-builder-can-now-publish-approved-updates-to-entries-that-are.md +6 -0
  36. package/corpus/templates/content/changelog/2026-07-24-builder-collection-loading-no-longer-restarts-from-the-begin.md +6 -0
  37. package/corpus/templates/content/changelog/2026-07-24-builder-connections-now-complete-securely-from-netlify-deplo.md +6 -0
  38. package/corpus/templates/content/changelog/2026-07-24-builder-draft-creation-now-scopes-execution-to-the-selected-.md +6 -0
  39. package/corpus/templates/content/changelog/2026-07-24-builder-review-now-distinguishes-preparing-a-safe-dry-run-from.md +6 -0
  40. package/corpus/templates/content/changelog/2026-07-24-builder-source-imports-now-recover-from-a-transient-paginati.md +6 -0
  41. package/corpus/templates/content/changelog/2026-07-24-builder-source-refresh-now-preserves-the-provider-bound-fiel.md +6 -0
  42. package/corpus/templates/content/changelog/2026-07-24-builder-update-reviews-now-open-reliably-on-large-collection.md +6 -0
  43. package/corpus/templates/content/changelog/2026-07-24-builder-write-policy-controls-now-reflect-successful-changes.md +6 -0
  44. package/corpus/templates/content/changelog/2026-07-24-date-properties-now-save-reliably-when-selected-with-the-nat.md +6 -0
  45. package/corpus/templates/content/changelog/2026-07-24-editor-slash-search-now-keeps-multi-word-block-names-open.md +6 -0
  46. package/corpus/templates/content/changelog/2026-07-24-exact-editor-slash-commands-now-execute-on-enter-even-if-the.md +6 -0
  47. package/corpus/templates/content/changelog/2026-07-24-new-collaborative-pages-now-save-their-first-edits.md +6 -0
  48. package/corpus/templates/content/changelog/2026-07-24-new-local-builder-rows-no-longer-remain-locked-behind-a-none.md +6 -0
  49. package/corpus/templates/content/changelog/2026-07-24-opening-or-closing-a-collaborative-document-no-longer-lets-u.md +6 -0
  50. package/corpus/templates/content/changelog/2026-07-24-selected-text-now-opens-the-link-editor-with-command-k-or-co.md +6 -0
  51. package/corpus/templates/content/changelog/2026-07-24-slash-menu-block-choices-no-longer-stall-pointer-interaction.md +6 -0
  52. package/corpus/templates/content/changelog/2026-07-24-uploaded-and-embedded-media-now-stay-intact-while-collaborative-document-drafts-save.md +6 -0
  53. package/corpus/templates/content/changelog/2026-07-24-video-link-insertion-now-keeps-its-source-panel-open-while-t.md +6 -0
  54. package/corpus/templates/content/changelog/2026-07-26-builder-publishing-now-shows-safe-validation-details-instead.md +6 -0
  55. package/corpus/templates/content/changelog/2026-07-26-builder-reviews-stay-responsive-on-large-collections-and-pre.md +6 -0
  56. package/corpus/templates/content/changelog/2026-07-26-collaborative-documents-no-longer-flash-duplicated-recent-bl.md +6 -0
  57. package/corpus/templates/content/changelog/2026-07-26-corrected-builder-updates-are-now-reviewed-separately-from-f.md +6 -0
  58. package/corpus/templates/content/changelog/2026-07-26-documents-now-remain-read-only-until-their-live-collaborativ.md +6 -0
  59. package/corpus/templates/content/changelog/2026-07-26-slash-command-menus-now-stay-reachable-when-editing-near-the.md +6 -0
  60. package/corpus/templates/content/changelog/2026-07-26-the-slash-menu-now-shows-one-clear-callout-choice-instead-of.md +6 -0
  61. package/corpus/templates/content/shared/api.ts +1 -0
  62. package/corpus/templates/content/shared/builder-mdx.ts +73 -16
  63. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
  64. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
  65. package/corpus/templates/crm/.agents/skills/crm/SKILL.md +205 -0
  66. package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +220 -0
  67. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
  68. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +228 -0
  69. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +178 -0
  70. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
  71. package/corpus/templates/crm/.env.example +5 -0
  72. package/corpus/templates/crm/AGENTS.md +82 -0
  73. package/corpus/templates/crm/CHANGELOG.md +25 -0
  74. package/corpus/templates/crm/DEVELOPING.md +66 -0
  75. package/corpus/templates/crm/README.md +122 -0
  76. package/corpus/templates/crm/_gitignore +12 -0
  77. package/corpus/templates/crm/actions/_crm-action-utils.ts +98 -0
  78. package/corpus/templates/crm/actions/_crm-attribute-utils.ts +219 -0
  79. package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
  80. package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
  81. package/corpus/templates/crm/actions/_crm-list-utils.ts +775 -0
  82. package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
  83. package/corpus/templates/crm/actions/add-crm-record-to-list.ts +164 -0
  84. package/corpus/templates/crm/actions/apply-crm-proposals.ts +291 -0
  85. package/corpus/templates/crm/actions/archive-crm-attribute.ts +69 -0
  86. package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
  87. package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
  88. package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
  89. package/corpus/templates/crm/actions/create-crm-attribute.ts +350 -0
  90. package/corpus/templates/crm/actions/create-crm-list.ts +194 -0
  91. package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
  92. package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
  93. package/corpus/templates/crm/actions/delete-crm-saved-view.ts +68 -0
  94. package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
  95. package/corpus/templates/crm/actions/estimate-crm-enrichment.ts +229 -0
  96. package/corpus/templates/crm/actions/find-crm-duplicates.ts +87 -0
  97. package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
  98. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +35 -0
  99. package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
  100. package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
  101. package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
  102. package/corpus/templates/crm/actions/get-crm-record-page.ts +287 -0
  103. package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
  104. package/corpus/templates/crm/actions/get-crm-workspace.ts +364 -0
  105. package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
  106. package/corpus/templates/crm/actions/list-crm-attributes.ts +71 -0
  107. package/corpus/templates/crm/actions/list-crm-connections.ts +68 -0
  108. package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
  109. package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
  110. package/corpus/templates/crm/actions/list-crm-enrichment-slots.ts +28 -0
  111. package/corpus/templates/crm/actions/list-crm-list-entries.ts +192 -0
  112. package/corpus/templates/crm/actions/list-crm-lists.ts +80 -0
  113. package/corpus/templates/crm/actions/list-crm-proposals.ts +55 -0
  114. package/corpus/templates/crm/actions/list-crm-record-field-history.ts +201 -0
  115. package/corpus/templates/crm/actions/list-crm-record-values.ts +200 -0
  116. package/corpus/templates/crm/actions/list-crm-records.ts +103 -0
  117. package/corpus/templates/crm/actions/list-crm-saved-views.ts +66 -0
  118. package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
  119. package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
  120. package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
  121. package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
  122. package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
  123. package/corpus/templates/crm/actions/manage-crm-attribute-option.ts +272 -0
  124. package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
  125. package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
  126. package/corpus/templates/crm/actions/merge-crm-records.ts +691 -0
  127. package/corpus/templates/crm/actions/navigate.ts +54 -0
  128. package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
  129. package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
  130. package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
  131. package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
  132. package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
  133. package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
  134. package/corpus/templates/crm/actions/remove-crm-list-entry.ts +79 -0
  135. package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
  136. package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
  137. package/corpus/templates/crm/actions/run-crm-attribute-fill.ts +443 -0
  138. package/corpus/templates/crm/actions/run-crm-enrichment.ts +647 -0
  139. package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
  140. package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +265 -0
  141. package/corpus/templates/crm/actions/run.ts +7 -0
  142. package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
  143. package/corpus/templates/crm/actions/save-crm-saved-view.ts +296 -0
  144. package/corpus/templates/crm/actions/sync-crm.ts +146 -0
  145. package/corpus/templates/crm/actions/update-crm-attribute.ts +111 -0
  146. package/corpus/templates/crm/actions/update-crm-list-entry.ts +102 -0
  147. package/corpus/templates/crm/actions/update-crm-list.ts +97 -0
  148. package/corpus/templates/crm/actions/update-crm-record.ts +543 -0
  149. package/corpus/templates/crm/actions/view-screen.ts +324 -0
  150. package/corpus/templates/crm/agent-native.app-skill.json +69 -0
  151. package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
  152. package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +78 -0
  153. package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +220 -0
  154. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +414 -0
  155. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +648 -0
  156. package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +488 -0
  157. package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +232 -0
  158. package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +93 -0
  159. package/corpus/templates/crm/app/components/crm/Surface.tsx +97 -0
  160. package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
  161. package/corpus/templates/crm/app/components/crm/board/CrmBoard.tsx +988 -0
  162. package/corpus/templates/crm/app/components/crm/board/board-model.ts +399 -0
  163. package/corpus/templates/crm/app/components/crm/board/entry-filter.ts +150 -0
  164. package/corpus/templates/crm/app/components/crm/board/view-draft.ts +227 -0
  165. package/corpus/templates/crm/app/components/crm/grid/CrmGrid.tsx +1033 -0
  166. package/corpus/templates/crm/app/components/crm/grid/GridCell.tsx +620 -0
  167. package/corpus/templates/crm/app/components/crm/grid/clipboard.ts +84 -0
  168. package/corpus/templates/crm/app/components/crm/grid/model.ts +304 -0
  169. package/corpus/templates/crm/app/components/crm/grid/navigation.ts +220 -0
  170. package/corpus/templates/crm/app/components/crm/grid/query.ts +214 -0
  171. package/corpus/templates/crm/app/components/crm/record/AttributePanel.tsx +192 -0
  172. package/corpus/templates/crm/app/components/crm/record/FieldHistory.tsx +179 -0
  173. package/corpus/templates/crm/app/components/crm/record/RecordHeader.tsx +103 -0
  174. package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +235 -0
  175. package/corpus/templates/crm/app/components/crm/record/RecordTabs.tsx +206 -0
  176. package/corpus/templates/crm/app/components/crm/record/field-editors.tsx +245 -0
  177. package/corpus/templates/crm/app/components/crm/record/record-data.ts +603 -0
  178. package/corpus/templates/crm/app/components/crm/settings/AdvancedSettings.tsx +49 -0
  179. package/corpus/templates/crm/app/components/crm/settings/AttributeOptionsEditor.tsx +441 -0
  180. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +169 -0
  181. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +935 -0
  182. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +339 -0
  183. package/corpus/templates/crm/app/components/crm/settings/settings-admin.ts +377 -0
  184. package/corpus/templates/crm/app/components/crm/shared/AttributeValueParts.tsx +69 -0
  185. package/corpus/templates/crm/app/components/crm/shared/attribute-value.ts +591 -0
  186. package/corpus/templates/crm/app/components/layout/CrmCommandMenu.tsx +556 -0
  187. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +130 -0
  188. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +407 -0
  189. package/corpus/templates/crm/app/components/layout/command-palette-search.ts +126 -0
  190. package/corpus/templates/crm/app/components/layout/crm-ui-intents.ts +41 -0
  191. package/corpus/templates/crm/app/components/layout/sidebar-collapse.ts +40 -0
  192. package/corpus/templates/crm/app/components/layout/sidebar-lists.ts +109 -0
  193. package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +1 -0
  194. package/corpus/templates/crm/app/components/ui/badge.tsx +1 -0
  195. package/corpus/templates/crm/app/components/ui/button.tsx +1 -0
  196. package/corpus/templates/crm/app/components/ui/card.tsx +1 -0
  197. package/corpus/templates/crm/app/components/ui/checkbox.tsx +1 -0
  198. package/corpus/templates/crm/app/components/ui/dialog.tsx +1 -0
  199. package/corpus/templates/crm/app/components/ui/dropdown-menu.tsx +1 -0
  200. package/corpus/templates/crm/app/components/ui/hover-card.tsx +1 -0
  201. package/corpus/templates/crm/app/components/ui/input.tsx +1 -0
  202. package/corpus/templates/crm/app/components/ui/label.tsx +1 -0
  203. package/corpus/templates/crm/app/components/ui/popover.tsx +1 -0
  204. package/corpus/templates/crm/app/components/ui/select.tsx +1 -0
  205. package/corpus/templates/crm/app/components/ui/sheet.tsx +1 -0
  206. package/corpus/templates/crm/app/components/ui/skeleton.tsx +1 -0
  207. package/corpus/templates/crm/app/components/ui/switch.tsx +1 -0
  208. package/corpus/templates/crm/app/components/ui/table.tsx +1 -0
  209. package/corpus/templates/crm/app/components/ui/tabs.tsx +1 -0
  210. package/corpus/templates/crm/app/components/ui/textarea.tsx +1 -0
  211. package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +17 -0
  212. package/corpus/templates/crm/app/components/ui/tooltip.tsx +1 -0
  213. package/corpus/templates/crm/app/design-system.ts +3 -0
  214. package/corpus/templates/crm/app/entry.client.tsx +19 -0
  215. package/corpus/templates/crm/app/entry.server.tsx +10 -0
  216. package/corpus/templates/crm/app/global.css +167 -0
  217. package/corpus/templates/crm/app/hooks/use-keyboard-shortcuts.ts +206 -0
  218. package/corpus/templates/crm/app/hooks/use-navigation-state.ts +48 -0
  219. package/corpus/templates/crm/app/i18n/en-US.ts +684 -0
  220. package/corpus/templates/crm/app/i18n/index.ts +34 -0
  221. package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
  222. package/corpus/templates/crm/app/lib/navigation.ts +17 -0
  223. package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
  224. package/corpus/templates/crm/app/lib/types.ts +172 -0
  225. package/corpus/templates/crm/app/lib/utils.ts +1 -0
  226. package/corpus/templates/crm/app/root.tsx +93 -0
  227. package/corpus/templates/crm/app/routes/_index.tsx +26 -0
  228. package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
  229. package/corpus/templates/crm/app/routes/agent.tsx +5 -0
  230. package/corpus/templates/crm/app/routes/ask.tsx +53 -0
  231. package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
  232. package/corpus/templates/crm/app/routes/lists.tsx +228 -0
  233. package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
  234. package/corpus/templates/crm/app/routes/people.tsx +41 -0
  235. package/corpus/templates/crm/app/routes/proposals.tsx +388 -0
  236. package/corpus/templates/crm/app/routes/records.$recordId.tsx +53 -0
  237. package/corpus/templates/crm/app/routes/records.tsx +64 -0
  238. package/corpus/templates/crm/app/routes/settings.tsx +118 -0
  239. package/corpus/templates/crm/app/routes/setup.tsx +308 -0
  240. package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
  241. package/corpus/templates/crm/app/routes/views.tsx +683 -0
  242. package/corpus/templates/crm/app/routes.ts +19 -0
  243. package/corpus/templates/crm/app/vite-env.d.ts +6 -0
  244. 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
  245. package/corpus/templates/crm/changelog/2026-07-26-crm-now-finds-likely-duplicate-records-and-tells-you-exactly.md +6 -0
  246. package/corpus/templates/crm/changelog/2026-07-26-enrichment-now-shows-what-a-run-will-cost-before-it-starts-g.md +6 -0
  247. package/corpus/templates/crm/changelog/2026-07-26-lists-turn-any-set-of-records-into-a-pipeline-with-its-own-s.md +6 -0
  248. package/corpus/templates/crm/changelog/2026-07-26-moving-a-record-or-list-entry-into-a-stage-that-was-retired-.md +6 -0
  249. package/corpus/templates/crm/changelog/2026-07-26-opening-a-record-now-shows-one-page-with-its-fields-the-list.md +6 -0
  250. 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
  251. package/corpus/templates/crm/changelog/2026-07-26-provider-record-edits-now-show-an-exact-before-after-diff-an.md +6 -0
  252. package/corpus/templates/crm/changelog/2026-07-26-record-page-attribute-labels-now-read-in-full-instead-of-bei.md +6 -0
  253. package/corpus/templates/crm/changelog/2026-07-26-records-now-use-typed-fields-currency-dates-ratings-status-s.md +6 -0
  254. package/corpus/templates/crm/changelog/2026-07-26-retyping-a-value-on-the-record-page-now-replaces-it-instead-.md +6 -0
  255. package/corpus/templates/crm/changelog/2026-07-26-settings-now-has-fields-and-lists-tabs-for-adding-fields-edi.md +6 -0
  256. package/corpus/templates/crm/changelog/2026-07-26-the-proposal-review-now-shows-the-field-that-actually-change.md +6 -0
  257. package/corpus/templates/crm/changelog/2026-07-26-the-record-grid-is-now-a-real-spreadsheet-filter-sort-and-pa.md +6 -0
  258. package/corpus/templates/crm/components.json +20 -0
  259. package/corpus/templates/crm/docs/architecture/crm-contract.md +214 -0
  260. package/corpus/templates/crm/docs/features/README.md +55 -0
  261. package/corpus/templates/crm/docs/features/f1-records-and-fields.mdx +52 -0
  262. package/corpus/templates/crm/docs/features/f2-lists-pipelines-and-views.mdx +48 -0
  263. package/corpus/templates/crm/docs/features/f3-work-and-record-quality.mdx +40 -0
  264. package/corpus/templates/crm/docs/features/f4-evidence-and-chat.mdx +47 -0
  265. package/corpus/templates/crm/learnings.defaults.md +5 -0
  266. package/corpus/templates/crm/netlify.toml +11 -0
  267. package/corpus/templates/crm/package.json +61 -0
  268. package/corpus/templates/crm/public/favicon.svg +1 -0
  269. package/corpus/templates/crm/public/icon-180.svg +1 -0
  270. package/corpus/templates/crm/public/manifest.json +11 -0
  271. package/corpus/templates/crm/react-router.config.ts +7 -0
  272. package/corpus/templates/crm/server/crm/adapter.ts +32 -0
  273. package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
  274. package/corpus/templates/crm/server/crm/crm-mirror.ts +721 -0
  275. package/corpus/templates/crm/server/crm/field-policy-attributes.ts +48 -0
  276. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1146 -0
  277. package/corpus/templates/crm/server/crm/native-adapter.ts +1810 -0
  278. package/corpus/templates/crm/server/crm/provider-record-link.ts +264 -0
  279. package/corpus/templates/crm/server/crm/read-through.ts +207 -0
  280. package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
  281. package/corpus/templates/crm/server/db/crm-store.ts +1112 -0
  282. package/corpus/templates/crm/server/db/index.ts +191 -0
  283. package/corpus/templates/crm/server/db/schema.ts +647 -0
  284. package/corpus/templates/crm/server/lib/crm-query.ts +1558 -0
  285. package/corpus/templates/crm/server/lib/dedupe.ts +714 -0
  286. package/corpus/templates/crm/server/lib/enrichment-cost.ts +381 -0
  287. package/corpus/templates/crm/server/lib/enrichment-slots.ts +519 -0
  288. package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
  289. package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
  290. package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +94 -0
  291. package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +39 -0
  292. package/corpus/templates/crm/server/lib/intelligence/summary.ts +19 -0
  293. package/corpus/templates/crm/server/lib/lifecycle.ts +634 -0
  294. package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
  295. package/corpus/templates/crm/server/middleware/auth.ts +4 -0
  296. package/corpus/templates/crm/server/plugins/agent-chat.ts +65 -0
  297. package/corpus/templates/crm/server/plugins/auth.ts +14 -0
  298. package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
  299. package/corpus/templates/crm/server/plugins/db.ts +738 -0
  300. package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
  301. package/corpus/templates/crm/shared/crm-attributes.ts +469 -0
  302. package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
  303. package/corpus/templates/crm/shared/crm-contract.ts +417 -0
  304. package/corpus/templates/crm/shared/crm-navigation.ts +190 -0
  305. package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
  306. package/corpus/templates/crm/ssr-entry.ts +11 -0
  307. package/corpus/templates/crm/tsconfig.json +24 -0
  308. package/corpus/templates/crm/vite.config.ts +15 -0
  309. package/corpus/templates/crm/vitest.config.ts +5 -0
  310. package/corpus/templates/design/app/i18n-data.ts +12 -12
  311. package/corpus/templates/design/changelog/2026-07-26-apply-design-updates-from-a-centered-top-toolbar-without-cov.md +6 -0
  312. package/corpus/toolkit/CHANGELOG.md +6 -0
  313. package/corpus/toolkit/package.json +1 -1
  314. package/corpus/toolkit/src/editor/useCollabReconcile.ts +14 -1
  315. package/dist/cli/templates-meta.d.ts.map +1 -1
  316. package/dist/cli/templates-meta.js +12 -0
  317. package/dist/cli/templates-meta.js.map +1 -1
  318. package/dist/collab/routes.d.ts +2 -2
  319. package/dist/deploy/build.js +6 -0
  320. package/dist/deploy/build.js.map +1 -1
  321. package/dist/observability/routes.d.ts +3 -3
  322. package/dist/server/builder-browser.d.ts +13 -0
  323. package/dist/server/builder-browser.d.ts.map +1 -1
  324. package/dist/server/builder-browser.js +59 -0
  325. package/dist/server/builder-browser.js.map +1 -1
  326. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  327. package/dist/server/core-routes-plugin.js +10 -5
  328. package/dist/server/core-routes-plugin.js.map +1 -1
  329. package/dist/server/transcribe-voice.d.ts +1 -1
  330. package/package.json +2 -2
  331. package/src/cli/templates-meta.ts +12 -0
  332. package/src/deploy/build.ts +8 -0
  333. package/src/server/builder-browser.ts +76 -0
  334. package/src/server/core-routes-plugin.ts +15 -5
@@ -0,0 +1,417 @@
1
+ import type { CrmAttributeType } from "./crm-attributes.js";
2
+
3
+ export {
4
+ ATTRIBUTE_TYPE_SPECS,
5
+ CRM_ATTRIBUTE_TYPES,
6
+ type CrmAttributeType,
7
+ } from "./crm-attributes.js";
8
+
9
+ export const CRM_PROVIDERS = [
10
+ "hubspot",
11
+ "salesforce",
12
+ "native",
13
+ "custom",
14
+ ] as const;
15
+
16
+ export type CrmProvider = (typeof CRM_PROVIDERS)[number];
17
+
18
+ /**
19
+ * `hybrid` is DEPRECATED. Per-field `authority` on an attribute expresses
20
+ * everything hybrid promised, per field instead of per connection. The enum
21
+ * value stays so existing rows remain valid — treat it as `mirrored`
22
+ * everywhere and do not offer it in new UI or docs.
23
+ */
24
+ export const CRM_CONNECTION_MODES = ["connected", "hybrid", "native"] as const;
25
+
26
+ export type CrmConnectionMode = (typeof CRM_CONNECTION_MODES)[number];
27
+
28
+ export const CRM_OBJECT_KINDS = [
29
+ "account",
30
+ "person",
31
+ "opportunity",
32
+ "activity",
33
+ "task",
34
+ "custom",
35
+ ] as const;
36
+
37
+ export type CrmObjectKind = (typeof CRM_OBJECT_KINDS)[number];
38
+
39
+ export const CRM_FIELD_STORAGE_POLICIES = [
40
+ "mirrored",
41
+ "remote-only",
42
+ "redacted",
43
+ "derived-local",
44
+ "local-authoritative",
45
+ ] as const;
46
+
47
+ export type CrmFieldStoragePolicy = (typeof CRM_FIELD_STORAGE_POLICIES)[number];
48
+
49
+ export type CrmScalar = string | number | boolean | null;
50
+ export type CrmValue = CrmScalar | CrmScalar[] | { [key: string]: CrmValue };
51
+
52
+ export interface CrmConnectionRef {
53
+ connectionId: string;
54
+ provider: CrmProvider;
55
+ accountId?: string;
56
+ actorId?: string;
57
+ }
58
+
59
+ export interface CrmObjectRef extends CrmConnectionRef {
60
+ objectType: string;
61
+ kind: CrmObjectKind;
62
+ }
63
+
64
+ export interface CrmRecordRef extends CrmObjectRef {
65
+ remoteId: string;
66
+ localId?: string;
67
+ }
68
+
69
+ export interface CrmAccessScope {
70
+ key: string;
71
+ actorId?: string;
72
+ grantId?: string;
73
+ mode: "user" | "service-account" | "native";
74
+ objectReadable: boolean;
75
+ objectCreateable: boolean;
76
+ objectUpdateable: boolean;
77
+ objectDeleteable: boolean;
78
+ recordVisibility: "actor" | "cohort" | "workspace" | "unknown";
79
+ fieldPermissionsHash?: string;
80
+ sharingFingerprint?: string;
81
+ }
82
+
83
+ export interface CrmFieldDefinition {
84
+ name: string;
85
+ label: string;
86
+ valueType:
87
+ | "string"
88
+ | "number"
89
+ | "boolean"
90
+ | "date"
91
+ | "datetime"
92
+ | "currency"
93
+ | "percent"
94
+ | "enum"
95
+ | "multi-enum"
96
+ | "reference"
97
+ | "json";
98
+ storagePolicy: CrmFieldStoragePolicy;
99
+ sensitive: boolean;
100
+ readable: boolean;
101
+ createable: boolean;
102
+ updateable: boolean;
103
+ required: boolean;
104
+ options?: Array<{ value: string; label: string; active?: boolean }>;
105
+ referencedObjectType?: string;
106
+ /**
107
+ * The typed-attribute surface a field-policy writer should stamp onto
108
+ * `crm_field_policies`. Optional: providers that only discover the legacy
109
+ * `valueType` (HubSpot, Salesforce) leave this unset and the writer falls
110
+ * back to `text`, matching the column's own default.
111
+ */
112
+ attributeType?: CrmAttributeType;
113
+ multi?: boolean;
114
+ /** `{ currency: { code } }`, `{ reference: { allowedObjectTypes } }`, … */
115
+ config?: Record<string, unknown>;
116
+ }
117
+
118
+ export const CRM_ATTRIBUTE_AUTHORITIES = [
119
+ "provider",
120
+ "derived-local",
121
+ "local-authoritative",
122
+ ] as const;
123
+
124
+ export type CrmAttributeAuthority = (typeof CRM_ATTRIBUTE_AUTHORITIES)[number];
125
+
126
+ export const CRM_ATTRIBUTE_FILL_MODES = [
127
+ "agent-summarize",
128
+ "agent-classify",
129
+ "agent-research",
130
+ "formula",
131
+ ] as const;
132
+
133
+ export type CrmAttributeFillMode = (typeof CRM_ATTRIBUTE_FILL_MODES)[number];
134
+
135
+ export const CRM_ACTOR_TYPES = [
136
+ "user",
137
+ "agent",
138
+ "automation",
139
+ "provider",
140
+ "system",
141
+ ] as const;
142
+
143
+ export type CrmActorType = (typeof CRM_ACTOR_TYPES)[number];
144
+
145
+ export interface CrmAttributeOption {
146
+ id: string;
147
+ value: string;
148
+ title: string;
149
+ color?: string;
150
+ position: number;
151
+ archived: boolean;
152
+ /** `status` only — stage SLA in days. */
153
+ targetDays?: number | null;
154
+ /** `status` only. */
155
+ celebrate?: boolean;
156
+ }
157
+
158
+ /**
159
+ * A typed attribute. Persisted in `crm_field_policies`, which is the attribute
160
+ * table — there is no parallel attributes table.
161
+ */
162
+ export interface CrmAttributeDefinition {
163
+ id: string;
164
+ connectionId: string;
165
+ target: "object" | "list";
166
+ /** objectType for `object`, listId for `list`. */
167
+ targetId: string;
168
+ apiSlug: string;
169
+ label: string;
170
+ description?: string;
171
+ attributeType: CrmAttributeType;
172
+ multi: boolean;
173
+ authority: CrmAttributeAuthority;
174
+ historyTracked: boolean;
175
+ uniqueValue: boolean;
176
+ archived: boolean;
177
+ position: number;
178
+ inverseAttributeId?: string | null;
179
+ fillMode?: CrmAttributeFillMode | null;
180
+ /** Agent/formula fill configuration. Never auto-runs; manual trigger only. */
181
+ fillConfig?: Record<string, unknown>;
182
+ /** `{ currency: { code } }`, `{ reference: { allowedObjectTypes } }`, … */
183
+ config?: Record<string, unknown>;
184
+ options?: CrmAttributeOption[];
185
+ storagePolicy: CrmFieldStoragePolicy;
186
+ sensitive: boolean;
187
+ readable: boolean;
188
+ createable: boolean;
189
+ updateable: boolean;
190
+ required: boolean;
191
+ }
192
+
193
+ /**
194
+ * Lists are always local-authoritative, on every backend including HubSpot and
195
+ * Salesforce. An imported list keeps its provider origin for reference only.
196
+ */
197
+ export interface CrmListDefinition {
198
+ id: string;
199
+ connectionId: string;
200
+ name: string;
201
+ apiSlug: string;
202
+ parentObjectType: string;
203
+ description?: string;
204
+ defaultViewId?: string | null;
205
+ archived: boolean;
206
+ position: number;
207
+ source: "local" | "imported";
208
+ sourceRemoteId?: string | null;
209
+ lastSyncedAt?: string | null;
210
+ }
211
+
212
+ /**
213
+ * One record's membership in one list. A record may appear in the same list
214
+ * more than once — there is deliberately no unique `(listId, recordId)`.
215
+ */
216
+ export interface CrmListEntry {
217
+ id: string;
218
+ listId: string;
219
+ recordId: string;
220
+ position: number;
221
+ createdByActorType: CrmActorType;
222
+ createdByActorId?: string | null;
223
+ createdAt: string;
224
+ }
225
+
226
+ export interface CrmSavedViewDefinition {
227
+ id: string;
228
+ name: string;
229
+ description?: string;
230
+ viewKind: "table" | "board";
231
+ targetKind: "object" | "list";
232
+ /** objectType for `object`, listId for `list`. */
233
+ targetId?: string | null;
234
+ /** Board views only; must reference a `status` attribute. */
235
+ groupByAttributeId?: string | null;
236
+ columns: string[];
237
+ /**
238
+ * Filter condition values may be the literal `"@currentUser"`, resolved
239
+ * server-side against the calling actor — never resolved on the client.
240
+ */
241
+ filters: Record<string, unknown>;
242
+ sort: Array<{ field: string; direction: "asc" | "desc" }>;
243
+ /**
244
+ * Projection of the framework `visibility` column on `crm_saved_views`:
245
+ * `private` reads as personal, `org` as shared. There is no separate column.
246
+ */
247
+ audience: "personal" | "shared";
248
+ pinned: boolean;
249
+ }
250
+
251
+ export const CRM_CURRENT_USER_TOKEN = "@currentUser";
252
+
253
+ export interface CrmObjectDefinition extends CrmObjectRef {
254
+ label: string;
255
+ pluralLabel: string;
256
+ custom: boolean;
257
+ queryable: boolean;
258
+ searchable: boolean;
259
+ createable: boolean;
260
+ updateable: boolean;
261
+ deleteable: boolean;
262
+ fields: CrmFieldDefinition[];
263
+ }
264
+
265
+ export interface CrmRecord {
266
+ ref: CrmRecordRef;
267
+ displayName: string;
268
+ fields: Record<string, CrmValue>;
269
+ remoteRevision?: string;
270
+ remoteUpdatedAt?: string;
271
+ deleted: boolean;
272
+ accessScope: CrmAccessScope;
273
+ provenance: CrmProvenance[];
274
+ }
275
+
276
+ export interface CrmRelationship {
277
+ from: CrmRecordRef;
278
+ to: CrmRecordRef;
279
+ relationshipType: string;
280
+ label?: string;
281
+ inverseLabel?: string;
282
+ sourceField?: string;
283
+ }
284
+
285
+ export interface CrmProvenance {
286
+ provider: CrmProvider;
287
+ connectionId: string;
288
+ objectType: string;
289
+ remoteId: string;
290
+ fieldName?: string;
291
+ remoteRevision?: string;
292
+ observedAt: string;
293
+ evidenceRef?: string;
294
+ }
295
+
296
+ export interface CrmSyncScope {
297
+ objectType: string;
298
+ pipelineIds?: string[];
299
+ ownerIds?: string[];
300
+ recordIds?: string[];
301
+ associatedRecordIds?: string[];
302
+ updatedAfter?: string;
303
+ includeDeleted?: boolean;
304
+ }
305
+
306
+ export interface CrmSyncPage {
307
+ records: CrmRecord[];
308
+ relationships: CrmRelationship[];
309
+ nextCursor?: string;
310
+ complete: boolean;
311
+ }
312
+
313
+ export interface CrmMutation {
314
+ operation: "create" | "update" | "delete" | "associate" | "disassociate";
315
+ record: CrmRecordRef;
316
+ fields?: Record<string, CrmValue>;
317
+ relationship?: CrmRelationship;
318
+ expectedRemoteRevision?: string;
319
+ idempotencyKey: string;
320
+ }
321
+
322
+ export interface CrmMutationResult {
323
+ status: "applied" | "conflict" | "rejected";
324
+ record?: CrmRecord;
325
+ remoteRevision?: string;
326
+ message?: string;
327
+ }
328
+
329
+ export interface CrmAdapterCapabilities {
330
+ schemaDiscovery: boolean;
331
+ customObjects: boolean;
332
+ search: boolean;
333
+ incrementalSync: boolean;
334
+ deletedRecordSync: boolean;
335
+ conditionalMutations: boolean;
336
+ labeledRelationships: boolean;
337
+ perFieldPermissions: boolean;
338
+ perRecordPermissions: boolean;
339
+ }
340
+
341
+ export interface CrmAdapter {
342
+ readonly connection: CrmConnectionRef;
343
+ readonly capabilities: CrmAdapterCapabilities;
344
+ discoverObjects(): Promise<CrmObjectDefinition[]>;
345
+ describeObject(objectType: string): Promise<CrmObjectDefinition>;
346
+ syncPage(input: {
347
+ scope: CrmSyncScope;
348
+ fieldAllowList: string[];
349
+ cursor?: string;
350
+ limit: number;
351
+ }): Promise<CrmSyncPage>;
352
+ getRecord(input: {
353
+ record: CrmRecordRef;
354
+ fields: string[];
355
+ }): Promise<CrmRecord | null>;
356
+ search(input: {
357
+ objectTypes: string[];
358
+ query: string;
359
+ fields: string[];
360
+ limit: number;
361
+ cursor?: string;
362
+ }): Promise<CrmSyncPage>;
363
+ listRelationships(input: {
364
+ record: CrmRecordRef;
365
+ targetObjectTypes?: string[];
366
+ limit: number;
367
+ cursor?: string;
368
+ }): Promise<{
369
+ relationships: CrmRelationship[];
370
+ nextCursor?: string;
371
+ complete: boolean;
372
+ }>;
373
+ applyMutation(mutation: CrmMutation): Promise<CrmMutationResult>;
374
+ }
375
+
376
+ export const CRM_WRITE_DECISIONS = [
377
+ "execute",
378
+ "propose",
379
+ "require-approval",
380
+ "deny",
381
+ ] as const;
382
+
383
+ export type CrmWriteDecision = (typeof CRM_WRITE_DECISIONS)[number];
384
+
385
+ export interface CrmWritePolicyInput {
386
+ initiatedBy: "human" | "agent" | "automation";
387
+ target: "local" | "provider";
388
+ reversibility: "reversible" | "compensatable" | "destructive";
389
+ scope: "single-field" | "single-record" | "bulk";
390
+ risk: "routine" | "ownership" | "amount" | "stage" | "external-side-effect";
391
+ delegatedAuthority: boolean;
392
+ storedAutomationPolicy: boolean;
393
+ }
394
+
395
+ export function decideCrmWritePolicy(
396
+ input: CrmWritePolicyInput,
397
+ ): CrmWriteDecision {
398
+ if (
399
+ input.reversibility === "destructive" ||
400
+ input.scope === "bulk" ||
401
+ input.risk !== "routine"
402
+ ) {
403
+ return input.initiatedBy === "human" ? "execute" : "require-approval";
404
+ }
405
+
406
+ if (input.initiatedBy === "human") return "execute";
407
+
408
+ if (input.initiatedBy === "automation") {
409
+ if (input.target === "provider") return "propose";
410
+ return input.storedAutomationPolicy && input.delegatedAuthority
411
+ ? "execute"
412
+ : "deny";
413
+ }
414
+
415
+ if (input.target === "local") return "execute";
416
+ return "propose";
417
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * The CRM navigation vocabulary: which surfaces exist, the path each one lives
3
+ * at, and how to read the current selection back out of a path.
4
+ *
5
+ * The agent (`navigate`, `view-screen`) and the browser route hook have to mean
6
+ * the same thing by "the board for this list", so the view union, the path
7
+ * builder, and the selection parser live here rather than once per surface.
8
+ * Two copies of this map is how the agent ends up navigating somewhere the UI
9
+ * does not report back.
10
+ */
11
+
12
+ export const CRM_SETTINGS_SECTIONS = [
13
+ "connection",
14
+ "fields",
15
+ "lists",
16
+ "intelligence",
17
+ "advanced",
18
+ ] as const;
19
+
20
+ export type CrmSettingsSection = (typeof CRM_SETTINGS_SECTIONS)[number];
21
+
22
+ export const CRM_RECORD_KINDS = ["account", "person", "opportunity"] as const;
23
+
24
+ export type CrmRecordKind = (typeof CRM_RECORD_KINDS)[number];
25
+
26
+ export const CRM_VIEWS = [
27
+ "work",
28
+ "account",
29
+ "person",
30
+ "opportunity",
31
+ "records",
32
+ "record",
33
+ "lists",
34
+ "views",
35
+ "board",
36
+ "tasks",
37
+ "proposals",
38
+ "dashboard",
39
+ "ask",
40
+ "setup",
41
+ "settings",
42
+ ] as const;
43
+
44
+ export type CrmView = (typeof CRM_VIEWS)[number];
45
+
46
+ /**
47
+ * `board` shares the `/views` route: a board is a presentation mode of one
48
+ * saved view or list, not a route of its own. `viewFromPath` therefore never
49
+ * returns it — only `navigate` accepts it as a destination.
50
+ */
51
+ export const CRM_VIEW_PATHS: Record<Exclude<CrmView, "record">, string> = {
52
+ work: "/",
53
+ account: "/accounts",
54
+ person: "/people",
55
+ opportunity: "/opportunities",
56
+ records: "/records",
57
+ lists: "/lists",
58
+ views: "/views",
59
+ board: "/views",
60
+ tasks: "/tasks",
61
+ proposals: "/proposals",
62
+ dashboard: "/dashboard",
63
+ ask: "/ask",
64
+ setup: "/setup",
65
+ settings: "/settings",
66
+ };
67
+
68
+ export interface CrmNavigationTarget {
69
+ view?: string;
70
+ recordId?: string;
71
+ listId?: string;
72
+ viewId?: string;
73
+ dashboardId?: string;
74
+ kind?: CrmRecordKind;
75
+ query?: string;
76
+ settingsSection?: CrmSettingsSection;
77
+ }
78
+
79
+ export function viewFromPath(pathname: string): CrmView {
80
+ if (pathname.startsWith("/records/")) return "record";
81
+ if (pathname.startsWith("/records")) return "records";
82
+ if (pathname.startsWith("/accounts")) return "account";
83
+ if (pathname.startsWith("/people")) return "person";
84
+ if (pathname.startsWith("/opportunities")) return "opportunity";
85
+ if (pathname.startsWith("/lists")) return "lists";
86
+ if (pathname.startsWith("/tasks")) return "tasks";
87
+ if (pathname.startsWith("/proposals")) return "proposals";
88
+ if (pathname.startsWith("/views")) return "views";
89
+ if (pathname.startsWith("/dashboard")) return "dashboard";
90
+ if (pathname.startsWith("/ask")) return "ask";
91
+ if (pathname.startsWith("/setup")) return "setup";
92
+ if (pathname.startsWith("/settings")) return "settings";
93
+ return "work";
94
+ }
95
+
96
+ /**
97
+ * The complete path — including search — for a navigation target.
98
+ *
99
+ * A destination the UI cannot actually open is thrown, not silently degraded to
100
+ * an index page: "navigated to the board" while the board never opened is the
101
+ * failure mode the root CLAUDE.md forbids.
102
+ */
103
+ export function crmNavigationPath(target: CrmNavigationTarget): string {
104
+ if (target.view === "record") {
105
+ if (!target.recordId) {
106
+ throw new Error("recordId is required when navigating to a CRM record.");
107
+ }
108
+ return `/records/${encodeURIComponent(target.recordId)}`;
109
+ }
110
+ if (target.view === "settings" && target.settingsSection) {
111
+ return `/settings/${target.settingsSection}`;
112
+ }
113
+ if (target.view === "board" && !target.viewId && !target.listId) {
114
+ throw new Error(
115
+ "listId or viewId is required when navigating to a CRM board; /views on its own is the saved-view index.",
116
+ );
117
+ }
118
+
119
+ const params = new URLSearchParams();
120
+ let base = CRM_VIEW_PATHS[target.view as Exclude<CrmView, "record">] ?? "/";
121
+ // A saved view or a list always opens on the /views surface; /lists is only
122
+ // the index of lists.
123
+ if (
124
+ (target.view === "views" ||
125
+ target.view === "board" ||
126
+ target.view === "lists") &&
127
+ (target.viewId || target.listId)
128
+ ) {
129
+ base = "/views";
130
+ if (target.viewId) params.set("view", target.viewId);
131
+ else params.set("list", target.listId!);
132
+ }
133
+ if (target.view === "board") params.set("mode", "board");
134
+ if (target.view === "records" && target.kind) params.set("kind", target.kind);
135
+ if (target.view === "dashboard" && target.dashboardId) {
136
+ params.set("id", target.dashboardId);
137
+ }
138
+ if (target.query) params.set("q", target.query);
139
+ return params.size ? `${base}?${params}` : base;
140
+ }
141
+
142
+ export interface CrmNavigationSelection {
143
+ viewId?: string;
144
+ listId?: string;
145
+ dashboardId?: string;
146
+ kind?: CrmRecordKind;
147
+ mode?: "table" | "board";
148
+ query?: string;
149
+ settingsSection?: CrmSettingsSection;
150
+ }
151
+
152
+ /**
153
+ * The selection carried by a visited path. `null` means no path was published
154
+ * — which is not the same as a path with nothing selected, and callers must be
155
+ * able to tell those apart.
156
+ */
157
+ export function parseCrmNavigationSelection(
158
+ pathAndSearch: string | null | undefined,
159
+ ): CrmNavigationSelection | null {
160
+ if (typeof pathAndSearch !== "string" || !pathAndSearch) return null;
161
+ let url: URL;
162
+ try {
163
+ url = new URL(pathAndSearch, "http://crm.invalid");
164
+ } catch {
165
+ return null;
166
+ }
167
+ const params = url.searchParams;
168
+ const kind = params.get("kind");
169
+ const mode = params.get("mode");
170
+ const section = url.pathname.split("/settings/")[1]?.split("/")[0];
171
+ return {
172
+ ...(params.get("view") ? { viewId: params.get("view")! } : {}),
173
+ ...(params.get("list") ? { listId: params.get("list")! } : {}),
174
+ ...(params.get("id") ? { dashboardId: params.get("id")! } : {}),
175
+ ...(isRecordKind(kind) ? { kind } : {}),
176
+ ...(mode === "table" || mode === "board" ? { mode } : {}),
177
+ ...(params.get("q") ? { query: params.get("q")! } : {}),
178
+ ...(isSettingsSection(section) ? { settingsSection: section } : {}),
179
+ };
180
+ }
181
+
182
+ function isRecordKind(value: string | null): value is CrmRecordKind {
183
+ return CRM_RECORD_KINDS.includes(value as CrmRecordKind);
184
+ }
185
+
186
+ function isSettingsSection(
187
+ value: string | undefined,
188
+ ): value is CrmSettingsSection {
189
+ return CRM_SETTINGS_SECTIONS.includes(value as CrmSettingsSection);
190
+ }
@@ -0,0 +1,63 @@
1
+ import type { ActionRunContext } from "@agent-native/core/action";
2
+
3
+ import type { CrmWritePolicyInput } from "./crm-contract.js";
4
+
5
+ export const CRM_SALES_ROUTINE_LOCAL_POLICY_ID =
6
+ "crm-sales-routine-local-v1" as const;
7
+
8
+ export interface CrmSalesConfigPack {
9
+ id: "crm-sales-v1";
10
+ version: 1;
11
+ delegatedWritePolicies: readonly [
12
+ {
13
+ id: typeof CRM_SALES_ROUTINE_LOCAL_POLICY_ID;
14
+ target: "local";
15
+ operation: "update";
16
+ reversibility: "compensatable";
17
+ scopes: readonly ["single-field", "single-record"];
18
+ risk: "routine";
19
+ },
20
+ ];
21
+ }
22
+
23
+ export const CRM_SALES_CONFIG_PACK: CrmSalesConfigPack = {
24
+ id: "crm-sales-v1",
25
+ version: 1,
26
+ delegatedWritePolicies: [
27
+ {
28
+ id: CRM_SALES_ROUTINE_LOCAL_POLICY_ID,
29
+ target: "local",
30
+ operation: "update",
31
+ reversibility: "compensatable",
32
+ scopes: ["single-field", "single-record"],
33
+ risk: "routine",
34
+ },
35
+ ],
36
+ };
37
+
38
+ export function resolveCrmSalesDelegatedWrite(input: {
39
+ context?: ActionRunContext;
40
+ operation: "update";
41
+ policy: CrmWritePolicyInput;
42
+ }) {
43
+ const policyId = input.context?.automation?.policyId;
44
+ const policy = CRM_SALES_CONFIG_PACK.delegatedWritePolicies.find(
45
+ (candidate) => candidate.id === policyId,
46
+ );
47
+ if (input.context?.caller !== "automation" || !policy) {
48
+ return { delegatedAuthority: false, storedAutomationPolicy: false };
49
+ }
50
+
51
+ const matches =
52
+ policy.operation === input.operation &&
53
+ policy.target === input.policy.target &&
54
+ policy.reversibility === input.policy.reversibility &&
55
+ (input.policy.scope === "single-field" ||
56
+ input.policy.scope === "single-record") &&
57
+ policy.scopes.includes(input.policy.scope) &&
58
+ policy.risk === input.policy.risk;
59
+ return {
60
+ delegatedAuthority: matches,
61
+ storedAutomationPolicy: matches,
62
+ };
63
+ }
@@ -0,0 +1,11 @@
1
+ import { createRequestHandler } from "react-router";
2
+
3
+ const handler = createRequestHandler(
4
+ () => import("virtual:react-router/server-build"),
5
+ );
6
+
7
+ export default {
8
+ async fetch(request: Request) {
9
+ return handler(request);
10
+ },
11
+ };
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "@agent-native/core/tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "ignoreDeprecations": "6.0",
5
+ "rootDirs": [".", "./.react-router/types"],
6
+ "paths": {
7
+ "*": ["./*"],
8
+ "@/*": ["./app/*"],
9
+ "@shared/*": ["./shared/*"]
10
+ }
11
+ },
12
+ "include": [
13
+ "app/**/*",
14
+ "server/**/*",
15
+ "shared/**/*",
16
+ "actions/**/*",
17
+ "vite.config.ts",
18
+ "vitest.config.ts",
19
+ "react-router.config.ts",
20
+ ".react-router/types/**/*",
21
+ ".generated/**/*"
22
+ ],
23
+ "exclude": ["**/*.test.ts", "**/*.spec.ts"]
24
+ }