@graph8/sdk 0.362.0 → 0.370.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 (349) hide show
  1. package/README.md +14 -10
  2. package/blocks/_foundation/README.md +64 -1
  3. package/blocks/_foundation/block.json +79 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  7. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  8. package/blocks/_foundation/components/screen/index.ts +1 -0
  9. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  10. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  11. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  12. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  13. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  14. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  15. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  16. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  17. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  18. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  19. package/blocks/_foundation/lib/g8-client.ts +1 -1
  20. package/blocks/_foundation/lib/plural.ts +23 -0
  21. package/blocks/_foundation/server/connect-route.ts +15 -0
  22. package/blocks/_foundation/server/connect.ts +127 -0
  23. package/blocks/_foundation/server/local-key.ts +70 -0
  24. package/blocks/_foundation/server/proxy.ts +103 -3
  25. package/blocks/_foundation/server/route.ts +16 -4
  26. package/blocks/agents/README.md +14 -0
  27. package/blocks/agents/block.json +33 -1
  28. package/blocks/agents/components/agent-builder.tsx +13 -8
  29. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -1
  30. package/blocks/agents/components/agent-run-detail.tsx +102 -0
  31. package/blocks/agents/components/agent-runs.tsx +16 -7
  32. package/blocks/agents/components/agents-screen.tsx +184 -0
  33. package/blocks/analytics/README.md +13 -0
  34. package/blocks/analytics/block.json +45 -1
  35. package/blocks/analytics/components/analytics-screen.tsx +209 -0
  36. package/blocks/analytics/components/leaderboard.tsx +14 -7
  37. package/blocks/analytics/components/report-builder.tsx +23 -9
  38. package/blocks/analytics/components/report-detail.tsx +84 -0
  39. package/blocks/analytics/components/trend-chart.tsx +62 -0
  40. package/blocks/appointments/block.json +6 -2
  41. package/blocks/appointments/components/availability-editor.tsx +17 -5
  42. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  43. package/blocks/appointments/components/event-type-related.tsx +4 -2
  44. package/blocks/appointments/components/meeting-list.tsx +9 -5
  45. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  46. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  47. package/blocks/block.schema.json +18 -0
  48. package/blocks/block.types.ts +17 -0
  49. package/blocks/campaigns/README.md +41 -11
  50. package/blocks/campaigns/block.json +105 -42
  51. package/blocks/campaigns/components/campaign-assets.tsx +16 -5
  52. package/blocks/campaigns/components/campaign-launch-step.tsx +102 -19
  53. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  54. package/blocks/campaigns/components/campaign-wizard.tsx +43 -10
  55. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  56. package/blocks/campaigns/components/icp-detail.tsx +154 -0
  57. package/blocks/campaigns/components/icp-editor.tsx +15 -15
  58. package/blocks/campaigns/components/persona-detail.tsx +172 -0
  59. package/blocks/campaigns/components/persona-editor.tsx +14 -11
  60. package/blocks/campaigns/lib/campaigns-utils.ts +134 -1
  61. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  62. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  63. package/blocks/coach/README.md +10 -0
  64. package/blocks/coach/block.json +16 -1
  65. package/blocks/coach/components/coach-screen.tsx +222 -0
  66. package/blocks/copilot/README.md +13 -0
  67. package/blocks/copilot/block.json +14 -1
  68. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  69. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  70. package/blocks/copilot/components/copilot-team-chat.tsx +7 -5
  71. package/blocks/crm/README.md +28 -0
  72. package/blocks/crm/block.json +58 -2
  73. package/blocks/crm/components/companies-screen.tsx +345 -0
  74. package/blocks/crm/components/company-detail.tsx +8 -6
  75. package/blocks/crm/components/company-table.tsx +11 -4
  76. package/blocks/crm/components/contact-detail.tsx +6 -3
  77. package/blocks/crm/components/contact-table.tsx +10 -3
  78. package/blocks/crm/components/contacts-screen.tsx +359 -0
  79. package/blocks/crm/components/dedupe-review.tsx +7 -5
  80. package/blocks/crm/components/import-wizard.tsx +4 -2
  81. package/blocks/crm/components/notes-panel.tsx +4 -4
  82. package/blocks/crm/components/record-form.tsx +5 -3
  83. package/blocks/crm/components/task-list.tsx +4 -4
  84. package/blocks/crm/components/tasks-screen.tsx +379 -0
  85. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  86. package/blocks/crm/hooks/use-page-query.ts +7 -1
  87. package/blocks/crm/lib/crm-types.ts +12 -2
  88. package/blocks/crm/lib/screen-parts.tsx +105 -0
  89. package/blocks/deals/block.json +9 -3
  90. package/blocks/deals/components/deal-detail.tsx +23 -11
  91. package/blocks/deals/components/deal-related.tsx +1 -1
  92. package/blocks/deals/components/deals-screen.tsx +10 -5
  93. package/blocks/deals/components/stage-editor.tsx +4 -4
  94. package/blocks/deals/hooks/use-pipeline-deals.ts +5 -1
  95. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  96. package/blocks/dialer/README.md +34 -2
  97. package/blocks/dialer/block.json +67 -1
  98. package/blocks/dialer/components/call-contact-button.tsx +252 -0
  99. package/blocks/dialer/components/call-log-screen.tsx +205 -0
  100. package/blocks/dialer/components/call-log.tsx +7 -3
  101. package/blocks/dialer/components/call-queue.tsx +1 -1
  102. package/blocks/dialer/components/dialer-screen.tsx +319 -0
  103. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  104. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  105. package/blocks/dialer/components/session-start-form.tsx +4 -2
  106. package/blocks/dialer/components/softphone.tsx +3 -1
  107. package/blocks/enrichment/README.md +14 -2
  108. package/blocks/enrichment/block.json +32 -4
  109. package/blocks/enrichment/components/bulk-enrich-panel.tsx +10 -5
  110. package/blocks/enrichment/components/email-verify-badge.tsx +28 -8
  111. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  112. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  113. package/blocks/enrichment/components/enrichment-screen.tsx +288 -0
  114. package/blocks/enrichment/components/waterfall-config.tsx +39 -10
  115. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  116. package/blocks/forms/block.json +32 -18
  117. package/blocks/forms/components/form-builder.tsx +3 -1
  118. package/blocks/forms/components/form-embed.tsx +3 -1
  119. package/blocks/forms/components/forms-screen.tsx +5 -2
  120. package/blocks/forms/components/submissions-table.tsx +3 -1
  121. package/blocks/inbox/README.md +27 -3
  122. package/blocks/inbox/block.json +14 -2
  123. package/blocks/inbox/components/inbox-list.tsx +7 -3
  124. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  125. package/blocks/inbox/components/reply-composer.tsx +65 -9
  126. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  127. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  128. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  129. package/blocks/inbox/lib/inbox-types.ts +47 -1
  130. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  131. package/blocks/integrations/README.md +9 -0
  132. package/blocks/integrations/block.json +46 -1
  133. package/blocks/integrations/components/crm-detail.tsx +100 -0
  134. package/blocks/integrations/components/integration-list.tsx +9 -4
  135. package/blocks/integrations/components/integrations-screen.tsx +135 -0
  136. package/blocks/integrations/components/sync-status.tsx +5 -3
  137. package/blocks/integrations/components/webhook-detail.tsx +160 -0
  138. package/blocks/integrations/components/webhook-manager.tsx +13 -8
  139. package/blocks/integrations/lib/integrations-utils.ts +6 -1
  140. package/blocks/intent/README.md +14 -0
  141. package/blocks/intent/block.json +27 -1
  142. package/blocks/intent/components/intent-feed.tsx +5 -2
  143. package/blocks/intent/components/intent-keyword-manager.tsx +8 -4
  144. package/blocks/intent/components/intent-screen.tsx +265 -0
  145. package/blocks/intent/components/keyword-detail.tsx +124 -0
  146. package/blocks/lifecycle/block.json +13 -8
  147. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  148. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  149. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  150. package/blocks/lists/README.md +13 -0
  151. package/blocks/lists/block.json +39 -1
  152. package/blocks/lists/components/add-to-list-button.tsx +3 -2
  153. package/blocks/lists/components/list-picker.tsx +5 -1
  154. package/blocks/lists/components/list-table.tsx +11 -5
  155. package/blocks/lists/components/lists-screen.tsx +344 -0
  156. package/blocks/lists/components/suppression-manager.tsx +6 -2
  157. package/blocks/lists/components/suppressions-screen.tsx +255 -0
  158. package/blocks/mailboxes/README.md +17 -0
  159. package/blocks/mailboxes/block.json +65 -17
  160. package/blocks/mailboxes/components/connect-mailbox.tsx +23 -13
  161. package/blocks/mailboxes/components/domain-detail.tsx +142 -0
  162. package/blocks/mailboxes/components/domain-purchase.tsx +6 -3
  163. package/blocks/mailboxes/components/mailbox-detail.tsx +156 -0
  164. package/blocks/mailboxes/components/mailboxes-screen.tsx +223 -0
  165. package/blocks/mailboxes/components/warmup-health.tsx +4 -2
  166. package/blocks/mailboxes/lib/mailboxes-utils.ts +22 -1
  167. package/blocks/newsletter/block.json +40 -24
  168. package/blocks/newsletter/components/issue-scheduler.tsx +2 -1
  169. package/blocks/newsletter/components/newsletter-screen.tsx +3 -1
  170. package/blocks/nurture/block.json +28 -14
  171. package/blocks/nurture/components/nurture-builder.tsx +3 -1
  172. package/blocks/nurture/components/nurture-enroll.tsx +6 -3
  173. package/blocks/nurture/components/nurture-form.tsx +23 -3
  174. package/blocks/nurture/components/nurture-screen.tsx +10 -6
  175. package/blocks/nurture/lib/nurture-types.ts +1 -1
  176. package/blocks/objects/README.md +17 -0
  177. package/blocks/objects/block.json +29 -16
  178. package/blocks/objects/components/object-not-found.tsx +3 -1
  179. package/blocks/objects/components/object-record-detail.tsx +2 -1
  180. package/blocks/objects/components/object-schema-editor.tsx +3 -1
  181. package/blocks/objects/components/object-table.tsx +13 -3
  182. package/blocks/objects/components/objects-screen.tsx +147 -0
  183. package/blocks/objects/lib/object-types.ts +1 -1
  184. package/blocks/pages/block.json +28 -23
  185. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  186. package/blocks/pages/components/content-library.tsx +9 -4
  187. package/blocks/pages/components/page-list.tsx +2 -1
  188. package/blocks/quotes/block.json +17 -12
  189. package/blocks/quotes/components/product-picker.tsx +3 -1
  190. package/blocks/quotes/components/quote-related.tsx +5 -2
  191. package/blocks/radar/README.md +10 -0
  192. package/blocks/radar/block.json +41 -1
  193. package/blocks/radar/components/competitor-detail.tsx +117 -0
  194. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  195. package/blocks/radar/components/radar-report.tsx +4 -2
  196. package/blocks/radar/components/radar-screen.tsx +214 -0
  197. package/blocks/radar/components/report-detail.tsx +104 -0
  198. package/blocks/search/README.md +10 -0
  199. package/blocks/search/block.json +16 -1
  200. package/blocks/search/components/filter-builder.tsx +3 -3
  201. package/blocks/search/components/prospect-results.tsx +5 -3
  202. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  203. package/blocks/search/components/saved-searches.tsx +4 -1
  204. package/blocks/search/hooks/use-filter-options.ts +20 -6
  205. package/blocks/search/lib/search-types.ts +2 -0
  206. package/blocks/sequencer/README.md +49 -9
  207. package/blocks/sequencer/block.json +99 -19
  208. package/blocks/sequencer/components/enroll-dialog.tsx +9 -3
  209. package/blocks/sequencer/components/sequence-builder.tsx +211 -46
  210. package/blocks/sequencer/components/sequence-detail.tsx +259 -0
  211. package/blocks/sequencer/components/sequence-enrollments.tsx +114 -0
  212. package/blocks/sequencer/components/sequence-list.tsx +33 -8
  213. package/blocks/sequencer/components/sequence-stats.tsx +3 -2
  214. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  215. package/blocks/sequencer/components/sequences-screen.tsx +159 -0
  216. package/blocks/sequencer/components/step-editor.tsx +3 -1
  217. package/blocks/sequencer/lib/sequencer-utils.ts +128 -5
  218. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  219. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  220. package/blocks/social-ads/README.md +11 -0
  221. package/blocks/social-ads/block.json +101 -2
  222. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  223. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  224. package/blocks/social-ads/components/audience-sync-detail.tsx +188 -0
  225. package/blocks/social-ads/components/audience-sync.tsx +11 -7
  226. package/blocks/social-ads/components/campaign-detail.tsx +133 -0
  227. package/blocks/social-ads/components/creative-detail.tsx +150 -0
  228. package/blocks/social-ads/components/creative-gallery.tsx +15 -5
  229. package/blocks/social-ads/components/post-composer.tsx +3 -1
  230. package/blocks/social-ads/components/post-detail.tsx +160 -0
  231. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  232. package/blocks/social-ads/components/social-screen.tsx +160 -0
  233. package/blocks/visitors/README.md +10 -0
  234. package/blocks/visitors/block.json +25 -1
  235. package/blocks/visitors/components/snippet-install.tsx +3 -1
  236. package/blocks/visitors/components/visitor-company-card.tsx +2 -2
  237. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  238. package/blocks/visitors/components/visitor-feed.tsx +11 -2
  239. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  240. package/blocks/webchat/README.md +10 -0
  241. package/blocks/webchat/block.json +21 -7
  242. package/blocks/webchat/components/chat-widget-config.tsx +7 -4
  243. package/blocks/webchat/components/conversation-list.tsx +7 -3
  244. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  245. package/blocks/workbench/README.md +11 -0
  246. package/blocks/workbench/block.json +48 -1
  247. package/blocks/workbench/components/column-recipe-editor.tsx +4 -2
  248. package/blocks/workbench/components/workbench-grid.tsx +9 -3
  249. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  250. package/blocks/workbench/components/workbench-screen.tsx +192 -0
  251. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  252. package/blocks/workflows/README.md +13 -0
  253. package/blocks/workflows/block.json +28 -9
  254. package/blocks/workflows/components/run-history.tsx +31 -9
  255. package/blocks/workflows/components/trigger-config.tsx +3 -1
  256. package/blocks/workflows/components/workflow-editor.tsx +3 -1
  257. package/blocks/workflows/components/workflow-list.tsx +13 -6
  258. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  259. package/dist/cli.js +2709 -1721
  260. package/dist/errors.d.mts +34 -0
  261. package/dist/errors.d.ts +34 -0
  262. package/dist/errors.js +46 -0
  263. package/dist/errors.js.map +1 -0
  264. package/dist/errors.mjs +21 -0
  265. package/dist/errors.mjs.map +1 -0
  266. package/dist/index.browser.js +11 -5
  267. package/dist/index.browser.js.map +1 -1
  268. package/dist/index.browser.mjs +11 -5
  269. package/dist/index.browser.mjs.map +1 -1
  270. package/dist/index.d.mts +164 -25
  271. package/dist/index.d.ts +164 -25
  272. package/dist/index.js +11 -5
  273. package/dist/index.js.map +1 -1
  274. package/dist/index.mjs +11 -5
  275. package/dist/index.mjs.map +1 -1
  276. package/dist/react.browser.js +10 -4
  277. package/dist/react.browser.js.map +1 -1
  278. package/dist/react.browser.mjs +10 -4
  279. package/dist/react.browser.mjs.map +1 -1
  280. package/dist/react.d.mts +142 -5
  281. package/dist/react.d.ts +142 -5
  282. package/dist/react.js +10 -4
  283. package/dist/react.js.map +1 -1
  284. package/dist/react.mjs +10 -4
  285. package/dist/react.mjs.map +1 -1
  286. package/dist/types/blocks/add.d.ts +11 -4
  287. package/dist/types/blocks/brand.d.ts +113 -0
  288. package/dist/types/blocks/compose.d.ts +53 -0
  289. package/dist/types/blocks/create.d.ts +22 -19
  290. package/dist/types/blocks/doctor.d.ts +5 -1
  291. package/dist/types/blocks/navfile.d.ts +35 -0
  292. package/dist/types/blocks/paths.d.ts +4 -0
  293. package/dist/types/blocks/prefixes.d.ts +37 -0
  294. package/dist/types/blocks/registry.d.ts +17 -0
  295. package/dist/types/blocks/screens.d.ts +53 -0
  296. package/dist/types/cli.d.ts +2 -0
  297. package/dist/types/errors.d.ts +33 -0
  298. package/dist/types/http.d.ts +2 -18
  299. package/dist/types/version.d.ts +2 -2
  300. package/docs/agents.md +15 -4
  301. package/docs/api/README.md +2 -2
  302. package/docs/api/by-path.md +1 -0
  303. package/docs/api/desk.d.ts +98 -2
  304. package/docs/api/desk.md +123 -1
  305. package/docs/api/inbox.d.ts +10 -2
  306. package/docs/api/inbox.md +10 -2
  307. package/docs/api/seo.d.ts +19 -1
  308. package/docs/api/seo.md +24 -1
  309. package/package.json +16 -2
  310. package/stacks/6sense.json +41 -16
  311. package/stacks/apollo/pages/companies.tsx.tmpl +50 -0
  312. package/stacks/apollo/pages/contacts.tsx.tmpl +117 -0
  313. package/stacks/apollo.json +118 -36
  314. package/stacks/calendly.json +60 -0
  315. package/stacks/clay/pages/companies.tsx.tmpl +15 -0
  316. package/stacks/clay/pages/contacts.tsx.tmpl +19 -0
  317. package/stacks/clay.json +59 -26
  318. package/stacks/gong.json +81 -35
  319. package/stacks/hootsuite.json +99 -0
  320. package/stacks/hubspot/pages/companies.tsx.tmpl +47 -0
  321. package/stacks/hubspot/pages/contacts.tsx.tmpl +111 -0
  322. package/stacks/hubspot/pages/deals.tsx.tmpl +11 -0
  323. package/stacks/hubspot/pages/inbox.tsx.tmpl +92 -0
  324. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  325. package/stacks/hubspot.json +167 -66
  326. package/stacks/intercom.json +48 -20
  327. package/stacks/mailchimp.json +52 -33
  328. package/stacks/outreach/pages/contacts.tsx.tmpl +85 -0
  329. package/stacks/outreach.json +88 -25
  330. package/stacks/pipedrive/pages/companies.tsx.tmpl +47 -0
  331. package/stacks/pipedrive/pages/contacts.tsx.tmpl +47 -0
  332. package/stacks/pipedrive/pages/deals.tsx.tmpl +11 -0
  333. package/stacks/pipedrive/pages/inbox.tsx.tmpl +92 -0
  334. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  335. package/stacks/pipedrive.json +115 -0
  336. package/stacks/salesforce/pages/companies.tsx.tmpl +47 -0
  337. package/stacks/salesforce/pages/contacts.tsx.tmpl +47 -0
  338. package/stacks/salesforce/pages/deals.tsx.tmpl +11 -0
  339. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  340. package/stacks/salesforce.json +81 -31
  341. package/stacks/zoominfo/pages/companies.tsx.tmpl +15 -0
  342. package/stacks/zoominfo/pages/contacts.tsx.tmpl +19 -0
  343. package/stacks/zoominfo.json +102 -0
  344. package/styles/styles.css +1 -1
  345. package/templates/next-app/README.md.tmpl +40 -9
  346. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  347. package/templates/next-app/components/app-shell.tsx.tmpl +99 -89
  348. package/templates/next-app/dot-gitignore.tmpl +2 -0
  349. package/templates/next-app/package.json.tmpl +1 -1
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_fs6 = __toESM(require("fs"));
40
- var import_node_path6 = __toESM(require("path"));
39
+ var import_node_fs9 = __toESM(require("fs"));
40
+ var import_node_path10 = __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, path7, next) => {
123
- const current = read(path7);
124
- plans.push({ target, path: path7, content: next, action: current === null ? "create" : current === next ? "unchanged" : "update" });
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" });
125
125
  };
126
126
  for (const target of targets) {
127
127
  if (target === "claude") add(target, ".claude/skills/graph8-sdk/SKILL.md", claudeSkill(catalog));
@@ -154,1692 +154,21 @@ ${INIT_AGENT_USAGE}`);
154
154
 
155
155
  // src/blocks/add.ts
156
156
  var import_node_crypto = require("crypto");
157
- var import_node_fs2 = __toESM(require("fs"));
158
- var import_node_path2 = __toESM(require("path"));
157
+ var import_node_fs4 = __toESM(require("fs"));
158
+ var import_node_path5 = __toESM(require("path"));
159
159
 
160
160
  // src/version.ts
161
- var SDK_VERSION = "0.362.0";
161
+ var SDK_VERSION = "0.370.0";
162
162
  var SDK_CLIENT_ID = `js-sdk/${SDK_VERSION}`;
163
163
 
164
- // src/blocks/registry.ts
165
- var import_node_fs = __toESM(require("fs"));
164
+ // src/blocks/paths.ts
166
165
  var import_node_path = __toESM(require("path"));
167
- function arr(v) {
168
- return Array.isArray(v) ? v : [];
169
- }
170
- function readJson(file) {
171
- try {
172
- const parsed = JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
173
- return parsed && typeof parsed === "object" ? parsed : null;
174
- } catch {
175
- return null;
176
- }
177
- }
178
- function toBlock(raw, dir, root) {
179
- if (typeof raw.id !== "string" || !raw.id) return null;
180
- return {
181
- id: raw.id,
182
- title: typeof raw.title === "string" ? raw.title : raw.id,
183
- summary: typeof raw.summary === "string" ? raw.summary : "",
184
- features: arr(raw.features),
185
- dependsOn: arr(raw.dependsOn),
186
- operations: arr(raw.operations),
187
- components: arr(raw.components),
188
- hooks: arr(raw.hooks),
189
- lib: arr(raw.lib),
190
- server: arr(raw.server),
191
- recipes: arr(raw.recipes),
192
- prerequisites: arr(raw.prerequisites),
193
- scopes: arr(raw.scopes),
194
- defaults: arr(raw.defaults),
195
- peerDependencies: raw.peerDependencies ?? {},
196
- dir,
197
- root
198
- };
199
- }
200
- function loadRegistry(blocksRoot) {
201
- const blocks = [];
202
- if (import_node_fs.default.existsSync(blocksRoot)) {
203
- for (const entry of import_node_fs.default.readdirSync(blocksRoot, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
204
- if (!entry.isDirectory()) continue;
205
- const root = import_node_path.default.join(blocksRoot, entry.name);
206
- const raw = readJson(import_node_path.default.join(root, "block.json"));
207
- const block = raw && toBlock(raw, entry.name, root);
208
- if (block) blocks.push(block);
209
- }
210
- }
211
- const stacks = [];
212
- const stacksDir = import_node_path.default.join(import_node_path.default.dirname(blocksRoot), "stacks");
213
- if (import_node_fs.default.existsSync(stacksDir)) {
214
- for (const file of import_node_fs.default.readdirSync(stacksDir).filter((f) => f.endsWith(".json")).sort()) {
215
- const raw = readJson(import_node_path.default.join(stacksDir, file));
216
- if (!raw || typeof raw.id !== "string") continue;
217
- stacks.push({
218
- id: raw.id,
219
- title: typeof raw.title === "string" ? raw.title : raw.id,
220
- summary: typeof raw.summary === "string" ? raw.summary : "",
221
- status: raw.status === "planned" ? "planned" : "available",
222
- blocks: arr(raw.blocks),
223
- nav: arr(raw.nav),
224
- ...typeof raw.home === "string" ? { home: raw.home } : {}
225
- });
226
- }
227
- }
228
- return { blocks, stacks, stacksRoot: stacksDir };
229
- }
230
- function findBlock(reg, id) {
231
- return reg.blocks.find((b) => b.id === id);
232
- }
233
- function resolveWithDeps(reg, ids) {
234
- const ordered = [];
235
- const done = /* @__PURE__ */ new Set();
236
- const visiting = [];
237
- const visit = (id) => {
238
- if (done.has(id)) return;
239
- if (visiting.includes(id)) throw new Error(`dependency cycle: ${[...visiting, id].join(" -> ")}`);
240
- const block = findBlock(reg, id);
241
- if (!block) {
242
- const from = visiting.length ? ` (required by ${visiting[visiting.length - 1]})` : "";
243
- throw new Error(`unknown block "${id}"${from}. Run \`graph8-sdk list\` to see the available blocks.`);
244
- }
245
- visiting.push(id);
246
- for (const dep of block.dependsOn) visit(dep);
247
- visiting.pop();
248
- done.add(id);
249
- ordered.push(block);
250
- };
251
- ids.forEach(visit);
252
- return ordered;
253
- }
254
- function searchBlocks(reg, text) {
255
- const needle = text.trim().toLowerCase();
256
- if (!needle) return [];
257
- return reg.blocks.filter(
258
- (b) => [b.id, b.title, b.summary, ...b.features, ...b.components.map((c) => c.name)].some((s) => s.toLowerCase().includes(needle))
259
- );
260
- }
261
- function searchStacks(reg, text) {
262
- const needle = text.trim().toLowerCase();
263
- if (!needle) return [];
264
- return reg.stacks.filter((s) => [s.id, s.title, s.summary].some((x) => x.toLowerCase().includes(needle)));
265
- }
266
- function screenNames(b) {
267
- return b.components.map((c) => c.name).filter((n) => n.endsWith("Screen"));
268
- }
269
- function blockSummary(b) {
270
- const screens = screenNames(b);
271
- return {
272
- id: b.id,
273
- title: b.title,
274
- summary: b.summary,
275
- dependsOn: b.dependsOn,
276
- components: b.components.map((c) => c.name),
277
- ...screens.length ? { screens } : {}
278
- };
279
- }
280
- function stackStatus(reg, s) {
281
- const missing = s.blocks.filter((id) => !findBlock(reg, id));
282
- return { status: missing.length ? "planned" : "available", missing };
283
- }
284
- function stackSummary(s, reg) {
285
- const base = { id: s.id, title: s.title, summary: s.summary, blocks: s.blocks };
286
- if (!reg) return base;
287
- const { status, missing } = stackStatus(reg, s);
288
- return { ...base, status, ...missing.length ? { missingBlocks: missing } : {} };
289
- }
290
- function catalogSection(reg) {
291
- if (!reg.blocks.length && !reg.stacks.length) return void 0;
292
- const lines = ["### graph8 blocks", "", "Ready-made, copy-in UI + hooks per graph8 feature. Pick from this list instead of reading every README:", ""];
293
- for (const b of reg.blocks) {
294
- const deps = b.dependsOn.length ? ` (needs ${b.dependsOn.join(", ")})` : "";
295
- const screens = screenNames(b);
296
- lines.push(`- block \`${b.id}\`: ${b.summary}${deps}${screens.length ? ` Screens (start here): ${screens.join(", ")}.` : ""}`);
297
- }
298
- for (const s of reg.stacks) {
299
- const { missing } = stackStatus(reg, s);
300
- const planned = missing.length ? ` [planned, not creatable yet: missing blocks ${missing.join(", ")}]` : "";
301
- lines.push(`- stack \`${s.id}\`: ${s.summary} (blocks: ${s.blocks.join(", ")})${planned}`);
302
- }
303
- lines.push(
304
- "",
305
- "Add one: `npx @graph8/sdk add <block>[/<Component>]` (dependencies and the `foundation` block come with it). Find one:",
306
- "`npx @graph8/sdk list` / `search <text>`. Check setup: `npx @graph8/sdk doctor --blocks a,b`."
307
- );
308
- if (reg.stacks.some((s) => !stackStatus(reg, s).missing.length)) lines.push("Start a whole app from an available stack: `npx @graph8/sdk create <stack> <dir>` (Next.js + the stack's blocks wired into a sidebar).");
309
- return lines.join("\n");
310
- }
311
-
312
- // src/blocks/add.ts
313
- var ADD_USAGE = `Usage: npx @graph8/sdk add <block>[/<Component>] [options]
314
-
315
- Copy a graph8 block (components, hooks, recipes) into your app. The block's
316
- dependencies (always including \`foundation\`) are added first.
317
- --dir <path> project root (default: current directory)
318
- --target <path> where to copy, relative to --dir (default: components/g8)
319
- --dry-run print what would change, write and install nothing
320
- --force overwrite files that differ from the template
321
- --no-install do not install missing peer dependencies
322
- Existing files are never overwritten without --force. Installed blocks are
323
- recorded in graph8.json so re-runs do nothing.`;
324
- function parseAddArgs(argv) {
325
- const o = { spec: "", dir: ".", dryRun: false, force: false, install: true };
326
- for (let i = 0; i < argv.length; i++) {
327
- const arg = argv[i];
328
- const [flag, inline] = arg.startsWith("--") && arg.includes("=") ? [arg.slice(0, arg.indexOf("=")), arg.slice(arg.indexOf("=") + 1)] : [arg, void 0];
329
- const value = () => {
330
- const v = inline ?? argv[++i];
331
- if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
332
-
333
- ${ADD_USAGE}`);
334
- return v;
335
- };
336
- if (flag === "--dir") o.dir = value();
337
- else if (flag === "--target") o.target = value();
338
- else if (arg === "--dry-run") o.dryRun = true;
339
- else if (arg === "--force") o.force = true;
340
- else if (arg === "--no-install") o.install = false;
341
- else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
342
-
343
- ${ADD_USAGE}`);
344
- else if (!o.spec) o.spec = arg;
345
- else throw new Error(`unexpected argument ${arg}
346
-
347
- ${ADD_USAGE}`);
348
- }
349
- if (!o.spec) throw new Error(`add needs a block name
350
-
351
- ${ADD_USAGE}`);
352
- return o;
353
- }
354
- function detectPackageManager(root) {
355
- const has = (f) => import_node_fs2.default.existsSync(import_node_path2.default.join(root, f));
356
- if (has("pnpm-lock.yaml")) return "pnpm";
357
- if (has("yarn.lock")) return "yarn";
358
- if (has("bun.lockb") || has("bun.lock")) return "bun";
359
- return "npm";
360
- }
361
- function installCommand(pm, packages) {
362
- const verb = pm === "npm" ? "install" : "add";
363
- return { cmd: pm, args: [verb, ...packages] };
364
- }
365
- var sha = (s) => (0, import_node_crypto.createHash)("sha256").update(s).digest("hex");
366
- var posix = (p) => p.split(import_node_path2.default.sep).join("/");
166
+ var posix = (p) => p.split(import_node_path.default.sep).join("/");
367
167
  function installedRel(rel) {
368
168
  const r = posix(rel);
369
169
  if (r === "block.json") return null;
370
170
  return r.startsWith("components/") ? r.slice("components/".length) : r;
371
171
  }
