@graph8/sdk 0.370.0 → 0.373.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. package/README.md +19 -0
  2. package/blocks/_foundation/README.md +14 -0
  3. package/blocks/_foundation/block.json +44 -0
  4. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  5. package/blocks/_foundation/components/empty-state.tsx +5 -2
  6. package/blocks/_foundation/components/list-select.tsx +112 -0
  7. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  8. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  9. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  10. package/blocks/_foundation/components/screen/index.ts +1 -0
  11. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  12. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  13. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  14. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  15. package/blocks/_foundation/lib/paging.ts +57 -0
  16. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  17. package/blocks/agents/README.md +81 -4
  18. package/blocks/agents/block.json +311 -2
  19. package/blocks/agents/components/agent-activity.tsx +112 -0
  20. package/blocks/agents/components/agent-approvals.tsx +135 -0
  21. package/blocks/agents/components/agent-builder.tsx +17 -3
  22. package/blocks/agents/components/agent-conversations.tsx +143 -0
  23. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
  24. package/blocks/agents/components/agent-operators.tsx +164 -0
  25. package/blocks/agents/components/agent-run-detail.tsx +20 -3
  26. package/blocks/agents/components/agent-runs.tsx +3 -3
  27. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  28. package/blocks/agents/components/agent-test-history.tsx +121 -0
  29. package/blocks/agents/components/agents-screen.tsx +53 -11
  30. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  31. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  32. package/blocks/agents/components/from-template-button.tsx +65 -0
  33. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  34. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  35. package/blocks/agents/components/knowledge-form.tsx +69 -0
  36. package/blocks/agents/components/name-dialog.tsx +32 -0
  37. package/blocks/agents/components/receptionist-button.tsx +46 -0
  38. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  39. package/blocks/agents/components/skill-editor.tsx +147 -0
  40. package/blocks/agents/components/skill-library.tsx +111 -0
  41. package/blocks/agents/lib/agent-types.ts +7 -1
  42. package/blocks/agents/lib/facts.tsx +13 -0
  43. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  44. package/blocks/agents/lib/load-more.tsx +141 -0
  45. package/blocks/agents/lib/skill-utils.ts +91 -0
  46. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  47. package/blocks/analytics/components/analytics-screen.tsx +16 -3
  48. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  49. package/blocks/analytics/components/report-builder.tsx +15 -2
  50. package/blocks/appointments/README.md +6 -0
  51. package/blocks/appointments/block.json +27 -0
  52. package/blocks/appointments/components/booking-page.tsx +2 -2
  53. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  54. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  55. package/blocks/appointments/components/meeting-list.tsx +29 -13
  56. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  57. package/blocks/campaigns/components/campaign-assets.tsx +1 -1
  58. package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
  59. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  60. package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
  61. package/blocks/campaigns/components/icp-detail.tsx +11 -2
  62. package/blocks/campaigns/components/icp-editor.tsx +8 -3
  63. package/blocks/campaigns/components/persona-detail.tsx +11 -2
  64. package/blocks/campaigns/components/persona-editor.tsx +6 -1
  65. package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
  66. package/blocks/coach/components/call-scorecard.tsx +7 -7
  67. package/blocks/company-intelligence/README.md +132 -0
  68. package/blocks/company-intelligence/block.json +318 -0
  69. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  70. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  71. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  72. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  73. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  74. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  75. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  76. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  77. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  78. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  79. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  80. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  81. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  82. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  83. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  84. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  85. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  86. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  87. package/blocks/copilot/components/copilot-panel.tsx +1 -1
  88. package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
  89. package/blocks/crm/README.md +6 -0
  90. package/blocks/crm/block.json +53 -4
  91. package/blocks/crm/components/companies-screen.tsx +23 -9
  92. package/blocks/crm/components/company-detail.tsx +2 -2
  93. package/blocks/crm/components/contact-detail.tsx +4 -4
  94. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  95. package/blocks/crm/components/contacts-screen.tsx +31 -13
  96. package/blocks/crm/components/dedupe-review.tsx +6 -1
  97. package/blocks/crm/components/import-wizard.tsx +3 -7
  98. package/blocks/crm/components/list-select.tsx +20 -0
  99. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  100. package/blocks/crm/components/record-form.tsx +1 -1
  101. package/blocks/crm/components/tasks-screen.tsx +35 -15
  102. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  103. package/blocks/crm/hooks/use-page-query.ts +3 -1
  104. package/blocks/crm/lib/crm-types.ts +14 -0
  105. package/blocks/crm/lib/read-lists.ts +16 -0
  106. package/blocks/deals/README.md +24 -6
  107. package/blocks/deals/block.json +151 -2
  108. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  109. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  110. package/blocks/deals/components/deal-detail.tsx +2 -2
  111. package/blocks/deals/components/deal-filters.tsx +65 -0
  112. package/blocks/deals/components/deal-line-items.tsx +120 -0
  113. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  114. package/blocks/deals/components/deal-related.tsx +30 -9
  115. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  116. package/blocks/deals/components/deals-screen.tsx +57 -22
  117. package/blocks/deals/components/forecast-view.tsx +4 -4
  118. package/blocks/deals/components/pipeline-board.tsx +8 -6
  119. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  120. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  121. package/blocks/deals/components/stage-editor.tsx +1 -1
  122. package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
  123. package/blocks/deals/lib/deal-extras.ts +156 -0
  124. package/blocks/dialer/README.md +14 -5
  125. package/blocks/dialer/block.json +17 -2
  126. package/blocks/dialer/components/call-contact-button.tsx +26 -9
  127. package/blocks/dialer/components/call-log-screen.tsx +4 -1
  128. package/blocks/dialer/components/call-log.tsx +16 -4
  129. package/blocks/dialer/components/call-queue.tsx +3 -3
  130. package/blocks/dialer/components/dialer-screen.tsx +69 -27
  131. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  132. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  133. package/blocks/dialer/components/session-start-form.tsx +58 -11
  134. package/blocks/dialer/components/softphone.tsx +1 -1
  135. package/blocks/enrichment/README.md +16 -6
  136. package/blocks/enrichment/block.json +19 -7
  137. package/blocks/enrichment/components/bulk-enrich-panel.tsx +76 -27
  138. package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
  139. package/blocks/enrichment/components/enrich-button.tsx +1 -1
  140. package/blocks/enrichment/components/enrichment-screen.tsx +70 -17
  141. package/blocks/enrichment/components/waterfall-config.tsx +27 -7
  142. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  143. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  144. package/blocks/forms/README.md +8 -0
  145. package/blocks/forms/block.json +107 -0
  146. package/blocks/forms/components/form-activity.tsx +71 -0
  147. package/blocks/forms/components/form-backfill.tsx +108 -0
  148. package/blocks/forms/components/form-builder.tsx +1 -1
  149. package/blocks/forms/components/form-embed.tsx +1 -1
  150. package/blocks/forms/components/form-groups.tsx +151 -0
  151. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  152. package/blocks/forms/components/form-route-history.tsx +72 -0
  153. package/blocks/forms/components/form-saved-views.tsx +81 -0
  154. package/blocks/forms/components/forms-screen.tsx +51 -17
  155. package/blocks/forms/components/more-rows.tsx +30 -0
  156. package/blocks/forms/components/submissions-export.tsx +67 -0
  157. package/blocks/forms/components/submissions-table.tsx +15 -6
  158. package/blocks/forms/lib/forms-extra.ts +67 -0
  159. package/blocks/inbox/README.md +5 -0
  160. package/blocks/inbox/block.json +15 -0
  161. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  162. package/blocks/inbox/components/thread-badge.tsx +2 -2
  163. package/blocks/inbox/components/thread-view.tsx +1 -1
  164. package/blocks/integrations/README.md +54 -1
  165. package/blocks/integrations/block.json +189 -15
  166. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  167. package/blocks/integrations/components/connection-health.tsx +59 -0
  168. package/blocks/integrations/components/crm-detail.tsx +20 -1
  169. package/blocks/integrations/components/crm-records.tsx +103 -0
  170. package/blocks/integrations/components/field-mappings.tsx +61 -0
  171. package/blocks/integrations/components/integration-list.tsx +4 -4
  172. package/blocks/integrations/components/integrations-screen.tsx +20 -3
  173. package/blocks/integrations/components/migration-panel.tsx +133 -0
  174. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  175. package/blocks/integrations/components/sync-status.tsx +6 -6
  176. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  177. package/blocks/integrations/components/webhook-detail.tsx +26 -7
  178. package/blocks/integrations/components/webhook-manager.tsx +30 -17
  179. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  180. package/blocks/integrations/lib/crm-utils.ts +39 -0
  181. package/blocks/integrations/lib/facts.tsx +13 -0
  182. package/blocks/integrations/lib/integrations-utils.ts +5 -0
  183. package/blocks/integrations/lib/load-more.tsx +141 -0
  184. package/blocks/intent/components/account-intent-score.tsx +5 -5
  185. package/blocks/intent/components/intent-feed.tsx +8 -8
  186. package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
  187. package/blocks/intent/lib/intent-utils.ts +4 -4
  188. package/blocks/lifecycle/components/account-related.tsx +8 -1
  189. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  190. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  191. package/blocks/lists/README.md +7 -1
  192. package/blocks/lists/block.json +31 -3
  193. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  194. package/blocks/lists/components/add-to-list-button.tsx +17 -7
  195. package/blocks/lists/components/list-picker.tsx +13 -3
  196. package/blocks/lists/components/list-table.tsx +1 -1
  197. package/blocks/lists/components/lists-screen.tsx +4 -2
  198. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  199. package/blocks/lists/components/suppression-manager.tsx +27 -6
  200. package/blocks/lists/components/suppressions-screen.tsx +21 -12
  201. package/blocks/mailboxes/block.json +4 -0
  202. package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
  203. package/blocks/mailboxes/components/domain-detail.tsx +10 -4
  204. package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
  205. package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
  206. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  207. package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
  208. package/blocks/mailboxes/components/warmup-health.tsx +20 -20
  209. package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
  210. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  211. package/blocks/newsletter/README.md +12 -0
  212. package/blocks/newsletter/block.json +117 -0
  213. package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
  214. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  215. package/blocks/newsletter/components/more-rows.tsx +30 -0
  216. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  217. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  218. package/blocks/newsletter/components/newsletter-screen.tsx +36 -13
  219. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  220. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  221. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  222. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  223. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  224. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  225. package/blocks/nurture/components/nurture-builder.tsx +10 -2
  226. package/blocks/nurture/components/nurture-enroll.tsx +6 -7
  227. package/blocks/nurture/components/nurture-form.tsx +5 -8
  228. package/blocks/nurture/components/nurture-screen.tsx +16 -5
  229. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  230. package/blocks/objects/README.md +1 -1
  231. package/blocks/objects/block.json +13 -5
  232. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  233. package/blocks/objects/components/object-record-detail.tsx +8 -1
  234. package/blocks/objects/components/object-schema-editor.tsx +1 -1
  235. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  236. package/blocks/pages/README.md +14 -0
  237. package/blocks/pages/block.json +113 -1
  238. package/blocks/pages/components/more-rows.tsx +30 -0
  239. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  240. package/blocks/pages/components/page-analytics.tsx +176 -0
  241. package/blocks/pages/components/page-clone.tsx +99 -0
  242. package/blocks/pages/components/page-domains.tsx +167 -0
  243. package/blocks/pages/components/page-editor.tsx +1 -1
  244. package/blocks/pages/components/page-generation-status.tsx +48 -0
  245. package/blocks/pages/components/page-tracking.tsx +137 -0
  246. package/blocks/pages/components/pages-screen.tsx +57 -16
  247. package/blocks/pages/components/seo-report.tsx +6 -1
  248. package/blocks/pages/lib/studio.ts +7 -0
  249. package/blocks/phone-numbers/README.md +74 -0
  250. package/blocks/phone-numbers/block.json +262 -0
  251. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  252. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  253. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  254. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  255. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  256. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  257. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  258. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  259. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  260. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  261. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  262. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  263. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  264. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  265. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  266. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  267. package/blocks/pickers/README.md +23 -0
  268. package/blocks/pickers/block.json +103 -0
  269. package/blocks/pickers/components/company-picker.tsx +90 -0
  270. package/blocks/pickers/components/contact-picker.tsx +131 -0
  271. package/blocks/pickers/components/member-picker.tsx +99 -0
  272. package/blocks/pickers/components/search-picker.tsx +98 -0
  273. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  274. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  275. package/blocks/pickers/lib/use-debounced.ts +13 -0
  276. package/blocks/quotes/README.md +13 -0
  277. package/blocks/quotes/block.json +48 -1
  278. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  279. package/blocks/quotes/components/quote-builder.tsx +1 -1
  280. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  281. package/blocks/quotes/components/quote-list.tsx +5 -3
  282. package/blocks/quotes/components/quote-related.tsx +3 -2
  283. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  284. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  285. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  286. package/blocks/radar/components/radar-overview.tsx +4 -4
  287. package/blocks/radar/components/radar-screen.tsx +2 -2
  288. package/blocks/sequencer/README.md +6 -0
  289. package/blocks/sequencer/block.json +28 -1
  290. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  291. package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
  292. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  293. package/blocks/sequencer/components/sequence-builder.tsx +19 -20
  294. package/blocks/sequencer/components/sequence-detail.tsx +4 -2
  295. package/blocks/sequencer/components/sequence-enrollments.tsx +4 -2
  296. package/blocks/sequencer/components/sequence-list.tsx +1 -1
  297. package/blocks/sequencer/components/sequence-stats.tsx +8 -8
  298. package/blocks/sequencer/components/sequences-screen.tsx +3 -0
  299. package/blocks/sequencer/components/step-editor.tsx +11 -11
  300. package/blocks/sequencer/lib/sequencer-utils.ts +5 -5
  301. package/blocks/social-ads/README.md +8 -0
  302. package/blocks/social-ads/block.json +137 -4
  303. package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
  304. package/blocks/social-ads/components/audience-sync.tsx +6 -2
  305. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  306. package/blocks/social-ads/components/campaign-detail.tsx +6 -1
  307. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  308. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  309. package/blocks/social-ads/components/creative-detail.tsx +6 -1
  310. package/blocks/social-ads/components/creative-gallery.tsx +20 -2
  311. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  312. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  313. package/blocks/social-ads/components/post-composer.tsx +4 -2
  314. package/blocks/social-ads/components/post-detail.tsx +3 -0
  315. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  316. package/blocks/social-ads/components/social-performance.tsx +80 -0
  317. package/blocks/social-ads/components/social-screen.tsx +33 -6
  318. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  319. package/blocks/surveys/README.md +91 -0
  320. package/blocks/surveys/block.json +299 -0
  321. package/blocks/surveys/components/account-nps.tsx +69 -0
  322. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  323. package/blocks/surveys/components/org-summary.tsx +82 -0
  324. package/blocks/surveys/components/sending-domains.tsx +113 -0
  325. package/blocks/surveys/components/survey-form.tsx +126 -0
  326. package/blocks/surveys/components/survey-responses.tsx +145 -0
  327. package/blocks/surveys/components/survey-results.tsx +72 -0
  328. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  329. package/blocks/surveys/components/survey-send.tsx +162 -0
  330. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  331. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  332. package/blocks/surveys/lib/surveys-types.ts +228 -0
  333. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  334. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  335. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  336. package/blocks/visitors/components/snippet-install.tsx +18 -9
  337. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  338. package/blocks/visitors/components/visitor-company-card.tsx +24 -14
  339. package/blocks/visitors/components/visitor-feed.tsx +5 -5
  340. package/blocks/webchat/components/chat-widget-config.tsx +1 -1
  341. package/blocks/webchat/components/conversation-list.tsx +1 -1
  342. package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
  343. package/blocks/workbench/components/run-progress.tsx +1 -1
  344. package/blocks/workbench/components/workbench-grid.tsx +1 -1
  345. package/blocks/workbench/components/workbench-screen.tsx +15 -5
  346. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  347. package/blocks/workflows/components/workflow-editor.tsx +1 -1
  348. package/dist/cli.js +536 -392
  349. package/dist/index.browser.js +1 -1
  350. package/dist/index.browser.js.map +1 -1
  351. package/dist/index.browser.mjs +1 -1
  352. package/dist/index.browser.mjs.map +1 -1
  353. package/dist/index.d.mts +2 -2
  354. package/dist/index.d.ts +2 -2
  355. package/dist/index.js +1 -1
  356. package/dist/index.js.map +1 -1
  357. package/dist/index.mjs +1 -1
  358. package/dist/index.mjs.map +1 -1
  359. package/dist/react.browser.js +1 -1
  360. package/dist/react.browser.js.map +1 -1
  361. package/dist/react.browser.mjs +1 -1
  362. package/dist/react.browser.mjs.map +1 -1
  363. package/dist/react.d.mts +2 -2
  364. package/dist/react.d.ts +2 -2
  365. package/dist/react.js +1 -1
  366. package/dist/react.js.map +1 -1
  367. package/dist/react.mjs +1 -1
  368. package/dist/react.mjs.map +1 -1
  369. package/dist/types/blocks/brand.d.ts +11 -2
  370. package/dist/types/blocks/create.d.ts +7 -3
  371. package/dist/types/blocks/doctor.d.ts +1 -0
  372. package/dist/types/blocks/flows.d.ts +7 -1
  373. package/dist/types/blocks/orgBrand.d.ts +34 -0
  374. package/dist/types/cli.d.ts +3 -0
  375. package/dist/types/version.d.ts +2 -2
  376. package/docs/api/desk.d.ts +2 -2
  377. package/docs/api/desk.md +3 -3
  378. package/package.json +1 -1
  379. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  380. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  381. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  382. package/stacks/6sense.json +17 -4
  383. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  384. package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
  385. package/stacks/apollo/pages/contacts.tsx.tmpl +29 -95
  386. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  387. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  388. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  389. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  390. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  391. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  392. package/stacks/apollo.json +36 -8
  393. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  394. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  395. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  396. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  397. package/stacks/calendly.json +8 -4
  398. package/stacks/clay/pages/companies.tsx.tmpl +17 -1
  399. package/stacks/clay/pages/contacts.tsx.tmpl +12 -0
  400. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  401. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  402. package/stacks/clay.json +16 -3
  403. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  404. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  405. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  406. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  407. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  408. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  409. package/stacks/gong.json +23 -6
  410. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  411. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  412. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  413. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  414. package/stacks/hootsuite.json +9 -4
  415. package/stacks/hubspot/pages/companies.tsx.tmpl +14 -35
  416. package/stacks/hubspot/pages/contacts.tsx.tmpl +29 -95
  417. package/stacks/hubspot/pages/deals.tsx.tmpl +10 -1
  418. package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
  419. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  420. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  421. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  422. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  423. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  424. package/stacks/hubspot.json +21 -5
  425. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  426. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  427. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  428. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  429. package/stacks/intercom.json +8 -4
  430. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  431. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  432. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  433. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  434. package/stacks/mailchimp.json +20 -5
  435. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  436. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  437. package/stacks/outreach/pages/contacts.tsx.tmpl +27 -66
  438. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  439. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  440. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  441. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  442. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  443. package/stacks/outreach.json +25 -7
  444. package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
  445. package/stacks/pipedrive/pages/contacts.tsx.tmpl +15 -35
  446. package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
  447. package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
  448. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  449. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  450. package/stacks/pipedrive.json +4 -2
  451. package/stacks/salesforce/pages/companies.tsx.tmpl +14 -35
  452. package/stacks/salesforce/pages/contacts.tsx.tmpl +20 -35
  453. package/stacks/salesforce/pages/deals.tsx.tmpl +10 -1
  454. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  455. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  456. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  457. package/stacks/salesforce.json +7 -3
  458. package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
  459. package/stacks/zoominfo/pages/contacts.tsx.tmpl +12 -0
  460. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  461. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  462. package/stacks/zoominfo.json +16 -3
  463. package/styles/styles.css +1 -1
  464. package/templates/next-app/app/globals.css.tmpl +349 -64
  465. package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
  466. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
