@graph8/sdk 0.370.0 → 0.374.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 (646) hide show
  1. package/README.md +22 -3
  2. package/blocks/_foundation/README.md +61 -0
  3. package/blocks/_foundation/block.json +57 -0
  4. package/blocks/_foundation/components/auth-gate.tsx +363 -0
  5. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  6. package/blocks/_foundation/components/empty-state.tsx +5 -2
  7. package/blocks/_foundation/components/list-select.tsx +112 -0
  8. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  9. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  10. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  11. package/blocks/_foundation/components/screen/index.ts +1 -0
  12. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  13. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  14. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  15. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  16. package/blocks/_foundation/lib/paging.ts +57 -0
  17. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  18. package/blocks/_foundation/server/auth.ts +815 -0
  19. package/blocks/agents/README.md +81 -4
  20. package/blocks/agents/block.json +311 -2
  21. package/blocks/agents/components/agent-activity.tsx +112 -0
  22. package/blocks/agents/components/agent-approvals.tsx +135 -0
  23. package/blocks/agents/components/agent-builder.tsx +17 -3
  24. package/blocks/agents/components/agent-conversations.tsx +143 -0
  25. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
  26. package/blocks/agents/components/agent-operators.tsx +164 -0
  27. package/blocks/agents/components/agent-run-detail.tsx +20 -3
  28. package/blocks/agents/components/agent-runs.tsx +3 -3
  29. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  30. package/blocks/agents/components/agent-test-history.tsx +121 -0
  31. package/blocks/agents/components/agents-screen.tsx +53 -11
  32. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  33. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  34. package/blocks/agents/components/from-template-button.tsx +65 -0
  35. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  36. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  37. package/blocks/agents/components/knowledge-form.tsx +69 -0
  38. package/blocks/agents/components/name-dialog.tsx +32 -0
  39. package/blocks/agents/components/receptionist-button.tsx +46 -0
  40. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  41. package/blocks/agents/components/skill-editor.tsx +147 -0
  42. package/blocks/agents/components/skill-library.tsx +111 -0
  43. package/blocks/agents/lib/agent-types.ts +7 -1
  44. package/blocks/agents/lib/facts.tsx +13 -0
  45. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  46. package/blocks/agents/lib/load-more.tsx +141 -0
  47. package/blocks/agents/lib/skill-utils.ts +91 -0
  48. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  49. package/blocks/analytics/components/analytics-screen.tsx +16 -3
  50. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  51. package/blocks/analytics/components/report-builder.tsx +15 -2
  52. package/blocks/appointments/README.md +6 -0
  53. package/blocks/appointments/block.json +27 -0
  54. package/blocks/appointments/components/booking-page.tsx +2 -2
  55. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  56. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  57. package/blocks/appointments/components/meeting-list.tsx +29 -13
  58. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  59. package/blocks/campaigns/README.md +1 -1
  60. package/blocks/campaigns/block.json +34 -1
  61. package/blocks/campaigns/components/campaign-assets.tsx +1 -1
  62. package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
  63. package/blocks/campaigns/components/campaign-links.tsx +180 -0
  64. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  65. package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
  66. package/blocks/campaigns/components/icp-detail.tsx +11 -2
  67. package/blocks/campaigns/components/icp-editor.tsx +8 -3
  68. package/blocks/campaigns/components/persona-detail.tsx +11 -2
  69. package/blocks/campaigns/components/persona-editor.tsx +6 -1
  70. package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
  71. package/blocks/coach/components/call-scorecard.tsx +7 -7
  72. package/blocks/company-intelligence/README.md +132 -0
  73. package/blocks/company-intelligence/block.json +318 -0
  74. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  75. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  76. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  77. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  78. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  79. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  80. package/blocks/company-intelligence/components/research-run.tsx +165 -0
  81. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  82. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  83. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  84. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  85. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  86. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  87. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  88. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  89. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  90. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  91. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  92. package/blocks/copilot/components/copilot-panel.tsx +1 -1
  93. package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
  94. package/blocks/crm/README.md +25 -0
  95. package/blocks/crm/block.json +257 -9
  96. package/blocks/crm/components/bulk-update.tsx +119 -0
  97. package/blocks/crm/components/companies-screen.tsx +23 -9
  98. package/blocks/crm/components/company-detail.tsx +2 -2
  99. package/blocks/crm/components/contact-detail.tsx +4 -4
  100. package/blocks/crm/components/contact-documents-tab.tsx +144 -0
  101. package/blocks/crm/components/contact-insights-tab.tsx +118 -0
  102. package/blocks/crm/components/contact-lookup-link.tsx +85 -0
  103. package/blocks/crm/components/contact-row-actions.tsx +43 -0
  104. package/blocks/crm/components/contact-tag-chips.tsx +113 -0
  105. package/blocks/crm/components/contacts-screen.tsx +77 -18
  106. package/blocks/crm/components/dedupe-review.tsx +6 -1
  107. package/blocks/crm/components/import-wizard.tsx +3 -7
  108. package/blocks/crm/components/list-select.tsx +20 -0
  109. package/blocks/crm/components/owner-assign.tsx +132 -0
  110. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  111. package/blocks/crm/components/record-form.tsx +1 -1
  112. package/blocks/crm/components/record-open-links.tsx +79 -0
  113. package/blocks/crm/components/tag-assign.tsx +112 -0
  114. package/blocks/crm/components/tag-manager.tsx +178 -0
  115. package/blocks/crm/components/tasks-screen.tsx +35 -15
  116. package/blocks/crm/components/unlock-info-button.tsx +82 -0
  117. package/blocks/crm/components/verify-email-button.tsx +93 -0
  118. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  119. package/blocks/crm/hooks/use-contact-tags.ts +29 -0
  120. package/blocks/crm/hooks/use-page-query.ts +3 -1
  121. package/blocks/crm/lib/crm-types.ts +14 -0
  122. package/blocks/crm/lib/read-lists.ts +16 -0
  123. package/blocks/crm/lib/record-actions.ts +102 -0
  124. package/blocks/deals/README.md +25 -6
  125. package/blocks/deals/block.json +168 -2
  126. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  127. package/blocks/deals/components/create-deal-button.tsx +109 -0
  128. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  129. package/blocks/deals/components/deal-detail.tsx +7 -5
  130. package/blocks/deals/components/deal-filters.tsx +65 -0
  131. package/blocks/deals/components/deal-line-items.tsx +120 -0
  132. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  133. package/blocks/deals/components/deal-related.tsx +30 -9
  134. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  135. package/blocks/deals/components/deals-screen.tsx +57 -22
  136. package/blocks/deals/components/forecast-view.tsx +4 -4
  137. package/blocks/deals/components/pipeline-board.tsx +8 -6
  138. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  139. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  140. package/blocks/deals/components/stage-editor.tsx +1 -1
  141. package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
  142. package/blocks/deals/lib/deal-extras.ts +156 -0
  143. package/blocks/dialer/README.md +14 -5
  144. package/blocks/dialer/block.json +17 -2
  145. package/blocks/dialer/components/call-contact-button.tsx +26 -9
  146. package/blocks/dialer/components/call-log-screen.tsx +10 -2
  147. package/blocks/dialer/components/call-log.tsx +16 -4
  148. package/blocks/dialer/components/call-queue.tsx +3 -3
  149. package/blocks/dialer/components/dialer-screen.tsx +69 -27
  150. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  151. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  152. package/blocks/dialer/components/session-start-form.tsx +58 -11
  153. package/blocks/dialer/components/softphone.tsx +1 -1
  154. package/blocks/documents/README.md +86 -0
  155. package/blocks/documents/block.json +378 -0
  156. package/blocks/documents/components/account-bar.tsx +48 -0
  157. package/blocks/documents/components/correspondence-form.tsx +95 -0
  158. package/blocks/documents/components/correspondence-import.tsx +106 -0
  159. package/blocks/documents/components/correspondence-panel.tsx +191 -0
  160. package/blocks/documents/components/documents-screen.tsx +76 -0
  161. package/blocks/documents/components/library-document-detail.tsx +175 -0
  162. package/blocks/documents/components/library-document-form.tsx +105 -0
  163. package/blocks/documents/components/library-folder-form.tsx +74 -0
  164. package/blocks/documents/components/library-panel.tsx +235 -0
  165. package/blocks/documents/components/library-upload.tsx +160 -0
  166. package/blocks/documents/components/question-row.tsx +130 -0
  167. package/blocks/documents/components/questionnaire-detail.tsx +197 -0
  168. package/blocks/documents/components/questionnaires-panel.tsx +173 -0
  169. package/blocks/documents/components/record-documents-tab.tsx +109 -0
  170. package/blocks/documents/lib/documents-types.ts +252 -0
  171. package/blocks/documents/lib/download.ts +64 -0
  172. package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
  173. package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
  174. package/blocks/documents/recipes/organize-the-library.ts +25 -0
  175. package/blocks/email-signatures/README.md +123 -0
  176. package/blocks/email-signatures/block.json +341 -0
  177. package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
  178. package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
  179. package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
  180. package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
  181. package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
  182. package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
  183. package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
  184. package/blocks/email-signatures/components/signature-policy.tsx +130 -0
  185. package/blocks/email-signatures/components/signature-preview.tsx +119 -0
  186. package/blocks/email-signatures/components/signature-report.tsx +77 -0
  187. package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
  188. package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
  189. package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
  190. package/blocks/email-signatures/lib/signatures-types.ts +392 -0
  191. package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
  192. package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
  193. package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
  194. package/blocks/enrichment/README.md +38 -10
  195. package/blocks/enrichment/block.json +164 -12
  196. package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
  197. package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
  198. package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
  199. package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
  200. package/blocks/enrichment/components/enrich-button.tsx +1 -1
  201. package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
  202. package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
  203. package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
  204. package/blocks/enrichment/components/list-pipelines.tsx +97 -0
  205. package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
  206. package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
  207. package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
  208. package/blocks/enrichment/components/waterfall-config.tsx +27 -7
  209. package/blocks/enrichment/lib/enrichment-types.ts +51 -0
  210. package/blocks/enrichment/lib/pipeline-types.ts +234 -0
  211. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  212. package/blocks/forms/README.md +10 -0
  213. package/blocks/forms/block.json +107 -0
  214. package/blocks/forms/components/form-activity.tsx +71 -0
  215. package/blocks/forms/components/form-backfill.tsx +108 -0
  216. package/blocks/forms/components/form-builder.tsx +1 -1
  217. package/blocks/forms/components/form-embed.tsx +1 -1
  218. package/blocks/forms/components/form-groups.tsx +151 -0
  219. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  220. package/blocks/forms/components/form-route-history.tsx +72 -0
  221. package/blocks/forms/components/form-saved-views.tsx +81 -0
  222. package/blocks/forms/components/forms-screen.tsx +57 -20
  223. package/blocks/forms/components/more-rows.tsx +30 -0
  224. package/blocks/forms/components/submissions-export.tsx +67 -0
  225. package/blocks/forms/components/submissions-table.tsx +15 -6
  226. package/blocks/forms/lib/forms-extra.ts +67 -0
  227. package/blocks/inbox/README.md +45 -4
  228. package/blocks/inbox/block.json +145 -8
  229. package/blocks/inbox/components/blocked-senders.tsx +103 -0
  230. package/blocks/inbox/components/compose-message.tsx +193 -0
  231. package/blocks/inbox/components/inbox-screen.tsx +54 -6
  232. package/blocks/inbox/components/inbox-search.tsx +106 -0
  233. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  234. package/blocks/inbox/components/tag-manager.tsx +136 -0
  235. package/blocks/inbox/components/thread-actions.tsx +133 -0
  236. package/blocks/inbox/components/thread-badge.tsx +2 -2
  237. package/blocks/inbox/components/thread-view.tsx +3 -1
  238. package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
  239. package/blocks/inbox/lib/inbox-manage.ts +227 -0
  240. package/blocks/integrations/README.md +54 -1
  241. package/blocks/integrations/block.json +189 -15
  242. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  243. package/blocks/integrations/components/connection-health.tsx +59 -0
  244. package/blocks/integrations/components/crm-detail.tsx +20 -1
  245. package/blocks/integrations/components/crm-records.tsx +103 -0
  246. package/blocks/integrations/components/field-mappings.tsx +61 -0
  247. package/blocks/integrations/components/integration-list.tsx +4 -4
  248. package/blocks/integrations/components/integrations-screen.tsx +20 -3
  249. package/blocks/integrations/components/migration-panel.tsx +133 -0
  250. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  251. package/blocks/integrations/components/sync-status.tsx +6 -6
  252. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  253. package/blocks/integrations/components/webhook-detail.tsx +26 -7
  254. package/blocks/integrations/components/webhook-manager.tsx +30 -17
  255. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  256. package/blocks/integrations/lib/crm-utils.ts +39 -0
  257. package/blocks/integrations/lib/facts.tsx +13 -0
  258. package/blocks/integrations/lib/integrations-utils.ts +5 -0
  259. package/blocks/integrations/lib/load-more.tsx +141 -0
  260. package/blocks/intent/components/account-intent-score.tsx +5 -5
  261. package/blocks/intent/components/intent-feed.tsx +8 -8
  262. package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
  263. package/blocks/intent/lib/intent-utils.ts +4 -4
  264. package/blocks/lifecycle/components/account-related.tsx +8 -1
  265. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  266. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  267. package/blocks/lists/README.md +7 -1
  268. package/blocks/lists/block.json +31 -3
  269. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  270. package/blocks/lists/components/add-to-list-button.tsx +17 -7
  271. package/blocks/lists/components/list-picker.tsx +13 -3
  272. package/blocks/lists/components/list-table.tsx +1 -1
  273. package/blocks/lists/components/lists-screen.tsx +4 -2
  274. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  275. package/blocks/lists/components/suppression-manager.tsx +27 -6
  276. package/blocks/lists/components/suppressions-screen.tsx +21 -12
  277. package/blocks/mailboxes/block.json +4 -0
  278. package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
  279. package/blocks/mailboxes/components/domain-detail.tsx +10 -4
  280. package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
  281. package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
  282. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  283. package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
  284. package/blocks/mailboxes/components/warmup-health.tsx +20 -20
  285. package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
  286. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  287. package/blocks/newsletter/README.md +12 -0
  288. package/blocks/newsletter/block.json +117 -0
  289. package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
  290. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  291. package/blocks/newsletter/components/more-rows.tsx +30 -0
  292. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  293. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  294. package/blocks/newsletter/components/newsletter-screen.tsx +38 -13
  295. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  296. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  297. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  298. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  299. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  300. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  301. package/blocks/nurture/components/nurture-builder.tsx +10 -2
  302. package/blocks/nurture/components/nurture-enroll.tsx +6 -7
  303. package/blocks/nurture/components/nurture-form.tsx +5 -8
  304. package/blocks/nurture/components/nurture-screen.tsx +16 -5
  305. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  306. package/blocks/objects/README.md +1 -1
  307. package/blocks/objects/block.json +13 -5
  308. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  309. package/blocks/objects/components/object-record-detail.tsx +8 -1
  310. package/blocks/objects/components/object-schema-editor.tsx +1 -1
  311. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  312. package/blocks/pages/README.md +14 -0
  313. package/blocks/pages/block.json +123 -2
  314. package/blocks/pages/components/more-rows.tsx +30 -0
  315. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  316. package/blocks/pages/components/page-analytics.tsx +176 -0
  317. package/blocks/pages/components/page-clone.tsx +99 -0
  318. package/blocks/pages/components/page-domains.tsx +167 -0
  319. package/blocks/pages/components/page-editor.tsx +1 -1
  320. package/blocks/pages/components/page-generation-status.tsx +48 -0
  321. package/blocks/pages/components/page-tracking.tsx +137 -0
  322. package/blocks/pages/components/pages-screen.tsx +57 -16
  323. package/blocks/pages/components/seo-report.tsx +6 -1
  324. package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
  325. package/blocks/pages/lib/studio.ts +7 -0
  326. package/blocks/phone-numbers/README.md +74 -0
  327. package/blocks/phone-numbers/block.json +262 -0
  328. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  329. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  330. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  331. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  332. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  333. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  334. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  335. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  336. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  337. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  338. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  339. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  340. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  341. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  342. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  343. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  344. package/blocks/pickers/README.md +23 -0
  345. package/blocks/pickers/block.json +103 -0
  346. package/blocks/pickers/components/company-picker.tsx +90 -0
  347. package/blocks/pickers/components/contact-picker.tsx +131 -0
  348. package/blocks/pickers/components/member-picker.tsx +99 -0
  349. package/blocks/pickers/components/search-picker.tsx +98 -0
  350. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  351. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  352. package/blocks/pickers/lib/use-debounced.ts +13 -0
  353. package/blocks/programmatic-pages/README.md +82 -0
  354. package/blocks/programmatic-pages/block.json +292 -0
  355. package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
  356. package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
  357. package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
  358. package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
  359. package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
  360. package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
  361. package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
  362. package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
  363. package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
  364. package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
  365. package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
  366. package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
  367. package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
  368. package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
  369. package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
  370. package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
  371. package/blocks/quotes/README.md +38 -1
  372. package/blocks/quotes/block.json +170 -6
  373. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  374. package/blocks/quotes/components/manual-products.tsx +144 -0
  375. package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
  376. package/blocks/quotes/components/quote-builder.tsx +1 -1
  377. package/blocks/quotes/components/quote-documents.tsx +94 -0
  378. package/blocks/quotes/components/quote-evidence.tsx +95 -0
  379. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  380. package/blocks/quotes/components/quote-list.tsx +28 -8
  381. package/blocks/quotes/components/quote-related.tsx +3 -2
  382. package/blocks/quotes/components/quote-settings.tsx +184 -0
  383. package/blocks/quotes/components/quote-templates.tsx +189 -0
  384. package/blocks/quotes/components/quote-viewer.tsx +23 -5
  385. package/blocks/quotes/components/quotes-screen.tsx +15 -5
  386. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  387. package/blocks/quotes/lib/quote-admin.ts +264 -0
  388. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  389. package/blocks/radar/components/radar-overview.tsx +4 -4
  390. package/blocks/radar/components/radar-screen.tsx +2 -2
  391. package/blocks/sequencer/README.md +61 -0
  392. package/blocks/sequencer/block.json +200 -6
  393. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  394. package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
  395. package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
  396. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  397. package/blocks/sequencer/components/sequence-builder.tsx +32 -23
  398. package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
  399. package/blocks/sequencer/components/sequence-detail.tsx +10 -2
  400. package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
  401. package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
  402. package/blocks/sequencer/components/sequence-list.tsx +1 -1
  403. package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
  404. package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
  405. package/blocks/sequencer/components/sequence-stats.tsx +8 -8
  406. package/blocks/sequencer/components/sequence-templates.tsx +160 -0
  407. package/blocks/sequencer/components/sequences-screen.tsx +39 -6
  408. package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
  409. package/blocks/sequencer/components/step-editor.tsx +21 -12
  410. package/blocks/sequencer/components/step-templates.tsx +224 -0
  411. package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
  412. package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
  413. package/blocks/signals/README.md +39 -0
  414. package/blocks/signals/block.json +287 -0
  415. package/blocks/signals/components/auto-seed-panel.tsx +111 -0
  416. package/blocks/signals/components/listener-form.tsx +184 -0
  417. package/blocks/signals/components/matched-posts.tsx +195 -0
  418. package/blocks/signals/components/post-engagements.tsx +114 -0
  419. package/blocks/signals/components/record-social-tab.tsx +49 -0
  420. package/blocks/signals/components/social-listeners-screen.tsx +250 -0
  421. package/blocks/signals/components/social-settings.tsx +128 -0
  422. package/blocks/signals/components/source-lists.tsx +83 -0
  423. package/blocks/signals/components/top-engagers.tsx +68 -0
  424. package/blocks/signals/components/x-integration.tsx +129 -0
  425. package/blocks/signals/lib/signals-types.ts +228 -0
  426. package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
  427. package/blocks/signals/recipes/spend-against-caps.ts +14 -0
  428. package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
  429. package/blocks/social-ads/README.md +8 -0
  430. package/blocks/social-ads/block.json +137 -4
  431. package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
  432. package/blocks/social-ads/components/audience-sync.tsx +6 -2
  433. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  434. package/blocks/social-ads/components/campaign-detail.tsx +6 -1
  435. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  436. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  437. package/blocks/social-ads/components/creative-detail.tsx +6 -1
  438. package/blocks/social-ads/components/creative-gallery.tsx +20 -2
  439. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  440. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  441. package/blocks/social-ads/components/post-composer.tsx +4 -2
  442. package/blocks/social-ads/components/post-detail.tsx +3 -0
  443. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  444. package/blocks/social-ads/components/social-performance.tsx +80 -0
  445. package/blocks/social-ads/components/social-screen.tsx +33 -6
  446. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  447. package/blocks/surveys/README.md +91 -0
  448. package/blocks/surveys/block.json +299 -0
  449. package/blocks/surveys/components/account-nps.tsx +69 -0
  450. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  451. package/blocks/surveys/components/org-summary.tsx +82 -0
  452. package/blocks/surveys/components/sending-domains.tsx +113 -0
  453. package/blocks/surveys/components/survey-form.tsx +126 -0
  454. package/blocks/surveys/components/survey-responses.tsx +145 -0
  455. package/blocks/surveys/components/survey-results.tsx +72 -0
  456. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  457. package/blocks/surveys/components/survey-send.tsx +162 -0
  458. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  459. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  460. package/blocks/surveys/lib/surveys-types.ts +228 -0
  461. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  462. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  463. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  464. package/blocks/visitors/components/snippet-install.tsx +18 -9
  465. package/blocks/visitors/components/traffic-chart.tsx +7 -6
  466. package/blocks/visitors/components/visitor-company-card.tsx +24 -14
  467. package/blocks/visitors/components/visitor-feed.tsx +5 -5
  468. package/blocks/web-analytics/README.md +102 -0
  469. package/blocks/web-analytics/block.json +292 -0
  470. package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
  471. package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
  472. package/blocks/web-analytics/components/audience-tab.tsx +135 -0
  473. package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
  474. package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
  475. package/blocks/web-analytics/components/overview-tab.tsx +102 -0
  476. package/blocks/web-analytics/components/pages-tab.tsx +148 -0
  477. package/blocks/web-analytics/components/ranked-table.tsx +103 -0
  478. package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
  479. package/blocks/web-analytics/components/report-card.tsx +71 -0
  480. package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
  481. package/blocks/web-analytics/hooks/use-report.ts +48 -0
  482. package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
  483. package/blocks/web-analytics/lib/page-path.ts +14 -0
  484. package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
  485. package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
  486. package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
  487. package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
  488. package/blocks/webchat/components/chat-widget-config.tsx +1 -1
  489. package/blocks/webchat/components/conversation-list.tsx +1 -1
  490. package/blocks/workbench/README.md +54 -2
  491. package/blocks/workbench/block.json +234 -5
  492. package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
  493. package/blocks/workbench/components/run-progress.tsx +1 -1
  494. package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
  495. package/blocks/workbench/components/workbench-automation.tsx +36 -0
  496. package/blocks/workbench/components/workbench-connections.tsx +136 -0
  497. package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
  498. package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
  499. package/blocks/workbench/components/workbench-grid.tsx +71 -8
  500. package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
  501. package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
  502. package/blocks/workbench/components/workbench-schedule.tsx +191 -0
  503. package/blocks/workbench/components/workbench-screen.tsx +65 -22
  504. package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
  505. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  506. package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
  507. package/blocks/workbench/components/workbench-triggers.tsx +178 -0
  508. package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
  509. package/blocks/workbench/lib/api-status.ts +8 -0
  510. package/blocks/workbench/lib/automation-types.ts +257 -0
  511. package/blocks/workflows/components/workflow-editor.tsx +1 -1
  512. package/dist/cli.js +744 -394
  513. package/dist/index.browser.js +1 -1
  514. package/dist/index.browser.js.map +1 -1
  515. package/dist/index.browser.mjs +1 -1
  516. package/dist/index.browser.mjs.map +1 -1
  517. package/dist/index.d.mts +2 -2
  518. package/dist/index.d.ts +2 -2
  519. package/dist/index.js +1 -1
  520. package/dist/index.js.map +1 -1
  521. package/dist/index.mjs +1 -1
  522. package/dist/index.mjs.map +1 -1
  523. package/dist/react.browser.js +1 -1
  524. package/dist/react.browser.js.map +1 -1
  525. package/dist/react.browser.mjs +1 -1
  526. package/dist/react.browser.mjs.map +1 -1
  527. package/dist/react.d.mts +2 -2
  528. package/dist/react.d.ts +2 -2
  529. package/dist/react.js +1 -1
  530. package/dist/react.js.map +1 -1
  531. package/dist/react.mjs +1 -1
  532. package/dist/react.mjs.map +1 -1
  533. package/dist/types/blocks/auth.d.ts +45 -0
  534. package/dist/types/blocks/brand.d.ts +11 -2
  535. package/dist/types/blocks/create.d.ts +11 -4
  536. package/dist/types/blocks/doctor.d.ts +1 -0
  537. package/dist/types/blocks/flows.d.ts +7 -1
  538. package/dist/types/blocks/orgBrand.d.ts +34 -0
  539. package/dist/types/cli.d.ts +3 -0
  540. package/dist/types/version.d.ts +2 -2
  541. package/docs/api/desk.d.ts +2 -2
  542. package/docs/api/desk.md +3 -3
  543. package/docs/api/forms.md +1 -1
  544. package/package.json +1 -1
  545. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  546. package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
  547. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  548. package/stacks/6sense.json +37 -4
  549. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  550. package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
  551. package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
  552. package/stacks/apollo/pages/contacts.tsx.tmpl +35 -95
  553. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  554. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  555. package/stacks/apollo/pages/lists.tsx.tmpl +24 -0
  556. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  557. package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
  558. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  559. package/stacks/apollo.json +68 -9
  560. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  561. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  562. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  563. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  564. package/stacks/calendly.json +8 -4
  565. package/stacks/clay/pages/companies.tsx.tmpl +17 -1
  566. package/stacks/clay/pages/contacts.tsx.tmpl +14 -0
  567. package/stacks/clay/pages/lists.tsx.tmpl +24 -0
  568. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  569. package/stacks/clay.json +16 -3
  570. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  571. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  572. package/stacks/gong/pages/contacts.tsx.tmpl +34 -0
  573. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  574. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  575. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  576. package/stacks/gong.json +23 -6
  577. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  578. package/stacks/hootsuite/pages/contacts.tsx.tmpl +30 -0
  579. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  580. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  581. package/stacks/hootsuite.json +19 -4
  582. package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
  583. package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
  584. package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
  585. package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
  586. package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
  587. package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
  588. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  589. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  590. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  591. package/stacks/hubspot/pages/sequences.tsx.tmpl +18 -0
  592. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  593. package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
  594. package/stacks/hubspot.json +66 -7
  595. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  596. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  597. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  598. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  599. package/stacks/intercom.json +18 -4
  600. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  601. package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
  602. package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
  603. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  604. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  605. package/stacks/mailchimp.json +22 -6
  606. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  607. package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
  608. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  609. package/stacks/outreach/pages/contacts.tsx.tmpl +29 -66
  610. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  611. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  612. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  613. package/stacks/outreach/pages/sequences.tsx.tmpl +18 -0
  614. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  615. package/stacks/outreach.json +38 -9
  616. package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
  617. package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -35
  618. package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
  619. package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
  620. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  621. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  622. package/stacks/pipedrive.json +14 -2
  623. package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
  624. package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
  625. package/stacks/salesforce/pages/deals.tsx.tmpl +14 -1
  626. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  627. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  628. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  629. package/stacks/salesforce.json +17 -3
  630. package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
  631. package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
  632. package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
  633. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  634. package/stacks/zoominfo.json +36 -3
  635. package/styles/styles.css +1 -1
  636. package/templates/auth/auth-route.ts.tmpl +16 -0
  637. package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
  638. package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
  639. package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
  640. package/templates/auth/g8.auth.password.ts.tmpl +18 -0
  641. package/templates/auth/g8.auth.session.ts.tmpl +23 -0
  642. package/templates/auth/middleware.ts.tmpl +14 -0
  643. package/templates/next-app/README.md.tmpl +5 -0
  644. package/templates/next-app/app/globals.css.tmpl +349 -64
  645. package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
  646. 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_fs11 = __toESM(require("fs"));
