@graph8/sdk 0.308.1 → 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 (654) 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 +1440 -21
  390. package/dist/index.d.ts +1440 -21
  391. package/dist/index.js +42 -3
  392. package/dist/index.js.map +1 -1
  393. package/dist/index.mjs +42 -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 +1340 -18
  400. package/dist/react.d.ts +1340 -18
  401. package/dist/react.js +41 -2
  402. package/dist/react.js.map +1 -1
  403. package/dist/react.mjs +41 -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 +6 -3
  611. package/docs/api/appObjects.d.ts +1 -1
  612. package/docs/api/appObjects.md +1 -1
  613. package/docs/api/by-path.md +29 -0
  614. package/docs/api/objects.d.ts +72 -5
  615. package/docs/api/objects.md +84 -7
  616. package/docs/api/processLists.d.ts +247 -0
  617. package/docs/api/processLists.md +409 -0
  618. package/docs/api/reports.d.ts +476 -0
  619. package/docs/api/reports.md +539 -0
  620. package/docs/api/scheduleBuy.d.ts +12 -8
  621. package/docs/api/scheduleBuy.md +13 -10
  622. package/docs/api/seo.d.ts +406 -3
  623. package/docs/api/seo.md +505 -1
  624. package/docs/api/workspaceBackup.d.ts +324 -0
  625. package/docs/api/workspaceBackup.md +427 -0
  626. package/package.json +174 -5
  627. package/stacks/6sense.json +46 -0
  628. package/stacks/README.md +20 -0
  629. package/stacks/apollo.json +128 -0
  630. package/stacks/clay.json +59 -0
  631. package/stacks/gong.json +72 -0
  632. package/stacks/hubspot.json +162 -0
  633. package/stacks/intercom.json +53 -0
  634. package/stacks/mailchimp.json +71 -0
  635. package/stacks/outreach.json +86 -0
  636. package/stacks/salesforce.json +94 -0
  637. package/stacks/stack.schema.json +32 -0
  638. package/stacks/starter.json +22 -0
  639. package/styles/styles.css +2 -0
  640. package/styles/theme.css +181 -0
  641. package/styles/tokens.css +284 -0
  642. package/templates/next-app/README.md.tmpl +35 -0
  643. package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
  644. package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
  645. package/templates/next-app/app/globals.css.tmpl +8 -0
  646. package/templates/next-app/app/layout.tsx.tmpl +19 -0
  647. package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
  648. package/templates/next-app/dot-env.local.example.tmpl +6 -0
  649. package/templates/next-app/dot-gitignore.tmpl +7 -0
  650. package/templates/next-app/lib/nav.ts.tmpl +2 -0
  651. package/templates/next-app/next.config.mjs.tmpl +4 -0
  652. package/templates/next-app/package.json.tmpl +25 -0
  653. package/templates/next-app/postcss.config.mjs.tmpl +4 -0
  654. package/templates/next-app/tsconfig.json.tmpl +21 -0