package/dist/cli.js CHANGED
@@ -36,8 +36,8 @@ __export(cli_exports, {
36
36
  });
37
37
  module.exports = __toCommonJS(cli_exports);
38
38
  var import_node_child_process = require("child_process");
39
- var import_node_fs9 = __toESM(require("fs"));
40
- var import_node_path10 = __toESM(require("path"));
39
+ var import_node_fs10 = __toESM(require("fs"));
40
+ var import_node_path11 = __toESM(require("path"));
41
41
 
42
42
  // src/agentSetup.ts
43
43
  var ALL_TARGETS = ["claude", "codex", "cursor"];
@@ -119,9 +119,9 @@ ${section}
119
119
  }
120
120
  function planAgentSetup(targets, read, catalog) {
121
121
  const plans = [];
122
- const add = (target, path11, next) => {
123
- const current = read(path11);
124
- plans.push({ target, path: path11, content: next, action: current === null ? "create" : current === next ? "unchanged" : "update" });
122
+ const add = (target, path12, next) => {
123
+ const current = read(path12);
124
+ plans.push({ target, path: path12, content: next, action: current === null ? "create" : current === next ? "unchanged" : "update" });
125
125
  };
126
126
  for (const target of targets) {
127
127
  if (target === "claude") add(target, ".claude/skills/graph8-sdk/SKILL.md", claudeSkill(catalog));
@@ -158,7 +158,7 @@ var import_node_fs4 = __toESM(require("fs"));
158
158
  var import_node_path5 = __toESM(require("path"));
159
159
 
160
160
  // src/version.ts
161
- var SDK_VERSION = "0.370.0";
161
+ var SDK_VERSION = "0.373.0";
162
162
  var SDK_CLIENT_ID = `js-sdk/${SDK_VERSION}`;
163
163
 
164
164
  // src/blocks/paths.ts
@@ -4618,6 +4618,10 @@ Brand an app made by \`create\` (run it in the app's root). Every option is opti
4618
4618
  --logo <path> an svg, png, jpg, webp or gif (max 512 KB), copied to public/brand/ and shown in the sidebar
4619
4619
  --app-url <url> white-label: where link-outs go (default https://app.graph8.com)
4620
4620
  --link-label <text> white-label: what the platform is called in copy and links (default graph8)
4621
+ --from-org fill in what you did not pass from the organization's Brand Kit (needs G8_API_KEY in the
4622
+ environment or .env.local): name, primary colour, logo. Running brand with no options
4623
+ does the same when the kit has something. Values you pass always win; they replace what the app has now.
4624
+ --no-org-brand never read the Brand Kit
4621
4625
  --dir <path> the app's root (default: current directory)
4622
4626
  --dry-run print what would change, write nothing
4623
4627
  Running it again with the same options changes nothing.`;
@@ -4650,17 +4654,30 @@ ${BRAND_USAGE}`);
4650
4654
  if (takeBrandFlag(o, flag, next)) continue;
4651
4655
  if (flag === "--dir") o.dir = next();
4652
4656
  else if (arg === "--dry-run") o.dryRun = true;
4657
+ else if (arg === "--from-org") o.fromOrg = true;
4658
+ else if (arg === "--no-org-brand") o.noOrgBrand = true;
4653
4659
  else throw new Error(`unknown option ${arg}
4654
4660
 
4655
4661
  ${BRAND_USAGE}`);
4656
4662
  }
4657
- if (!hasBrandInput(o)) throw new Error(`brand needs at least one of --name, --brand-color, --dark-brand-color, --logo, --app-url, --link-label
4663
+ if (o.fromOrg && o.noOrgBrand) throw new Error(`--from-org and --no-org-brand contradict each other
4664
+
4665
+ ${BRAND_USAGE}`);
4666
+ if (!hasBrandInput(o) && !o.fromOrg) throw new Error(`brand needs at least one of --name, --brand-color, --dark-brand-color, --logo, --app-url, --link-label
4658
4667
 
4659
4668
  ${BRAND_USAGE}`);
4660
4669
  return o;
4661
4670
  }
4662
4671
  var LOGO_TYPES = ["svg", "png", "jpg", "jpeg", "webp", "gif"];
4663
4672
  var MAX_LOGO_BYTES = 512 * 1024;
4673
+ function mergeOrgBrand(explicit, org, skip = {}) {
4674
+ const e = explicit ?? {};
4675
+ const out = { ...e };
4676
+ if (e.name === void 0 && !skip.name && org.name) out.name = org.name;
4677
+ if (e.color === void 0 && e.darkColor === void 0 && org.color) out.color = org.color;
4678
+ if (e.logo === void 0 && org.logo) out.logo = org.logo;
4679
+ return out;
4680
+ }
4664
4681
  function brandProblem(o, cwd) {
4665
4682
  if (o.name !== void 0 && !o.name.trim()) return "--name cannot be empty";
4666
4683
  if (o.linkLabel !== void 0 && !o.linkLabel.trim()) return "--link-label cannot be empty";
@@ -4731,7 +4748,11 @@ function same(abs, content) {
4731
4748
  const cur = import_node_fs5.default.readFileSync(abs);
4732
4749
  return Buffer.isBuffer(content) ? cur.equals(content) : cur.toString("utf8") === content;
4733
4750
  }
4734
- function runBrand(o, io, cwd, fallbackName) {
4751
+ function runBrand(given, io, cwd, fallbackName, org) {
4752
+ const o = org && given.fromOrg ? mergeOrgBrand(given, org) : given;
4753
+ if (given.fromOrg && !hasBrandInput(o)) {
4754
+ return io.out("nothing to apply from the Brand Kit"), 0;
4755
+ }
4735
4756
  const root = import_node_path6.default.resolve(cwd, o.dir);
4736
4757
  if (!import_node_fs5.default.existsSync(import_node_path6.default.join(root, "package.json"))) {
4737
4758
  io.err(`no package.json in ${root}. Run this from your app's root, or pass --dir.`);
@@ -4769,348 +4790,10 @@ ${BRAND_USAGE}`), 1;
4769
4790
  return 0;
4770
4791
  }
4771
4792
 
4772
- // src/blocks/create.ts
4773
- var import_node_fs6 = __toESM(require("fs"));
4774
- var import_node_path7 = __toESM(require("path"));
4775
- var CREATE_USAGE = `Usage: npx @graph8/sdk create <stack> <dir> [options]
4776
-
4777
- Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
4778
- install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
4779
- --package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm
4780
- --no-install write the files and blocks, do not install dependencies
4781
- --dry-run print what would be written, write and install nothing
4782
- --force allow a non-empty <dir> (template files are overwritten, and graph8.json
4783
- is reset: blocks you added to that app earlier are no longer recorded)
4784
- Then: \`npm run dev\` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run
4785
- \`npx @graph8/sdk doctor --stack <stack>\` (add --production before going live).
4786
-
4787
- Or pick the features yourself, for any app (also: \`create custom --blocks ...\`):
4788
- npx @graph8/sdk create --blocks <id,id,...> [--title "Acme CRM"] <dir> [options]
4789
- Every requested block's dependencies come with it. The app gets one route and one sidebar entry per finished
4790
- Screen of the requested blocks, plus the user-facing Screens of their dependencies (asking for deals also gives
4791
- you crm's screens). \`/\` opens the analytics screen if you asked for one, else the first screen.
4792
- --title <text> the app's name (default: graph8 app)
4793
- --with-deps-screens also add the plain fallback page of dependency blocks that have no Screen
4794
- --no-deps-screens add screens only for the blocks you named
4795
- Run \`graph8-sdk list\` to see each block and which have Screens.
4796
-
4797
- Brand it (any stack or --blocks; the same as running \`graph8-sdk brand\` in the new app afterwards):
4798
- --name <text> the app's name (with --blocks, the same as --title)
4799
- --brand-color <hex> primary colour, AA-checked; --dark-brand-color <hex> for dark mode
4800
- --logo <path> svg/png/jpg/webp/gif, shown in the sidebar
4801
- --app-url <url> white-label: where "Open in graph8" links go; --link-label <text>: what they call it
4802
- A directory literally named \`custom\` would read as the alias: pass \`--blocks crm ./custom\`.`;
4803
- var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
4804
- function parseCreateArgs(argv) {
4805
- const o = { stack: "", dir: "", install: true, dryRun: false, force: false };
4806
- const positional = [];
4807
- for (let i = 0; i < argv.length; i++) {
4808
- const arg = argv[i];
4809
- const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
4810
- const [flag, inline] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [arg, void 0];
4811
- if (flag === "--package-manager") {
4812
- const v = inline ?? argv[++i];
4813
- if (!PACKAGE_MANAGERS.includes(v)) throw new Error(`--package-manager must be one of ${PACKAGE_MANAGERS.join(", ")}
4814
-
4815
- ${CREATE_USAGE}`);
4816
- o.packageManager = v;
4817
- } else if (flag === "--blocks") {
4818
- const v = inline ?? argv[++i];
4819
- if (v === void 0 || v.startsWith("--")) throw new Error(`--blocks needs a comma-separated list of block ids
4820
-
4821
- ${CREATE_USAGE}`);
4822
- o.blocks = [...o.blocks ?? [], ...v.split(",").map((x) => x.trim()).filter(Boolean)];
4823
- } else if (flag === "--title") {
4824
- const v = inline ?? argv[++i];
4825
- if (v === void 0 || v.startsWith("--")) throw new Error(`--title needs a value
4826
-
4827
- ${CREATE_USAGE}`);
4828
- o.title = v;
4829
- } else if (takeBrandFlag(o.brand ??= {}, flag, () => {
4830
- const v = inline ?? argv[++i];
4831
- if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
4832
-
4833
- ${CREATE_USAGE}`);
4834
- return v;
4835
- })) {
4836
- } else if (arg === "--with-deps-screens") o.depsScreens = "with";
4837
- else if (arg === "--no-deps-screens") o.depsScreens = "none";
4838
- else if (arg === "--no-install") o.install = false;
4839
- else if (arg === "--dry-run") o.dryRun = true;
4840
- else if (arg === "--force") o.force = true;
4841
- else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
4842
-
4843
- ${CREATE_USAGE}`);
4844
- else positional.push(arg);
4845
- }
4846
- if (o.brand && !hasBrandInput(o.brand)) delete o.brand;
4847
- if (o.brand?.name !== void 0 && o.title !== void 0 && o.brand.name !== o.title) throw new Error(`--name and --title name the same thing: pass one
4848
-
4849
- ${CREATE_USAGE}`);
4850
- if (o.blocks || positional[0] === "custom") return parseCustom(o, positional);
4851
- if (o.title || o.depsScreens) throw new Error(`--title, --with-deps-screens and --no-deps-screens go with --blocks
4852
-
4853
- ${CREATE_USAGE}`);
4854
- if (positional.length !== 2) throw new Error(`create needs a stack and a directory
4855
-
4856
- ${CREATE_USAGE}`);
4857
- [o.stack, o.dir] = positional;
4858
- return o;
4859
- }
4860
- function parseCustom(o, positional) {
4861
- const rest = positional[0] === "custom" ? positional.slice(1) : positional;
4862
- if (!o.blocks?.length) throw new Error(`create custom needs --blocks <id,id,...> (see \`graph8-sdk list\`)
4863
-
4864
- ${CREATE_USAGE}`);
4865
- if (rest.length !== 1) {
4866
- throw new Error(`${rest.length ? "a stack name cannot be combined with --blocks; pass only the directory" : "create --blocks needs a directory"}
4867
-
4868
- ${CREATE_USAGE}`);
4869
- }
4870
- return { ...o, stack: "custom", dir: rest[0] };
4871
- }
4872
- function packageManagerFromUserAgent(ua) {
4873
- const name = /^(npm|pnpm|yarn|bun)\//.exec(ua ?? "")?.[1];
4874
- return name ?? "npm";
4875
- }
4876
- function appNameFor(dir) {
4877
- const base = import_node_path7.default.basename(import_node_path7.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
4878
- return base || "g8-app";
4879
- }
4880
- var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
4881
- function walk(dir, base = dir) {
4882
- const out = [];
4883
- for (const e of import_node_fs6.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
4884
- const full = import_node_path7.default.join(dir, e.name);
4885
- if (e.isDirectory()) out.push(...walk(full, base));
4886
- else out.push(posix(import_node_path7.default.relative(base, full)));
4887
- }
4888
- return out;
4889
- }
4890
- function outputName(rel) {
4891
- const noExt = rel.replace(/\.tmpl$/, "");
4892
- const dir = import_node_path7.default.posix.dirname(noExt);
4893
- const base = import_node_path7.default.posix.basename(noExt).replace(/^dot-/, ".");
4894
- return dir === "." ? base : `${dir}/${base}`;
4895
- }
4896
- function stackProblem(reg, stack) {
4897
- const { missing } = stackStatus(reg, stack);
4898
- if (missing.length) {
4899
- const ready = reg.stacks.filter((s) => !stackStatus(reg, s).missing.length).map((s) => s.id);
4900
- return [
4901
- `stack "${stack.id}" is planned: these blocks are not in this version of @graph8/sdk yet: ${missing.join(", ")}.`,
4902
- `Stacks you can create now: ${ready.join(", ") || "none"}. Or create one of those and add blocks as they ship (\`graph8-sdk add <block>\`).`
4903
- ].join("\n");
4904
- }
4905
- const seen = /* @__PURE__ */ new Set();
4906
- const homeIssue = homeProblem(stack);
4907
- if (homeIssue) return homeIssue;
4908
- for (const n of stack.nav) {
4909
- const block = findBlock(reg, n.block);
4910
- if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
4911
- if (!block?.components.some((c) => c.name === n.component)) return `stack "${stack.id}" nav "${n.label}" uses ${n.component}, which block "${n.block}" does not declare.`;
4912
- if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
4913
- if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
4914
- seen.add(n.path);
4915
- if (n.page && (!reg.stacksRoot || !import_node_fs6.default.existsSync(import_node_path7.default.join(reg.stacksRoot, stack.id, n.page)))) {
4916
- return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
4917
- }
4918
- }
4919
- return null;
4920
- }
4921
- function homeProblem(stack) {
4922
- if (!stack.home) return null;
4923
- if (!stack.nav.some((n) => n.path === stack.home)) return `stack "${stack.id}" has home ${stack.home}, which is not one of its nav paths.`;
4924
- if (stack.home !== "/" && stack.nav.some((n) => n.path === "/")) return `stack "${stack.id}" has home ${stack.home} but also a nav entry at "/": drop one.`;
4925
- return null;
4926
- }
4927
- function stackPageFile(reg, stack, n) {
4928
- return n.page && reg.stacksRoot ? import_node_path7.default.join(reg.stacksRoot, stack.id, n.page) : null;
4929
- }
4930
- function homeRedirect(stack) {
4931
- if (stack.home) return stack.home === "/" ? null : stack.home;
4932
- return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
4933
- }
4934
- function renderPages(reg, stack, templates) {
4935
- const pageTpl = import_node_fs6.default.readFileSync(import_node_path7.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
4936
- const files = stack.nav.map((n) => {
4937
- const block = findBlock(reg, n.block);
4938
- const comp = block.components.find((c) => c.name === n.component);
4939
- const custom = stackPageFile(reg, stack, n);
4940
- const vars2 = {
4941
- stackId: stack.id,
4942
- block: block.id,
4943
- blockDir: `components/g8/${block.dir}`,
4944
- component: n.component,
4945
- importPath: componentImportPath(block, comp.file),
4946
- propsJson: JSON.stringify(n.props ?? {}, null, 2)
4947
- };
4948
- return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs6.default.readFileSync(custom, "utf8") : pageTpl, vars2) };
4949
- });
4950
- const to = homeRedirect(stack);
4951
- if (to) {
4952
- const redirect = import_node_fs6.default.readFileSync(import_node_path7.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
4953
- files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
4954
- }
4955
- return files;
4956
- }
4957
- function peerDependenciesFor(blocks, io, stack) {
4958
- const peers = {};
4959
- for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
4960
- const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(import_node_fs6.default.readFileSync(import_node_path7.default.join(b.root, f), "utf8"))));
4961
- if (usesUi && io.sdkPackageJson && import_node_fs6.default.existsSync(io.sdkPackageJson)) {
4962
- const sdk = JSON.parse(import_node_fs6.default.readFileSync(io.sdkPackageJson, "utf8"));
4963
- for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
4964
- }
4965
- if (navUsesExtras(stack.nav)) peers["lucide-react"] ??= lucideRange(io);
4966
- return peers;
4967
- }
4968
- function lucideRange(io) {
4969
- try {
4970
- const sdk = JSON.parse(import_node_fs6.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
4971
- return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
4972
- } catch {
4973
- return ">=0.400.0";
4974
- }
4975
- }
4976
- function mergedPackageJson(rendered, peers) {
4977
- const pkg = JSON.parse(rendered);
4978
- for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
4979
- pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
4980
- return `${JSON.stringify(pkg, null, 2)}
4981
- `;
4982
- }
4983
- function planCreate(reg, stack, appName, pm, io, app) {
4984
- const templates = import_node_path7.default.join(io.templatesRoot, "next-app");
4985
- const blocks = resolveWithDeps(reg, stack.blocks);
4986
- const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
4987
- const vars2 = {
4988
- appName,
4989
- sdkVersion: SDK_VERSION,
4990
- stackId: stack.id,
4991
- title: stack.title,
4992
- titleJson: JSON.stringify(stack.title),
4993
- ...navParts(stack),
4994
- devCommand,
4995
- installCommand: `${pm} install`,
4996
- doctorCommand: app ? "npx @graph8/sdk doctor" : `npx @graph8/sdk doctor --stack ${stack.id}`,
4997
- screens: stack.nav.map((n) => `- ${n.label}: \`${n.path}\` (${n.block} block, \`${n.component}\`)`).join("\n") || "- (none yet: add a block with `npx @graph8/sdk add <block>`)"
4998
- };
4999
- const files = [];
5000
- for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
5001
- let content = render(import_node_fs6.default.readFileSync(import_node_path7.default.join(templates, rel), "utf8"), vars2);
5002
- if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
5003
- files.push({ rel: outputName(rel), content });
5004
- }
5005
- files.push(...renderPages(reg, stack, templates));
5006
- files.push({ rel: CONFIG_FILE, content: renderBrandConfig({ name: stack.title, ...DEFAULT_FIELDS }) });
5007
- const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, ...app ? { app } : {}, blocks: {} };
5008
- files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
5009
- ` });
5010
- return files;
5011
- }
5012
- function dirProblem(abs, force) {
5013
- if (!import_node_fs6.default.existsSync(abs)) return null;
5014
- if (!import_node_fs6.default.statSync(abs).isDirectory()) return `${abs} exists and is not a directory.`;
5015
- if (force || import_node_fs6.default.readdirSync(abs).length === 0) return null;
5016
- return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
5017
- }
5018
- function runCreate(opts, reg, io, cwd) {
5019
- let app;
5020
- let stack = opts.blocks ? void 0 : reg.stacks.find((s) => s.id === opts.stack);
5021
- if (opts.blocks) {
5022
- let composed;
5023
- try {
5024
- composed = composeApp(reg, opts.blocks, { title: opts.title, withDepsScreens: opts.depsScreens === "with", noDepsScreens: opts.depsScreens === "none" });
5025
- } catch (e) {
5026
- io.err(e.message);
5027
- return 1;
5028
- }
5029
- stack = composed.stack;
5030
- app = { title: stack.title, blocks: [...new Set(opts.blocks)], screens: composed.screens.map((x) => ({ block: x.block, component: x.component, path: x.path })) };
5031
- composed.warnings.forEach((w) => io.err(`warning: ${w}`));
5032
- } else if (!stack) {
5033
- io.err(`unknown stack "${opts.stack}". Stacks: ${reg.stacks.map((s) => s.id).join(", ") || "none"} (\`graph8-sdk list\`).`);
5034
- return 1;
5035
- }
5036
- if (!stack) return 1;
5037
- const name = opts.brand?.name?.trim();
5038
- if (name) {
5039
- stack = { ...stack, title: name };
5040
- if (app) app = { ...app, title: name };
5041
- }
5042
- const problem = stackProblem(reg, stack);
5043
- if (problem) return io.err(problem), 1;
5044
- const abs = import_node_path7.default.resolve(cwd, opts.dir);
5045
- const dirIssue = dirProblem(abs, opts.force);
5046
- if (dirIssue) return io.err(dirIssue), 1;
5047
- const brandIssue = opts.brand ? brandProblem(opts.brand, cwd) : null;
5048
- if (brandIssue) return io.err(brandIssue), 1;
5049
- const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
5050
- let files;
5051
- try {
5052
- files = planCreate(reg, stack, appNameFor(opts.dir), pm, io, app);
5053
- } catch (e) {
5054
- io.err(e.message);
5055
- return 1;
5056
- }
5057
- const blocks = resolveWithDeps(reg, stack.blocks);
5058
- io.out(`${opts.dryRun ? "Dry run: " : ""}creating "${stack.title}" (${app ? `custom: ${app.blocks.join(", ")}` : stack.id}) in ${opts.dir}`);
5059
- for (const f of files) io.out(`${opts.dryRun ? "would write" : "write"} ${f.rel}`);
5060
- if (opts.dryRun) {
5061
- io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
5062
- if (opts.brand) io.out(`would brand the app: ${Object.entries(opts.brand).map(([k, v]) => `${k} ${v}`).join(", ")}`);
5063
- io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
5064
- return 0;
5065
- }
5066
- for (const f of files) {
5067
- const dest = import_node_path7.default.join(abs, f.rel);
5068
- import_node_fs6.default.mkdirSync(import_node_path7.default.dirname(dest), { recursive: true });
5069
- import_node_fs6.default.writeFileSync(dest, f.content, "utf8");
5070
- }
5071
- return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
5072
- }
5073
- function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
5074
- for (const b of blocks) {
5075
- const code = runAdd({ spec: b.id, dir: abs, target: "components/g8", dryRun: false, force: opts.force, install: false, wireScreens: false }, reg, io, cwd);
5076
- if (code !== 0) {
5077
- io.err(`adding block "${b.id}" failed; the app in ${opts.dir} is incomplete.`);
5078
- return code;
5079
- }
5080
- }
5081
- if (opts.brand) {
5082
- io.out("branding the app (graph8-sdk brand)");
5083
- const code = runBrand({ ...opts.brand, dir: abs }, io, cwd, stack.title);
5084
- if (code !== 0) {
5085
- io.err(`branding failed; the app in ${opts.dir} is complete but unbranded. Fix the option and run \`npx @graph8/sdk brand\` in it.`);
5086
- return code;
5087
- }
5088
- }
5089
- if (opts.install) {
5090
- io.out(`installing dependencies: ${pm} install`);
5091
- const code = io.spawn(pm, ["install"], abs);
5092
- if (code !== 0) {
5093
- io.err(`dependency install failed (exit ${code}). Run it yourself: cd ${quoted(opts.dir)} && ${pm} install`);
5094
- return 1;
5095
- }
5096
- }
5097
- const cd = opts.dir === "." ? "" : `cd ${quoted(opts.dir)} && `;
5098
- io.out(
5099
- [
5100
- "",
5101
- `Done. Next:`,
5102
- ` 1. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`,
5103
- ` 2. open http://localhost:3000 and paste a graph8 API key in the Connect graph8 screen (local preview: only`,
5104
- ` this computer can reach the app's /api/g8 route), or set G8_API_KEY in ${import_node_path7.default.posix.join(opts.dir, ".env.local")}`,
5105
- ` 3. ${cd}npx @graph8/sdk doctor${opts.blocks ? "" : ` --stack ${stack.id}`} (before going live: add --production)`
5106
- ].join("\n")
5107
- );
5108
- return 0;
5109
- }
5110
-
5111
- // src/blocks/doctor.ts
5112
- var import_node_fs8 = __toESM(require("fs"));
5113
- var import_node_path9 = __toESM(require("path"));
4793
+ // src/blocks/orgBrand.ts
4794
+ var import_node_fs8 = __toESM(require("fs"));
4795
+ var import_node_os = __toESM(require("os"));
4796
+ var import_node_path9 = __toESM(require("path"));
5114
4797
 
5115
4798
  // src/generated/billable.ts
5116
4799
  var BILLABLE_ROUTES = {
@@ -5935,7 +5618,7 @@ function warnIfTargetOrgIgnored(apiKey, headers) {
5935
5618
  );
5936
5619
  }
5937
5620
  }
5938
- async function request(baseUrl, path11, apiKey, opts = {}) {
5621
+ async function request(baseUrl, path12, apiKey, opts = {}) {
5939
5622
  const {
5940
5623
  method = "GET",
5941
5624
  body,
@@ -5948,7 +5631,7 @@ async function request(baseUrl, path11, apiKey, opts = {}) {
5948
5631
  fetchImpl = fetch,
5949
5632
  sleepImpl = defaultSleep
5950
5633
  } = opts;
5951
- const url = baseUrl.replace(/\/+$/, "") + path11 + buildQuery(query);
5634
+ const url = baseUrl.replace(/\/+$/, "") + path12 + buildQuery(query);
5952
5635
  const finalHeaders = {
5953
5636
  "Content-Type": "application/json",
5954
5637
  Authorization: `Bearer ${apiKey}`,
@@ -6108,9 +5791,13 @@ function createApiClient(apiKey, apiUrl) {
6108
5791
  return client;
6109
5792
  }
6110
5793
 
6111
- // src/blocks/flows.ts
5794
+ // src/blocks/doctor.ts
6112
5795
  var import_node_fs7 = __toESM(require("fs"));
6113
5796
  var import_node_path8 = __toESM(require("path"));
5797
+
5798
+ // src/blocks/flows.ts
5799
+ var import_node_fs6 = __toESM(require("fs"));
5800
+ var import_node_path7 = __toESM(require("path"));
6114
5801
  var IMPORT_RE2 = /(?:^|\n)\s*(?:import|export)\s[^'"`;]*?from\s*["']([^"']+)["']|(?:^|\n)\s*import\s*["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g;
6115
5802
  var EXTENSIONS = ["", ".tsx", ".ts", "/index.tsx", "/index.ts"];
6116
5803
  function specifiers(source) {
@@ -6119,18 +5806,18 @@ function specifiers(source) {
6119
5806
  function resolveFile(base) {
6120
5807
  for (const ext of EXTENSIONS) {
6121
5808
  const candidate = base + ext;
6122
- if (import_node_fs7.default.existsSync(candidate) && import_node_fs7.default.statSync(candidate).isFile()) return candidate;
5809
+ if (import_node_fs6.default.existsSync(candidate) && import_node_fs6.default.statSync(candidate).isFile()) return candidate;
6123
5810
  }
6124
5811
  return null;
6125
5812
  }
6126
5813
  function resolveImport(spec, from, reg) {
6127
- if (spec.startsWith(".")) return resolveFile(import_node_path8.default.resolve(import_node_path8.default.dirname(from), spec));
5814
+ if (spec.startsWith(".")) return resolveFile(import_node_path7.default.resolve(import_node_path7.default.dirname(from), spec));
6128
5815
  const g8 = /^@\/g8\/([^/]+)\/(.+)$/.exec(spec);
6129
5816
  if (!g8) return null;
6130
5817
  const owner = findBlock(reg, g8[1]);
6131
- if (owner) return resolveFile(import_node_path8.default.join(owner.root, "components", g8[2])) ?? resolveFile(import_node_path8.default.join(owner.root, g8[2]));
5818
+ if (owner) return resolveFile(import_node_path7.default.join(owner.root, "components", g8[2])) ?? resolveFile(import_node_path7.default.join(owner.root, g8[2]));
6132
5819
  const foundation = findBlock(reg, "foundation");
6133
- return foundation ? resolveFile(import_node_path8.default.join(foundation.root, g8[1], g8[2])) : null;
5820
+ return foundation ? resolveFile(import_node_path7.default.join(foundation.root, g8[1], g8[2])) : null;
6134
5821
  }
6135
5822
  function resolveInstalled(spec, reg) {
6136
5823
  const m = /^@\/components\/g8\/([^/]+)\/(.+)$/.exec(spec);
@@ -6138,7 +5825,7 @@ function resolveInstalled(spec, reg) {
6138
5825
  if (!m || !block) return null;
6139
5826
  const files = [...block.components, ...block.hooks, ...block.lib].map((x) => x.file);
6140
5827
  const hit = files.find((f) => (installedRel(f) ?? f).replace(/\.(tsx|ts|jsx|js)$/, "") === m[2]);
6141
- return hit ? import_node_path8.default.join(block.root, hit) : null;
5828
+ return hit ? import_node_path7.default.join(block.root, hit) : null;
6142
5829
  }
6143
5830
  function seeds(reg, stack) {
6144
5831
  const out = [];
@@ -6146,13 +5833,13 @@ function seeds(reg, stack) {
6146
5833
  const comp = findBlock(reg, n.block)?.components.find((c) => c.name === n.component);
6147
5834
  const block = findBlock(reg, n.block);
6148
5835
  if (!block || !comp) continue;
6149
- const pageFile = n.page && reg.stacksRoot ? import_node_path8.default.join(reg.stacksRoot, stack.id, n.page) : null;
6150
- if (!pageFile || !import_node_fs7.default.existsSync(pageFile)) {
6151
- out.push(import_node_path8.default.join(block.root, comp.file));
5836
+ const pageFile = n.page && reg.stacksRoot ? import_node_path7.default.join(reg.stacksRoot, stack.id, n.page) : null;
5837
+ if (!pageFile || !import_node_fs6.default.existsSync(pageFile)) {
5838
+ out.push(import_node_path7.default.join(block.root, comp.file));
6152
5839
  continue;
6153
5840
  }
6154
- const source = import_node_fs7.default.readFileSync(pageFile, "utf8");
6155
- if (source.includes("{{importPath}}")) out.push(import_node_path8.default.join(block.root, comp.file));
5841
+ const source = import_node_fs6.default.readFileSync(pageFile, "utf8");
5842
+ if (source.includes("{{importPath}}")) out.push(import_node_path7.default.join(block.root, comp.file));
6156
5843
  for (const spec of specifiers(source)) {
6157
5844
  const hit = resolveInstalled(spec, reg);
6158
5845
  if (hit) out.push(hit);
@@ -6168,17 +5855,18 @@ function reachedFiles(reg, stack) {
6168
5855
  if (seen.has(file)) continue;
6169
5856
  seen.add(file);
6170
5857
  if (!/\.(tsx?|jsx?)$/.test(file)) continue;
6171
- for (const spec of specifiers(import_node_fs7.default.readFileSync(file, "utf8"))) {
5858
+ for (const spec of specifiers(import_node_fs6.default.readFileSync(file, "utf8"))) {
6172
5859
  const next = resolveImport(spec, file, reg);
6173
5860
  if (next && !seen.has(next)) queue.push(next);
6174
5861
  }
6175
5862
  }
6176
5863
  return seen;
6177
5864
  }
5865
+ var LIBRARY_BLOCKS = /* @__PURE__ */ new Set(["foundation", "pickers"]);
6178
5866
  function unreachedComponents(reg, stack) {
6179
5867
  const reached = reachedFiles(reg, stack);
6180
- const blocks = resolveWithDeps(reg, stack.blocks).filter((b) => b.id !== "foundation" && stack.blocks.includes(b.id));
6181
- return blocks.flatMap((b) => b.components.filter((c) => !reached.has(import_node_path8.default.join(b.root, c.file))).map((c) => ({ block: b.id, component: c.name })));
5868
+ const blocks = resolveWithDeps(reg, stack.blocks).filter((b) => !LIBRARY_BLOCKS.has(b.id) && stack.blocks.includes(b.id));
5869
+ return blocks.flatMap((b) => b.components.filter((c) => !reached.has(import_node_path7.default.join(b.root, c.file))).map((c) => ({ block: b.id, component: c.name })));
6182
5870
  }
6183
5871
 
6184
5872
  // src/blocks/doctor.ts
@@ -6236,9 +5924,9 @@ function parseEnvFile(text) {
6236
5924
  function readSetting(name, root, env) {
6237
5925
  if (env[name]) return env[name];
6238
5926
  for (const f of [".env.local"]) {
6239
- const file = import_node_path9.default.join(root, f);
6240
- if (import_node_fs8.default.existsSync(file)) {
6241
- const v = parseEnvFile(import_node_fs8.default.readFileSync(file, "utf8"))[name];
5927
+ const file = import_node_path8.default.join(root, f);
5928
+ if (import_node_fs7.default.existsSync(file)) {
5929
+ const v = parseEnvFile(import_node_fs7.default.readFileSync(file, "utf8"))[name];
6242
5930
  if (v) return v;
6243
5931
  }
6244
5932
  }
@@ -6259,7 +5947,7 @@ function selectBlocks(opts, reg, root) {
6259
5947
  }
6260
5948
  if (!ids.length) {
6261
5949
  try {
6262
- const ledger = JSON.parse(import_node_fs8.default.readFileSync(import_node_path9.default.join(root, "graph8.json"), "utf8"));
5950
+ const ledger = JSON.parse(import_node_fs7.default.readFileSync(import_node_path8.default.join(root, "graph8.json"), "utf8"));
6263
5951
  ids = Object.keys(ledger.blocks ?? {});
6264
5952
  } catch {
6265
5953
  ids = [];
@@ -6273,7 +5961,7 @@ function errorText(e, key) {
6273
5961
  return `${status ? `HTTP ${status}: ` : ""}${msg}`.split(key).join("[redacted]");
6274
5962
  }
6275
5963
  async function runDoctor(opts, reg, io, cwd) {
6276
- const root = import_node_path9.default.resolve(cwd, opts.dir);
5964
+ const root = import_node_path8.default.resolve(cwd, opts.dir);
6277
5965
  let blocks;
6278
5966
  try {
6279
5967
  blocks = selectBlocks(opts, reg, root);
@@ -6359,7 +6047,7 @@ function printFlows(opts, reg, out, line) {
6359
6047
  function printAppScreens(root, reg, out) {
6360
6048
  let app;
6361
6049
  try {
6362
- app = JSON.parse(import_node_fs8.default.readFileSync(import_node_path9.default.join(root, "graph8.json"), "utf8")).app;
6050
+ app = JSON.parse(import_node_fs7.default.readFileSync(import_node_path8.default.join(root, "graph8.json"), "utf8")).app;
6363
6051
  } catch {
6364
6052
  return;
6365
6053
  }
@@ -6382,14 +6070,14 @@ function printChecklist(blocks, out) {
6382
6070
  }
6383
6071
  function readLocalKey(root) {
6384
6072
  try {
6385
- return import_node_fs8.default.readFileSync(import_node_path9.default.join(root, ".g8", "local-key"), "utf8").trim() || void 0;
6073
+ return import_node_fs7.default.readFileSync(import_node_path8.default.join(root, ".g8", "local-key"), "utf8").trim() || void 0;
6386
6074
  } catch {
6387
6075
  return void 0;
6388
6076
  }
6389
6077
  }
6390
6078
  var ROUTE_FILES = ["app/api/g8/[...path]/route.ts", "src/app/api/g8/[...path]/route.ts"];
6391
6079
  function findRoute(root) {
6392
- return ROUTE_FILES.find((f) => import_node_fs8.default.existsSync(import_node_path9.default.join(root, f))) ?? null;
6080
+ return ROUTE_FILES.find((f) => import_node_fs7.default.existsSync(import_node_path8.default.join(root, f))) ?? null;
6393
6081
  }
6394
6082
  function checkRoute(root, blocks, production, out, line) {
6395
6083
  const rel = findRoute(root);
@@ -6397,7 +6085,7 @@ function checkRoute(root, blocks, production, out, line) {
6397
6085
  if (production) line(false, `no proxy route found (${ROUTE_FILES[0]}): run doctor from the app's root or pass --dir`);
6398
6086
  return;
6399
6087
  }
6400
- const facts = inspectRoute(import_node_fs8.default.readFileSync(import_node_path9.default.join(root, rel), "utf8"));
6088
+ const facts = inspectRoute(import_node_fs7.default.readFileSync(import_node_path8.default.join(root, rel), "utf8"));
6401
6089
  out(`
6402
6090
  Proxy route (${rel})${production ? ", production gate" : ""}:`);
6403
6091
  const problem = (text) => production ? line(false, text) : out(`warn ${text}`);
@@ -6431,8 +6119,8 @@ function fixPrefixes(root, blocks, io) {
6431
6119
  io.err("no blocks found: graph8.json lists none (or pass --blocks / --stack).");
6432
6120
  return 1;
6433
6121
  }
6434
- const file = import_node_path9.default.join(root, rel);
6435
- const source = import_node_fs8.default.readFileSync(file, "utf8");
6122
+ const file = import_node_path8.default.join(root, rel);
6123
+ const source = import_node_fs7.default.readFileSync(file, "utf8");
6436
6124
  const prefixes = derivePrefixes(blocks);
6437
6125
  const next = applyPrefixes(source, prefixes) ?? upgradeLegacyRoute(source, prefixes);
6438
6126
  if (next === null) {
@@ -6442,12 +6130,448 @@ function fixPrefixes(root, blocks, io) {
6442
6130
  }
6443
6131
  if (next === source) io.out(`keep ${rel} (allowedPrefixes already match the ${prefixes.length} areas of the installed blocks)`);
6444
6132
  else {
6445
- import_node_fs8.default.writeFileSync(file, next, "utf8");
6133
+ import_node_fs7.default.writeFileSync(file, next, "utf8");
6446
6134
  io.out(`update ${rel} (allowedPrefixes: ${prefixes.join(", ")})`);
6447
6135
  }
6448
6136
  return 0;
6449
6137
  }
6450
6138
 
6139
+ // src/blocks/orgBrand.ts
6140
+ var TIMEOUT_MS = 8e3;
6141
+ var NONE = (info) => ({ values: {}, info, cleanup: () => {
6142
+ } });
6143
+ function colorOf(v) {
6144
+ const raw = typeof v === "string" ? v : v && typeof v === "object" ? v.value : null;
6145
+ return typeof raw === "string" ? normalizeHex(raw) : null;
6146
+ }
6147
+ function pickColor(tokens) {
6148
+ const t = tokens ?? {};
6149
+ for (const k of ["primary", "brand", "accent"]) {
6150
+ const c = colorOf(t.colors?.[k]);
6151
+ if (c) return c;
6152
+ }
6153
+ return colorOf(t.components?.button?.background);
6154
+ }
6155
+ function cleanName(v) {
6156
+ if (typeof v !== "string") return null;
6157
+ const s = v.replace(/[\u0000-\u001f\u007f<>`$\\]/g, "").replace(/\s+/g, " ").trim().slice(0, 60);
6158
+ return s || null;
6159
+ }
6160
+ var MIME = { "image/svg+xml": "svg", "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif" };
6161
+ function privateHost(host) {
6162
+ const h = host.toLowerCase().replace(/^\[|\]$/g, "");
6163
+ return h === "localhost" || h.endsWith(".localhost") || h.endsWith(".internal") || /^(127\.|10\.|0\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(h) || h === "::1" || /^(fc|fd|fe80)/.test(h);
6164
+ }
6165
+ async function downloadLogo(url, f) {
6166
+ let bytes;
6167
+ let ext;
6168
+ if (url.startsWith("data:")) {
6169
+ const m = /^data:([^;,]+)(;base64)?,(.*)$/s.exec(url);
6170
+ if (!m) return null;
6171
+ ext = MIME[m[1].toLowerCase()];
6172
+ bytes = m[2] ? Buffer.from(m[3], "base64") : Buffer.from(decodeURIComponent(m[3]));
6173
+ } else {
6174
+ const u = new URL(url);
6175
+ if (u.protocol !== "https:" || privateHost(u.hostname)) return null;
6176
+ const res = await f(u, { redirect: "error", signal: AbortSignal.timeout(TIMEOUT_MS) });
6177
+ if (!res.ok) return null;
6178
+ const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
6179
+ ext = MIME[type] ?? (type && type !== "application/octet-stream" ? void 0 : LOGO_TYPES.find((e) => u.pathname.toLowerCase().endsWith(`.${e}`)));
6180
+ bytes = Buffer.from(await res.arrayBuffer());
6181
+ }
6182
+ if (!ext || !LOGO_TYPES.includes(ext) || !bytes.length || bytes.length > MAX_LOGO_BYTES) return null;
6183
+ const dir = import_node_fs8.default.mkdtempSync(import_node_path9.default.join(import_node_os.default.tmpdir(), "g8-org-logo-"));
6184
+ const file = import_node_path9.default.join(dir, `logo.${ext}`);
6185
+ import_node_fs8.default.writeFileSync(file, bytes);
6186
+ return { file, dir };
6187
+ }
6188
+ async function tryCall(call, op) {
6189
+ try {
6190
+ return { data: (await call(op))?.data };
6191
+ } catch (e) {
6192
+ return { status: e.status ?? 0 };
6193
+ }
6194
+ }
6195
+ async function pullOrgBrand(io, root) {
6196
+ try {
6197
+ const key = readSetting("G8_API_KEY", root, io.env);
6198
+ if (!key) return NONE("org brand: no G8_API_KEY found, using the default look (set one to pick up your Brand Kit, or pass --no-org-brand)");
6199
+ const api = createApiClient(key, readSetting("G8_API_URL", root, io.env));
6200
+ const f = io.fetch ?? fetch;
6201
+ const call = (op) => api.call(op, void 0, { fetchImpl: (u, i) => f(u, { ...i, signal: AbortSignal.timeout(TIMEOUT_MS) }), maxRetries: 0 });
6202
+ const [kit, me] = await Promise.all([tryCall(call, "get_brand_kit_overview_brand_kit_get"), tryCall(call, "describe_current_key_me_get")]);
6203
+ if (!kit.data) return NONE(`org brand: the Brand Kit could not be read (${kit.status ? `HTTP ${kit.status}` : "unreachable"}), using the default look`);
6204
+ const values = {};
6205
+ const color = pickColor(kit.data.design_tokens);
6206
+ if (color) values.color = color;
6207
+ const name = cleanName(me.data?.org_name);
6208
+ if (name) values.name = name;
6209
+ let dir = null;
6210
+ const logoUrl = typeof kit.data.logo_url === "string" ? kit.data.logo_url.trim() : "";
6211
+ if (logoUrl) {
6212
+ try {
6213
+ const dl = await downloadLogo(logoUrl, f);
6214
+ if (dl) values.logo = dl.file, dir = dl.dir;
6215
+ } catch {
6216
+ }
6217
+ }
6218
+ const got = [values.name && "name", values.color && "colour", values.logo && "logo"].filter(Boolean);
6219
+ const cleanup = () => dir && import_node_fs8.default.rmSync(dir, { recursive: true, force: true });
6220
+ if (!got.length) return NONE("org brand: the Brand Kit is empty, using the default look");
6221
+ return { values, info: `org brand: using your Brand Kit (${got.join(", ")}); explicit flags win, --no-org-brand skips this`, cleanup };
6222
+ } catch {
6223
+ return NONE("org brand: could not reach the Brand Kit, using the default look");
6224
+ }
6225
+ }
6226
+
6227
+ // src/blocks/create.ts
6228
+ var import_node_fs9 = __toESM(require("fs"));
6229
+ var import_node_path10 = __toESM(require("path"));
6230
+ var CREATE_USAGE = `Usage: npx @graph8/sdk create <stack> <dir> [options]
6231
+
6232
+ Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
6233
+ install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
6234
+ --package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm
6235
+ --no-install write the files and blocks, do not install dependencies
6236
+ --dry-run print what would be written, write and install nothing
6237
+ --force allow a non-empty <dir> (template files are overwritten, and graph8.json
6238
+ is reset: blocks you added to that app earlier are no longer recorded)
6239
+ Then: \`npm run dev\` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run
6240
+ \`npx @graph8/sdk doctor --stack <stack>\` (add --production before going live).
6241
+
6242
+ Or pick the features yourself, for any app (also: \`create custom --blocks ...\`):
6243
+ npx @graph8/sdk create --blocks <id,id,...> [--title "Acme CRM"] <dir> [options]
6244
+ Every requested block's dependencies come with it. The app gets one route and one sidebar entry per finished
6245
+ Screen of the requested blocks, plus the user-facing Screens of their dependencies (asking for deals also gives
6246
+ you crm's screens). \`/\` opens the analytics screen if you asked for one, else the first screen.
6247
+ --title <text> the app's name (default: graph8 app)
6248
+ --with-deps-screens also add the plain fallback page of dependency blocks that have no Screen
6249
+ --no-deps-screens add screens only for the blocks you named
6250
+ Run \`graph8-sdk list\` to see each block and which have Screens.
6251
+
6252
+ Brand it (any stack or --blocks; the same as running \`graph8-sdk brand\` in the new app afterwards):
6253
+ --name <text> the app's name (with --blocks, the same as --title)
6254
+ --brand-color <hex> primary colour, AA-checked; --dark-brand-color <hex> for dark mode
6255
+ --logo <path> svg/png/jpg/webp/gif, shown in the sidebar
6256
+ --app-url <url> white-label: where "Open in graph8" links go; --link-label <text>: what they call it
6257
+ With a graph8 API key (G8_API_KEY in the environment or .env.local) the org's Brand Kit fills in what you did not
6258
+ pass: the app name, the primary colour and the logo (downloaded into public/brand/). No key, no kit or an error:
6259
+ the default look, one info line, never a failure. --no-org-brand skips it.
6260
+ A directory literally named \`custom\` would read as the alias: pass \`--blocks crm ./custom\`.`;
6261
+ var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
6262
+ function parseCreateArgs(argv) {
6263
+ const o = { stack: "", dir: "", install: true, dryRun: false, force: false };
6264
+ const positional = [];
6265
+ for (let i = 0; i < argv.length; i++) {
6266
+ const arg = argv[i];
6267
+ const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
6268
+ const [flag, inline] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [arg, void 0];
6269
+ if (flag === "--package-manager") {
6270
+ const v = inline ?? argv[++i];
6271
+ if (!PACKAGE_MANAGERS.includes(v)) throw new Error(`--package-manager must be one of ${PACKAGE_MANAGERS.join(", ")}
6272
+
6273
+ ${CREATE_USAGE}`);
6274
+ o.packageManager = v;
6275
+ } else if (flag === "--blocks") {
6276
+ const v = inline ?? argv[++i];
6277
+ if (v === void 0 || v.startsWith("--")) throw new Error(`--blocks needs a comma-separated list of block ids
6278
+
6279
+ ${CREATE_USAGE}`);
6280
+ o.blocks = [...o.blocks ?? [], ...v.split(",").map((x) => x.trim()).filter(Boolean)];
6281
+ } else if (flag === "--title") {
6282
+ const v = inline ?? argv[++i];
6283
+ if (v === void 0 || v.startsWith("--")) throw new Error(`--title needs a value
6284
+
6285
+ ${CREATE_USAGE}`);
6286
+ o.title = v;
6287
+ } else if (takeBrandFlag(o.brand ??= {}, flag, () => {
6288
+ const v = inline ?? argv[++i];
6289
+ if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
6290
+
6291
+ ${CREATE_USAGE}`);
6292
+ return v;
6293
+ })) {
6294
+ } else if (arg === "--no-org-brand") o.orgBrand = false;
6295
+ else if (arg === "--with-deps-screens") o.depsScreens = "with";
6296
+ else if (arg === "--no-deps-screens") o.depsScreens = "none";
6297
+ else if (arg === "--no-install") o.install = false;
6298
+ else if (arg === "--dry-run") o.dryRun = true;
6299
+ else if (arg === "--force") o.force = true;
6300
+ else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
6301
+
6302
+ ${CREATE_USAGE}`);
6303
+ else positional.push(arg);
6304
+ }
6305
+ if (o.brand && !hasBrandInput(o.brand)) delete o.brand;
6306
+ if (o.brand?.name !== void 0 && o.title !== void 0 && o.brand.name !== o.title) throw new Error(`--name and --title name the same thing: pass one
6307
+
6308
+ ${CREATE_USAGE}`);
6309
+ if (o.blocks || positional[0] === "custom") return parseCustom(o, positional);
6310
+ if (o.title || o.depsScreens) throw new Error(`--title, --with-deps-screens and --no-deps-screens go with --blocks
6311
+
6312
+ ${CREATE_USAGE}`);
6313
+ if (positional.length !== 2) throw new Error(`create needs a stack and a directory
6314
+
6315
+ ${CREATE_USAGE}`);
6316
+ [o.stack, o.dir] = positional;
6317
+ return o;
6318
+ }
6319
+ function parseCustom(o, positional) {
6320
+ const rest = positional[0] === "custom" ? positional.slice(1) : positional;
6321
+ if (!o.blocks?.length) throw new Error(`create custom needs --blocks <id,id,...> (see \`graph8-sdk list\`)
6322
+
6323
+ ${CREATE_USAGE}`);
6324
+ if (rest.length !== 1) {
6325
+ throw new Error(`${rest.length ? "a stack name cannot be combined with --blocks; pass only the directory" : "create --blocks needs a directory"}
6326
+
6327
+ ${CREATE_USAGE}`);
6328
+ }
6329
+ return { ...o, stack: "custom", dir: rest[0] };
6330
+ }
6331
+ function packageManagerFromUserAgent(ua) {
6332
+ const name = /^(npm|pnpm|yarn|bun)\//.exec(ua ?? "")?.[1];
6333
+ return name ?? "npm";
6334
+ }
6335
+ function appNameFor(dir) {
6336
+ const base = import_node_path10.default.basename(import_node_path10.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
6337
+ return base || "g8-app";
6338
+ }
6339
+ var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
6340
+ function walk(dir, base = dir) {
6341
+ const out = [];
6342
+ for (const e of import_node_fs9.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
6343
+ const full = import_node_path10.default.join(dir, e.name);
6344
+ if (e.isDirectory()) out.push(...walk(full, base));
6345
+ else out.push(posix(import_node_path10.default.relative(base, full)));
6346
+ }
6347
+ return out;
6348
+ }
6349
+ function outputName(rel) {
6350
+ const noExt = rel.replace(/\.tmpl$/, "");
6351
+ const dir = import_node_path10.default.posix.dirname(noExt);
6352
+ const base = import_node_path10.default.posix.basename(noExt).replace(/^dot-/, ".");
6353
+ return dir === "." ? base : `${dir}/${base}`;
6354
+ }
6355
+ function stackProblem(reg, stack) {
6356
+ const { missing } = stackStatus(reg, stack);
6357
+ if (missing.length) {
6358
+ const ready = reg.stacks.filter((s) => !stackStatus(reg, s).missing.length).map((s) => s.id);
6359
+ return [
6360
+ `stack "${stack.id}" is planned: these blocks are not in this version of @graph8/sdk yet: ${missing.join(", ")}.`,
6361
+ `Stacks you can create now: ${ready.join(", ") || "none"}. Or create one of those and add blocks as they ship (\`graph8-sdk add <block>\`).`
6362
+ ].join("\n");
6363
+ }
6364
+ const seen = /* @__PURE__ */ new Set();
6365
+ const homeIssue = homeProblem(stack);
6366
+ if (homeIssue) return homeIssue;
6367
+ for (const n of stack.nav) {
6368
+ const block = findBlock(reg, n.block);
6369
+ if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
6370
+ if (!block?.components.some((c) => c.name === n.component)) return `stack "${stack.id}" nav "${n.label}" uses ${n.component}, which block "${n.block}" does not declare.`;
6371
+ if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
6372
+ if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
6373
+ seen.add(n.path);
6374
+ if (n.page && (!reg.stacksRoot || !import_node_fs9.default.existsSync(import_node_path10.default.join(reg.stacksRoot, stack.id, n.page)))) {
6375
+ return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
6376
+ }
6377
+ }
6378
+ return null;
6379
+ }
6380
+ function homeProblem(stack) {
6381
+ if (!stack.home) return null;
6382
+ if (!stack.nav.some((n) => n.path === stack.home)) return `stack "${stack.id}" has home ${stack.home}, which is not one of its nav paths.`;
6383
+ if (stack.home !== "/" && stack.nav.some((n) => n.path === "/")) return `stack "${stack.id}" has home ${stack.home} but also a nav entry at "/": drop one.`;
6384
+ return null;
6385
+ }
6386
+ function stackPageFile(reg, stack, n) {
6387
+ return n.page && reg.stacksRoot ? import_node_path10.default.join(reg.stacksRoot, stack.id, n.page) : null;
6388
+ }
6389
+ function homeRedirect(stack) {
6390
+ if (stack.home) return stack.home === "/" ? null : stack.home;
6391
+ return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
6392
+ }
6393
+ function renderPages(reg, stack, templates) {
6394
+ const pageTpl = import_node_fs9.default.readFileSync(import_node_path10.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
6395
+ const files = stack.nav.map((n) => {
6396
+ const block = findBlock(reg, n.block);
6397
+ const comp = block.components.find((c) => c.name === n.component);
6398
+ const custom = stackPageFile(reg, stack, n);
6399
+ const vars2 = {
6400
+ stackId: stack.id,
6401
+ block: block.id,
6402
+ blockDir: `components/g8/${block.dir}`,
6403
+ component: n.component,
6404
+ importPath: componentImportPath(block, comp.file),
6405
+ propsJson: JSON.stringify(n.props ?? {}, null, 2)
6406
+ };
6407
+ return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs9.default.readFileSync(custom, "utf8") : pageTpl, vars2) };
6408
+ });
6409
+ const to = homeRedirect(stack);
6410
+ if (to) {
6411
+ const redirect = import_node_fs9.default.readFileSync(import_node_path10.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
6412
+ files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
6413
+ }
6414
+ return files;
6415
+ }
6416
+ function peerDependenciesFor(blocks, io, stack) {
6417
+ const peers = {};
6418
+ for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
6419
+ const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(import_node_fs9.default.readFileSync(import_node_path10.default.join(b.root, f), "utf8"))));
6420
+ if (usesUi && io.sdkPackageJson && import_node_fs9.default.existsSync(io.sdkPackageJson)) {
6421
+ const sdk = JSON.parse(import_node_fs9.default.readFileSync(io.sdkPackageJson, "utf8"));
6422
+ for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
6423
+ }
6424
+ if (navUsesExtras(stack.nav)) peers["lucide-react"] ??= lucideRange(io);
6425
+ return peers;
6426
+ }
6427
+ function lucideRange(io) {
6428
+ try {
6429
+ const sdk = JSON.parse(import_node_fs9.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
6430
+ return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
6431
+ } catch {
6432
+ return ">=0.400.0";
6433
+ }
6434
+ }
6435
+ function mergedPackageJson(rendered, peers) {
6436
+ const pkg = JSON.parse(rendered);
6437
+ for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
6438
+ pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
6439
+ return `${JSON.stringify(pkg, null, 2)}
6440
+ `;
6441
+ }
6442
+ function planCreate(reg, stack, appName, pm, io, app) {
6443
+ const templates = import_node_path10.default.join(io.templatesRoot, "next-app");
6444
+ const blocks = resolveWithDeps(reg, stack.blocks);
6445
+ const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
6446
+ const vars2 = {
6447
+ appName,
6448
+ sdkVersion: SDK_VERSION,
6449
+ stackId: stack.id,
6450
+ title: stack.title,
6451
+ titleJson: JSON.stringify(stack.title),
6452
+ ...navParts(stack),
6453
+ devCommand,
6454
+ installCommand: `${pm} install`,
6455
+ doctorCommand: app ? "npx @graph8/sdk doctor" : `npx @graph8/sdk doctor --stack ${stack.id}`,
6456
+ screens: stack.nav.map((n) => `- ${n.label}: \`${n.path}\` (${n.block} block, \`${n.component}\`)`).join("\n") || "- (none yet: add a block with `npx @graph8/sdk add <block>`)"
6457
+ };
6458
+ const files = [];
6459
+ for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
6460
+ let content = render(import_node_fs9.default.readFileSync(import_node_path10.default.join(templates, rel), "utf8"), vars2);
6461
+ if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
6462
+ files.push({ rel: outputName(rel), content });
6463
+ }
6464
+ files.push(...renderPages(reg, stack, templates));
6465
+ files.push({ rel: CONFIG_FILE, content: renderBrandConfig({ name: stack.title, ...DEFAULT_FIELDS }) });
6466
+ const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, ...app ? { app } : {}, blocks: {} };
6467
+ files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
6468
+ ` });
6469
+ return files;
6470
+ }
6471
+ function dirProblem(abs, force) {
6472
+ if (!import_node_fs9.default.existsSync(abs)) return null;
6473
+ if (!import_node_fs9.default.statSync(abs).isDirectory()) return `${abs} exists and is not a directory.`;
6474
+ if (force || import_node_fs9.default.readdirSync(abs).length === 0) return null;
6475
+ return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
6476
+ }
6477
+ function runCreate(given, reg, io, cwd) {
6478
+ let opts = given;
6479
+ if (io.orgBrand && given.orgBrand !== false) {
6480
+ const brand = mergeOrgBrand(given.brand, io.orgBrand, { name: given.title !== void 0 });
6481
+ if (hasBrandInput(brand)) opts = { ...given, brand };
6482
+ }
6483
+ let app;
6484
+ let stack = opts.blocks ? void 0 : reg.stacks.find((s) => s.id === opts.stack);
6485
+ if (opts.blocks) {
6486
+ let composed;
6487
+ try {
6488
+ composed = composeApp(reg, opts.blocks, { title: opts.title, withDepsScreens: opts.depsScreens === "with", noDepsScreens: opts.depsScreens === "none" });
6489
+ } catch (e) {
6490
+ io.err(e.message);
6491
+ return 1;
6492
+ }
6493
+ stack = composed.stack;
6494
+ app = { title: stack.title, blocks: [...new Set(opts.blocks)], screens: composed.screens.map((x) => ({ block: x.block, component: x.component, path: x.path })) };
6495
+ composed.warnings.forEach((w) => io.err(`warning: ${w}`));
6496
+ } else if (!stack) {
6497
+ io.err(`unknown stack "${opts.stack}". Stacks: ${reg.stacks.map((s) => s.id).join(", ") || "none"} (\`graph8-sdk list\`).`);
6498
+ return 1;
6499
+ }
6500
+ if (!stack) return 1;
6501
+ const name = opts.brand?.name?.trim();
6502
+ if (name) {
6503
+ stack = { ...stack, title: name };
6504
+ if (app) app = { ...app, title: name };
6505
+ }
6506
+ const problem = stackProblem(reg, stack);
6507
+ if (problem) return io.err(problem), 1;
6508
+ const abs = import_node_path10.default.resolve(cwd, opts.dir);
6509
+ const dirIssue = dirProblem(abs, opts.force);
6510
+ if (dirIssue) return io.err(dirIssue), 1;
6511
+ const brandIssue = opts.brand ? brandProblem(opts.brand, cwd) : null;
6512
+ if (brandIssue) return io.err(brandIssue), 1;
6513
+ const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
6514
+ let files;
6515
+ try {
6516
+ files = planCreate(reg, stack, appNameFor(opts.dir), pm, io, app);
6517
+ } catch (e) {
6518
+ io.err(e.message);
6519
+ return 1;
6520
+ }
6521
+ const blocks = resolveWithDeps(reg, stack.blocks);
6522
+ io.out(`${opts.dryRun ? "Dry run: " : ""}creating "${stack.title}" (${app ? `custom: ${app.blocks.join(", ")}` : stack.id}) in ${opts.dir}`);
6523
+ for (const f of files) io.out(`${opts.dryRun ? "would write" : "write"} ${f.rel}`);
6524
+ if (opts.dryRun) {
6525
+ io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
6526
+ if (opts.brand) io.out(`would brand the app: ${Object.entries(opts.brand).map(([k, v]) => `${k} ${v}`).join(", ")}`);
6527
+ io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
6528
+ return 0;
6529
+ }
6530
+ for (const f of files) {
6531
+ const dest = import_node_path10.default.join(abs, f.rel);
6532
+ import_node_fs9.default.mkdirSync(import_node_path10.default.dirname(dest), { recursive: true });
6533
+ import_node_fs9.default.writeFileSync(dest, f.content, "utf8");
6534
+ }
6535
+ return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
6536
+ }
6537
+ function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
6538
+ for (const b of blocks) {
6539
+ const code = runAdd({ spec: b.id, dir: abs, target: "components/g8", dryRun: false, force: opts.force, install: false, wireScreens: false }, reg, io, cwd);
6540
+ if (code !== 0) {
6541
+ io.err(`adding block "${b.id}" failed; the app in ${opts.dir} is incomplete.`);
6542
+ return code;
6543
+ }
6544
+ }
6545
+ if (opts.brand) {
6546
+ io.out("branding the app (graph8-sdk brand)");
6547
+ const code = runBrand({ ...opts.brand, dir: abs }, io, cwd, stack.title);
6548
+ if (code !== 0) {
6549
+ io.err(`branding failed; the app in ${opts.dir} is complete but unbranded. Fix the option and run \`npx @graph8/sdk brand\` in it.`);
6550
+ return code;
6551
+ }
6552
+ }
6553
+ if (opts.install) {
6554
+ io.out(`installing dependencies: ${pm} install`);
6555
+ const code = io.spawn(pm, ["install"], abs);
6556
+ if (code !== 0) {
6557
+ io.err(`dependency install failed (exit ${code}). Run it yourself: cd ${quoted(opts.dir)} && ${pm} install`);
6558
+ return 1;
6559
+ }
6560
+ }
6561
+ const cd = opts.dir === "." ? "" : `cd ${quoted(opts.dir)} && `;
6562
+ io.out(
6563
+ [
6564
+ "",
6565
+ `Done. Next:`,
6566
+ ` 1. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`,
6567
+ ` 2. open http://localhost:3000 and paste a graph8 API key in the Connect graph8 screen (local preview: only`,
6568
+ ` this computer can reach the app's /api/g8 route), or set G8_API_KEY in ${import_node_path10.default.posix.join(opts.dir, ".env.local")}`,
6569
+ ` 3. ${cd}npx @graph8/sdk doctor${opts.blocks ? "" : ` --stack ${stack.id}`} (before going live: add --production)`
6570
+ ].join("\n")
6571
+ );
6572
+ return 0;
6573
+ }
6574
+
6451
6575
  // src/cli.ts
6452
6576
  var LIST_USAGE = `Usage: npx @graph8/sdk list [--json]
6453
6577
 
@@ -6471,7 +6595,7 @@ Run a command with --help for its options.
6471
6595
 
6472
6596
  ${INIT_AGENT_USAGE}`;
6473
6597
  function registryFor(io) {
6474
- const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path10.default.resolve(__dirname, "..", "blocks");
6598
+ const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path11.default.resolve(__dirname, "..", "blocks");
6475
6599
  return loadRegistry(root);
6476
6600
  }
6477
6601
  function defaultSpawn(cmd, args, cwd) {
@@ -6545,14 +6669,15 @@ function runCreateCommand(rest, io) {
6545
6669
  if (wantsHelp(rest)) return io.out(CREATE_USAGE), 0;
6546
6670
  try {
6547
6671
  const opts = parseCreateArgs(rest);
6548
- const packageRoot = import_node_path10.default.resolve(__dirname, "..");
6672
+ const packageRoot = import_node_path11.default.resolve(__dirname, "..");
6549
6673
  const cio = {
6550
6674
  out: io.out,
6551
6675
  err: io.err,
6552
6676
  spawn: io.spawn ?? defaultSpawn,
6553
- templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path10.default.join(packageRoot, "templates"),
6554
- sdkPackageJson: import_node_path10.default.join(packageRoot, "package.json"),
6555
- userAgent: io.userAgent ?? process.env.npm_config_user_agent
6677
+ templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path11.default.join(packageRoot, "templates"),
6678
+ sdkPackageJson: import_node_path11.default.join(packageRoot, "package.json"),
6679
+ userAgent: io.userAgent ?? process.env.npm_config_user_agent,
6680
+ orgBrand: io.orgBrand
6556
6681
  };
6557
6682
  return runCreate(opts, registryFor(io), cio, io.cwd);
6558
6683
  } catch (e) {
@@ -6563,13 +6688,13 @@ function runAddCommand(rest, io) {
6563
6688
  if (wantsHelp(rest)) return io.out(ADD_USAGE), 0;
6564
6689
  try {
6565
6690
  const opts = parseAddArgs(rest);
6566
- const packageRoot = import_node_path10.default.resolve(__dirname, "..");
6691
+ const packageRoot = import_node_path11.default.resolve(__dirname, "..");
6567
6692
  const aio = {
6568
6693
  out: io.out,
6569
6694
  err: io.err,
6570
6695
  spawn: io.spawn ?? defaultSpawn,
6571
- templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path10.default.join(packageRoot, "templates"),
6572
- sdkPackageJson: import_node_path10.default.join(packageRoot, "package.json")
6696
+ templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path11.default.join(packageRoot, "templates"),
6697
+ sdkPackageJson: import_node_path11.default.join(packageRoot, "package.json")
6573
6698
  };
6574
6699
  return runAdd(opts, registryFor(io), aio, io.cwd);
6575
6700
  } catch (e) {
@@ -6589,7 +6714,7 @@ function runCli(argv, io) {
6589
6714
  if (command === "brand") {
6590
6715
  if (wantsHelp(rest)) return io.out(BRAND_USAGE), 0;
6591
6716
  try {
6592
- return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd);
6717
+ return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd, void 0, io.orgBrand);
6593
6718
  } catch (e) {
6594
6719
  return fail(io, e.message);
6595
6720
  }
@@ -6613,8 +6738,8 @@ ${USAGE}`);
6613
6738
  return 1;
6614
6739
  }
6615
6740
  const read = (rel) => {
6616
- const full = import_node_path10.default.join(io.cwd, rel);
6617
- return import_node_fs9.default.existsSync(full) ? import_node_fs9.default.readFileSync(full, "utf8") : null;
6741
+ const full = import_node_path11.default.join(io.cwd, rel);
6742
+ return import_node_fs10.default.existsSync(full) ? import_node_fs10.default.readFileSync(full, "utf8") : null;
6618
6743
  };
6619
6744
  let plans;
6620
6745
  try {
@@ -6625,17 +6750,36 @@ ${USAGE}`);
6625
6750
  }
6626
6751
  for (const plan of plans) {
6627
6752
  if (!options.dryRun && plan.action !== "unchanged") {
6628
- const full = import_node_path10.default.join(io.cwd, plan.path);
6629
- import_node_fs9.default.mkdirSync(import_node_path10.default.dirname(full), { recursive: true });
6630
- import_node_fs9.default.writeFileSync(full, plan.content, "utf8");
6753
+ const full = import_node_path11.default.join(io.cwd, plan.path);
6754
+ import_node_fs10.default.mkdirSync(import_node_path11.default.dirname(full), { recursive: true });
6755
+ import_node_fs10.default.writeFileSync(full, plan.content, "utf8");
6631
6756
  }
6632
6757
  io.out(`${options.dryRun ? "would " : ""}${plan.action === "unchanged" ? "keep" : plan.action} ${plan.path} (${plan.target})`);
6633
6758
  }
6634
6759
  if (!options.dryRun) io.out("\nDone. The full guide is node_modules/@graph8/sdk/docs/agents.md.");
6635
6760
  return 0;
6636
6761
  }
6762
+ function brandRoot(command, rest, io) {
6763
+ const at = rest.findIndex((a) => a === "--dir" || a.startsWith("--dir="));
6764
+ const dir = at < 0 ? void 0 : rest[at].includes("=") ? rest[at].slice(6) : rest[at + 1];
6765
+ return command === "brand" && dir ? import_node_path11.default.resolve(io.cwd, dir) : io.cwd;
6766
+ }
6767
+ async function runWithOrgBrand(command, rest, argv, io) {
6768
+ const wants = !wantsHelp(rest) && !rest.includes("--no-org-brand") && (command === "brand" || !rest.includes("--dry-run")) && (command === "create" || rest.includes("--from-org") || !rest.some((a) => a.startsWith("--") && a !== "--dir" && !a.startsWith("--dir=")));
6769
+ if (!wants) return runCli(argv, io);
6770
+ const pull = await pullOrgBrand({ env: io.env ?? process.env, fetch: io.fetch }, brandRoot(command, rest, io));
6771
+ io.out(pull.info);
6772
+ const bare = command === "brand" && !rest.includes("--from-org") && !rest.some((a) => a.startsWith("--") && a !== "--dir" && !a.startsWith("--dir="));
6773
+ if (bare && !Object.keys(pull.values).length) return runCli(argv, io);
6774
+ try {
6775
+ return runCli(bare ? [...argv, "--from-org"] : argv, { ...io, orgBrand: pull.values });
6776
+ } finally {
6777
+ pull.cleanup();
6778
+ }
6779
+ }
6637
6780
  async function runCliAsync(argv, io) {
6638
6781
  const [command, ...rest] = argv;
6782
+ if (command === "create" || command === "brand") return runWithOrgBrand(command, rest, argv, io);
6639
6783
  if (command !== "doctor") return runCli(argv, io);
6640
6784
  if (wantsHelp(rest)) return io.out(DOCTOR_USAGE), 0;
6641
6785
  try {