40
+ var import_node_path12 = __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, path13, next) => {
123
+ const current = read(path13);
124
+ plans.push({ target, path: path13, 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.374.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, path13, 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(/\/+$/, "") + path13 + 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);
@@ -6304,7 +5992,7 @@ async function runDoctor(opts, reg, io, cwd) {
6304
5992
  await checkKey(call, blocks, key, line);
6305
5993
  await checkSubscription(call, key, line);
6306
5994
  }
6307
- checkRoute(root, blocks, opts.production === true, io.out, line);
5995
+ checkRoute(root, blocks, opts.production === true, io.out, line, io.env);
6308
5996
  if (opts.stack) printFlows(opts, reg, io.out, line);
6309
5997
  else if (!opts.blocks.length) printAppScreens(root, reg, io.out);
6310
5998
  printChecklist(blocks, io.out);
@@ -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,22 +6070,22 @@ 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
- function checkRoute(root, blocks, production, out, line) {
6082
+ function checkRoute(root, blocks, production, out, line, env = {}) {
6395
6083
  const rel = findRoute(root);
6396
6084
  if (!rel) {
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}`);
@@ -6406,6 +6094,7 @@ Proxy route (${rel})${production ? ", production gate" : ""}:`);
6406
6094
  facts.localPreview ? "`authorize` is not set: local preview (this computer, `next dev` only) is the only gate. Set `authorize` before going live; a production build refuses every call until you do" : "`authorize` is not set: the route refuses every call until you set it (check your app's session)"
6407
6095
  );
6408
6096
  } else line(true, "`authorize` is set");
6097
+ checkAuthConfig(root, problem, out, env);
6409
6098
  if (facts.dangerouslyAllowUnauthenticated) problem("dangerouslyAllowUnauthenticated is on: anyone who can reach the route can use the key");
6410
6099
  if (facts.dangerouslyAllowAllPaths) problem("dangerouslyAllowAllPaths is on: every /api/v1 area is reachable from the browser");
6411
6100
  const need = derivePrefixes(blocks);
@@ -6421,6 +6110,21 @@ Proxy route (${rel})${production ? ", production gate" : ""}:`);
6421
6110
  if (extra.length && blocks.length) out(`warn allowedPrefixes allows areas no installed block declares: ${extra.join(", ")}`);
6422
6111
  }
6423
6112
  }
6113
+ function checkAuthConfig(root, problem, out, env = {}) {
6114
+ const rel = ["g8.auth.ts", "src/g8.auth.ts"].find((f) => import_node_fs7.default.existsSync(import_node_path8.default.join(root, f)));
6115
+ if (!rel) return;
6116
+ const source = import_node_fs7.default.readFileSync(import_node_path8.default.join(root, rel), "utf8");
6117
+ const code = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`])\/\/[^\n]*/g, "$1");
6118
+ if (/\ballowAnonymous\s*\(/.test(code)) {
6119
+ out(`warn ${rel} uses allowAnonymous(): NO sign-in. Anyone who can reach this app can use its graph8 API key through /api/g8. Use a narrowly scoped key with a spend cap, or switch to a sign-in preset`);
6120
+ }
6121
+ if (/\bjwtAuth\s*\(/.test(code) && !/\bclaims\s*:/.test(code) && !/\b(audience|secret)\s*:/.test(code)) {
6122
+ if (!readSetting("G8_JWT_AUDIENCE", root, env) && !readSetting("G8_JWT_SECRET", root, env)) {
6123
+ out(`warn ${rel} uses jwtAuth() with no audience: set G8_JWT_AUDIENCE (or a claims check), otherwise tokens your identity provider minted for other apps on the same tenant are accepted`);
6124
+ }
6125
+ }
6126
+ if (source.includes("graph8:auth-todo")) problem(`${rel} still has the sessionAuth stub (graph8:auth-todo): it refuses everyone until you wire your session library`);
6127
+ }
6424
6128
  function fixPrefixes(root, blocks, io) {
6425
6129
  const rel = findRoute(root);
6426
6130
  if (!rel) {
@@ -6431,8 +6135,8 @@ function fixPrefixes(root, blocks, io) {
6431
6135
  io.err("no blocks found: graph8.json lists none (or pass --blocks / --stack).");
6432
6136
  return 1;
6433
6137
  }
6434
- const file = import_node_path9.default.join(root, rel);
6435
- const source = import_node_fs8.default.readFileSync(file, "utf8");
6138
+ const file = import_node_path8.default.join(root, rel);
6139
+ const source = import_node_fs7.default.readFileSync(file, "utf8");
6436
6140
  const prefixes = derivePrefixes(blocks);
6437
6141
  const next = applyPrefixes(source, prefixes) ?? upgradeLegacyRoute(source, prefixes);
6438
6142
  if (next === null) {
@@ -6442,12 +6146,638 @@ function fixPrefixes(root, blocks, io) {
6442
6146
  }
6443
6147
  if (next === source) io.out(`keep ${rel} (allowedPrefixes already match the ${prefixes.length} areas of the installed blocks)`);
6444
6148
  else {
6445
- import_node_fs8.default.writeFileSync(file, next, "utf8");
6149
+ import_node_fs7.default.writeFileSync(file, next, "utf8");
6446
6150
  io.out(`update ${rel} (allowedPrefixes: ${prefixes.join(", ")})`);
6447
6151
  }
6448
6152
  return 0;
6449
6153
  }
6450
6154
 
6155
+ // src/blocks/orgBrand.ts
6156
+ var TIMEOUT_MS = 8e3;
6157
+ var NONE = (info) => ({ values: {}, info, cleanup: () => {
6158
+ } });
6159
+ function colorOf(v) {
6160
+ const raw = typeof v === "string" ? v : v && typeof v === "object" ? v.value : null;
6161
+ return typeof raw === "string" ? normalizeHex(raw) : null;
6162
+ }
6163
+ function pickColor(tokens) {
6164
+ const t = tokens ?? {};
6165
+ for (const k of ["primary", "brand", "accent"]) {
6166
+ const c = colorOf(t.colors?.[k]);
6167
+ if (c) return c;
6168
+ }
6169
+ return colorOf(t.components?.button?.background);
6170
+ }
6171
+ function cleanName(v) {
6172
+ if (typeof v !== "string") return null;
6173
+ const s = v.replace(/[\u0000-\u001f\u007f<>`$\\]/g, "").replace(/\s+/g, " ").trim().slice(0, 60);
6174
+ return s || null;
6175
+ }
6176
+ var MIME = { "image/svg+xml": "svg", "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif" };
6177
+ function privateHost(host) {
6178
+ const h = host.toLowerCase().replace(/^\[|\]$/g, "");
6179
+ 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);
6180
+ }
6181
+ async function downloadLogo(url, f) {
6182
+ let bytes;
6183
+ let ext;
6184
+ if (url.startsWith("data:")) {
6185
+ const m = /^data:([^;,]+)(;base64)?,(.*)$/s.exec(url);
6186
+ if (!m) return null;
6187
+ ext = MIME[m[1].toLowerCase()];
6188
+ bytes = m[2] ? Buffer.from(m[3], "base64") : Buffer.from(decodeURIComponent(m[3]));
6189
+ } else {
6190
+ const u = new URL(url);
6191
+ if (u.protocol !== "https:" || privateHost(u.hostname)) return null;
6192
+ const res = await f(u, { redirect: "error", signal: AbortSignal.timeout(TIMEOUT_MS) });
6193
+ if (!res.ok) return null;
6194
+ const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
6195
+ ext = MIME[type] ?? (type && type !== "application/octet-stream" ? void 0 : LOGO_TYPES.find((e) => u.pathname.toLowerCase().endsWith(`.${e}`)));
6196
+ bytes = Buffer.from(await res.arrayBuffer());
6197
+ }
6198
+ if (!ext || !LOGO_TYPES.includes(ext) || !bytes.length || bytes.length > MAX_LOGO_BYTES) return null;
6199
+ const dir = import_node_fs8.default.mkdtempSync(import_node_path9.default.join(import_node_os.default.tmpdir(), "g8-org-logo-"));
6200
+ const file = import_node_path9.default.join(dir, `logo.${ext}`);
6201
+ import_node_fs8.default.writeFileSync(file, bytes);
6202
+ return { file, dir };
6203
+ }
6204
+ async function tryCall(call, op) {
6205
+ try {
6206
+ return { data: (await call(op))?.data };
6207
+ } catch (e) {
6208
+ return { status: e.status ?? 0 };
6209
+ }
6210
+ }
6211
+ async function pullOrgBrand(io, root) {
6212
+ try {
6213
+ const key = readSetting("G8_API_KEY", root, io.env);
6214
+ 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)");
6215
+ const api = createApiClient(key, readSetting("G8_API_URL", root, io.env));
6216
+ const f = io.fetch ?? fetch;
6217
+ const call = (op) => api.call(op, void 0, { fetchImpl: (u, i) => f(u, { ...i, signal: AbortSignal.timeout(TIMEOUT_MS) }), maxRetries: 0 });
6218
+ const [kit, me] = await Promise.all([tryCall(call, "get_brand_kit_overview_brand_kit_get"), tryCall(call, "describe_current_key_me_get")]);
6219
+ if (!kit.data) return NONE(`org brand: the Brand Kit could not be read (${kit.status ? `HTTP ${kit.status}` : "unreachable"}), using the default look`);
6220
+ const values = {};
6221
+ const color = pickColor(kit.data.design_tokens);
6222
+ if (color) values.color = color;
6223
+ const name = cleanName(me.data?.org_name);
6224
+ if (name) values.name = name;
6225
+ let dir = null;
6226
+ const logoUrl = typeof kit.data.logo_url === "string" ? kit.data.logo_url.trim() : "";
6227
+ if (logoUrl) {
6228
+ try {
6229
+ const dl = await downloadLogo(logoUrl, f);
6230
+ if (dl) values.logo = dl.file, dir = dl.dir;
6231
+ } catch {
6232
+ }
6233
+ }
6234
+ const got = [values.name && "name", values.color && "colour", values.logo && "logo"].filter(Boolean);
6235
+ const cleanup = () => dir && import_node_fs8.default.rmSync(dir, { recursive: true, force: true });
6236
+ if (!got.length) return NONE("org brand: the Brand Kit is empty, using the default look");
6237
+ return { values, info: `org brand: using your Brand Kit (${got.join(", ")}); explicit flags win, --no-org-brand skips this`, cleanup };
6238
+ } catch {
6239
+ return NONE("org brand: could not reach the Brand Kit, using the default look");
6240
+ }
6241
+ }
6242
+
6243
+ // src/blocks/create.ts
6244
+ var import_node_fs10 = __toESM(require("fs"));
6245
+ var import_node_path11 = __toESM(require("path"));
6246
+
6247
+ // src/blocks/auth.ts
6248
+ var import_node_crypto2 = require("crypto");
6249
+ var import_node_fs9 = __toESM(require("fs"));
6250
+ var import_node_path10 = __toESM(require("path"));
6251
+ var AUTH_PRESETS = ["password", "basic", "jwt", "session", "anonymous"];
6252
+ var AUTH_CONFIG_FILE = "g8.auth.ts";
6253
+ var AUTH_ROUTE_FILE = "app/api/g8-auth/route.ts";
6254
+ function isAuthPreset(v) {
6255
+ return AUTH_PRESETS.includes(v);
6256
+ }
6257
+ function usesSignInScreen(p) {
6258
+ return p === "password" || p === "jwt" || p === "session";
6259
+ }
6260
+ var SUMMARY = {
6261
+ password: "one shared team password, a signed HttpOnly session cookie and a sign-in screen",
6262
+ basic: "HTTP Basic auth: the browser asks for a user and password on every page",
6263
+ jwt: "your identity provider's JWT (OIDC: Auth0, Okta, Cognito, Keycloak, Clerk...), checked against its keys",
6264
+ session: "your own session library (Clerk, NextAuth, Supabase...): wire one function in g8.auth.ts",
6265
+ anonymous: "NO sign-in: anyone who can reach the app can use its key (public demos only)"
6266
+ };
6267
+ function authSummary(p) {
6268
+ return SUMMARY[p];
6269
+ }
6270
+ var ENV = {
6271
+ password: [
6272
+ "# Sign-in (create --auth password): the team password (12+ characters) and the cookie signing secret",
6273
+ "# (32+ random characters: openssl rand -base64 48). create wrote fresh values to .env.local.",
6274
+ "G8_APP_PASSWORD=",
6275
+ "G8_SESSION_SECRET="
6276
+ ],
6277
+ basic: ["# Sign-in (create --auth basic): user:password (12+ character passwords, no commas), comma-separated for several users.", "G8_BASIC_AUTH="],
6278
+ jwt: [
6279
+ "# Sign-in (create --auth jwt): your identity provider. See g8.auth.ts for examples.",
6280
+ "G8_JWT_ISSUER=",
6281
+ "G8_JWT_JWKS_URL=",
6282
+ "G8_JWT_AUDIENCE=",
6283
+ "G8_JWT_COOKIE=",
6284
+ "# Or an HS256 secret instead of a JWKS (32+ characters):",
6285
+ "G8_JWT_SECRET=",
6286
+ "G8_SIGN_IN_URL=",
6287
+ "G8_SIGN_OUT_URL="
6288
+ ],
6289
+ session: ["# Sign-in (create --auth session): where the sign-in screen sends people, and the Sign out link.", "G8_SIGN_IN_URL=", "G8_SIGN_OUT_URL="],
6290
+ anonymous: []
6291
+ };
6292
+ function authEnvLines(p) {
6293
+ return ENV[p];
6294
+ }
6295
+ function planAuthFiles(p, templatesRoot) {
6296
+ const dir = import_node_path10.default.join(templatesRoot, "auth");
6297
+ const read = (f) => render(import_node_fs9.default.readFileSync(import_node_path10.default.join(dir, f), "utf8"), { preset: p });
6298
+ const files = [{ rel: AUTH_CONFIG_FILE, content: read(`g8.auth.${p}.ts.tmpl`) }];
6299
+ if (usesSignInScreen(p)) files.push({ rel: AUTH_ROUTE_FILE, content: read("auth-route.ts.tmpl") });
6300
+ if (p === "basic") files.push({ rel: "middleware.ts", content: read("middleware.ts.tmpl") });
6301
+ return files;
6302
+ }
6303
+ var AUTHORIZE_HINT = /^[ \t]*\/\/ authorize: async \(request\) => \{[^\n]*\n/m;
6304
+ var LAST_IMPORT = /^import [^\n]+;\n(?![\s\S]*^import )/m;
6305
+ function applyAuthToRoute(source) {
6306
+ if (!AUTHORIZE_HINT.test(source) || !LAST_IMPORT.test(source) || source.includes('from "@/g8.auth"')) return null;
6307
+ return source.replace(LAST_IMPORT, (m) => `${m}import { appAuth } from "@/g8.auth";
6308
+ `).replace(AUTHORIZE_HINT, " authorize: appAuth.authorize, // g8.auth.ts: who may use this route (npx @graph8/sdk create --auth)\n");
6309
+ }
6310
+ var LAYOUT_IMPORT = 'import { G8ConnectGate } from "@/components/g8/_foundation/connect-graph8";\n';
6311
+ var LAYOUT_BODY = /^([ \t]*)<AppShell>\n([\s\S]*?)^\1<\/AppShell>\n/m;
6312
+ function applyAuthToLayout(source) {
6313
+ const m = LAYOUT_BODY.exec(source);
6314
+ if (!m || !source.includes(LAYOUT_IMPORT)) return null;
6315
+ const pad = m[1];
6316
+ const inner = m[2].split("\n").map((l) => l ? ` ${l}` : l).join("\n");
6317
+ const body = [
6318
+ `${pad}{/* Sign-in (g8.auth.ts): the sign-in screen until the visitor is signed in. The proxy enforces it on the server. */}`,
6319
+ `${pad}<BrandProvider brand={brand}>`,
6320
+ `${pad} <G8AuthGate>`,
6321
+ `${pad} <AppShell headerSlot={<G8SignOutButton />}>`,
6322
+ `${inner}${pad} </AppShell>`,
6323
+ `${pad} </G8AuthGate>`,
6324
+ `${pad}</BrandProvider>`,
6325
+ ""
6326
+ ].join("\n");
6327
+ const imports = `${LAYOUT_IMPORT}import { G8AuthGate, G8SignOutButton } from "@/components/g8/_foundation/auth-gate";
6328
+ import { BrandProvider } from "@/components/g8/_foundation/brand-provider";
6329
+ `;
6330
+ return source.replace(LAYOUT_IMPORT, imports).replace(LAYOUT_BODY, body);
6331
+ }
6332
+ var GO_LIVE_ITEM = /^- \[ \] Set `authorize` in `app\/api\/g8\/\[\.\.\.path\]\/route\.ts`[^\n]*\n(?: {6}[^\n]*\n)*/m;
6333
+ function readmeSection(p) {
6334
+ const vars2 = authEnvLines(p).filter((l) => !l.startsWith("#")).map((l) => `\`${l.replace(/=$/, "")}\``);
6335
+ const lines = [
6336
+ "## Sign-in",
6337
+ "",
6338
+ `This app was created with \`--auth ${p}\`: ${authSummary(p)}. \`g8.auth.ts\` holds the preset, and`,
6339
+ "`app/api/g8/[...path]/route.ts` runs its `authorize` before it forwards any call with the server's graph8 API key.",
6340
+ ""
6341
+ ];
6342
+ if (vars2.length) lines.push(`Server environment (\`.env.local\` while developing, your host's settings in production): ${vars2.join(", ")}.`, "");
6343
+ if (p === "password") lines.push("`create` wrote a fresh team password and session secret to `.env.local` (git-ignored): read the password there, and set your own values on the host before deploying.", "");
6344
+ if (p === "basic") lines.push("`create` wrote a fresh `admin:<random>` login to `.env.local` (git-ignored). `middleware.ts` makes the browser ask for it on every page. Serve the app over HTTPS only.", "");
6345
+ if (p === "jwt") lines.push("Set the issuer (or JWKS URL), the audience and the cookie your provider uses; examples are in `g8.auth.ts`.", "");
6346
+ if (p === "session") lines.push("Wire your session library in `g8.auth.ts` (Clerk, NextAuth and Supabase examples inside). Until then it refuses everyone and `doctor --production` fails.", "");
6347
+ if (p === "anonymous") lines.push("**Warning: no sign-in.** Anyone who can reach the app can use its key through `/api/g8`. Use a narrowly scoped key with a spend cap, or switch preset in `g8.auth.ts`.", "");
6348
+ lines.push('Switch preset or tune it (session length, extra JWT claim checks): `components/g8/_foundation/README.md`, "Sign-in presets".', "", "");
6349
+ return lines.join("\n");
6350
+ }
6351
+ function applyAuthToReadme(source, p) {
6352
+ if (!source.includes("\n## Go live\n") || !GO_LIVE_ITEM.test(source)) return null;
6353
+ const item = p === "anonymous" ? "- [ ] `authorize` is set by `--auth anonymous`: NO sign-in. Make sure that is what you want (see Sign-in above).\n" : `- [x] \`authorize\` is set by the \`--auth ${p}\` preset (\`g8.auth.ts\`). Set its variables on your host (see Sign-in above).
6354
+ `;
6355
+ return source.replace("\n## Go live\n", `
6356
+ ${readmeSection(p)}## Go live
6357
+ `).replace(GO_LIVE_ITEM, item);
6358
+ }
6359
+ function applyAuthToEnvExample(source, p) {
6360
+ const lines = authEnvLines(p);
6361
+ return lines.length ? `${source.replace(/\n*$/, "\n")}${lines.join("\n")}
6362
+ ` : source;
6363
+ }
6364
+ var secret = (bytes) => (0, import_node_crypto2.randomBytes)(bytes).toString("base64url");
6365
+ function writeAuthSecrets(root, p) {
6366
+ const wanted = p === "password" ? { G8_APP_PASSWORD: secret(18), G8_SESSION_SECRET: secret(48) } : p === "basic" ? { G8_BASIC_AUTH: `admin:${secret(18)}` } : {};
6367
+ const file = import_node_path10.default.join(root, ".env.local");
6368
+ const current = import_node_fs9.default.existsSync(file) ? import_node_fs9.default.readFileSync(file, "utf8") : "";
6369
+ const has = (name) => new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=\\s*\\S`, "m").test(current);
6370
+ const add = Object.entries(wanted).filter(([name]) => !has(name));
6371
+ if (!add.length) return [];
6372
+ const block = [`# Written by \`npx @graph8/sdk create --auth ${p}\` for local development. Set your own values on your host.`, ...add.map(([k, v]) => `${k}=${v}`)].join("\n");
6373
+ import_node_fs9.default.writeFileSync(file, `${current ? `${current.replace(/\n*$/, "\n")}
6374
+ ` : ""}${block}
6375
+ `, { encoding: "utf8", mode: 384 });
6376
+ return add.map(([k]) => k);
6377
+ }
6378
+
6379
+ // src/blocks/create.ts
6380
+ var CREATE_USAGE = `Usage: npx @graph8/sdk create <stack> <dir> [options]
6381
+
6382
+ Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
6383
+ install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
6384
+ --package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm
6385
+ --no-install write the files and blocks, do not install dependencies
6386
+ --dry-run print what would be written, write and install nothing
6387
+ --force allow a non-empty <dir> (template files are overwritten, and graph8.json
6388
+ is reset: blocks you added to that app earlier are no longer recorded)
6389
+ --auth <preset> scaffold sign-in so the app can go live (sets \`authorize\` on the proxy route):
6390
+ password one shared team password, session cookie, sign-in screen
6391
+ basic HTTP Basic auth on every page (the browser asks)
6392
+ jwt your identity provider's JWT (OIDC JWKS or an HS256 secret)
6393
+ session your own session library (Clerk, NextAuth, Supabase): wire g8.auth.ts
6394
+ anonymous NO sign-in: anyone who can reach the app uses its key (public demos)
6395
+ Without --auth: local preview only, and a production build refuses /api/g8 until you
6396
+ set \`authorize\` yourself (unchanged).
6397
+ Then: \`npm run dev\` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run
6398
+ \`npx @graph8/sdk doctor --stack <stack>\` (add --production before going live).
6399
+
6400
+ Or pick the features yourself, for any app (also: \`create custom --blocks ...\`):
6401
+ npx @graph8/sdk create --blocks <id,id,...> [--title "Acme CRM"] <dir> [options]
6402
+ Every requested block's dependencies come with it. The app gets one route and one sidebar entry per finished
6403
+ Screen of the requested blocks, plus the user-facing Screens of their dependencies (asking for deals also gives
6404
+ you crm's screens). \`/\` opens the analytics screen if you asked for one, else the first screen.
6405
+ --title <text> the app's name (default: graph8 app)
6406
+ --with-deps-screens also add the plain fallback page of dependency blocks that have no Screen
6407
+ --no-deps-screens add screens only for the blocks you named
6408
+ Run \`graph8-sdk list\` to see each block and which have Screens.
6409
+
6410
+ Brand it (any stack or --blocks; the same as running \`graph8-sdk brand\` in the new app afterwards):
6411
+ --name <text> the app's name (with --blocks, the same as --title)
6412
+ --brand-color <hex> primary colour, AA-checked; --dark-brand-color <hex> for dark mode
6413
+ --logo <path> svg/png/jpg/webp/gif, shown in the sidebar
6414
+ --app-url <url> white-label: where "Open in graph8" links go; --link-label <text>: what they call it
6415
+ 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
6416
+ pass: the app name, the primary colour and the logo (downloaded into public/brand/). No key, no kit or an error:
6417
+ the default look, one info line, never a failure. --no-org-brand skips it.
6418
+ A directory literally named \`custom\` would read as the alias: pass \`--blocks crm ./custom\`.`;
6419
+ var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
6420
+ function parseCreateArgs(argv) {
6421
+ const o = { stack: "", dir: "", install: true, dryRun: false, force: false };
6422
+ const positional = [];
6423
+ for (let i = 0; i < argv.length; i++) {
6424
+ const arg = argv[i];
6425
+ const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
6426
+ const [flag, inline] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [arg, void 0];
6427
+ if (flag === "--package-manager") {
6428
+ const v = inline ?? argv[++i];
6429
+ if (!PACKAGE_MANAGERS.includes(v)) throw new Error(`--package-manager must be one of ${PACKAGE_MANAGERS.join(", ")}
6430
+
6431
+ ${CREATE_USAGE}`);
6432
+ o.packageManager = v;
6433
+ } else if (flag === "--blocks") {
6434
+ const v = inline ?? argv[++i];
6435
+ if (v === void 0 || v.startsWith("--")) throw new Error(`--blocks needs a comma-separated list of block ids
6436
+
6437
+ ${CREATE_USAGE}`);
6438
+ o.blocks = [...o.blocks ?? [], ...v.split(",").map((x) => x.trim()).filter(Boolean)];
6439
+ } else if (flag === "--auth") {
6440
+ const v = inline ?? argv[++i];
6441
+ if (v === void 0 || !isAuthPreset(v)) throw new Error(`--auth must be one of ${AUTH_PRESETS.join(", ")}
6442
+
6443
+ ${CREATE_USAGE}`);
6444
+ o.auth = v;
6445
+ } else if (flag === "--title") {
6446
+ const v = inline ?? argv[++i];
6447
+ if (v === void 0 || v.startsWith("--")) throw new Error(`--title needs a value
6448
+
6449
+ ${CREATE_USAGE}`);
6450
+ o.title = v;
6451
+ } else if (takeBrandFlag(o.brand ??= {}, flag, () => {
6452
+ const v = inline ?? argv[++i];
6453
+ if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
6454
+
6455
+ ${CREATE_USAGE}`);
6456
+ return v;
6457
+ })) {
6458
+ } else if (arg === "--no-org-brand") o.orgBrand = false;
6459
+ else if (arg === "--with-deps-screens") o.depsScreens = "with";
6460
+ else if (arg === "--no-deps-screens") o.depsScreens = "none";
6461
+ else if (arg === "--no-install") o.install = false;
6462
+ else if (arg === "--dry-run") o.dryRun = true;
6463
+ else if (arg === "--force") o.force = true;
6464
+ else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
6465
+
6466
+ ${CREATE_USAGE}`);
6467
+ else positional.push(arg);
6468
+ }
6469
+ if (o.brand && !hasBrandInput(o.brand)) delete o.brand;
6470
+ 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
6471
+
6472
+ ${CREATE_USAGE}`);
6473
+ if (o.blocks || positional[0] === "custom") return parseCustom(o, positional);
6474
+ if (o.title || o.depsScreens) throw new Error(`--title, --with-deps-screens and --no-deps-screens go with --blocks
6475
+
6476
+ ${CREATE_USAGE}`);
6477
+ if (positional.length !== 2) throw new Error(`create needs a stack and a directory
6478
+
6479
+ ${CREATE_USAGE}`);
6480
+ [o.stack, o.dir] = positional;
6481
+ return o;
6482
+ }
6483
+ function parseCustom(o, positional) {
6484
+ const rest = positional[0] === "custom" ? positional.slice(1) : positional;
6485
+ if (!o.blocks?.length) throw new Error(`create custom needs --blocks <id,id,...> (see \`graph8-sdk list\`)
6486
+
6487
+ ${CREATE_USAGE}`);
6488
+ if (rest.length !== 1) {
6489
+ throw new Error(`${rest.length ? "a stack name cannot be combined with --blocks; pass only the directory" : "create --blocks needs a directory"}
6490
+
6491
+ ${CREATE_USAGE}`);
6492
+ }
6493
+ return { ...o, stack: "custom", dir: rest[0] };
6494
+ }
6495
+ function packageManagerFromUserAgent(ua) {
6496
+ const name = /^(npm|pnpm|yarn|bun)\//.exec(ua ?? "")?.[1];
6497
+ return name ?? "npm";
6498
+ }
6499
+ function appNameFor(dir) {
6500
+ const base = import_node_path11.default.basename(import_node_path11.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
6501
+ return base || "g8-app";
6502
+ }
6503
+ var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
6504
+ function walk(dir, base = dir) {
6505
+ const out = [];
6506
+ for (const e of import_node_fs10.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
6507
+ const full = import_node_path11.default.join(dir, e.name);
6508
+ if (e.isDirectory()) out.push(...walk(full, base));
6509
+ else out.push(posix(import_node_path11.default.relative(base, full)));
6510
+ }
6511
+ return out;
6512
+ }
6513
+ function outputName(rel) {
6514
+ const noExt = rel.replace(/\.tmpl$/, "");
6515
+ const dir = import_node_path11.default.posix.dirname(noExt);
6516
+ const base = import_node_path11.default.posix.basename(noExt).replace(/^dot-/, ".");
6517
+ return dir === "." ? base : `${dir}/${base}`;
6518
+ }
6519
+ function stackProblem(reg, stack) {
6520
+ const { missing } = stackStatus(reg, stack);
6521
+ if (missing.length) {
6522
+ const ready = reg.stacks.filter((s) => !stackStatus(reg, s).missing.length).map((s) => s.id);
6523
+ return [
6524
+ `stack "${stack.id}" is planned: these blocks are not in this version of @graph8/sdk yet: ${missing.join(", ")}.`,
6525
+ `Stacks you can create now: ${ready.join(", ") || "none"}. Or create one of those and add blocks as they ship (\`graph8-sdk add <block>\`).`
6526
+ ].join("\n");
6527
+ }
6528
+ const seen = /* @__PURE__ */ new Set();
6529
+ const homeIssue = homeProblem(stack);
6530
+ if (homeIssue) return homeIssue;
6531
+ for (const n of stack.nav) {
6532
+ const block = findBlock(reg, n.block);
6533
+ if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
6534
+ 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.`;
6535
+ if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
6536
+ if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
6537
+ seen.add(n.path);
6538
+ if (n.page && (!reg.stacksRoot || !import_node_fs10.default.existsSync(import_node_path11.default.join(reg.stacksRoot, stack.id, n.page)))) {
6539
+ return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
6540
+ }
6541
+ }
6542
+ return null;
6543
+ }
6544
+ function homeProblem(stack) {
6545
+ if (!stack.home) return null;
6546
+ 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.`;
6547
+ 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.`;
6548
+ return null;
6549
+ }
6550
+ function stackPageFile(reg, stack, n) {
6551
+ return n.page && reg.stacksRoot ? import_node_path11.default.join(reg.stacksRoot, stack.id, n.page) : null;
6552
+ }
6553
+ function homeRedirect(stack) {
6554
+ if (stack.home) return stack.home === "/" ? null : stack.home;
6555
+ return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
6556
+ }
6557
+ function renderPages(reg, stack, templates) {
6558
+ const pageTpl = import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
6559
+ const files = stack.nav.map((n) => {
6560
+ const block = findBlock(reg, n.block);
6561
+ const comp = block.components.find((c) => c.name === n.component);
6562
+ const custom = stackPageFile(reg, stack, n);
6563
+ const vars2 = {
6564
+ stackId: stack.id,
6565
+ block: block.id,
6566
+ blockDir: `components/g8/${block.dir}`,
6567
+ component: n.component,
6568
+ importPath: componentImportPath(block, comp.file),
6569
+ propsJson: JSON.stringify(n.props ?? {}, null, 2)
6570
+ };
6571
+ return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs10.default.readFileSync(custom, "utf8") : pageTpl, vars2) };
6572
+ });
6573
+ const to = homeRedirect(stack);
6574
+ if (to) {
6575
+ const redirect = import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
6576
+ files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
6577
+ }
6578
+ return files;
6579
+ }
6580
+ function peerDependenciesFor(blocks, io, stack) {
6581
+ const peers = {};
6582
+ for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
6583
+ const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(import_node_fs10.default.readFileSync(import_node_path11.default.join(b.root, f), "utf8"))));
6584
+ if (usesUi && io.sdkPackageJson && import_node_fs10.default.existsSync(io.sdkPackageJson)) {
6585
+ const sdk = JSON.parse(import_node_fs10.default.readFileSync(io.sdkPackageJson, "utf8"));
6586
+ for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
6587
+ }
6588
+ if (navUsesExtras(stack.nav)) peers["lucide-react"] ??= lucideRange(io);
6589
+ return peers;
6590
+ }
6591
+ function lucideRange(io) {
6592
+ try {
6593
+ const sdk = JSON.parse(import_node_fs10.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
6594
+ return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
6595
+ } catch {
6596
+ return ">=0.400.0";
6597
+ }
6598
+ }
6599
+ function mergedPackageJson(rendered, peers) {
6600
+ const pkg = JSON.parse(rendered);
6601
+ for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
6602
+ pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
6603
+ return `${JSON.stringify(pkg, null, 2)}
6604
+ `;
6605
+ }
6606
+ function withAuth(files, auth, templatesRoot) {
6607
+ const patch = (rel, fn) => {
6608
+ const f = files.find((x) => x.rel === rel);
6609
+ if (!f) throw new Error(`--auth ${auth}: the template has no ${rel}`);
6610
+ const next = fn(f.content);
6611
+ if (next === null) throw new Error(`--auth ${auth}: ${rel} does not have the generated shape`);
6612
+ f.content = next;
6613
+ };
6614
+ patch("README.md", (s) => applyAuthToReadme(s, auth));
6615
+ patch(".env.local.example", (s) => applyAuthToEnvExample(s, auth));
6616
+ if (usesSignInScreen(auth)) patch("app/layout.tsx", applyAuthToLayout);
6617
+ return [...files, ...planAuthFiles(auth, templatesRoot)];
6618
+ }
6619
+ function planCreate(reg, stack, appName, pm, io, app, auth) {
6620
+ const templates = import_node_path11.default.join(io.templatesRoot, "next-app");
6621
+ const blocks = resolveWithDeps(reg, stack.blocks);
6622
+ const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
6623
+ const vars2 = {
6624
+ appName,
6625
+ sdkVersion: SDK_VERSION,
6626
+ stackId: stack.id,
6627
+ title: stack.title,
6628
+ titleJson: JSON.stringify(stack.title),
6629
+ ...navParts(stack),
6630
+ devCommand,
6631
+ installCommand: `${pm} install`,
6632
+ doctorCommand: app ? "npx @graph8/sdk doctor" : `npx @graph8/sdk doctor --stack ${stack.id}`,
6633
+ 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>`)"
6634
+ };
6635
+ const files = [];
6636
+ for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
6637
+ let content = render(import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, rel), "utf8"), vars2);
6638
+ if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
6639
+ files.push({ rel: outputName(rel), content });
6640
+ }
6641
+ files.push(...renderPages(reg, stack, templates));
6642
+ files.push({ rel: CONFIG_FILE, content: renderBrandConfig({ name: stack.title, ...DEFAULT_FIELDS }) });
6643
+ const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, ...app ? { app } : {}, blocks: {} };
6644
+ files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
6645
+ ` });
6646
+ return auth ? withAuth(files, auth, io.templatesRoot) : files;
6647
+ }
6648
+ function dirProblem(abs, force) {
6649
+ if (!import_node_fs10.default.existsSync(abs)) return null;
6650
+ if (!import_node_fs10.default.statSync(abs).isDirectory()) return `${abs} exists and is not a directory.`;
6651
+ if (force || import_node_fs10.default.readdirSync(abs).length === 0) return null;
6652
+ return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
6653
+ }
6654
+ function runCreate(given, reg, io, cwd) {
6655
+ let opts = given;
6656
+ if (io.orgBrand && given.orgBrand !== false) {
6657
+ const brand = mergeOrgBrand(given.brand, io.orgBrand, { name: given.title !== void 0 });
6658
+ if (hasBrandInput(brand)) opts = { ...given, brand };
6659
+ }
6660
+ let app;
6661
+ let stack = opts.blocks ? void 0 : reg.stacks.find((s) => s.id === opts.stack);
6662
+ if (opts.blocks) {
6663
+ let composed;
6664
+ try {
6665
+ composed = composeApp(reg, opts.blocks, { title: opts.title, withDepsScreens: opts.depsScreens === "with", noDepsScreens: opts.depsScreens === "none" });
6666
+ } catch (e) {
6667
+ io.err(e.message);
6668
+ return 1;
6669
+ }
6670
+ stack = composed.stack;
6671
+ app = { title: stack.title, blocks: [...new Set(opts.blocks)], screens: composed.screens.map((x) => ({ block: x.block, component: x.component, path: x.path })) };
6672
+ composed.warnings.forEach((w) => io.err(`warning: ${w}`));
6673
+ } else if (!stack) {
6674
+ io.err(`unknown stack "${opts.stack}". Stacks: ${reg.stacks.map((s) => s.id).join(", ") || "none"} (\`graph8-sdk list\`).`);
6675
+ return 1;
6676
+ }
6677
+ if (!stack) return 1;
6678
+ const name = opts.brand?.name?.trim();
6679
+ if (name) {
6680
+ stack = { ...stack, title: name };
6681
+ if (app) app = { ...app, title: name };
6682
+ }
6683
+ const problem = stackProblem(reg, stack);
6684
+ if (problem) return io.err(problem), 1;
6685
+ const abs = import_node_path11.default.resolve(cwd, opts.dir);
6686
+ const dirIssue = dirProblem(abs, opts.force);
6687
+ if (dirIssue) return io.err(dirIssue), 1;
6688
+ const brandIssue = opts.brand ? brandProblem(opts.brand, cwd) : null;
6689
+ if (brandIssue) return io.err(brandIssue), 1;
6690
+ const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
6691
+ let files;
6692
+ try {
6693
+ files = planCreate(reg, stack, appNameFor(opts.dir), pm, io, app, opts.auth);
6694
+ } catch (e) {
6695
+ io.err(e.message);
6696
+ return 1;
6697
+ }
6698
+ const blocks = resolveWithDeps(reg, stack.blocks);
6699
+ io.out(`${opts.dryRun ? "Dry run: " : ""}creating "${stack.title}" (${app ? `custom: ${app.blocks.join(", ")}` : stack.id}) in ${opts.dir}`);
6700
+ for (const f of files) io.out(`${opts.dryRun ? "would write" : "write"} ${f.rel}`);
6701
+ if (opts.dryRun) {
6702
+ io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
6703
+ if (opts.brand) io.out(`would brand the app: ${Object.entries(opts.brand).map(([k, v]) => `${k} ${v}`).join(", ")}`);
6704
+ if (opts.auth) io.out(`would set up sign-in (--auth ${opts.auth}): ${authSummary(opts.auth)}; authorize: appAuth.authorize in app/api/g8/[...path]/route.ts`);
6705
+ io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
6706
+ return 0;
6707
+ }
6708
+ for (const f of files) {
6709
+ const dest = import_node_path11.default.join(abs, f.rel);
6710
+ import_node_fs10.default.mkdirSync(import_node_path11.default.dirname(dest), { recursive: true });
6711
+ import_node_fs10.default.writeFileSync(dest, f.content, "utf8");
6712
+ }
6713
+ return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
6714
+ }
6715
+ function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
6716
+ for (const b of blocks) {
6717
+ const code = runAdd({ spec: b.id, dir: abs, target: "components/g8", dryRun: false, force: opts.force, install: false, wireScreens: false }, reg, io, cwd);
6718
+ if (code !== 0) {
6719
+ io.err(`adding block "${b.id}" failed; the app in ${opts.dir} is incomplete.`);
6720
+ return code;
6721
+ }
6722
+ }
6723
+ if (opts.auth) {
6724
+ const code = finishAuth(opts.auth, abs, io);
6725
+ if (code !== 0) return code;
6726
+ }
6727
+ if (opts.brand) {
6728
+ io.out("branding the app (graph8-sdk brand)");
6729
+ const code = runBrand({ ...opts.brand, dir: abs }, io, cwd, stack.title);
6730
+ if (code !== 0) {
6731
+ io.err(`branding failed; the app in ${opts.dir} is complete but unbranded. Fix the option and run \`npx @graph8/sdk brand\` in it.`);
6732
+ return code;
6733
+ }
6734
+ }
6735
+ if (opts.install) {
6736
+ io.out(`installing dependencies: ${pm} install`);
6737
+ const code = io.spawn(pm, ["install"], abs);
6738
+ if (code !== 0) {
6739
+ io.err(`dependency install failed (exit ${code}). Run it yourself: cd ${quoted(opts.dir)} && ${pm} install`);
6740
+ return 1;
6741
+ }
6742
+ }
6743
+ const cd = opts.dir === "." ? "" : `cd ${quoted(opts.dir)} && `;
6744
+ io.out(
6745
+ [
6746
+ "",
6747
+ `Done. Next:`,
6748
+ ` 1. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`,
6749
+ ...opts.auth ? authOutro(opts.auth, opts.dir) : [],
6750
+ ` 2. open http://localhost:3000 and paste a graph8 API key in the Connect graph8 screen (local preview: only`,
6751
+ ` this computer can reach the app's /api/g8 route), or set G8_API_KEY in ${import_node_path11.default.posix.join(opts.dir, ".env.local")}`,
6752
+ ` 3. ${cd}npx @graph8/sdk doctor${opts.blocks ? "" : ` --stack ${stack.id}`} (before going live: add --production)`
6753
+ ].join("\n")
6754
+ );
6755
+ return 0;
6756
+ }
6757
+ function finishAuth(auth, abs, io) {
6758
+ const rel = "app/api/g8/[...path]/route.ts";
6759
+ const file = import_node_path11.default.join(abs, rel);
6760
+ const next = import_node_fs10.default.existsSync(file) ? applyAuthToRoute(import_node_fs10.default.readFileSync(file, "utf8")) : null;
6761
+ if (next === null) {
6762
+ io.err(`--auth ${auth}: ${rel} does not have the generated shape; set \`authorize: appAuth.authorize\` (import { appAuth } from "@/g8.auth") in it yourself.`);
6763
+ return 1;
6764
+ }
6765
+ import_node_fs10.default.writeFileSync(file, next, "utf8");
6766
+ io.out(`sign-in (--auth ${auth}): ${authSummary(auth)}`);
6767
+ io.out(`update ${rel} (authorize: appAuth.authorize, from g8.auth.ts)`);
6768
+ const written = writeAuthSecrets(abs, auth);
6769
+ if (written.length) io.out(`write .env.local (fresh ${written.join(" and ")} for local development; git-ignored, not printed)`);
6770
+ return 0;
6771
+ }
6772
+ function authOutro(auth, dir) {
6773
+ const env = import_node_path11.default.posix.join(dir, ".env.local");
6774
+ if (auth === "password") return [` sign in with G8_APP_PASSWORD from ${env} (set your own G8_APP_PASSWORD and G8_SESSION_SECRET on your host)`];
6775
+ if (auth === "basic") return [` the browser asks for the G8_BASIC_AUTH user and password in ${env}`];
6776
+ if (auth === "jwt") return [` set your identity provider in ${env} (G8_JWT_ISSUER, G8_JWT_AUDIENCE, G8_JWT_COOKIE, G8_SIGN_IN_URL; see g8.auth.ts)`];
6777
+ if (auth === "session") return [" wire your session library in g8.auth.ts (Clerk, NextAuth, Supabase examples inside); until then it refuses everyone"];
6778
+ return [" WARNING: --auth anonymous: anyone who can reach this app can use its graph8 API key. Use a narrow key with a spend cap."];
6779
+ }
6780
+
6451
6781
  // src/cli.ts
