@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,125 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "coach",
4
+ "title": "Sales coach",
5
+ "summary": "Graded practice-call scorecards (stages, verdicts, strengths, recording, transcript) and a coaching feed with a leaderboard.",
6
+ "features": [
7
+ "scorecard with overall score, stage results and checklist verdicts",
8
+ "summary, strengths and what to improve",
9
+ "on-demand signed recording and the transcript",
10
+ "coaching feed: newest graded calls with the first thing to work on",
11
+ "leaderboard ranked by total score",
12
+ "optional real dialer calls tab",
13
+ "delete (owner or manager) behind a confirmation"
14
+ ],
15
+ "dependsOn": [
16
+ "dialer"
17
+ ],
18
+ "operations": [
19
+ "create_call_sales_coach_calls_post",
20
+ "delete_call_sales_coach_calls__call_id__delete",
21
+ "get_call_audio_url_sales_coach_calls__call_id__audio_url_get",
22
+ "get_call_sales_coach_calls__call_id__get",
23
+ "get_call_transcript_sales_coach_calls__call_id__transcript_get",
24
+ "get_sales_coach_leaderboard_sales_coach_leaderboard_get",
25
+ "list_calls_for_contact_voice_dialer_calls_get",
26
+ "list_calls_sales_coach_calls_get"
27
+ ],
28
+ "components": [
29
+ {
30
+ "name": "CallScorecard",
31
+ "file": "components/call-scorecard.tsx",
32
+ "purpose": "One graded practice call: overall score, stages with checklist verdicts, strengths and improvements, recording on demand, transcript, delete (confirmed).",
33
+ "operations": [
34
+ "get_call_sales_coach_calls__call_id__get",
35
+ "get_call_transcript_sales_coach_calls__call_id__transcript_get",
36
+ "get_call_audio_url_sales_coach_calls__call_id__audio_url_get",
37
+ "delete_call_sales_coach_calls__call_id__delete"
38
+ ],
39
+ "tiers": [
40
+ "read",
41
+ "destructive"
42
+ ]
43
+ },
44
+ {
45
+ "name": "CoachingFeed",
46
+ "file": "components/coaching-feed.tsx",
47
+ "purpose": "Practice calls newest first with the first tip, a leaderboard tab, optional real dialer calls tab and a create-row form.",
48
+ "operations": [
49
+ "list_calls_sales_coach_calls_get",
50
+ "get_sales_coach_leaderboard_sales_coach_leaderboard_get",
51
+ "create_call_sales_coach_calls_post",
52
+ "list_calls_for_contact_voice_dialer_calls_get"
53
+ ],
54
+ "tiers": [
55
+ "read",
56
+ "write"
57
+ ]
58
+ }
59
+ ],
60
+ "hooks": [],
61
+ "lib": [
62
+ {
63
+ "file": "lib/coach-types.ts",
64
+ "purpose": "Call, segment and leaderboard shapes, and scorecardOf(), which reads checklist-stage and legacy three-category evaluations into one Scorecard."
65
+ }
66
+ ],
67
+ "recipes": [
68
+ {
69
+ "file": "recipes/read-a-scorecard.ts",
70
+ "title": "Read a call's score, stages and transcript",
71
+ "when": "Building a report or alert from graded practice calls."
72
+ },
73
+ {
74
+ "file": "recipes/weekly-coaching-digest.ts",
75
+ "title": "Weekly digest of the weakest areas per rep",
76
+ "when": "A manager wants one message per rep with what to work on."
77
+ }
78
+ ],
79
+ "prerequisites": [
80
+ {
81
+ "id": "dialer-installed",
82
+ "description": "The dialer block is installed (CoachingFeed's Real calls tab uses its CallLog).",
83
+ "where": "app",
84
+ "verify": "components/g8/dialer/components/call-log.tsx exists (the CLI installs it)."
85
+ },
86
+ {
87
+ "id": "manager-key",
88
+ "description": "To read other reps' calls the key must belong to an org admin or sales manager; a rep's key sees only its own.",
89
+ "where": "api",
90
+ "verify": "GET /sales-coach/calls?email=<another rep> answers 200, not 403."
91
+ },
92
+ {
93
+ "id": "practice-in-app",
94
+ "description": "The live practice conversation itself runs in the graph8 app; the API exposes the resulting calls, scores and transcripts.",
95
+ "where": "app",
96
+ "verify": "A rep has completed at least one practice call in graph8."
97
+ }
98
+ ],
99
+ "scopes": [
100
+ "team:delete",
101
+ "team:read",
102
+ "team:write",
103
+ "voice:read"
104
+ ],
105
+ "defaults": [
106
+ {
107
+ "decision": "Whose calls the feed shows",
108
+ "default": "the key owner's own",
109
+ "askHuman": false
110
+ },
111
+ {
112
+ "decision": "Show delete and the create-row form",
113
+ "default": "delete on (turn it off for reps with allowDelete), create form off",
114
+ "askHuman": false
115
+ },
116
+ {
117
+ "decision": "Who may read other reps' calls",
118
+ "default": "managers only; enforced by graph8, 403 otherwise",
119
+ "askHuman": true
120
+ }
121
+ ],
122
+ "peerDependencies": {
123
+ "react": ">=18"
124
+ }
125
+ }
@@ -0,0 +1,126 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CallScorecard: one graded practice call. Overall score, each stage's result with its checklist lines
5
+ * (met / partial / missed), the summary, strengths and improvements, the recording, and the transcript.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. getCall GET /sales-coach/calls/{call_id} (read) row + scores + inline segments
9
+ * 2. getCallTranscript GET /sales-coach/calls/{call_id}/transcript (read) ordered segments (owner or manager)
10
+ * 3. getCallAudioUrl GET /sales-coach/calls/{call_id}/audio-url (read) signed URL, fetched only on "Load recording"
11
+ * 4. deleteCall DELETE /sales-coach/calls/{call_id} (destructive, ConfirmSpendDialog)
12
+ *
13
+ * Reading another rep's call needs a manager key (403 otherwise; the error is shown as-is). Deleting a call
14
+ * removes its transcript and scores, so a score that was on the leaderboard disappears.
15
+ */
16
+
17
+ import { useEffect, useState } from "react";
18
+ import { Button, Skeleton } from "@graph8/sdk/ui";
19
+ import { ApiError } from "@/g8/components/api-error";
20
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
21
+ import { EmptyState } from "@/g8/components/empty-state";
22
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
23
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
24
+ import { g8Delete, g8Get } from "@/g8/lib/g8-client";
25
+ import { bodyOf, clock, listOf, scorecardOf } from "../lib/coach-types";
26
+ import type { CoachCall, CoachSegment } from "../lib/coach-types";
27
+
28
+ export interface CallScorecardProps {
29
+ callId: string;
30
+ /** Called after the call was deleted. */
31
+ onDeleted?: (callId: string) => void;
32
+ /** Hide the delete button (reps usually should not see it). */
33
+ allowDelete?: boolean;
34
+ className?: string;
35
+ }
36
+
37
+ const VERDICT_STYLE: Record<string, string> = { met: "text-green-700", partial: "text-amber-600", missed: "text-destructive" };
38
+
39
+ export function CallScorecard({ callId, onDeleted, allowDelete = true, className }: CallScorecardProps) {
40
+ const [audio, setAudio] = useState<{ callId: string; url: string } | null>(null);
41
+ const [deleting, setDeleting] = useState(false);
42
+
43
+ // A different call is a different context: drop the recording and any open confirmation.
44
+ useEffect(() => { setAudio(null); setDeleting(false); loadAudio.reset(); remove.reset(); }, [callId]); // eslint-disable-line react-hooks/exhaustive-deps
45
+
46
+ const call = useG8Query((signal) => g8Get(`/sales-coach/calls/${callId}`, undefined, { signal }), [callId]);
47
+ const transcript = useG8Query((signal) => g8Get(`/sales-coach/calls/${callId}/transcript`, undefined, { signal }), [callId]);
48
+ const loadAudio = useG8Mutation(async (id: string) => {
49
+ const res = bodyOf<{ signedUrl?: string; signed_url?: string }>(await g8Get(`/sales-coach/calls/${id}/audio-url`));
50
+ return { callId: id, url: res.signedUrl ?? res.signed_url ?? "" };
51
+ }, { onSuccess: (a) => setAudio(a) });
52
+ const remove = useG8Mutation((id: string) => g8Delete(`/sales-coach/calls/${id}`), { onSuccess: (_r, id) => { setDeleting(false); onDeleted?.(id); } });
53
+
54
+ const loadedRow = bodyOf<CoachCall>(call.data);
55
+ // Stale data from the previous call stays in the query while the new one loads: never show it next to this call's Delete.
56
+ const showingOther = Boolean(call.data) && loadedRow.id !== callId;
57
+ if (call.error) return <ApiError error={call.error} title="Could not load the call" onRetry={() => void call.refetch()} />;
58
+ if ((call.loading && !call.data) || showingOther) return <Skeleton className="h-48 w-full" aria-label="Loading call" />;
59
+
60
+ const row = loadedRow;
61
+ const card = scorecardOf(row);
62
+ const fetched = transcript.loading ? [] : listOf<CoachSegment>(transcript.data, "items");
63
+ const segments = fetched.length ? fetched : (row.transcript_segments ?? []);
64
+ const shownAudio = audio?.callId === callId ? audio.url : "";
65
+
66
+ return (
67
+ <div className={`flex flex-col gap-4 ${className ?? ""}`}>
68
+ <header className="flex flex-wrap items-start justify-between gap-3">
69
+ <div>
70
+ <h2 className="text-lg font-semibold">{row.prospect_name || "Practice call"}{row.prospect_company ? ` at ${row.prospect_company}` : ""}</h2>
71
+ <p className="text-sm text-muted-foreground">
72
+ {[row.user_email, row.call_mode, row.call_status, row.duration_ms ? clock(row.duration_ms) : null, row.date_of_call ? new Date(row.date_of_call).toLocaleDateString() : null].filter(Boolean).join(" - ")}
73
+ </p>
74
+ </div>
75
+ <div className="text-right">
76
+ <div data-testid="overall-score" className="text-3xl font-semibold">{card?.scorePct != null ? `${Math.round(card.scorePct)}%` : "-"}</div>
77
+ <div className="text-xs text-muted-foreground">{card ? "overall" : "not graded yet"}</div>
78
+ </div>
79
+ </header>
80
+
81
+ {!card ? <EmptyState title="Not graded yet" description="The score appears here once the call finishes and is graded." /> : (
82
+ <>
83
+ {card.summary ? <p className="text-sm">{card.summary}</p> : null}
84
+ <ul className="flex flex-col gap-3" data-testid="stages">
85
+ {card.stages.map((s) => (
86
+ <li key={s.label} className="rounded-md border p-3">
87
+ <div className="flex items-center justify-between"><span className="font-medium">{s.label}</span><span className="text-sm">{s.pct != null ? `${s.pct}%` : "n/a"}</span></div>
88
+ <ul className="mt-2 flex flex-col gap-1 text-sm">
89
+ {s.criteria.map((c, i) => (
90
+ <li key={i} className="flex gap-2"><span className={`w-16 shrink-0 text-xs font-medium uppercase ${VERDICT_STYLE[c.verdict] ?? ""}`}>{c.verdict || "-"}</span><span>{c.label}{c.evidence ? <span className="block text-xs text-muted-foreground">&quot;{c.evidence}&quot;</span> : null}</span></li>
91
+ ))}
92
+ </ul>
93
+ </li>
94
+ ))}
95
+ </ul>
96
+ <div className="grid gap-3 sm:grid-cols-2">
97
+ <div><h3 className="mb-1 text-sm font-semibold">Strengths</h3><ul className="list-disc pl-5 text-sm">{card.strengths.map((t, i) => <li key={i}>{t}</li>)}</ul></div>
98
+ <div><h3 className="mb-1 text-sm font-semibold">To improve</h3><ul className="list-disc pl-5 text-sm">{card.improvements.map((t, i) => <li key={i}>{t}</li>)}</ul></div>
99
+ </div>
100
+ </>
101
+ )}
102
+
103
+ <div className="flex flex-col gap-2">
104
+ <h3 className="text-sm font-semibold">Recording</h3>
105
+ {row.audio_url ? (shownAudio ? <audio controls src={shownAudio} className="w-full" /> : <div><Button size="sm" variant="outline" loading={loadAudio.loading} onClick={() => void loadAudio.mutate(callId)}>Load recording</Button></div>) : <p className="text-sm text-muted-foreground">No recording.</p>}
106
+ {loadAudio.error ? <ApiError error={loadAudio.error} title="Could not load the recording" /> : null}
107
+ </div>
108
+
109
+ <div className="flex flex-col gap-2">
110
+ <h3 className="text-sm font-semibold">Transcript</h3>
111
+ {transcript.error ? <ApiError error={transcript.error} title="Could not load the transcript" onRetry={() => void transcript.refetch()} /> : null}
112
+ <ul className="flex max-h-96 flex-col gap-1 overflow-y-auto" data-testid="transcript">
113
+ {segments.map((s, i) => (
114
+ <li key={s.id ?? i} className="text-sm"><span className="mr-2 text-xs text-muted-foreground">{clock(s.in_call_timestamp)}</span><span className="mr-2 font-medium">{s.speaker}</span>{s.content}</li>
115
+ ))}
116
+ </ul>
117
+ {!transcript.loading && !transcript.error && segments.length === 0 ? <p className="text-sm text-muted-foreground">No transcript.</p> : null}
118
+ </div>
119
+
120
+ {allowDelete ? <div><Button size="sm" variant="ghost" onClick={() => setDeleting(true)}>Delete call</Button></div> : null}
121
+ {remove.error ? <ApiError error={remove.error} title="Could not delete the call" /> : null}
122
+ <ConfirmSpendDialog open={deleting} tier="destructive" title="Delete this call" description="Its transcript, scores and recording link are removed, and its score leaves the leaderboard." affected={1}
123
+ confirming={remove.loading} onConfirm={() => void remove.mutate(callId)} onCancel={() => setDeleting(false)} />
124
+ </div>
125
+ );
126
+ }
@@ -0,0 +1,140 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CoachingFeed: a rep's (or, with a manager key, anyone's) practice calls as a coaching feed, newest first:
5
+ * each graded call shows its score and the first thing to work on. Click one to open its CallScorecard.
6
+ * A Leaderboard tab ranks the floor; an optional "Real calls" tab lists the rep's dialer calls (dialer block).
7
+ *
8
+ * SDK operations, in order:
9
+ * 1. listCalls GET /sales-coach/calls?email= (read) scores and segments inline; another rep's email is manager-only
10
+ * 2. getSalesCoachLeaderboard GET /sales-coach/leaderboard (read) completed calls, grouped by rep
11
+ * 3. createCall POST /sales-coach/calls { call_mode, prospect_name, prospect_company, persona_type } (write)
12
+ * 4. getCall, getCallTranscript, getCallAudioUrl, deleteCall via CallScorecard (see its header)
13
+ * 5. listCallsForContact GET /voice/dialer/calls?user_email= (read) via the dialer block's CallLog, "Real calls" tab only
14
+ *
15
+ * The create form and the Real calls tab need to know whose calls they are: the form is hidden when `userEmail` names another rep
16
+ * (it would create a call for the key owner), and the Real calls tab only shows when `userEmail` is set (the dialer API needs a rep).
17
+ * `createCall` only creates the call ROW. Running the practice conversation itself happens in the graph8 app
18
+ * (the live audio session is not part of the public API), so `allowCreate` is off by default.
19
+ */
20
+
21
+ import { useEffect, useState } from "react";
22
+ import { Button, Input, Label, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
23
+ import { ApiError } from "@/g8/components/api-error";
24
+ import { EmptyState } from "@/g8/components/empty-state";
25
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
26
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
27
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
28
+ import { CallLog } from "@/g8/dialer/components/call-log";
29
+ import { avgOf, listOf, scorecardOf } from "../lib/coach-types";
30
+ import type { CoachCall, LeaderboardRow } from "../lib/coach-types";
31
+ import { CallScorecard } from "./call-scorecard";
32
+
33
+ export interface CoachingFeedProps {
34
+ /** Whose practice calls. Omit for the key owner's own. Another rep's needs a manager key. */
35
+ userEmail?: string;
36
+ /** Called with a call id instead of opening the scorecard inline. */
37
+ onOpenCall?: (callId: string) => void;
38
+ /** Show the "New practice call" form (creates the row only). Default false. */
39
+ allowCreate?: boolean;
40
+ /** Add a "Real calls" tab with the rep's dialer calls. Default false. */
41
+ showDialerCalls?: boolean;
42
+ allowDelete?: boolean;
43
+ /** Rows shown before "Show more". Default 15. */
44
+ pageSize?: number;
45
+ className?: string;
46
+ }
47
+
48
+ type Tab = "feed" | "board" | "real";
49
+
50
+ export function CoachingFeed({ userEmail, onOpenCall, allowCreate = false, showDialerCalls = false, allowDelete, pageSize = 15, className }: CoachingFeedProps) {
51
+ const [tab, setTab] = useState<Tab>("feed");
52
+ const [open, setOpen] = useState<string | null>(null);
53
+ const [shown, setShown] = useState(pageSize);
54
+ const [form, setForm] = useState<{ call_mode: "assisted" | "compete"; prospect_name: string; prospect_company: string; persona_type: string } | null>(null);
55
+
56
+ // Another rep's calls are a different list: forget the selection and paging.
57
+ useEffect(() => { setOpen(null); setShown(pageSize); }, [userEmail, pageSize]);
58
+
59
+ const calls = useG8Query((signal) => g8Get("/sales-coach/calls", { email: userEmail }, { signal }), [userEmail]);
60
+ const board = useG8Query((signal) => g8Get("/sales-coach/leaderboard", undefined, { signal }), [], { enabled: tab === "board" });
61
+ const create = useG8Mutation(() => g8Post("/sales-coach/calls", { call_mode: form?.call_mode, prospect_name: form?.prospect_name.trim() || undefined, prospect_company: form?.prospect_company.trim() || undefined, persona_type: form?.persona_type.trim() || undefined }), {
62
+ onSuccess: () => { setForm(null); void calls.refetch(); },
63
+ });
64
+
65
+ const rows = listOf<CoachCall>(calls.data, "items");
66
+ const ranked = [...listOf<LeaderboardRow>(board.data, "items")].sort((a, b) => b.score - a.score);
67
+ const select = (id: string) => (onOpenCall ? onOpenCall(id) : setOpen(id));
68
+ const tabs: Array<[Tab, string]> = [["feed", "Practice calls"], ["board", "Leaderboard"], ...(showDialerCalls && userEmail ? [["real", "Real calls"] as [Tab, string]] : [])];
69
+
70
+ return (
71
+ <div className={`flex flex-col gap-3 ${className ?? ""}`}>
72
+ <div className="flex items-center justify-between border-b">
73
+ <div role="tablist" className="flex gap-1">
74
+ {tabs.map(([id, label]) => (
75
+ <button key={id} role="tab" type="button" aria-selected={tab === id} onClick={() => setTab(id)} className={`px-3 py-2 text-sm ${tab === id ? "border-b-2 border-primary font-medium" : "text-muted-foreground"}`}>{label}</button>
76
+ ))}
77
+ </div>
78
+ {allowCreate && !userEmail && tab === "feed" ? <Button size="sm" onClick={() => setForm({ call_mode: "assisted", prospect_name: "", prospect_company: "", persona_type: "" })}>New practice call</Button> : null}
79
+ </div>
80
+
81
+ {tab === "feed" ? (
82
+ <>
83
+ {form ? (
84
+ <div className="flex flex-col gap-2 rounded-md border p-3">
85
+ <div className="grid gap-2 sm:grid-cols-2">
86
+ <div className="flex flex-col gap-1"><Label htmlFor="pc-name">Prospect</Label><Input id="pc-name" value={form.prospect_name} onChange={(e) => setForm({ ...form, prospect_name: e.target.value })} /></div>
87
+ <div className="flex flex-col gap-1"><Label htmlFor="pc-company">Company</Label><Input id="pc-company" value={form.prospect_company} onChange={(e) => setForm({ ...form, prospect_company: e.target.value })} /></div>
88
+ <div className="flex flex-col gap-1"><Label htmlFor="pc-persona">Persona</Label><Input id="pc-persona" value={form.persona_type} onChange={(e) => setForm({ ...form, persona_type: e.target.value })} /></div>
89
+ <div className="flex flex-col gap-1"><Label htmlFor="pc-mode">Mode</Label>
90
+ <select id="pc-mode" className="h-9 rounded-md border bg-background px-2 text-sm" value={form.call_mode} onChange={(e) => setForm({ ...form, call_mode: e.target.value as "assisted" | "compete" })}><option value="assisted">Training</option><option value="compete">Compete</option></select></div>
91
+ </div>
92
+ {create.error ? <ApiError error={create.error} title="Could not create the call" /> : null}
93
+ <div className="flex justify-end gap-2"><Button variant="ghost" onClick={() => setForm(null)}>Cancel</Button><Button loading={create.loading} onClick={() => void create.mutate(undefined)}>Create call</Button></div>
94
+ </div>
95
+ ) : null}
96
+ {calls.error ? <ApiError error={calls.error} title="Could not load practice calls" onRetry={() => void calls.refetch()} /> : null}
97
+ {calls.loading && rows.length === 0 ? Array.from({ length: 3 }, (_, i) => <Skeleton key={i} className="h-16 w-full" />) : null}
98
+ <ul className="flex flex-col gap-2" data-testid="feed">
99
+ {rows.slice(0, shown).map((c) => {
100
+ const card = scorecardOf(c);
101
+ const tip = card?.improvements[0];
102
+ return (
103
+ <li key={c.id}>
104
+ <button type="button" data-testid={`call-${c.id}`} onClick={() => select(c.id)} className={`w-full rounded-md border p-3 text-left ${open === c.id ? "bg-accent" : ""}`}>
105
+ <span className="flex items-center justify-between"><span className="font-medium">{c.prospect_name || "Practice call"}{c.prospect_company ? ` at ${c.prospect_company}` : ""}</span><span className="text-sm">{card?.scorePct != null ? `${Math.round(card.scorePct)}%` : c.call_status ?? ""}</span></span>
106
+ <span className="block text-xs text-muted-foreground">{c.date_of_call ? new Date(c.date_of_call).toLocaleDateString() : ""} {c.call_mode ?? ""}</span>
107
+ {tip ? <span className="mt-1 block text-sm">Work on: {tip}</span> : null}
108
+ </button>
109
+ </li>
110
+ );
111
+ })}
112
+ </ul>
113
+ {!calls.loading && !calls.error && rows.length === 0 ? <EmptyState title="No practice calls yet" description="Graded practice calls and what to work on next show up here." /> : null}
114
+ {rows.length > shown ? <Button variant="outline" size="sm" onClick={() => setShown(shown + pageSize)}>Show more</Button> : null}
115
+ {open && !onOpenCall ? <CallScorecard key={open} callId={open} allowDelete={allowDelete} onDeleted={() => { setOpen(null); void calls.refetch(); void board.refetch(); }} /> : null}
116
+ </>
117
+ ) : null}
118
+
119
+ {tab === "board" ? (
120
+ <>
121
+ {board.error ? <ApiError error={board.error} title="Could not load the leaderboard" onRetry={() => void board.refetch()} /> : null}
122
+ <div className="rounded-md border">
123
+ <Table>
124
+ <TableHeader><TableRow><TableHead className="w-12">#</TableHead><TableHead>Rep</TableHead><TableHead className="text-right">Calls</TableHead><TableHead className="text-right">Avg</TableHead><TableHead className="text-right">Total</TableHead></TableRow></TableHeader>
125
+ <TableBody>
126
+ {board.loading && ranked.length === 0 ? Array.from({ length: 3 }, (_, i) => <TableRow key={i}><TableCell colSpan={5}><Skeleton className="h-5 w-full" /></TableCell></TableRow>) : null}
127
+ {ranked.map((r, i) => (
128
+ <TableRow key={r.email} data-testid={`rank-${r.email}`}><TableCell>{i + 1}</TableCell><TableCell>{r.name}<span className="ml-2 text-xs text-muted-foreground">{r.email}</span></TableCell><TableCell className="text-right">{r.calls}</TableCell><TableCell className="text-right">{avgOf(r)}</TableCell><TableCell className="text-right">{r.score}</TableCell></TableRow>
129
+ ))}
130
+ </TableBody>
131
+ </Table>
132
+ {!board.loading && !board.error && ranked.length === 0 ? <EmptyState title="Nobody on the board yet" description="Completed practice calls rank here." /> : null}
133
+ </div>
134
+ </>
135
+ ) : null}
136
+
137
+ {tab === "real" ? <CallLog userEmail={userEmail} limit={25} /> : null}
138
+ </div>
139
+ );
140
+ }
@@ -0,0 +1,116 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Shapes of the sales-coach routes and the reader that turns a call's stored evaluation into a scorecard.
5
+ *
6
+ * A practice call row carries `scores[0].evaluation_data` (sometimes a JSON string, sometimes an object,
7
+ * snake_case or camelCase depending on the route). Checklist-graded calls have `stages[].criteria[]` with a
8
+ * `verdict` of met / partial / missed; older calls have `evaluatorResult.{opening,pattern_interrupt,
9
+ * value_proposition}.score` out of 10. Both are turned into the same `Scorecard`.
10
+ */
11
+
12
+ import type { G8Envelope } from "@/g8/lib/g8-client";
13
+
14
+ export interface CoachSegment {
15
+ id?: string;
16
+ speaker: string;
17
+ content: string;
18
+ /** Milliseconds from the start of the call. */
19
+ in_call_timestamp?: number;
20
+ }
21
+
22
+ export interface CoachCall {
23
+ id: string;
24
+ user_email?: string;
25
+ prospect_name?: string | null;
26
+ prospect_company?: string | null;
27
+ date_of_call?: string | null;
28
+ call_mode?: string;
29
+ call_status?: string;
30
+ persona_type?: string | null;
31
+ audio_url?: string | null;
32
+ final_score?: number | null;
33
+ duration_ms?: number | null;
34
+ scores?: Array<{ evaluation_data?: unknown; evaluationData?: unknown; created_at?: string }>;
35
+ transcript_segments?: CoachSegment[];
36
+ }
37
+
38
+ export interface LeaderboardRow {
39
+ id?: string | null;
40
+ name: string;
41
+ email: string;
42
+ score: number;
43
+ calls: number;
44
+ avgScore?: number;
45
+ avg_score?: number;
46
+ }
47
+
48
+ export type Verdict = "met" | "partial" | "missed" | string;
49
+
50
+ export interface ScoreStage {
51
+ label: string;
52
+ /** 0-100, or null when no line applied. */
53
+ pct: number | null;
54
+ criteria: Array<{ label: string; verdict: Verdict; evidence?: string }>;
55
+ }
56
+
57
+ export interface Scorecard {
58
+ scorePct: number | null;
59
+ stages: ScoreStage[];
60
+ summary: string;
61
+ strengths: string[];
62
+ improvements: string[];
63
+ }
64
+
65
+ const POINTS: Record<string, number> = { met: 1, partial: 0.5, missed: 0 };
66
+ const LEGACY = [["opening", "Opening"], ["pattern_interrupt", "Pattern interrupt"], ["value_proposition", "Value proposition"]] as const;
67
+
68
+ export function bodyOf<T = Record<string, unknown>>(res: G8Envelope<unknown> | null | undefined): T {
69
+ const data = (res as { data?: unknown } | null | undefined)?.data;
70
+ return (data ?? res ?? {}) as T;
71
+ }
72
+
73
+ export function listOf<T>(res: G8Envelope<unknown> | null | undefined, ...keys: string[]): T[] {
74
+ const body = bodyOf<unknown>(res);
75
+ if (Array.isArray(body)) return body as T[];
76
+ const rec = (body ?? {}) as Record<string, any>;
77
+ for (const k of keys) if (Array.isArray(rec[k])) return rec[k] as T[];
78
+ return [];
79
+ }
80
+
81
+ function strings(v: unknown): string[] {
82
+ return Array.isArray(v) ? v.map((x) => (typeof x === "string" ? x : String((x as any)?.text ?? (x as any)?.content ?? ""))).filter(Boolean) : [];
83
+ }
84
+
85
+ /** The scorecard of a call, or null when it has not been graded. Never throws on odd data. */
86
+ export function scorecardOf(call: CoachCall | null | undefined): Scorecard | null {
87
+ const first = call?.scores?.[0];
88
+ let raw: any = first?.evaluation_data ?? first?.evaluationData;
89
+ if (typeof raw === "string") { try { raw = JSON.parse(raw); } catch { raw = null; } }
90
+ if (!raw || typeof raw !== "object") return null;
91
+ const er = raw.evaluatorResult ?? raw.evaluator_result ?? {};
92
+ const stages: ScoreStage[] = Array.isArray(raw.stages) && raw.stages.length
93
+ ? raw.stages.map((s: any) => {
94
+ const criteria = (Array.isArray(s.criteria) ? s.criteria : []).map((c: any) => ({ label: String(c.label ?? c.name ?? c.criterion ?? ""), verdict: String(c.verdict ?? ""), evidence: c.evidence ? String(c.evidence) : undefined }));
95
+ const judged = criteria.filter((c: { verdict: string }) => Object.hasOwn(POINTS, c.verdict));
96
+ const pct = judged.length ? Math.round((100 * judged.reduce((n: number, c: { verdict: string }) => n + POINTS[c.verdict], 0)) / judged.length) : null;
97
+ return { label: String(s.label ?? s.name ?? "Stage"), pct, criteria };
98
+ })
99
+ : LEGACY.filter(([k]) => er[k] != null).map(([k, label]) => ({ label, pct: Math.round(((er[k].score ?? 0) / 10) * 100), criteria: [] }));
100
+ // Legacy rows store `final_score` as a total out of 30 (three categories out of 10); checklist rows store a percent AND scorePct.
101
+ const checklist = Array.isArray(raw.stages) && raw.stages.length > 0;
102
+ const fromFinal = typeof call?.final_score === "number" ? (checklist ? call.final_score : Math.round((100 * call.final_score) / 30)) : null;
103
+ const scorePct = typeof raw.scorePct === "number" ? raw.scorePct : fromFinal;
104
+ return { scorePct, stages, summary: String(er.summary ?? ""), strengths: strings(er.areas_of_strength ?? er.strengths), improvements: strings(er.areas_of_improvement ?? er.improvements) };
105
+ }
106
+
107
+ export const avgOf = (r: LeaderboardRow): number => r.avgScore ?? r.avg_score ?? 0;
108
+
109
+ /** Legacy total score ceiling: three categories, ten points each. */
110
+ export const LEGACY_MAX = 30;
111
+
112
+ /** m:ss from milliseconds. */
113
+ export function clock(ms: number | undefined | null): string {
114
+ const s = Math.max(0, Math.round((ms ?? 0) / 1000));
115
+ return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
116
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @title Read a graded practice call: score, stages, transcript
3
+ * @when Building a report or an alert from practice calls. `listCalls` returns the key owner's calls newest
4
+ * first with their scores inline; `?email=` for another rep needs a manager key (403 otherwise). A call's
5
+ * `scores[0].evaluation_data` is either checklist `stages[].criteria[].verdict` (met / partial / missed) or
6
+ * the older `evaluatorResult` categories scored out of 10. Call ids come from the list, never a guess.
7
+ * Audio is a short-lived signed URL: fetch it when needed, do not store it.
8
+ */
9
+ import { g8 } from "@graph8/sdk";
10
+
11
+ g8.init({ apiKey: process.env.G8_API_KEY! });
12
+
13
+ export async function latestGradedCall(email?: string) {
14
+ const list = await g8.api.salesCoach.listCalls({ query: email ? { email } : {} });
15
+ const calls = (list.data.items ?? []) as Array<{ id: string; final_score?: number | null; scores?: unknown[] }>;
16
+ const graded = calls.find((c) => (c.scores?.length ?? 0) > 0);
17
+ if (!graded) return null;
18
+
19
+ const transcript = await g8.api.salesCoach.getCallTranscript({ path: { call_id: graded.id } });
20
+ return { callId: graded.id, score: graded.final_score ?? null, segments: transcript.data.items ?? [] };
21
+ }
22
+
23
+ export async function recordingUrl(callId: string) {
24
+ const res = await g8.api.salesCoach.getCallAudioUrl({ path: { call_id: callId } });
25
+ const data = res.data as { signedUrl?: string; signed_url?: string };
26
+ return data.signedUrl ?? data.signed_url ?? null;
27
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @title A weekly digest: each rep's weakest stage and first tip
3
+ * @when A manager wants one message per rep. Needs a manager key (other reps' calls are manager-only).
4
+ * The leaderboard gives the reps (email, calls, avg score); each rep's calls give the stages. Only calls
5
+ * with a score count. Sending the digest is up to you (email, Slack); nothing here contacts anyone.
6
+ */
7
+ import { g8 } from "@graph8/sdk";
8
+
9
+ g8.init({ apiKey: process.env.G8_API_KEY! });
10
+
11
+ type Stage = { label?: string; criteria?: Array<{ verdict?: string }> };
12
+ type Evaluation = { stages?: Stage[]; evaluatorResult?: { areas_of_improvement?: string[] } };
13
+ type Call = { date_of_call?: string | null; scores?: Array<{ evaluation_data?: Evaluation | string }> };
14
+
15
+ /** evaluation_data is an object on some routes and a JSON string on others. */
16
+ function evaluationOf(call: Call): Evaluation | undefined {
17
+ const raw = call.scores?.[0]?.evaluation_data;
18
+ if (typeof raw !== "string") return raw;
19
+ try { return JSON.parse(raw) as Evaluation; } catch { return undefined; }
20
+ }
21
+
22
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
23
+
24
+ const POINTS: Record<string, number> = { met: 1, partial: 0.5, missed: 0 };
25
+
26
+ export async function digest() {
27
+ const board = await g8.api.salesCoach.getSalesCoachLeaderboard();
28
+ const reps = (board.data.items ?? []) as Array<{ email: string; name: string; calls: number }>;
29
+ const out: Array<{ email: string; weakest?: string; tip?: string }> = [];
30
+
31
+ for (const rep of reps) {
32
+ const list = await g8.api.salesCoach.listCalls({ query: { email: rep.email } });
33
+ const totals = new Map<string, { sum: number; n: number }>();
34
+ let tip: string | undefined;
35
+ for (const call of (list.data.items ?? []) as Call[]) {
36
+ // Last 7 days only; a call without a date is skipped rather than guessed.
37
+ const when = call.date_of_call ? Date.parse(call.date_of_call) : NaN;
38
+ if (!(Date.now() - when <= WEEK_MS)) continue;
39
+ const ev = evaluationOf(call);
40
+ tip ??= ev?.evaluatorResult?.areas_of_improvement?.[0];
41
+ for (const stage of ev?.stages ?? []) {
42
+ for (const c of stage.criteria ?? []) {
43
+ if (!c.verdict || !Object.hasOwn(POINTS, c.verdict) || !stage.label) continue;
44
+ const t = totals.get(stage.label) ?? { sum: 0, n: 0 };
45
+ totals.set(stage.label, { sum: t.sum + POINTS[c.verdict], n: t.n + 1 });
46
+ }
47
+ }
48
+ }
49
+ const weakest = [...totals.entries()].sort((a, b) => a[1].sum / a[1].n - b[1].sum / b[1].n)[0]?.[0];
50
+ out.push({ email: rep.email, weakest, tip });
51
+ }
52
+ return out;
53
+ }