372
- function appTargeted(block) {
373
- const m = /* @__PURE__ */ new Map();
374
- for (const f of block.server) if (f.target && !f.target.startsWith("g8/")) m.set(posix(f.file), f.target);
375
- return m;
376
- }
377
- function listFiles(dir, base = dir) {
378
- const out = [];
379
- for (const e of import_node_fs2.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
380
- const full = import_node_path2.default.join(dir, e.name);
381
- if (e.isDirectory()) out.push(...listFiles(full, base));
382
- else out.push(posix(import_node_path2.default.relative(base, full)));
383
- }
384
- return out;
385
- }
386
- var IMPORT_RE = /(\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)(["'])([^"'\n]+)\2/g;
387
- function relSpec(fromDir, toFile) {
388
- const noExt = toFile;
389
- let r = posix(import_node_path2.default.relative(fromDir, noExt));
390
- if (!r.startsWith(".")) r = `./${r}`;
391
- return r;
392
- }
393
- function g8AliasTarget(spec, ctx) {
394
- const [seg, ...rest] = spec.split("/");
395
- const reserved = ["components", "hooks", "lib", "server", "recipes"].includes(seg);
396
- const owner = rest.length && !reserved ? ctx.reg.blocks.find((b) => b.id === seg || b.dir === seg) : void 0;
397
- const block = owner ?? ctx.reg.blocks.find((b) => b.id === "foundation");
398
- const sub = owner ? rest.join("/") : spec;
399
- return import_node_path2.default.join(ctx.targetAbs, block?.dir ?? "_foundation", installedRel(sub) ?? sub);
400
- }
401
- function rewriteImports(content, ctx) {
402
- return content.replace(IMPORT_RE, (whole, pre, q, spec) => {
403
- if (spec.startsWith("@/g8/")) {
404
- return `${pre}${q}${relSpec(ctx.destDir, g8AliasTarget(spec.slice("@/g8/".length), ctx))}${q}`;
405
- }
406
- if (spec.startsWith(".") && ctx.block && ctx.srcFile) {
407
- const abs = import_node_path2.default.resolve(import_node_path2.default.dirname(ctx.srcFile), spec);
408
- const relInBlock = import_node_path2.default.relative(ctx.block.root, abs);
409
- if (relInBlock.startsWith("..")) return whole;
410
- const mapped = installedRel(relInBlock);
411
- if (mapped === null) return whole;
412
- return `${pre}${q}${relSpec(ctx.destDir, import_node_path2.default.join(ctx.targetAbs, ctx.block.dir, mapped))}${q}`;
413
- }
414
- return whole;
415
- });
416
- }
417
- function diffSummary(before, after) {
418
- const count = (s) => {
419
- const m = /* @__PURE__ */ new Map();
420
- for (const l of s.split("\n")) m.set(l, (m.get(l) ?? 0) + 1);
421
- return m;
422
- };
423
- const a = count(before);
424
- const b = count(after);
425
- let removed = 0;
426
- let added = 0;
427
- for (const [l, n] of a) removed += Math.max(0, n - (b.get(l) ?? 0));
428
- for (const [l, n] of b) added += Math.max(0, n - (a.get(l) ?? 0));
429
- return `+${added} -${removed} lines vs the template`;
430
- }
431
- function classify(abs, rel, content, force) {
432
- if (!import_node_fs2.default.existsSync(abs)) return { rel, content, action: "create" };
433
- const current = import_node_fs2.default.readFileSync(abs, "utf8");
434
- if (current === content) return { rel, content, action: "unchanged" };
435
- if (force) return { rel, content, action: "update", detail: diffSummary(current, content) };
436
- return { rel, content, action: "skip", detail: `exists and differs (${diffSummary(current, content)}); use --force to overwrite` };
437
- }
438
- function readPkg(root) {
439
- try {
440
- return JSON.parse(import_node_fs2.default.readFileSync(import_node_path2.default.join(root, "package.json"), "utf8"));
441
- } catch {
442
- return null;
443
- }
444
- }
445
- function missingPeers(blocks, pkg) {
446
- const have = /* @__PURE__ */ new Set([
447
- ...Object.keys(pkg.dependencies ?? {}),
448
- ...Object.keys(pkg.devDependencies ?? {}),
449
- ...Object.keys(pkg.peerDependencies ?? {}),
450
- ...Object.keys(pkg.optionalDependencies ?? {})
451
- ]);
452
- const want = /* @__PURE__ */ new Map();
453
- for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) if (!want.has(name)) want.set(name, range);
454
- return [...want].filter(([name]) => !have.has(name)).map(([name, range]) => `${name}@${range}`);
455
- }
456
- function parseSpec(spec) {
457
- const [blockId, component, ...extra] = spec.split("/");
458
- if (!blockId || extra.length) throw new Error(`invalid block "${spec}": use <block> or <block>/<Component>`);
459
- return { blockId, component: component || void 0 };
460
- }
461
- function planBlock(block, only, reg, targetAbs, root, force) {
462
- const skipComponentFiles = /* @__PURE__ */ new Set();
463
- if (only) {
464
- for (const c of block.components) if (c.name !== only) skipComponentFiles.add(posix(c.file));
465
- const byStem = new Map(block.components.map((c) => [posix(c.file).replace(/\.[tj]sx?$/, ""), posix(c.file)]));
466
- const start = block.components.find((c) => c.name === only);
467
- const queue = start ? [posix(start.file)] : [];
468
- while (queue.length) {
469
- const file = queue.pop();
470
- const text = import_node_fs2.default.readFileSync(import_node_path2.default.join(block.root, file), "utf8");
471
- for (const m of text.matchAll(IMPORT_RE)) {
472
- if (!m[3].startsWith(".")) continue;
473
- const stem = posix(import_node_path2.default.relative(block.root, import_node_path2.default.resolve(block.root, import_node_path2.default.dirname(file), m[3]))).replace(/\.[tj]sx?$/, "");
474
- const sibling = byStem.get(stem);
475
- if (sibling && skipComponentFiles.delete(sibling)) queue.push(sibling);
476
- }
477
- }
478
- }
479
- const files = [];
480
- const toApp = appTargeted(block);
481
- for (const rel of listFiles(block.root)) {
482
- if (skipComponentFiles.has(rel) || toApp.has(rel)) continue;
483
- const mapped = installedRel(rel);
484
- if (mapped === null) continue;
485
- const destAbs = import_node_path2.default.join(targetAbs, block.dir, mapped);
486
- const src = import_node_path2.default.join(block.root, rel);
487
- const text = import_node_fs2.default.readFileSync(src, "utf8");
488
- const content = rewriteImports(text, { reg, targetAbs, block, srcFile: src, destDir: import_node_path2.default.dirname(destAbs) });
489
- files.push(classify(destAbs, posix(import_node_path2.default.relative(root, destAbs)), content, force));
490
- }
491
- return files;
492
- }
493
- var PROXY_CONTRACT = [
494
- "This is not a Next.js project, so the proxy route was not written. Your server must provide the same contract:",
495
- " - handle every method on /api/g8/* (the browser calls these paths)",
496
- " - forward to ${G8_API_URL:-https://be.graph8.com}/api/v1/<rest of path>, same method, query and body",
497
- " - add the header `Authorization: Bearer $G8_API_KEY` on the server; never send the key to the browser",
498
- " - return the upstream status and JSON body unchanged"
499
- ];
500
- function writeFile(root, f) {
501
- const abs = import_node_path2.default.join(root, f.rel);
502
- import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(abs), { recursive: true });
503
- import_node_fs2.default.writeFileSync(abs, f.content, "utf8");
504
- }
505
- function readLedger(file) {
506
- try {
507
- const j = JSON.parse(import_node_fs2.default.readFileSync(file, "utf8"));
508
- return j && typeof j === "object" && j.blocks ? j : null;
509
- } catch {
510
- return null;
511
- }
512
- }
513
- function ledgerContent(prev, target, entries, only) {
514
- const blocks = { ...prev?.blocks ?? {} };
515
- for (const [id, files] of entries) {
516
- const old = blocks[id];
517
- const recorded = { ...old?.files ?? {} };
518
- for (const f of files) {
519
- if (f.action === "skip") continue;
520
- recorded[f.rel] = sha(f.content);
521
- }
522
- const comp = only.get(id);
523
- let components;
524
- if (!comp) components = "all";
525
- else if (old?.components === "all") components = "all";
526
- else components = [.../* @__PURE__ */ new Set([...old?.components ?? [], comp])].sort();
527
- blocks[id] = { files: Object.fromEntries(Object.entries(recorded).sort(([a], [b]) => a.localeCompare(b))), components };
528
- }
529
- const sorted = Object.fromEntries(Object.entries(blocks).sort(([a], [b]) => a.localeCompare(b)));
530
- const stack = prev?.stack ? { stack: prev.stack } : {};
531
- return `${JSON.stringify({ sdkVersion: SDK_VERSION, target, ...stack, blocks: sorted }, null, 2)}
532
- `;
533
- }
534
- function ensureEnvExample(root, dryRun, out) {
535
- const file = import_node_path2.default.join(root, ".env.local.example");
536
- const current = import_node_fs2.default.existsSync(file) ? import_node_fs2.default.readFileSync(file, "utf8") : null;
537
- if (current !== null && /^G8_API_KEY=/m.test(current)) {
538
- out("keep .env.local.example (G8_API_KEY already listed)");
539
- return;
540
- }
541
- out(`${dryRun ? "would " : ""}${current === null ? "create" : "update"} .env.local.example (+ G8_API_KEY=)`);
542
- if (dryRun) return;
543
- const base = current === null ? "" : current.endsWith("\n") || current === "" ? current : `${current}
544
- `;
545
- import_node_fs2.default.writeFileSync(file, `${base}G8_API_KEY=
546
- `, "utf8");
547
- }
548
- function runAdd(opts, reg, io, cwd) {
549
- const root = import_node_path2.default.resolve(cwd, opts.dir);
550
- const pkg = readPkg(root);
551
- if (!pkg) {
552
- io.err(`no package.json in ${root}. Run this from your app's root, or pass --dir.`);
553
- return 1;
554
- }
555
- let parsed;
556
- let blocks;
557
- try {
558
- parsed = parseSpec(opts.spec);
559
- blocks = resolveWithDeps(reg, [parsed.blockId]);
560
- const main2 = blocks[blocks.length - 1];
561
- if (parsed.component && !main2.components.some((c) => c.name === parsed.component)) {
562
- const names = main2.components.map((c) => c.name).join(", ") || "none";
563
- throw new Error(`block "${main2.id}" has no component "${parsed.component}" (available: ${names})`);
564
- }
565
- } catch (e) {
566
- io.err(e.message);
567
- return 1;
568
- }
569
- const exists = (p) => import_node_fs2.default.existsSync(import_node_path2.default.join(root, p));
570
- const target = opts.target ?? (exists("src") && !exists("components") && !exists("app") ? "src/components/g8" : "components/g8");
571
- const targetAbs = import_node_path2.default.resolve(root, target);
572
- const isNext = Boolean(pkg.dependencies?.next ?? pkg.devDependencies?.next);
573
- const dry = opts.dryRun;
574
- const verb = (a) => a === "unchanged" ? "keep" : a === "skip" ? "skip" : dry ? `would ${a}` : a;
575
- const entries = /* @__PURE__ */ new Map();
576
- const only = /* @__PURE__ */ new Map();
577
- const main = blocks[blocks.length - 1];
578
- for (const b of blocks) {
579
- const comp = b === main ? parsed.component : void 0;
580
- only.set(b.id, comp);
581
- entries.set(b.id, planBlock(b, comp, reg, targetAbs, root, opts.force));
582
- }
583
- const all = [...entries.values()].flat();
584
- const route = planAppFiles(blocks, reg, targetAbs, root, isNext, opts.force);
585
- io.out(`${dry ? "Dry run: " : ""}adding ${blocks.map((b) => b.id).join(" + ")} to ${target}`);
586
- for (const f of [...all, ...route.files]) io.out(`${verb(f.action)} ${f.rel}${f.detail ? ` (${f.detail})` : ""}`);
587
- if (route.note) route.note.forEach((l) => io.out(l));
588
- if (!dry) {
589
- for (const f of [...all, ...route.files]) if (f.action === "create" || f.action === "update") writeFile(root, f);
590
- }
591
- ensureEnvExample(root, dry, io.out);
592
- const ledgerPath = import_node_path2.default.join(root, "graph8.json");
593
- const next = ledgerContent(readLedger(ledgerPath), posix(import_node_path2.default.relative(root, targetAbs)), entries, only);
594
- const prevText = import_node_fs2.default.existsSync(ledgerPath) ? import_node_fs2.default.readFileSync(ledgerPath, "utf8") : null;
595
- if (next !== prevText) {
596
- io.out(`${dry ? "would " : ""}${prevText === null ? "create" : "update"} graph8.json`);
597
- if (!dry) import_node_fs2.default.writeFileSync(ledgerPath, next, "utf8");
598
- } else io.out("keep graph8.json");
599
- return installPeers(blocks, pkg, root, opts, io);
600
- }
601
- function installPeers(blocks, pkg, root, opts, io) {
602
- const missing = missingPeers(blocks, pkg);
603
- if (!missing.length) return 0;
604
- const pm = detectPackageManager(root);
605
- const { cmd, args } = installCommand(pm, missing);
606
- const shown = `${cmd} ${args.join(" ")}`;
607
- if (!opts.install || opts.dryRun) {
608
- io.out(`${opts.dryRun ? "would run" : "not installing (--no-install). Run"}: ${shown}`);
609
- return 0;
610
- }
611
- io.out(`installing peer dependencies: ${shown}`);
612
- const code = io.spawn(cmd, args, root);
613
- if (code !== 0) {
614
- io.err(`peer dependency install failed (exit ${code}). Run it yourself: ${shown}`);
615
- return 1;
616
- }
617
- return 0;
618
- }
619
- function appTargetPath(root, target) {
620
- const useSrc = import_node_fs2.default.existsSync(import_node_path2.default.join(root, "src", "app")) && !import_node_fs2.default.existsSync(import_node_path2.default.join(root, "app"));
621
- return useSrc && target.startsWith("app/") ? `src/${target}` : target;
622
- }
623
- function planAppFiles(blocks, reg, targetAbs, root, isNext, force) {
624
- const files = [];
625
- let skipped = false;
626
- for (const b of blocks) {
627
- for (const [file, target] of appTargeted(b)) {
628
- if (!isNext) {
629
- skipped = true;
630
- continue;
631
- }
632
- const destAbs = import_node_path2.default.join(root, appTargetPath(root, target));
633
- const src = import_node_path2.default.join(b.root, file);
634
- const content = rewriteImports(import_node_fs2.default.readFileSync(src, "utf8"), { reg, targetAbs, destDir: import_node_path2.default.dirname(destAbs) });
635
- files.push(classify(destAbs, posix(import_node_path2.default.relative(root, destAbs)), content, force));
636
- }
637
- }
638
- return { files, note: skipped ? PROXY_CONTRACT : void 0 };
639
- }
640
-
641
- // src/blocks/create.ts
642
- var import_node_fs3 = __toESM(require("fs"));
643
- var import_node_path3 = __toESM(require("path"));
644
- var CREATE_USAGE = `Usage: npx @graph8/sdk create <stack> <dir> [options]
645
-
646
- Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
647
- install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
648
- --package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm
649
- --no-install write the files and blocks, do not install dependencies
650
- --dry-run print what would be written, write and install nothing
651
- --force allow a non-empty <dir> (template files are overwritten, and graph8.json
652
- is reset: blocks you added to that app earlier are no longer recorded)
653
- Then: set G8_API_KEY in <dir>/.env.local and run \`npx @graph8/sdk doctor --stack <stack>\`.`;
654
- var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
655
- function parseCreateArgs(argv) {
656
- const o = { stack: "", dir: "", install: true, dryRun: false, force: false };
657
- const positional = [];
658
- for (let i = 0; i < argv.length; i++) {
659
- const arg = argv[i];
660
- const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
661
- const [flag, inline] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [arg, void 0];
662
- if (flag === "--package-manager") {
663
- const v = inline ?? argv[++i];
664
- if (!PACKAGE_MANAGERS.includes(v)) throw new Error(`--package-manager must be one of ${PACKAGE_MANAGERS.join(", ")}
665
-
666
- ${CREATE_USAGE}`);
667
- o.packageManager = v;
668
- } else if (arg === "--no-install") o.install = false;
669
- else if (arg === "--dry-run") o.dryRun = true;
670
- else if (arg === "--force") o.force = true;
671
- else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
672
-
673
- ${CREATE_USAGE}`);
674
- else positional.push(arg);
675
- }
676
- if (positional.length !== 2) throw new Error(`create needs a stack and a directory
677
-
678
- ${CREATE_USAGE}`);
679
- [o.stack, o.dir] = positional;
680
- return o;
681
- }
682
- function packageManagerFromUserAgent(ua) {
683
- const name = /^(npm|pnpm|yarn|bun)\//.exec(ua ?? "")?.[1];
684
- return name ?? "npm";
685
- }
686
- function appNameFor(dir) {
687
- const base = import_node_path3.default.basename(import_node_path3.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
688
- return base || "g8-app";
689
- }
690
- function render(template, vars) {
691
- return template.replace(/\{\{(\w+)\}\}/g, (_m, key) => {
692
- if (!(key in vars)) throw new Error(`template placeholder {{${key}}} has no value`);
693
- return vars[key];
694
- });
695
- }
696
- var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
697
- var posix2 = (p) => p.split(import_node_path3.default.sep).join("/");
698
- function walk(dir, base = dir) {
699
- const out = [];
700
- for (const e of import_node_fs3.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
701
- const full = import_node_path3.default.join(dir, e.name);
702
- if (e.isDirectory()) out.push(...walk(full, base));
703
- else out.push(posix2(import_node_path3.default.relative(base, full)));
704
- }
705
- return out;
706
- }
707
- function outputName(rel) {
708
- const noExt = rel.replace(/\.tmpl$/, "");
709
- const dir = import_node_path3.default.posix.dirname(noExt);
710
- const base = import_node_path3.default.posix.basename(noExt).replace(/^dot-/, ".");
711
- return dir === "." ? base : `${dir}/${base}`;
712
- }
713
- function pageFileFor(navPath) {
714
- const segs = navPath.split("/").filter(Boolean);
715
- return ["app", ...segs, "page.tsx"].join("/");
716
- }
717
- function componentImportPath(block, file) {
718
- const rel = installedRel(file) ?? file;
719
- return `@/components/g8/${block.dir}/${rel.replace(/\.(tsx|ts|jsx|js)$/, "")}`;
720
- }
721
- function stackProblem(reg, stack) {
722
- const { missing } = stackStatus(reg, stack);
723
- if (missing.length) {
724
- const ready = reg.stacks.filter((s) => !stackStatus(reg, s).missing.length).map((s) => s.id);
725
- return [
726
- `stack "${stack.id}" is planned: these blocks are not in this version of @graph8/sdk yet: ${missing.join(", ")}.`,
727
- `Stacks you can create now: ${ready.join(", ") || "none"}. Or create one of those and add blocks as they ship (\`graph8-sdk add <block>\`).`
728
- ].join("\n");
729
- }
730
- const seen = /* @__PURE__ */ new Set();
731
- const homeIssue = homeProblem(stack);
732
- if (homeIssue) return homeIssue;
733
- for (const n of stack.nav) {
734
- const block = findBlock(reg, n.block);
735
- if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
736
- 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.`;
737
- if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
738
- if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
739
- seen.add(n.path);
740
- if (n.page && (!reg.stacksRoot || !import_node_fs3.default.existsSync(import_node_path3.default.join(reg.stacksRoot, stack.id, n.page)))) {
741
- return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
742
- }
743
- }
744
- return null;
745
- }
746
- function homeProblem(stack) {
747
- if (!stack.home) return null;
748
- 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.`;
749
- 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.`;
750
- return null;
751
- }
752
- function stackPageFile(reg, stack, n) {
753
- return n.page && reg.stacksRoot ? import_node_path3.default.join(reg.stacksRoot, stack.id, n.page) : null;
754
- }
755
- function homeRedirect(stack) {
756
- if (stack.home) return stack.home === "/" ? null : stack.home;
757
- return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
758
- }
759
- function renderPages(reg, stack, templates) {
760
- const pageTpl = import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
761
- const files = stack.nav.map((n) => {
762
- const block = findBlock(reg, n.block);
763
- const comp = block.components.find((c) => c.name === n.component);
764
- const custom = stackPageFile(reg, stack, n);
765
- const vars = {
766
- stackId: stack.id,
767
- block: block.id,
768
- blockDir: `components/g8/${block.dir}`,
769
- component: n.component,
770
- importPath: componentImportPath(block, comp.file),
771
- propsJson: JSON.stringify(n.props ?? {}, null, 2)
772
- };
773
- return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs3.default.readFileSync(custom, "utf8") : pageTpl, vars) };
774
- });
775
- const to = homeRedirect(stack);
776
- if (to) {
777
- const redirect = import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
778
- files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
779
- }
780
- return files;
781
- }
782
- function navUsesExtras(stack) {
783
- return stack.nav.some((n) => n.icon || n.group || n.description);
784
- }
785
- function navParts(stack) {
786
- if (!navUsesExtras(stack)) {
787
- return { navImports: "", navType: "{ label: string; path: string }[]", navJson: JSON.stringify(stack.nav.map((n) => ({ label: n.label, path: n.path })), null, 2) };
788
- }
789
- const icons = [...new Set(stack.nav.flatMap((n) => n.icon ? [n.icon] : []))].sort();
790
- const named = [...icons, "type LucideIcon"].join(", ");
791
- const items = stack.nav.map((n) => ({
792
- label: n.label,
793
- path: n.path,
794
- ...n.icon ? { icon: `__ICON__${n.icon}` } : {},
795
- ...n.group ? { group: n.group } : {},
796
- ...n.description ? { description: n.description } : {}
797
- }));
798
- return {
799
- navImports: [
800
- `import { ${named} } from "lucide-react";`,
801
- "",
802
- "export type NavItem = { label: string; path: string; icon?: LucideIcon; group?: string; description?: string };",
803
- "",
804
- ""
805
- ].join("\n"),
806
- navType: "NavItem[]",
807
- navJson: JSON.stringify(items, null, 2).replace(/"__ICON__(\w+)"/g, "$1")
808
- };
809
- }
810
- function peerDependenciesFor(blocks, io, stack) {
811
- const peers = {};
812
- for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
813
- const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(import_node_fs3.default.readFileSync(import_node_path3.default.join(b.root, f), "utf8"))));
814
- if (usesUi && io.sdkPackageJson && import_node_fs3.default.existsSync(io.sdkPackageJson)) {
815
- const sdk = JSON.parse(import_node_fs3.default.readFileSync(io.sdkPackageJson, "utf8"));
816
- for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
817
- }
818
- if (navUsesExtras(stack)) peers["lucide-react"] ??= lucideRange(io);
819
- return peers;
820
- }
821
- function lucideRange(io) {
822
- try {
823
- const sdk = JSON.parse(import_node_fs3.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
824
- return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
825
- } catch {
826
- return ">=0.400.0";
827
- }
828
- }
829
- function mergedPackageJson(rendered, peers) {
830
- const pkg = JSON.parse(rendered);
831
- for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
832
- pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
833
- return `${JSON.stringify(pkg, null, 2)}
834
- `;
835
- }
836
- function planCreate(reg, stack, appName, pm, io) {
837
- const templates = import_node_path3.default.join(io.templatesRoot, "next-app");
838
- const blocks = resolveWithDeps(reg, stack.blocks);
839
- const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
840
- const vars = {
841
- appName,
842
- sdkVersion: SDK_VERSION,
843
- stackId: stack.id,
844
- title: stack.title,
845
- titleJson: JSON.stringify(stack.title),
846
- ...navParts(stack),
847
- devCommand,
848
- 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>`)"
849
- };
850
- const files = [];
851
- for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
852
- let content = render(import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, rel), "utf8"), vars);
853
- if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
854
- files.push({ rel: outputName(rel), content });
855
- }
856
- files.push(...renderPages(reg, stack, templates));
857
- const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, blocks: {} };
858
- files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
859
- ` });
860
- return files;
861
- }
862
- function dirProblem(abs, force) {
863
- if (!import_node_fs3.default.existsSync(abs)) return null;
864
- if (!import_node_fs3.default.statSync(abs).isDirectory()) return `${abs} exists and is not a directory.`;
865
- if (force || import_node_fs3.default.readdirSync(abs).length === 0) return null;
866
- return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
867
- }
868
- function runCreate(opts, reg, io, cwd) {
869
- const stack = reg.stacks.find((s) => s.id === opts.stack);
870
- if (!stack) {
871
- io.err(`unknown stack "${opts.stack}". Stacks: ${reg.stacks.map((s) => s.id).join(", ") || "none"} (\`graph8-sdk list\`).`);
872
- return 1;
873
- }
874
- const problem = stackProblem(reg, stack);
875
- if (problem) return io.err(problem), 1;
876
- const abs = import_node_path3.default.resolve(cwd, opts.dir);
877
- const dirIssue = dirProblem(abs, opts.force);
878
- if (dirIssue) return io.err(dirIssue), 1;
879
- const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
880
- let files;
881
- try {
882
- files = planCreate(reg, stack, appNameFor(opts.dir), pm, io);
883
- } catch (e) {
884
- io.err(e.message);
885
- return 1;
886
- }
887
- const blocks = resolveWithDeps(reg, stack.blocks);
888
- io.out(`${opts.dryRun ? "Dry run: " : ""}creating "${stack.title}" (${stack.id}) in ${opts.dir}`);
889
- for (const f of files) io.out(`${opts.dryRun ? "would write" : "write"} ${f.rel}`);
890
- if (opts.dryRun) {
891
- io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
892
- io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
893
- return 0;
894
- }
895
- for (const f of files) {
896
- const dest = import_node_path3.default.join(abs, f.rel);
897
- import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(dest), { recursive: true });
898
- import_node_fs3.default.writeFileSync(dest, f.content, "utf8");
899
- }
900
- return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
901
- }
902
- function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
903
- for (const b of blocks) {
904
- const code = runAdd({ spec: b.id, dir: abs, target: "components/g8", dryRun: false, force: opts.force, install: false }, reg, io, cwd);
905
- if (code !== 0) {
906
- io.err(`adding block "${b.id}" failed; the app in ${opts.dir} is incomplete.`);
907
- return code;
908
- }
909
- }
910
- if (opts.install) {
911
- io.out(`installing dependencies: ${pm} install`);
912
- const code = io.spawn(pm, ["install"], abs);
913
- if (code !== 0) {
914
- io.err(`dependency install failed (exit ${code}). Run it yourself: cd ${quoted(opts.dir)} && ${pm} install`);
915
- return 1;
916
- }
917
- }
918
- const cd = opts.dir === "." ? "" : `cd ${quoted(opts.dir)} && `;
919
- io.out(
920
- [
921
- "",
922
- `Done. Next:`,
923
- ` 1. set G8_API_KEY in ${import_node_path3.default.posix.join(opts.dir, ".env.local")} (copy .env.local.example)`,
924
- ` 2. ${cd}npx @graph8/sdk doctor --stack ${stack.id}`,
925
- ` 3. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`
926
- ].join("\n")
927
- );
928
- return 0;
929
- }
930
-
931
- // src/blocks/doctor.ts
932
- var import_node_fs5 = __toESM(require("fs"));
933
- var import_node_path5 = __toESM(require("path"));
934
-
935
- // src/generated/billable.ts
936
- var BILLABLE_ROUTES = {
937
- GET: [
938
- "/apps/{app_id}/runtime",
939
- "/audience-syncs/{config_id}/runs",
940
- "/campaigns/{campaign_id}/dashboard",
941
- "/companies/{company_id}/next-best-step",
942
- "/contacts/{contact_id}/next-best-step",
943
- "/desk/kickoff",
944
- "/desk/kickoff/audio",
945
- "/radar/competitors/{competitor_id}/discover-pages",
946
- "/repos/{repo_id}/scan",
947
- "/seo/domains/{domain}/backlinks",
948
- "/seo/domains/{domain}/overview",
949
- "/voice/campaigns/call-scripts",
950
- "/voice/campaigns/email-templates",
951
- "/voice/campaigns/templates",
952
- "/voice/research/business-data",
953
- "/voice/research/competitors",
954
- "/voice/research/faq",
955
- "/voice/research/keywords",
956
- "/voice/research/prospect-company",
957
- "/work/routines/{routine_id}/runs"
958
- ],
959
- PATCH: [
960
- "/enrichment/auto-enrichment/configs/{config_id}",
961
- "/intent/keywords/{keyword_id}",
962
- "/intent/keywords/{keyword_id}/sync-config",
963
- "/marketplace/agencies/{slug}",
964
- "/marketplace/agencies/{slug}/reps/{sdr_id}",
965
- "/marketplace/agencies/{slug}/service-statements/{statement_id}",
966
- "/marketplace/applications/{application_id}",
967
- "/marketplace/conversations/{conversation_id}/state",
968
- "/marketplace/job-posts/{post_id}",
969
- "/marketplace/me/meetings/{meeting_id}",
970
- "/marketplace/me/messages/{conversation_id}/state",
971
- "/marketplace/me/profile",
972
- "/marketplace/me/service-statements/{statement_id}",
973
- "/marketplace/org/meetings/{meeting_id}",
974
- "/radar/competitors/{competitor_id}",
975
- "/radar/gaps/{gap_id}",
976
- "/research-reports/{report_id}",
977
- "/social-listeners/settings",
978
- "/social-listeners/{listener_id}"
979
- ],
980
- POST: [
981
- "/accounts/{account_id}/generate-outreach-plan",
982
- "/accounts/{account_id}/security-questionnaires",
983
- "/accounts/{account_id}/stakeholders/{stakeholder_id}/generate-outreach",
984
- "/ad-creatives",
985
- "/ad-creatives/batch",
986
- "/ad-creatives/concepts",
987
- "/ad-creatives/video/clips",
988
- "/ad-creatives/video/clips/prompt-enhance",
989
- "/ad-creatives/video/clips/{clip_id}/continue",
990
- "/ad-creatives/video/clips/{clip_id}/extend",
991
- "/ad-creatives/video/clips/{clip_id}/regenerate",
992
- "/ad-creatives/{creative_id}/ai-review",
993
- "/ad-creatives/{creative_id}/chat-edit",
994
- "/ad-creatives/{creative_id}/image-edit-at-point",
995
- "/ad-creatives/{creative_id}/persona-lens",
996
- "/ad-creatives/{creative_id}/regenerate-copy",
997
- "/ad-creatives/{creative_id}/regenerate-image",
998
- "/ad-creatives/{creative_id}/retry",
999
- "/ad-creatives/{creative_id}/rewrite-field",
1000
- "/ad-creatives/{creative_id}/size-set",
1001
- "/agent/chat",
1002
- "/agent/onboarding/discovery/turn",
1003
- "/agent/operators/{operator}/approvals/{task_id}/decide",
1004
- "/agent/sessions/{session_id}/voice",
1005
- "/agent/work-items",
1006
- "/app-pages/{page_id}/generate",
1007
- "/campaigns/generate-ideas",
1008
- "/campaigns/generate-ideas-async",
1009
- "/campaigns/recommend-personas",
1010
- "/campaigns/{campaign_id}/audience-pipeline/accept-filters",
1011
- "/campaigns/{campaign_id}/audience-pipeline/trigger",
1012
- "/campaigns/{campaign_id}/documents/{document_id}/regenerate",
1013
- "/campaigns/{campaign_id}/sequence/steps/{step_id}/generate",
1014
- "/companies/enrich/bulk",
1015
- "/companies/enrich/people/bulk",
1016
- "/companies/enrich/stakeholders/bulk",
1017
- "/companies/import",
1018
- "/companies/{company_id}/documents/types/{doc_type}/generate",
1019
- "/companies/{company_id}/documents/types/{doc_type}/regenerate",
1020
- "/companies/{company_id}/enrich",
1021
- "/companies/{company_id}/retry-intelligence",
1022
- "/companies/{company_id}/retry-synthesis",
1023
- "/companies/{company_id}/workflows/{workflow_type}/start",
1024
- "/company-profile/generate",
1025
- "/company-profile/reindex",
1026
- "/company-profile/retry",
1027
- "/contacts/unlock-info",
1028
- "/contacts/verify-email",
1029
- "/contacts/verify-email/async",
1030
- "/contacts/workflow-executions/run",
1031
- "/contacts/{contact_id}/documents/generate-all",
1032
- "/contacts/{contact_id}/documents/types/{doc_type}/generate",
1033
- "/contacts/{contact_id}/documents/types/{doc_type}/regenerate",
1034
- "/content-workspace/import/crawl",
1035
- "/content/calendar/ideas/{idea_id}/create-content",
1036
- "/content/calendar/mine-signals",
1037
- "/content/calendar/repurpose/ad",
1038
- "/content/calendar/repurpose/linkedin-post",
1039
- "/content/grids/{grid_id}/columns/{column_id}/run",
1040
- "/content/grids/{grid_id}/rows/{row_id}/run/{column_id}",
1041
- "/content/pages/enrich-seo-bulk",
1042
- "/content/pages/{page_id}/enrich-seo",
1043
- "/copilot/chat",
1044
- "/copilot/landing-page-action",
1045
- "/copilot/memory/promote",
1046
- "/customer-success/companies/{company_id}/brief/synthesize",
1047
- "/deals/{deal_id}/documents/generate-all",
1048
- "/deals/{deal_id}/documents/types/{doc_type}/generate",
1049
- "/deals/{deal_id}/documents/types/{doc_type}/regenerate",
1050
- "/deals/{deal_id}/next-best-step",
1051
- "/desk/accounts/{account_id}/committee/{contact_id}/find-linkedin",
1052
- "/desk/accounts/{account_id}/plan/draft",
1053
- "/desk/ask",
1054
- "/desk/decisions/{decision_id}/replan",
1055
- "/desk/email-improve",
1056
- "/desk/email-score",
1057
- "/desk/org-settings/plan-stages/run",
1058
- "/desk/voice-memo",
1059
- "/dialer/parallel-calls/grading/backfill",
1060
- "/dialer/sessions/prewarm-voicemails",
1061
- "/enrichment/ai-formula/generate",
1062
- "/enrichment/ai-formula/generate-condition",
1063
- "/enrichment/ai/batch/enrich",
1064
- "/enrichment/ai/enrich",
1065
- "/enrichment/ai/qualification/generate-templates",
1066
- "/enrichment/ai/qualification/run",
1067
- "/enrichment/auto-enrichment/configs",
1068
- "/enrichment/enrich",
1069
- "/enrichment/find-people/configure",
1070
- "/enrichment/find-people/search-import",
1071
- "/enrichment/find-people/{config_id}/import",
1072
- "/enrichment/find-people/{config_id}/preview",
1073
- "/enrichment/find-people/{config_id}/run",
1074
- "/enrichment/generate-prompt",
1075
- "/enrichment/jobs/{job_id}/resume",
1076
- "/enrichment/list-routing/generate-filter",
1077
- "/enrichment/lists/{list_id}/pipelines",
1078
- "/enrichment/lists/{list_id}/pipelines/ai-draft",
1079
- "/enrichment/lists/{list_id}/pipelines/{pipeline_id}/enabled",
1080
- "/enrichment/lists/{list_id}/pipelines/{pipeline_id}/runs",
1081
- "/enrichment/lookup/company",
1082
- "/enrichment/lookup/person",
1083
- "/enrichment/person",
1084
- "/enrichment/record-sync/{entity_type}/run",
1085
- "/enrichment/verify-email",
1086
- "/enrichment/waterfall/configs",
1087
- "/enrichment/waterfall/enrich",
1088
- "/global-context/generate-all",
1089
- "/global-context/generate/{doc_type}",
1090
- "/hiring-signals/aggregators",
1091
- "/hiring-signals/companies",
1092
- "/hiring-signals/companies/job-counts",
1093
- "/hiring-signals/companies/resolve-ids",
1094
- "/hiring-signals/decision-makers",
1095
- "/hiring-signals/job-changes/match",
1096
- "/hiring-signals/jobs",
1097
- "/hiring-signals/recruiters",
1098
- "/icps/generate",
1099
- "/inbox/drafts/generate",
1100
- "/intelligence/analyze",
1101
- "/intelligence/retry",
1102
- "/intent/contacts/metadata",
1103
- "/intent/keywords/add",
1104
- "/intent/keywords/auto-seed-proposals",
1105
- "/intent/keywords/create-from-domain",
1106
- "/intent/keywords/create-from-search",
1107
- "/intent/keywords/list",
1108
- "/intent/keywords/{keyword_id}/companies",
1109
- "/intent/keywords/{keyword_id}/contacts",
1110
- "/intent/keywords/{keyword_id}/filters",
1111
- "/intent/keywords/{keyword_id}/job-resolution",
1112
- "/intent/keywords/{keyword_id}/sync",
1113
- "/intent/keywords/{keyword_id}/urls",
1114
- "/intent/pages-by-domain",
1115
- "/intent/pages/contacts",
1116
- "/intent/pages/search",
1117
- "/intent/pages/visitor-counts",
1118
- "/intent/pages/visitors",
1119
- "/landing-pages",
1120
- "/landing-pages/clone-homepage",
1121
- "/landing-pages/clone-html",
1122
- "/landing-pages/clone-url",
1123
- "/landing-pages/components/extract",
1124
- "/landing-pages/ensure-base-template",
1125
- "/landing-pages/{landing_page_id}/ai-review",
1126
- "/landing-pages/{landing_page_id}/ai-review/fix-with-ai",
1127
- "/landing-pages/{landing_page_id}/edit",
1128
- "/landing-pages/{landing_page_id}/edit-element",
1129
- "/landing-pages/{landing_page_id}/edit-page",
1130
- "/landing-pages/{landing_page_id}/generate-seo-metadata",
1131
- "/landing-pages/{landing_page_id}/rewrite-text",
1132
- "/leads/generate-content/batches",
1133
- "/leads/{lead_id}/generate-content",
1134
- "/library/case-studies/backfill",
1135
- "/lifecycle-contacts/unlock",
1136
- "/mailboxes/schedule-buy/plans",
1137
- "/mailboxes/schedule-buy/plans/{plan_id}/resume",
1138
- "/mailboxes/uncancel",
1139
- "/mailboxes/{mailbox_id}/health/placement-test",
1140
- "/marketplace/agencies/{slug}/intake/upload",
1141
- "/marketplace/agencies/{slug}/reps/{sdr_id}/unlist",
1142
- "/marketplace/agencies/{slug}/reps/{sdr_id}/video-intro",
1143
- "/marketplace/agencies/{slug}/service-statements",
1144
- "/marketplace/agencies/{slug}/service-statements/{statement_id}/line-items",
1145
- "/marketplace/agencies/{slug}/service-statements/{statement_id}/submit",
1146
- "/marketplace/commissions/entries",
1147
- "/marketplace/commissions/entries/{entry_id}/quality",
1148
- "/marketplace/commissions/entries/{entry_id}/reversals",
1149
- "/marketplace/commissions/entries/{entry_id}/statement",
1150
- "/marketplace/commissions/plans",
1151
- "/marketplace/commissions/plans/{version_id}/activate",
1152
- "/marketplace/commissions/preview",
1153
- "/marketplace/conversations/{conversation_id}/read",
1154
- "/marketplace/job-posts",
1155
- "/marketplace/job-posts/{post_id}/jd",
1156
- "/marketplace/me/headshot",
1157
- "/marketplace/me/invoices",
1158
- "/marketplace/me/invoices/{invoice_id}/approve",
1159
- "/marketplace/me/messages/{conversation_id}/read",
1160
- "/marketplace/me/offers/{hiring_id}/accept",
1161
- "/marketplace/me/offers/{hiring_id}/reject",
1162
- "/marketplace/me/payout-account/w8-certify",
1163
- "/marketplace/me/resume",
1164
- "/marketplace/me/service-statements",
1165
- "/marketplace/me/service-statements/{statement_id}/line-items",
1166
- "/marketplace/me/service-statements/{statement_id}/revise",
1167
- "/marketplace/me/service-statements/{statement_id}/submit",
1168
- "/marketplace/me/video-intro",
1169
- "/marketplace/me/work-certificates",
1170
- "/marketplace/opportunities/{post_id}/apply",
1171
- "/marketplace/org/hirings/{hiring_id}/meetings",
1172
- "/marketplace/org/hirings/{hiring_id}/terminate",
1173
- "/marketplace/org/hirings/{hiring_id}/withdraw",
1174
- "/marketplace/org/invoices/{invoice_id}/approve",
1175
- "/marketplace/org/invoices/{invoice_id}/dispute",
1176
- "/marketplace/org/service-statements/{statement_id}/approve",
1177
- "/marketplace/org/service-statements/{statement_id}/line-items",
1178
- "/marketplace/org/service-statements/{statement_id}/reject",
1179
- "/marketplace/org/service-statements/{statement_id}/request-changes",
1180
- "/marketplace/shortlist/export",
1181
- "/marketplace/talent/{sdr_id}/hire",
1182
- "/marketplace/talent/{sdr_id}/match/analyze",
1183
- "/marketplace/talent/{sdr_id}/match/refresh",
1184
- "/marketplace/terms-acknowledgements/ack-all",
1185
- "/meeting-transcripts/{transcript_id}/follow-up-draft",
1186
- "/meeting-transcripts/{transcript_id}/review/regenerate",
1187
- "/newsletters/templates/clone",
1188
- "/newsletters/templates/custom",
1189
- "/newsletters/templates/layout",
1190
- "/newsletters/{newsletter_id}/issues/{issue_id}/generate",
1191
- "/newsletters/{newsletter_id}/issues/{issue_id}/rewrite-text",
1192
- "/newsletters/{newsletter_id}/subscribers/import-audience",
1193
- "/newsletters/{newsletter_id}/subscribers/verify",
1194
- "/org-videos/generate",
1195
- "/org/brand-snapshot/refresh",
1196
- "/personas/generate",
1197
- "/phone-numbers/purchase",
1198
- "/pipelines/suggest",
1199
- "/placement/tests",
1200
- "/provider-domains/add-external",
1201
- "/provider-domains/check-availability",
1202
- "/provider-domains/checkout-purchase",
1203
- "/provider-domains/checkout-transfer",
1204
- "/provider-domains/{domain_id}/auto-warmup",
1205
- "/provider-domains/{domain_id}/check-propagation",
1206
- "/provider-domains/{domain_id}/checkout-mailboxes",
1207
- "/provider-domains/{domain_id}/connect-provider",
1208
- "/provider-domains/{domain_id}/dns",
1209
- "/public/enrich/lookup",
1210
- "/radar/aeo/run",
1211
- "/radar/competitors",
1212
- "/radar/competitors/bulk",
1213
- "/radar/competitors/{competitor_id}/scan",
1214
- "/radar/gaps/analyze",
1215
- "/radar/initiatives/measure",
1216
- "/radar/opportunities/approve-all",
1217
- "/radar/opportunities/{gap_id}/approve",
1218
- "/radar/reports",
1219
- "/radar/scan-all",
1220
- "/repos/{repo_id}/scan",
1221
- "/research-reports/generate",
1222
- "/research-reports/generate-all",
1223
- "/research-reports/retry",
1224
- "/research-reports/workflow/resume",
1225
- "/revenue/stage-docs/{lifecycle_entry_id}/{entity}/backfill",
1226
- "/scrape/requests",
1227
- "/search/companies",
1228
- "/search/companies/save",
1229
- "/search/contacts",
1230
- "/search/contacts/save",
1231
- "/seo/audits",
1232
- "/seo/keywords/research",
1233
- "/sequencer/content/email/generate",
1234
- "/sequencer/content/instructions/improve",
1235
- "/sequencer/content/linkedin/generate",
1236
- "/sequencer/templates/steps/generate",
1237
- "/social-listeners",
1238
- "/social-listeners/auto-seed/all",
1239
- "/social-listeners/auto-seed/brand",
1240
- "/social-listeners/auto-seed/competitors",
1241
- "/social-listeners/auto-seed/engagement",
1242
- "/social-listeners/auto-seed/keywords",
1243
- "/social-listeners/bulk-from-list",
1244
- "/social-listeners/integrations/x",
1245
- "/social-listeners/integrations/x/test",
1246
- "/social-listeners/posts/{post_id}/engagements/add-to-list",
1247
- "/social-listeners/posts/{post_id}/mark",
1248
- "/social-listeners/posts/{post_id}/save-contact",
1249
- "/social-listeners/queries/generate",
1250
- "/social/posts/generate",
1251
- "/tam/generate",
1252
- "/tam/regenerate",
1253
- "/tasks/{task_id}/start",
1254
- "/team-members/me/enrich-linkedin",
1255
- "/team-members/{team_member_id}/enrich-linkedin",
1256
- "/team-members/{team_member_id}/generate-post",
1257
- "/teams/auto-compose/suggest",
1258
- "/voice/agents/{agent_id}/documents/upload",
1259
- "/voice/call-flow-examples/transcribe",
1260
- "/voice/call-scripts/generate",
1261
- "/voice/campaigns/call-scripts",
1262
- "/voice/campaigns/email-enhancements",
1263
- "/voice/campaigns/email-spintax",
1264
- "/voice/campaigns/emails",
1265
- "/voice/campaigns/linkedin-messages",
1266
- "/voice/campaigns/onboarding/messages",
1267
- "/voice/campaigns/onboarding/threads/actions",
1268
- "/voice/campaigns/voicemails",
1269
- "/voice/chat-agent/messages",
1270
- "/voice/collections/{collection_id}/files/paste-text",
1271
- "/voice/collections/{collection_id}/files/upload",
1272
- "/voice/collections/{collection_id}/files/upload-multiple",
1273
- "/voice/collections/{collection_id}/reindex",
1274
- "/voice/collections/{collection_id}/scrape-sources",
1275
- "/voice/collections/{collection_id}/scrape-sources/{source_id}/run",
1276
- "/voice/email-agent/batch",
1277
- "/voice/email-agent/compose",
1278
- "/voice/email-agent/draft-reply",
1279
- "/voice/icp/queries",
1280
- "/voice/knowledge-base/files/upload",
1281
- "/voice/receptionist/messages",
1282
- "/voice/research/company",
1283
- "/voice/research/org-chart",
1284
- "/voice/research/prospect-data",
1285
- "/voice/research/prospect-profile",
1286
- "/voice/topics/classify",
1287
- "/voice/twins",
1288
- "/voice/twins/extract-linkedin",
1289
- "/voice/video-recordings/upload",
1290
- "/voice/voice-library/onboarding-voice",
1291
- "/voice/voice-library/voices",
1292
- "/voice/worksheets/calculate",
1293
- "/work/signals/promote",
1294
- "/workbench/tables/{table_id}/ai/generate-prompt",
1295
- "/workbench/tables/{table_id}/ai/groups/{group_id}/run",
1296
- "/workbench/tables/{table_id}/ai/preview",
1297
- "/workbench/tables/{table_id}/ai/run",
1298
- "/workbench/tables/{table_id}/ai/run-all",
1299
- "/workbench/tables/{table_id}/columns/{col_id}/ai/run",
1300
- "/workbench/tables/{table_id}/find-people/configure",
1301
- "/workbench/tables/{table_id}/find-people/{config_id}/run",
1302
- "/workbench/tables/{table_id}/import/search",
1303
- "/workbench/tables/{table_id}/import/search/research/preview",
1304
- "/workbench/tables/{table_id}/pipelines",
1305
- "/workbench/tables/{table_id}/scraper/providers/{provider}/runs",
1306
- "/workbench/tables/{table_id}/scraper/runs",
1307
- "/workbench/tables/{table_id}/suggest-mappings",
1308
- "/workbench/tables/{table_id}/triggers",
1309
- "/workbench/tables/{table_id}/unlock-rows",
1310
- "/workbench/tables/{table_id}/waterfall/configs/{column_id}/run"
1311
- ],
1312
- PUT: [
1313
- "/agent/operators/{operator}/enabled",
1314
- "/agent/operators/{operator}/skills/{skill_key}",
1315
- "/deliverability/autopilot/preferences",
1316
- "/enrichment/auto-enrichment/settings/{entity_type}",
1317
- "/enrichment/lists/{list_id}/pipelines/{pipeline_id}",
1318
- "/enrichment/record-sync/{entity_type}/policy",
1319
- "/enrichment/waterfall/configs/{column_id}",
1320
- "/marketplace/me/invoices/{invoice_id}",
1321
- "/marketplace/me/rates",
1322
- "/radar/settings",
1323
- "/workbench/tables/{table_id}/auto-enrich",
1324
- "/workbench/tables/{table_id}/pipelines/{pipeline_id}",
1325
- "/workbench/tables/{table_id}/schedule",
1326
- "/workbench/tables/{table_id}/triggers/{trigger_id}"
1327
- ]
1328
- };
1329
-
1330
- // src/generated/external.ts
1331
- var EXTERNAL_ROUTES = {
1332
- GET: [
1333
- "/dialer/sessions/token",
1334
- "/marketplace/me/connect/dashboard-link",
1335
- "/marketplace/me/connect/onboarding-link",
1336
- "/marketplace/me/payout-account/login-link",
1337
- "/marketplace/me/payout-account/onboarding-link",
1338
- "/voice/mcp-servers/{mcp_server_id}/tools"
1339
- ],
1340
- PATCH: [
1341
- "/ad-campaigns/{campaign_id}/ads/{ad_id}",
1342
- "/appointments/bookings/{booking_uid}/location",
1343
- "/appointments/bookings/{booking_uid}/sequence",
1344
- "/appointments/workflows/{workflow_id}",
1345
- "/audience-syncs/{config_id}",
1346
- "/campaigns/{campaign_id}",
1347
- "/campaigns/{campaign_id}/full",
1348
- "/content/calendar/{content_type}/{content_id}/schedule",
1349
- "/inbox/mailboxes/bulk",
1350
- "/inbox/mailboxes/{mailbox}/settings",
1351
- "/integrations/crm/meetings/{event_id}",
1352
- "/newsletters/{newsletter_id}/subscribers/{subscriber_id}/unsubscribe",
1353
- "/quotes/{quote_id}",
1354
- "/sequences/{sequence_id}",
1355
- "/sequences/{sequence_id}/steps/{step_id}",
1356
- "/voice/dialer/sessions/{session_id}/status",
1357
- "/voice/dispositions/{disposition_id}",
1358
- "/webhooks/{webhook_id}"
1359
- ],
1360
- POST: [
1361
- "/ad-campaigns/images",
1362
- "/ad-campaigns/status-refresh",
1363
- "/ad-campaigns/{campaign_id}/ads",
1364
- "/ad-campaigns/{campaign_id}/ads/batch",
1365
- "/ad-campaigns/{campaign_id}/ads/{ad_id}/status",
1366
- "/ad-campaigns/{campaign_id}/edit-preview",
1367
- "/ad-campaigns/{campaign_id}/retry",
1368
- "/ad-campaigns/{campaign_id}/status-refresh",
1369
- "/appointments/bookings",
1370
- "/appointments/bookings/import",
1371
- "/appointments/bookings/{booking_uid}/confirm",
1372
- "/appointments/bookings/{booking_uid}/guests",
1373
- "/appointments/bookings/{booking_uid}/mark-absent",
1374
- "/appointments/bookings/{booking_uid}/notes",
1375
- "/appointments/bookings/{booking_uid}/request-reschedule",
1376
- "/appointments/bookings/{booking_uid}/reschedule",
1377
- "/appointments/calendars/selected",
1378
- "/appointments/conferencing/connect/{provider}",
1379
- "/appointments/conferencing/{provider}/default",
1380
- "/appointments/workflows",
1381
- "/appointments/workflows/{workflow_id}/toggle",
1382
- "/audience-syncs",
1383
- "/audience-syncs/google/conversion-actions",
1384
- "/audience-syncs/google/conversions",
1385
- "/audience-syncs/google/video-assets/library",
1386
- "/audience-syncs/google/video-assets/links",
1387
- "/audience-syncs/google/video-assets/upload-urls",
1388
- "/audience-syncs/google/video-assets/uploads",
1389
- "/audience-syncs/{config_id}/launch",
1390
- "/audience-syncs/{config_id}/trigger",
1391
- "/campaigns",
1392
- "/campaigns/{campaign_id}/ad-images/{image_id}/hide",
1393
- "/campaigns/{campaign_id}/documents",
1394
- "/campaigns/{campaign_id}/launch",
1395
- "/campaigns/{campaign_id}/sequence/steps",
1396
- "/content/calendar/{content_type}/{content_id}/retry-publish",
1397
- "/crm-syncs/{provider}/companies/push",
1398
- "/crm-syncs/{provider}/contacts/push",
1399
- "/crm-syncs/{provider}/lists/push",
1400
- "/customer-success/companies/{company_id}/sync",
1401
- "/desk/accounts/{account_id}/committee/plays/{action_id}/run",
1402
- "/desk/accounts/{account_id}/committee/{contact_id}/linkedin-invite",
1403
- "/desk/decisions/{decision_id}/plays/{action_id}/run",
1404
- "/desk/decisions/{decision_id}/send-reply",
1405
- "/desk/decisions/{decision_id}/send-sms",
1406
- "/desk/plays/approvals/{execution_id}/decide",
1407
- "/dialer/parallel-calls/calls/{livekit_room_name}/drop-voicemail",
1408
- "/dialer/parallel-calls/manual-dial",
1409
- "/dialer/parallel-calls/redial-contact",
1410
- "/dialer/parallel-calls/sessions/start",
1411
- "/dialer/sessions/calls/drop-voicemail",
1412
- "/dialer/sessions/{session_id}/next-batch",
1413
- "/envelopes/{envelope_id}/resend",
1414
- "/envelopes/{envelope_id}/send",
1415
- "/feedback/sending-domains/verify",
1416
- "/feedback/surveys/{survey_id}/send",
1417
- "/forms/deliveries/{receipt_id}/retry",
1418
- "/forms/{form_key}/backfills",
1419
- "/forms/{form_key}/delivery/pause",
1420
- "/forms/{form_key}/delivery/resume",
1421
- "/inbox/emails/compose",
1422
- "/inbox/emails/{email_id}/send",
1423
- "/inbox/linkedin/accounts/sync",
1424
- "/inbox/linkedin/accounts/{account_id}/enable",
1425
- "/inbox/linkedin/compose",
1426
- "/inbox/mailboxes/activate",
1427
- "/inbox/mailboxes/{mailbox}/sync",
1428
- "/inbox/sms/compose",
1429
- "/inbox/{reply_id}/assign",
1430
- "/inbox/{reply_id}/send",
1431
- "/inbox/{reply_id}/tag",
1432
- "/integrations/connections",
1433
- "/integrations/connections/session-token",
1434
- "/integrations/crm-sync/audiences",
1435
- "/integrations/crm-sync/contacts/pull",
1436
- "/integrations/crm-sync/contacts/push",
1437
- "/integrations/crm-sync/deals/pull",
1438
- "/integrations/crm-sync/deals/push",
1439
- "/integrations/crm-sync/leads/previews",
1440
- "/integrations/crm-sync/leads/pull",
1441
- "/integrations/crm-sync/leads/push",
1442
- "/integrations/crm-sync/pull",
1443
- "/integrations/crm-sync/push",
1444
- "/integrations/crm/connections/{connection_id}/batch/contacts",
1445
- "/integrations/crm/connections/{connection_id}/companies",
1446
- "/integrations/crm/connections/{connection_id}/contacts",
1447
- "/integrations/crm/connections/{connection_id}/deals",
1448
- "/integrations/crm/connections/{connection_id}/leads",
1449
- "/integrations/crm/lists",
1450
- "/integrations/crm/lists/memberships",
1451
- "/integrations/crm/meetings",
1452
- "/integrations/linkedin-outreach/campaigns/{campaign_id}/pause",
1453
- "/integrations/linkedin-outreach/campaigns/{campaign_id}/resume",
1454
- "/integrations/linkedin-outreach/connection/reconnect",
1455
- "/integrations/linkedin-outreach/lists",
1456
- "/integrations/linkedin-outreach/lists/{list_id}/leads",
1457
- "/integrations/linkedin-outreach/workspaces/sync",
1458
- "/integrations/migrations/delta-sync",
1459
- "/integrations/migrations/discovery",
1460
- "/integrations/migrations/{migration_id}/confirm",
1461
- "/integrations/sugarcrm/connect",
1462
- "/integrations/sugarcrm/reconnect",
1463
- "/landing-pages/{landing_page_id}/publish",
1464
- "/landing-pages/{landing_page_id}/unpublish",
1465
- "/linkedin/accounts/sync",
1466
- "/marketplace/agencies/{slug}/reps/{sdr_id}/publish",
1467
- "/marketplace/me/messages/{conversation_id}/reply",
1468
- "/marketplace/me/payout-account/bank-details",
1469
- "/marketplace/me/payout-account/methods",
1470
- "/marketplace/me/payout-account/setup",
1471
- "/marketplace/me/sync-workbench",
1472
- "/marketplace/talent/{sdr_id}/messages",
1473
- "/marketplace/talent/{sdr_id}/request",
1474
- "/newsletters",
1475
- "/newsletters/{newsletter_id}/activate",
1476
- "/newsletters/{newsletter_id}/issues",
1477
- "/newsletters/{newsletter_id}/issues/{issue_id}/duplicate",
1478
- "/newsletters/{newsletter_id}/issues/{issue_id}/send",
1479
- "/newsletters/{newsletter_id}/issues/{issue_id}/send-test",
1480
- "/newsletters/{newsletter_id}/issues/{issue_id}/send/resume",
1481
- "/newsletters/{newsletter_id}/resume",
1482
- "/newsletters/{newsletter_id}/settings/verify-domain",
1483
- "/newsletters/{newsletter_id}/subscribers",
1484
- "/nurtures/{nurture_id}/launch",
1485
- "/org/sso/sync",
1486
- "/phone-numbers/a2p/register",
1487
- "/profile/deal-automations/sync-now",
1488
- "/programmatic-campaigns/{campaign_id}/pages/actions",
1489
- "/programmatic-campaigns/{campaign_id}/pages/{page_id}/actions",
1490
- "/provider-domains/{domain_id}/forwarding",
1491
- "/provider-domains/{domain_id}/mailbox-profile-picture",
1492
- "/provider-domains/{domain_id}/regenerate-nameservers",
1493
- "/provider-domains/{domain_id}/resend-transfer-password",
1494
- "/provider-domains/{domain_id}/resend-verification",
1495
- "/provider-domains/{domain_id}/set-nameservers",
1496
- "/provider-usage/refresh",
1497
- "/provider-usage/sync",
1498
- "/quote-billing-contact",
1499
- "/quote-contacts",
1500
- "/quote-settings/documents",
1501
- "/quote-templates",
1502
- "/quotes",
1503
- "/quotes/batch",
1504
- "/quotes/{quote_id}/duplicate",
1505
- "/quotes/{quote_id}/edit-as-draft",
1506
- "/quotes/{quote_id}/mark-accepted",
1507
- "/quotes/{quote_id}/send",
1508
- "/quotes/{quote_id}/unarchive",
1509
- "/radar/alerts/dispatch",
1510
- "/repos/{repo_id}/campaigns/{campaign_id}/launch",
1511
- "/roles/invite",
1512
- "/sales-coach/interviews/{interview_id}/members",
1513
- "/sales-coach/interviews/{interview_id}/members/{member_id}/send-invite",
1514
- "/sequencer/content/voice/test-call",
1515
- "/sequences",
1516
- "/sequences/generate-spintax",
1517
- "/sequences/{sequence_id}/contacts",
1518
- "/sequences/{sequence_id}/contacts/{contact_id}/resume",
1519
- "/sequences/{sequence_id}/pause",
1520
- "/sequences/{sequence_id}/resume",
1521
- "/sequences/{sequence_id}/run",
1522
- "/sequences/{sequence_id}/sync",
1523
- "/settings/scrape-config/crawl",
1524
- "/skills/{action_id}/execute",
1525
- "/skills/{action_id}/test-runs",
1526
- "/social-listeners/posts/{post_id}/add-to-sequence",
1527
- "/social/posts/{post_id}/engagers/add-to-audience",
1528
- "/social/posts/{post_id}/engagers/enroll",
1529
- "/social/posts/{post_id}/publish",
1530
- "/social/posts/{post_id}/schedule",
1531
- "/stripe/api-key",
1532
- "/stripe/sync",
1533
- "/team-members/sync",
1534
- "/voice/calls",
1535
- "/voice/dialer/sessions",
1536
- "/voice/dialer/sessions/{session_id}/resume",
1537
- "/voice/dispositions",
1538
- "/voice/inbound-calls/decline",
1539
- "/voice/inbound-calls/dialer/{call_id}/decline",
1540
- "/voice/inbound-calls/{room_name}/claim",
1541
- "/voice/mcp-servers/{mcp_server_id}/test",
1542
- "/webhooks",
1543
- "/webhooks/{webhook_id}/rotate-secret",
1544
- "/website-editor/publish",
1545
- "/website-editor/sync",
1546
- "/workbench/scraper/providers/{provider}/connection/test",
1547
- "/workbench/tables/{table_id}/import/crm",
1548
- "/workbench/tables/{table_id}/send-to-sequence",
1549
- "/workflows",
1550
- "/workflows/executions/approve",
1551
- "/workflows/executions/bulk-restart",
1552
- "/workflows/executions/request-changes",
1553
- "/workflows/executions/{execution_id}/pause",
1554
- "/workflows/executions/{execution_id}/restart",
1555
- "/workflows/executions/{execution_id}/resume",
1556
- "/workflows/executions/{execution_id}/stop",
1557
- "/workflows/integrations/{provider_name}/test",
1558
- "/workflows/validate",
1559
- "/workflows/workbenches",
1560
- "/workflows/{action_id}/execute",
1561
- "/workflows/{action_id}/trigger-reset"
1562
- ],
1563
- PUT: [
1564
- "/ad-campaigns/{campaign_id}",
1565
- "/appointments/calendars/destination",
1566
- "/appointments/conferencing/{provider}/link",
1567
- "/campaigns/{campaign_id}/audience",
1568
- "/campaigns/{campaign_id}/documents/{document_id}",
1569
- "/campaigns/{campaign_id}/sequence",
1570
- "/campaigns/{campaign_id}/sequence/steps/{step_id}",
1571
- "/desk/autonomy",
1572
- "/desk/org-settings",
1573
- "/inbox/linkedin/accounts/{account_id}",
1574
- "/integrations/crm-sync/pull-enabled",
1575
- "/integrations/crm-sync/push-enabled",
1576
- "/integrations/crm/connections/{connection_id}/batch/contacts",
1577
- "/integrations/crm/connections/{connection_id}/companies/{company_id}",
1578
- "/integrations/crm/connections/{connection_id}/contacts/{contact_id}",
1579
- "/integrations/crm/connections/{connection_id}/deals/{deal_id}",
1580
- "/integrations/crm/connections/{connection_id}/leads/{lead_id}",
1581
- "/integrations/linkedin-outreach/workspaces/active",
1582
- "/linkedin/accounts/{account_id}/schedule",
1583
- "/newsletters/{newsletter_id}/issues/{issue_id}",
1584
- "/quote-settings",
1585
- "/quote-templates/{template_id}",
1586
- "/quotes/{quote_id}",
1587
- "/sms/phone-numbers/{phone_number}",
1588
- "/social/autopilot/{account_id}",
1589
- "/workflows/{action_id}"
1590
- ]
1591
- };
1592
-
1593
- // src/http.ts
1594
- var G8Error = class _G8Error extends Error {
1595
- constructor(args) {
1596
- super(args.message);
1597
- this.name = "G8Error";
1598
- this.status = args.status;
1599
- this.type = args.type;
1600
- this.code = args.code;
1601
- this.requestId = args.requestId;
1602
- this.detail = args.detail;
1603
- this.retryable = args.retryable ?? false;
1604
- Object.setPrototypeOf(this, _G8Error.prototype);
1605
- }
1606
- };
1607
- function isRetryableStatus(status) {
1608
- return status === 429 || status >= 500 && status <= 599;
1609
- }
1610
- function isWriteMethod(method) {
1611
- const m = method.toUpperCase();
1612
- return m === "POST" || m === "PUT" || m === "PATCH" || m === "DELETE";
1613
- }
1614
- var API_BASE_SEGMENT = "/api/v1";
1615
- var billableMatchers = null;
1616
- var externalMatchers = null;
1617
- function templateToRegexSource(template) {
1618
- return template.split(/(\{[^}/]+\})/).map((part) => /^\{[^}/]+\}$/.test(part) ? "[^/]+" : part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
1619
- }
1620
- function buildMatchers(routes) {
1621
- const out = /* @__PURE__ */ new Map();
1622
- for (const [m, templates] of Object.entries(routes)) {
1623
- if (!templates.length) continue;
1624
- out.set(m, new RegExp(`^(?:${templates.map(templateToRegexSource).join("|")})/?$`));
1625
- }
1626
- return out;
1627
- }
1628
- function matcherFor(method) {
1629
- if (!billableMatchers) billableMatchers = buildMatchers(BILLABLE_ROUTES);
1630
- return billableMatchers.get(method.toUpperCase());
1631
- }
1632
- function externalMatcherFor(method) {
1633
- if (!externalMatchers) externalMatchers = buildMatchers(EXTERNAL_ROUTES);
1634
- return externalMatchers.get(method.toUpperCase());
1635
- }
1636
- function apiRelativePath(urlOrPath) {
1637
- let p = urlOrPath.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/]*/, "");
1638
- p = p.split(/[?#]/, 1)[0];
1639
- const at = p.indexOf(API_BASE_SEGMENT + "/");
1640
- if (at < 0) return null;
1641
- return p.slice(at + API_BASE_SEGMENT.length);
1642
- }
1643
- function isBillableOperation(method, urlOrPath) {
1644
- const matcher = matcherFor(method);
1645
- if (!matcher) return false;
1646
- const p = apiRelativePath(urlOrPath);
1647
- return p != null && matcher.test(p);
1648
- }
1649
- function isExternalOperation(method, urlOrPath) {
1650
- const matcher = externalMatcherFor(method);
1651
- if (!matcher) return false;
1652
- const p = apiRelativePath(urlOrPath);
1653
- return p != null && matcher.test(p);
1654
- }
1655
- function isNoBlindRetryOperation(method, urlOrPath) {
1656
- return isBillableOperation(method, urlOrPath) || isExternalOperation(method, urlOrPath);
1657
- }
1658
- var PRE_SEND_ERROR_CODES = /* @__PURE__ */ new Set([
1659
- "ECONNREFUSED",
1660
- "ENOTFOUND",
1661
- "EAI_AGAIN",
1662
- "ENETUNREACH",
1663
- "EHOSTUNREACH",
1664
- "UND_ERR_CONNECT_TIMEOUT"
1665
- ]);
1666
- function isPreSendNetworkError(err) {
1667
- let cur = err;
1668
- for (let depth = 0; cur && depth < 4; depth++) {
1669
- if (typeof cur.code === "string" && PRE_SEND_ERROR_CODES.has(cur.code)) return true;
1670
- if (Array.isArray(cur.errors) && cur.errors.some((e) => isPreSendNetworkError(e))) return true;
1671
- cur = cur.cause;
1672
- }
1673
- return false;
1674
- }
1675
- function newIdempotencyKey() {
1676
- const c = globalThis.crypto;
1677
- if (c?.randomUUID) return c.randomUUID();
1678
- if (c?.getRandomValues) {
1679
- const bytes = c.getRandomValues(new Uint8Array(16));
1680
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
1681
- }
1682
- return `g8-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
1683
- }
1684
- function parseRetryAfter(header, nowMs = Date.now()) {
1685
- if (!header) return null;
1686
- const secs = Number(header);
1687
- if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1e3);
1688
- const dateMs = Date.parse(header);
1689
- if (!Number.isNaN(dateMs)) {
1690
- const delta = dateMs - nowMs;
1691
- return delta > 0 ? delta : 0;
1692
- }
1693
- return null;
1694
- }
1695
- function backoffDelayMs(attempt, baseMs = 200, rand = Math.random) {
1696
- const expo = Math.min(baseMs * 2 ** attempt, 1e4);
1697
- return Math.floor(expo / 2 + rand() * (expo / 2));
1698
- }
1699
- function defaultSleep(ms) {
1700
- return new Promise((resolve) => setTimeout(resolve, ms));
1701
- }
1702
- function buildQuery(query) {
1703
- if (!query) return "";
1704
- const qs = new URLSearchParams();
1705
- for (const [k, v] of Object.entries(query)) {
1706
- if (v != null) qs.set(k, String(v));
1707
- }
1708
- const s = qs.toString();
1709
- return s ? `?${s}` : "";
1710
- }
1711
- async function toG8Error(resp, retryable = isRetryableStatus(resp.status)) {
1712
- let body = void 0;
1713
- try {
1714
- const text = await resp.text();
1715
- body = text ? JSON.parse(text) : void 0;
1716
- } catch {
1717
- body = void 0;
1718
- }
1719
- const env = body ?? {};
1720
- const message = typeof env.message === "string" && env.message || typeof env.error === "string" && env.error || `Request failed with status ${resp.status}`;
1721
- return new G8Error({
1722
- message,
1723
- status: resp.status,
1724
- type: typeof env.type === "string" && env.type || typeof env.error === "string" && env.error || "api_error",
1725
- code: typeof env.code === "string" ? env.code : String(resp.status),
1726
- requestId: typeof env.request_id === "string" && env.request_id || resp.headers.get("x-request-id") || void 0,
1727
- detail: env.detail,
1728
- retryable
1729
- });
1730
- }
1731
- var TARGET_ORG_HEADER = "x-target-org-id";
1732
- var nonAgencyKeys = /* @__PURE__ */ new Set();
1733
- var warnedTargetOrgKeys = /* @__PURE__ */ new Set();
1734
- function keyFingerprint(apiKey) {
1735
- let h = 2166136261;
1736
- for (let i = 0; i < apiKey.length; i++) {
1737
- h ^= apiKey.charCodeAt(i);
1738
- h = Math.imul(h, 16777619) >>> 0;
1739
- }
1740
- return `${apiKey.length}:${h.toString(36)}`;
1741
- }
1742
- function warnIfTargetOrgIgnored(apiKey, headers) {
1743
- if (!Object.keys(headers).some((h) => h.toLowerCase() === TARGET_ORG_HEADER)) return;
1744
- const fp = keyFingerprint(apiKey);
1745
- if (!nonAgencyKeys.has(fp) || warnedTargetOrgKeys.has(fp)) return;
1746
- warnedTargetOrgKeys.add(fp);
1747
- if (typeof console !== "undefined" && typeof console.warn === "function") {
1748
- console.warn(
1749
- "[@graph8/sdk] X-Target-Org-Id is set, but the server reported this API key is not an agency key. The header is ignored for non-agency keys: this call runs against the key's own org. Use an agency key (minted with is_agency=true) to target a client org."
1750
- );
1751
- }
1752
- }
1753
- async function request(baseUrl, path7, apiKey, opts = {}) {
1754
- const {
1755
- method = "GET",
1756
- body,
1757
- headers = {},
1758
- query,
1759
- idempotencyKey,
1760
- maxRetries = 2,
1761
- retryBaseMs = 200,
1762
- signal,
1763
- fetchImpl = fetch,
1764
- sleepImpl = defaultSleep
1765
- } = opts;
1766
- const url = baseUrl.replace(/\/+$/, "") + path7 + buildQuery(query);
1767
- const finalHeaders = {
1768
- "Content-Type": "application/json",
1769
- Authorization: `Bearer ${apiKey}`,
1770
- ...clientIdentityHeaders(headers),
1771
- ...headers
1772
- };
1773
- warnIfTargetOrgIgnored(apiKey, headers);
1774
- const effectiveIdempotencyKey = idempotencyKey ?? (maxRetries > 0 && isWriteMethod(method) ? newIdempotencyKey() : void 0);
1775
- if (effectiveIdempotencyKey) finalHeaders["Idempotency-Key"] = effectiveIdempotencyKey;
1776
- const billable = isNoBlindRetryOperation(method, url);
1777
- let attempt = 0;
1778
- for (; ; ) {
1779
- let resp;
1780
- try {
1781
- resp = await fetchImpl(url, {
1782
- method,
1783
- headers: finalHeaders,
1784
- body: body == null ? void 0 : JSON.stringify(body),
1785
- signal
1786
- });
1787
- } catch (err) {
1788
- const safeToRetry = !billable || isPreSendNetworkError(err);
1789
- if (attempt < maxRetries && safeToRetry) {
1790
- await sleepImpl(backoffDelayMs(attempt, retryBaseMs));
1791
- attempt++;
1792
- continue;
1793
- }
1794
- throw new G8Error({
1795
- message: `Network error: ${err?.message ?? "request failed"}`,
1796
- status: 0,
1797
- type: "network_error",
1798
- retryable: safeToRetry
1799
- });
1800
- }
1801
- if (resp.ok) {
1802
- const text = await resp.text();
1803
- return text ? JSON.parse(text) : void 0;
1804
- }
1805
- const statusRetryable = isRetryableStatus(resp.status) && (!billable || resp.status === 429);
1806
- if (statusRetryable && attempt < maxRetries) {
1807
- const retryAfter = parseRetryAfter(resp.headers.get("retry-after"));
1808
- const delay = retryAfter != null ? retryAfter : backoffDelayMs(attempt, retryBaseMs);
1809
- await sleepImpl(delay);
1810
- attempt++;
1811
- continue;
1812
- }
1813
- throw await toG8Error(resp, billable ? statusRetryable : isRetryableStatus(resp.status));
1814
- }
1815
- }
1816
- function isServerRuntime() {
1817
- const g = globalThis;
1818
- if (typeof g.window !== "undefined") return false;
1819
- return typeof g.process?.versions?.node === "string";
1820
- }
1821
- function runtimeUserAgent() {
1822
- const g = globalThis;
1823
- const node = typeof g.process?.version === "string" ? ` node/${g.process.version.replace(/^v/, "")}` : "";
1824
- return `graph8-sdk-js/${SDK_VERSION}${node}`;
1825
- }
1826
- function clientIdentityHeaders(callerHeaders = {}) {
1827
- const taken = new Set(Object.keys(callerHeaders).map((h) => h.toLowerCase()));
1828
- const out = {};
1829
- if (!taken.has("x-g8-client")) out["X-G8-Client"] = SDK_CLIENT_ID;
1830
- if (!taken.has("user-agent") && isServerRuntime()) out["User-Agent"] = runtimeUserAgent();
1831
- return out;
1832
- }
1833
- async function* paginate(fetchPage) {
1834
- let cursor = void 0;
1835
- for (; ; ) {
1836
- const page = await fetchPage(cursor);
1837
- for (const item of page.data ?? []) yield item;
1838
- const next = page.pagination?.next_cursor;
1839
- if (!next) break;
1840
- cursor = next;
1841
- }
1842
- }
1843
172
 
1844
173
  // src/generated/operations.ts
1845
174
  var API_OPERATIONS = {
@@ -2183,6 +512,7 @@ var API_OPERATIONS = {
2183
512
  create_deal_deals_post: ["POST", "/deals", "deals", "createDeal", "write", "deals:write"],
2184
513
  create_deal_note_deals__deal_id__notes_post: ["POST", "/deals/{deal_id}/notes", "notes", "createDealNote", "write", "notes:write"],
2185
514
  create_default_waterfall_config_enrichment_waterfall_configs_post: ["POST", "/enrichment/waterfall/configs", "enrichment", "createDefaultWaterfallConfig", "billable", "enrichment:run"],
515
+ create_desk_card_desk_cards_post: ["POST", "/desk/cards", "desk", "createDeskCard", "write", "deals:write"],
2186
516
  create_dialer_session_voice_dialer_sessions_post: ["POST", "/voice/dialer/sessions", "voice", "createDialerSession", "external", "voice:run"],
2187
517
  create_dynamic_column_enrichment_columns_post: ["POST", "/enrichment/columns", "enrichment", "createDynamicColumn", "write", "enrichment:write"],
2188
518
  create_empty_list_lists_create_empty_post: ["POST", "/lists/create-empty", "lists", "createEmptyList", "write", "lists:write"],
@@ -5159,7 +3489,2542 @@ var API_OPERATIONS = {
5159
3489
  withdraw_contacts_from_sequences_contacts_withdraw_from_sequences_post: ["POST", "/contacts/withdraw-from-sequences", "contacts", "withdrawContactsFromSequences", "write", "contacts:write"],
5160
3490
  withdraw_org_hiring_marketplace_org_hirings__hiring_id__withdraw_post: ["POST", "/marketplace/org/hirings/{hiring_id}/withdraw", "marketplace", "withdrawOrgHiring", "billable", "marketplace:run"]
5161
3491
  };
5162
- var API_OPERATION_IDS = Object.keys(API_OPERATIONS);
3492
+ var API_OPERATION_IDS = Object.keys(API_OPERATIONS);
3493
+
3494
+ // src/blocks/prefixes.ts
3495
+ var PREFIX_START = "// graph8:allowed-prefixes:start";
3496
+ var PREFIX_END = "// graph8:allowed-prefixes:end";
3497
+ var LEGACY_LINE = /^const allowedPrefixes: string\[\] \| undefined = undefined;[^\n]*$/m;
3498
+ var OPS = API_OPERATIONS;
3499
+ function prefixOf(operationId) {
3500
+ const p = OPS[operationId]?.[1];
3501
+ if (typeof p !== "string") return null;
3502
+ const first = p.replace(/^\/+/, "").split("/")[0];
3503
+ return first && !first.startsWith("{") ? first : null;
3504
+ }
3505
+ function derivePrefixes(blocks) {
3506
+ const out = /* @__PURE__ */ new Set();
3507
+ for (const b of blocks) for (const id of b.operations) {
3508
+ const p = prefixOf(id);
3509
+ if (p) out.add(p);
3510
+ }
3511
+ return [...out].sort();
3512
+ }
3513
+ function formatPrefixes(prefixes) {
3514
+ if (!prefixes.length) return "[]";
3515
+ const items = prefixes.map((p) => JSON.stringify(p));
3516
+ const one = `[${items.join(", ")}]`;
3517
+ if (one.length <= 80) return one;
3518
+ const lines = [];
3519
+ let cur = "";
3520
+ for (const it of items) {
3521
+ const next = cur ? `${cur} ${it},` : `${it},`;
3522
+ if (cur && next.length + 2 > 110) {
3523
+ lines.push(cur);
3524
+ cur = `${it},`;
3525
+ } else cur = next;
3526
+ }
3527
+ if (cur) lines.push(cur);
3528
+ return `[
3529
+ ${lines.map((l) => ` ${l}`).join("\n")}
3530
+ ]`;
3531
+ }
3532
+ function prefixRegion(prefixes) {
3533
+ return [
3534
+ PREFIX_START,
3535
+ `// Generated from the operations of the installed blocks. Regenerate: npx @graph8/sdk doctor --fix-prefixes`,
3536
+ `const allowedPrefixes: string[] | undefined = ${formatPrefixes(prefixes)};`,
3537
+ PREFIX_END
3538
+ ].join("\n");
3539
+ }
3540
+ function hasPrefixRegion(source) {
3541
+ const a = source.indexOf(PREFIX_START);
3542
+ return a >= 0 && source.indexOf(PREFIX_END, a) > a;
3543
+ }
3544
+ function applyPrefixes(source, prefixes) {
3545
+ const a = source.indexOf(PREFIX_START);
3546
+ const b = a >= 0 ? source.indexOf(PREFIX_END, a) : -1;
3547
+ if (a < 0 || b < 0) return null;
3548
+ return `${source.slice(0, a)}${prefixRegion(prefixes)}${source.slice(b + PREFIX_END.length)}`;
3549
+ }
3550
+ function upgradeLegacyRoute(source, prefixes) {
3551
+ return LEGACY_LINE.test(source) ? source.replace(LEGACY_LINE, prefixRegion(prefixes)) : null;
3552
+ }
3553
+ function stripComments(source) {
3554
+ return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`])\/\/[^\n]*/g, "$1");
3555
+ }
3556
+ function inspectRoute(source) {
3557
+ const code = stripComments(source);
3558
+ const flag = (name) => new RegExp(`\\b${name}\\s*:\\s*true\\b`).test(code);
3559
+ const m = /\bconst\s+allowedPrefixes\b[^=]*=\s*([^;]*);/.exec(code);
3560
+ let prefixes = "custom";
3561
+ const expr = m?.[1].trim();
3562
+ if (!m || expr === "undefined") prefixes = void 0;
3563
+ else if (expr && /^\[[\s\S]*\]$/.test(expr) && /^\[(\s*(["'])[^"'\n]*\2\s*,?)*\s*\]$/.test(expr)) {
3564
+ prefixes = [...expr.matchAll(/(["'])([^"'\n]*)\1/g)].map((x) => x[2]);
3565
+ }
3566
+ if (!m && /\ballowedPrefixes\s*:/.test(code)) prefixes = "custom";
3567
+ return {
3568
+ // A property of the config object: `authorize: fn`, `authorize,` (shorthand) or `authorize(req) {` (method).
3569
+ authorize: /[{,]\s*authorize\s*[:,}(]/.test(code),
3570
+ localPreview: flag("localPreview"),
3571
+ dangerouslyAllowUnauthenticated: flag("dangerouslyAllowUnauthenticated"),
3572
+ dangerouslyAllowAllPaths: flag("dangerouslyAllowAllPaths"),
3573
+ prefixes,
3574
+ generated: hasPrefixRegion(source)
3575
+ };
3576
+ }
3577
+
3578
+ // src/blocks/screens.ts
3579
+ var import_node_fs3 = __toESM(require("fs"));
3580
+ var import_node_path4 = __toESM(require("path"));
3581
+
3582
+ // src/blocks/compose.ts
3583
+ var import_node_fs2 = __toESM(require("fs"));
3584
+ var import_node_path3 = __toESM(require("path"));
3585
+
3586
+ // src/blocks/registry.ts
3587
+ var import_node_fs = __toESM(require("fs"));
3588
+ var import_node_path2 = __toESM(require("path"));
3589
+ function arr(v) {
3590
+ return Array.isArray(v) ? v : [];
3591
+ }
3592
+ function readJson(file) {
3593
+ try {
3594
+ const parsed = JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
3595
+ return parsed && typeof parsed === "object" ? parsed : null;
3596
+ } catch {
3597
+ return null;
3598
+ }
3599
+ }
3600
+ function toBlock(raw, dir, root) {
3601
+ if (typeof raw.id !== "string" || !raw.id) return null;
3602
+ return {
3603
+ id: raw.id,
3604
+ title: typeof raw.title === "string" ? raw.title : raw.id,
3605
+ summary: typeof raw.summary === "string" ? raw.summary : "",
3606
+ features: arr(raw.features),
3607
+ dependsOn: arr(raw.dependsOn),
3608
+ operations: arr(raw.operations),
3609
+ components: arr(raw.components),
3610
+ ...Array.isArray(raw.screens) ? { screens: arr(raw.screens) } : {},
3611
+ hooks: arr(raw.hooks),
3612
+ lib: arr(raw.lib),
3613
+ server: arr(raw.server),
3614
+ recipes: arr(raw.recipes),
3615
+ prerequisites: arr(raw.prerequisites),
3616
+ scopes: arr(raw.scopes),
3617
+ defaults: arr(raw.defaults),
3618
+ peerDependencies: raw.peerDependencies ?? {},
3619
+ dir,
3620
+ root
3621
+ };
3622
+ }
3623
+ function loadRegistry(blocksRoot) {
3624
+ const blocks = [];
3625
+ if (import_node_fs.default.existsSync(blocksRoot)) {
3626
+ for (const entry of import_node_fs.default.readdirSync(blocksRoot, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
3627
+ if (!entry.isDirectory()) continue;
3628
+ const root = import_node_path2.default.join(blocksRoot, entry.name);
3629
+ const raw = readJson(import_node_path2.default.join(root, "block.json"));
3630
+ const block = raw && toBlock(raw, entry.name, root);
3631
+ if (block) blocks.push(block);
3632
+ }
3633
+ }
3634
+ const stacks = [];
3635
+ const stacksDir = import_node_path2.default.join(import_node_path2.default.dirname(blocksRoot), "stacks");
3636
+ if (import_node_fs.default.existsSync(stacksDir)) {
3637
+ for (const file of import_node_fs.default.readdirSync(stacksDir).filter((f) => f.endsWith(".json")).sort()) {
3638
+ const raw = readJson(import_node_path2.default.join(stacksDir, file));
3639
+ if (!raw || typeof raw.id !== "string") continue;
3640
+ stacks.push({
3641
+ id: raw.id,
3642
+ title: typeof raw.title === "string" ? raw.title : raw.id,
3643
+ summary: typeof raw.summary === "string" ? raw.summary : "",
3644
+ status: raw.status === "planned" ? "planned" : "available",
3645
+ blocks: arr(raw.blocks),
3646
+ nav: arr(raw.nav),
3647
+ ...typeof raw.home === "string" ? { home: raw.home } : {}
3648
+ });
3649
+ }
3650
+ }
3651
+ return { blocks, stacks, stacksRoot: stacksDir };
3652
+ }
3653
+ function findBlock(reg, id) {
3654
+ return reg.blocks.find((b) => b.id === id);
3655
+ }
3656
+ function resolveWithDeps(reg, ids) {
3657
+ const ordered = [];
3658
+ const done = /* @__PURE__ */ new Set();
3659
+ const visiting = [];
3660
+ const visit = (id) => {
3661
+ if (done.has(id)) return;
3662
+ if (visiting.includes(id)) throw new Error(`dependency cycle: ${[...visiting, id].join(" -> ")}`);
3663
+ const block = findBlock(reg, id);
3664
+ if (!block) {
3665
+ const from = visiting.length ? ` (required by ${visiting[visiting.length - 1]})` : "";
3666
+ throw new Error(`unknown block "${id}"${from}. Run \`graph8-sdk list\` to see the available blocks.`);
3667
+ }
3668
+ visiting.push(id);
3669
+ for (const dep of block.dependsOn) visit(dep);
3670
+ visiting.pop();
3671
+ done.add(id);
3672
+ ordered.push(block);
3673
+ };
3674
+ ids.forEach(visit);
3675
+ return ordered;
3676
+ }
3677
+ function searchBlocks(reg, text) {
3678
+ const needle = text.trim().toLowerCase();
3679
+ if (!needle) return [];
3680
+ return reg.blocks.filter(
3681
+ (b) => [b.id, b.title, b.summary, ...b.features, ...b.components.map((c) => c.name)].some((s) => s.toLowerCase().includes(needle))
3682
+ );
3683
+ }
3684
+ function searchStacks(reg, text) {
3685
+ const needle = text.trim().toLowerCase();
3686
+ if (!needle) return [];
3687
+ return reg.stacks.filter((s) => [s.id, s.title, s.summary].some((x) => x.toLowerCase().includes(needle)));
3688
+ }
3689
+ function screenNames(b) {
3690
+ return b.components.map((c) => c.name).filter((n) => n.endsWith("Screen"));
3691
+ }
3692
+ function blockSummary(b) {
3693
+ const screens = [.../* @__PURE__ */ new Set([...screenNames(b), ...(b.screens ?? []).map((x) => x.component)])];
3694
+ return {
3695
+ id: b.id,
3696
+ title: b.title,
3697
+ summary: b.summary,
3698
+ dependsOn: b.dependsOn,
3699
+ components: b.components.map((c) => c.name),
3700
+ ...screens.length ? { screens } : {}
3701
+ };
3702
+ }
3703
+ function stackStatus(reg, s) {
3704
+ const missing = s.blocks.filter((id) => !findBlock(reg, id));
3705
+ return { status: missing.length ? "planned" : "available", missing };
3706
+ }
3707
+ function stackSummary(s, reg) {
3708
+ const base = { id: s.id, title: s.title, summary: s.summary, blocks: s.blocks };
3709
+ if (!reg) return base;
3710
+ const { status, missing } = stackStatus(reg, s);
3711
+ return { ...base, status, ...missing.length ? { missingBlocks: missing } : {} };
3712
+ }
3713
+ function catalogSection(reg) {
3714
+ if (!reg.blocks.length && !reg.stacks.length) return void 0;
3715
+ const lines = [
3716
+ "### Build an app from graph8 blocks",
3717
+ "",
3718
+ "To build an app: `npx @graph8/sdk create --blocks <ids> <dir>` (for example `--blocks crm,deals,inbox`). Dependencies come with it, and every block's",
3719
+ "finished screens become routes plus a sidebar entry. Use a stack below instead when one matches the product you are building.",
3720
+ "",
3721
+ "### graph8 blocks",
3722
+ "",
3723
+ "Ready-made, copy-in UI + hooks per graph8 feature. Pick from this list instead of reading every README:",
3724
+ ""
3725
+ ];
3726
+ for (const b of reg.blocks) {
3727
+ const deps = b.dependsOn.length ? ` (needs ${b.dependsOn.join(", ")})` : "";
3728
+ const screens = [.../* @__PURE__ */ new Set([...screenNames(b), ...(b.screens ?? []).map((x) => x.component)])];
3729
+ lines.push(`- block \`${b.id}\`: ${b.summary}${deps}${screens.length ? ` Screens (start here): ${screens.join(", ")}.` : ""}`);
3730
+ }
3731
+ for (const s of reg.stacks) {
3732
+ const { missing } = stackStatus(reg, s);
3733
+ const planned = missing.length ? ` [planned, not creatable yet: missing blocks ${missing.join(", ")}]` : "";
3734
+ lines.push(`- stack \`${s.id}\`: ${s.summary} (blocks: ${s.blocks.join(", ")})${planned}`);
3735
+ }
3736
+ lines.push(
3737
+ "",
3738
+ "Add one: `npx @graph8/sdk add <block>[/<Component>]` (dependencies and the `foundation` block come with it). Find one:",
3739
+ "`npx @graph8/sdk list` / `search <text>`. Check setup: `npx @graph8/sdk doctor --blocks a,b`."
3740
+ );
3741
+ if (reg.stacks.some((s) => !stackStatus(reg, s).missing.length)) lines.push("Start a whole app from an available stack: `npx @graph8/sdk create <stack> <dir>` (Next.js + the stack's blocks wired into a sidebar).");
3742
+ return lines.join("\n");
3743
+ }
3744
+
3745
+ // src/blocks/compose.ts
3746
+ var BLOCK_ICONS = {
3747
+ agents: "Bot",
3748
+ analytics: "ChartBar",
3749
+ appointments: "CalendarDays",
3750
+ campaigns: "Rocket",
3751
+ coach: "GraduationCap",
3752
+ copilot: "MessageCircle",
3753
+ crm: "Users",
3754
+ deals: "Handshake",
3755
+ dialer: "Phone",
3756
+ enrichment: "Sparkles",
3757
+ forms: "ClipboardList",
3758
+ inbox: "Inbox",
3759
+ integrations: "Plug",
3760
+ intent: "Target",
3761
+ lifecycle: "Repeat",
3762
+ lists: "ListChecks",
3763
+ mailboxes: "Mail",
3764
+ newsletter: "Newspaper",
3765
+ nurture: "Sprout",
3766
+ objects: "Boxes",
3767
+ pages: "LayoutTemplate",
3768
+ quotes: "FileText",
3769
+ radar: "Radar",
3770
+ search: "Search",
3771
+ sequencer: "Send",
3772
+ "social-ads": "Megaphone",
3773
+ visitors: "Eye",
3774
+ webchat: "MessageSquare",
3775
+ workbench: "Table2",
3776
+ workflows: "Workflow"
3777
+ };
3778
+ var FALLBACK_ICON = "LayoutGrid";
3779
+ var words = (pascal) => pascal.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(" ").filter(Boolean);
3780
+ var kebab = (pascal) => words(pascal).map((w) => w.toLowerCase()).join("-");
3781
+ var sentence = (pascal) => {
3782
+ const w = words(pascal).map((x) => x.toLowerCase());
3783
+ return w.length ? w[0][0].toUpperCase() + w[0].slice(1) + (w.length > 1 ? ` ${w.slice(1).join(" ")}` : "") : pascal;
3784
+ };
3785
+ function matching(s, open, pair) {
3786
+ let depth = 0;
3787
+ for (let i = open; i < s.length; i++) {
3788
+ if (s[i] === pair[0]) depth++;
3789
+ else if (s[i] === pair[1] && --depth === 0) return i;
3790
+ }
3791
+ return -1;
3792
+ }
3793
+ function requiredMembers(body) {
3794
+ const out = [];
3795
+ let depth = 0;
3796
+ let cur = "";
3797
+ const flush = () => {
3798
+ const m = /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*(\?)?\s*:/.exec(cur.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, ""));
3799
+ if (m && !m[2]) out.push(m[1]);
3800
+ cur = "";
3801
+ };
3802
+ for (const ch of body) {
3803
+ if ("{[(<".includes(ch)) depth++;
3804
+ else if ("}])>".includes(ch) && !(ch === ">" && cur.endsWith("="))) depth--;
3805
+ if (depth === 0 && (ch === ";" || ch === "," || ch === "\n")) flush();
3806
+ else cur += ch;
3807
+ }
3808
+ flush();
3809
+ return out;
3810
+ }
3811
+ function typeBody(source, typeName) {
3812
+ const m = new RegExp(`(?:interface\\s+${typeName}\\b[^{=]*|type\\s+${typeName}\\s*=\\s*)\\{`).exec(source);
3813
+ if (!m) return null;
3814
+ const open = m.index + m[0].length - 1;
3815
+ const close = matching(source, open, ["{", "}"]);
3816
+ return close < 0 ? null : source.slice(open + 1, close);
3817
+ }
3818
+ function takesNoRequiredProps(source, name) {
3819
+ const m = new RegExp(`export\\s+(?:default\\s+)?(?:async\\s+)?function\\s+${name}\\s*(?:<[^>]*>)?\\(|export\\s+const\\s+${name}\\s*(?::[^=]+)?=\\s*(?:async\\s*)?(?:<[^>]*>)?\\(`).exec(source);
3820
+ if (!m) return false;
3821
+ const open = m.index + m[0].length - 1;
3822
+ const close = matching(source, open, ["(", ")"]);
3823
+ if (close < 0) return false;
3824
+ const params = source.slice(open + 1, close).trim();
3825
+ if (!params) return true;
3826
+ if (/=\s*\{\s*\}\s*$/.test(params)) return true;
3827
+ const colon = params.indexOf(":");
3828
+ const type = (colon < 0 ? "" : params.slice(colon + 1)).trim();
3829
+ if (!type) return false;
3830
+ if (type.startsWith("{")) {
3831
+ const close2 = matching(type, 0, ["{", "}"]);
3832
+ return close2 > 0 && requiredMembers(type.slice(1, close2)).length === 0;
3833
+ }
3834
+ const named = /^([A-Za-z_$][\w$]*)$/.exec(type);
3835
+ const body = named ? typeBody(source, named[1]) : null;
3836
+ return body !== null && requiredMembers(body).length === 0;
3837
+ }
3838
+ function readSource(block, file) {
3839
+ try {
3840
+ return import_node_fs2.default.readFileSync(import_node_path3.default.join(block.root, file), "utf8");
3841
+ } catch {
3842
+ return "";
3843
+ }
3844
+ }
3845
+ function screensOf(block) {
3846
+ const base = BLOCK_ICONS[block.id] ?? FALLBACK_ICON;
3847
+ const have = new Set(block.components.map((c) => c.name));
3848
+ const make = (component, p, label, extra = {}, fallback2 = false) => ({
3849
+ block: block.id,
3850
+ component,
3851
+ path: p,
3852
+ label,
3853
+ icon: extra.icon ?? base,
3854
+ group: extra.group ?? block.title,
3855
+ ...extra.description ? { description: extra.description } : {},
3856
+ fallback: fallback2
3857
+ });
3858
+ if (block.screens?.length) {
3859
+ const declared = block.screens.filter((s) => have.has(s.component));
3860
+ return declared.map((s, i) => ({ s, i })).sort((a, b) => (a.s.order ?? 1e6) - (b.s.order ?? 1e6) || a.i - b.i).map(({ s }) => make(s.component, s.path, s.label, s));
3861
+ }
3862
+ const derived = block.components.map((c) => c.name).filter((n) => n.endsWith("Screen") && n.length > "Screen".length);
3863
+ if (derived.length) return derived.map((n) => make(n, `/${kebab(n.slice(0, -"Screen".length))}`, sentence(n.slice(0, -"Screen".length))));
3864
+ const fallback = block.components.find((c) => takesNoRequiredProps(readSource(block, c.file), c.name));
3865
+ return fallback ? [make(fallback.name, `/${block.id}`, block.title, {}, true)] : [];
3866
+ }
3867
+ var hasRealScreen = (block) => screensOf(block).some((s) => !s.fallback);
3868
+ function uniquePaths(screens) {
3869
+ const taken = /* @__PURE__ */ new Set();
3870
+ return screens.map((s) => {
3871
+ let p = s.path;
3872
+ const prefixed = `/${s.block}${s.path === "/" ? "" : s.path}`;
3873
+ if (taken.has(p)) p = prefixed;
3874
+ for (let n = 2; taken.has(p); n++) p = `${prefixed}-${n}`;
3875
+ taken.add(p);
3876
+ return { ...s, path: p };
3877
+ });
3878
+ }
3879
+ function composeApp(reg, ids, opts = {}) {
3880
+ const requested = [...new Set(ids)];
3881
+ const closure = resolveWithDeps(reg, requested);
3882
+ const depOnly = closure.filter((b) => !requested.includes(b.id) && b.id !== "foundation");
3883
+ const warnings = [];
3884
+ const chosen = [];
3885
+ for (const id of requested) {
3886
+ const block = findBlock(reg, id);
3887
+ if (id === "foundation") continue;
3888
+ const screens2 = screensOf(block);
3889
+ if (!screens2.length) warnings.push(`block "${id}" has no screen and no component that renders without props: it adds no page (wire its components yourself).`);
3890
+ else if (screens2[0].fallback) warnings.push(`block "${id}" has no finished Screen yet: its page is the plain ${screens2[0].component} component.`);
3891
+ chosen.push(...screens2);
3892
+ }
3893
+ if (!opts.noDepsScreens) {
3894
+ for (const block of depOnly) chosen.push(...screensOf(block).filter((s) => opts.withDepsScreens || !s.fallback));
3895
+ }
3896
+ const screens = uniquePaths(chosen);
3897
+ const nav = screens.map((s) => ({
3898
+ label: s.label,
3899
+ path: s.path,
3900
+ block: s.block,
3901
+ component: s.component,
3902
+ icon: s.icon,
3903
+ group: s.group,
3904
+ ...s.description ? { description: s.description } : {}
3905
+ }));
3906
+ const home = screens.find((s) => s.block === "analytics" && !s.fallback)?.path ?? screens[0]?.path;
3907
+ const stack = {
3908
+ id: "custom",
3909
+ title: opts.title?.trim() || "graph8 app",
3910
+ summary: `Custom app: ${requested.join(", ")}`,
3911
+ status: "available",
3912
+ blocks: closure.map((b) => b.id),
3913
+ nav,
3914
+ ...home ? { home } : {}
3915
+ };
3916
+ return { stack, screens, warnings };
3917
+ }
3918
+
3919
+ // src/blocks/navfile.ts
3920
+ function render(template, vars2) {
3921
+ return template.replace(/\{\{(\w+)\}\}/g, (_m, key) => {
3922
+ if (!(key in vars2)) throw new Error(`template placeholder {{${key}}} has no value`);
3923
+ return vars2[key];
3924
+ });
3925
+ }
3926
+ function pageFileFor(navPath) {
3927
+ const segs = navPath.split("/").filter(Boolean);
3928
+ return ["app", ...segs, "page.tsx"].join("/");
3929
+ }
3930
+ function componentImportPath(block, file) {
3931
+ const rel = installedRel(file) ?? file;
3932
+ return `@/components/g8/${block.dir}/${rel.replace(/\.(tsx|ts|jsx|js)$/, "")}`;
3933
+ }
3934
+ function navUsesExtras(nav) {
3935
+ return nav.some((n) => n.icon || n.group || n.description);
3936
+ }
3937
+ function navPartsFor(nav) {
3938
+ if (!navUsesExtras(nav)) {
3939
+ return { navImports: "", navType: "{ label: string; path: string }[]", navJson: JSON.stringify(nav.map((n) => ({ label: n.label, path: n.path })), null, 2) };
3940
+ }
3941
+ const icons = [...new Set(nav.flatMap((n) => n.icon ? [n.icon] : []))].sort();
3942
+ const named = [...icons, "type LucideIcon"].join(", ");
3943
+ const items = nav.map((n) => ({
3944
+ label: n.label,
3945
+ path: n.path,
3946
+ ...n.icon ? { icon: `__ICON__${n.icon}` } : {},
3947
+ ...n.group ? { group: n.group } : {},
3948
+ ...n.description ? { description: n.description } : {}
3949
+ }));
3950
+ return {
3951
+ navImports: [
3952
+ `import { ${named} } from "lucide-react";`,
3953
+ "",
3954
+ "export type NavItem = { label: string; path: string; icon?: LucideIcon; group?: string; description?: string };",
3955
+ "",
3956
+ ""
3957
+ ].join("\n"),
3958
+ navType: "NavItem[]",
3959
+ navJson: JSON.stringify(items, null, 2).replace(/"__ICON__(\w+)"/g, "$1")
3960
+ };
3961
+ }
3962
+ function navParts(stack) {
3963
+ return navPartsFor(stack.nav);
3964
+ }
3965
+
3966
+ // src/blocks/screens.ts
3967
+ var NAV_FILES = ["lib/nav.ts", "src/lib/nav.ts"];
3968
+ function parseNavFile(text) {
3969
+ const decl = /export const nav\s*:[^=]*=\s*(\[[\s\S]*\]);?\s*$/.exec(text);
3970
+ if (!decl) return null;
3971
+ const first = /^(?:import |export )/m.exec(text);
3972
+ try {
3973
+ const quoted2 = decl[1].replace(/("icon":\s*)([A-Za-z_][A-Za-z0-9_]*)/g, '$1"$2"');
3974
+ const items = JSON.parse(quoted2);
3975
+ if (!Array.isArray(items) || items.some((i) => typeof i?.path !== "string" || typeof i?.label !== "string")) return null;
3976
+ return { header: first ? text.slice(0, first.index) : "", items };
3977
+ } catch {
3978
+ return null;
3979
+ }
3980
+ }
3981
+ function toNavEntry(s) {
3982
+ return { label: s.label, path: s.path, icon: s.icon, group: s.group, ...s.description ? { description: s.description } : {} };
3983
+ }
3984
+ function candidates(blocks) {
3985
+ const main = blocks[blocks.length - 1];
3986
+ return [main, ...blocks.filter((b) => b !== main)].filter((b) => b.id !== "foundation").flatMap((b) => screensOf(b).filter((s) => b === main || !s.fallback));
3987
+ }
3988
+ function importAlias(target) {
3989
+ const t = target.replace(/^src\//, "");
3990
+ return `@/${t}`;
3991
+ }
3992
+ function planScreenWiring(input) {
3993
+ const navRel = NAV_FILES.find((f) => import_node_fs3.default.existsSync(import_node_path4.default.join(input.root, f)));
3994
+ const none = { files: [], notes: [], screens: [], usesIcons: false };
3995
+ if (!navRel) return null;
3996
+ const navText = import_node_fs3.default.readFileSync(import_node_path4.default.join(input.root, navRel), "utf8");
3997
+ const parsed = parseNavFile(navText);
3998
+ const useSrc = navRel.startsWith("src/");
3999
+ const appRel = (rel) => useSrc ? `src/${rel}` : rel;
4000
+ const pageTpl = import_node_fs3.default.readFileSync(import_node_path4.default.join(input.templatesRoot, "next-app", "_pages", "route-page.tsx.tmpl"), "utf8");
4001
+ const items = parsed ? [...parsed.items] : [];
4002
+ const taken = /* @__PURE__ */ new Set([...items.map((i) => i.path), ...input.recorded.map((r) => r.path)]);
4003
+ const files = [];
4004
+ const notes = [];
4005
+ const out = { ...none, screens: [] };
4006
+ const manual = [];
4007
+ for (const s of candidates(input.blocks)) {
4008
+ if (input.recorded.some((r) => r.block === s.block && r.component === s.component)) continue;
4009
+ const block = input.blocks.find((b) => b.id === s.block);
4010
+ const comp = block.components.find((c) => c.name === s.component);
4011
+ if (!comp) continue;
4012
+ const prefixed = `/${s.block}${s.path === "/" ? "" : s.path}`;
4013
+ const importPath = componentImportPath(block, comp.file).replace("@/components/g8", importAlias(input.target));
4014
+ const content = render(pageTpl, {
4015
+ stackId: input.stackId,
4016
+ block: block.id,
4017
+ blockDir: `${input.target}/${block.dir}`,
4018
+ component: s.component,
4019
+ importPath,
4020
+ propsJson: "{}"
4021
+ });
4022
+ const handWritten = (x) => {
4023
+ const file = import_node_path4.default.join(input.root, appRel(pageFileFor(x)));
4024
+ return import_node_fs3.default.existsSync(file) && import_node_fs3.default.readFileSync(file, "utf8") !== content;
4025
+ };
4026
+ const isTaken = (x) => taken.has(x) || handWritten(x);
4027
+ let p = s.path;
4028
+ if (isTaken(p)) p = prefixed;
4029
+ for (let n = 2; isTaken(p); n++) p = `${prefixed}-${n}`;
4030
+ if (p !== s.path && !taken.has(s.path) && handWritten(s.path)) notes.push(`kept your edited ${appRel(pageFileFor(s.path))} (not generated by graph8); wiring ${p} instead`);
4031
+ const screen = { ...s, path: p };
4032
+ taken.add(p);
4033
+ const rel = appRel(pageFileFor(p));
4034
+ const abs = import_node_path4.default.join(input.root, rel);
4035
+ if (import_node_fs3.default.existsSync(abs)) {
4036
+ files.push({ rel, content, action: "unchanged" });
4037
+ } else files.push({ rel, content, action: "create" });
4038
+ out.screens.push({ block: s.block, component: s.component, path: p });
4039
+ (parsed ? items : manual).push(toNavEntry(screen));
4040
+ }
4041
+ if (!out.screens.length) return { ...out, files, notes };
4042
+ if (parsed) {
4043
+ const { navImports, navType, navJson } = navPartsFor(items);
4044
+ const next = `${parsed.header}${navImports}export const nav: ${navType} = ${navJson};
4045
+ `;
4046
+ files.push({ rel: navRel, content: next, action: next === navText ? "unchanged" : "update" });
4047
+ out.usesIcons = items.some((i) => i.icon);
4048
+ } else {
4049
+ notes.push(`could not update ${navRel} (it no longer has the generated shape). Add these entries yourself:`);
4050
+ for (const m of manual) notes.push(` ${JSON.stringify(m)}`);
4051
+ }
4052
+ return { ...out, files, notes };
4053
+ }
4054
+
4055
+ // src/blocks/add.ts
4056
+ var ADD_USAGE = `Usage: npx @graph8/sdk add <block>[/<Component>] [options]
4057
+
4058
+ Copy a graph8 block (components, hooks, recipes) into your app. The block's
4059
+ dependencies (always including \`foundation\`) are added first.
4060
+ --dir <path> project root (default: current directory)
4061
+ --target <path> where to copy, relative to --dir (default: components/g8)
4062
+ --dry-run print what would change, write and install nothing
4063
+ --force overwrite files that differ from the template
4064
+ --no-install do not install missing peer dependencies
4065
+ --routes also add the block's screens as routes and sidebar entries (lib/nav.ts, plus the
4066
+ lucide-react icon dependency). Automatic in apps made with \`create --blocks\`; off by
4067
+ default in apps made from a named stack (create apollo, starter, ...), where running it once opts the
4068
+ app in, so later add calls wire routes without the flag
4069
+ Existing files are never overwritten without --force. Installed blocks are
4070
+ recorded in graph8.json so re-runs do nothing.`;
4071
+ function parseAddArgs(argv) {
4072
+ const o = { spec: "", dir: ".", dryRun: false, force: false, install: true };
4073
+ for (let i = 0; i < argv.length; i++) {
4074
+ const arg = argv[i];
4075
+ const [flag, inline] = arg.startsWith("--") && arg.includes("=") ? [arg.slice(0, arg.indexOf("=")), arg.slice(arg.indexOf("=") + 1)] : [arg, void 0];
4076
+ const value = () => {
4077
+ const v = inline ?? argv[++i];
4078
+ if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
4079
+
4080
+ ${ADD_USAGE}`);
4081
+ return v;
4082
+ };
4083
+ if (flag === "--dir") o.dir = value();
4084
+ else if (flag === "--target") o.target = value();
4085
+ else if (arg === "--dry-run") o.dryRun = true;
4086
+ else if (arg === "--force") o.force = true;
4087
+ else if (arg === "--no-install") o.install = false;
4088
+ else if (arg === "--routes") o.routes = true;
4089
+ else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
4090
+
4091
+ ${ADD_USAGE}`);
4092
+ else if (!o.spec) o.spec = arg;
4093
+ else throw new Error(`unexpected argument ${arg}
4094
+
4095
+ ${ADD_USAGE}`);
4096
+ }
4097
+ if (!o.spec) throw new Error(`add needs a block name
4098
+
4099
+ ${ADD_USAGE}`);
4100
+ return o;
4101
+ }
4102
+ function detectPackageManager(root) {
4103
+ const has = (f) => import_node_fs4.default.existsSync(import_node_path5.default.join(root, f));
4104
+ if (has("pnpm-lock.yaml")) return "pnpm";
4105
+ if (has("yarn.lock")) return "yarn";
4106
+ if (has("bun.lockb") || has("bun.lock")) return "bun";
4107
+ return "npm";
4108
+ }
4109
+ function installCommand(pm, packages) {
4110
+ const verb = pm === "npm" ? "install" : "add";
4111
+ return { cmd: pm, args: [verb, ...packages] };
4112
+ }
4113
+ var sha = (s) => (0, import_node_crypto.createHash)("sha256").update(s).digest("hex");
4114
+ function appTargeted(block) {
4115
+ const m = /* @__PURE__ */ new Map();
4116
+ for (const f of block.server) if (f.target && !f.target.startsWith("g8/")) m.set(posix(f.file), f.target);
4117
+ return m;
4118
+ }
4119
+ function listFiles(dir, base = dir) {
4120
+ const out = [];
4121
+ for (const e of import_node_fs4.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
4122
+ const full = import_node_path5.default.join(dir, e.name);
4123
+ if (e.isDirectory()) out.push(...listFiles(full, base));
4124
+ else out.push(posix(import_node_path5.default.relative(base, full)));
4125
+ }
4126
+ return out;
4127
+ }
4128
+ var IMPORT_RE = /(\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)(["'])([^"'\n]+)\2/g;
4129
+ function relSpec(fromDir, toFile) {
4130
+ const noExt = toFile;
4131
+ let r = posix(import_node_path5.default.relative(fromDir, noExt));
4132
+ if (!r.startsWith(".")) r = `./${r}`;
4133
+ return r;
4134
+ }
4135
+ function g8AliasTarget(spec, ctx) {
4136
+ const [seg, ...rest] = spec.split("/");
4137
+ const reserved = ["components", "hooks", "lib", "server", "recipes"].includes(seg);
4138
+ const owner = rest.length && !reserved ? ctx.reg.blocks.find((b) => b.id === seg || b.dir === seg) : void 0;
4139
+ const block = owner ?? ctx.reg.blocks.find((b) => b.id === "foundation");
4140
+ const sub = owner ? rest.join("/") : spec;
4141
+ return import_node_path5.default.join(ctx.targetAbs, block?.dir ?? "_foundation", installedRel(sub) ?? sub);
4142
+ }
4143
+ function rewriteImports(content, ctx) {
4144
+ return content.replace(IMPORT_RE, (whole, pre, q, spec) => {
4145
+ if (spec.startsWith("@/g8/")) {
4146
+ return `${pre}${q}${relSpec(ctx.destDir, g8AliasTarget(spec.slice("@/g8/".length), ctx))}${q}`;
4147
+ }
4148
+ if (spec.startsWith(".") && ctx.block && ctx.srcFile) {
4149
+ const abs = import_node_path5.default.resolve(import_node_path5.default.dirname(ctx.srcFile), spec);
4150
+ const relInBlock = import_node_path5.default.relative(ctx.block.root, abs);
4151
+ if (relInBlock.startsWith("..")) return whole;
4152
+ const mapped = installedRel(relInBlock);
4153
+ if (mapped === null) return whole;
4154
+ return `${pre}${q}${relSpec(ctx.destDir, import_node_path5.default.join(ctx.targetAbs, ctx.block.dir, mapped))}${q}`;
4155
+ }
4156
+ return whole;
4157
+ });
4158
+ }
4159
+ function diffSummary(before, after) {
4160
+ const count = (s) => {
4161
+ const m = /* @__PURE__ */ new Map();
4162
+ for (const l of s.split("\n")) m.set(l, (m.get(l) ?? 0) + 1);
4163
+ return m;
4164
+ };
4165
+ const a = count(before);
4166
+ const b = count(after);
4167
+ let removed = 0;
4168
+ let added = 0;
4169
+ for (const [l, n] of a) removed += Math.max(0, n - (b.get(l) ?? 0));
4170
+ for (const [l, n] of b) added += Math.max(0, n - (a.get(l) ?? 0));
4171
+ return `+${added} -${removed} lines vs the template`;
4172
+ }
4173
+ function classify(abs, rel, content, force) {
4174
+ if (!import_node_fs4.default.existsSync(abs)) return { rel, content, action: "create" };
4175
+ const current = import_node_fs4.default.readFileSync(abs, "utf8");
4176
+ if (current === content) return { rel, content, action: "unchanged" };
4177
+ if (force) return { rel, content, action: "update", detail: diffSummary(current, content) };
4178
+ return { rel, content, action: "skip", detail: `exists and differs (${diffSummary(current, content)}); use --force to overwrite` };
4179
+ }
4180
+ function readPkg(root) {
4181
+ try {
4182
+ return JSON.parse(import_node_fs4.default.readFileSync(import_node_path5.default.join(root, "package.json"), "utf8"));
4183
+ } catch {
4184
+ return null;
4185
+ }
4186
+ }
4187
+ function missingPeers(blocks, pkg) {
4188
+ const have = /* @__PURE__ */ new Set([
4189
+ ...Object.keys(pkg.dependencies ?? {}),
4190
+ ...Object.keys(pkg.devDependencies ?? {}),
4191
+ ...Object.keys(pkg.peerDependencies ?? {}),
4192
+ ...Object.keys(pkg.optionalDependencies ?? {})
4193
+ ]);
4194
+ const want = /* @__PURE__ */ new Map();
4195
+ for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) if (!want.has(name)) want.set(name, range);
4196
+ return [...want].filter(([name]) => !have.has(name)).map(([name, range]) => `${name}@${range}`);
4197
+ }
4198
+ function parseSpec(spec) {
4199
+ const [blockId, component, ...extra] = spec.split("/");
4200
+ if (!blockId || extra.length) throw new Error(`invalid block "${spec}": use <block> or <block>/<Component>`);
4201
+ return { blockId, component: component || void 0 };
4202
+ }
4203
+ function planBlock(block, only, reg, targetAbs, root, force) {
4204
+ const skipComponentFiles = /* @__PURE__ */ new Set();
4205
+ if (only) {
4206
+ for (const c of block.components) if (c.name !== only) skipComponentFiles.add(posix(c.file));
4207
+ const byStem = new Map(block.components.map((c) => [posix(c.file).replace(/\.[tj]sx?$/, ""), posix(c.file)]));
4208
+ const start = block.components.find((c) => c.name === only);
4209
+ const queue = start ? [posix(start.file)] : [];
4210
+ while (queue.length) {
4211
+ const file = queue.pop();
4212
+ const text = import_node_fs4.default.readFileSync(import_node_path5.default.join(block.root, file), "utf8");
4213
+ for (const m of text.matchAll(IMPORT_RE)) {
4214
+ if (!m[3].startsWith(".")) continue;
4215
+ const stem = posix(import_node_path5.default.relative(block.root, import_node_path5.default.resolve(block.root, import_node_path5.default.dirname(file), m[3]))).replace(/\.[tj]sx?$/, "");
4216
+ const sibling = byStem.get(stem);
4217
+ if (sibling && skipComponentFiles.delete(sibling)) queue.push(sibling);
4218
+ }
4219
+ }
4220
+ }
4221
+ const files = [];
4222
+ const toApp = appTargeted(block);
4223
+ for (const rel of listFiles(block.root)) {
4224
+ if (skipComponentFiles.has(rel) || toApp.has(rel)) continue;
4225
+ const mapped = installedRel(rel);
4226
+ if (mapped === null) continue;
4227
+ const destAbs = import_node_path5.default.join(targetAbs, block.dir, mapped);
4228
+ const src = import_node_path5.default.join(block.root, rel);
4229
+ const text = import_node_fs4.default.readFileSync(src, "utf8");
4230
+ const content = rewriteImports(text, { reg, targetAbs, block, srcFile: src, destDir: import_node_path5.default.dirname(destAbs) });
4231
+ files.push(classify(destAbs, posix(import_node_path5.default.relative(root, destAbs)), content, force));
4232
+ }
4233
+ return files;
4234
+ }
4235
+ var PROXY_CONTRACT = [
4236
+ "This is not a Next.js project, so the proxy route was not written. Your server must provide the same contract:",
4237
+ " - handle every method on /api/g8/* (the browser calls these paths)",
4238
+ " - forward to ${G8_API_URL:-https://be.graph8.com}/api/v1/<rest of path>, same method, query and body",
4239
+ " - add the header `Authorization: Bearer $G8_API_KEY` on the server; never send the key to the browser",
4240
+ " - return the upstream status and JSON body unchanged"
4241
+ ];
4242
+ function writeFile(root, f) {
4243
+ const abs = import_node_path5.default.join(root, f.rel);
4244
+ import_node_fs4.default.mkdirSync(import_node_path5.default.dirname(abs), { recursive: true });
4245
+ import_node_fs4.default.writeFileSync(abs, f.content, "utf8");
4246
+ }
4247
+ function readLedger(file) {
4248
+ try {
4249
+ const j = JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
4250
+ return j && typeof j === "object" && j.blocks ? j : null;
4251
+ } catch {
4252
+ return null;
4253
+ }
4254
+ }
4255
+ function ledgerContent(prev, target, entries, only, wired) {
4256
+ const blocks = { ...prev?.blocks ?? {} };
4257
+ for (const [id, files] of entries) {
4258
+ const old = blocks[id];
4259
+ const recorded = { ...old?.files ?? {} };
4260
+ for (const f of files) {
4261
+ if (f.action === "skip") continue;
4262
+ recorded[f.rel] = sha(f.content);
4263
+ }
4264
+ const comp = only.get(id);
4265
+ let components;
4266
+ if (!comp) components = "all";
4267
+ else if (old?.components === "all") components = "all";
4268
+ else components = [.../* @__PURE__ */ new Set([...old?.components ?? [], comp])].sort();
4269
+ blocks[id] = { files: Object.fromEntries(Object.entries(recorded).sort(([a], [b]) => a.localeCompare(b))), components };
4270
+ }
4271
+ const sorted = Object.fromEntries(Object.entries(blocks).sort(([a], [b]) => a.localeCompare(b)));
4272
+ const stack = prev?.stack ? { stack: prev.stack } : {};
4273
+ const app = appRecord(prev, wired);
4274
+ return `${JSON.stringify({ sdkVersion: SDK_VERSION, target, ...stack, ...app ? { app } : {}, blocks: sorted }, null, 2)}
4275
+ `;
4276
+ }
4277
+ function appRecord(prev, wired) {
4278
+ if (!wired?.screens.length) return prev?.app;
4279
+ const old = prev?.app ?? {};
4280
+ const blocks = [.../* @__PURE__ */ new Set([...old.blocks ?? [], wired.requested])];
4281
+ return { ...old, blocks, screens: [...old.screens ?? [], ...wired.screens] };
4282
+ }
4283
+ function ensureEnvExample(root, dryRun, out) {
4284
+ const file = import_node_path5.default.join(root, ".env.local.example");
4285
+ const current = import_node_fs4.default.existsSync(file) ? import_node_fs4.default.readFileSync(file, "utf8") : null;
4286
+ if (current !== null && /^G8_API_KEY=/m.test(current)) {
4287
+ out("keep .env.local.example (G8_API_KEY already listed)");
4288
+ return;
4289
+ }
4290
+ out(`${dryRun ? "would " : ""}${current === null ? "create" : "update"} .env.local.example (+ G8_API_KEY=)`);
4291
+ if (dryRun) return;
4292
+ const base = current === null ? "" : current.endsWith("\n") || current === "" ? current : `${current}
4293
+ `;
4294
+ import_node_fs4.default.writeFileSync(file, `${base}G8_API_KEY=
4295
+ `, "utf8");
4296
+ }
4297
+ function runAdd(opts, reg, io, cwd) {
4298
+ const root = import_node_path5.default.resolve(cwd, opts.dir);
4299
+ const pkg = readPkg(root);
4300
+ if (!pkg) {
4301
+ io.err(`no package.json in ${root}. Run this from your app's root, or pass --dir.`);
4302
+ return 1;
4303
+ }
4304
+ let parsed;
4305
+ let blocks;
4306
+ try {
4307
+ parsed = parseSpec(opts.spec);
4308
+ blocks = resolveWithDeps(reg, [parsed.blockId]);
4309
+ const main2 = blocks[blocks.length - 1];
4310
+ if (parsed.component && !main2.components.some((c) => c.name === parsed.component)) {
4311
+ const names = main2.components.map((c) => c.name).join(", ") || "none";
4312
+ throw new Error(`block "${main2.id}" has no component "${parsed.component}" (available: ${names})`);
4313
+ }
4314
+ } catch (e) {
4315
+ io.err(e.message);
4316
+ return 1;
4317
+ }
4318
+ const exists = (p) => import_node_fs4.default.existsSync(import_node_path5.default.join(root, p));
4319
+ const target = opts.target ?? (exists("src") && !exists("components") && !exists("app") ? "src/components/g8" : "components/g8");
4320
+ const targetAbs = import_node_path5.default.resolve(root, target);
4321
+ const isNext = Boolean(pkg.dependencies?.next ?? pkg.devDependencies?.next);
4322
+ const dry = opts.dryRun;
4323
+ const verb = (a) => a === "unchanged" ? "keep" : a === "skip" ? "skip" : dry ? `would ${a}` : a;
4324
+ const entries = /* @__PURE__ */ new Map();
4325
+ const only = /* @__PURE__ */ new Map();
4326
+ const main = blocks[blocks.length - 1];
4327
+ for (const b of blocks) {
4328
+ const comp = b === main ? parsed.component : void 0;
4329
+ only.set(b.id, comp);
4330
+ entries.set(b.id, planBlock(b, comp, reg, targetAbs, root, opts.force));
4331
+ }
4332
+ const all = [...entries.values()].flat();
4333
+ const prevLedger = readLedger(import_node_path5.default.join(root, "graph8.json"));
4334
+ const route = planAppFiles(blocks, reg, targetAbs, root, isNext, opts.force, installedBlocks(reg, prevLedger, blocks));
4335
+ const wiring = opts.wireScreens !== false && !parsed.component && isNext ? screenWiring(blocks, prevLedger, opts.routes === true, reg, root, posix(import_node_path5.default.relative(root, targetAbs)), io) : null;
4336
+ io.out(`${dry ? "Dry run: " : ""}adding ${blocks.map((b) => b.id).join(" + ")} to ${target}`);
4337
+ for (const f of [...all, ...route.files]) io.out(`${verb(f.action)} ${f.rel}${f.detail ? ` (${f.detail})` : ""}`);
4338
+ if (route.note) route.note.forEach((l) => io.out(l));
4339
+ for (const f of wiring?.files ?? []) io.out(`${verb(f.action)} ${f.rel}`);
4340
+ wiring?.notes.forEach((l) => io.out(l));
4341
+ if (!dry) {
4342
+ for (const f of [...all, ...route.files, ...wiring?.files ?? []]) if (f.action === "create" || f.action === "update") writeFile(root, f);
4343
+ }
4344
+ ensureEnvExample(root, dry, io.out);
4345
+ const ledgerPath = import_node_path5.default.join(root, "graph8.json");
4346
+ const next = ledgerContent(prevLedger, posix(import_node_path5.default.relative(root, targetAbs)), entries, only, wiring ? { requested: main.id, screens: wiring.screens } : void 0);
4347
+ const prevText = import_node_fs4.default.existsSync(ledgerPath) ? import_node_fs4.default.readFileSync(ledgerPath, "utf8") : null;
4348
+ if (next !== prevText) {
4349
+ io.out(`${dry ? "would " : ""}${prevText === null ? "create" : "update"} graph8.json`);
4350
+ if (!dry) import_node_fs4.default.writeFileSync(ledgerPath, next, "utf8");
4351
+ } else io.out("keep graph8.json");
4352
+ return installPeers(blocks, pkg, root, opts, io, wiring?.usesIcons ? lucidePeer(io) : []);
4353
+ }
4354
+ function screenWiring(blocks, prev, routes, reg, root, target, io) {
4355
+ if (!prev?.app && !(routes && prev?.stack)) return null;
4356
+ if (!io.templatesRoot) return null;
4357
+ const fresh = blocks.filter((b) => !prev.blocks[b.id]);
4358
+ if (!fresh.length) return null;
4359
+ return planScreenWiring({ reg, root, target, stackId: prev.stack ?? "custom", blocks: fresh, templatesRoot: io.templatesRoot, recorded: prev.app?.screens ?? [] });
4360
+ }
4361
+ function lucidePeer(io) {
4362
+ try {
4363
+ const sdk = JSON.parse(import_node_fs4.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
4364
+ return [`lucide-react@${sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0"}`];
4365
+ } catch {
4366
+ return ["lucide-react@>=0.400.0"];
4367
+ }
4368
+ }
4369
+ function installPeers(blocks, pkg, root, opts, io, extra = []) {
4370
+ const have = new Set(missingPeers(blocks, pkg).map((m) => m.slice(0, m.lastIndexOf("@"))));
4371
+ const declared = new Set(Object.keys({ ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies, ...pkg.optionalDependencies }));
4372
+ const missing = [...missingPeers(blocks, pkg), ...extra.filter((e) => !declared.has(e.slice(0, e.lastIndexOf("@"))) && !have.has(e.slice(0, e.lastIndexOf("@"))))];
4373
+ if (!missing.length) return 0;
4374
+ const pm = detectPackageManager(root);
4375
+ const { cmd, args } = installCommand(pm, missing);
4376
+ const shown = `${cmd} ${args.join(" ")}`;
4377
+ if (!opts.install || opts.dryRun) {
4378
+ io.out(`${opts.dryRun ? "would run" : "not installing (--no-install). Run"}: ${shown}`);
4379
+ return 0;
4380
+ }
4381
+ io.out(`installing peer dependencies: ${shown}`);
4382
+ const code = io.spawn(cmd, args, root);
4383
+ if (code !== 0) {
4384
+ io.err(`peer dependency install failed (exit ${code}). Run it yourself: ${shown}`);
4385
+ return 1;
4386
+ }
4387
+ return 0;
4388
+ }
4389
+ function appTargetPath(root, target) {
4390
+ const useSrc = import_node_fs4.default.existsSync(import_node_path5.default.join(root, "src", "app")) && !import_node_fs4.default.existsSync(import_node_path5.default.join(root, "app"));
4391
+ return useSrc && target.startsWith("app/") ? `src/${target}` : target;
4392
+ }
4393
+ function installedBlocks(reg, prev, adding) {
4394
+ const ids = /* @__PURE__ */ new Set([...Object.keys(prev?.blocks ?? {}), ...adding.map((b) => b.id)]);
4395
+ return reg.blocks.filter((b) => ids.has(b.id));
4396
+ }
4397
+ function planPrefixedRoute(destAbs, rel, template, prefixes, force) {
4398
+ const fresh = applyPrefixes(template, prefixes) ?? template;
4399
+ if (!import_node_fs4.default.existsSync(destAbs)) return { rel, content: fresh, action: "create", detail: `allowedPrefixes: ${prefixes.length} API areas from the installed blocks` };
4400
+ const current = import_node_fs4.default.readFileSync(destAbs, "utf8");
4401
+ const regenerated = hasPrefixRegion(current) ? applyPrefixes(current, prefixes) : null;
4402
+ if (regenerated !== null && !force) {
4403
+ if (regenerated === current) return { rel, content: current, action: "unchanged" };
4404
+ return { rel, content: regenerated, action: "update", detail: `allowedPrefixes regenerated: ${prefixes.length} API areas` };
4405
+ }
4406
+ const planned = classify(destAbs, rel, fresh, force);
4407
+ if (planned.action === "skip") planned.detail = `${planned.detail}; its allowedPrefixes are yours: \`npx @graph8/sdk doctor --fix-prefixes\` prints the list the blocks need`;
4408
+ return planned;
4409
+ }
4410
+ function planAppFiles(blocks, reg, targetAbs, root, isNext, force, installed = blocks) {
4411
+ const files = [];
4412
+ let skipped = false;
4413
+ for (const b of blocks) {
4414
+ for (const [file, target] of appTargeted(b)) {
4415
+ if (!isNext) {
4416
+ skipped = true;
4417
+ continue;
4418
+ }
4419
+ const destAbs = import_node_path5.default.join(root, appTargetPath(root, target));
4420
+ const src = import_node_path5.default.join(b.root, file);
4421
+ const content = rewriteImports(import_node_fs4.default.readFileSync(src, "utf8"), { reg, targetAbs, destDir: import_node_path5.default.dirname(destAbs) });
4422
+ const rel = posix(import_node_path5.default.relative(root, destAbs));
4423
+ if (hasPrefixRegion(content)) files.push(planPrefixedRoute(destAbs, rel, content, derivePrefixes(installed), force));
4424
+ else files.push(classify(destAbs, rel, content, force));
4425
+ }
4426
+ }
4427
+ return { files, note: skipped ? PROXY_CONTRACT : void 0 };
4428
+ }
4429
+
4430
+ // src/blocks/brand.ts
4431
+ var import_node_fs5 = __toESM(require("fs"));
4432
+ var import_node_path6 = __toESM(require("path"));
4433
+ function normalizeHex(input) {
4434
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(input.trim());
4435
+ if (!m) return null;
4436
+ const h = m[1].length === 3 ? [...m[1]].map((c) => c + c).join("") : m[1];
4437
+ return `#${h.toLowerCase()}`;
4438
+ }
4439
+ function hexToRgb(hex) {
4440
+ const h = (normalizeHex(hex) ?? "#000000").slice(1);
4441
+ return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
4442
+ }
4443
+ function rgbToHex([r, g, b]) {
4444
+ return `#${[r, g, b].map((v) => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, "0")).join("")}`;
4445
+ }
4446
+ function luminance(hex) {
4447
+ const [r, g, b] = hexToRgb(hex).map((v) => {
4448
+ const c = v / 255;
4449
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
4450
+ });
4451
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
4452
+ }
4453
+ function contrast(a, b) {
4454
+ const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
4455
+ return (hi + 0.05) / (lo + 0.05);
4456
+ }
4457
+ function mix(hex, toward, amount) {
4458
+ const a = hexToRgb(hex);
4459
+ const b = hexToRgb(toward);
4460
+ return rgbToHex([0, 1, 2].map((i) => a[i] + (b[i] - a[i]) * amount));
4461
+ }
4462
+ var CANVAS = { light: "#ffffff", dark: "#111110" };
4463
+ var INK = { light: "#16140f", dark: "#f2efe9" };
4464
+ var AA_TEXT = 4.5;
4465
+ var AA_UI = 3;
4466
+ var STEPS = [
4467
+ ["50", "#ffffff", 0.94],
4468
+ ["100", "#ffffff", 0.86],
4469
+ ["200", "#ffffff", 0.7],
4470
+ ["300", "#ffffff", 0.5],
4471
+ ["400", "#ffffff", 0.25],
4472
+ ["600", "#000000", 0.14],
4473
+ ["700", "#000000", 0.28],
4474
+ ["800", "#000000", 0.42],
4475
+ ["900", "#000000", 0.56]
4476
+ ];
4477
+ function scaleOf(brand) {
4478
+ const out = {};
4479
+ for (const [k, to, amt] of STEPS) out[k] = mix(brand, to, amt);
4480
+ out["500"] = brand;
4481
+ return Object.fromEntries(Object.entries(out).sort(([a], [b]) => Number(a) - Number(b)));
4482
+ }
4483
+ function bestForeground(bg, ink) {
4484
+ return [ink, "#ffffff", "#000000"].sort((a, b) => contrast(b, bg) - contrast(a, bg))[0];
4485
+ }
4486
+ function swatch(input, mode, explicit, notes) {
4487
+ let brand = input;
4488
+ const canvas = CANVAS[mode];
4489
+ if (mode === "dark" && !explicit) {
4490
+ for (let i = 1; i <= 20 && contrast(brand, canvas) < AA_UI; i++) brand = mix(input, "#ffffff", i * 0.05);
4491
+ if (brand !== input) notes.push(`dark mode: lightened ${input} to ${brand} so it stands out from the dark background (${contrast(brand, canvas).toFixed(2)}:1)`);
4492
+ }
4493
+ let fg = bestForeground(brand, INK[mode]);
4494
+ for (let i = 1; i <= 20 && contrast(fg, brand) < AA_TEXT; i++) {
4495
+ const before = brand;
4496
+ brand = mix(brand, luminance(fg) > 0.5 ? "#000000" : "#ffffff", 0.05);
4497
+ fg = bestForeground(brand, INK[mode]);
4498
+ if (i === 1) notes.push(`${mode} mode: adjusted ${before} for readable button text`);
4499
+ }
4500
+ const canvasContrast = contrast(brand, canvas);
4501
+ if (canvasContrast < AA_UI) {
4502
+ notes.push(
4503
+ `${mode} mode: ${brand} is faint against the ${mode} background (${canvasContrast.toFixed(2)}:1, under ${AA_UI}:1); button text stays readable, but consider a deeper colour${mode === "dark" ? " or --dark-brand-color" : ""}`
4504
+ );
4505
+ }
4506
+ return { brand, foreground: fg, scale: scaleOf(brand), canvasContrast };
4507
+ }
4508
+ function brandTheme(color, darkColor) {
4509
+ const light = normalizeHex(color);
4510
+ if (!light) throw new Error(`--brand-color must be a hex colour like #0f62fe (got "${color}")`);
4511
+ const dark = darkColor === void 0 ? light : normalizeHex(darkColor);
4512
+ if (!dark) throw new Error(`--dark-brand-color must be a hex colour like #78a9ff (got "${darkColor}")`);
4513
+ const notes = [];
4514
+ return { light: swatch(light, "light", true, notes), dark: swatch(dark, "dark", darkColor !== void 0, notes), notes };
4515
+ }
4516
+ var CSS_START = "/* graph8:brand:start */";
4517
+ var CSS_END = "/* graph8:brand:end */";
4518
+ function vars(s, indent) {
4519
+ const lines = [
4520
+ `--brand: ${s.brand};`,
4521
+ `--brand-foreground: ${s.foreground};`,
4522
+ ...Object.entries(s.scale).map(([k, v]) => `--brand-${k}: ${v};`),
4523
+ "--primary: var(--brand);",
4524
+ "--primary-foreground: var(--brand-foreground);",
4525
+ "--ring: color-mix(in srgb, var(--brand) 45%, transparent);"
4526
+ ];
4527
+ return lines.map((l) => `${indent}${l}`).join("\n");
4528
+ }
4529
+ function brandCss(theme, source) {
4530
+ return [
4531
+ CSS_START,
4532
+ `/* Generated by \`npx @graph8/sdk brand --brand-color ${source.color}${source.dark ? ` --dark-brand-color ${source.dark}` : ""}\`. Re-run it to change`,
4533
+ " the colour; edits inside these markers are replaced. Text on --brand meets WCAG AA (4.5:1). */",
4534
+ "@theme inline {",
4535
+ " --color-brand: var(--brand);",
4536
+ " --color-brand-foreground: var(--brand-foreground);",
4537
+ ...Object.keys(theme.light.scale).map((k) => ` --color-brand-${k}: var(--brand-${k});`),
4538
+ "}",
4539
+ ":root {",
4540
+ vars(theme.light, " "),
4541
+ "}",
4542
+ ".dark {",
4543
+ vars(theme.dark, " "),
4544
+ "}",
4545
+ CSS_END
4546
+ ].join("\n");
4547
+ }
4548
+ function applyCssRegion(css, region) {
4549
+ const a = css.indexOf(CSS_START);
4550
+ const b = a >= 0 ? css.indexOf(CSS_END, a) : -1;
4551
+ if (a >= 0 && b > a) return `${css.slice(0, a)}${region}${css.slice(b + CSS_END.length)}`;
4552
+ return `${css.replace(/\s*$/, "")}
4553
+
4554
+ ${region}
4555
+ `;
4556
+ }
4557
+ var DEFAULT_FIELDS = { appUrl: "https://app.graph8.com", linkLabel: "graph8" };
4558
+ var CONFIG_FILE = "g8.config.ts";
4559
+ var CFG_START = "// graph8:brand:start";
4560
+ var CFG_END = "// graph8:brand:end";
4561
+ function renderBrandConfig(f) {
4562
+ return [
4563
+ "/**",
4564
+ " * This app's brand. The app shell shows `name` and `logo`; every graph8 block reads `linkLabel` and `appUrl`",
4565
+ ' * (through <BrandProvider>) for its copy and link-outs ("Connect in graph8", "Open in graph8"), so a white-label',
4566
+ " * app can name its own platform and link to its own URL. Fields left out keep graph8's defaults.",
4567
+ " *",
4568
+ ' * Change it with `npx @graph8/sdk brand --name "Acme" --brand-color "#0f62fe" --logo ./logo.svg`',
4569
+ " * (also --app-url, --link-label, --dark-brand-color), or edit the values below. The CLI rewrites only the",
4570
+ " * lines between the graph8:brand markers.",
4571
+ " */",
4572
+ configRegion(f),
4573
+ ""
4574
+ ].join("\n");
4575
+ }
4576
+ function configRegion(f) {
4577
+ return [
4578
+ CFG_START,
4579
+ "export const brand: { name: string; appUrl: string; linkLabel: string; logo?: string } = {",
4580
+ ` name: ${JSON.stringify(f.name)},`,
4581
+ ` appUrl: ${JSON.stringify(f.appUrl)},`,
4582
+ ` linkLabel: ${JSON.stringify(f.linkLabel)},`,
4583
+ ...f.logo ? [` logo: ${JSON.stringify(f.logo)},`] : [],
4584
+ "};",
4585
+ CFG_END
4586
+ ].join("\n");
4587
+ }
4588
+ function readBrandConfig(source) {
4589
+ const a = source.indexOf(CFG_START);
4590
+ const b = a >= 0 ? source.indexOf(CFG_END, a) : -1;
4591
+ if (a < 0 || b < 0) return null;
4592
+ const region = source.slice(a, b);
4593
+ const out = {};
4594
+ for (const key of ["name", "appUrl", "linkLabel", "logo"]) {
4595
+ const m = new RegExp(`^\\s*${key}:\\s*("(?:[^"\\\\]|\\\\.)*")`, "m").exec(region);
4596
+ if (m) {
4597
+ try {
4598
+ out[key] = JSON.parse(m[1]);
4599
+ } catch {
4600
+ }
4601
+ }
4602
+ }
4603
+ return out;
4604
+ }
4605
+ function applyBrandConfig(source, f) {
4606
+ const a = source.indexOf(CFG_START);
4607
+ const b = a >= 0 ? source.indexOf(CFG_END, a) : -1;
4608
+ if (a < 0 || b < 0) return null;
4609
+ return `${source.slice(0, a)}${configRegion(f)}${source.slice(b + CFG_END.length)}`;
4610
+ }
4611
+ var BRAND_USAGE = `Usage: npx @graph8/sdk brand [options]
4612
+
4613
+ Brand an app made by \`create\` (run it in the app's root). Every option is optional; values you leave out stay.
4614
+ --name <text> the app's name (sidebar, page title)
4615
+ --brand-color <hex> primary colour, e.g. "#0f62fe": writes --brand, a 50-900 scale and --primary into
4616
+ app/globals.css, with button text checked for WCAG AA (adjusted when needed)
4617
+ --dark-brand-color <hex> the colour in dark mode (default: derived from --brand-color, lightened if too dark)
4618
+ --logo <path> an svg, png, jpg, webp or gif (max 512 KB), copied to public/brand/ and shown in the sidebar
4619
+ --app-url <url> white-label: where link-outs go (default https://app.graph8.com)
4620
+ --link-label <text> white-label: what the platform is called in copy and links (default graph8)
4621
+ --dir <path> the app's root (default: current directory)
4622
+ --dry-run print what would change, write nothing
4623
+ Running it again with the same options changes nothing.`;
4624
+ function takeBrandFlag(o, flag, next) {
4625
+ if (flag === "--name") o.name = next();
4626
+ else if (flag === "--brand-color") o.color = next();
4627
+ else if (flag === "--dark-brand-color") o.darkColor = next();
4628
+ else if (flag === "--logo") o.logo = next();
4629
+ else if (flag === "--app-url") o.appUrl = next();
4630
+ else if (flag === "--link-label") o.linkLabel = next();
4631
+ else return false;
4632
+ return true;
4633
+ }
4634
+ function hasBrandInput(o) {
4635
+ return [o.name, o.color, o.darkColor, o.logo, o.appUrl, o.linkLabel].some((v) => v !== void 0);
4636
+ }
4637
+ function parseBrandArgs(argv) {
4638
+ const o = { dir: "." };
4639
+ for (let i = 0; i < argv.length; i++) {
4640
+ const arg = argv[i];
4641
+ const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
4642
+ const flag = eq > 0 ? arg.slice(0, eq) : arg;
4643
+ const next = () => {
4644
+ const v = eq > 0 ? arg.slice(eq + 1) : argv[++i];
4645
+ if (v === void 0 || v.startsWith("--") && v !== "--") throw new Error(`${flag} needs a value
4646
+
4647
+ ${BRAND_USAGE}`);
4648
+ return v;
4649
+ };
4650
+ if (takeBrandFlag(o, flag, next)) continue;
4651
+ if (flag === "--dir") o.dir = next();
4652
+ else if (arg === "--dry-run") o.dryRun = true;
4653
+ else throw new Error(`unknown option ${arg}
4654
+
4655
+ ${BRAND_USAGE}`);
4656
+ }
4657
+ if (!hasBrandInput(o)) throw new Error(`brand needs at least one of --name, --brand-color, --dark-brand-color, --logo, --app-url, --link-label
4658
+
4659
+ ${BRAND_USAGE}`);
4660
+ return o;
4661
+ }
4662
+ var LOGO_TYPES = ["svg", "png", "jpg", "jpeg", "webp", "gif"];
4663
+ var MAX_LOGO_BYTES = 512 * 1024;
4664
+ function brandProblem(o, cwd) {
4665
+ if (o.name !== void 0 && !o.name.trim()) return "--name cannot be empty";
4666
+ if (o.linkLabel !== void 0 && !o.linkLabel.trim()) return "--link-label cannot be empty";
4667
+ if (o.appUrl !== void 0 && !/^https?:\/\/[^\s/]+[^\s]*$/i.test(o.appUrl)) return `--app-url must be an http(s) URL (got "${o.appUrl}")`;
4668
+ if (o.color !== void 0 && !normalizeHex(o.color)) return `--brand-color must be a hex colour like #0f62fe (got "${o.color}")`;
4669
+ if (o.darkColor !== void 0 && !normalizeHex(o.darkColor)) return `--dark-brand-color must be a hex colour like #78a9ff (got "${o.darkColor}")`;
4670
+ if (o.darkColor !== void 0 && o.color === void 0) return "--dark-brand-color goes with --brand-color (pass both)";
4671
+ if (o.logo !== void 0) {
4672
+ const abs = import_node_path6.default.resolve(cwd, o.logo);
4673
+ const ext = import_node_path6.default.extname(abs).slice(1).toLowerCase();
4674
+ if (!LOGO_TYPES.includes(ext)) return `--logo must be one of ${LOGO_TYPES.join(", ")} (got "${o.logo}")`;
4675
+ if (!import_node_fs5.default.existsSync(abs) || !import_node_fs5.default.statSync(abs).isFile()) return `--logo: no file at ${o.logo}`;
4676
+ if (import_node_fs5.default.statSync(abs).size > MAX_LOGO_BYTES) return `--logo is larger than ${MAX_LOGO_BYTES / 1024} KB: use a smaller (or svg) file`;
4677
+ }
4678
+ return null;
4679
+ }
4680
+ function cssFile(root) {
4681
+ return ["app/globals.css", "src/app/globals.css"].find((f) => import_node_fs5.default.existsSync(import_node_path6.default.join(root, f))) ?? null;
4682
+ }
4683
+ function planBrand(o, root, cwd, fallbackName = "graph8 app") {
4684
+ const files = [];
4685
+ const notes = [];
4686
+ const cfgPath = import_node_path6.default.join(root, CONFIG_FILE);
4687
+ const existing = import_node_fs5.default.existsSync(cfgPath) ? import_node_fs5.default.readFileSync(cfgPath, "utf8") : null;
4688
+ const current = existing === null ? {} : readBrandConfig(existing);
4689
+ if (existing !== null && current === null) {
4690
+ throw new Error(`${CONFIG_FILE} has no graph8:brand markers, so it is yours and was not changed. Set the brand there by hand.`);
4691
+ }
4692
+ const fields = {
4693
+ name: o.name?.trim() ?? current?.name ?? fallbackName,
4694
+ appUrl: (o.appUrl ?? current?.appUrl ?? DEFAULT_FIELDS.appUrl).replace(/\/+$/, ""),
4695
+ linkLabel: o.linkLabel?.trim() ?? current?.linkLabel ?? DEFAULT_FIELDS.linkLabel,
4696
+ logo: current?.logo
4697
+ };
4698
+ if (o.logo !== void 0) {
4699
+ const src = import_node_path6.default.resolve(cwd, o.logo);
4700
+ const ext = import_node_path6.default.extname(src).slice(1).toLowerCase().replace("jpeg", "jpg");
4701
+ const rel = `public/brand/logo.${ext}`;
4702
+ files.push({ rel, content: import_node_fs5.default.readFileSync(src) });
4703
+ for (const other of LOGO_TYPES.map((e) => `public/brand/logo.${e}`)) {
4704
+ if (other !== rel && import_node_fs5.default.existsSync(import_node_path6.default.join(root, other))) files.push({ rel: other, content: null, note: "replaced by the new logo" });
4705
+ }
4706
+ fields.logo = `/brand/logo.${ext}`;
4707
+ }
4708
+ const cfg = existing === null ? renderBrandConfig(fields) : applyBrandConfig(existing, fields);
4709
+ files.push({ rel: CONFIG_FILE, content: cfg });
4710
+ if (o.color !== void 0) {
4711
+ const css = cssFile(root);
4712
+ if (!css) throw new Error("no app/globals.css found: run brand in the root of an app made by `graph8-sdk create`, or pass --dir");
4713
+ const theme = brandTheme(o.color, o.darkColor);
4714
+ notes.push(...theme.notes);
4715
+ notes.push(
4716
+ `colours: light ${theme.light.brand} with ${theme.light.foreground} text (${contrast(theme.light.foreground, theme.light.brand).toFixed(2)}:1), dark ${theme.dark.brand} with ${theme.dark.foreground} text (${contrast(theme.dark.foreground, theme.dark.brand).toFixed(2)}:1)`
4717
+ );
4718
+ const region = brandCss(theme, { color: normalizeHex(o.color), dark: o.darkColor ? normalizeHex(o.darkColor) : void 0 });
4719
+ files.push({ rel: css, content: applyCssRegion(import_node_fs5.default.readFileSync(import_node_path6.default.join(root, css), "utf8"), region) });
4720
+ }
4721
+ const shell = ["components/app-shell.tsx", "src/components/app-shell.tsx"].find((f) => import_node_fs5.default.existsSync(import_node_path6.default.join(root, f)));
4722
+ if (shell && !import_node_fs5.default.readFileSync(import_node_path6.default.join(root, shell), "utf8").includes("g8.config")) {
4723
+ notes.push(
4724
+ `${shell} predates branding, so it does not show the name and logo yet: import { brand } from "@/g8.config" there, and wrap its content in <BrandProvider brand={brand}> (components/g8/_foundation/brand-provider) so the blocks pick up linkLabel and appUrl.`
4725
+ );
4726
+ }
4727
+ return { files, notes };
4728
+ }
4729
+ function same(abs, content) {
4730
+ if (!import_node_fs5.default.existsSync(abs)) return false;
4731
+ const cur = import_node_fs5.default.readFileSync(abs);
4732
+ return Buffer.isBuffer(content) ? cur.equals(content) : cur.toString("utf8") === content;
4733
+ }
4734
+ function runBrand(o, io, cwd, fallbackName) {
4735
+ const root = import_node_path6.default.resolve(cwd, o.dir);
4736
+ if (!import_node_fs5.default.existsSync(import_node_path6.default.join(root, "package.json"))) {
4737
+ io.err(`no package.json in ${root}. Run this from your app's root, or pass --dir.`);
4738
+ return 1;
4739
+ }
4740
+ const problem = brandProblem(o, cwd);
4741
+ if (problem) return io.err(`${problem}
4742
+
4743
+ ${BRAND_USAGE}`), 1;
4744
+ let plan;
4745
+ try {
4746
+ plan = planBrand(o, root, cwd, fallbackName ?? import_node_path6.default.basename(root));
4747
+ } catch (e) {
4748
+ io.err(e.message);
4749
+ return 1;
4750
+ }
4751
+ for (const f of plan.files) {
4752
+ const abs = import_node_path6.default.join(root, f.rel);
4753
+ if (f.content === null) {
4754
+ io.out(`${o.dryRun ? "would remove" : "remove"} ${f.rel}${f.note ? ` (${f.note})` : ""}`);
4755
+ if (!o.dryRun) import_node_fs5.default.rmSync(abs, { force: true });
4756
+ continue;
4757
+ }
4758
+ if (same(abs, f.content)) {
4759
+ io.out(`keep ${f.rel}`);
4760
+ continue;
4761
+ }
4762
+ io.out(`${o.dryRun ? "would " : ""}${import_node_fs5.default.existsSync(abs) ? "update" : "create"} ${f.rel}`);
4763
+ if (!o.dryRun) {
4764
+ import_node_fs5.default.mkdirSync(import_node_path6.default.dirname(abs), { recursive: true });
4765
+ import_node_fs5.default.writeFileSync(abs, f.content);
4766
+ }
4767
+ }
4768
+ plan.notes.forEach((n) => io.out(`note: ${n}`));
4769
+ return 0;
4770
+ }
4771
+
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"));
5114
+
5115
+ // src/generated/billable.ts
5116
+ var BILLABLE_ROUTES = {
5117
+ GET: [
5118
+ "/apps/{app_id}/runtime",
5119
+ "/audience-syncs/{config_id}/runs",
5120
+ "/campaigns/{campaign_id}/dashboard",
5121
+ "/companies/{company_id}/next-best-step",
5122
+ "/contacts/{contact_id}/next-best-step",
5123
+ "/desk/kickoff",
5124
+ "/desk/kickoff/audio",
5125
+ "/radar/competitors/{competitor_id}/discover-pages",
5126
+ "/repos/{repo_id}/scan",
5127
+ "/seo/domains/{domain}/backlinks",
5128
+ "/seo/domains/{domain}/overview",
5129
+ "/voice/campaigns/call-scripts",
5130
+ "/voice/campaigns/email-templates",
5131
+ "/voice/campaigns/templates",
5132
+ "/voice/research/business-data",
5133
+ "/voice/research/competitors",
5134
+ "/voice/research/faq",
5135
+ "/voice/research/keywords",
5136
+ "/voice/research/prospect-company",
5137
+ "/work/routines/{routine_id}/runs"
5138
+ ],
5139
+ PATCH: [
5140
+ "/enrichment/auto-enrichment/configs/{config_id}",
5141
+ "/intent/keywords/{keyword_id}",
5142
+ "/intent/keywords/{keyword_id}/sync-config",
5143
+ "/marketplace/agencies/{slug}",
5144
+ "/marketplace/agencies/{slug}/reps/{sdr_id}",
5145
+ "/marketplace/agencies/{slug}/service-statements/{statement_id}",
5146
+ "/marketplace/applications/{application_id}",
5147
+ "/marketplace/conversations/{conversation_id}/state",
5148
+ "/marketplace/job-posts/{post_id}",
5149
+ "/marketplace/me/meetings/{meeting_id}",
5150
+ "/marketplace/me/messages/{conversation_id}/state",
5151
+ "/marketplace/me/profile",
5152
+ "/marketplace/me/service-statements/{statement_id}",
5153
+ "/marketplace/org/meetings/{meeting_id}",
5154
+ "/radar/competitors/{competitor_id}",
5155
+ "/radar/gaps/{gap_id}",
5156
+ "/research-reports/{report_id}",
5157
+ "/social-listeners/settings",
5158
+ "/social-listeners/{listener_id}"
5159
+ ],
5160
+ POST: [
5161
+ "/accounts/{account_id}/generate-outreach-plan",
5162
+ "/accounts/{account_id}/security-questionnaires",
5163
+ "/accounts/{account_id}/stakeholders/{stakeholder_id}/generate-outreach",
5164
+ "/ad-creatives",
5165
+ "/ad-creatives/batch",
5166
+ "/ad-creatives/concepts",
5167
+ "/ad-creatives/video/clips",
5168
+ "/ad-creatives/video/clips/prompt-enhance",
5169
+ "/ad-creatives/video/clips/{clip_id}/continue",
5170
+ "/ad-creatives/video/clips/{clip_id}/extend",
5171
+ "/ad-creatives/video/clips/{clip_id}/regenerate",
5172
+ "/ad-creatives/{creative_id}/ai-review",
5173
+ "/ad-creatives/{creative_id}/chat-edit",
5174
+ "/ad-creatives/{creative_id}/image-edit-at-point",
5175
+ "/ad-creatives/{creative_id}/persona-lens",
5176
+ "/ad-creatives/{creative_id}/regenerate-copy",
5177
+ "/ad-creatives/{creative_id}/regenerate-image",
5178
+ "/ad-creatives/{creative_id}/retry",
5179
+ "/ad-creatives/{creative_id}/rewrite-field",
5180
+ "/ad-creatives/{creative_id}/size-set",
5181
+ "/agent/chat",
5182
+ "/agent/onboarding/discovery/turn",
5183
+ "/agent/operators/{operator}/approvals/{task_id}/decide",
5184
+ "/agent/sessions/{session_id}/voice",
5185
+ "/agent/work-items",
5186
+ "/app-pages/{page_id}/generate",
5187
+ "/campaigns/generate-ideas",
5188
+ "/campaigns/generate-ideas-async",
5189
+ "/campaigns/recommend-personas",
5190
+ "/campaigns/{campaign_id}/audience-pipeline/accept-filters",
5191
+ "/campaigns/{campaign_id}/audience-pipeline/trigger",
5192
+ "/campaigns/{campaign_id}/documents/{document_id}/regenerate",
5193
+ "/campaigns/{campaign_id}/sequence/steps/{step_id}/generate",
5194
+ "/companies/enrich/bulk",
5195
+ "/companies/enrich/people/bulk",
5196
+ "/companies/enrich/stakeholders/bulk",
5197
+ "/companies/import",
5198
+ "/companies/{company_id}/documents/types/{doc_type}/generate",
5199
+ "/companies/{company_id}/documents/types/{doc_type}/regenerate",
5200
+ "/companies/{company_id}/enrich",
5201
+ "/companies/{company_id}/retry-intelligence",
5202
+ "/companies/{company_id}/retry-synthesis",
5203
+ "/companies/{company_id}/workflows/{workflow_type}/start",
5204
+ "/company-profile/generate",
5205
+ "/company-profile/reindex",
5206
+ "/company-profile/retry",
5207
+ "/contacts/unlock-info",
5208
+ "/contacts/verify-email",
5209
+ "/contacts/verify-email/async",
5210
+ "/contacts/workflow-executions/run",
5211
+ "/contacts/{contact_id}/documents/generate-all",
5212
+ "/contacts/{contact_id}/documents/types/{doc_type}/generate",
5213
+ "/contacts/{contact_id}/documents/types/{doc_type}/regenerate",
5214
+ "/content-workspace/import/crawl",
5215
+ "/content/calendar/ideas/{idea_id}/create-content",
5216
+ "/content/calendar/mine-signals",
5217
+ "/content/calendar/repurpose/ad",
5218
+ "/content/calendar/repurpose/linkedin-post",
5219
+ "/content/grids/{grid_id}/columns/{column_id}/run",
5220
+ "/content/grids/{grid_id}/rows/{row_id}/run/{column_id}",
5221
+ "/content/pages/enrich-seo-bulk",
5222
+ "/content/pages/{page_id}/enrich-seo",
5223
+ "/copilot/chat",
5224
+ "/copilot/landing-page-action",
5225
+ "/copilot/memory/promote",
5226
+ "/customer-success/companies/{company_id}/brief/synthesize",
5227
+ "/deals/{deal_id}/documents/generate-all",
5228
+ "/deals/{deal_id}/documents/types/{doc_type}/generate",
5229
+ "/deals/{deal_id}/documents/types/{doc_type}/regenerate",
5230
+ "/deals/{deal_id}/next-best-step",
5231
+ "/desk/accounts/{account_id}/committee/{contact_id}/find-linkedin",
5232
+ "/desk/accounts/{account_id}/plan/draft",
5233
+ "/desk/ask",
5234
+ "/desk/decisions/{decision_id}/replan",
5235
+ "/desk/email-improve",
5236
+ "/desk/email-score",
5237
+ "/desk/org-settings/plan-stages/run",
5238
+ "/desk/voice-memo",
5239
+ "/dialer/parallel-calls/grading/backfill",
5240
+ "/dialer/sessions/prewarm-voicemails",
5241
+ "/enrichment/ai-formula/generate",
5242
+ "/enrichment/ai-formula/generate-condition",
5243
+ "/enrichment/ai/batch/enrich",
5244
+ "/enrichment/ai/enrich",
5245
+ "/enrichment/ai/qualification/generate-templates",
5246
+ "/enrichment/ai/qualification/run",
5247
+ "/enrichment/auto-enrichment/configs",
5248
+ "/enrichment/enrich",
5249
+ "/enrichment/find-people/configure",
5250
+ "/enrichment/find-people/search-import",
5251
+ "/enrichment/find-people/{config_id}/import",
5252
+ "/enrichment/find-people/{config_id}/preview",
5253
+ "/enrichment/find-people/{config_id}/run",
5254
+ "/enrichment/generate-prompt",
5255
+ "/enrichment/jobs/{job_id}/resume",
5256
+ "/enrichment/list-routing/generate-filter",
5257
+ "/enrichment/lists/{list_id}/pipelines",
5258
+ "/enrichment/lists/{list_id}/pipelines/ai-draft",
5259
+ "/enrichment/lists/{list_id}/pipelines/{pipeline_id}/enabled",
5260
+ "/enrichment/lists/{list_id}/pipelines/{pipeline_id}/runs",
5261
+ "/enrichment/lookup/company",
5262
+ "/enrichment/lookup/person",
5263
+ "/enrichment/person",
5264
+ "/enrichment/record-sync/{entity_type}/run",
5265
+ "/enrichment/verify-email",
5266
+ "/enrichment/waterfall/configs",
5267
+ "/enrichment/waterfall/enrich",
5268
+ "/global-context/generate-all",
5269
+ "/global-context/generate/{doc_type}",
5270
+ "/hiring-signals/aggregators",
5271
+ "/hiring-signals/companies",
5272
+ "/hiring-signals/companies/job-counts",
5273
+ "/hiring-signals/companies/resolve-ids",
5274
+ "/hiring-signals/decision-makers",
5275
+ "/hiring-signals/job-changes/match",
5276
+ "/hiring-signals/jobs",
5277
+ "/hiring-signals/recruiters",
5278
+ "/icps/generate",
5279
+ "/inbox/drafts/generate",
5280
+ "/intelligence/analyze",
5281
+ "/intelligence/retry",
5282
+ "/intent/contacts/metadata",
5283
+ "/intent/keywords/add",
5284
+ "/intent/keywords/auto-seed-proposals",
5285
+ "/intent/keywords/create-from-domain",
5286
+ "/intent/keywords/create-from-search",
5287
+ "/intent/keywords/list",
5288
+ "/intent/keywords/{keyword_id}/companies",
5289
+ "/intent/keywords/{keyword_id}/contacts",
5290
+ "/intent/keywords/{keyword_id}/filters",
5291
+ "/intent/keywords/{keyword_id}/job-resolution",
5292
+ "/intent/keywords/{keyword_id}/sync",
5293
+ "/intent/keywords/{keyword_id}/urls",
5294
+ "/intent/pages-by-domain",
5295
+ "/intent/pages/contacts",
5296
+ "/intent/pages/search",
5297
+ "/intent/pages/visitor-counts",
5298
+ "/intent/pages/visitors",
5299
+ "/landing-pages",
5300
+ "/landing-pages/clone-homepage",
5301
+ "/landing-pages/clone-html",
5302
+ "/landing-pages/clone-url",
5303
+ "/landing-pages/components/extract",
5304
+ "/landing-pages/ensure-base-template",
5305
+ "/landing-pages/{landing_page_id}/ai-review",
5306
+ "/landing-pages/{landing_page_id}/ai-review/fix-with-ai",
5307
+ "/landing-pages/{landing_page_id}/edit",
5308
+ "/landing-pages/{landing_page_id}/edit-element",
5309
+ "/landing-pages/{landing_page_id}/edit-page",
5310
+ "/landing-pages/{landing_page_id}/generate-seo-metadata",
5311
+ "/landing-pages/{landing_page_id}/rewrite-text",
5312
+ "/leads/generate-content/batches",
5313
+ "/leads/{lead_id}/generate-content",
5314
+ "/library/case-studies/backfill",
5315
+ "/lifecycle-contacts/unlock",
5316
+ "/mailboxes/schedule-buy/plans",
5317
+ "/mailboxes/schedule-buy/plans/{plan_id}/resume",
5318
+ "/mailboxes/uncancel",
5319
+ "/mailboxes/{mailbox_id}/health/placement-test",
5320
+ "/marketplace/agencies/{slug}/intake/upload",
5321
+ "/marketplace/agencies/{slug}/reps/{sdr_id}/unlist",
5322
+ "/marketplace/agencies/{slug}/reps/{sdr_id}/video-intro",
5323
+ "/marketplace/agencies/{slug}/service-statements",
5324
+ "/marketplace/agencies/{slug}/service-statements/{statement_id}/line-items",
5325
+ "/marketplace/agencies/{slug}/service-statements/{statement_id}/submit",
5326
+ "/marketplace/commissions/entries",
5327
+ "/marketplace/commissions/entries/{entry_id}/quality",
5328
+ "/marketplace/commissions/entries/{entry_id}/reversals",
5329
+ "/marketplace/commissions/entries/{entry_id}/statement",
5330
+ "/marketplace/commissions/plans",
5331
+ "/marketplace/commissions/plans/{version_id}/activate",
5332
+ "/marketplace/commissions/preview",
5333
+ "/marketplace/conversations/{conversation_id}/read",
5334
+ "/marketplace/job-posts",
5335
+ "/marketplace/job-posts/{post_id}/jd",
5336
+ "/marketplace/me/headshot",
5337
+ "/marketplace/me/invoices",
5338
+ "/marketplace/me/invoices/{invoice_id}/approve",
5339
+ "/marketplace/me/messages/{conversation_id}/read",
5340
+ "/marketplace/me/offers/{hiring_id}/accept",
5341
+ "/marketplace/me/offers/{hiring_id}/reject",
5342
+ "/marketplace/me/payout-account/w8-certify",
5343
+ "/marketplace/me/resume",
5344
+ "/marketplace/me/service-statements",
5345
+ "/marketplace/me/service-statements/{statement_id}/line-items",
5346
+ "/marketplace/me/service-statements/{statement_id}/revise",
5347
+ "/marketplace/me/service-statements/{statement_id}/submit",
5348
+ "/marketplace/me/video-intro",
5349
+ "/marketplace/me/work-certificates",
5350
+ "/marketplace/opportunities/{post_id}/apply",
5351
+ "/marketplace/org/hirings/{hiring_id}/meetings",
5352
+ "/marketplace/org/hirings/{hiring_id}/terminate",
5353
+ "/marketplace/org/hirings/{hiring_id}/withdraw",
5354
+ "/marketplace/org/invoices/{invoice_id}/approve",
5355
+ "/marketplace/org/invoices/{invoice_id}/dispute",
5356
+ "/marketplace/org/service-statements/{statement_id}/approve",
5357
+ "/marketplace/org/service-statements/{statement_id}/line-items",
5358
+ "/marketplace/org/service-statements/{statement_id}/reject",
5359
+ "/marketplace/org/service-statements/{statement_id}/request-changes",
5360
+ "/marketplace/shortlist/export",
5361
+ "/marketplace/talent/{sdr_id}/hire",
5362
+ "/marketplace/talent/{sdr_id}/match/analyze",
5363
+ "/marketplace/talent/{sdr_id}/match/refresh",
5364
+ "/marketplace/terms-acknowledgements/ack-all",
5365
+ "/meeting-transcripts/{transcript_id}/follow-up-draft",
5366
+ "/meeting-transcripts/{transcript_id}/review/regenerate",
5367
+ "/newsletters/templates/clone",
5368
+ "/newsletters/templates/custom",
5369
+ "/newsletters/templates/layout",
5370
+ "/newsletters/{newsletter_id}/issues/{issue_id}/generate",
5371
+ "/newsletters/{newsletter_id}/issues/{issue_id}/rewrite-text",
5372
+ "/newsletters/{newsletter_id}/subscribers/import-audience",
5373
+ "/newsletters/{newsletter_id}/subscribers/verify",
5374
+ "/org-videos/generate",
5375
+ "/org/brand-snapshot/refresh",
5376
+ "/personas/generate",
5377
+ "/phone-numbers/purchase",
5378
+ "/pipelines/suggest",
5379
+ "/placement/tests",
5380
+ "/provider-domains/add-external",
5381
+ "/provider-domains/check-availability",
5382
+ "/provider-domains/checkout-purchase",
5383
+ "/provider-domains/checkout-transfer",
5384
+ "/provider-domains/{domain_id}/auto-warmup",
5385
+ "/provider-domains/{domain_id}/check-propagation",
5386
+ "/provider-domains/{domain_id}/checkout-mailboxes",
5387
+ "/provider-domains/{domain_id}/connect-provider",
5388
+ "/provider-domains/{domain_id}/dns",
5389
+ "/public/enrich/lookup",
5390
+ "/radar/aeo/run",
5391
+ "/radar/competitors",
5392
+ "/radar/competitors/bulk",
5393
+ "/radar/competitors/{competitor_id}/scan",
5394
+ "/radar/gaps/analyze",
5395
+ "/radar/initiatives/measure",
5396
+ "/radar/opportunities/approve-all",
5397
+ "/radar/opportunities/{gap_id}/approve",
5398
+ "/radar/reports",
5399
+ "/radar/scan-all",
5400
+ "/repos/{repo_id}/scan",
5401
+ "/research-reports/generate",
5402
+ "/research-reports/generate-all",
5403
+ "/research-reports/retry",
5404
+ "/research-reports/workflow/resume",
5405
+ "/revenue/stage-docs/{lifecycle_entry_id}/{entity}/backfill",
5406
+ "/scrape/requests",
5407
+ "/search/companies",
5408
+ "/search/companies/save",
5409
+ "/search/contacts",
5410
+ "/search/contacts/save",
5411
+ "/seo/audits",
5412
+ "/seo/keywords/research",
5413
+ "/sequencer/content/email/generate",
5414
+ "/sequencer/content/instructions/improve",
5415
+ "/sequencer/content/linkedin/generate",
5416
+ "/sequencer/templates/steps/generate",
5417
+ "/social-listeners",
5418
+ "/social-listeners/auto-seed/all",
5419
+ "/social-listeners/auto-seed/brand",
5420
+ "/social-listeners/auto-seed/competitors",
5421
+ "/social-listeners/auto-seed/engagement",
5422
+ "/social-listeners/auto-seed/keywords",
5423
+ "/social-listeners/bulk-from-list",
5424
+ "/social-listeners/integrations/x",
5425
+ "/social-listeners/integrations/x/test",
5426
+ "/social-listeners/posts/{post_id}/engagements/add-to-list",
5427
+ "/social-listeners/posts/{post_id}/mark",
5428
+ "/social-listeners/posts/{post_id}/save-contact",
5429
+ "/social-listeners/queries/generate",
5430
+ "/social/posts/generate",
5431
+ "/tam/generate",
5432
+ "/tam/regenerate",
5433
+ "/tasks/{task_id}/start",
5434
+ "/team-members/me/enrich-linkedin",
5435
+ "/team-members/{team_member_id}/enrich-linkedin",
5436
+ "/team-members/{team_member_id}/generate-post",
5437
+ "/teams/auto-compose/suggest",
5438
+ "/voice/agents/{agent_id}/documents/upload",
5439
+ "/voice/call-flow-examples/transcribe",
5440
+ "/voice/call-scripts/generate",
5441
+ "/voice/campaigns/call-scripts",
5442
+ "/voice/campaigns/email-enhancements",
5443
+ "/voice/campaigns/email-spintax",
5444
+ "/voice/campaigns/emails",
5445
+ "/voice/campaigns/linkedin-messages",
5446
+ "/voice/campaigns/onboarding/messages",
5447
+ "/voice/campaigns/onboarding/threads/actions",
5448
+ "/voice/campaigns/voicemails",
5449
+ "/voice/chat-agent/messages",
5450
+ "/voice/collections/{collection_id}/files/paste-text",
5451
+ "/voice/collections/{collection_id}/files/upload",
5452
+ "/voice/collections/{collection_id}/files/upload-multiple",
5453
+ "/voice/collections/{collection_id}/reindex",
5454
+ "/voice/collections/{collection_id}/scrape-sources",
5455
+ "/voice/collections/{collection_id}/scrape-sources/{source_id}/run",
5456
+ "/voice/email-agent/batch",
5457
+ "/voice/email-agent/compose",
5458
+ "/voice/email-agent/draft-reply",
5459
+ "/voice/icp/queries",
5460
+ "/voice/knowledge-base/files/upload",
5461
+ "/voice/receptionist/messages",
5462
+ "/voice/research/company",
5463
+ "/voice/research/org-chart",
5464
+ "/voice/research/prospect-data",
5465
+ "/voice/research/prospect-profile",
5466
+ "/voice/topics/classify",
5467
+ "/voice/twins",
5468
+ "/voice/twins/extract-linkedin",
5469
+ "/voice/video-recordings/upload",
5470
+ "/voice/voice-library/onboarding-voice",
5471
+ "/voice/voice-library/voices",
5472
+ "/voice/worksheets/calculate",
5473
+ "/work/signals/promote",
5474
+ "/workbench/tables/{table_id}/ai/generate-prompt",
5475
+ "/workbench/tables/{table_id}/ai/groups/{group_id}/run",
5476
+ "/workbench/tables/{table_id}/ai/preview",
5477
+ "/workbench/tables/{table_id}/ai/run",
5478
+ "/workbench/tables/{table_id}/ai/run-all",
5479
+ "/workbench/tables/{table_id}/columns/{col_id}/ai/run",
5480
+ "/workbench/tables/{table_id}/find-people/configure",
5481
+ "/workbench/tables/{table_id}/find-people/{config_id}/run",
5482
+ "/workbench/tables/{table_id}/import/search",
5483
+ "/workbench/tables/{table_id}/import/search/research/preview",
5484
+ "/workbench/tables/{table_id}/pipelines",
5485
+ "/workbench/tables/{table_id}/scraper/providers/{provider}/runs",
5486
+ "/workbench/tables/{table_id}/scraper/runs",
5487
+ "/workbench/tables/{table_id}/suggest-mappings",
5488
+ "/workbench/tables/{table_id}/triggers",
5489
+ "/workbench/tables/{table_id}/unlock-rows",
5490
+ "/workbench/tables/{table_id}/waterfall/configs/{column_id}/run"
5491
+ ],
5492
+ PUT: [
5493
+ "/agent/operators/{operator}/enabled",
5494
+ "/agent/operators/{operator}/skills/{skill_key}",
5495
+ "/deliverability/autopilot/preferences",
5496
+ "/enrichment/auto-enrichment/settings/{entity_type}",
5497
+ "/enrichment/lists/{list_id}/pipelines/{pipeline_id}",
5498
+ "/enrichment/record-sync/{entity_type}/policy",
5499
+ "/enrichment/waterfall/configs/{column_id}",
5500
+ "/marketplace/me/invoices/{invoice_id}",
5501
+ "/marketplace/me/rates",
5502
+ "/radar/settings",
5503
+ "/workbench/tables/{table_id}/auto-enrich",
5504
+ "/workbench/tables/{table_id}/pipelines/{pipeline_id}",
5505
+ "/workbench/tables/{table_id}/schedule",
5506
+ "/workbench/tables/{table_id}/triggers/{trigger_id}"
5507
+ ]
5508
+ };
5509
+
5510
+ // src/generated/external.ts
5511
+ var EXTERNAL_ROUTES = {
5512
+ GET: [
5513
+ "/dialer/sessions/token",
5514
+ "/marketplace/me/connect/dashboard-link",
5515
+ "/marketplace/me/connect/onboarding-link",
5516
+ "/marketplace/me/payout-account/login-link",
5517
+ "/marketplace/me/payout-account/onboarding-link",
5518
+ "/voice/mcp-servers/{mcp_server_id}/tools"
5519
+ ],
5520
+ PATCH: [
5521
+ "/ad-campaigns/{campaign_id}/ads/{ad_id}",
5522
+ "/appointments/bookings/{booking_uid}/location",
5523
+ "/appointments/bookings/{booking_uid}/sequence",
5524
+ "/appointments/workflows/{workflow_id}",
5525
+ "/audience-syncs/{config_id}",
5526
+ "/campaigns/{campaign_id}",
5527
+ "/campaigns/{campaign_id}/full",
5528
+ "/content/calendar/{content_type}/{content_id}/schedule",
5529
+ "/inbox/mailboxes/bulk",
5530
+ "/inbox/mailboxes/{mailbox}/settings",
5531
+ "/integrations/crm/meetings/{event_id}",
5532
+ "/newsletters/{newsletter_id}/subscribers/{subscriber_id}/unsubscribe",
5533
+ "/quotes/{quote_id}",
5534
+ "/sequences/{sequence_id}",
5535
+ "/sequences/{sequence_id}/steps/{step_id}",
5536
+ "/voice/dialer/sessions/{session_id}/status",
5537
+ "/voice/dispositions/{disposition_id}",
5538
+ "/webhooks/{webhook_id}"
5539
+ ],
5540
+ POST: [
5541
+ "/ad-campaigns/images",
5542
+ "/ad-campaigns/status-refresh",
5543
+ "/ad-campaigns/{campaign_id}/ads",
5544
+ "/ad-campaigns/{campaign_id}/ads/batch",
5545
+ "/ad-campaigns/{campaign_id}/ads/{ad_id}/status",
5546
+ "/ad-campaigns/{campaign_id}/edit-preview",
5547
+ "/ad-campaigns/{campaign_id}/retry",
5548
+ "/ad-campaigns/{campaign_id}/status-refresh",
5549
+ "/appointments/bookings",
5550
+ "/appointments/bookings/import",
5551
+ "/appointments/bookings/{booking_uid}/confirm",
5552
+ "/appointments/bookings/{booking_uid}/guests",
5553
+ "/appointments/bookings/{booking_uid}/mark-absent",
5554
+ "/appointments/bookings/{booking_uid}/notes",
5555
+ "/appointments/bookings/{booking_uid}/request-reschedule",
5556
+ "/appointments/bookings/{booking_uid}/reschedule",
5557
+ "/appointments/calendars/selected",
5558
+ "/appointments/conferencing/connect/{provider}",
5559
+ "/appointments/conferencing/{provider}/default",
5560
+ "/appointments/workflows",
5561
+ "/appointments/workflows/{workflow_id}/toggle",
5562
+ "/audience-syncs",
5563
+ "/audience-syncs/google/conversion-actions",
5564
+ "/audience-syncs/google/conversions",
5565
+ "/audience-syncs/google/video-assets/library",
5566
+ "/audience-syncs/google/video-assets/links",
5567
+ "/audience-syncs/google/video-assets/upload-urls",
5568
+ "/audience-syncs/google/video-assets/uploads",
5569
+ "/audience-syncs/{config_id}/launch",
5570
+ "/audience-syncs/{config_id}/trigger",
5571
+ "/campaigns",
5572
+ "/campaigns/{campaign_id}/ad-images/{image_id}/hide",
5573
+ "/campaigns/{campaign_id}/documents",
5574
+ "/campaigns/{campaign_id}/launch",
5575
+ "/campaigns/{campaign_id}/sequence/steps",
5576
+ "/content/calendar/{content_type}/{content_id}/retry-publish",
5577
+ "/crm-syncs/{provider}/companies/push",
5578
+ "/crm-syncs/{provider}/contacts/push",
5579
+ "/crm-syncs/{provider}/lists/push",
5580
+ "/customer-success/companies/{company_id}/sync",
5581
+ "/desk/accounts/{account_id}/committee/plays/{action_id}/run",
5582
+ "/desk/accounts/{account_id}/committee/{contact_id}/linkedin-invite",
5583
+ "/desk/decisions/{decision_id}/plays/{action_id}/run",
5584
+ "/desk/decisions/{decision_id}/send-reply",
5585
+ "/desk/decisions/{decision_id}/send-sms",
5586
+ "/desk/plays/approvals/{execution_id}/decide",
5587
+ "/dialer/parallel-calls/calls/{livekit_room_name}/drop-voicemail",
5588
+ "/dialer/parallel-calls/manual-dial",
5589
+ "/dialer/parallel-calls/redial-contact",
5590
+ "/dialer/parallel-calls/sessions/start",
5591
+ "/dialer/sessions/calls/drop-voicemail",
5592
+ "/dialer/sessions/{session_id}/next-batch",
5593
+ "/envelopes/{envelope_id}/resend",
5594
+ "/envelopes/{envelope_id}/send",
5595
+ "/feedback/sending-domains/verify",
5596
+ "/feedback/surveys/{survey_id}/send",
5597
+ "/forms/deliveries/{receipt_id}/retry",
5598
+ "/forms/{form_key}/backfills",
5599
+ "/forms/{form_key}/delivery/pause",
5600
+ "/forms/{form_key}/delivery/resume",
5601
+ "/inbox/emails/compose",
5602
+ "/inbox/emails/{email_id}/send",
5603
+ "/inbox/linkedin/accounts/sync",
5604
+ "/inbox/linkedin/accounts/{account_id}/enable",
5605
+ "/inbox/linkedin/compose",
5606
+ "/inbox/mailboxes/activate",
5607
+ "/inbox/mailboxes/{mailbox}/sync",
5608
+ "/inbox/sms/compose",
5609
+ "/inbox/{reply_id}/assign",
5610
+ "/inbox/{reply_id}/send",
5611
+ "/inbox/{reply_id}/tag",
5612
+ "/integrations/connections",
5613
+ "/integrations/connections/session-token",
5614
+ "/integrations/crm-sync/audiences",
5615
+ "/integrations/crm-sync/contacts/pull",
5616
+ "/integrations/crm-sync/contacts/push",
5617
+ "/integrations/crm-sync/deals/pull",
5618
+ "/integrations/crm-sync/deals/push",
5619
+ "/integrations/crm-sync/leads/previews",
5620
+ "/integrations/crm-sync/leads/pull",
5621
+ "/integrations/crm-sync/leads/push",
5622
+ "/integrations/crm-sync/pull",
5623
+ "/integrations/crm-sync/push",
5624
+ "/integrations/crm/connections/{connection_id}/batch/contacts",
5625
+ "/integrations/crm/connections/{connection_id}/companies",
5626
+ "/integrations/crm/connections/{connection_id}/contacts",
5627
+ "/integrations/crm/connections/{connection_id}/deals",
5628
+ "/integrations/crm/connections/{connection_id}/leads",
5629
+ "/integrations/crm/lists",
5630
+ "/integrations/crm/lists/memberships",
5631
+ "/integrations/crm/meetings",
5632
+ "/integrations/linkedin-outreach/campaigns/{campaign_id}/pause",
5633
+ "/integrations/linkedin-outreach/campaigns/{campaign_id}/resume",
5634
+ "/integrations/linkedin-outreach/connection/reconnect",
5635
+ "/integrations/linkedin-outreach/lists",
5636
+ "/integrations/linkedin-outreach/lists/{list_id}/leads",
5637
+ "/integrations/linkedin-outreach/workspaces/sync",
5638
+ "/integrations/migrations/delta-sync",
5639
+ "/integrations/migrations/discovery",
5640
+ "/integrations/migrations/{migration_id}/confirm",
5641
+ "/integrations/sugarcrm/connect",
5642
+ "/integrations/sugarcrm/reconnect",
5643
+ "/landing-pages/{landing_page_id}/publish",
5644
+ "/landing-pages/{landing_page_id}/unpublish",
5645
+ "/linkedin/accounts/sync",
5646
+ "/marketplace/agencies/{slug}/reps/{sdr_id}/publish",
5647
+ "/marketplace/me/messages/{conversation_id}/reply",
5648
+ "/marketplace/me/payout-account/bank-details",
5649
+ "/marketplace/me/payout-account/methods",
5650
+ "/marketplace/me/payout-account/setup",
5651
+ "/marketplace/me/sync-workbench",
5652
+ "/marketplace/talent/{sdr_id}/messages",
5653
+ "/marketplace/talent/{sdr_id}/request",
5654
+ "/newsletters",
5655
+ "/newsletters/{newsletter_id}/activate",
5656
+ "/newsletters/{newsletter_id}/issues",
5657
+ "/newsletters/{newsletter_id}/issues/{issue_id}/duplicate",
5658
+ "/newsletters/{newsletter_id}/issues/{issue_id}/send",
5659
+ "/newsletters/{newsletter_id}/issues/{issue_id}/send-test",
5660
+ "/newsletters/{newsletter_id}/issues/{issue_id}/send/resume",
5661
+ "/newsletters/{newsletter_id}/resume",
5662
+ "/newsletters/{newsletter_id}/settings/verify-domain",
5663
+ "/newsletters/{newsletter_id}/subscribers",
5664
+ "/nurtures/{nurture_id}/launch",
5665
+ "/org/sso/sync",
5666
+ "/phone-numbers/a2p/register",
5667
+ "/profile/deal-automations/sync-now",
5668
+ "/programmatic-campaigns/{campaign_id}/pages/actions",
5669
+ "/programmatic-campaigns/{campaign_id}/pages/{page_id}/actions",
5670
+ "/provider-domains/{domain_id}/forwarding",
5671
+ "/provider-domains/{domain_id}/mailbox-profile-picture",
5672
+ "/provider-domains/{domain_id}/regenerate-nameservers",
5673
+ "/provider-domains/{domain_id}/resend-transfer-password",
5674
+ "/provider-domains/{domain_id}/resend-verification",
5675
+ "/provider-domains/{domain_id}/set-nameservers",
5676
+ "/provider-usage/refresh",
5677
+ "/provider-usage/sync",
5678
+ "/quote-billing-contact",
5679
+ "/quote-contacts",
5680
+ "/quote-settings/documents",
5681
+ "/quote-templates",
5682
+ "/quotes",
5683
+ "/quotes/batch",
5684
+ "/quotes/{quote_id}/duplicate",
5685
+ "/quotes/{quote_id}/edit-as-draft",
5686
+ "/quotes/{quote_id}/mark-accepted",
5687
+ "/quotes/{quote_id}/send",
5688
+ "/quotes/{quote_id}/unarchive",
5689
+ "/radar/alerts/dispatch",
5690
+ "/repos/{repo_id}/campaigns/{campaign_id}/launch",
5691
+ "/roles/invite",
5692
+ "/sales-coach/interviews/{interview_id}/members",
5693
+ "/sales-coach/interviews/{interview_id}/members/{member_id}/send-invite",
5694
+ "/sequencer/content/voice/test-call",
5695
+ "/sequences",
5696
+ "/sequences/generate-spintax",
5697
+ "/sequences/{sequence_id}/contacts",
5698
+ "/sequences/{sequence_id}/contacts/{contact_id}/resume",
5699
+ "/sequences/{sequence_id}/pause",
5700
+ "/sequences/{sequence_id}/resume",
5701
+ "/sequences/{sequence_id}/run",
5702
+ "/sequences/{sequence_id}/sync",
5703
+ "/settings/scrape-config/crawl",
5704
+ "/skills/{action_id}/execute",
5705
+ "/skills/{action_id}/test-runs",
5706
+ "/social-listeners/posts/{post_id}/add-to-sequence",
5707
+ "/social/posts/{post_id}/engagers/add-to-audience",
5708
+ "/social/posts/{post_id}/engagers/enroll",
5709
+ "/social/posts/{post_id}/publish",
5710
+ "/social/posts/{post_id}/schedule",
5711
+ "/stripe/api-key",
5712
+ "/stripe/sync",
5713
+ "/team-members/sync",
5714
+ "/voice/calls",
5715
+ "/voice/dialer/sessions",
5716
+ "/voice/dialer/sessions/{session_id}/resume",
5717
+ "/voice/dispositions",
5718
+ "/voice/inbound-calls/decline",
5719
+ "/voice/inbound-calls/dialer/{call_id}/decline",
5720
+ "/voice/inbound-calls/{room_name}/claim",
5721
+ "/voice/mcp-servers/{mcp_server_id}/test",
5722
+ "/webhooks",
5723
+ "/webhooks/{webhook_id}/rotate-secret",
5724
+ "/website-editor/publish",
5725
+ "/website-editor/sync",
5726
+ "/workbench/scraper/providers/{provider}/connection/test",
5727
+ "/workbench/tables/{table_id}/import/crm",
5728
+ "/workbench/tables/{table_id}/send-to-sequence",
5729
+ "/workflows",
5730
+ "/workflows/executions/approve",
5731
+ "/workflows/executions/bulk-restart",
5732
+ "/workflows/executions/request-changes",
5733
+ "/workflows/executions/{execution_id}/pause",
5734
+ "/workflows/executions/{execution_id}/restart",
5735
+ "/workflows/executions/{execution_id}/resume",
5736
+ "/workflows/executions/{execution_id}/stop",
5737
+ "/workflows/integrations/{provider_name}/test",
5738
+ "/workflows/validate",
5739
+ "/workflows/workbenches",
5740
+ "/workflows/{action_id}/execute",
5741
+ "/workflows/{action_id}/trigger-reset"
5742
+ ],
5743
+ PUT: [
5744
+ "/ad-campaigns/{campaign_id}",
5745
+ "/appointments/calendars/destination",
5746
+ "/appointments/conferencing/{provider}/link",
5747
+ "/campaigns/{campaign_id}/audience",
5748
+ "/campaigns/{campaign_id}/documents/{document_id}",
5749
+ "/campaigns/{campaign_id}/sequence",
5750
+ "/campaigns/{campaign_id}/sequence/steps/{step_id}",
5751
+ "/desk/autonomy",
5752
+ "/desk/org-settings",
5753
+ "/inbox/linkedin/accounts/{account_id}",
5754
+ "/integrations/crm-sync/pull-enabled",
5755
+ "/integrations/crm-sync/push-enabled",
5756
+ "/integrations/crm/connections/{connection_id}/batch/contacts",
5757
+ "/integrations/crm/connections/{connection_id}/companies/{company_id}",
5758
+ "/integrations/crm/connections/{connection_id}/contacts/{contact_id}",
5759
+ "/integrations/crm/connections/{connection_id}/deals/{deal_id}",
5760
+ "/integrations/crm/connections/{connection_id}/leads/{lead_id}",
5761
+ "/integrations/linkedin-outreach/workspaces/active",
5762
+ "/linkedin/accounts/{account_id}/schedule",
5763
+ "/newsletters/{newsletter_id}/issues/{issue_id}",
5764
+ "/quote-settings",
5765
+ "/quote-templates/{template_id}",
5766
+ "/quotes/{quote_id}",
5767
+ "/sms/phone-numbers/{phone_number}",
5768
+ "/social/autopilot/{account_id}",
5769
+ "/workflows/{action_id}"
5770
+ ]
5771
+ };
5772
+
5773
+ // src/errors.ts
5774
+ var G8ErrorClass = class _G8ErrorClass extends Error {
5775
+ constructor(args) {
5776
+ super(args.message);
5777
+ this.name = "G8Error";
5778
+ this.status = args.status;
5779
+ this.type = args.type;
5780
+ this.code = args.code;
5781
+ this.requestId = args.requestId;
5782
+ this.detail = args.detail;
5783
+ this.retryable = args.retryable ?? false;
5784
+ Object.setPrototypeOf(this, _G8ErrorClass.prototype);
5785
+ }
5786
+ };
5787
+ var REGISTRY = /* @__PURE__ */ Symbol.for("@graph8/sdk/G8Error");
5788
+ var registry = globalThis;
5789
+ var G8Error = registry[REGISTRY] ?? (registry[REGISTRY] = G8ErrorClass);
5790
+
5791
+ // src/http.ts
5792
+ function isRetryableStatus(status) {
5793
+ return status === 429 || status >= 500 && status <= 599;
5794
+ }
5795
+ function isWriteMethod(method) {
5796
+ const m = method.toUpperCase();
5797
+ return m === "POST" || m === "PUT" || m === "PATCH" || m === "DELETE";
5798
+ }
5799
+ var API_BASE_SEGMENT = "/api/v1";
5800
+ var billableMatchers = null;
5801
+ var externalMatchers = null;
5802
+ function templateToRegexSource(template) {
5803
+ return template.split(/(\{[^}/]+\})/).map((part) => /^\{[^}/]+\}$/.test(part) ? "[^/]+" : part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
5804
+ }
5805
+ function buildMatchers(routes) {
5806
+ const out = /* @__PURE__ */ new Map();
5807
+ for (const [m, templates] of Object.entries(routes)) {
5808
+ if (!templates.length) continue;
5809
+ out.set(m, new RegExp(`^(?:${templates.map(templateToRegexSource).join("|")})/?$`));
5810
+ }
5811
+ return out;
5812
+ }
5813
+ function matcherFor(method) {
5814
+ if (!billableMatchers) billableMatchers = buildMatchers(BILLABLE_ROUTES);
5815
+ return billableMatchers.get(method.toUpperCase());
5816
+ }
5817
+ function externalMatcherFor(method) {
5818
+ if (!externalMatchers) externalMatchers = buildMatchers(EXTERNAL_ROUTES);
5819
+ return externalMatchers.get(method.toUpperCase());
5820
+ }
5821
+ function apiRelativePath(urlOrPath) {
5822
+ let p = urlOrPath.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/]*/, "");
5823
+ p = p.split(/[?#]/, 1)[0];
5824
+ const at = p.indexOf(API_BASE_SEGMENT + "/");
5825
+ if (at < 0) return null;
5826
+ return p.slice(at + API_BASE_SEGMENT.length);
5827
+ }
5828
+ function isBillableOperation(method, urlOrPath) {
5829
+ const matcher = matcherFor(method);
5830
+ if (!matcher) return false;
5831
+ const p = apiRelativePath(urlOrPath);
5832
+ return p != null && matcher.test(p);
5833
+ }
5834
+ function isExternalOperation(method, urlOrPath) {
5835
+ const matcher = externalMatcherFor(method);
5836
+ if (!matcher) return false;
5837
+ const p = apiRelativePath(urlOrPath);
5838
+ return p != null && matcher.test(p);
5839
+ }
5840
+ function isNoBlindRetryOperation(method, urlOrPath) {
5841
+ return isBillableOperation(method, urlOrPath) || isExternalOperation(method, urlOrPath);
5842
+ }
5843
+ var PRE_SEND_ERROR_CODES = /* @__PURE__ */ new Set([
5844
+ "ECONNREFUSED",
5845
+ "ENOTFOUND",
5846
+ "EAI_AGAIN",
5847
+ "ENETUNREACH",
5848
+ "EHOSTUNREACH",
5849
+ "UND_ERR_CONNECT_TIMEOUT"
5850
+ ]);
5851
+ function isPreSendNetworkError(err) {
5852
+ let cur = err;
5853
+ for (let depth = 0; cur && depth < 4; depth++) {
5854
+ if (typeof cur.code === "string" && PRE_SEND_ERROR_CODES.has(cur.code)) return true;
5855
+ if (Array.isArray(cur.errors) && cur.errors.some((e) => isPreSendNetworkError(e))) return true;
5856
+ cur = cur.cause;
5857
+ }
5858
+ return false;
5859
+ }
5860
+ function newIdempotencyKey() {
5861
+ const c = globalThis.crypto;
5862
+ if (c?.randomUUID) return c.randomUUID();
5863
+ if (c?.getRandomValues) {
5864
+ const bytes = c.getRandomValues(new Uint8Array(16));
5865
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
5866
+ }
5867
+ return `g8-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
5868
+ }
5869
+ function parseRetryAfter(header, nowMs = Date.now()) {
5870
+ if (!header) return null;
5871
+ const secs = Number(header);
5872
+ if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1e3);
5873
+ const dateMs = Date.parse(header);
5874
+ if (!Number.isNaN(dateMs)) {
5875
+ const delta = dateMs - nowMs;
5876
+ return delta > 0 ? delta : 0;
5877
+ }
5878
+ return null;
5879
+ }
5880
+ function backoffDelayMs(attempt, baseMs = 200, rand = Math.random) {
5881
+ const expo = Math.min(baseMs * 2 ** attempt, 1e4);
5882
+ return Math.floor(expo / 2 + rand() * (expo / 2));
5883
+ }
5884
+ function defaultSleep(ms) {
5885
+ return new Promise((resolve) => setTimeout(resolve, ms));
5886
+ }
5887
+ function buildQuery(query) {
5888
+ if (!query) return "";
5889
+ const qs = new URLSearchParams();
5890
+ for (const [k, v] of Object.entries(query)) {
5891
+ if (v != null) qs.set(k, String(v));
5892
+ }
5893
+ const s = qs.toString();
5894
+ return s ? `?${s}` : "";
5895
+ }
5896
+ async function toG8Error(resp, retryable = isRetryableStatus(resp.status)) {
5897
+ let body = void 0;
5898
+ try {
5899
+ const text = await resp.text();
5900
+ body = text ? JSON.parse(text) : void 0;
5901
+ } catch {
5902
+ body = void 0;
5903
+ }
5904
+ const env = body ?? {};
5905
+ const message = typeof env.message === "string" && env.message || typeof env.error === "string" && env.error || `Request failed with status ${resp.status}`;
5906
+ return new G8Error({
5907
+ message,
5908
+ status: resp.status,
5909
+ type: typeof env.type === "string" && env.type || typeof env.error === "string" && env.error || "api_error",
5910
+ code: typeof env.code === "string" ? env.code : String(resp.status),
5911
+ requestId: typeof env.request_id === "string" && env.request_id || resp.headers.get("x-request-id") || void 0,
5912
+ detail: env.detail,
5913
+ retryable
5914
+ });
5915
+ }
5916
+ var TARGET_ORG_HEADER = "x-target-org-id";
5917
+ var nonAgencyKeys = /* @__PURE__ */ new Set();
5918
+ var warnedTargetOrgKeys = /* @__PURE__ */ new Set();
5919
+ function keyFingerprint(apiKey) {
5920
+ let h = 2166136261;
5921
+ for (let i = 0; i < apiKey.length; i++) {
5922
+ h ^= apiKey.charCodeAt(i);
5923
+ h = Math.imul(h, 16777619) >>> 0;
5924
+ }
5925
+ return `${apiKey.length}:${h.toString(36)}`;
5926
+ }
5927
+ function warnIfTargetOrgIgnored(apiKey, headers) {
5928
+ if (!Object.keys(headers).some((h) => h.toLowerCase() === TARGET_ORG_HEADER)) return;
5929
+ const fp = keyFingerprint(apiKey);
5930
+ if (!nonAgencyKeys.has(fp) || warnedTargetOrgKeys.has(fp)) return;
5931
+ warnedTargetOrgKeys.add(fp);
5932
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
5933
+ console.warn(
5934
+ "[@graph8/sdk] X-Target-Org-Id is set, but the server reported this API key is not an agency key. The header is ignored for non-agency keys: this call runs against the key's own org. Use an agency key (minted with is_agency=true) to target a client org."
5935
+ );
5936
+ }
5937
+ }
5938
+ async function request(baseUrl, path11, apiKey, opts = {}) {
5939
+ const {
5940
+ method = "GET",
5941
+ body,
5942
+ headers = {},
5943
+ query,
5944
+ idempotencyKey,
5945
+ maxRetries = 2,
5946
+ retryBaseMs = 200,
5947
+ signal,
5948
+ fetchImpl = fetch,
5949
+ sleepImpl = defaultSleep
5950
+ } = opts;
5951
+ const url = baseUrl.replace(/\/+$/, "") + path11 + buildQuery(query);
5952
+ const finalHeaders = {
5953
+ "Content-Type": "application/json",
5954
+ Authorization: `Bearer ${apiKey}`,
5955
+ ...clientIdentityHeaders(headers),
5956
+ ...headers
5957
+ };
5958
+ warnIfTargetOrgIgnored(apiKey, headers);
5959
+ const effectiveIdempotencyKey = idempotencyKey ?? (maxRetries > 0 && isWriteMethod(method) ? newIdempotencyKey() : void 0);
5960
+ if (effectiveIdempotencyKey) finalHeaders["Idempotency-Key"] = effectiveIdempotencyKey;
5961
+ const billable = isNoBlindRetryOperation(method, url);
5962
+ let attempt = 0;
5963
+ for (; ; ) {
5964
+ let resp;
5965
+ try {
5966
+ resp = await fetchImpl(url, {
5967
+ method,
5968
+ headers: finalHeaders,
5969
+ body: body == null ? void 0 : JSON.stringify(body),
5970
+ signal
5971
+ });
5972
+ } catch (err) {
5973
+ const safeToRetry = !billable || isPreSendNetworkError(err);
5974
+ if (attempt < maxRetries && safeToRetry) {
5975
+ await sleepImpl(backoffDelayMs(attempt, retryBaseMs));
5976
+ attempt++;
5977
+ continue;
5978
+ }
5979
+ throw new G8Error({
5980
+ message: `Network error: ${err?.message ?? "request failed"}`,
5981
+ status: 0,
5982
+ type: "network_error",
5983
+ retryable: safeToRetry
5984
+ });
5985
+ }
5986
+ if (resp.ok) {
5987
+ const text = await resp.text();
5988
+ return text ? JSON.parse(text) : void 0;
5989
+ }
5990
+ const statusRetryable = isRetryableStatus(resp.status) && (!billable || resp.status === 429);
5991
+ if (statusRetryable && attempt < maxRetries) {
5992
+ const retryAfter = parseRetryAfter(resp.headers.get("retry-after"));
5993
+ const delay = retryAfter != null ? retryAfter : backoffDelayMs(attempt, retryBaseMs);
5994
+ await sleepImpl(delay);
5995
+ attempt++;
5996
+ continue;
5997
+ }
5998
+ throw await toG8Error(resp, billable ? statusRetryable : isRetryableStatus(resp.status));
5999
+ }
6000
+ }
6001
+ function isServerRuntime() {
6002
+ const g = globalThis;
6003
+ if (typeof g.window !== "undefined") return false;
6004
+ return typeof g.process?.versions?.node === "string";
6005
+ }
6006
+ function runtimeUserAgent() {
6007
+ const g = globalThis;
6008
+ const node = typeof g.process?.version === "string" ? ` node/${g.process.version.replace(/^v/, "")}` : "";
6009
+ return `graph8-sdk-js/${SDK_VERSION}${node}`;
6010
+ }
6011
+ function clientIdentityHeaders(callerHeaders = {}) {
6012
+ const taken = new Set(Object.keys(callerHeaders).map((h) => h.toLowerCase()));
6013
+ const out = {};
6014
+ if (!taken.has("x-g8-client")) out["X-G8-Client"] = SDK_CLIENT_ID;
6015
+ if (!taken.has("user-agent") && isServerRuntime()) out["User-Agent"] = runtimeUserAgent();
6016
+ return out;
6017
+ }
6018
+ async function* paginate(fetchPage) {
6019
+ let cursor = void 0;
6020
+ for (; ; ) {
6021
+ const page = await fetchPage(cursor);
6022
+ for (const item of page.data ?? []) yield item;
6023
+ const next = page.pagination?.next_cursor;
6024
+ if (!next) break;
6025
+ cursor = next;
6026
+ }
6027
+ }
5163
6028
 
5164
6029
  // src/api.ts
5165
6030
  var DEFAULT_API = "https://be.graph8.com";
@@ -5244,8 +6109,8 @@ function createApiClient(apiKey, apiUrl) {
5244
6109
  }
5245
6110
 
5246
6111
  // src/blocks/flows.ts
5247
- var import_node_fs4 = __toESM(require("fs"));
5248
- var import_node_path4 = __toESM(require("path"));
6112
+ var import_node_fs7 = __toESM(require("fs"));
6113
+ var import_node_path8 = __toESM(require("path"));
5249
6114
  var IMPORT_RE2 = /(?:^|\n)\s*(?:import|export)\s[^'"`;]*?from\s*["']([^"']+)["']|(?:^|\n)\s*import\s*["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g;
5250
6115
  var EXTENSIONS = ["", ".tsx", ".ts", "/index.tsx", "/index.ts"];
5251
6116
  function specifiers(source) {
@@ -5254,18 +6119,18 @@ function specifiers(source) {
5254
6119
  function resolveFile(base) {
5255
6120
  for (const ext of EXTENSIONS) {
5256
6121
  const candidate = base + ext;
5257
- if (import_node_fs4.default.existsSync(candidate) && import_node_fs4.default.statSync(candidate).isFile()) return candidate;
6122
+ if (import_node_fs7.default.existsSync(candidate) && import_node_fs7.default.statSync(candidate).isFile()) return candidate;
5258
6123
  }
5259
6124
  return null;
5260
6125
  }
5261
6126
  function resolveImport(spec, from, reg) {
5262
- if (spec.startsWith(".")) return resolveFile(import_node_path4.default.resolve(import_node_path4.default.dirname(from), spec));
6127
+ if (spec.startsWith(".")) return resolveFile(import_node_path8.default.resolve(import_node_path8.default.dirname(from), spec));
5263
6128
  const g8 = /^@\/g8\/([^/]+)\/(.+)$/.exec(spec);
5264
6129
  if (!g8) return null;
5265
6130
  const owner = findBlock(reg, g8[1]);
5266
- if (owner) return resolveFile(import_node_path4.default.join(owner.root, "components", g8[2])) ?? resolveFile(import_node_path4.default.join(owner.root, g8[2]));
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]));
5267
6132
  const foundation = findBlock(reg, "foundation");
5268
- return foundation ? resolveFile(import_node_path4.default.join(foundation.root, g8[1], g8[2])) : null;
6133
+ return foundation ? resolveFile(import_node_path8.default.join(foundation.root, g8[1], g8[2])) : null;
5269
6134
  }
5270
6135
  function resolveInstalled(spec, reg) {
5271
6136
  const m = /^@\/components\/g8\/([^/]+)\/(.+)$/.exec(spec);
@@ -5273,7 +6138,7 @@ function resolveInstalled(spec, reg) {
5273
6138
  if (!m || !block) return null;
5274
6139
  const files = [...block.components, ...block.hooks, ...block.lib].map((x) => x.file);
5275
6140
  const hit = files.find((f) => (installedRel(f) ?? f).replace(/\.(tsx|ts|jsx|js)$/, "") === m[2]);
5276
- return hit ? import_node_path4.default.join(block.root, hit) : null;
6141
+ return hit ? import_node_path8.default.join(block.root, hit) : null;
5277
6142
  }
5278
6143
  function seeds(reg, stack) {
5279
6144
  const out = [];
@@ -5281,13 +6146,13 @@ function seeds(reg, stack) {
5281
6146
  const comp = findBlock(reg, n.block)?.components.find((c) => c.name === n.component);
5282
6147
  const block = findBlock(reg, n.block);
5283
6148
  if (!block || !comp) continue;
5284
- const pageFile = n.page && reg.stacksRoot ? import_node_path4.default.join(reg.stacksRoot, stack.id, n.page) : null;
5285
- if (!pageFile || !import_node_fs4.default.existsSync(pageFile)) {
5286
- out.push(import_node_path4.default.join(block.root, comp.file));
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));
5287
6152
  continue;
5288
6153
  }
5289
- const source = import_node_fs4.default.readFileSync(pageFile, "utf8");
5290
- if (source.includes("{{importPath}}")) out.push(import_node_path4.default.join(block.root, comp.file));
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));
5291
6156
  for (const spec of specifiers(source)) {
5292
6157
  const hit = resolveInstalled(spec, reg);
5293
6158
  if (hit) out.push(hit);
@@ -5303,7 +6168,7 @@ function reachedFiles(reg, stack) {
5303
6168
  if (seen.has(file)) continue;
5304
6169
  seen.add(file);
5305
6170
  if (!/\.(tsx?|jsx?)$/.test(file)) continue;
5306
- for (const spec of specifiers(import_node_fs4.default.readFileSync(file, "utf8"))) {
6171
+ for (const spec of specifiers(import_node_fs7.default.readFileSync(file, "utf8"))) {
5307
6172
  const next = resolveImport(spec, file, reg);
5308
6173
  if (next && !seen.has(next)) queue.push(next);
5309
6174
  }
@@ -5313,11 +6178,11 @@ function reachedFiles(reg, stack) {
5313
6178
  function unreachedComponents(reg, stack) {
5314
6179
  const reached = reachedFiles(reg, stack);
5315
6180
  const blocks = resolveWithDeps(reg, stack.blocks).filter((b) => b.id !== "foundation" && stack.blocks.includes(b.id));
5316
- return blocks.flatMap((b) => b.components.filter((c) => !reached.has(import_node_path4.default.join(b.root, c.file))).map((c) => ({ block: b.id, component: c.name })));
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 })));
5317
6182
  }
5318
6183
 
5319
6184
  // src/blocks/doctor.ts
5320
- var DOCTOR_USAGE = `Usage: npx @graph8/sdk doctor [--stack <id>] [--blocks a,b] [--dir <path>] [--strict]
6185
+ var DOCTOR_USAGE = `Usage: npx @graph8/sdk doctor [--stack <id>] [--blocks a,b] [--dir <path>] [--strict] [--production] [--fix-prefixes]
5321
6186
 
5322
6187
  Check that this project is ready for graph8 blocks:
5323
6188
  - G8_API_KEY (env or .env.local) is valid
@@ -5327,6 +6192,11 @@ Check that this project is ready for graph8 blocks:
5327
6192
  With --stack it also lists "Flows": block components that no page of the stack reaches
5328
6193
  (a warning; --strict makes them fail the run).
5329
6194
  With no --stack/--blocks, the blocks recorded in graph8.json are checked.
6195
+ It also reads the proxy route (app/api/g8/[...path]/route.ts) and warns about what is left before going live:
6196
+ --production fail instead: no \`authorize\` (local preview would be the only gate), no allowedPrefixes,
6197
+ or a dangerouslyAllow* switch on; and the key must come from the environment
6198
+ --fix-prefixes rewrite the route's generated allowedPrefixes from the installed blocks (graph8.json), then exit
6199
+ (no network). A route written before 0.367.0 is upgraded only if its list is still the default.
5330
6200
  Optional G8_API_URL points at a non-default API. Exit code 0 when all checks
5331
6201
  pass, 1 otherwise.`;
5332
6202
  function parseDoctorArgs(argv) {
@@ -5346,6 +6216,8 @@ ${DOCTOR_USAGE}`);
5346
6216
  else if (flag === "--blocks") o.blocks.push(...value().split(",").map((s) => s.trim()).filter(Boolean));
5347
6217
  else if (flag === "--dir") o.dir = value();
5348
6218
  else if (arg === "--strict") o.strict = true;
6219
+ else if (arg === "--production") o.production = true;
6220
+ else if (arg === "--fix-prefixes") o.fixPrefixes = true;
5349
6221
  else throw new Error(`unknown option ${arg}
5350
6222
 
5351
6223
  ${DOCTOR_USAGE}`);
@@ -5364,9 +6236,9 @@ function parseEnvFile(text) {
5364
6236
  function readSetting(name, root, env) {
5365
6237
  if (env[name]) return env[name];
5366
6238
  for (const f of [".env.local"]) {
5367
- const file = import_node_path5.default.join(root, f);
5368
- if (import_node_fs5.default.existsSync(file)) {
5369
- const v = parseEnvFile(import_node_fs5.default.readFileSync(file, "utf8"))[name];
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];
5370
6242
  if (v) return v;
5371
6243
  }
5372
6244
  }
@@ -5387,7 +6259,7 @@ function selectBlocks(opts, reg, root) {
5387
6259
  }
5388
6260
  if (!ids.length) {
5389
6261
  try {
5390
- const ledger = JSON.parse(import_node_fs5.default.readFileSync(import_node_path5.default.join(root, "graph8.json"), "utf8"));
6262
+ const ledger = JSON.parse(import_node_fs8.default.readFileSync(import_node_path9.default.join(root, "graph8.json"), "utf8"));
5391
6263
  ids = Object.keys(ledger.blocks ?? {});
5392
6264
  } catch {
5393
6265
  ids = [];
@@ -5401,7 +6273,7 @@ function errorText(e, key) {
5401
6273
  return `${status ? `HTTP ${status}: ` : ""}${msg}`.split(key).join("[redacted]");
5402
6274
  }
5403
6275
  async function runDoctor(opts, reg, io, cwd) {
5404
- const root = import_node_path5.default.resolve(cwd, opts.dir);
6276
+ const root = import_node_path9.default.resolve(cwd, opts.dir);
5405
6277
  let blocks;
5406
6278
  try {
5407
6279
  blocks = selectBlocks(opts, reg, root);
@@ -5409,15 +6281,20 @@ async function runDoctor(opts, reg, io, cwd) {
5409
6281
  io.err(e.message);
5410
6282
  return 1;
5411
6283
  }
6284
+ if (opts.fixPrefixes) return fixPrefixes(root, blocks, io);
5412
6285
  let failed = false;
5413
6286
  const line = (ok, text) => {
5414
6287
  if (ok === false) failed = true;
5415
6288
  io.out(`${ok === null ? "info" : ok ? "ok " : "FAIL"} ${text}`);
5416
6289
  };
5417
6290
  io.out(blocks.length ? `Checking blocks: ${blocks.map((b) => b.id).join(", ")}` : "No blocks selected (use --stack or --blocks); checking the key and subscription only.");
5418
- const key = readSetting("G8_API_KEY", root, io.env);
6291
+ let key = readSetting("G8_API_KEY", root, io.env);
6292
+ if (!key && !opts.production) {
6293
+ key = readLocalKey(root);
6294
+ if (key) line(null, "using the key pasted in the Connect graph8 screen (.g8/local-key, local preview only)");
6295
+ }
5419
6296
  if (!key) {
5420
- line(false, "G8_API_KEY is not set (checked the environment and .env.local)");
6297
+ line(false, opts.production ? "G8_API_KEY is not set (production reads it from the environment only; checked the environment and .env.local)" : "G8_API_KEY is not set (checked the environment, .env.local and .g8/local-key)");
5421
6298
  } else {
5422
6299
  const api = createApiClient(key, readSetting("G8_API_URL", root, io.env));
5423
6300
  const call = (op) => api.call(op, void 0, {
@@ -5427,7 +6304,9 @@ async function runDoctor(opts, reg, io, cwd) {
5427
6304
  await checkKey(call, blocks, key, line);
5428
6305
  await checkSubscription(call, key, line);
5429
6306
  }
6307
+ checkRoute(root, blocks, opts.production === true, io.out, line);
5430
6308
  if (opts.stack) printFlows(opts, reg, io.out, line);
6309
+ else if (!opts.blocks.length) printAppScreens(root, reg, io.out);
5431
6310
  printChecklist(blocks, io.out);
5432
6311
  io.out(failed ? "\nSome checks failed." : "\nAll checks passed.");
5433
6312
  return failed ? 1 : 0;
@@ -5477,6 +6356,21 @@ function printFlows(opts, reg, out, line) {
5477
6356
  else out(`warn ${text}`);
5478
6357
  }
5479
6358
  }
6359
+ function printAppScreens(root, reg, out) {
6360
+ let app;
6361
+ try {
6362
+ app = JSON.parse(import_node_fs8.default.readFileSync(import_node_path9.default.join(root, "graph8.json"), "utf8")).app;
6363
+ } catch {
6364
+ return;
6365
+ }
6366
+ if (!app?.blocks?.length) return;
6367
+ const ids = app.blocks.filter((id) => reg.blocks.some((b) => b.id === id));
6368
+ out("\nScreens (what each block of this app gives its users):");
6369
+ for (const id of ids) {
6370
+ const block = reg.blocks.find((b) => b.id === id);
6371
+ if (!hasRealScreen(block)) out(`warn (${id}) has no finished Screen in this version: the app shows its plain ${composeApp(reg, [id], { noDepsScreens: true }).screens[0]?.component ?? "components"} page. A newer @graph8/sdk ships the finished screen.`);
6372
+ }
6373
+ }
5480
6374
  function printChecklist(blocks, out) {
5481
6375
  const items = blocks.flatMap((b) => b.prerequisites.filter((p) => p.where === "app").map((p) => ({ block: b.id, p })));
5482
6376
  if (!items.length) return;
@@ -5486,6 +6380,73 @@ function printChecklist(blocks, out) {
5486
6380
  if (p.verify) out(` verify: ${p.verify}`);
5487
6381
  }
5488
6382
  }
6383
+ function readLocalKey(root) {
6384
+ try {
6385
+ return import_node_fs8.default.readFileSync(import_node_path9.default.join(root, ".g8", "local-key"), "utf8").trim() || void 0;
6386
+ } catch {
6387
+ return void 0;
6388
+ }
6389
+ }
6390
+ var ROUTE_FILES = ["app/api/g8/[...path]/route.ts", "src/app/api/g8/[...path]/route.ts"];
6391
+ function findRoute(root) {
6392
+ return ROUTE_FILES.find((f) => import_node_fs8.default.existsSync(import_node_path9.default.join(root, f))) ?? null;
6393
+ }
6394
+ function checkRoute(root, blocks, production, out, line) {
6395
+ const rel = findRoute(root);
6396
+ if (!rel) {
6397
+ if (production) line(false, `no proxy route found (${ROUTE_FILES[0]}): run doctor from the app's root or pass --dir`);
6398
+ return;
6399
+ }
6400
+ const facts = inspectRoute(import_node_fs8.default.readFileSync(import_node_path9.default.join(root, rel), "utf8"));
6401
+ out(`
6402
+ Proxy route (${rel})${production ? ", production gate" : ""}:`);
6403
+ const problem = (text) => production ? line(false, text) : out(`warn ${text}`);
6404
+ if (!facts.authorize) {
6405
+ problem(
6406
+ 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
+ );
6408
+ } else line(true, "`authorize` is set");
6409
+ if (facts.dangerouslyAllowUnauthenticated) problem("dangerouslyAllowUnauthenticated is on: anyone who can reach the route can use the key");
6410
+ if (facts.dangerouslyAllowAllPaths) problem("dangerouslyAllowAllPaths is on: every /api/v1 area is reachable from the browser");
6411
+ const need = derivePrefixes(blocks);
6412
+ if (facts.prefixes === void 0) {
6413
+ problem(`allowedPrefixes is not set${need.length ? `: \`npx @graph8/sdk doctor --fix-prefixes\` writes the ${need.length} areas the blocks use` : ""}`);
6414
+ } else if (facts.prefixes === "custom") {
6415
+ out("info allowedPrefixes is not a literal list; it was not compared with the blocks");
6416
+ } else {
6417
+ const missing = need.filter((p) => !facts.prefixes.includes(p));
6418
+ const extra = facts.prefixes.filter((p) => !need.includes(p));
6419
+ if (!missing.length && !extra.length) line(true, `allowedPrefixes match the installed blocks (${need.length} areas)`);
6420
+ if (missing.length) out(`warn allowedPrefixes lacks areas the installed blocks call: ${missing.join(", ")} (\`npx @graph8/sdk doctor --fix-prefixes\`)`);
6421
+ if (extra.length && blocks.length) out(`warn allowedPrefixes allows areas no installed block declares: ${extra.join(", ")}`);
6422
+ }
6423
+ }
6424
+ function fixPrefixes(root, blocks, io) {
6425
+ const rel = findRoute(root);
6426
+ if (!rel) {
6427
+ io.err(`no proxy route found (${ROUTE_FILES.join(" or ")}). Run this in the app's root or pass --dir.`);
6428
+ return 1;
6429
+ }
6430
+ if (!blocks.length) {
6431
+ io.err("no blocks found: graph8.json lists none (or pass --blocks / --stack).");
6432
+ return 1;
6433
+ }
6434
+ const file = import_node_path9.default.join(root, rel);
6435
+ const source = import_node_fs8.default.readFileSync(file, "utf8");
6436
+ const prefixes = derivePrefixes(blocks);
6437
+ const next = applyPrefixes(source, prefixes) ?? upgradeLegacyRoute(source, prefixes);
6438
+ if (next === null) {
6439
+ io.err(`${rel} has no generated allowedPrefixes region and its list is not the default, so it is yours. The installed blocks need:`);
6440
+ io.err(`const allowedPrefixes = ${formatPrefixes(prefixes)};`);
6441
+ return 1;
6442
+ }
6443
+ if (next === source) io.out(`keep ${rel} (allowedPrefixes already match the ${prefixes.length} areas of the installed blocks)`);
6444
+ else {
6445
+ import_node_fs8.default.writeFileSync(file, next, "utf8");
6446
+ io.out(`update ${rel} (allowedPrefixes: ${prefixes.join(", ")})`);
6447
+ }
6448
+ return 0;
6449
+ }
5489
6450
 