@@ -0,0 +1,142 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Types and pure helpers for quotes: line-item math, money in cents, statuses, and the request bodies.
5
+ *
6
+ * All amounts are CENTS (`unit_amount`, `total`, `tax_amount`); `discount_pct` is a percentage (10 = 10%).
7
+ * The same arithmetic runs in the builder preview and in the tests, so the numbers shown while editing match what the
8
+ * server stores. The server is still the authority: after saving, the screens show the saved totals.
9
+ */
10
+
11
+ export type Rec = Record<string, unknown>;
12
+
13
+ export function asRec(value: unknown): Rec {
14
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Rec) : {};
15
+ }
16
+
17
+ export function text(value: unknown, fallback = ""): string {
18
+ return typeof value === "string" ? value : value == null ? fallback : String(value);
19
+ }
20
+
21
+ /** The body of a response: unwrap `{ data }`. */
22
+ export function body(res: unknown): Rec {
23
+ const r = asRec(res);
24
+ return asRec("data" in r ? r.data : r);
25
+ }
26
+
27
+ export function money(cents: unknown, currency = "USD"): string {
28
+ if (typeof cents !== "number" || !Number.isFinite(cents)) return "";
29
+ return (cents / 100).toLocaleString(undefined, { style: "currency", currency: currency || "USD" });
30
+ }
31
+
32
+ export type BillingFrequency = "one_time" | "month" | "quarter" | "year";
33
+
34
+ export interface LineItem {
35
+ /** Local key for React lists only; never sent. */
36
+ key: string;
37
+ product_name: string;
38
+ description?: string;
39
+ unit_amount: number;
40
+ quantity: number;
41
+ discount_pct: number;
42
+ billing_frequency: BillingFrequency;
43
+ stripe_product_id?: string | null;
44
+ stripe_price_id?: string | null;
45
+ manual_product_id?: string | null;
46
+ }
47
+
48
+ export const PAYMENT_TERMS = ["due_on_receipt", "net_15", "net_30", "net_45", "net_60", "net_90"] as const;
49
+ export type PaymentTerms = (typeof PAYMENT_TERMS)[number];
50
+
51
+ export function lineTotal(l: Pick<LineItem, "unit_amount" | "quantity" | "discount_pct">): number {
52
+ return Math.round(l.unit_amount * l.quantity * (1 - Math.min(100, Math.max(0, l.discount_pct)) / 100));
53
+ }
54
+
55
+ export function subtotal(lines: LineItem[]): number {
56
+ return lines.reduce((sum, l) => sum + lineTotal(l), 0);
57
+ }
58
+
59
+ /** A quotable-products price row (one per Stripe price, or the single price of a manual product). */
60
+ export interface ProductPrice {
61
+ stripe_price_id: string | null;
62
+ unit_amount: number | null;
63
+ currency: string | null;
64
+ recurring_interval: string | null;
65
+ type: string | null;
66
+ }
67
+
68
+ export interface QuotableProduct {
69
+ stripe_product_id: string | null;
70
+ manual_product_id: string | null;
71
+ name: string;
72
+ description?: string | null;
73
+ category?: string | null;
74
+ source: "stripe" | "manual";
75
+ prices: ProductPrice[];
76
+ }
77
+
78
+ export function frequencyOf(interval: string | null | undefined): BillingFrequency {
79
+ return interval === "month" || interval === "year" || interval === "quarter" ? interval : "one_time";
80
+ }
81
+
82
+ let counter = 0;
83
+ export const nextKey = (): string => `li-${Date.now().toString(36)}-${counter++}`;
84
+
85
+ export function lineFromProduct(p: QuotableProduct, price: ProductPrice | undefined): LineItem {
86
+ return {
87
+ key: nextKey(),
88
+ product_name: p.name,
89
+ description: p.description ?? undefined,
90
+ unit_amount: price?.unit_amount ?? 0,
91
+ quantity: 1,
92
+ discount_pct: 0,
93
+ billing_frequency: frequencyOf(price?.recurring_interval),
94
+ stripe_product_id: p.stripe_product_id,
95
+ stripe_price_id: price?.stripe_price_id ?? null,
96
+ manual_product_id: p.manual_product_id,
97
+ };
98
+ }
99
+
100
+ /** The `line_items` of a create/update request (the local key is dropped, empty links are omitted). */
101
+ export function toRequestLines(lines: LineItem[]): Rec[] {
102
+ return lines.map((l, i) => {
103
+ const out: Rec = { product_name: l.product_name, unit_amount: l.unit_amount, quantity: l.quantity, discount_pct: l.discount_pct, billing_frequency: l.billing_frequency, sort_order: i };
104
+ if (l.description) out.description = l.description;
105
+ if (l.stripe_product_id) out.stripe_product_id = l.stripe_product_id;
106
+ if (l.stripe_price_id) out.stripe_price_id = l.stripe_price_id;
107
+ if (l.manual_product_id) out.manual_product_id = l.manual_product_id;
108
+ return out;
109
+ });
110
+ }
111
+
112
+ export function linesFromQuote(items: unknown): LineItem[] {
113
+ if (!Array.isArray(items)) return [];
114
+ return items.map((raw) => {
115
+ const r = asRec(raw);
116
+ const freq = text(r.billing_frequency);
117
+ return {
118
+ key: nextKey(),
119
+ product_name: text(r.product_name),
120
+ description: text(r.description) || undefined,
121
+ unit_amount: Number(r.unit_amount ?? 0),
122
+ quantity: Number(r.quantity ?? 1),
123
+ discount_pct: Number(r.discount_pct ?? 0),
124
+ billing_frequency: (["one_time", "month", "quarter", "year"].includes(freq) ? freq : "one_time") as BillingFrequency,
125
+ stripe_product_id: (r.stripe_product_id as string | null) ?? null,
126
+ stripe_price_id: (r.stripe_price_id as string | null) ?? null,
127
+ manual_product_id: (r.manual_product_id as string | null) ?? null,
128
+ };
129
+ });
130
+ }
131
+
132
+ export const STATUS_LABEL: Record<string, string> = {
133
+ draft: "Draft",
134
+ sent: "Sent",
135
+ viewed: "Viewed",
136
+ accepted: "Accepted",
137
+ declined: "Declined",
138
+ voided: "Voided",
139
+ expired: "Expired",
140
+ archived: "Archived",
141
+ };
142
+ export const statusLabel = (s: unknown): string => STATUS_LABEL[text(s)] ?? text(s, "Unknown");
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @title Create a quote from quotable products and send it for signature
3
+ * @when A deal is ready for paper. Line items MUST reference a quotable product: read /quotable-products first and copy
4
+ * the ids and the price, never type a Stripe id. Amounts are CENTS. Creating stores a draft (nobody is emailed);
5
+ * SENDING emails the signer a signing link (external tier), so only send after the person confirmed the quote.
6
+ * The `dealId` comes from the deals block; the signer is an existing contact id or an email.
7
+ */
8
+ import { g8 } from "@graph8/sdk";
9
+
10
+ g8.init({ apiKey: process.env.G8_API_KEY! });
11
+
12
+ export async function createQuote(opts: { dealId: string; title: string; signerEmail: string; contractStart: string; productName: string; quantity?: number; send?: boolean }): Promise<string> {
13
+ // 1. Pick a real quotable product and its first flat price.
14
+ const catalog = await g8.api.quotes.listQuotableProducts({ query: { limit: 100 } });
15
+ const product = catalog.data.items.find((p) => String(p.name).toLowerCase() === opts.productName.toLowerCase());
16
+ const price = (product?.prices as Array<{ stripe_price_id: string | null; unit_amount: number; recurring_interval: string | null }> | undefined)?.[0];
17
+ if (!product || !price) throw new Error(`"${opts.productName}" is not a quotable product with a flat price`);
18
+
19
+ // 2. Create the draft. contract_start_date, a signer and at least one line item are required.
20
+ const created = await g8.api.quotes.createQuote({
21
+ body: {
22
+ deal_id: opts.dealId,
23
+ title: opts.title,
24
+ contract_start_date: opts.contractStart,
25
+ signer_email: opts.signerEmail,
26
+ line_items: [
27
+ {
28
+ product_name: String(product.name),
29
+ unit_amount: price.unit_amount,
30
+ quantity: opts.quantity ?? 1,
31
+ billing_frequency: price.recurring_interval === "month" || price.recurring_interval === "year" || price.recurring_interval === "quarter" ? price.recurring_interval : "one_time",
32
+ stripe_product_id: (product.stripe_product_id as string | null) ?? null,
33
+ stripe_price_id: price.stripe_price_id,
34
+ manual_product_id: (product.manual_product_id as string | null) ?? null,
35
+ },
36
+ ],
37
+ },
38
+ });
39
+ const quoteId = String(created.data.id);
40
+
41
+ // 3. Send only when asked: this emails the signer.
42
+ if (opts.send) await g8.api.quotes.sendQuote({ path: { quote_id: quoteId }, body: {} });
43
+ return quoteId;
44
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @title Change a quote that was already sent
3
+ * @when The customer asked for different terms. A sent quote cannot be edited in place: "edit as draft" VOIDS the sent
4
+ * copy (the signing link stops working) and returns a new draft (its id is `new_quote_id`), which is edited and sent again. Both the void and the
5
+ * resend are external tier: confirm with the person before running this.
6
+ */
7
+ import { g8 } from "@graph8/sdk";
8
+
9
+ g8.init({ apiKey: process.env.G8_API_KEY! });
10
+
11
+ export async function reviseAndResend(sentQuoteId: string, changes: { title?: string; paymentTerms?: "due_on_receipt" | "net_15" | "net_30" | "net_45" | "net_60" | "net_90" }): Promise<string> {
12
+ const draft = await g8.api.quotes.editQuoteAsDraft({ path: { quote_id: sentQuoteId } });
13
+ const draftId = String((draft.data as Record<string, unknown>).new_quote_id);
14
+
15
+ await g8.api.quotes.updateQuotePatch({ path: { quote_id: draftId }, body: { title: changes.title, payment_terms: changes.paymentTerms } });
16
+ await g8.api.quotes.sendQuote({ path: { quote_id: draftId }, body: { message: "Updated as discussed." } });
17
+ return draftId;
18
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @title Wait for a quote to be signed (or declined)
3
+ * @when After sending, when the next step depends on the signature. The envelope inside the quote reports `sent`,
4
+ * `viewed`, `signed` or `declined`; there is no webhook to wait on from this recipe, so poll slowly. Reading is free.
5
+ */
6
+ import { g8 } from "@graph8/sdk";
7
+
8
+ g8.init({ apiKey: process.env.G8_API_KEY! });
9
+
10
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
11
+
12
+ export async function waitForSignature(quoteId: string, maxChecks = 20): Promise<{ status: string; signedAt?: string | null }> {
13
+ for (let i = 0; i < maxChecks; i++) {
14
+ const quote = (await g8.api.quotes.getQuote({ path: { quote_id: quoteId } })).data;
15
+ const envelope = (quote.envelope ?? {}) as { status?: string; signed_at?: string | null };
16
+ if (envelope.status === "signed" || envelope.status === "declined" || envelope.status === "voided" || envelope.status === "expired") {
17
+ return { status: envelope.status, signedAt: envelope.signed_at };
18
+ }
19
+ await sleep(Math.min(5 * 60_000, 30_000 * 1.5 ** i));
20
+ }
21
+ return { status: "still waiting" };
22
+ }
@@ -0,0 +1,106 @@
1
+ # radar
2
+
3
+ Know what competitors are doing: track them, scan them, read a threat board and a free battle card, and generate
4
+ deep-dive reports. Depends only on `foundation`.
5
+
6
+ Install: `npx @graph8/sdk add radar`.
7
+
8
+ ## What you get
9
+
10
+ | Component | Use it for | Props |
11
+ |---|---|---|
12
+ | `CompetitorTracker` | Roster: add, bulk-add detected, scan one or all, pause, resume, remove | `onSelect(competitor)` |
13
+ | `RadarOverview` | Counts, moves, open gaps, threat board, alerts | `onSelectCompetitor(id)` |
14
+ | `CompetitorBattleCard` | Free battle card with a talk track | `competitorId` |
15
+ | `RadarReport` | Generate and open deep-dive reports | `capabilityId` |
16
+
17
+ Hook `useCompetitors()`; helpers in `lib/radar-types.ts` (`radarBody`, `scanCostEstimate`, `normalizeDomain`).
18
+
19
+ ## Cost, in plain numbers
20
+
21
+ | Action | Cost | Confirmed with |
22
+ |---|---|---|
23
+ | Add a competitor | free, unless `scan_on_add` | `ConfirmSpendDialog` (0 credits shown; the operation's tier is billable) |
24
+ | Pause / resume | free | `ConfirmSpendDialog` (0 credits) |
25
+ | Scan one competitor | 1 credit per page captured (25 pages tier 1, 8 watchlist) + 1 for traffic: at most 26 / 9 | `ConfirmSpendDialog` (billable) |
26
+ | Scan all | the sum of the above for every active competitor not already scanning | `ConfirmSpendDialog` (billable) |
27
+ | Generate a report | uses credits; **no advance price** | `ConfirmSpendDialog` (billable) without a number |
28
+ | Remove a competitor | free, irreversible | `ConfirmSpendDialog` (destructive) |
29
+ | Overview, battle card, lists | free | none |
30
+
31
+ There is no quote and no balance read; the figures are the most a scan can charge. A monthly radar credit cap pauses
32
+ automatic scans (`summary.credit_cap_reached`); manual scans still run.
33
+
34
+ ## The flow, with the ids that pass between steps
35
+
36
+ 1. `GET /radar/competitors/suggestions` (detected, untracked) -> `POST /radar/competitors { domain, tier, scan_on_add }` -> competitor `id` (a UUID string).
37
+ 2. `POST /radar/competitors/{id}/scan` -> `{ task_id, status: "queued" }`. Poll `GET /radar/competitors` (`useJobPoll`; there is no stream) until no
38
+ competitor has `scan_status` `scanning`. A scan already queued or running answers 409.
39
+ 3. `GET /radar/competitors/{id}/battle-card` (free): `talk_track.lead_with`, `watch_outs`, `our_advantages`, `recent_moves`.
40
+ 4. Reports: `GET /radar/feature-matrix` -> `capabilities[].id` -> `POST /radar/reports { capability_id, audience, edge }` -> `{ report_id, status: "generating" }`
41
+ -> poll `GET /radar/reports` until the status is final -> open `/api/g8/radar/reports/{id}/html` or `/pdf` in a new tab (through your proxy, so the signed-in
42
+ session authorizes it).
43
+
44
+ **Response shapes differ:** competitor, overview, battle card, matrix and scan bodies are the app's own and come **unwrapped** (`{ competitors, capacity }`);
45
+ reports use the usual `{ data: { items } }`. `radarBody()` reads both.
46
+
47
+ ```tsx
48
+ "use client";
49
+ import { useState } from "react";
50
+ import { CompetitorTracker } from "@/components/g8/radar/competitor-tracker";
51
+ import { CompetitorBattleCard } from "@/components/g8/radar/competitor-battle-card";
52
+
53
+ export function Competitors() {
54
+ const [id, setId] = useState<string | null>(null);
55
+ return (<div className="grid gap-6 md:grid-cols-2"><CompetitorTracker onSelect={(c) => setId(c.id)} />{id ? <CompetitorBattleCard competitorId={id} /> : null}</div>);
56
+ }
57
+ ```
58
+
59
+ (`add` flattens `components/` into `components/g8/radar/`; adjust the alias to your app.)
60
+
61
+ ## Prerequisites (a human must do these)
62
+
63
+ - `proxy-allowlist` (app): `allowedPrefixes` includes `radar` (and `analytics` for the marketing snapshot recipe).
64
+ - `credits` (api): credits for scans and reports.
65
+ - `market-research` (api): detected competitors, the feature matrix and verified advantages come from the workspace's research and proof catalog in graph8.
66
+
67
+ ## Defaults the agent can use without asking
68
+
69
+ | Decision | Default |
70
+ |---|---|
71
+ | Tier of a new competitor | watchlist (tier 2) |
72
+ | Scan when adding | off |
73
+ | Battle card advantages | evidence-backed only |
74
+ | Report audience and angle | executive, where we win |
75
+
76
+ Ask the person: who may scan or generate reports (the proxy's `authorize`).
77
+
78
+ ## Recipes
79
+
80
+ | File | Does |
81
+ |---|---|
82
+ | `recipes/track-and-scan.ts` | Suggestions, add, scan, poll the roster |
83
+ | `recipes/battle-card.ts` | Battle card and a weekly digest from the overview |
84
+ | `recipes/deep-dive-report.ts` | Matrix -> create report -> poll -> done |
85
+ | `recipes/marketing-snapshot.ts` | Your marketing overview next to rival moves |
86
+
87
+ ## Operations
88
+
89
+ | Operation | Method and path | Tier | Used by |
90
+ |---|---|---|---|
91
+ | bulkAddCompetitors | POST /radar/competitors/bulk | billable | CompetitorTracker |
92
+ | competitorBattleCard | GET /radar/competitors/{competitor_id}/battle-card | read | CompetitorBattleCard |
93
+ | competitorSuggestions | GET /radar/competitors/suggestions | read | CompetitorTracker |
94
+ | createCompetitor | POST /radar/competitors | billable | CompetitorTracker |
95
+ | createRadarReport | POST /radar/reports | billable | RadarReport |
96
+ | deleteCompetitor | DELETE /radar/competitors/{competitor_id} | destructive | CompetitorTracker |
97
+ | featureMatrix | GET /radar/feature-matrix | read | RadarReport |
98
+ | getMarketingOverview | GET /analytics/marketing/overview | read | recipes |
99
+ | getRadarReportHtml | GET /radar/reports/{report_id}/html | read | recipes |
100
+ | getRadarReportPdf | GET /radar/reports/{report_id}/pdf | read | recipes |
101
+ | listCompetitors | GET /radar/competitors | read | CompetitorTracker, useCompetitors |
102
+ | listRadarReports | GET /radar/reports | read | RadarReport |
103
+ | overview | GET /radar/overview | read | RadarOverview |
104
+ | scanAllCompetitors | POST /radar/scan-all | billable | CompetitorTracker |
105
+ | scanCompetitor | POST /radar/competitors/{competitor_id}/scan | billable | CompetitorTracker |
106
+ | updateCompetitor | PATCH /radar/competitors/{competitor_id} | billable | CompetitorTracker |
@@ -0,0 +1,189 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "radar",
4
+ "title": "Competitors & market",
5
+ "summary": "Track competitors, scan them, read the threat board and battle cards, and generate deep-dive reports.",
6
+ "features": [
7
+ "roster of tracked competitors with threat score, traffic, ads and scan status, polled while a scan runs",
8
+ "add, bulk-add detected competitors, pause, resume and remove, each confirmed with the credits it can use",
9
+ "overview with moves, alerts and the threat board",
10
+ "free battle card with a talk track; evidence-backed advantages by default",
11
+ "deep-dive reports per capability and audience, generated, polled and opened as HTML or PDF"
12
+ ],
13
+ "dependsOn": [
14
+ "foundation"
15
+ ],
16
+ "operations": [
17
+ "bulk_add_competitors_radar_competitors_bulk_post",
18
+ "competitor_battle_card_radar_competitors__competitor_id__battle_card_get",
19
+ "competitor_suggestions_radar_competitors_suggestions_get",
20
+ "create_competitor_radar_competitors_post",
21
+ "create_radar_report_radar_reports_post",
22
+ "delete_competitor_radar_competitors__competitor_id__delete",
23
+ "feature_matrix_radar_feature_matrix_get",
24
+ "get_marketing_overview_analytics_marketing_overview_get",
25
+ "get_radar_report_html_radar_reports__report_id__html_get",
26
+ "get_radar_report_pdf_radar_reports__report_id__pdf_get",
27
+ "list_competitors_radar_competitors_get",
28
+ "list_radar_reports_radar_reports_get",
29
+ "overview_radar_overview_get",
30
+ "scan_all_competitors_radar_scan_all_post",
31
+ "scan_competitor_radar_competitors__competitor_id__scan_post",
32
+ "update_competitor_radar_competitors__competitor_id__patch"
33
+ ],
34
+ "components": [
35
+ {
36
+ "name": "CompetitorTracker",
37
+ "file": "components/competitor-tracker.tsx",
38
+ "purpose": "The competitor roster: add, bulk-add detected ones, scan one or all (confirmed, 1 credit per page up to the tier budget plus 1), pause, resume and remove.",
39
+ "operations": [
40
+ "list_competitors_radar_competitors_get",
41
+ "competitor_suggestions_radar_competitors_suggestions_get",
42
+ "create_competitor_radar_competitors_post",
43
+ "bulk_add_competitors_radar_competitors_bulk_post",
44
+ "scan_competitor_radar_competitors__competitor_id__scan_post",
45
+ "scan_all_competitors_radar_scan_all_post",
46
+ "update_competitor_radar_competitors__competitor_id__patch",
47
+ "delete_competitor_radar_competitors__competitor_id__delete"
48
+ ],
49
+ "tiers": [
50
+ "read",
51
+ "billable",
52
+ "destructive"
53
+ ]
54
+ },
55
+ {
56
+ "name": "RadarOverview",
57
+ "file": "components/radar-overview.tsx",
58
+ "purpose": "Tracked counts, moves in 7 days, open gaps, the threat board, alerts and recent moves. Read-only.",
59
+ "operations": [
60
+ "overview_radar_overview_get"
61
+ ],
62
+ "tiers": [
63
+ "read"
64
+ ]
65
+ },
66
+ {
67
+ "name": "CompetitorBattleCard",
68
+ "file": "components/competitor-battle-card.tsx",
69
+ "purpose": "A free battle card for one competitor: what to lead with, what to be ready for, proven advantages and recent moves.",
70
+ "operations": [
71
+ "competitor_battle_card_radar_competitors__competitor_id__battle_card_get"
72
+ ],
73
+ "tiers": [
74
+ "read"
75
+ ]
76
+ },
77
+ {
78
+ "name": "RadarReport",
79
+ "file": "components/radar-report.tsx",
80
+ "purpose": "Pick a capability, audience and angle, confirm and generate a deep-dive report, watch it generate, open the HTML or PDF.",
81
+ "operations": [
82
+ "feature_matrix_radar_feature_matrix_get",
83
+ "list_radar_reports_radar_reports_get",
84
+ "create_radar_report_radar_reports_post"
85
+ ],
86
+ "tiers": [
87
+ "read",
88
+ "billable"
89
+ ]
90
+ }
91
+ ],
92
+ "hooks": [
93
+ {
94
+ "name": "useCompetitors",
95
+ "file": "hooks/use-competitors.ts",
96
+ "purpose": "Tracked competitors with plan capacity and a `scanning` flag for polling.",
97
+ "operations": [
98
+ "list_competitors_radar_competitors_get"
99
+ ]
100
+ }
101
+ ],
102
+ "lib": [
103
+ {
104
+ "file": "lib/radar-types.ts",
105
+ "purpose": "Competitor, overview, battle card and report shapes; radarBody (wrapped or not), scanCostEstimate, isScanning, normalizeDomain."
106
+ }
107
+ ],
108
+ "recipes": [
109
+ {
110
+ "file": "recipes/track-and-scan.ts",
111
+ "title": "Track competitors, scan them and wait for the scan",
112
+ "when": "Setting up monitoring. Adding is free unless scan_on_add; a scan is billable (1 credit per page up to the tier budget, plus 1) with no quote, so tell a human the figure first. Radar bodies are unwrapped."
113
+ },
114
+ {
115
+ "file": "recipes/battle-card.ts",
116
+ "title": "Read a competitor battle card and the market overview",
117
+ "when": "Preparing a call or a weekly digest. Free reads; only evidence-backed advantages unless include_unverified, which you must label."
118
+ },
119
+ {
120
+ "file": "recipes/deep-dive-report.ts",
121
+ "title": "Generate a deep-dive report on one capability and download it",
122
+ "when": "An enablement document on one feature-matrix claim. Billable, no advance price: get approval. Duplicates in flight answer 409; regenerating overwrites."
123
+ },
124
+ {
125
+ "file": "recipes/marketing-snapshot.ts",
126
+ "title": "Pair competitor moves with your own marketing numbers",
127
+ "when": "A market-versus-us summary from free reads of marketing intelligence and the radar overview."
128
+ }
129
+ ],
130
+ "prerequisites": [
131
+ {
132
+ "id": "proxy-allowlist",
133
+ "where": "app",
134
+ "description": "allowedPrefixes in the /api/g8 route includes `radar` (and `analytics` for the marketing snapshot recipe).",
135
+ "verify": "GET /api/g8/radar/competitors returns 200 when signed in."
136
+ },
137
+ {
138
+ "id": "credits",
139
+ "where": "api",
140
+ "description": "The workspace has credits for scans and reports. The API has no balance read and no scan quote, so the UI shows the most a scan can charge.",
141
+ "verify": "A scan of one competitor starts."
142
+ },
143
+ {
144
+ "id": "market-research",
145
+ "where": "api",
146
+ "description": "Detected competitors, the feature matrix and battle-card advantages come from the workspace's market research and proof catalog in graph8. Without them those lists are empty.",
147
+ "verify": "GET /radar/competitors/suggestions returns suggestions, or you add competitors by hand."
148
+ }
149
+ ],
150
+ "scopes": [
151
+ "analytics:read",
152
+ "intent:read",
153
+ "intent:run",
154
+ "radar:delete",
155
+ "radar:read",
156
+ "radar:run",
157
+ "radar:write"
158
+ ],
159
+ "defaults": [
160
+ {
161
+ "decision": "Tier of a new competitor",
162
+ "default": "Watchlist (tier 2, 8 pages per scan)",
163
+ "askHuman": false
164
+ },
165
+ {
166
+ "decision": "Scan when adding",
167
+ "default": "Off",
168
+ "askHuman": false
169
+ },
170
+ {
171
+ "decision": "Battle card advantages",
172
+ "default": "Only evidence-backed ones; unverified are an explicit toggle and labelled",
173
+ "askHuman": false
174
+ },
175
+ {
176
+ "decision": "Report audience and angle",
177
+ "default": "Executive, where we win",
178
+ "askHuman": false
179
+ },
180
+ {
181
+ "decision": "Who may scan or generate reports (credits)",
182
+ "default": "Anyone the proxy's authorize lets through",
183
+ "askHuman": true
184
+ }
185
+ ],
186
+ "peerDependencies": {
187
+ "react": ">=18"
188
+ }
189
+ }
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CompetitorBattleCard: a sales-enablement card for one competitor, composed from what radar already knows (no
5
+ * AI call, free): our proven advantages, where they beat us (watch-outs), their recent moves and a talk track.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. competitorBattleCard GET /radar/competitors/{competitor_id}/battle-card?include_unverified (read, free)
9
+ * By default only advantages backed by evidence are shown; the toggle also shows unverified ones, labelled.
10
+ */
11
+
12
+ import { useState } from "react";
13
+ import { Skeleton } from "@graph8/sdk/ui";
14
+ import { ApiError } from "@/g8/components/api-error";
15
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
16
+ import { g8Get } from "@/g8/lib/g8-client";
17
+ import { radarBody } from "../lib/radar-types";
18
+ import type { BattleCard } from "../lib/radar-types";
19
+
20
+ export interface CompetitorBattleCardProps {
21
+ competitorId: string;
22
+ className?: string;
23
+ }
24
+
25
+ export function CompetitorBattleCard({ competitorId, className }: CompetitorBattleCardProps) {
26
+ const [unverified, setUnverified] = useState(false);
27
+ const query = useG8Query(
28
+ async (signal) => radarBody<BattleCard>(await g8Get(`/radar/competitors/${competitorId}/battle-card`, { include_unverified: unverified }, { signal })),
29
+ [competitorId, unverified],
30
+ );
31
+ const card = query.data;
32
+
33
+ if (query.error) return <ApiError error={query.error} title="Could not load the battle card" onRetry={() => void query.refetch()} />;
34
+ if (!card) return <Skeleton className="h-40 w-full" />;
35
+ return (
36
+ <article className={`flex flex-col gap-4 ${className ?? ""}`} aria-busy={query.loading}>
37
+ <header><h3 className="text-base font-semibold">{card.competitor.name || card.competitor.domain}</h3><p className="text-sm text-muted-foreground">{card.summary}</p></header>
38
+ <section aria-label="Lead with"><h4 className="text-sm font-medium">Lead with</h4>
39
+ {card.talk_track.lead_with.length ? <ul className="list-disc pl-5 text-sm">{card.talk_track.lead_with.map((t) => <li key={t}>{t}</li>)}</ul> : <p className="text-sm text-muted-foreground">No proven advantage yet. Add proof to your catalog or show unverified ones.</p>}
40
+ </section>
41
+ <section aria-label="Be ready for"><h4 className="text-sm font-medium">Be ready for</h4>
42
+ {card.watch_outs.length ? <ul className="list-disc pl-5 text-sm">{card.watch_outs.map((w) => <li key={w.id}>{w.title}</li>)}</ul> : <p className="text-sm text-muted-foreground">No open gaps against this competitor.</p>}
43
+ </section>
44
+ <section aria-label="Our advantages"><h4 className="text-sm font-medium">Our advantages</h4>
45
+ <ul className="flex flex-col gap-1 text-sm">{card.our_advantages.map((a) => <li key={a.claim}>{a.claim}{!a.verified ? <span className="ml-1 rounded bg-muted px-1 text-xs">unverified</span> : null}</li>)}</ul>
46
+ <label className="mt-1 flex items-center gap-2 text-xs text-muted-foreground"><input type="checkbox" checked={unverified} onChange={(e) => setUnverified(e.target.checked)} /> Also show unverified advantages{card.unverified_advantages_hidden ? ` (${card.unverified_advantages_hidden} hidden)` : ""}</label>
47
+ </section>
48
+ <section aria-label="Recent moves"><h4 className="text-sm font-medium">Recent moves</h4>
49
+ {card.recent_moves.length ? <ul className="text-sm">{card.recent_moves.map((m) => <li key={m.id}>{m.title || m.url}{m.captured_at ? <span className="text-muted-foreground"> · {m.captured_at.slice(0, 10)}</span> : null}</li>)}</ul> : <p className="text-sm text-muted-foreground">No recent moves.</p>}
50
+ </section>
51
+ </article>
52
+ );
53
+ }