@graph8/sdk 0.303.0 → 0.357.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 (664) hide show
  1. package/README.md +81 -0
  2. package/blocks/README.md +32 -0
  3. package/blocks/_foundation/README.md +78 -0
  4. package/blocks/_foundation/block.json +79 -0
  5. package/blocks/_foundation/components/api-error.tsx +62 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
  7. package/blocks/_foundation/components/empty-state.tsx +24 -0
  8. package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
  9. package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
  10. package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
  11. package/blocks/_foundation/hooks/use-paginated.ts +105 -0
  12. package/blocks/_foundation/lib/g8-client.ts +128 -0
  13. package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
  14. package/blocks/_foundation/server/proxy.ts +233 -0
  15. package/blocks/_foundation/server/route.ts +30 -0
  16. package/blocks/agents/README.md +133 -0
  17. package/blocks/agents/block.json +223 -0
  18. package/blocks/agents/components/agent-builder.tsx +199 -0
  19. package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
  20. package/blocks/agents/components/agent-runs.tsx +175 -0
  21. package/blocks/agents/components/agent-skills-panel.tsx +126 -0
  22. package/blocks/agents/components/agent-test-console.tsx +175 -0
  23. package/blocks/agents/lib/agent-types.ts +114 -0
  24. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
  25. package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
  26. package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
  27. package/blocks/analytics/README.md +73 -0
  28. package/blocks/analytics/block.json +178 -0
  29. package/blocks/analytics/components/funnel-chart.tsx +79 -0
  30. package/blocks/analytics/components/kpi-tiles.tsx +74 -0
  31. package/blocks/analytics/components/leaderboard.tsx +86 -0
  32. package/blocks/analytics/components/report-builder.tsx +200 -0
  33. package/blocks/analytics/hooks/use-report.ts +26 -0
  34. package/blocks/analytics/lib/analytics-types.ts +108 -0
  35. package/blocks/analytics/lib/use-debounced.ts +16 -0
  36. package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
  37. package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
  38. package/blocks/analytics/recipes/03-saved-report.ts +30 -0
  39. package/blocks/appointments/README.md +80 -0
  40. package/blocks/appointments/block.json +218 -0
  41. package/blocks/appointments/components/availability-editor.tsx +192 -0
  42. package/blocks/appointments/components/booking-page.tsx +154 -0
  43. package/blocks/appointments/components/event-type-editor.tsx +214 -0
  44. package/blocks/appointments/components/meeting-detail.tsx +147 -0
  45. package/blocks/appointments/components/meeting-list.tsx +148 -0
  46. package/blocks/appointments/hooks/use-available-slots.ts +29 -0
  47. package/blocks/appointments/hooks/use-schedules.ts +18 -0
  48. package/blocks/appointments/lib/appointments-types.ts +159 -0
  49. package/blocks/appointments/lib/idempotency.ts +30 -0
  50. package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
  51. package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
  52. package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
  53. package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
  54. package/blocks/block.schema.json +130 -0
  55. package/blocks/block.types.ts +109 -0
  56. package/blocks/campaigns/README.md +118 -0
  57. package/blocks/campaigns/block.json +361 -0
  58. package/blocks/campaigns/components/campaign-assets.tsx +180 -0
  59. package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
  60. package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
  61. package/blocks/campaigns/components/campaign-list.tsx +120 -0
  62. package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
  63. package/blocks/campaigns/components/campaign-select.tsx +52 -0
  64. package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
  65. package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
  66. package/blocks/campaigns/components/form-field.tsx +29 -0
  67. package/blocks/campaigns/components/icp-editor.tsx +230 -0
  68. package/blocks/campaigns/components/persona-editor.tsx +210 -0
  69. package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
  70. package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
  71. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
  72. package/blocks/campaigns/recipes/campaign-report.ts +22 -0
  73. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
  74. package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
  75. package/blocks/coach/README.md +93 -0
  76. package/blocks/coach/block.json +125 -0
  77. package/blocks/coach/components/call-scorecard.tsx +126 -0
  78. package/blocks/coach/components/coaching-feed.tsx +140 -0
  79. package/blocks/coach/lib/coach-types.ts +116 -0
  80. package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
  81. package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
  82. package/blocks/copilot/README.md +99 -0
  83. package/blocks/copilot/block.json +151 -0
  84. package/blocks/copilot/components/copilot-memory.tsx +87 -0
  85. package/blocks/copilot/components/copilot-panel.tsx +189 -0
  86. package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
  87. package/blocks/copilot/lib/copilot-types.ts +111 -0
  88. package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
  89. package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
  90. package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
  91. package/blocks/crm/README.md +122 -0
  92. package/blocks/crm/block.json +378 -0
  93. package/blocks/crm/components/activity-timeline.tsx +99 -0
  94. package/blocks/crm/components/company-detail.tsx +118 -0
  95. package/blocks/crm/components/company-table.tsx +173 -0
  96. package/blocks/crm/components/contact-detail.tsx +135 -0
  97. package/blocks/crm/components/contact-table.tsx +217 -0
  98. package/blocks/crm/components/dedupe-review.tsx +164 -0
  99. package/blocks/crm/components/import-wizard.tsx +193 -0
  100. package/blocks/crm/components/notes-panel.tsx +112 -0
  101. package/blocks/crm/components/record-form.tsx +191 -0
  102. package/blocks/crm/components/task-list.tsx +107 -0
  103. package/blocks/crm/hooks/use-custom-fields.ts +18 -0
  104. package/blocks/crm/hooks/use-debounced.ts +13 -0
  105. package/blocks/crm/hooks/use-page-query.ts +52 -0
  106. package/blocks/crm/lib/crm-types.ts +136 -0
  107. package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
  108. package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
  109. package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
  110. package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
  111. package/blocks/deals/README.md +78 -0
  112. package/blocks/deals/block.json +198 -0
  113. package/blocks/deals/components/deal-detail.tsx +134 -0
  114. package/blocks/deals/components/forecast-view.tsx +112 -0
  115. package/blocks/deals/components/pipeline-board.tsx +142 -0
  116. package/blocks/deals/components/stage-editor.tsx +96 -0
  117. package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
  118. package/blocks/deals/hooks/use-pipelines.ts +19 -0
  119. package/blocks/deals/lib/deals-types.ts +128 -0
  120. package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
  121. package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
  122. package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
  123. package/blocks/dialer/README.md +114 -0
  124. package/blocks/dialer/block.json +260 -0
  125. package/blocks/dialer/components/call-log.tsx +78 -0
  126. package/blocks/dialer/components/call-queue.tsx +188 -0
  127. package/blocks/dialer/components/dialer-workspace.tsx +47 -0
  128. package/blocks/dialer/components/disposition-panel.tsx +75 -0
  129. package/blocks/dialer/components/live-calls-table.tsx +107 -0
  130. package/blocks/dialer/components/session-start-form.tsx +111 -0
  131. package/blocks/dialer/components/softphone.tsx +122 -0
  132. package/blocks/dialer/components/transcript-viewer.tsx +82 -0
  133. package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
  134. package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
  135. package/blocks/dialer/hooks/use-dispositions.ts +29 -0
  136. package/blocks/dialer/lib/dialer-types.ts +158 -0
  137. package/blocks/dialer/lib/softphone-transport.ts +67 -0
  138. package/blocks/dialer/lib/twilio-transport.ts +74 -0
  139. package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
  140. package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
  141. package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
  142. package/blocks/enrichment/README.md +105 -0
  143. package/blocks/enrichment/block.json +204 -0
  144. package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
  145. package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
  146. package/blocks/enrichment/components/enrich-button.tsx +136 -0
  147. package/blocks/enrichment/components/waterfall-config.tsx +127 -0
  148. package/blocks/enrichment/hooks/use-providers.ts +18 -0
  149. package/blocks/enrichment/lib/enrichment-types.ts +104 -0
  150. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
  151. package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
  152. package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
  153. package/blocks/forms/README.md +112 -0
  154. package/blocks/forms/block.json +200 -0
  155. package/blocks/forms/components/form-builder.tsx +150 -0
  156. package/blocks/forms/components/form-embed.tsx +103 -0
  157. package/blocks/forms/components/form-mapping-editor.tsx +125 -0
  158. package/blocks/forms/components/site-audit-panel.tsx +73 -0
  159. package/blocks/forms/components/submissions-table.tsx +158 -0
  160. package/blocks/forms/lib/form-types.ts +127 -0
  161. package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
  162. package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
  163. package/blocks/forms/recipes/route-form-to-list.ts +44 -0
  164. package/blocks/inbox/README.md +72 -0
  165. package/blocks/inbox/block.json +194 -0
  166. package/blocks/inbox/components/inbox-list.tsx +156 -0
  167. package/blocks/inbox/components/inbox-screen.tsx +49 -0
  168. package/blocks/inbox/components/reply-composer.tsx +183 -0
  169. package/blocks/inbox/components/tag-and-assign.tsx +189 -0
  170. package/blocks/inbox/components/thread-badge.tsx +17 -0
  171. package/blocks/inbox/components/thread-view.tsx +108 -0
  172. package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
  173. package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
  174. package/blocks/inbox/lib/idempotency.ts +30 -0
  175. package/blocks/inbox/lib/inbox-types.ts +100 -0
  176. package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
  177. package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
  178. package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
  179. package/blocks/integrations/README.md +75 -0
  180. package/blocks/integrations/block.json +172 -0
  181. package/blocks/integrations/components/integration-list.tsx +165 -0
  182. package/blocks/integrations/components/sync-status.tsx +223 -0
  183. package/blocks/integrations/components/webhook-manager.tsx +300 -0
  184. package/blocks/integrations/lib/integrations-utils.ts +62 -0
  185. package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
  186. package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
  187. package/blocks/integrations/recipes/register-webhook.ts +18 -0
  188. package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
  189. package/blocks/intent/README.md +57 -0
  190. package/blocks/intent/block.json +138 -0
  191. package/blocks/intent/components/account-intent-score.tsx +91 -0
  192. package/blocks/intent/components/intent-feed.tsx +215 -0
  193. package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
  194. package/blocks/intent/hooks/use-account-intent.ts +46 -0
  195. package/blocks/intent/lib/intent-utils.ts +95 -0
  196. package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
  197. package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
  198. package/blocks/intent/recipes/who-visited-url.ts +18 -0
  199. package/blocks/lifecycle/README.md +79 -0
  200. package/blocks/lifecycle/block.json +198 -0
  201. package/blocks/lifecycle/components/churn-list.tsx +92 -0
  202. package/blocks/lifecycle/components/customer-table.tsx +119 -0
  203. package/blocks/lifecycle/components/lead-queue.tsx +127 -0
  204. package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
  205. package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
  206. package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
  207. package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
  208. package/blocks/lifecycle/lib/lifecycle.ts +99 -0
  209. package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
  210. package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
  211. package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
  212. package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
  213. package/blocks/lists/README.md +96 -0
  214. package/blocks/lists/block.json +166 -0
  215. package/blocks/lists/components/add-to-list-button.tsx +111 -0
  216. package/blocks/lists/components/list-picker.tsx +94 -0
  217. package/blocks/lists/components/list-table.tsx +160 -0
  218. package/blocks/lists/components/suppression-manager.tsx +153 -0
  219. package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
  220. package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
  221. package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
  222. package/blocks/mailboxes/README.md +71 -0
  223. package/blocks/mailboxes/block.json +187 -0
  224. package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
  225. package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
  226. package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
  227. package/blocks/mailboxes/components/warmup-health.tsx +232 -0
  228. package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
  229. package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
  230. package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
  231. package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
  232. package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
  233. package/blocks/newsletter/README.md +122 -0
  234. package/blocks/newsletter/block.json +224 -0
  235. package/blocks/newsletter/components/issue-form.tsx +124 -0
  236. package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
  237. package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
  238. package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
  239. package/blocks/newsletter/components/subscriber-table.tsx +173 -0
  240. package/blocks/newsletter/lib/newsletter-types.ts +97 -0
  241. package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
  242. package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
  243. package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
  244. package/blocks/nurture/README.md +110 -0
  245. package/blocks/nurture/block.json +191 -0
  246. package/blocks/nurture/components/nurture-builder.tsx +114 -0
  247. package/blocks/nurture/components/nurture-enroll.tsx +107 -0
  248. package/blocks/nurture/components/nurture-form.tsx +120 -0
  249. package/blocks/nurture/components/nurture-stats.tsx +81 -0
  250. package/blocks/nurture/lib/nurture-types.ts +109 -0
  251. package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
  252. package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
  253. package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
  254. package/blocks/objects/README.md +120 -0
  255. package/blocks/objects/block.json +243 -0
  256. package/blocks/objects/components/attribute-field-input.tsx +82 -0
  257. package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
  258. package/blocks/objects/components/object-not-found.tsx +34 -0
  259. package/blocks/objects/components/object-record-detail.tsx +143 -0
  260. package/blocks/objects/components/object-schema-editor.tsx +197 -0
  261. package/blocks/objects/components/object-table.tsx +130 -0
  262. package/blocks/objects/components/record-form-dialog.tsx +112 -0
  263. package/blocks/objects/components/record-roles-panel.tsx +106 -0
  264. package/blocks/objects/hooks/use-object-schema.ts +36 -0
  265. package/blocks/objects/lib/object-types.ts +214 -0
  266. package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
  267. package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
  268. package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
  269. package/blocks/pages/README.md +104 -0
  270. package/blocks/pages/block.json +223 -0
  271. package/blocks/pages/components/brand-kit-editor.tsx +152 -0
  272. package/blocks/pages/components/content-library.tsx +163 -0
  273. package/blocks/pages/components/page-editor.tsx +253 -0
  274. package/blocks/pages/components/page-list.tsx +159 -0
  275. package/blocks/pages/components/seo-report.tsx +159 -0
  276. package/blocks/pages/lib/studio.ts +77 -0
  277. package/blocks/pages/recipes/audit-site-seo.ts +28 -0
  278. package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
  279. package/blocks/pages/recipes/edit-page-section.ts +25 -0
  280. package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
  281. package/blocks/quotes/README.md +79 -0
  282. package/blocks/quotes/block.json +183 -0
  283. package/blocks/quotes/components/product-picker.tsx +80 -0
  284. package/blocks/quotes/components/quote-builder.tsx +195 -0
  285. package/blocks/quotes/components/quote-list.tsx +79 -0
  286. package/blocks/quotes/components/quote-viewer.tsx +135 -0
  287. package/blocks/quotes/components/signature-status.tsx +77 -0
  288. package/blocks/quotes/hooks/use-debounced.ts +13 -0
  289. package/blocks/quotes/lib/quotes.ts +142 -0
  290. package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
  291. package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
  292. package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
  293. package/blocks/radar/README.md +106 -0
  294. package/blocks/radar/block.json +189 -0
  295. package/blocks/radar/components/competitor-battle-card.tsx +53 -0
  296. package/blocks/radar/components/competitor-tracker.tsx +158 -0
  297. package/blocks/radar/components/radar-overview.tsx +67 -0
  298. package/blocks/radar/components/radar-report.tsx +86 -0
  299. package/blocks/radar/hooks/use-competitors.ts +23 -0
  300. package/blocks/radar/lib/radar-types.ts +124 -0
  301. package/blocks/radar/recipes/battle-card.ts +28 -0
  302. package/blocks/radar/recipes/deep-dive-report.ts +32 -0
  303. package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
  304. package/blocks/radar/recipes/track-and-scan.ts +39 -0
  305. package/blocks/search/README.md +129 -0
  306. package/blocks/search/block.json +185 -0
  307. package/blocks/search/components/filter-builder.tsx +149 -0
  308. package/blocks/search/components/prospect-results.tsx +175 -0
  309. package/blocks/search/components/save-to-list-dialog.tsx +135 -0
  310. package/blocks/search/components/saved-searches.tsx +95 -0
  311. package/blocks/search/hooks/use-filter-options.ts +41 -0
  312. package/blocks/search/lib/search-types.ts +149 -0
  313. package/blocks/search/recipes/explore-filter-values.ts +27 -0
  314. package/blocks/search/recipes/saved-searches.ts +34 -0
  315. package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
  316. package/blocks/sequencer/README.md +87 -0
  317. package/blocks/sequencer/block.json +214 -0
  318. package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
  319. package/blocks/sequencer/components/sequence-builder.tsx +306 -0
  320. package/blocks/sequencer/components/sequence-list.tsx +200 -0
  321. package/blocks/sequencer/components/sequence-stats.tsx +256 -0
  322. package/blocks/sequencer/components/step-editor.tsx +213 -0
  323. package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
  324. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
  325. package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
  326. package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
  327. package/blocks/sequencer/recipes/sequence-report.ts +22 -0
  328. package/blocks/social-ads/README.md +128 -0
  329. package/blocks/social-ads/block.json +256 -0
  330. package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
  331. package/blocks/social-ads/components/audience-sync.tsx +150 -0
  332. package/blocks/social-ads/components/creative-gallery.tsx +121 -0
  333. package/blocks/social-ads/components/post-composer.tsx +122 -0
  334. package/blocks/social-ads/components/social-post-list.tsx +116 -0
  335. package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
  336. package/blocks/social-ads/lib/social-ads-types.ts +167 -0
  337. package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
  338. package/blocks/social-ads/recipes/schedule-post.ts +51 -0
  339. package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
  340. package/blocks/visitors/README.md +56 -0
  341. package/blocks/visitors/block.json +139 -0
  342. package/blocks/visitors/components/snippet-install.tsx +169 -0
  343. package/blocks/visitors/components/traffic-chart.tsx +156 -0
  344. package/blocks/visitors/components/visitor-company-card.tsx +92 -0
  345. package/blocks/visitors/components/visitor-feed.tsx +157 -0
  346. package/blocks/visitors/lib/visitors-utils.ts +78 -0
  347. package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
  348. package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
  349. package/blocks/visitors/recipes/traffic-summary.ts +24 -0
  350. package/blocks/webchat/README.md +87 -0
  351. package/blocks/webchat/block.json +134 -0
  352. package/blocks/webchat/components/chat-widget-config.tsx +160 -0
  353. package/blocks/webchat/components/conversation-list.tsx +74 -0
  354. package/blocks/webchat/lib/webchat-types.ts +83 -0
  355. package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
  356. package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
  357. package/blocks/workbench/README.md +135 -0
  358. package/blocks/workbench/block.json +259 -0
  359. package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
  360. package/blocks/workbench/components/run-progress.tsx +132 -0
  361. package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
  362. package/blocks/workbench/components/workbench-grid.tsx +159 -0
  363. package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
  364. package/blocks/workbench/components/workbench-table-list.tsx +105 -0
  365. package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
  366. package/blocks/workbench/lib/waterfall-presets.ts +119 -0
  367. package/blocks/workbench/lib/workbench-types.ts +191 -0
  368. package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
  369. package/blocks/workbench/recipes/import-from-search.ts +42 -0
  370. package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
  371. package/blocks/workbench/recipes/waterfall-column.ts +55 -0
  372. package/blocks/workflows/README.md +100 -0
  373. package/blocks/workflows/block.json +188 -0
  374. package/blocks/workflows/components/run-history.tsx +136 -0
  375. package/blocks/workflows/components/trigger-config.tsx +134 -0
  376. package/blocks/workflows/components/workflow-editor.tsx +142 -0
  377. package/blocks/workflows/components/workflow-list.tsx +153 -0
  378. package/blocks/workflows/hooks/use-workflow.ts +51 -0
  379. package/blocks/workflows/lib/graph.ts +187 -0
  380. package/blocks/workflows/recipes/create-workflow.ts +35 -0
  381. package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
  382. package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
  383. package/blocks/workflows/recipes/set-trigger.ts +22 -0
  384. package/dist/cli.js +5330 -29
  385. package/dist/index.browser.js +7740 -0
  386. package/dist/index.browser.js.map +1 -0
  387. package/dist/index.browser.mjs +7717 -0
  388. package/dist/index.browser.mjs.map +1 -0
  389. package/dist/index.d.mts +2220 -23
  390. package/dist/index.d.ts +2220 -23
  391. package/dist/index.js +64 -3
  392. package/dist/index.js.map +1 -1
  393. package/dist/index.mjs +64 -3
  394. package/dist/index.mjs.map +1 -1
  395. package/dist/react.browser.js +7598 -0
  396. package/dist/react.browser.js.map +1 -0
  397. package/dist/react.browser.mjs +7568 -0
  398. package/dist/react.browser.mjs.map +1 -0
  399. package/dist/react.d.mts +2082 -20
  400. package/dist/react.d.ts +2082 -20
  401. package/dist/react.js +63 -2
  402. package/dist/react.js.map +1 -1
  403. package/dist/react.mjs +63 -2
  404. package/dist/react.mjs.map +1 -1
  405. package/dist/types/agentSetup.d.ts +6 -6
  406. package/dist/types/blocks/add.d.ts +53 -0
  407. package/dist/types/blocks/create.d.ts +46 -0
  408. package/dist/types/blocks/doctor.d.ts +26 -0
  409. package/dist/types/blocks/registry.d.ts +134 -0
  410. package/dist/types/cli.d.ts +16 -0
  411. package/dist/types/core.d.ts +1 -1
  412. package/dist/types/crypto-browser.d.ts +2 -0
  413. package/dist/types/crypto-node.d.ts +6 -0
  414. package/dist/types/react.d.ts +1 -1
  415. package/dist/types/version.d.ts +2 -2
  416. package/dist/types/webhooks.d.ts +2 -2
  417. package/dist/ui/index.d.mts +54 -0
  418. package/dist/ui/index.d.ts +54 -0
  419. package/dist/ui/index.js +105 -0
  420. package/dist/ui/index.js.map +1 -0
  421. package/dist/ui/index.mjs +44 -0
  422. package/dist/ui/index.mjs.map +1 -0
  423. package/dist/ui/lib/compose-refs.d.mts +19 -0
  424. package/dist/ui/lib/compose-refs.d.ts +19 -0
  425. package/dist/ui/lib/compose-refs.js +76 -0
  426. package/dist/ui/lib/compose-refs.js.map +1 -0
  427. package/dist/ui/lib/compose-refs.mjs +41 -0
  428. package/dist/ui/lib/compose-refs.mjs.map +1 -0
  429. package/dist/ui/lib/use-mobile.d.mts +4 -0
  430. package/dist/ui/lib/use-mobile.d.ts +4 -0
  431. package/dist/ui/lib/use-mobile.js +57 -0
  432. package/dist/ui/lib/use-mobile.js.map +1 -0
  433. package/dist/ui/lib/use-mobile.mjs +22 -0
  434. package/dist/ui/lib/use-mobile.mjs.map +1 -0
  435. package/dist/ui/lib/utils.d.mts +10 -0
  436. package/dist/ui/lib/utils.d.ts +10 -0
  437. package/dist/ui/lib/utils.js +33 -0
  438. package/dist/ui/lib/utils.js.map +1 -0
  439. package/dist/ui/lib/utils.mjs +9 -0
  440. package/dist/ui/lib/utils.mjs.map +1 -0
  441. package/dist/ui/primitives/alert-dialog.d.mts +22 -0
  442. package/dist/ui/primitives/alert-dialog.d.ts +22 -0
  443. package/dist/ui/primitives/alert-dialog.js +163 -0
  444. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  445. package/dist/ui/primitives/alert-dialog.mjs +119 -0
  446. package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
  447. package/dist/ui/primitives/avatar.d.mts +8 -0
  448. package/dist/ui/primitives/avatar.d.ts +8 -0
  449. package/dist/ui/primitives/avatar.js +91 -0
  450. package/dist/ui/primitives/avatar.js.map +1 -0
  451. package/dist/ui/primitives/avatar.mjs +55 -0
  452. package/dist/ui/primitives/avatar.mjs.map +1 -0
  453. package/dist/ui/primitives/breadcrumb.d.mts +13 -0
  454. package/dist/ui/primitives/breadcrumb.d.ts +13 -0
  455. package/dist/ui/primitives/breadcrumb.js +127 -0
  456. package/dist/ui/primitives/breadcrumb.js.map +1 -0
  457. package/dist/ui/primitives/breadcrumb.mjs +97 -0
  458. package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
  459. package/dist/ui/primitives/button.d.mts +15 -0
  460. package/dist/ui/primitives/button.d.ts +15 -0
  461. package/dist/ui/primitives/button.js +91 -0
  462. package/dist/ui/primitives/button.js.map +1 -0
  463. package/dist/ui/primitives/button.mjs +56 -0
  464. package/dist/ui/primitives/button.mjs.map +1 -0
  465. package/dist/ui/primitives/card.d.mts +10 -0
  466. package/dist/ui/primitives/card.d.ts +10 -0
  467. package/dist/ui/primitives/card.js +101 -0
  468. package/dist/ui/primitives/card.js.map +1 -0
  469. package/dist/ui/primitives/card.mjs +62 -0
  470. package/dist/ui/primitives/card.mjs.map +1 -0
  471. package/dist/ui/primitives/carousel.d.mts +45 -0
  472. package/dist/ui/primitives/carousel.d.ts +45 -0
  473. package/dist/ui/primitives/carousel.js +300 -0
  474. package/dist/ui/primitives/carousel.js.map +1 -0
  475. package/dist/ui/primitives/carousel.mjs +261 -0
  476. package/dist/ui/primitives/carousel.mjs.map +1 -0
  477. package/dist/ui/primitives/chart.d.mts +68 -0
  478. package/dist/ui/primitives/chart.d.ts +68 -0
  479. package/dist/ui/primitives/chart.js +280 -0
  480. package/dist/ui/primitives/chart.js.map +1 -0
  481. package/dist/ui/primitives/chart.mjs +241 -0
  482. package/dist/ui/primitives/chart.mjs.map +1 -0
  483. package/dist/ui/primitives/collapsible.d.mts +8 -0
  484. package/dist/ui/primitives/collapsible.d.ts +8 -0
  485. package/dist/ui/primitives/collapsible.js +72 -0
  486. package/dist/ui/primitives/collapsible.js.map +1 -0
  487. package/dist/ui/primitives/collapsible.mjs +36 -0
  488. package/dist/ui/primitives/collapsible.mjs.map +1 -0
  489. package/dist/ui/primitives/context-menu.d.mts +29 -0
  490. package/dist/ui/primitives/context-menu.d.ts +29 -0
  491. package/dist/ui/primitives/context-menu.js +203 -0
  492. package/dist/ui/primitives/context-menu.js.map +1 -0
  493. package/dist/ui/primitives/context-menu.mjs +155 -0
  494. package/dist/ui/primitives/context-menu.mjs.map +1 -0
  495. package/dist/ui/primitives/dialog.d.mts +26 -0
  496. package/dist/ui/primitives/dialog.d.ts +26 -0
  497. package/dist/ui/primitives/dialog.js +192 -0
  498. package/dist/ui/primitives/dialog.js.map +1 -0
  499. package/dist/ui/primitives/dialog.mjs +148 -0
  500. package/dist/ui/primitives/dialog.mjs.map +1 -0
  501. package/dist/ui/primitives/drawer.d.mts +26 -0
  502. package/dist/ui/primitives/drawer.d.ts +26 -0
  503. package/dist/ui/primitives/drawer.js +146 -0
  504. package/dist/ui/primitives/drawer.js.map +1 -0
  505. package/dist/ui/primitives/drawer.mjs +103 -0
  506. package/dist/ui/primitives/drawer.mjs.map +1 -0
  507. package/dist/ui/primitives/input.d.mts +5 -0
  508. package/dist/ui/primitives/input.d.ts +5 -0
  509. package/dist/ui/primitives/input.js +46 -0
  510. package/dist/ui/primitives/input.js.map +1 -0
  511. package/dist/ui/primitives/input.mjs +22 -0
  512. package/dist/ui/primitives/input.mjs.map +1 -0
  513. package/dist/ui/primitives/item.d.mts +27 -0
  514. package/dist/ui/primitives/item.d.ts +27 -0
  515. package/dist/ui/primitives/item.js +221 -0
  516. package/dist/ui/primitives/item.js.map +1 -0
  517. package/dist/ui/primitives/item.mjs +188 -0
  518. package/dist/ui/primitives/item.mjs.map +1 -0
  519. package/dist/ui/primitives/kbd.d.mts +15 -0
  520. package/dist/ui/primitives/kbd.d.ts +15 -0
  521. package/dist/ui/primitives/kbd.js +45 -0
  522. package/dist/ui/primitives/kbd.js.map +1 -0
  523. package/dist/ui/primitives/kbd.mjs +21 -0
  524. package/dist/ui/primitives/kbd.mjs.map +1 -0
  525. package/dist/ui/primitives/label.d.mts +6 -0
  526. package/dist/ui/primitives/label.d.ts +6 -0
  527. package/dist/ui/primitives/label.js +55 -0
  528. package/dist/ui/primitives/label.js.map +1 -0
  529. package/dist/ui/primitives/label.mjs +21 -0
  530. package/dist/ui/primitives/label.mjs.map +1 -0
  531. package/dist/ui/primitives/resizable.d.mts +10 -0
  532. package/dist/ui/primitives/resizable.d.ts +10 -0
  533. package/dist/ui/primitives/resizable.js +68 -0
  534. package/dist/ui/primitives/resizable.js.map +1 -0
  535. package/dist/ui/primitives/resizable.mjs +42 -0
  536. package/dist/ui/primitives/resizable.mjs.map +1 -0
  537. package/dist/ui/primitives/save-status.d.mts +31 -0
  538. package/dist/ui/primitives/save-status.d.ts +31 -0
  539. package/dist/ui/primitives/save-status.js +150 -0
  540. package/dist/ui/primitives/save-status.js.map +1 -0
  541. package/dist/ui/primitives/save-status.mjs +126 -0
  542. package/dist/ui/primitives/save-status.mjs.map +1 -0
  543. package/dist/ui/primitives/separator.d.mts +6 -0
  544. package/dist/ui/primitives/separator.d.ts +6 -0
  545. package/dist/ui/primitives/separator.js +62 -0
  546. package/dist/ui/primitives/separator.js.map +1 -0
  547. package/dist/ui/primitives/separator.mjs +28 -0
  548. package/dist/ui/primitives/separator.mjs.map +1 -0
  549. package/dist/ui/primitives/sheet.d.mts +16 -0
  550. package/dist/ui/primitives/sheet.d.ts +16 -0
  551. package/dist/ui/primitives/sheet.js +170 -0
  552. package/dist/ui/primitives/sheet.js.map +1 -0
  553. package/dist/ui/primitives/sheet.mjs +129 -0
  554. package/dist/ui/primitives/sheet.mjs.map +1 -0
  555. package/dist/ui/primitives/sidebar.d.mts +74 -0
  556. package/dist/ui/primitives/sidebar.d.ts +74 -0
  557. package/dist/ui/primitives/sidebar.js +697 -0
  558. package/dist/ui/primitives/sidebar.js.map +1 -0
  559. package/dist/ui/primitives/sidebar.mjs +651 -0
  560. package/dist/ui/primitives/sidebar.mjs.map +1 -0
  561. package/dist/ui/primitives/skeleton.d.mts +5 -0
  562. package/dist/ui/primitives/skeleton.d.ts +5 -0
  563. package/dist/ui/primitives/skeleton.js +40 -0
  564. package/dist/ui/primitives/skeleton.js.map +1 -0
  565. package/dist/ui/primitives/skeleton.mjs +16 -0
  566. package/dist/ui/primitives/skeleton.mjs.map +1 -0
  567. package/dist/ui/primitives/slider.d.mts +20 -0
  568. package/dist/ui/primitives/slider.d.ts +20 -0
  569. package/dist/ui/primitives/slider.js +112 -0
  570. package/dist/ui/primitives/slider.js.map +1 -0
  571. package/dist/ui/primitives/slider.mjs +78 -0
  572. package/dist/ui/primitives/slider.mjs.map +1 -0
  573. package/dist/ui/primitives/sortable.d.mts +50 -0
  574. package/dist/ui/primitives/sortable.d.ts +50 -0
  575. package/dist/ui/primitives/sortable.js +456 -0
  576. package/dist/ui/primitives/sortable.js.map +1 -0
  577. package/dist/ui/primitives/sortable.mjs +435 -0
  578. package/dist/ui/primitives/sortable.mjs.map +1 -0
  579. package/dist/ui/primitives/switch.d.mts +6 -0
  580. package/dist/ui/primitives/switch.d.ts +6 -0
  581. package/dist/ui/primitives/switch.js +67 -0
  582. package/dist/ui/primitives/switch.js.map +1 -0
  583. package/dist/ui/primitives/switch.mjs +33 -0
  584. package/dist/ui/primitives/switch.mjs.map +1 -0
  585. package/dist/ui/primitives/table.d.mts +12 -0
  586. package/dist/ui/primitives/table.d.ts +12 -0
  587. package/dist/ui/primitives/table.js +130 -0
  588. package/dist/ui/primitives/table.js.map +1 -0
  589. package/dist/ui/primitives/table.mjs +89 -0
  590. package/dist/ui/primitives/table.mjs.map +1 -0
  591. package/dist/ui/primitives/tabs.d.mts +9 -0
  592. package/dist/ui/primitives/tabs.d.ts +9 -0
  593. package/dist/ui/primitives/tabs.js +86 -0
  594. package/dist/ui/primitives/tabs.js.map +1 -0
  595. package/dist/ui/primitives/tabs.mjs +49 -0
  596. package/dist/ui/primitives/tabs.mjs.map +1 -0
  597. package/dist/ui/primitives/textarea.d.mts +5 -0
  598. package/dist/ui/primitives/textarea.d.ts +5 -0
  599. package/dist/ui/primitives/textarea.js +43 -0
  600. package/dist/ui/primitives/textarea.js.map +1 -0
  601. package/dist/ui/primitives/textarea.mjs +19 -0
  602. package/dist/ui/primitives/textarea.mjs.map +1 -0
  603. package/dist/ui/primitives/tooltip.d.mts +9 -0
  604. package/dist/ui/primitives/tooltip.d.ts +9 -0
  605. package/dist/ui/primitives/tooltip.js +94 -0
  606. package/dist/ui/primitives/tooltip.js.map +1 -0
  607. package/dist/ui/primitives/tooltip.mjs +57 -0
  608. package/dist/ui/primitives/tooltip.mjs.map +1 -0
  609. package/docs/agents.md +72 -29
  610. package/docs/api/README.md +7 -3
  611. package/docs/api/appObjects.d.ts +3 -1
  612. package/docs/api/appObjects.md +2 -1
  613. package/docs/api/by-path.md +51 -0
  614. package/docs/api/dataPipelines.d.ts +149 -0
  615. package/docs/api/dataPipelines.md +232 -0
  616. package/docs/api/forms.d.ts +16 -0
  617. package/docs/api/forms.md +8 -0
  618. package/docs/api/objects.d.ts +705 -5
  619. package/docs/api/objects.md +1099 -14
  620. package/docs/api/ops.d.ts +1 -1
  621. package/docs/api/ops.md +1 -1
  622. package/docs/api/processLists.d.ts +247 -0
  623. package/docs/api/processLists.md +409 -0
  624. package/docs/api/quotes.d.ts +3 -1
  625. package/docs/api/quotes.md +7 -2
  626. package/docs/api/radar.d.ts +4 -0
  627. package/docs/api/radar.md +1 -0
  628. package/docs/api/reports.d.ts +476 -0
  629. package/docs/api/reports.md +539 -0
  630. package/docs/api/scheduleBuy.d.ts +12 -8
  631. package/docs/api/scheduleBuy.md +13 -10
  632. package/docs/api/seo.d.ts +406 -3
  633. package/docs/api/seo.md +505 -1
  634. package/docs/api/workspaceBackup.d.ts +324 -0
  635. package/docs/api/workspaceBackup.md +427 -0
  636. package/package.json +174 -5
  637. package/stacks/6sense.json +46 -0
  638. package/stacks/README.md +20 -0
  639. package/stacks/apollo.json +128 -0
  640. package/stacks/clay.json +59 -0
  641. package/stacks/gong.json +72 -0
  642. package/stacks/hubspot.json +162 -0
  643. package/stacks/intercom.json +53 -0
  644. package/stacks/mailchimp.json +71 -0
  645. package/stacks/outreach.json +86 -0
  646. package/stacks/salesforce.json +94 -0
  647. package/stacks/stack.schema.json +32 -0
  648. package/stacks/starter.json +22 -0
  649. package/styles/styles.css +2 -0
  650. package/styles/theme.css +181 -0
  651. package/styles/tokens.css +284 -0
  652. package/templates/next-app/README.md.tmpl +35 -0
  653. package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
  654. package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
  655. package/templates/next-app/app/globals.css.tmpl +8 -0
  656. package/templates/next-app/app/layout.tsx.tmpl +19 -0
  657. package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
  658. package/templates/next-app/dot-env.local.example.tmpl +6 -0
  659. package/templates/next-app/dot-gitignore.tmpl +7 -0
  660. package/templates/next-app/lib/nav.ts.tmpl +2 -0
  661. package/templates/next-app/next.config.mjs.tmpl +4 -0
  662. package/templates/next-app/package.json.tmpl +25 -0
  663. package/templates/next-app/postcss.config.mjs.tmpl +4 -0
  664. package/templates/next-app/tsconfig.json.tmpl +21 -0