5490
6451
  // src/cli.ts
5491
6452
  var LIST_USAGE = `Usage: npx @graph8/sdk list [--json]
@@ -5501,14 +6462,16 @@ Commands:
5501
6462
  list list the blocks and stacks this package ships
5502
6463
  search find a block by what it does
5503
6464
  add copy a block (components, hooks, recipes) into your app
5504
- create scaffold a Next.js app for a stack (create apollo my-app)
6465
+ create scaffold a Next.js app: from a stack (create apollo my-app) or from the blocks you pick
6466
+ (create --blocks crm,deals,inbox my-app)
5505
6467
  doctor check your key, scopes, subscription and setup for the blocks you use
6468
+ brand give an app its name, colour and logo, and white-label its graph8 links
5506
6469
 
5507
6470
  Run a command with --help for its options.
5508
6471
 
5509
6472
  ${INIT_AGENT_USAGE}`;
5510
6473
  function registryFor(io) {
5511
- const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path6.default.resolve(__dirname, "..", "blocks");
6474
+ const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path10.default.resolve(__dirname, "..", "blocks");
5512
6475
  return loadRegistry(root);
5513
6476
  }
5514
6477
  function defaultSpawn(cmd, args, cwd) {
@@ -5531,6 +6494,7 @@ function printCatalog(blocks, stacks, io, empty) {
5531
6494
  if (s.missingBlocks?.length) io.out(` planned (cannot be created yet), missing blocks: ${s.missingBlocks.join(", ")}`);
5532
6495
  }
5533
6496
  }
