@graph8/sdk 0.362.0 → 0.373.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (615) hide show
  1. package/README.md +33 -10
  2. package/blocks/_foundation/README.md +78 -1
  3. package/blocks/_foundation/block.json +123 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  7. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  8. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  9. package/blocks/_foundation/components/empty-state.tsx +5 -2
  10. package/blocks/_foundation/components/list-select.tsx +112 -0
  11. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  12. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  13. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  14. package/blocks/_foundation/components/screen/index.ts +2 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  17. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  18. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  19. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  20. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  21. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  22. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  23. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  24. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  25. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  26. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  27. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  28. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  29. package/blocks/_foundation/lib/g8-client.ts +1 -1
  30. package/blocks/_foundation/lib/paging.ts +57 -0
  31. package/blocks/_foundation/lib/plural.ts +23 -0
  32. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  33. package/blocks/_foundation/server/connect-route.ts +15 -0
  34. package/blocks/_foundation/server/connect.ts +127 -0
  35. package/blocks/_foundation/server/local-key.ts +70 -0
  36. package/blocks/_foundation/server/proxy.ts +103 -3
  37. package/blocks/_foundation/server/route.ts +16 -4
  38. package/blocks/agents/README.md +93 -2
  39. package/blocks/agents/block.json +343 -2
  40. package/blocks/agents/components/agent-activity.tsx +112 -0
  41. package/blocks/agents/components/agent-approvals.tsx +135 -0
  42. package/blocks/agents/components/agent-builder.tsx +29 -10
  43. package/blocks/agents/components/agent-conversations.tsx +143 -0
  44. package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
  45. package/blocks/agents/components/agent-operators.tsx +164 -0
  46. package/blocks/agents/components/agent-run-detail.tsx +119 -0
  47. package/blocks/agents/components/agent-runs.tsx +19 -10
  48. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  49. package/blocks/agents/components/agent-test-history.tsx +121 -0
  50. package/blocks/agents/components/agents-screen.tsx +226 -0
  51. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  52. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  53. package/blocks/agents/components/from-template-button.tsx +65 -0
  54. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  55. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  56. package/blocks/agents/components/knowledge-form.tsx +69 -0
  57. package/blocks/agents/components/name-dialog.tsx +32 -0
  58. package/blocks/agents/components/receptionist-button.tsx +46 -0
  59. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  60. package/blocks/agents/components/skill-editor.tsx +147 -0
  61. package/blocks/agents/components/skill-library.tsx +111 -0
  62. package/blocks/agents/lib/agent-types.ts +7 -1
  63. package/blocks/agents/lib/facts.tsx +13 -0
  64. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  65. package/blocks/agents/lib/load-more.tsx +141 -0
  66. package/blocks/agents/lib/skill-utils.ts +91 -0
  67. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  68. package/blocks/analytics/README.md +13 -0
  69. package/blocks/analytics/block.json +45 -1
  70. package/blocks/analytics/components/analytics-screen.tsx +222 -0
  71. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  72. package/blocks/analytics/components/leaderboard.tsx +14 -7
  73. package/blocks/analytics/components/report-builder.tsx +38 -11
  74. package/blocks/analytics/components/report-detail.tsx +84 -0
  75. package/blocks/analytics/components/trend-chart.tsx +62 -0
  76. package/blocks/appointments/README.md +6 -0
  77. package/blocks/appointments/block.json +33 -2
  78. package/blocks/appointments/components/availability-editor.tsx +17 -5
  79. package/blocks/appointments/components/booking-page.tsx +2 -2
  80. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  81. package/blocks/appointments/components/event-type-related.tsx +4 -2
  82. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  83. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  84. package/blocks/appointments/components/meeting-list.tsx +38 -18
  85. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  86. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  87. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  88. package/blocks/block.schema.json +18 -0
  89. package/blocks/block.types.ts +17 -0
  90. package/blocks/campaigns/README.md +41 -11
  91. package/blocks/campaigns/block.json +105 -42
  92. package/blocks/campaigns/components/campaign-assets.tsx +17 -6
  93. package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
  94. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  95. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  96. package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
  97. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  98. package/blocks/campaigns/components/icp-detail.tsx +163 -0
  99. package/blocks/campaigns/components/icp-editor.tsx +22 -17
  100. package/blocks/campaigns/components/persona-detail.tsx +181 -0
  101. package/blocks/campaigns/components/persona-editor.tsx +19 -11
  102. package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
  103. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  104. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  105. package/blocks/coach/README.md +10 -0
  106. package/blocks/coach/block.json +16 -1
  107. package/blocks/coach/components/call-scorecard.tsx +7 -7
  108. package/blocks/coach/components/coach-screen.tsx +222 -0
  109. package/blocks/company-intelligence/README.md +132 -0
  110. package/blocks/company-intelligence/block.json +318 -0
  111. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  112. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  113. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  114. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  115. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  116. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  117. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  118. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  119. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  120. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  121. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  122. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  123. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  124. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  125. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  126. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  127. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  128. package/blocks/copilot/README.md +13 -0
  129. package/blocks/copilot/block.json +14 -1
  130. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  131. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  132. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  133. package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
  134. package/blocks/crm/README.md +34 -0
  135. package/blocks/crm/block.json +107 -2
  136. package/blocks/crm/components/companies-screen.tsx +359 -0
  137. package/blocks/crm/components/company-detail.tsx +10 -8
  138. package/blocks/crm/components/company-table.tsx +11 -4
  139. package/blocks/crm/components/contact-detail.tsx +10 -7
  140. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  141. package/blocks/crm/components/contact-table.tsx +10 -3
  142. package/blocks/crm/components/contacts-screen.tsx +377 -0
  143. package/blocks/crm/components/dedupe-review.tsx +13 -6
  144. package/blocks/crm/components/import-wizard.tsx +7 -9
  145. package/blocks/crm/components/list-select.tsx +20 -0
  146. package/blocks/crm/components/notes-panel.tsx +4 -4
  147. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  148. package/blocks/crm/components/record-form.tsx +6 -4
  149. package/blocks/crm/components/task-list.tsx +4 -4
  150. package/blocks/crm/components/tasks-screen.tsx +399 -0
  151. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  152. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  153. package/blocks/crm/hooks/use-page-query.ts +9 -1
  154. package/blocks/crm/lib/crm-types.ts +26 -2
  155. package/blocks/crm/lib/read-lists.ts +16 -0
  156. package/blocks/crm/lib/screen-parts.tsx +105 -0
  157. package/blocks/deals/README.md +24 -6
  158. package/blocks/deals/block.json +160 -5
  159. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  160. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  161. package/blocks/deals/components/deal-detail.tsx +25 -13
  162. package/blocks/deals/components/deal-filters.tsx +65 -0
  163. package/blocks/deals/components/deal-line-items.tsx +120 -0
  164. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  165. package/blocks/deals/components/deal-related.tsx +31 -10
  166. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  167. package/blocks/deals/components/deals-screen.tsx +66 -26
  168. package/blocks/deals/components/forecast-view.tsx +4 -4
  169. package/blocks/deals/components/pipeline-board.tsx +8 -6
  170. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  171. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  172. package/blocks/deals/components/stage-editor.tsx +5 -5
  173. package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
  174. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  175. package/blocks/deals/lib/deal-extras.ts +156 -0
  176. package/blocks/dialer/README.md +45 -4
  177. package/blocks/dialer/block.json +84 -3
  178. package/blocks/dialer/components/call-contact-button.tsx +269 -0
  179. package/blocks/dialer/components/call-log-screen.tsx +208 -0
  180. package/blocks/dialer/components/call-log.tsx +23 -7
  181. package/blocks/dialer/components/call-queue.tsx +4 -4
  182. package/blocks/dialer/components/dialer-screen.tsx +361 -0
  183. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  184. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  185. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  186. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  187. package/blocks/dialer/components/session-start-form.tsx +61 -12
  188. package/blocks/dialer/components/softphone.tsx +4 -2
  189. package/blocks/enrichment/README.md +29 -7
  190. package/blocks/enrichment/block.json +49 -9
  191. package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
  192. package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
  193. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  194. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  195. package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
  196. package/blocks/enrichment/components/waterfall-config.tsx +66 -17
  197. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  198. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  199. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  200. package/blocks/forms/README.md +8 -0
  201. package/blocks/forms/block.json +139 -18
  202. package/blocks/forms/components/form-activity.tsx +71 -0
  203. package/blocks/forms/components/form-backfill.tsx +108 -0
  204. package/blocks/forms/components/form-builder.tsx +4 -2
  205. package/blocks/forms/components/form-embed.tsx +4 -2
  206. package/blocks/forms/components/form-groups.tsx +151 -0
  207. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  208. package/blocks/forms/components/form-route-history.tsx +72 -0
  209. package/blocks/forms/components/form-saved-views.tsx +81 -0
  210. package/blocks/forms/components/forms-screen.tsx +56 -19
  211. package/blocks/forms/components/more-rows.tsx +30 -0
  212. package/blocks/forms/components/submissions-export.tsx +67 -0
  213. package/blocks/forms/components/submissions-table.tsx +17 -6
  214. package/blocks/forms/lib/forms-extra.ts +67 -0
  215. package/blocks/inbox/README.md +32 -3
  216. package/blocks/inbox/block.json +29 -2
  217. package/blocks/inbox/components/inbox-list.tsx +7 -3
  218. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  219. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  220. package/blocks/inbox/components/reply-composer.tsx +65 -9
  221. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  222. package/blocks/inbox/components/thread-badge.tsx +2 -2
  223. package/blocks/inbox/components/thread-view.tsx +1 -1
  224. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  225. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  226. package/blocks/inbox/lib/inbox-types.ts +47 -1
  227. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  228. package/blocks/integrations/README.md +63 -1
  229. package/blocks/integrations/block.json +234 -15
  230. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  231. package/blocks/integrations/components/connection-health.tsx +59 -0
  232. package/blocks/integrations/components/crm-detail.tsx +119 -0
  233. package/blocks/integrations/components/crm-records.tsx +103 -0
  234. package/blocks/integrations/components/field-mappings.tsx +61 -0
  235. package/blocks/integrations/components/integration-list.tsx +13 -8
  236. package/blocks/integrations/components/integrations-screen.tsx +152 -0
  237. package/blocks/integrations/components/migration-panel.tsx +133 -0
  238. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  239. package/blocks/integrations/components/sync-status.tsx +11 -9
  240. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  241. package/blocks/integrations/components/webhook-detail.tsx +179 -0
  242. package/blocks/integrations/components/webhook-manager.tsx +43 -25
  243. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  244. package/blocks/integrations/lib/crm-utils.ts +39 -0
  245. package/blocks/integrations/lib/facts.tsx +13 -0
  246. package/blocks/integrations/lib/integrations-utils.ts +11 -1
  247. package/blocks/integrations/lib/load-more.tsx +141 -0
  248. package/blocks/intent/README.md +14 -0
  249. package/blocks/intent/block.json +27 -1
  250. package/blocks/intent/components/account-intent-score.tsx +5 -5
  251. package/blocks/intent/components/intent-feed.tsx +13 -10
  252. package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
  253. package/blocks/intent/components/intent-screen.tsx +265 -0
  254. package/blocks/intent/components/keyword-detail.tsx +124 -0
  255. package/blocks/intent/lib/intent-utils.ts +4 -4
  256. package/blocks/lifecycle/block.json +13 -8
  257. package/blocks/lifecycle/components/account-related.tsx +8 -1
  258. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  259. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  260. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  261. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  262. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  263. package/blocks/lists/README.md +20 -1
  264. package/blocks/lists/block.json +69 -3
  265. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  266. package/blocks/lists/components/add-to-list-button.tsx +20 -9
  267. package/blocks/lists/components/list-picker.tsx +17 -3
  268. package/blocks/lists/components/list-table.tsx +12 -6
  269. package/blocks/lists/components/lists-screen.tsx +346 -0
  270. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  271. package/blocks/lists/components/suppression-manager.tsx +33 -8
  272. package/blocks/lists/components/suppressions-screen.tsx +264 -0
  273. package/blocks/mailboxes/README.md +17 -0
  274. package/blocks/mailboxes/block.json +69 -17
  275. package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
  276. package/blocks/mailboxes/components/domain-detail.tsx +148 -0
  277. package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
  278. package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
  279. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  280. package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
  281. package/blocks/mailboxes/components/warmup-health.tsx +24 -22
  282. package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
  283. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  284. package/blocks/newsletter/README.md +12 -0
  285. package/blocks/newsletter/block.json +157 -24
  286. package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
  287. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  288. package/blocks/newsletter/components/more-rows.tsx +30 -0
  289. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  290. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  291. package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
  292. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  293. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  294. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  295. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  296. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  297. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  298. package/blocks/nurture/block.json +28 -14
  299. package/blocks/nurture/components/nurture-builder.tsx +13 -3
  300. package/blocks/nurture/components/nurture-enroll.tsx +12 -10
  301. package/blocks/nurture/components/nurture-form.tsx +27 -10
  302. package/blocks/nurture/components/nurture-screen.tsx +26 -11
  303. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  304. package/blocks/nurture/lib/nurture-types.ts +1 -1
  305. package/blocks/objects/README.md +18 -1
  306. package/blocks/objects/block.json +42 -21
  307. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  308. package/blocks/objects/components/object-not-found.tsx +3 -1
  309. package/blocks/objects/components/object-record-detail.tsx +10 -2
  310. package/blocks/objects/components/object-schema-editor.tsx +4 -2
  311. package/blocks/objects/components/object-table.tsx +13 -3
  312. package/blocks/objects/components/objects-screen.tsx +147 -0
  313. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  314. package/blocks/objects/lib/object-types.ts +1 -1
  315. package/blocks/pages/README.md +14 -0
  316. package/blocks/pages/block.json +140 -23
  317. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  318. package/blocks/pages/components/content-library.tsx +9 -4
  319. package/blocks/pages/components/more-rows.tsx +30 -0
  320. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  321. package/blocks/pages/components/page-analytics.tsx +176 -0
  322. package/blocks/pages/components/page-clone.tsx +99 -0
  323. package/blocks/pages/components/page-domains.tsx +167 -0
  324. package/blocks/pages/components/page-editor.tsx +1 -1
  325. package/blocks/pages/components/page-generation-status.tsx +48 -0
  326. package/blocks/pages/components/page-list.tsx +2 -1
  327. package/blocks/pages/components/page-tracking.tsx +137 -0
  328. package/blocks/pages/components/pages-screen.tsx +57 -16
  329. package/blocks/pages/components/seo-report.tsx +6 -1
  330. package/blocks/pages/lib/studio.ts +7 -0
  331. package/blocks/phone-numbers/README.md +74 -0
  332. package/blocks/phone-numbers/block.json +262 -0
  333. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  334. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  335. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  336. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  337. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  338. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  339. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  340. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  341. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  342. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  343. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  344. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  345. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  346. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  347. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  348. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  349. package/blocks/pickers/README.md +23 -0
  350. package/blocks/pickers/block.json +103 -0
  351. package/blocks/pickers/components/company-picker.tsx +90 -0
  352. package/blocks/pickers/components/contact-picker.tsx +131 -0
  353. package/blocks/pickers/components/member-picker.tsx +99 -0
  354. package/blocks/pickers/components/search-picker.tsx +98 -0
  355. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  356. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  357. package/blocks/pickers/lib/use-debounced.ts +13 -0
  358. package/blocks/quotes/README.md +13 -0
  359. package/blocks/quotes/block.json +65 -13
  360. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  361. package/blocks/quotes/components/product-picker.tsx +3 -1
  362. package/blocks/quotes/components/quote-builder.tsx +1 -1
  363. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  364. package/blocks/quotes/components/quote-list.tsx +5 -3
  365. package/blocks/quotes/components/quote-related.tsx +8 -4
  366. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  367. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  368. package/blocks/radar/README.md +10 -0
  369. package/blocks/radar/block.json +41 -1
  370. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  371. package/blocks/radar/components/competitor-detail.tsx +117 -0
  372. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  373. package/blocks/radar/components/radar-overview.tsx +4 -4
  374. package/blocks/radar/components/radar-report.tsx +4 -2
  375. package/blocks/radar/components/radar-screen.tsx +214 -0
  376. package/blocks/radar/components/report-detail.tsx +104 -0
  377. package/blocks/search/README.md +10 -0
  378. package/blocks/search/block.json +16 -1
  379. package/blocks/search/components/filter-builder.tsx +3 -3
  380. package/blocks/search/components/prospect-results.tsx +5 -3
  381. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  382. package/blocks/search/components/saved-searches.tsx +4 -1
  383. package/blocks/search/hooks/use-filter-options.ts +20 -6
  384. package/blocks/search/lib/search-types.ts +2 -0
  385. package/blocks/sequencer/README.md +55 -9
  386. package/blocks/sequencer/block.json +126 -19
  387. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  388. package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
  389. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  390. package/blocks/sequencer/components/sequence-builder.tsx +224 -60
  391. package/blocks/sequencer/components/sequence-detail.tsx +261 -0
  392. package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
  393. package/blocks/sequencer/components/sequence-list.tsx +34 -9
  394. package/blocks/sequencer/components/sequence-stats.tsx +11 -10
  395. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  396. package/blocks/sequencer/components/sequences-screen.tsx +162 -0
  397. package/blocks/sequencer/components/step-editor.tsx +14 -12
  398. package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
  399. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  400. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  401. package/blocks/social-ads/README.md +19 -0
  402. package/blocks/social-ads/block.json +233 -1
  403. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  404. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  405. package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
  406. package/blocks/social-ads/components/audience-sync.tsx +17 -9
  407. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  408. package/blocks/social-ads/components/campaign-detail.tsx +138 -0
  409. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  410. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  411. package/blocks/social-ads/components/creative-detail.tsx +155 -0
  412. package/blocks/social-ads/components/creative-gallery.tsx +35 -7
  413. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  414. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  415. package/blocks/social-ads/components/post-composer.tsx +7 -3
  416. package/blocks/social-ads/components/post-detail.tsx +163 -0
  417. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  418. package/blocks/social-ads/components/social-performance.tsx +80 -0
  419. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  420. package/blocks/social-ads/components/social-screen.tsx +187 -0
  421. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  422. package/blocks/surveys/README.md +91 -0
  423. package/blocks/surveys/block.json +299 -0
  424. package/blocks/surveys/components/account-nps.tsx +69 -0
  425. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  426. package/blocks/surveys/components/org-summary.tsx +82 -0
  427. package/blocks/surveys/components/sending-domains.tsx +113 -0
  428. package/blocks/surveys/components/survey-form.tsx +126 -0
  429. package/blocks/surveys/components/survey-responses.tsx +145 -0
  430. package/blocks/surveys/components/survey-results.tsx +72 -0
  431. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  432. package/blocks/surveys/components/survey-send.tsx +162 -0
  433. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  434. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  435. package/blocks/surveys/lib/surveys-types.ts +228 -0
  436. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  437. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  438. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  439. package/blocks/visitors/README.md +10 -0
  440. package/blocks/visitors/block.json +25 -1
  441. package/blocks/visitors/components/snippet-install.tsx +21 -10
  442. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  443. package/blocks/visitors/components/visitor-company-card.tsx +26 -16
  444. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  445. package/blocks/visitors/components/visitor-feed.tsx +16 -7
  446. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  447. package/blocks/webchat/README.md +10 -0
  448. package/blocks/webchat/block.json +21 -7
  449. package/blocks/webchat/components/chat-widget-config.tsx +8 -5
  450. package/blocks/webchat/components/conversation-list.tsx +8 -4
  451. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  452. package/blocks/workbench/README.md +11 -0
  453. package/blocks/workbench/block.json +48 -1
  454. package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
  455. package/blocks/workbench/components/run-progress.tsx +1 -1
  456. package/blocks/workbench/components/workbench-grid.tsx +10 -4
  457. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  458. package/blocks/workbench/components/workbench-screen.tsx +202 -0
  459. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  460. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  461. package/blocks/workflows/README.md +13 -0
  462. package/blocks/workflows/block.json +28 -9
  463. package/blocks/workflows/components/run-history.tsx +31 -9
  464. package/blocks/workflows/components/trigger-config.tsx +3 -1
  465. package/blocks/workflows/components/workflow-editor.tsx +4 -2
  466. package/blocks/workflows/components/workflow-list.tsx +13 -6
  467. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  468. package/dist/cli.js +3109 -1977
  469. package/dist/errors.d.mts +34 -0
  470. package/dist/errors.d.ts +34 -0
  471. package/dist/errors.js +46 -0
  472. package/dist/errors.js.map +1 -0
  473. package/dist/errors.mjs +21 -0
  474. package/dist/errors.mjs.map +1 -0
  475. package/dist/index.browser.js +11 -5
  476. package/dist/index.browser.js.map +1 -1
  477. package/dist/index.browser.mjs +11 -5
  478. package/dist/index.browser.mjs.map +1 -1
  479. package/dist/index.d.mts +164 -25
  480. package/dist/index.d.ts +164 -25
  481. package/dist/index.js +11 -5
  482. package/dist/index.js.map +1 -1
  483. package/dist/index.mjs +11 -5
  484. package/dist/index.mjs.map +1 -1
  485. package/dist/react.browser.js +10 -4
  486. package/dist/react.browser.js.map +1 -1
  487. package/dist/react.browser.mjs +10 -4
  488. package/dist/react.browser.mjs.map +1 -1
  489. package/dist/react.d.mts +142 -5
  490. package/dist/react.d.ts +142 -5
  491. package/dist/react.js +10 -4
  492. package/dist/react.js.map +1 -1
  493. package/dist/react.mjs +10 -4
  494. package/dist/react.mjs.map +1 -1
  495. package/dist/types/blocks/add.d.ts +11 -4
  496. package/dist/types/blocks/brand.d.ts +122 -0
  497. package/dist/types/blocks/compose.d.ts +53 -0
  498. package/dist/types/blocks/create.d.ts +27 -20
  499. package/dist/types/blocks/doctor.d.ts +6 -1
  500. package/dist/types/blocks/flows.d.ts +7 -1
  501. package/dist/types/blocks/navfile.d.ts +35 -0
  502. package/dist/types/blocks/orgBrand.d.ts +34 -0
  503. package/dist/types/blocks/paths.d.ts +4 -0
  504. package/dist/types/blocks/prefixes.d.ts +37 -0
  505. package/dist/types/blocks/registry.d.ts +17 -0
  506. package/dist/types/blocks/screens.d.ts +53 -0
  507. package/dist/types/cli.d.ts +5 -0
  508. package/dist/types/errors.d.ts +33 -0
  509. package/dist/types/http.d.ts +2 -18
  510. package/dist/types/version.d.ts +2 -2
  511. package/docs/agents.md +15 -4
  512. package/docs/api/README.md +2 -2
  513. package/docs/api/by-path.md +1 -0
  514. package/docs/api/desk.d.ts +98 -2
  515. package/docs/api/desk.md +123 -1
  516. package/docs/api/inbox.d.ts +10 -2
  517. package/docs/api/inbox.md +10 -2
  518. package/docs/api/seo.d.ts +19 -1
  519. package/docs/api/seo.md +24 -1
  520. package/package.json +16 -2
  521. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  522. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  523. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  524. package/stacks/6sense.json +55 -17
  525. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  526. package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
  527. package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
  528. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  529. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  530. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  531. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  532. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  533. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  534. package/stacks/apollo.json +147 -37
  535. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  536. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  537. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  538. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  539. package/stacks/calendly.json +64 -0
  540. package/stacks/clay/pages/companies.tsx.tmpl +31 -0
  541. package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
  542. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  543. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  544. package/stacks/clay.json +73 -27
  545. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  546. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  547. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  548. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  549. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  550. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  551. package/stacks/gong.json +98 -35
  552. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  553. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  554. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  555. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  556. package/stacks/hootsuite.json +104 -0
  557. package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
  558. package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
  559. package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
  560. package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
  561. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  562. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  563. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  564. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  565. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  566. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  567. package/stacks/hubspot.json +181 -64
  568. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  569. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  570. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  571. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  572. package/stacks/intercom.json +52 -20
  573. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  574. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  575. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  576. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  577. package/stacks/mailchimp.json +68 -34
  578. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  579. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  580. package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
  581. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  582. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  583. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  584. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  585. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  586. package/stacks/outreach.json +106 -25
  587. package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
  588. package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
  589. package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
  590. package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
  591. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  592. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  593. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  594. package/stacks/pipedrive.json +117 -0
  595. package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
  596. package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
  597. package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
  598. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  599. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  600. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  601. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  602. package/stacks/salesforce.json +85 -31
  603. package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
  604. package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
  605. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  606. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  607. package/stacks/zoominfo.json +115 -0
  608. package/styles/styles.css +1 -1
  609. package/templates/next-app/README.md.tmpl +40 -9
  610. package/templates/next-app/app/globals.css.tmpl +349 -64
  611. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  612. package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
  613. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
  614. package/templates/next-app/dot-gitignore.tmpl +2 -0
  615. package/templates/next-app/package.json.tmpl +1 -1