@@ -0,0 +1,189 @@
1
+ "use client";
2
+
3
+ /**
4
+ * TagAndAssign: apply tags to a conversation and hand it to a teammate.
5
+ *
6
+ * SDK operations, in order:
7
+ * 1. GET /inbox/users (aiInbox.listInboxUsers) read - teammates you can assign to
8
+ * 2. POST /inbox/tags (aiInbox.createInboxTag) write - only when you type a new tag
9
+ * 3. POST /inbox/{reply_id}/tag?channel= (inbox.tagReply) EXTERNAL -> ConfirmSpendDialog first
10
+ * 4. POST /inbox/{reply_id}/assign?channel= (inbox.assignReply) EXTERNAL -> ConfirmSpendDialog first
11
+ * Tagging and assigning are classified `external` because they can fire tag automations and notify the
12
+ * assignee, so they are confirmed like a send.
13
+ *
14
+ * There is no "list tags" operation: pass `availableTags` (collect them from InboxList rows, or keep your
15
+ * own list) and newly created tags are added to the picker for the session.
16
+ */
17
+ import { useEffect, useMemo, useState } from "react";
18
+ import { Button, Input } from "@graph8/sdk/ui";
19
+ import { ApiError } from "@/g8/components/api-error";
20
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
21
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
22
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
23
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
24
+ import type { InboxTag, InboxThread } from "../lib/inbox-types";
25
+ import { ThreadBadge } from "./thread-badge";
26
+
27
+ type Pending = { kind: "tag"; ids: string[] } | { kind: "assign"; email: string } | null;
28
+ interface TeamUser {
29
+ email: string;
30
+ name?: string | null;
31
+ }
32
+
33
+ export interface TagAndAssignProps {
34
+ thread: InboxThread;
35
+ availableTags?: InboxTag[];
36
+ /** Called after a tag or assignment succeeded; reload the thread here. */
37
+ onChanged?: () => void;
38
+ className?: string;
39
+ }
40
+
41
+ function usersFrom(data: unknown): TeamUser[] {
42
+ const raw = Array.isArray(data) ? data : ((data as { items?: unknown[] } | null)?.items ?? []);
43
+ return (raw as TeamUser[]).filter((u) => typeof u?.email === "string");
44
+ }
45
+
46
+ export function TagAndAssign({ thread, availableTags = [], onChanged, className }: TagAndAssignProps) {
47
+ const [created, setCreated] = useState<InboxTag[]>([]);
48
+ const [selected, setSelected] = useState<string[]>([]);
49
+ const [newTag, setNewTag] = useState("");
50
+ const [assignee, setAssignee] = useState("");
51
+ const [pending, setPending] = useState<Pending>(null);
52
+ // Stale context: picks made for one conversation never carry over to the next.
53
+ useEffect(() => { setSelected([]); setAssignee(""); setNewTag(""); setPending(null); }, [thread.id, thread.channel]);
54
+ const channelQuery = { channel: String(thread.channel) };
55
+
56
+ const users = useG8Query(async (signal) => usersFrom((await g8Get("/inbox/users", undefined, { signal })).data), []);
57
+
58
+ const applied = new Set(thread.tags.map((t) => t.id));
59
+ const options = useMemo(() => {
60
+ const seen = new Map<string, InboxTag>();
61
+ for (const t of [...availableTags, ...created]) if (t.id && !applied.has(t.id)) seen.set(t.id, t);
62
+ return [...seen.values()];
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, [availableTags, created, thread.tags]);
65
+
66
+ const createTag = useG8Mutation(async (name: string) => {
67
+ const env = await g8Post<{ id?: string; name?: string }>("/inbox/tags", { name });
68
+ return { id: String(env.data.id ?? ""), name: env.data.name ?? name } satisfies InboxTag;
69
+ });
70
+ const tag = useG8Mutation(async (ids: string[]) => {
71
+ // Only the NEW ids: association can fire tag automations, so already-applied tags are not re-sent.
72
+ return g8Post(`/inbox/${encodeURIComponent(thread.id)}/tag`, { tag_ids: ids.filter((id) => !applied.has(id)) }, { query: channelQuery });
73
+ });
74
+ const assign = useG8Mutation(async (email: string) =>
75
+ g8Post(`/inbox/${encodeURIComponent(thread.id)}/assign`, { assignee_email: email }, { query: channelQuery }),
76
+ );
77
+
78
+ const addTag = async () => {
79
+ const name = newTag.trim();
80
+ if (!name) return;
81
+ const made = await createTag.mutate(name);
82
+ if (made?.id) {
83
+ setCreated((prev) => [...prev, made]);
84
+ setSelected((prev) => [...prev, made.id as string]);
85
+ setNewTag("");
86
+ }
87
+ };
88
+
89
+ const confirm = async () => {
90
+ const current = pending;
91
+ if (!current) return;
92
+ try {
93
+ if (current.kind === "tag") {
94
+ await tag.mutateAsync(current.ids);
95
+ setSelected([]);
96
+ } else {
97
+ await assign.mutateAsync(current.email);
98
+ setAssignee("");
99
+ }
100
+ onChanged?.();
101
+ } catch {
102
+ /* surfaced by the ApiError below */
103
+ }
104
+ setPending(null);
105
+ };
106
+
107
+ const toggle = (id: string) => setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
108
+ const team = users.data ?? [];
109
+
110
+ return (
111
+ <div className={`space-y-4 rounded-lg border p-3 text-sm ${className ?? ""}`}>
112
+ <section aria-label="Tags" className="space-y-2">
113
+ <h3 className="font-medium">Tags</h3>
114
+ <div className="flex flex-wrap gap-1">
115
+ {thread.tags.length === 0 ? <span className="text-muted-foreground">No tags yet</span> : null}
116
+ {thread.tags.map((t) => (
117
+ <ThreadBadge key={t.id ?? t.name}>{t.name ?? t.id}</ThreadBadge>
118
+ ))}
119
+ </div>
120
+ <div className="flex flex-wrap gap-2">
121
+ {options.map((t) => (
122
+ <label key={t.id} className="flex items-center gap-1">
123
+ <input type="checkbox" checked={selected.includes(t.id as string)} onChange={() => toggle(t.id as string)} />
124
+ {t.name}
125
+ </label>
126
+ ))}
127
+ </div>
128
+ <div className="flex gap-2">
129
+ <Input aria-label="New tag" placeholder="New tag name" value={newTag} onChange={(e) => setNewTag(e.target.value)} />
130
+ <Button size="sm" variant="outline" disabled={!newTag.trim() || createTag.loading} onClick={() => void addTag()}>
131
+ Create
132
+ </Button>
133
+ </div>
134
+ <Button size="sm" disabled={selected.length === 0 || tag.loading} onClick={() => setPending({ kind: "tag", ids: selected })}>
135
+ Apply {selected.length > 0 ? `${selected.length} ` : ""}tag{selected.length === 1 ? "" : "s"}
136
+ </Button>
137
+ {createTag.error ? <ApiError error={createTag.error} title="Could not create the tag" /> : null}
138
+ {tag.error ? <ApiError error={tag.error} title="Could not apply the tags" /> : null}
139
+ </section>
140
+
141
+ <section aria-label="Assignee" className="space-y-2">
142
+ <h3 className="font-medium">Assign to</h3>
143
+ {users.error ? <ApiError error={users.error} title="Could not load teammates" onRetry={() => void users.refetch()} /> : null}
144
+ <div className="flex gap-2">
145
+ <select
146
+ aria-label="Teammate"
147
+ className="h-9 flex-1 rounded-md border bg-background px-2"
148
+ value={assignee}
149
+ disabled={users.loading}
150
+ onChange={(e) => setAssignee(e.target.value)}
151
+ >
152
+ <option value="">{users.loading ? "Loading teammates..." : "Choose a teammate"}</option>
153
+ {team.map((u) => (
154
+ <option key={u.email} value={u.email}>
155
+ {u.name ? `${u.name} (${u.email})` : u.email}
156
+ </option>
157
+ ))}
158
+ </select>
159
+ <Button size="sm" disabled={!assignee || assign.loading} onClick={() => setPending({ kind: "assign", email: assignee })}>
160
+ Assign
161
+ </Button>
162
+ </div>
163
+ {assign.error ? <ApiError error={assign.error} title="Could not assign the conversation" /> : null}
164
+ </section>
165
+
166
+ <ConfirmSpendDialog
167
+ open={pending?.kind === "tag"}
168
+ tier="external"
169
+ title="Apply tags to this conversation?"
170
+ description="Tags can trigger inbox automations and notifications."
171
+ confirmLabel="Apply tags"
172
+ confirming={tag.loading}
173
+ onConfirm={() => void confirm()}
174
+ onCancel={() => setPending(null)}
175
+ />
176
+ <ConfirmSpendDialog
177
+ open={pending?.kind === "assign"}
178
+ tier="external"
179
+ title={`Assign to ${pending?.kind === "assign" ? pending.email : ""}?`}
180
+ description="They are notified that the conversation is theirs."
181
+ confirmLabel="Assign"
182
+ recipients={1}
183
+ confirming={assign.loading}
184
+ onConfirm={() => void confirm()}
185
+ onCancel={() => setPending(null)}
186
+ />
187
+ </div>
188
+ );
189
+ }
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ThreadBadge: the small status pill ThreadView and TagAndAssign share. (The kit has no badge primitive; this is
5
+ * plain markup you can restyle.) No SDK operations.
6
+ */
7
+ import type { ReactNode } from "react";
8
+
9
+ const TONES = {
10
+ neutral: "bg-muted text-foreground",
11
+ info: "bg-blue-100 text-blue-900 dark:bg-blue-950 dark:text-blue-100",
12
+ warn: "bg-amber-100 text-amber-900 dark:bg-amber-950 dark:text-amber-100",
13
+ } as const;
14
+
15
+ export function ThreadBadge({ children, tone = "neutral" }: { children: ReactNode; tone?: keyof typeof TONES }) {
16
+ return <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${TONES[tone]}`}>{children}</span>;
17
+ }
@@ -0,0 +1,108 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ThreadView: one conversation, oldest message first, with the contact, tags, assignees and any unsent
5
+ * drafts clearly marked. It owns no write: replying is ReplyComposer, tagging and assigning is TagAndAssign.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. GET /inbox/{reply_id}?channel= (inbox.getReply) read
9
+ * Never use GET /inbox/{reply_id}/draft to "preview" a draft: it is classified read but CHARGES CREDITS.
10
+ * AI drafting lives in ReplyComposer behind a confirm dialog (POST /inbox/drafts/generate, billable).
11
+ *
12
+ * Compose it: <ThreadView thread={t}>{(thread) => <ReplyComposer thread={thread} />}</ThreadView>
13
+ */
14
+ import type { ReactNode } from "react";
15
+ import { ThreadBadge } from "./thread-badge";
16
+ import { Skeleton } from "@graph8/sdk/ui";
17
+ import { ApiError } from "@/g8/components/api-error";
18
+ import { EmptyState } from "@/g8/components/empty-state";
19
+ import { useInboxThread } from "../hooks/use-inbox-thread";
20
+ import { contactLabel, formatWhen, stripHtml, type InboxMessage, type InboxThread } from "../lib/inbox-types";
21
+
22
+ export interface ThreadViewProps {
23
+ /** A row from InboxList. Only `id` and `channel` are required; the rest is loaded fresh. */
24
+ thread: Pick<InboxThread, "id" | "channel"> & Partial<InboxThread>;
25
+ /** Render controls under the messages; receives the freshly loaded thread and a reload function. */
26
+ children?: (thread: InboxThread, reload: () => void) => ReactNode;
27
+ className?: string;
28
+ }
29
+
30
+ function MessageBubble({ message, outbound }: { message: InboxMessage; outbound: boolean }) {
31
+ const text = stripHtml(message.content);
32
+ return (
33
+ <li
34
+ data-draft={message.is_draft ? "true" : undefined}
35
+ className={`max-w-[85%] rounded-lg border p-3 text-sm ${outbound ? "ml-auto bg-muted" : "bg-background"} ${
36
+ message.is_draft ? "border-dashed" : ""
37
+ }`}
38
+ >
39
+ <div className="mb-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
40
+ <span className="font-medium text-foreground">{message.from_address ?? "Unknown sender"}</span>
41
+ {message.responder ? <span>via {message.responder}</span> : null}
42
+ {message.date ? <span>{formatWhen(message.date)}</span> : null}
43
+ {message.is_draft ? <ThreadBadge tone="warn">Unsent draft</ThreadBadge> : null}
44
+ </div>
45
+ <p className="whitespace-pre-wrap">{text || "(no content)"}</p>
46
+ </li>
47
+ );
48
+ }
49
+
50
+ function ThreadHeader({ thread }: { thread: InboxThread }) {
51
+ return (
52
+ <header className="space-y-1 border-b pb-3">
53
+ <h2 className="text-base font-semibold">{thread.subject || contactLabel(thread)}</h2>
54
+ <p className="text-sm text-muted-foreground">
55
+ {contactLabel(thread)}
56
+ {thread.contact?.company ? ` at ${thread.contact.company}` : ""} · <span className="capitalize">{thread.channel}</span>
57
+ {thread.status ? ` · ${thread.status.toLowerCase().replace("_", " ")}` : ""}
58
+ </p>
59
+ <div className="flex flex-wrap gap-1">
60
+ {thread.tags.map((t) => (
61
+ <ThreadBadge key={t.id ?? t.name}>{t.name ?? t.id}</ThreadBadge>
62
+ ))}
63
+ {thread.assignees.map((a) => (
64
+ <ThreadBadge key={a.email} tone="info">
65
+ {a.name || a.email}
66
+ </ThreadBadge>
67
+ ))}
68
+ </div>
69
+ </header>
70
+ );
71
+ }
72
+
73
+ export function ThreadView({ thread: row, children, className }: ThreadViewProps) {
74
+ const { data, loading, error, refetch } = useInboxThread(row.id, row.channel);
75
+
76
+ if (error) return <ApiError error={error} title="Could not load this conversation" onRetry={() => void refetch()} />;
77
+ if (loading && !data) {
78
+ return (
79
+ <div className="space-y-2" aria-busy="true" aria-label="Loading conversation">
80
+ <Skeleton className="h-6 w-1/2" />
81
+ <Skeleton className="h-20 w-full" />
82
+ <Skeleton className="h-20 w-3/4" />
83
+ </div>
84
+ );
85
+ }
86
+ if (!data) return <EmptyState title="Conversation not found" description="It may have been removed or moved to another channel." />;
87
+
88
+ const contactAddress = (data.contact?.email ?? data.contact?.phone ?? "").toLowerCase();
89
+ return (
90
+ <section className={`flex flex-col gap-3 ${className ?? ""}`}>
91
+ <ThreadHeader thread={data} />
92
+ {data.messages.length === 0 ? (
93
+ <EmptyState title="No messages yet" description="The first message appears here once the sequence or channel sends it." />
94
+ ) : (
95
+ <ol className="flex flex-col gap-2" aria-label="Messages">
96
+ {data.messages.map((m, i) => (
97
+ <MessageBubble
98
+ key={m.message_id ?? i}
99
+ message={m}
100
+ outbound={Boolean(m.from_address) && m.from_address?.toLowerCase() !== contactAddress}
101
+ />
102
+ ))}
103
+ </ol>
104
+ )}
105
+ {children?.(data, () => void refetch())}
106
+ </section>
107
+ );
108
+ }
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ /**
4
+ * useInboxThread: one conversation with all of its messages (drafts included, flagged `is_draft`).
5
+ *
6
+ * SDK operations: GET /inbox/{reply_id}?channel= (inbox.getReply), read tier.
7
+ *
8
+ * The `channel` is required by the API to disambiguate ids across email, SMS and LinkedIn; take it from
9
+ * the list row you opened.
10
+ */
11
+ import { g8Get } from "@/g8/lib/g8-client";
12
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
13
+ import type { InboxThread } from "../lib/inbox-types";
14
+
15
+ export function useInboxThread(threadId: string | null | undefined, channel: string | null | undefined) {
16
+ return useG8Query(
17
+ async (signal) => {
18
+ const env = await g8Get<InboxThread>(`/inbox/${encodeURIComponent(threadId as string)}`, { channel }, { signal });
19
+ return env.data;
20
+ },
21
+ [threadId, channel],
22
+ { enabled: Boolean(threadId) },
23
+ );
24
+ }
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ /**
4
+ * useInboxThreads: one page of the unified inbox, re-fetched when a filter or the page changes.
5
+ *
6
+ * SDK operations: GET /inbox (aiInbox / inbox.listInbox), read tier.
7
+ *
8
+ * Pagination here is page-number based (`page`, `page_size`, response `pagination.has_next`), unlike the
9
+ * cursor lists elsewhere in the API, so this hook wraps useG8Query rather than usePaginated.
10
+ *
11
+ * Caveat: the API cannot filter by an arbitrary assignee email, and an organisation API key owns no
12
+ * threads, so `assignee` is intentionally not exposed. Filter assignees client-side from `thread.assignees`.
13
+ */
14
+ import { g8Get } from "@/g8/lib/g8-client";
15
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
16
+ import type { InboxChannel, InboxThread } from "../lib/inbox-types";
17
+
18
+ export interface InboxFilters {
19
+ channel?: InboxChannel;
20
+ /** SENT, AI_RESPONDED or DRAFT. */
21
+ status?: string;
22
+ /** Tag id. */
23
+ tag?: string;
24
+ sequenceId?: string;
25
+ }
26
+
27
+ export function useInboxThreads(filters: InboxFilters, page = 1, pageSize = 25) {
28
+ const query = useG8Query(
29
+ async (signal) => {
30
+ const env = await g8Get<InboxThread[]>(
31
+ "/inbox",
32
+ { channel: filters.channel, status: filters.status, tag: filters.tag, sequence_id: filters.sequenceId, page, page_size: pageSize },
33
+ { signal },
34
+ );
35
+ return { threads: env.data ?? [], hasNext: Boolean(env.pagination?.has_next), total: (env.pagination as { total?: number } | undefined)?.total };
36
+ },
37
+ [filters.channel, filters.status, filters.tag, filters.sequenceId, page, pageSize],
38
+ );
39
+ return { ...query, threads: query.data?.threads ?? [], hasNext: query.data?.hasNext ?? false, total: query.data?.total };
40
+ }
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ /**
4
+ * useIdempotencyKey: one Idempotency-Key per distinct payload.
5
+ *
6
+ * The API remembers a key together with a fingerprint of the request body. Replaying the SAME body under the same
7
+ * key is a safe retry (a double click, a timeout). Reusing the key with a DIFFERENT body is a 409 for as long as
8
+ * the server holds it, and after a 5xx on a send/booking it holds the failure under the key for minutes. So the
9
+ * key must follow the payload: pass a string that changes whenever anything in the request body changes.
10
+ *
11
+ * const key = useIdempotencyKey(JSON.stringify([thread.id, body, to, subject]));
12
+ * g8Post(path, payload, { idempotencyKey: key.get() });
13
+ * // after a success: key.rotate()
14
+ */
15
+ import { useRef } from "react";
16
+
17
+ export function useIdempotencyKey(fingerprint: string) {
18
+ const ref = useRef({ fingerprint, key: crypto.randomUUID() });
19
+ return {
20
+ /** The key for the current payload; a new one as soon as the payload differs from the last call's. */
21
+ get(): string {
22
+ if (ref.current.fingerprint !== fingerprint) ref.current = { fingerprint, key: crypto.randomUUID() };
23
+ return ref.current.key;
24
+ },
25
+ /** Start a new logical request (call after a success so the next identical payload is a new action). */
26
+ rotate(): void {
27
+ ref.current = { fingerprint, key: crypto.randomUUID() };
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,100 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Shapes and small helpers shared by the inbox components. The unified inbox (`GET /inbox`) returns one
5
+ * thread shape for email, SMS and LinkedIn; the field names below are the API's, not ours.
6
+ */
7
+
8
+ export type InboxChannel = "email" | "sms" | "linkedin";
9
+ export const INBOX_CHANNELS: ReadonlyArray<{ value: InboxChannel; label: string }> = [
10
+ { value: "email", label: "Email" },
11
+ { value: "sms", label: "SMS" },
12
+ { value: "linkedin", label: "LinkedIn" },
13
+ ];
14
+
15
+ export interface InboxMessage {
16
+ message_id?: string | null;
17
+ content?: string | null;
18
+ date?: string | null;
19
+ from_address?: string | null;
20
+ to_addresses?: string[];
21
+ /** true for an unsent AI/human draft that lives in the thread. */
22
+ is_draft: boolean;
23
+ /** Who wrote it, when the API knows (a teammate or an agent). */
24
+ responder?: string | null;
25
+ }
26
+
27
+ export interface InboxTag {
28
+ id: string | null;
29
+ name: string | null;
30
+ }
31
+
32
+ export interface InboxAssignee {
33
+ email: string;
34
+ name?: string | null;
35
+ }
36
+
37
+ export interface InboxThread {
38
+ id: string;
39
+ channel: InboxChannel | string;
40
+ subject?: string | null;
41
+ /** Contact snapshot: name, email, company (and a phone or LinkedIn id for those channels). */
42
+ contact?: { name?: string | null; email?: string | null; company?: string | null; phone?: string | null } | null;
43
+ messages: InboxMessage[];
44
+ status?: string | null;
45
+ tags: InboxTag[];
46
+ assignees: InboxAssignee[];
47
+ created_at?: string | null;
48
+ updated_at?: string | null;
49
+ }
50
+
51
+ /** Statuses `GET /inbox?status=` accepts (case-insensitive). Anything else is a 422. */
52
+ export const INBOX_STATUSES = ["SENT", "AI_RESPONDED", "DRAFT"] as const;
53
+
54
+ /** Plain text from the API's HTML message bodies. Good enough for previews and for the reply textarea. */
55
+ export function stripHtml(html: string | null | undefined): string {
56
+ if (!html) return "";
57
+ return html
58
+ .replace(/<\s*br\s*\/?>/gi, "\n")
59
+ .replace(/<\/(p|div|li|h[1-6])>/gi, "\n")
60
+ .replace(/<[^>]+>/g, "")
61
+ .replace(/&nbsp;/g, " ")
62
+ .replace(/&amp;/g, "&")
63
+ .replace(/&lt;/g, "<")
64
+ .replace(/&gt;/g, ">")
65
+ .replace(/&quot;/g, '"')
66
+ .replace(/&#39;/g, "'")
67
+ .replace(/\n{3,}/g, "\n\n")
68
+ .trim();
69
+ }
70
+
71
+ export function contactLabel(thread: InboxThread): string {
72
+ const c = thread.contact;
73
+ return c?.name || c?.email || c?.phone || "Unknown contact";
74
+ }
75
+
76
+ /** The newest message that is not a draft, falling back to the newest of any kind. */
77
+ export function latestMessage(thread: InboxThread): InboxMessage | undefined {
78
+ const real = thread.messages.filter((m) => !m.is_draft);
79
+ const list = real.length > 0 ? real : thread.messages;
80
+ return list[list.length - 1];
81
+ }
82
+
83
+ export function previewText(thread: InboxThread, max = 120): string {
84
+ const text = stripHtml(latestMessage(thread)?.content).replace(/\s+/g, " ");
85
+ return text.length > max ? `${text.slice(0, max - 1)}...` : text;
86
+ }
87
+
88
+ export function formatWhen(iso: string | null | undefined): string {
89
+ if (!iso) return "";
90
+ const d = new Date(iso);
91
+ if (Number.isNaN(d.getTime())) return "";
92
+ return d.toLocaleString(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
93
+ }
94
+
95
+ /** Default recipient for a reply: the contact's address on the thread's channel. */
96
+ export function replyRecipient(thread: InboxThread): string {
97
+ const c = thread.contact;
98
+ if (thread.channel === "sms") return c?.phone ?? "";
99
+ return c?.email ?? "";
100
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @title Triage the inbox and answer a reply (draft with AI, then send)
3
+ * @when A server job or agent that reads new replies and responds. Drafting CHARGES CREDITS (billable) and
4
+ * sending reaches a real person (external): ask a human before running steps 3 and 4. Never call
5
+ * GET /inbox/{id}/draft for a preview; it is classified read but spends credits too.
6
+ */
7
+ import { randomUUID } from "node:crypto";
8
+ import { g8 } from "@graph8/sdk";
9
+
10
+ g8.init({ apiKey: process.env.G8_API_KEY! });
11
+
12
+ // 1. The newest email conversations. `data` is an array; `pagination.has_next` says if there is another page.
13
+ const inbox = await g8.api.inbox.listInbox({ query: { channel: "email", page: 1, page_size: 20 } });
14
+ const thread = inbox.data[0];
15
+ if (!thread) throw new Error("The inbox is empty");
16
+
17
+ // 2. The full conversation. `channel` is required because ids are only unique per channel.
18
+ const full = await g8.api.inbox.getReply({ path: { reply_id: thread.id }, query: { channel: "email" } });
19
+ const lastInbound = [...(full.data.messages ?? [])].reverse().find((m) => !m.is_draft);
20
+ console.log(full.data.subject, "-", lastInbound?.content);
21
+
22
+ // 3. BILLABLE: an AI draft grounded in the thread. It returns text and delivers nothing.
23
+ const draft = await g8.api.aiInbox.generateInboxDraft({
24
+ body: {
25
+ operation: "generate_from_scratch",
26
+ conversation_id: thread.id,
27
+ channel_type: "email",
28
+ objective: "follow_up",
29
+ tone: "professional",
30
+ message_html: "",
31
+ },
32
+ });
33
+ const html = (draft.data as { content?: string }).content ?? "";
34
+
35
+ // 4. EXTERNAL: send. Reuse the same idempotency key if you retry this exact call.
36
+ const sent = await g8.api.inbox.sendReply(
37
+ {
38
+ path: { reply_id: thread.id },
39
+ body: { channel: "email", body: html, subject: full.data.subject ? `Re: ${full.data.subject}` : null },
40
+ },
41
+ { idempotencyKey: randomUUID() },
42
+ );
43
+ console.log(sent.data.status, sent.data.message_id);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @title Tag a conversation and assign it to a teammate
3
+ * @when Routing replies: create (or reuse) a tag, apply it, hand the thread to the right person. Tagging
4
+ * and assigning are tier `external` (they can fire automations and notify the assignee), so confirm with a
5
+ * human. There is no list-tags operation: remember the tag id from `createInboxTag`.
6
+ */
7
+ import { g8 } from "@graph8/sdk";
8
+
9
+ g8.init({ apiKey: process.env.G8_API_KEY! });
10
+
11
+ const threadId = process.argv[2];
12
+ if (!threadId) throw new Error("usage: tsx 02-tag-and-assign.ts <thread id>");
13
+
14
+ // Teammates you can assign to.
15
+ const users = await g8.api.aiInbox.listInboxUsers();
16
+ const teammate = (users.data.items as Array<{ email: string }>)[0];
17
+ if (!teammate) throw new Error("No teammates found");
18
+
19
+ // Create the tag once and keep its id. (A second create with the same name may conflict.)
20
+ const created = await g8.api.aiInbox.createInboxTag({ body: { name: "Interested", ai_can_apply: false } });
21
+ const tagId = String((created.data as { id?: string }).id);
22
+
23
+ // Send the FULL set of tag ids the thread should carry: existing ids plus the new one.
24
+ const thread = await g8.api.inbox.getReply({ path: { reply_id: threadId }, query: { channel: "email" } });
25
+ const tagIds = [...(thread.data.tags ?? []).map((t) => String((t as { id?: string }).id)), tagId];
26
+ await g8.api.inbox.tagReply({ path: { reply_id: threadId }, query: { channel: "email" }, body: { tag_ids: tagIds } });
27
+ await g8.api.inbox.assignReply({
28
+ path: { reply_id: threadId },
29
+ query: { channel: "email" },
30
+ body: { assignee_email: teammate.email },
31
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @title Daily digest of the contacts who are engaging right now
3
+ * @when A morning job that tells a rep who to answer first. Read-only: it sends nothing and spends nothing.
4
+ */
5
+ import { g8 } from "@graph8/sdk";
6
+
7
+ g8.init({ apiKey: process.env.G8_API_KEY! });
8
+
9
+ const hot = await g8.api.inbox.hotContacts({ query: { days: 7, limit: 25 } });
10
+ console.log(`Hot contacts in the last ${hot.data.days} days:`);
11
+ for (const contact of hot.data.contacts ?? []) console.log(JSON.stringify(contact));
12
+
13
+ // Open threads that nobody has taken yet: the ones to assign first.
14
+ const inbox = await g8.api.inbox.listInbox({ query: { page_size: 50 } });
15
+ const unowned = inbox.data.filter((t) => (t.assignees ?? []).length === 0);
16
+ console.log(`${unowned.length} of ${inbox.data.length} conversations have no owner`);