6497
+ var BUILD_HINT = "To build an app: npx @graph8/sdk create --blocks <ids> <dir> (every block's finished Screens become routes and sidebar entries)";
5534
6498
  function wantsHelp(rest) {
5535
6499
  return rest.includes("--help") || rest.includes("-h");
5536
6500
  }
@@ -5548,38 +6512,46 @@ ${LIST_USAGE}`);
5548
6512
  const blocks = reg.blocks.map(blockSummary);
5549
6513
  const stacks = reg.stacks.map((s) => stackSummary(s, reg));
5550
6514
  if (rest.includes("--json")) io.out(JSON.stringify({ blocks, stacks }, null, 2));
5551
- else printCatalog(blocks, stacks, io, "No blocks or stacks are included in this package version.");
6515
+ else {
6516
+ if (blocks.length) io.out(`${BUILD_HINT}
6517
+ `);
6518
+ printCatalog(blocks, stacks, io, "No blocks or stacks are included in this package version.");
6519
+ }
5552
6520
  return 0;
5553
6521
  }
5554
6522
  function runSearch(rest, io) {
5555
6523
  if (wantsHelp(rest)) return io.out(SEARCH_USAGE), 0;
5556
- const words = rest.filter((a) => a !== "--json");
5557
- const bad = words.find((a) => a.startsWith("-"));
6524
+ const words2 = rest.filter((a) => a !== "--json");
6525
+ const bad = words2.find((a) => a.startsWith("-"));
5558
6526
  if (bad) return fail(io, `unknown option ${bad}