6452
6782
  var LIST_USAGE = `Usage: npx @graph8/sdk list [--json]
6453
6783
 
@@ -6471,7 +6801,7 @@ Run a command with --help for its options.
6471
6801
 
6472
6802
  ${INIT_AGENT_USAGE}`;
6473
6803
  function registryFor(io) {
6474
- const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path10.default.resolve(__dirname, "..", "blocks");
6804
+ const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path12.default.resolve(__dirname, "..", "blocks");
6475
6805
  return loadRegistry(root);
6476
6806
  }
6477
6807
  function defaultSpawn(cmd, args, cwd) {
@@ -6545,14 +6875,15 @@ function runCreateCommand(rest, io) {
6545
6875
  if (wantsHelp(rest)) return io.out(CREATE_USAGE), 0;
6546
6876
  try {
6547
6877
  const opts = parseCreateArgs(rest);
6548
- const packageRoot = import_node_path10.default.resolve(__dirname, "..");
6878
+ const packageRoot = import_node_path12.default.resolve(__dirname, "..");
6549
6879
  const cio = {
6550
6880
  out: io.out,
6551
6881
  err: io.err,
6552
6882
  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
6883
+ templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path12.default.join(packageRoot, "templates"),
6884
+ sdkPackageJson: import_node_path12.default.join(packageRoot, "package.json"),
6885
+ userAgent: io.userAgent ?? process.env.npm_config_user_agent,
6886
+ orgBrand: io.orgBrand
6556
6887
  };
6557
6888
  return runCreate(opts, registryFor(io), cio, io.cwd);
6558
6889
  } catch (e) {
@@ -6563,13 +6894,13 @@ function runAddCommand(rest, io) {
6563
6894
  if (wantsHelp(rest)) return io.out(ADD_USAGE), 0;
6564
6895
  try {
6565
6896
  const opts = parseAddArgs(rest);
6566
- const packageRoot = import_node_path10.default.resolve(__dirname, "..");
6897
+ const packageRoot = import_node_path12.default.resolve(__dirname, "..");
6567
6898
  const aio = {
6568
6899
  out: io.out,
6569
6900
  err: io.err,
6570
6901
  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")
6902
+ templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path12.default.join(packageRoot, "templates"),
6903
+ sdkPackageJson: import_node_path12.default.join(packageRoot, "package.json")
6573
6904
  };
6574
6905
  return runAdd(opts, registryFor(io), aio, io.cwd);
6575
6906
  } catch (e) {
@@ -6589,7 +6920,7 @@ function runCli(argv, io) {
6589
6920
  if (command === "brand") {
6590
6921
  if (wantsHelp(rest)) return io.out(BRAND_USAGE), 0;
6591
6922
  try {
6592
- return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd);
6923
+ return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd, void 0, io.orgBrand);
6593
6924
  } catch (e) {
6594
6925
  return fail(io, e.message);
6595
6926
  }
@@ -6613,8 +6944,8 @@ ${USAGE}`);
6613
6944
  return 1;
6614
6945
  }
6615
6946
  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;
6947
+ const full = import_node_path12.default.join(io.cwd, rel);
6948
+ return import_node_fs11.default.existsSync(full) ? import_node_fs11.default.readFileSync(full, "utf8") : null;
6618
6949
  };
6619
6950
  let plans;
6620
6951
  try {
@@ -6625,17 +6956,36 @@ ${USAGE}`);
6625
6956
  }
6626
6957
  for (const plan of plans) {
6627
6958
  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");
6959
+ const full = import_node_path12.default.join(io.cwd, plan.path);
6960
+ import_node_fs11.default.mkdirSync(import_node_path12.default.dirname(full), { recursive: true });
6961
+ import_node_fs11.default.writeFileSync(full, plan.content, "utf8");
6631
6962
  }
6632
6963
  io.out(`${options.dryRun ? "would " : ""}${plan.action === "unchanged" ? "keep" : plan.action} ${plan.path} (${plan.target})`);
6633
6964
  }
6634
6965
  if (!options.dryRun) io.out("\nDone. The full guide is node_modules/@graph8/sdk/docs/agents.md.");
6635
6966
  return 0;
6636
6967
  }
6968
+ function brandRoot(command, rest, io) {
6969
+ const at = rest.findIndex((a) => a === "--dir" || a.startsWith("--dir="));
6970
+ const dir = at < 0 ? void 0 : rest[at].includes("=") ? rest[at].slice(6) : rest[at + 1];
6971
+ return command === "brand" && dir ? import_node_path12.default.resolve(io.cwd, dir) : io.cwd;
6972
+ }
6973
+ async function runWithOrgBrand(command, rest, argv, io) {
6974
+ 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=")));
6975
+ if (!wants) return runCli(argv, io);
6976
+ const pull = await pullOrgBrand({ env: io.env ?? process.env, fetch: io.fetch }, brandRoot(command, rest, io));
6977
+ io.out(pull.info);
6978
+ const bare = command === "brand" && !rest.includes("--from-org") && !rest.some((a) => a.startsWith("--") && a !== "--dir" && !a.startsWith("--dir="));
6979
+ if (bare && !Object.keys(pull.values).length) return runCli(argv, io);
6980
+ try {
6981
+ return runCli(bare ? [...argv, "--from-org"] : argv, { ...io, orgBrand: pull.values });
6982
+ } finally {
6983
+ pull.cleanup();
6984
+ }
6985
+ }
6637
6986
  async function runCliAsync(argv, io) {
6638
6987
  const [command, ...rest] = argv;
6988
+ if (command === "create" || command === "brand") return runWithOrgBrand(command, rest, argv, io);
6639
6989
  if (command !== "doctor") return runCli(argv, io);
6640
6990
  if (wantsHelp(rest)) return io.out(DOCTOR_USAGE), 0;
6641
6991
  try {