@@ -0,0 +1,202 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Types and helpers shared by the phone-numbers components. The voice endpoints answer open-shaped payloads, so every
5
+ * field is read defensively (a missing field is "unknown", never a crash and never a made-up default).
6
+ */
7
+
8
+ /** The carriers a number can be bought from (the contract's enum on purchase and release). */
9
+ export const PROVIDERS = ["twilio", "telnyx"] as const;
10
+ export type Provider = (typeof PROVIDERS)[number];
11
+
12
+ /** How a number answers inbound calls (the contract's enum on `inbound-type`). */
13
+ export const INBOUND_TYPES = ["ai", "sdr", "dialer", "work"] as const;
14
+ export type InboundType = (typeof INBOUND_TYPES)[number];
15
+
16
+ export const INBOUND_LABEL: Record<InboundType, string> = {
17
+ ai: "Voice AI agent",
18
+ sdr: "Rep answers",
19
+ dialer: "Outbound dialer",
20
+ work: "Personal work phone",
21
+ };
22
+
23
+ /**
24
+ * What the platform holds when you buy, and charges each month after (credits). These are the platform's current
25
+ * rates (250 credits held on purchase, 250 per monthly rental); the API has no price read, so they are ESTIMATES the
26
+ * dialogs say so. Numbers the platform provisioned itself (the onboarding receptionist line) are never rental-billed.
27
+ */
28
+ export const PURCHASE_CREDITS = 250;
29
+ export const MONTHLY_RENTAL_CREDITS = 250;
30
+
31
+ export interface NumberRow {
32
+ /** E.164, the number's id everywhere in the API. */
33
+ number: string;
34
+ /** Empty when the list does not say (never guessed). */
35
+ inboundType: string;
36
+ /** `twilio` or `telnyx`; empty when no list says (a number only the agents list knows). */
37
+ provider: string;
38
+ assignedTo: string | null;
39
+ callsToday: number | null;
40
+ calls7d: number | null;
41
+ connectRate7d: number | null;
42
+ dailyLimit: number | null;
43
+ createdAt: string | null;
44
+ /** False when the carrier would refuse to dial from it (for example an unverified Telnyx caller ID). */
45
+ isValid: boolean | null;
46
+ callerIdVerified: boolean | null;
47
+ /** Set when a voice AI agent has this number booked. */
48
+ agentName: string | null;
49
+ agentId: string | null;
50
+ }
51
+
52
+ export interface AvailableNumber {
53
+ phone_number: string;
54
+ capabilities?: { voice?: boolean; sms?: boolean; mms?: boolean; fax?: boolean };
55
+ region?: string | null;
56
+ locality?: string | null;
57
+ }
58
+
59
+ export const asObject = (v: unknown): Record<string, unknown> => (v && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>) : {});
60
+ const str = (v: unknown): string | null => (typeof v === "string" && v.trim() ? v : null);
61
+ const num = (v: unknown): number | null => (typeof v === "number" && Number.isFinite(v) ? v : null);
62
+ const bool = (v: unknown): boolean | null => (typeof v === "boolean" ? v : null);
63
+
64
+ /** The envelope's `data`, or the value itself. */
65
+ export function dataOf(envelope: unknown): Record<string, unknown> {
66
+ const o = asObject(envelope);
67
+ return "data" in o ? asObject(o.data) : o;
68
+ }
69
+
70
+ /** Rows of `data.<key>`, `data.items` or a bare `data` array. */
71
+ function listAt(envelope: unknown, key: string): unknown[] {
72
+ const o = asObject(envelope);
73
+ const data = "data" in o ? o.data : envelope;
74
+ if (Array.isArray(data)) return data;
75
+ const inner = asObject(data);
76
+ const rows = inner[key] ?? inner.items;
77
+ return Array.isArray(rows) ? rows : [];
78
+ }
79
+
80
+ /** GET /voice/dialer/numbers rows (`data.numbers`). */
81
+ export function dialerRows(envelope: unknown): NumberRow[] {
82
+ return listAt(envelope, "numbers")
83
+ .map((raw) => asObject(raw))
84
+ .filter((r) => str(r.number))
85
+ .map((r) => ({
86
+ number: String(r.number),
87
+ inboundType: str(r.inbound_type) ?? "",
88
+ provider: str(r.telephony_provider) ?? "",
89
+ assignedTo: str(r.assigned_to),
90
+ callsToday: num(r.calls_today),
91
+ calls7d: num(r.calls_7d),
92
+ connectRate7d: num(r.connect_rate_7d),
93
+ dailyLimit: num(r.daily_limit),
94
+ createdAt: str(r.created_at),
95
+ isValid: bool(r.is_valid),
96
+ callerIdVerified: bool(r.caller_id_verified),
97
+ agentName: null,
98
+ agentId: null,
99
+ }));
100
+ }
101
+
102
+ export interface AgentNumbers {
103
+ /** AI-capable numbers no agent holds. They also appear in the dialer list, so they only mark "free for an agent". */
104
+ available: string[];
105
+ booked: Array<{ number: string; agentName: string | null; agentId: string | null }>;
106
+ }
107
+
108
+ /** GET /voice/agent-phone-numbers (`available_numbers` strings, `booked_numbers` objects). */
109
+ export function agentNumbers(envelope: unknown): AgentNumbers {
110
+ const d = dataOf(envelope);
111
+ const available = Array.isArray(d.available_numbers) ? d.available_numbers.filter((n): n is string => typeof n === "string") : [];
112
+ const booked = (Array.isArray(d.booked_numbers) ? d.booked_numbers : [])
113
+ .map((b) => asObject(b))
114
+ .filter((b) => str(b.number))
115
+ .map((b) => ({ number: String(b.number), agentName: str(b.agent_name), agentId: str(b.agent_id) }));
116
+ return { available, booked };
117
+ }
118
+
119
+ /**
120
+ * The whole inventory. The dialer list leaves out the numbers a voice AI agent holds, and the agent list leaves out
121
+ * dialer-only numbers, so the union is every number. A number in both keeps the dialer row (it has the usage stats) and
122
+ * gains the agent's name.
123
+ */
124
+ export function mergeInventory(dialer: NumberRow[], agents: AgentNumbers): NumberRow[] {
125
+ const byKey = new Map<string, NumberRow>();
126
+ for (const r of dialer) byKey.set(digits(r.number), r);
127
+ for (const b of agents.booked) {
128
+ const hit = byKey.get(digits(b.number));
129
+ if (hit) byKey.set(digits(b.number), { ...hit, agentName: b.agentName, agentId: b.agentId });
130
+ else {
131
+ byKey.set(digits(b.number), {
132
+ number: b.number, inboundType: "ai", provider: "", assignedTo: null, callsToday: null, calls7d: null,
133
+ connectRate7d: null, dailyLimit: null, createdAt: null, isValid: null, callerIdVerified: null, agentName: b.agentName, agentId: b.agentId,
134
+ });
135
+ }
136
+ }
137
+ return [...byKey.values()].sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? "") || a.number.localeCompare(b.number));
138
+ }
139
+
140
+ /** Digits only, so `+1 (415) 555-0123` and `+14155550123` compare equal (stored formats drift). */
141
+ export function digits(value: string): string {
142
+ return value.replace(/\D/g, "");
143
+ }
144
+
145
+ /** True for a valid E.164 number: `+`, then 8 to 15 digits, no leading zero. */
146
+ export function isE164(value: string): boolean {
147
+ return /^\+[1-9]\d{7,14}$/.test(value.trim());
148
+ }
149
+
150
+ /** `+14155550123` -> `+1 (415) 555-0123`; any other shape is shown as it is. */
151
+ export function formatNumber(value: string): string {
152
+ const m = /^\+1(\d{3})(\d{3})(\d{4})$/.exec(value);
153
+ return m ? `+1 (${m[1]}) ${m[2]}-${m[3]}` : value;
154
+ }
155
+
156
+ export function typeLabel(inboundType: string): string {
157
+ return INBOUND_LABEL[inboundType as InboundType] ?? (inboundType || "-");
158
+ }
159
+
160
+ export type Tone = "neutral" | "info" | "positive" | "warning" | "negative";
161
+
162
+ /** Health of a number for the pill: unusable, near its daily cap, or fine. */
163
+ export function numberHealth(row: NumberRow): { tone: Tone; label: string } {
164
+ if (row.isValid === false) return { tone: "negative", label: "Cannot dial" };
165
+ if (row.dailyLimit && row.callsToday != null && row.callsToday >= row.dailyLimit) return { tone: "warning", label: "At daily limit" };
166
+ if (row.dailyLimit && row.callsToday != null && row.callsToday >= row.dailyLimit * 0.8) return { tone: "warning", label: "Near limit" };
167
+ return { tone: "positive", label: "Ready" };
168
+ }
169
+
170
+ /** A fresh idempotency key for one confirmed purchase (reuse it when the same click is retried). */
171
+ export function newIdempotencyKey(): string {
172
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `g8-${Date.now()}-${Math.random().toString(36).slice(2)}`;
173
+ }
174
+
175
+ export function percent(rate: number | null): string {
176
+ if (rate == null) return "-";
177
+ return `${Math.round((rate <= 1 ? rate * 100 : rate) * 10) / 10}%`;
178
+ }
179
+
180
+ export function shortDate(value: string | null): string {
181
+ if (!value) return "-";
182
+ const d = new Date(value);
183
+ return Number.isNaN(d.getTime()) ? "-" : d.toLocaleDateString("en-US", { dateStyle: "medium", timeZone: "UTC" });
184
+ }
185
+
186
+ /** A2P registration states the status payload can carry (carrier vocabulary): the pill tone for a status string. */
187
+ export function a2pTone(status: string | null | undefined): Tone {
188
+ const s = (status ?? "").toUpperCase();
189
+ if (["VERIFIED", "APPROVED", "REGISTERED"].includes(s)) return "positive";
190
+ if (["FAILED", "TIMEOUT", "REJECTED"].includes(s)) return "negative";
191
+ if (s) return "warning";
192
+ return "neutral";
193
+ }
194
+
195
+ const HIDDEN_FIELDS = /password|secret|token|credential/i;
196
+
197
+ /** Scalar fields of an open-shaped payload as label/value pairs (secrets are never shown). */
198
+ export function scalarItems(obj: Record<string, unknown>): Array<{ label: string; value: string }> {
199
+ return Object.entries(obj)
200
+ .filter(([k, v]) => !HIDDEN_FIELDS.test(k) && ["string", "number", "boolean"].includes(typeof v) && v !== "")
201
+ .map(([k, v]) => ({ label: k.replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase()), value: typeof v === "boolean" ? (v ? "Yes" : "No") : String(v) }));
202
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @title Search for a number and buy it
3
+ * @when An agent setting up calling for a workspace. The search is free. THE PURCHASE SPENDS REAL CREDITS (about 250
4
+ * held on purchase, then about 250 a month in rental; estimates, the API has no price read), so a human must approve the
5
+ * exact number first. The purchase uses an Idempotency-Key DERIVED from the number, so re-running after a timeout cannot
6
+ * buy it twice. 402 = not enough credits (nothing bought, nothing charged); 409 = the number is already taken.
7
+ */
8
+ import { createHash } from "node:crypto";
9
+ import { g8 } from "@graph8/sdk";
10
+
11
+ g8.init({ apiKey: process.env.G8_API_KEY! });
12
+
13
+ interface Available {
14
+ phone_number: string;
15
+ locality?: string | null;
16
+ region?: string | null;
17
+ capabilities?: { voice?: boolean; sms?: boolean };
18
+ }
19
+
20
+ export async function findNumbers(areaCode: string, provider: "twilio" | "telnyx" = "twilio"): Promise<Available[]> {
21
+ // READ, free: nothing is held or charged by searching.
22
+ const res = await g8.api.phoneNumbers.searchAvailablePhoneNumbers({
23
+ body: { provider, country: "US", number_type: "local", area_code: areaCode },
24
+ });
25
+ return (res.data?.items ?? []) as Available[];
26
+ }
27
+
28
+ export async function buyNumber(
29
+ phoneNumber: string,
30
+ humanApproved: boolean,
31
+ options: { provider?: "twilio" | "telnyx"; use?: "dialer" | "voice-ai"; assigneeEmail?: string } = {},
32
+ ) {
33
+ if (!humanApproved) throw new Error("Refusing to spend credits without approval");
34
+ const provider = options.provider ?? "twilio";
35
+ const key = createHash("sha256").update(["number", phoneNumber, provider].join("|")).digest("hex").slice(0, 40);
36
+ // BILLABLE. `dialer: true` makes an outbound dialer number; `dialer: false` a voice-AI number. Leave `assignee` out to
37
+ // own it as the identity behind the key.
38
+ return g8.api.phoneNumbers.purchasePhoneNumber(
39
+ {
40
+ body: {
41
+ phone_number: phoneNumber,
42
+ provider,
43
+ dialer: (options.use ?? "dialer") === "dialer",
44
+ ...(options.assigneeEmail ? { assignee: options.assigneeEmail } : {}),
45
+ },
46
+ },
47
+ { idempotencyKey: key },
48
+ );
49
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @title Assign a number to a rep, or clear it
3
+ * @when Onboarding a rep (give them a caller ID) or offboarding one (take their number back). Reassigning is ADMIN-ONLY
4
+ * through the API: an organisation key can never be the current assignee or the creator, so a non-admin key gets a 403.
5
+ * The assignee is not a label: it drives who the number rings, who hears about a missed call and whose outbound caller-ID
6
+ * rotation pool includes it.
7
+ */
8
+ import { g8 } from "@graph8/sdk";
9
+
10
+ g8.init({ apiKey: process.env.G8_API_KEY! });
11
+
12
+ /** Move a number to a person (their email): updates the CRM record and tells voice. */
13
+ export async function assignNumber(phoneNumber: string, repEmail: string) {
14
+ return g8.api.phoneNumbers.updatePhoneAssignee({ body: { phone_number: phoneNumber, assignee: repEmail } });
15
+ }
16
+
17
+ /** Clear the assignment on the voice side (`assigned_to: ""` is how voice spells "nobody"). */
18
+ export async function unassignNumber(phoneNumber: string) {
19
+ return g8.api.phoneNumbers.updateVoiceAssignee({ body: { number: phoneNumber, assigned_to: "" } });
20
+ }
21
+
22
+ /** The CRM and voice records can disagree (voice is told best-effort): send the same assignee again to reconcile voice. */
23
+ export async function resyncVoice(phoneNumber: string, repEmail: string) {
24
+ return g8.api.phoneNumbers.updateVoiceAssignee({ body: { number: phoneNumber, assigned_to: repEmail } });
25
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @title Audit the inventory: usage, unassigned numbers and text registration
3
+ * @when A health check before a calling or texting campaign. READ-ONLY: it changes nothing. The dialer list leaves out the
4
+ * numbers a voice AI agent holds and the agent list leaves out dialer-only numbers, so both are read and merged.
5
+ */
6
+ import { g8 } from "@graph8/sdk";
7
+
8
+ g8.init({ apiKey: process.env.G8_API_KEY! });
9
+
10
+ interface DialerNumber {
11
+ number: string;
12
+ assigned_to?: string | null;
13
+ calls_today?: number;
14
+ daily_limit?: number;
15
+ is_valid?: boolean;
16
+ inbound_type?: string;
17
+ telephony_provider?: string;
18
+ }
19
+
20
+ export async function auditNumbers() {
21
+ const [dialer, agents, a2p] = await Promise.all([
22
+ g8.api.voice.listDialerNumbers({}),
23
+ g8.api.voice.listAgentPhoneNumbers({}),
24
+ g8.api.phoneNumbers.getPhoneA2pStatus({}),
25
+ ]);
26
+ const numbers = (dialer.data?.numbers ?? []) as DialerNumber[];
27
+ const booked = ((agents.data as { booked_numbers?: unknown[] } | undefined)?.booked_numbers ?? []).length;
28
+ return {
29
+ total: numbers.length + booked,
30
+ heldByAgents: booked,
31
+ unassigned: numbers.filter((n) => !n.assigned_to).map((n) => n.number),
32
+ cannotDial: numbers.filter((n) => n.is_valid === false).map((n) => n.number),
33
+ atDailyLimit: numbers.filter((n) => n.daily_limit && (n.calls_today ?? 0) >= n.daily_limit).map((n) => n.number),
34
+ textRegistration: a2p.data,
35
+ };
36
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @title Save A2P details and check registration
3
+ * @when Before sending texts to US numbers: carriers filter business texts from numbers whose business is not registered
4
+ * (A2P 10DLC). SAVING a draft is safe (nothing reaches a carrier, nothing is charged) and REPLACES what was saved, so send
5
+ * the whole object. SUBMITTING reaches the carrier registry, carries carrier fees, takes days and can be rejected: a human
6
+ * must approve it. Submitting is idempotent per workspace.
7
+ */
8
+ import { g8 } from "@graph8/sdk";
9
+
10
+ g8.init({ apiKey: process.env.G8_API_KEY! });
11
+
12
+ export interface A2pDetails {
13
+ brand_inputs: Record<string, unknown>;
14
+ campaign_inputs: Record<string, unknown>;
15
+ }
16
+
17
+ export async function saveDraft(details: A2pDetails) {
18
+ // Merge over what is saved so a field this call does not know about (an address, say) is not wiped.
19
+ const saved = (await g8.api.phoneNumbers.getPhoneA2pStatus({})).data as { brand_inputs?: object; campaign_inputs?: object } | undefined;
20
+ await g8.api.phoneNumbers.saveA2pInputs({
21
+ body: {
22
+ brand_inputs: { ...saved?.brand_inputs, ...details.brand_inputs },
23
+ campaign_inputs: { ...saved?.campaign_inputs, ...details.campaign_inputs },
24
+ },
25
+ });
26
+ return (await g8.api.phoneNumbers.getPhoneA2pStatus({})).data as { inputs_complete?: boolean; missing_inputs?: string[] } | undefined;
27
+ }
28
+
29
+ export async function submit(details: A2pDetails, humanApproved: boolean) {
30
+ if (!humanApproved) throw new Error("Refusing to submit a carrier registration without approval");
31
+ const status = await saveDraft(details);
32
+ if (!status?.inputs_complete) throw new Error(`Still missing: ${(status?.missing_inputs ?? []).join(", ")}`);
33
+ return g8.api.phoneNumbers.startA2pRegistration({ body: details });
34
+ }
35
+
36
+ /** Re-attach pending or failed numbers (a write, safe to repeat). A worker also does this every six hours. */
37
+ export async function retryStuckNumbers() {
38
+ return g8.api.phoneNumbers.reconcileA2pRegistration({});
39
+ }
@@ -0,0 +1,23 @@
1
+ # pickers
2
+
3
+ No raw ids: nobody should type an internal id they cannot discover. This opt-in block holds the signed-in-member hook and the searchable pickers. It depends on the foundation only; the blocks that use it (dialer, lists, enrichment, objects) depend on it, so an app with none of those gets no extra proxy prefixes or scopes.
4
+
5
+ ## What you get
6
+
7
+ | File | What it is |
8
+ |---|---|
9
+ | `hooks/use-current-member.ts` | `useCurrentMember({ enabled? })` -> `{ member, userId, email, name, loading, forbidden }`. The signed-in team member (`GET /team-members/me`, a personal key). An organisation key has no person: `forbidden` is true, so ask with a `MemberPicker`. |
10
+ | `components/member-picker.tsx` | `MemberPicker`: searchable team-member select (`GET /team-members`, fetched on open, "Show more"). Reports the user id operations take; people without a user id are left out. A 403 shows a plain note. |
11
+ | `components/contact-picker.tsx` | `ContactPicker`: single, or `multiple` with chips. Search by name, or by work email for text with `@`. Paged; locked `***` values are never shown. |
12
+ | `components/company-picker.tsx` | `CompanyPicker`: search by name or domain, paged. |
13
+ | `components/search-picker.tsx`, `hooks/use-picker-pages.ts` | The dropdown shell and the paging/search engine the pickers share, to build another picker. Import them as `@/g8/pickers/components/search-picker` and `@/g8/pickers/hooks/use-picker-pages` from a block that `dependsOn` pickers. |
14
+ | `lib/use-debounced.ts` | `useDebounced(value, ms)`. |
15
+
16
+ ## Operations
17
+
18
+ | Operation | Endpoint | Tier | Scope | Used by |
19
+ |---|---|---|---|---|
20
+ | `teamMembers.getMyTeamMember` | GET /team-members/me | read | `team:read` | useCurrentMember |
21
+ | `teamMembers.listTeamMembers` | GET /team-members | read | `team:read` | MemberPicker |
22
+ | `contacts.listContacts` | GET /contacts | read | `contacts:read` | ContactPicker |
23
+ | `companies.listCompanies` | GET /companies | read | `companies:read` | CompanyPicker |
@@ -0,0 +1,103 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "pickers",
4
+ "title": "Pickers",
5
+ "summary": "No raw ids: signed-in member hook plus searchable team-member, contact and company pickers that report the id operations take.",
6
+ "features": [
7
+ "useCurrentMember: the signed-in teammate (user id, email, name) from GET /team-members/me",
8
+ "MemberPicker: searchable team-member select, paged",
9
+ "ContactPicker: searchable contact select (single or multiple), by name or work email, paged",
10
+ "CompanyPicker: searchable company select by name or domain, paged",
11
+ "SearchPicker + usePickerPages: the shell and paging engine to build another picker"
12
+ ],
13
+ "dependsOn": [
14
+ "foundation"
15
+ ],
16
+ "operations": [
17
+ "get_my_team_member_team_members_me_get",
18
+ "list_companies_companies_get",
19
+ "list_contacts_contacts_get",
20
+ "list_team_members_team_members_get"
21
+ ],
22
+ "components": [
23
+ {
24
+ "name": "SearchPicker",
25
+ "file": "components/search-picker.tsx",
26
+ "purpose": "The dropdown shell behind the pickers: trigger, search box, listbox, Show more, loading/empty/error states. Calls no API.",
27
+ "operations": [],
28
+ "tiers": []
29
+ },
30
+ {
31
+ "name": "MemberPicker",
32
+ "file": "components/member-picker.tsx",
33
+ "purpose": "Searchable team-member select (paged, fetched on open). Reports the user id operations take, so no screen asks a person to type one.",
34
+ "operations": [
35
+ "list_team_members_team_members_get"
36
+ ],
37
+ "tiers": [
38
+ "read"
39
+ ]
40
+ },
41
+ {
42
+ "name": "ContactPicker",
43
+ "file": "components/contact-picker.tsx",
44
+ "purpose": "Searchable contact select by name or work email (paged), single or multiple with chips. Reports the contact id, so no screen asks a person to type one.",
45
+ "operations": [
46
+ "list_contacts_contacts_get"
47
+ ],
48
+ "tiers": [
49
+ "read"
50
+ ]
51
+ },
52
+ {
53
+ "name": "CompanyPicker",
54
+ "file": "components/company-picker.tsx",
55
+ "purpose": "Searchable company select by name or domain (paged). Reports the company id, so no screen asks a person to type one.",
56
+ "operations": [
57
+ "list_companies_companies_get"
58
+ ],
59
+ "tiers": [
60
+ "read"
61
+ ]
62
+ }
63
+ ],
64
+ "hooks": [
65
+ {
66
+ "name": "useCurrentMember",
67
+ "file": "hooks/use-current-member.ts",
68
+ "purpose": "The signed-in team member (user id, email, name) from GET /team-members/me, shared by the dialer, CallContactButton and role/owner defaults. An organisation key has no person: `forbidden` is true and callers offer a MemberPicker.",
69
+ "operations": [
70
+ "get_my_team_member_team_members_me_get"
71
+ ]
72
+ },
73
+ {
74
+ "name": "usePickerPages",
75
+ "file": "hooks/use-picker-pages.ts",
76
+ "purpose": "Paging + debounced search engine behind ContactPicker and CompanyPicker: load more appends, a new search starts over, a stale response is dropped."
77
+ }
78
+ ],
79
+ "lib": [
80
+ {
81
+ "file": "lib/use-debounced.ts",
82
+ "purpose": "useDebounced: a value after it stops changing; the search box of every picker reads through it."
83
+ }
84
+ ],
85
+ "recipes": [],
86
+ "prerequisites": [],
87
+ "scopes": [
88
+ "companies:read",
89
+ "contacts:read",
90
+ "team:read"
91
+ ],
92
+ "defaults": [
93
+ {
94
+ "decision": "Which person a screen acts as when nobody passed a user id",
95
+ "default": "The signed-in team member (a personal API key). An organisation key has no person: ask with MemberPicker",
96
+ "askHuman": false
97
+ }
98
+ ],
99
+ "peerDependencies": {
100
+ "react": ">=18",
101
+ "lucide-react": ">=0.400.0"
102
+ }
103
+ }
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CompanyPicker: choose a company by name instead of typing a company id. A searchable select over the organisation's
5
+ * companies; it reports the company (id, name, hint) so a screen can show who was chosen and send the id.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. GET /companies?name|domain&page&limit (companies.listCompanies) read - fetched only once the picker is opened
9
+ *
10
+ * Search: a company name (partial match); text that looks like a domain ("acme.com") is looked up as the domain.
11
+ * Paged with usePickerPages: 25 per page, "Show more" keeps the earlier pages, a new search starts over.
12
+ */
13
+ import { useState } from "react";
14
+ import { g8Get } from "@/g8/lib/g8-client";
15
+ import { usePickerPages } from "../hooks/use-picker-pages";
16
+ import { SearchPicker } from "./search-picker";
17
+
18
+ export interface CompanyOption {
19
+ id: number;
20
+ name: string;
21
+ /** Domain and industry: what tells two companies with a similar name apart. */
22
+ hint?: string;
23
+ }
24
+
25
+ export interface CompanyPickerProps {
26
+ value?: CompanyOption | null;
27
+ onChange: (company: CompanyOption) => void;
28
+ /** Accessible name and trigger prefix. Default "Company". */
29
+ label?: string;
30
+ placeholder?: string;
31
+ disabled?: boolean;
32
+ /** Id of the trigger button, so a <label htmlFor> can name it. */
33
+ id?: string;
34
+ className?: string;
35
+ }
36
+
37
+ interface Row { id?: number | null; name?: string | null; domain?: string | null; industry?: string | null }
38
+
39
+ const PAGE = 25;
40
+ const DOMAIN = /^[^\s@/]+\.[a-z]{2,}$/i;
41
+
42
+ /** A companies-list row as a selectable option; null without an id. */
43
+ export function toCompanyOption(r: Row): CompanyOption | null {
44
+ if (r.id == null) return null;
45
+ const hint = [r.domain, r.industry].filter(Boolean).join(" · ");
46
+ return { id: r.id, name: r.name || r.domain || `Company ${r.id}`, hint: hint || undefined };
47
+ }
48
+
49
+ export function CompanyPicker({ value, onChange, label = "Company", placeholder, disabled, id, className }: CompanyPickerProps) {
50
+ const [open, setOpen] = useState(false);
51
+ const pages = usePickerPages<CompanyOption>(open, async (q, page, signal) => {
52
+ const filter = DOMAIN.test(q) ? { domain: q.toLowerCase() } : { name: q || undefined };
53
+ const env = await g8Get("/companies", { ...filter, page, limit: PAGE }, { signal });
54
+ const rows = ((env.data ?? []) as Row[]).map(toCompanyOption).filter((o): o is CompanyOption => o !== null);
55
+ const has = (env.pagination as { has_next?: boolean } | undefined)?.has_next;
56
+ return { rows, hasNext: has ?? rows.length >= PAGE };
57
+ });
58
+ const pick = (key: string) => {
59
+ const c = pages.rows.find((r) => String(r.id) === key);
60
+ if (!c) return;
61
+ setOpen(false);
62
+ pages.setSearch("");
63
+ onChange(c);
64
+ };
65
+ return (
66
+ <SearchPicker
67
+ label={label}
68
+ id={id}
69
+ className={className}
70
+ disabled={disabled}
71
+ placeholder={placeholder}
72
+ searchPlaceholder="Search by name or domain"
73
+ triggerContent={value ? <span>{value.name}</span> : undefined}
74
+ options={pages.rows.map((r) => ({ key: String(r.id), label: r.name, hint: r.hint }))}
75
+ selectedKeys={new Set(value ? [String(value.id)] : [])}
76
+ onSelect={pick}
77
+ search={pages.search}
78
+ onSearchChange={pages.setSearch}
79
+ open={open}
80
+ onOpenChange={setOpen}
81
+ loading={pages.loading}
82
+ error={pages.error}
83
+ onRetry={pages.refetch}
84
+ hasMore={pages.hasMore}
85
+ loadingMore={pages.loadingMore}
86
+ onLoadMore={pages.loadMore}
87
+ emptyText={pages.q ? "No company matches that search." : "No companies yet."}
88
+ />
89
+ );
90
+ }