5559
6527
 
5560
6528
  ${SEARCH_USAGE}`);
5561
- if (!words.length) return fail(io, `search needs some text
6529
+ if (!words2.length) return fail(io, `search needs some text
5562
6530
 
5563
6531
  ${SEARCH_USAGE}`);
5564
6532
  const reg = registryFor(io);
5565
- const text = words.join(" ");
6533
+ const text = words2.join(" ");
5566
6534
  const blocks = searchBlocks(reg, text).map(blockSummary);
5567
6535
  const stacks = searchStacks(reg, text).map((s) => stackSummary(s, reg));
5568
6536
  if (rest.includes("--json")) io.out(JSON.stringify({ query: text, blocks, stacks }, null, 2));
5569
- else printCatalog(blocks, stacks, io, `Nothing matches "${text}". Run \`graph8-sdk list\` to see everything.`);
6537
+ else {
6538
+ if (blocks.length) io.out(`${BUILD_HINT}
6539
+ `);
6540
+ printCatalog(blocks, stacks, io, `Nothing matches "${text}". Run \`graph8-sdk list\` to see everything.`);
6541
+ }
5570
6542
  return 0;
5571
6543
  }
5572
6544
  function runCreateCommand(rest, io) {
5573
6545
  if (wantsHelp(rest)) return io.out(CREATE_USAGE), 0;
5574
6546
  try {
5575
6547
  const opts = parseCreateArgs(rest);
5576
- const packageRoot = import_node_path6.default.resolve(__dirname, "..");
6548
+ const packageRoot = import_node_path10.default.resolve(__dirname, "..");
5577
6549
  const cio = {
5578
6550
  out: io.out,
5579
6551
  err: io.err,
5580
6552
  spawn: io.spawn ?? defaultSpawn,
5581
- templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path6.default.join(packageRoot, "templates"),
5582
- sdkPackageJson: import_node_path6.default.join(packageRoot, "package.json"),
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"),
5583
6555
  userAgent: io.userAgent ?? process.env.npm_config_user_agent
5584
6556
  };
5585
6557
  return runCreate(opts, registryFor(io), cio, io.cwd);
@@ -5591,7 +6563,15 @@ function runAddCommand(rest, io) {
5591
6563
  if (wantsHelp(rest)) return io.out(ADD_USAGE), 0;
5592
6564
  try {
5593
6565
  const opts = parseAddArgs(rest);
5594
- return runAdd(opts, registryFor(io), { out: io.out, err: io.err, spawn: io.spawn ?? defaultSpawn }, io.cwd);
6566
+ const packageRoot = import_node_path10.default.resolve(__dirname, "..");
6567
+ const aio = {
6568
+ out: io.out,
6569
+ err: io.err,
6570
+ 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")
6573
+ };
6574
+ return runAdd(opts, registryFor(io), aio, io.cwd);
5595
6575
  } catch (e) {
5596
6576
  return fail(io, e.message);
5597
6577
  }
@@ -5606,6 +6586,14 @@ function runCli(argv, io) {
5606
6586
  if (command === "search") return runSearch(rest, io);
5607
6587
  if (command === "add") return runAddCommand(rest, io);
5608
6588
  if (command === "create") return runCreateCommand(rest, io);
6589
+ if (command === "brand") {
6590
+ if (wantsHelp(rest)) return io.out(BRAND_USAGE), 0;
6591
+ try {
6592
+ return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd);
6593
+ } catch (e) {
6594
+ return fail(io, e.message);
6595
+ }
6596
+ }
5609
6597
  if (command === "doctor") return fail(io, "doctor makes network calls; run it through runCliAsync (the graph8-sdk bin does).");
5610
6598
  if (command !== "init-agent") {
5611
6599
  io.err(`unknown command ${command}
@@ -5625,8 +6613,8 @@ ${USAGE}`);
5625
6613
  return 1;
5626
6614
  }
5627
6615
  const read = (rel) => {
5628
- const full = import_node_path6.default.join(io.cwd, rel);
5629
- return import_node_fs6.default.existsSync(full) ? import_node_fs6.default.readFileSync(full, "utf8") : null;
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;
5630
6618
  };
5631
6619
  let plans;
5632
6620
  try {
@@ -5637,9 +6625,9 @@ ${USAGE}`);
5637
6625
  }
5638
6626
  for (const plan of plans) {
5639
6627
  if (!options.dryRun && plan.action !== "unchanged") {
5640
- const full = import_node_path6.default.join(io.cwd, plan.path);
5641
- import_node_fs6.default.mkdirSync(import_node_path6.default.dirname(full), { recursive: true });
5642
- import_node_fs6.default.writeFileSync(full, plan.content, "utf8");
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");
5643
6631
  }
5644
6632
  io.out(`${options.dryRun ? "would " : ""}${plan.action === "unchanged" ? "keep" : plan.action} ${plan.path} (${plan.target})`);
5645
6633
  }