@graph8/sdk 0.361.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.
- package/README.md +14 -10
- package/blocks/_foundation/README.md +172 -1
- package/blocks/_foundation/block.json +234 -13
- package/blocks/_foundation/components/api-error.tsx +71 -25
- package/blocks/_foundation/components/brand-provider.tsx +23 -0
- package/blocks/_foundation/components/confirmed-action.tsx +90 -0
- package/blocks/_foundation/components/connect-graph8.tsx +280 -0
- package/blocks/_foundation/components/empty-state.tsx +39 -7
- package/blocks/_foundation/components/screen/bar-list.tsx +71 -0
- package/blocks/_foundation/components/screen/detail-panel.tsx +114 -0
- package/blocks/_foundation/components/screen/entity-avatar.tsx +56 -0
- package/blocks/_foundation/components/screen/form-sheet.tsx +73 -0
- package/blocks/_foundation/components/screen/index.ts +22 -0
- package/blocks/_foundation/components/screen/key-value-list.tsx +65 -0
- package/blocks/_foundation/components/screen/record-link.tsx +76 -0
- package/blocks/_foundation/components/screen/screen-layout.tsx +124 -0
- package/blocks/_foundation/components/screen/section-card.tsx +47 -0
- package/blocks/_foundation/components/screen/sparkline.tsx +77 -0
- package/blocks/_foundation/components/screen/split-view.tsx +125 -0
- package/blocks/_foundation/components/screen/stat-strip.tsx +121 -0
- package/blocks/_foundation/components/screen/status-pill.tsx +104 -0
- package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
- package/blocks/_foundation/components/screen/time-series-chart.tsx +32 -0
- package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
- package/blocks/_foundation/components/screen/toolbar.tsx +144 -0
- package/blocks/_foundation/hooks/use-brand.ts +52 -0
- package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
- package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
- package/blocks/_foundation/hooks/use-paginated.ts +120 -0
- package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
- package/blocks/_foundation/hooks/use-url-state.ts +74 -0
- package/blocks/_foundation/lib/g8-cache.ts +454 -0
- package/blocks/_foundation/lib/g8-client.ts +1 -1
- package/blocks/_foundation/lib/plural.ts +23 -0
- package/blocks/_foundation/server/connect-route.ts +15 -0
- package/blocks/_foundation/server/connect.ts +127 -0
- package/blocks/_foundation/server/local-key.ts +70 -0
- package/blocks/_foundation/server/proxy.ts +103 -3
- package/blocks/_foundation/server/route.ts +16 -4
- package/blocks/agents/README.md +14 -0
- package/blocks/agents/block.json +33 -1
- package/blocks/agents/components/agent-builder.tsx +13 -8
- package/blocks/agents/components/agent-knowledge-panel.tsx +2 -1
- package/blocks/agents/components/agent-run-detail.tsx +102 -0
- package/blocks/agents/components/agent-runs.tsx +16 -7
- package/blocks/agents/components/agents-screen.tsx +184 -0
- package/blocks/analytics/README.md +13 -0
- package/blocks/analytics/block.json +45 -1
- package/blocks/analytics/components/analytics-screen.tsx +209 -0
- package/blocks/analytics/components/leaderboard.tsx +14 -7
- package/blocks/analytics/components/report-builder.tsx +23 -9
- package/blocks/analytics/components/report-detail.tsx +84 -0
- package/blocks/analytics/components/trend-chart.tsx +62 -0
- package/blocks/appointments/README.md +21 -1
- package/blocks/appointments/block.json +43 -2
- package/blocks/appointments/components/availability-editor.tsx +17 -5
- package/blocks/appointments/components/event-type-editor.tsx +2 -1
- package/blocks/appointments/components/event-type-related.tsx +77 -0
- package/blocks/appointments/components/meeting-list.tsx +14 -6
- package/blocks/appointments/components/meetings-screen.tsx +157 -0
- package/blocks/appointments/components/scheduling-screen.tsx +122 -0
- package/blocks/appointments/hooks/use-event-types.ts +17 -0
- package/blocks/block.schema.json +18 -0
- package/blocks/block.types.ts +26 -0
- package/blocks/campaigns/README.md +41 -11
- package/blocks/campaigns/block.json +105 -42
- package/blocks/campaigns/components/campaign-assets.tsx +16 -5
- package/blocks/campaigns/components/campaign-launch-step.tsx +102 -19
- package/blocks/campaigns/components/campaign-list.tsx +7 -3
- package/blocks/campaigns/components/campaign-wizard.tsx +43 -10
- package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
- package/blocks/campaigns/components/icp-detail.tsx +154 -0
- package/blocks/campaigns/components/icp-editor.tsx +15 -15
- package/blocks/campaigns/components/persona-detail.tsx +172 -0
- package/blocks/campaigns/components/persona-editor.tsx +14 -11
- package/blocks/campaigns/lib/campaigns-utils.ts +134 -1
- package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
- package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
- package/blocks/coach/README.md +10 -0
- package/blocks/coach/block.json +16 -1
- package/blocks/coach/components/coach-screen.tsx +222 -0
- package/blocks/copilot/README.md +13 -0
- package/blocks/copilot/block.json +14 -1
- package/blocks/copilot/components/copilot-panel.tsx +14 -5
- package/blocks/copilot/components/copilot-screen.tsx +148 -0
- package/blocks/copilot/components/copilot-team-chat.tsx +7 -5
- package/blocks/crm/README.md +28 -0
- package/blocks/crm/block.json +58 -2
- package/blocks/crm/components/companies-screen.tsx +345 -0
- package/blocks/crm/components/company-detail.tsx +8 -6
- package/blocks/crm/components/company-table.tsx +11 -4
- package/blocks/crm/components/contact-detail.tsx +6 -3
- package/blocks/crm/components/contact-table.tsx +10 -3
- package/blocks/crm/components/contacts-screen.tsx +359 -0
- package/blocks/crm/components/dedupe-review.tsx +7 -5
- package/blocks/crm/components/import-wizard.tsx +4 -2
- package/blocks/crm/components/notes-panel.tsx +4 -4
- package/blocks/crm/components/record-form.tsx +5 -3
- package/blocks/crm/components/task-list.tsx +4 -4
- package/blocks/crm/components/tasks-screen.tsx +379 -0
- package/blocks/crm/hooks/use-custom-fields.ts +6 -1
- package/blocks/crm/hooks/use-page-query.ts +7 -1
- package/blocks/crm/lib/crm-types.ts +12 -2
- package/blocks/crm/lib/screen-parts.tsx +105 -0
- package/blocks/deals/README.md +34 -3
- package/blocks/deals/block.json +45 -4
- package/blocks/deals/components/deal-detail.tsx +27 -13
- package/blocks/deals/components/deal-related.tsx +87 -0
- package/blocks/deals/components/deals-screen.tsx +298 -0
- package/blocks/deals/components/pipeline-board.tsx +4 -2
- package/blocks/deals/components/stage-editor.tsx +4 -4
- package/blocks/deals/hooks/use-default-owner.ts +60 -0
- package/blocks/deals/hooks/use-pipeline-deals.ts +5 -1
- package/blocks/deals/hooks/use-pipelines.ts +1 -1
- package/blocks/deals/lib/deals-types.ts +14 -0
- package/blocks/dialer/README.md +34 -2
- package/blocks/dialer/block.json +67 -1
- package/blocks/dialer/components/call-contact-button.tsx +252 -0
- package/blocks/dialer/components/call-log-screen.tsx +205 -0
- package/blocks/dialer/components/call-log.tsx +7 -3
- package/blocks/dialer/components/call-queue.tsx +1 -1
- package/blocks/dialer/components/dialer-screen.tsx +319 -0
- package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
- package/blocks/dialer/components/live-calls-table.tsx +7 -3
- package/blocks/dialer/components/session-start-form.tsx +4 -2
- package/blocks/dialer/components/softphone.tsx +3 -1
- package/blocks/enrichment/README.md +14 -2
- package/blocks/enrichment/block.json +32 -4
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +10 -5
- package/blocks/enrichment/components/email-verify-badge.tsx +28 -8
- package/blocks/enrichment/components/enrich-button.tsx +32 -9
- package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
- package/blocks/enrichment/components/enrichment-screen.tsx +288 -0
- package/blocks/enrichment/components/waterfall-config.tsx +39 -10
- package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
- package/blocks/forms/README.md +12 -0
- package/blocks/forms/block.json +39 -17
- package/blocks/forms/components/form-builder.tsx +3 -1
- package/blocks/forms/components/form-embed.tsx +3 -1
- package/blocks/forms/components/forms-screen.tsx +353 -0
- package/blocks/forms/components/submissions-table.tsx +16 -6
- package/blocks/inbox/README.md +27 -3
- package/blocks/inbox/block.json +14 -2
- package/blocks/inbox/components/inbox-list.tsx +7 -3
- package/blocks/inbox/components/inbox-screen.tsx +294 -27
- package/blocks/inbox/components/reply-composer.tsx +65 -9
- package/blocks/inbox/components/tag-and-assign.tsx +4 -2
- package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
- package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
- package/blocks/inbox/lib/inbox-types.ts +47 -1
- package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
- package/blocks/integrations/README.md +9 -0
- package/blocks/integrations/block.json +46 -1
- package/blocks/integrations/components/crm-detail.tsx +100 -0
- package/blocks/integrations/components/integration-list.tsx +9 -4
- package/blocks/integrations/components/integrations-screen.tsx +135 -0
- package/blocks/integrations/components/sync-status.tsx +5 -3
- package/blocks/integrations/components/webhook-detail.tsx +160 -0
- package/blocks/integrations/components/webhook-manager.tsx +13 -8
- package/blocks/integrations/lib/integrations-utils.ts +6 -1
- package/blocks/intent/README.md +14 -0
- package/blocks/intent/block.json +27 -1
- package/blocks/intent/components/intent-feed.tsx +5 -2
- package/blocks/intent/components/intent-keyword-manager.tsx +8 -4
- package/blocks/intent/components/intent-screen.tsx +265 -0
- package/blocks/intent/components/keyword-detail.tsx +124 -0
- package/blocks/lifecycle/README.md +16 -0
- package/blocks/lifecycle/block.json +34 -7
- package/blocks/lifecycle/components/account-related.tsx +66 -0
- package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
- package/blocks/lifecycle/components/lifecycle-screen.tsx +149 -0
- package/blocks/lifecycle/lib/lifecycle.ts +1 -1
- package/blocks/lists/README.md +13 -0
- package/blocks/lists/block.json +39 -1
- package/blocks/lists/components/add-to-list-button.tsx +3 -2
- package/blocks/lists/components/list-picker.tsx +5 -1
- package/blocks/lists/components/list-table.tsx +11 -5
- package/blocks/lists/components/lists-screen.tsx +344 -0
- package/blocks/lists/components/suppression-manager.tsx +6 -2
- package/blocks/lists/components/suppressions-screen.tsx +255 -0
- package/blocks/mailboxes/README.md +17 -0
- package/blocks/mailboxes/block.json +65 -17
- package/blocks/mailboxes/components/connect-mailbox.tsx +23 -13
- package/blocks/mailboxes/components/domain-detail.tsx +142 -0
- package/blocks/mailboxes/components/domain-purchase.tsx +6 -3
- package/blocks/mailboxes/components/mailbox-detail.tsx +156 -0
- package/blocks/mailboxes/components/mailboxes-screen.tsx +223 -0
- package/blocks/mailboxes/components/warmup-health.tsx +4 -2
- package/blocks/mailboxes/lib/mailboxes-utils.ts +22 -1
- package/blocks/newsletter/README.md +13 -0
- package/blocks/newsletter/block.json +47 -23
- package/blocks/newsletter/components/issue-scheduler.tsx +2 -1
- package/blocks/newsletter/components/newsletter-screen.tsx +404 -0
- package/blocks/newsletter/components/subscriber-table.tsx +12 -6
- package/blocks/nurture/README.md +12 -0
- package/blocks/nurture/block.json +35 -13
- package/blocks/nurture/components/nurture-builder.tsx +3 -1
- package/blocks/nurture/components/nurture-enroll.tsx +6 -3
- package/blocks/nurture/components/nurture-form.tsx +23 -3
- package/blocks/nurture/components/nurture-screen.tsx +283 -0
- package/blocks/nurture/lib/nurture-types.ts +1 -1
- package/blocks/objects/README.md +17 -0
- package/blocks/objects/block.json +29 -16
- package/blocks/objects/components/object-not-found.tsx +3 -1
- package/blocks/objects/components/object-record-detail.tsx +2 -1
- package/blocks/objects/components/object-schema-editor.tsx +3 -1
- package/blocks/objects/components/object-table.tsx +13 -3
- package/blocks/objects/components/objects-screen.tsx +147 -0
- package/blocks/objects/lib/object-types.ts +1 -1
- package/blocks/pages/README.md +12 -0
- package/blocks/pages/block.json +35 -22
- package/blocks/pages/components/brand-kit-editor.tsx +5 -2
- package/blocks/pages/components/content-library.tsx +46 -11
- package/blocks/pages/components/page-list.tsx +6 -3
- package/blocks/pages/components/pages-screen.tsx +154 -0
- package/blocks/quotes/README.md +17 -0
- package/blocks/quotes/block.json +38 -9
- package/blocks/quotes/components/product-picker.tsx +3 -1
- package/blocks/quotes/components/quote-list.tsx +7 -3
- package/blocks/quotes/components/quote-related.tsx +123 -0
- package/blocks/quotes/components/quotes-screen.tsx +169 -0
- package/blocks/radar/README.md +10 -0
- package/blocks/radar/block.json +41 -1
- package/blocks/radar/components/competitor-detail.tsx +117 -0
- package/blocks/radar/components/competitor-tracker.tsx +3 -1
- package/blocks/radar/components/radar-report.tsx +4 -2
- package/blocks/radar/components/radar-screen.tsx +214 -0
- package/blocks/radar/components/report-detail.tsx +104 -0
- package/blocks/search/README.md +10 -0
- package/blocks/search/block.json +16 -1
- package/blocks/search/components/filter-builder.tsx +3 -3
- package/blocks/search/components/prospect-results.tsx +5 -3
- package/blocks/search/components/prospect-search-screen.tsx +235 -0
- package/blocks/search/components/saved-searches.tsx +4 -1
- package/blocks/search/hooks/use-filter-options.ts +20 -6
- package/blocks/search/lib/search-types.ts +2 -0
- package/blocks/sequencer/README.md +49 -9
- package/blocks/sequencer/block.json +99 -19
- package/blocks/sequencer/components/enroll-dialog.tsx +9 -3
- package/blocks/sequencer/components/sequence-builder.tsx +211 -46
- package/blocks/sequencer/components/sequence-detail.tsx +259 -0
- package/blocks/sequencer/components/sequence-enrollments.tsx +114 -0
- package/blocks/sequencer/components/sequence-list.tsx +33 -8
- package/blocks/sequencer/components/sequence-stats.tsx +3 -2
- package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
- package/blocks/sequencer/components/sequences-screen.tsx +159 -0
- package/blocks/sequencer/components/step-editor.tsx +3 -1
- package/blocks/sequencer/lib/sequencer-utils.ts +128 -5
- package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
- package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
- package/blocks/social-ads/README.md +11 -0
- package/blocks/social-ads/block.json +101 -2
- package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
- package/blocks/social-ads/components/ads-screen.tsx +152 -0
- package/blocks/social-ads/components/audience-sync-detail.tsx +188 -0
- package/blocks/social-ads/components/audience-sync.tsx +11 -7
- package/blocks/social-ads/components/campaign-detail.tsx +133 -0
- package/blocks/social-ads/components/creative-detail.tsx +150 -0
- package/blocks/social-ads/components/creative-gallery.tsx +15 -5
- package/blocks/social-ads/components/post-composer.tsx +3 -1
- package/blocks/social-ads/components/post-detail.tsx +160 -0
- package/blocks/social-ads/components/social-post-list.tsx +11 -3
- package/blocks/social-ads/components/social-screen.tsx +160 -0
- package/blocks/visitors/README.md +10 -0
- package/blocks/visitors/block.json +25 -1
- package/blocks/visitors/components/snippet-install.tsx +3 -1
- package/blocks/visitors/components/visitor-company-card.tsx +2 -2
- package/blocks/visitors/components/visitor-detail.tsx +105 -0
- package/blocks/visitors/components/visitor-feed.tsx +11 -2
- package/blocks/visitors/components/visitors-screen.tsx +166 -0
- package/blocks/webchat/README.md +10 -0
- package/blocks/webchat/block.json +21 -7
- package/blocks/webchat/components/chat-widget-config.tsx +7 -4
- package/blocks/webchat/components/conversation-list.tsx +7 -3
- package/blocks/webchat/components/webchat-screen.tsx +245 -0
- package/blocks/workbench/README.md +11 -0
- package/blocks/workbench/block.json +48 -1
- package/blocks/workbench/components/column-recipe-editor.tsx +4 -2
- package/blocks/workbench/components/workbench-grid.tsx +9 -3
- package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
- package/blocks/workbench/components/workbench-screen.tsx +192 -0
- package/blocks/workbench/components/workbench-sources.tsx +194 -0
- package/blocks/workflows/README.md +13 -0
- package/blocks/workflows/block.json +28 -9
- package/blocks/workflows/components/run-history.tsx +31 -9
- package/blocks/workflows/components/trigger-config.tsx +3 -1
- package/blocks/workflows/components/workflow-editor.tsx +3 -1
- package/blocks/workflows/components/workflow-list.tsx +13 -6
- package/blocks/workflows/components/workflows-screen.tsx +193 -0
- package/dist/cli.js +2785 -1637
- package/dist/errors.d.mts +34 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.mjs +21 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.browser.js +11 -5
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +11 -5
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.mts +164 -25
- package/dist/index.d.ts +164 -25
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -5
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +10 -4
- package/dist/react.browser.js.map +1 -1
- package/dist/react.browser.mjs +10 -4
- package/dist/react.browser.mjs.map +1 -1
- package/dist/react.d.mts +142 -5
- package/dist/react.d.ts +142 -5
- package/dist/react.js +10 -4
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +10 -4
- package/dist/react.mjs.map +1 -1
- package/dist/types/blocks/add.d.ts +11 -4
- package/dist/types/blocks/brand.d.ts +113 -0
- package/dist/types/blocks/compose.d.ts +53 -0
- package/dist/types/blocks/create.d.ts +25 -8
- package/dist/types/blocks/doctor.d.ts +6 -1
- package/dist/types/blocks/flows.d.ts +20 -0
- package/dist/types/blocks/navfile.d.ts +35 -0
- package/dist/types/blocks/paths.d.ts +4 -0
- package/dist/types/blocks/prefixes.d.ts +37 -0
- package/dist/types/blocks/registry.d.ts +32 -0
- package/dist/types/blocks/screens.d.ts +53 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/errors.d.ts +33 -0
- package/dist/types/http.d.ts +2 -18
- package/dist/types/version.d.ts +2 -2
- package/docs/agents.md +15 -4
- package/docs/api/README.md +2 -2
- package/docs/api/by-path.md +1 -0
- package/docs/api/desk.d.ts +98 -2
- package/docs/api/desk.md +123 -1
- package/docs/api/inbox.d.ts +10 -2
- package/docs/api/inbox.md +10 -2
- package/docs/api/seo.d.ts +19 -1
- package/docs/api/seo.md +24 -1
- package/package.json +16 -2
- package/stacks/6sense.json +41 -16
- package/stacks/apollo/pages/companies.tsx.tmpl +50 -0
- package/stacks/apollo/pages/contacts.tsx.tmpl +117 -0
- package/stacks/apollo.json +118 -36
- package/stacks/calendly.json +60 -0
- package/stacks/clay/pages/companies.tsx.tmpl +15 -0
- package/stacks/clay/pages/contacts.tsx.tmpl +19 -0
- package/stacks/clay.json +59 -26
- package/stacks/gong.json +81 -35
- package/stacks/hootsuite.json +99 -0
- package/stacks/hubspot/pages/companies.tsx.tmpl +47 -0
- package/stacks/hubspot/pages/contacts.tsx.tmpl +111 -0
- package/stacks/hubspot/pages/deals.tsx.tmpl +11 -0
- package/stacks/hubspot/pages/inbox.tsx.tmpl +92 -0
- package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
- package/stacks/hubspot.json +167 -66
- package/stacks/intercom.json +48 -20
- package/stacks/mailchimp.json +52 -33
- package/stacks/outreach/pages/contacts.tsx.tmpl +85 -0
- package/stacks/outreach.json +88 -25
- package/stacks/pipedrive/pages/companies.tsx.tmpl +47 -0
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +47 -0
- package/stacks/pipedrive/pages/deals.tsx.tmpl +11 -0
- package/stacks/pipedrive/pages/inbox.tsx.tmpl +92 -0
- package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
- package/stacks/pipedrive.json +115 -0
- package/stacks/salesforce/pages/companies.tsx.tmpl +47 -0
- package/stacks/salesforce/pages/contacts.tsx.tmpl +47 -0
- package/stacks/salesforce/pages/deals.tsx.tmpl +11 -0
- package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
- package/stacks/salesforce.json +81 -31
- package/stacks/stack.schema.json +6 -1
- package/stacks/zoominfo/pages/companies.tsx.tmpl +15 -0
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +102 -0
- package/styles/styles.css +1 -1
- package/templates/next-app/README.md.tmpl +55 -8
- package/templates/next-app/app/globals.css.tmpl +105 -0
- package/templates/next-app/app/layout.tsx.tmpl +18 -4
- package/templates/next-app/components/app-shell.tsx.tmpl +248 -23
- package/templates/next-app/components/shell/connection-status.tsx.tmpl +109 -0
- package/templates/next-app/components/shell/nav-model.ts.tmpl +65 -0
- package/templates/next-app/components/shell/quick-jump.tsx.tmpl +131 -0
- package/templates/next-app/components/shell/theme-toggle.tsx.tmpl +40 -0
- package/templates/next-app/dot-gitignore.tmpl +2 -0
- package/templates/next-app/lib/nav.ts.tmpl +1 -1
- 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
|
|
40
|
-
var
|
|
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,
|
|
123
|
-
const current = read(
|
|
124
|
-
plans.push({ target, path:
|
|
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,1622 +154,21 @@ ${INIT_AGENT_USAGE}`);
|
|
|
154
154
|
|
|
155
155
|
// src/blocks/add.ts
|
|
156
156
|
var import_node_crypto = require("crypto");
|
|
157
|
-
var
|
|
158
|
-
var
|
|
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.
|
|
161
|
+
var SDK_VERSION = "0.370.0";
|
|
162
162
|
var SDK_CLIENT_ID = `js-sdk/${SDK_VERSION}`;
|
|
163
163
|
|
|
164
|
-
// src/blocks/
|
|
165
|
-
var import_node_fs = __toESM(require("fs"));
|
|
164
|
+
// src/blocks/paths.ts
|
|
166
165
|
var import_node_path = __toESM(require("path"));
|
|
167
|
-
|
|
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
|
-
});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return { blocks, stacks };
|
|
228
|
-
}
|
|
229
|
-
function findBlock(reg, id) {
|
|
230
|
-
return reg.blocks.find((b) => b.id === id);
|
|
231
|
-
}
|
|
232
|
-
function resolveWithDeps(reg, ids) {
|
|
233
|
-
const ordered = [];
|
|
234
|
-
const done = /* @__PURE__ */ new Set();
|
|
235
|
-
const visiting = [];
|
|
236
|
-
const visit = (id) => {
|
|
237
|
-
if (done.has(id)) return;
|
|
238
|
-
if (visiting.includes(id)) throw new Error(`dependency cycle: ${[...visiting, id].join(" -> ")}`);
|
|
239
|
-
const block = findBlock(reg, id);
|
|
240
|
-
if (!block) {
|
|
241
|
-
const from = visiting.length ? ` (required by ${visiting[visiting.length - 1]})` : "";
|
|
242
|
-
throw new Error(`unknown block "${id}"${from}. Run \`graph8-sdk list\` to see the available blocks.`);
|
|
243
|
-
}
|
|
244
|
-
visiting.push(id);
|
|
245
|
-
for (const dep of block.dependsOn) visit(dep);
|
|
246
|
-
visiting.pop();
|
|
247
|
-
done.add(id);
|
|
248
|
-
ordered.push(block);
|
|
249
|
-
};
|
|
250
|
-
ids.forEach(visit);
|
|
251
|
-
return ordered;
|
|
252
|
-
}
|
|
253
|
-
function searchBlocks(reg, text) {
|
|
254
|
-
const needle = text.trim().toLowerCase();
|
|
255
|
-
if (!needle) return [];
|
|
256
|
-
return reg.blocks.filter(
|
|
257
|
-
(b) => [b.id, b.title, b.summary, ...b.features, ...b.components.map((c) => c.name)].some((s) => s.toLowerCase().includes(needle))
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
function searchStacks(reg, text) {
|
|
261
|
-
const needle = text.trim().toLowerCase();
|
|
262
|
-
if (!needle) return [];
|
|
263
|
-
return reg.stacks.filter((s) => [s.id, s.title, s.summary].some((x) => x.toLowerCase().includes(needle)));
|
|
264
|
-
}
|
|
265
|
-
function blockSummary(b) {
|
|
266
|
-
return { id: b.id, title: b.title, summary: b.summary, dependsOn: b.dependsOn, components: b.components.map((c) => c.name) };
|
|
267
|
-
}
|
|
268
|
-
function stackStatus(reg, s) {
|
|
269
|
-
const missing = s.blocks.filter((id) => !findBlock(reg, id));
|
|
270
|
-
return { status: missing.length ? "planned" : "available", missing };
|
|
271
|
-
}
|
|
272
|
-
function stackSummary(s, reg) {
|
|
273
|
-
const base = { id: s.id, title: s.title, summary: s.summary, blocks: s.blocks };
|
|
274
|
-
if (!reg) return base;
|
|
275
|
-
const { status, missing } = stackStatus(reg, s);
|
|
276
|
-
return { ...base, status, ...missing.length ? { missingBlocks: missing } : {} };
|
|
277
|
-
}
|
|
278
|
-
function catalogSection(reg) {
|
|
279
|
-
if (!reg.blocks.length && !reg.stacks.length) return void 0;
|
|
280
|
-
const lines = ["### graph8 blocks", "", "Ready-made, copy-in UI + hooks per graph8 feature. Pick from this list instead of reading every README:", ""];
|
|
281
|
-
for (const b of reg.blocks) {
|
|
282
|
-
const deps = b.dependsOn.length ? ` (needs ${b.dependsOn.join(", ")})` : "";
|
|
283
|
-
lines.push(`- block \`${b.id}\`: ${b.summary}${deps}`);
|
|
284
|
-
}
|
|
285
|
-
for (const s of reg.stacks) {
|
|
286
|
-
const { missing } = stackStatus(reg, s);
|
|
287
|
-
const planned = missing.length ? ` [planned, not creatable yet: missing blocks ${missing.join(", ")}]` : "";
|
|
288
|
-
lines.push(`- stack \`${s.id}\`: ${s.summary} (blocks: ${s.blocks.join(", ")})${planned}`);
|
|
289
|
-
}
|
|
290
|
-
lines.push(
|
|
291
|
-
"",
|
|
292
|
-
"Add one: `npx @graph8/sdk add <block>[/<Component>]` (dependencies and the `foundation` block come with it). Find one:",
|
|
293
|
-
"`npx @graph8/sdk list` / `search <text>`. Check setup: `npx @graph8/sdk doctor --blocks a,b`."
|
|
294
|
-
);
|
|
295
|
-
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).");
|
|
296
|
-
return lines.join("\n");
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// src/blocks/add.ts
|
|
300
|
-
var ADD_USAGE = `Usage: npx @graph8/sdk add <block>[/<Component>] [options]
|
|
301
|
-
|
|
302
|
-
Copy a graph8 block (components, hooks, recipes) into your app. The block's
|
|
303
|
-
dependencies (always including \`foundation\`) are added first.
|
|
304
|
-
--dir <path> project root (default: current directory)
|
|
305
|
-
--target <path> where to copy, relative to --dir (default: components/g8)
|
|
306
|
-
--dry-run print what would change, write and install nothing
|
|
307
|
-
--force overwrite files that differ from the template
|
|
308
|
-
--no-install do not install missing peer dependencies
|
|
309
|
-
Existing files are never overwritten without --force. Installed blocks are
|
|
310
|
-
recorded in graph8.json so re-runs do nothing.`;
|
|
311
|
-
function parseAddArgs(argv) {
|
|
312
|
-
const o = { spec: "", dir: ".", dryRun: false, force: false, install: true };
|
|
313
|
-
for (let i = 0; i < argv.length; i++) {
|
|
314
|
-
const arg = argv[i];
|
|
315
|
-
const [flag, inline] = arg.startsWith("--") && arg.includes("=") ? [arg.slice(0, arg.indexOf("=")), arg.slice(arg.indexOf("=") + 1)] : [arg, void 0];
|
|
316
|
-
const value = () => {
|
|
317
|
-
const v = inline ?? argv[++i];
|
|
318
|
-
if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
|
|
319
|
-
|
|
320
|
-
${ADD_USAGE}`);
|
|
321
|
-
return v;
|
|
322
|
-
};
|
|
323
|
-
if (flag === "--dir") o.dir = value();
|
|
324
|
-
else if (flag === "--target") o.target = value();
|
|
325
|
-
else if (arg === "--dry-run") o.dryRun = true;
|
|
326
|
-
else if (arg === "--force") o.force = true;
|
|
327
|
-
else if (arg === "--no-install") o.install = false;
|
|
328
|
-
else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
|
|
329
|
-
|
|
330
|
-
${ADD_USAGE}`);
|
|
331
|
-
else if (!o.spec) o.spec = arg;
|
|
332
|
-
else throw new Error(`unexpected argument ${arg}
|
|
333
|
-
|
|
334
|
-
${ADD_USAGE}`);
|
|
335
|
-
}
|
|
336
|
-
if (!o.spec) throw new Error(`add needs a block name
|
|
337
|
-
|
|
338
|
-
${ADD_USAGE}`);
|
|
339
|
-
return o;
|
|
340
|
-
}
|
|
341
|
-
function detectPackageManager(root) {
|
|
342
|
-
const has = (f) => import_node_fs2.default.existsSync(import_node_path2.default.join(root, f));
|
|
343
|
-
if (has("pnpm-lock.yaml")) return "pnpm";
|
|
344
|
-
if (has("yarn.lock")) return "yarn";
|
|
345
|
-
if (has("bun.lockb") || has("bun.lock")) return "bun";
|
|
346
|
-
return "npm";
|
|
347
|
-
}
|
|
348
|
-
function installCommand(pm, packages) {
|
|
349
|
-
const verb = pm === "npm" ? "install" : "add";
|
|
350
|
-
return { cmd: pm, args: [verb, ...packages] };
|
|
351
|
-
}
|
|
352
|
-
var sha = (s) => (0, import_node_crypto.createHash)("sha256").update(s).digest("hex");
|
|
353
|
-
var posix = (p) => p.split(import_node_path2.default.sep).join("/");
|
|
166
|
+
var posix = (p) => p.split(import_node_path.default.sep).join("/");
|
|
354
167
|
function installedRel(rel) {
|
|
355
168
|
const r = posix(rel);
|
|
356
169
|
if (r === "block.json") return null;
|
|
357
170
|
return r.startsWith("components/") ? r.slice("components/".length) : r;
|
|
358
171
|
}
|
|
359
|
-
function appTargeted(block) {
|
|
360
|
-
const m = /* @__PURE__ */ new Map();
|
|
361
|
-
for (const f of block.server) if (f.target && !f.target.startsWith("g8/")) m.set(posix(f.file), f.target);
|
|
362
|
-
return m;
|
|
363
|
-
}
|
|
364
|
-
function listFiles(dir, base = dir) {
|
|
365
|
-
const out = [];
|
|
366
|
-
for (const e of import_node_fs2.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
367
|
-
const full = import_node_path2.default.join(dir, e.name);
|
|
368
|
-
if (e.isDirectory()) out.push(...listFiles(full, base));
|
|
369
|
-
else out.push(posix(import_node_path2.default.relative(base, full)));
|
|
370
|
-
}
|
|
371
|
-
return out;
|
|
372
|
-
}
|
|
373
|
-
var IMPORT_RE = /(\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)(["'])([^"'\n]+)\2/g;
|
|
374
|
-
function relSpec(fromDir, toFile) {
|
|
375
|
-
const noExt = toFile;
|
|
376
|
-
let r = posix(import_node_path2.default.relative(fromDir, noExt));
|
|
377
|
-
if (!r.startsWith(".")) r = `./${r}`;
|
|
378
|
-
return r;
|
|
379
|
-
}
|
|
380
|
-
function g8AliasTarget(spec, ctx) {
|
|
381
|
-
const [seg, ...rest] = spec.split("/");
|
|
382
|
-
const reserved = ["components", "hooks", "lib", "server", "recipes"].includes(seg);
|
|
383
|
-
const owner = rest.length && !reserved ? ctx.reg.blocks.find((b) => b.id === seg || b.dir === seg) : void 0;
|
|
384
|
-
const block = owner ?? ctx.reg.blocks.find((b) => b.id === "foundation");
|
|
385
|
-
const sub = owner ? rest.join("/") : spec;
|
|
386
|
-
return import_node_path2.default.join(ctx.targetAbs, block?.dir ?? "_foundation", installedRel(sub) ?? sub);
|
|
387
|
-
}
|
|
388
|
-
function rewriteImports(content, ctx) {
|
|
389
|
-
return content.replace(IMPORT_RE, (whole, pre, q, spec) => {
|
|
390
|
-
if (spec.startsWith("@/g8/")) {
|
|
391
|
-
return `${pre}${q}${relSpec(ctx.destDir, g8AliasTarget(spec.slice("@/g8/".length), ctx))}${q}`;
|
|
392
|
-
}
|
|
393
|
-
if (spec.startsWith(".") && ctx.block && ctx.srcFile) {
|
|
394
|
-
const abs = import_node_path2.default.resolve(import_node_path2.default.dirname(ctx.srcFile), spec);
|
|
395
|
-
const relInBlock = import_node_path2.default.relative(ctx.block.root, abs);
|
|
396
|
-
if (relInBlock.startsWith("..")) return whole;
|
|
397
|
-
const mapped = installedRel(relInBlock);
|
|
398
|
-
if (mapped === null) return whole;
|
|
399
|
-
return `${pre}${q}${relSpec(ctx.destDir, import_node_path2.default.join(ctx.targetAbs, ctx.block.dir, mapped))}${q}`;
|
|
400
|
-
}
|
|
401
|
-
return whole;
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
function diffSummary(before, after) {
|
|
405
|
-
const count = (s) => {
|
|
406
|
-
const m = /* @__PURE__ */ new Map();
|
|
407
|
-
for (const l of s.split("\n")) m.set(l, (m.get(l) ?? 0) + 1);
|
|
408
|
-
return m;
|
|
409
|
-
};
|
|
410
|
-
const a = count(before);
|
|
411
|
-
const b = count(after);
|
|
412
|
-
let removed = 0;
|
|
413
|
-
let added = 0;
|
|
414
|
-
for (const [l, n] of a) removed += Math.max(0, n - (b.get(l) ?? 0));
|
|
415
|
-
for (const [l, n] of b) added += Math.max(0, n - (a.get(l) ?? 0));
|
|
416
|
-
return `+${added} -${removed} lines vs the template`;
|
|
417
|
-
}
|
|
418
|
-
function classify(abs, rel, content, force) {
|
|
419
|
-
if (!import_node_fs2.default.existsSync(abs)) return { rel, content, action: "create" };
|
|
420
|
-
const current = import_node_fs2.default.readFileSync(abs, "utf8");
|
|
421
|
-
if (current === content) return { rel, content, action: "unchanged" };
|
|
422
|
-
if (force) return { rel, content, action: "update", detail: diffSummary(current, content) };
|
|
423
|
-
return { rel, content, action: "skip", detail: `exists and differs (${diffSummary(current, content)}); use --force to overwrite` };
|
|
424
|
-
}
|
|
425
|
-
function readPkg(root) {
|
|
426
|
-
try {
|
|
427
|
-
return JSON.parse(import_node_fs2.default.readFileSync(import_node_path2.default.join(root, "package.json"), "utf8"));
|
|
428
|
-
} catch {
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
function missingPeers(blocks, pkg) {
|
|
433
|
-
const have = /* @__PURE__ */ new Set([
|
|
434
|
-
...Object.keys(pkg.dependencies ?? {}),
|
|
435
|
-
...Object.keys(pkg.devDependencies ?? {}),
|
|
436
|
-
...Object.keys(pkg.peerDependencies ?? {}),
|
|
437
|
-
...Object.keys(pkg.optionalDependencies ?? {})
|
|
438
|
-
]);
|
|
439
|
-
const want = /* @__PURE__ */ new Map();
|
|
440
|
-
for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) if (!want.has(name)) want.set(name, range);
|
|
441
|
-
return [...want].filter(([name]) => !have.has(name)).map(([name, range]) => `${name}@${range}`);
|
|
442
|
-
}
|
|
443
|
-
function parseSpec(spec) {
|
|
444
|
-
const [blockId, component, ...extra] = spec.split("/");
|
|
445
|
-
if (!blockId || extra.length) throw new Error(`invalid block "${spec}": use <block> or <block>/<Component>`);
|
|
446
|
-
return { blockId, component: component || void 0 };
|
|
447
|
-
}
|
|
448
|
-
function planBlock(block, only, reg, targetAbs, root, force) {
|
|
449
|
-
const skipComponentFiles = /* @__PURE__ */ new Set();
|
|
450
|
-
if (only) {
|
|
451
|
-
for (const c of block.components) if (c.name !== only) skipComponentFiles.add(posix(c.file));
|
|
452
|
-
const byStem = new Map(block.components.map((c) => [posix(c.file).replace(/\.[tj]sx?$/, ""), posix(c.file)]));
|
|
453
|
-
const start = block.components.find((c) => c.name === only);
|
|
454
|
-
const queue = start ? [posix(start.file)] : [];
|
|
455
|
-
while (queue.length) {
|
|
456
|
-
const file = queue.pop();
|
|
457
|
-
const text = import_node_fs2.default.readFileSync(import_node_path2.default.join(block.root, file), "utf8");
|
|
458
|
-
for (const m of text.matchAll(IMPORT_RE)) {
|
|
459
|
-
if (!m[3].startsWith(".")) continue;
|
|
460
|
-
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?$/, "");
|
|
461
|
-
const sibling = byStem.get(stem);
|
|
462
|
-
if (sibling && skipComponentFiles.delete(sibling)) queue.push(sibling);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
const files = [];
|
|
467
|
-
const toApp = appTargeted(block);
|
|
468
|
-
for (const rel of listFiles(block.root)) {
|
|
469
|
-
if (skipComponentFiles.has(rel) || toApp.has(rel)) continue;
|
|
470
|
-
const mapped = installedRel(rel);
|
|
471
|
-
if (mapped === null) continue;
|
|
472
|
-
const destAbs = import_node_path2.default.join(targetAbs, block.dir, mapped);
|
|
473
|
-
const src = import_node_path2.default.join(block.root, rel);
|
|
474
|
-
const text = import_node_fs2.default.readFileSync(src, "utf8");
|
|
475
|
-
const content = rewriteImports(text, { reg, targetAbs, block, srcFile: src, destDir: import_node_path2.default.dirname(destAbs) });
|
|
476
|
-
files.push(classify(destAbs, posix(import_node_path2.default.relative(root, destAbs)), content, force));
|
|
477
|
-
}
|
|
478
|
-
return files;
|
|
479
|
-
}
|
|
480
|
-
var PROXY_CONTRACT = [
|
|
481
|
-
"This is not a Next.js project, so the proxy route was not written. Your server must provide the same contract:",
|
|
482
|
-
" - handle every method on /api/g8/* (the browser calls these paths)",
|
|
483
|
-
" - forward to ${G8_API_URL:-https://be.graph8.com}/api/v1/<rest of path>, same method, query and body",
|
|
484
|
-
" - add the header `Authorization: Bearer $G8_API_KEY` on the server; never send the key to the browser",
|
|
485
|
-
" - return the upstream status and JSON body unchanged"
|
|
486
|
-
];
|
|
487
|
-
function writeFile(root, f) {
|
|
488
|
-
const abs = import_node_path2.default.join(root, f.rel);
|
|
489
|
-
import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(abs), { recursive: true });
|
|
490
|
-
import_node_fs2.default.writeFileSync(abs, f.content, "utf8");
|
|
491
|
-
}
|
|
492
|
-
function readLedger(file) {
|
|
493
|
-
try {
|
|
494
|
-
const j = JSON.parse(import_node_fs2.default.readFileSync(file, "utf8"));
|
|
495
|
-
return j && typeof j === "object" && j.blocks ? j : null;
|
|
496
|
-
} catch {
|
|
497
|
-
return null;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
function ledgerContent(prev, target, entries, only) {
|
|
501
|
-
const blocks = { ...prev?.blocks ?? {} };
|
|
502
|
-
for (const [id, files] of entries) {
|
|
503
|
-
const old = blocks[id];
|
|
504
|
-
const recorded = { ...old?.files ?? {} };
|
|
505
|
-
for (const f of files) {
|
|
506
|
-
if (f.action === "skip") continue;
|
|
507
|
-
recorded[f.rel] = sha(f.content);
|
|
508
|
-
}
|
|
509
|
-
const comp = only.get(id);
|
|
510
|
-
let components;
|
|
511
|
-
if (!comp) components = "all";
|
|
512
|
-
else if (old?.components === "all") components = "all";
|
|
513
|
-
else components = [.../* @__PURE__ */ new Set([...old?.components ?? [], comp])].sort();
|
|
514
|
-
blocks[id] = { files: Object.fromEntries(Object.entries(recorded).sort(([a], [b]) => a.localeCompare(b))), components };
|
|
515
|
-
}
|
|
516
|
-
const sorted = Object.fromEntries(Object.entries(blocks).sort(([a], [b]) => a.localeCompare(b)));
|
|
517
|
-
const stack = prev?.stack ? { stack: prev.stack } : {};
|
|
518
|
-
return `${JSON.stringify({ sdkVersion: SDK_VERSION, target, ...stack, blocks: sorted }, null, 2)}
|
|
519
|
-
`;
|
|
520
|
-
}
|
|
521
|
-
function ensureEnvExample(root, dryRun, out) {
|
|
522
|
-
const file = import_node_path2.default.join(root, ".env.local.example");
|
|
523
|
-
const current = import_node_fs2.default.existsSync(file) ? import_node_fs2.default.readFileSync(file, "utf8") : null;
|
|
524
|
-
if (current !== null && /^G8_API_KEY=/m.test(current)) {
|
|
525
|
-
out("keep .env.local.example (G8_API_KEY already listed)");
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
out(`${dryRun ? "would " : ""}${current === null ? "create" : "update"} .env.local.example (+ G8_API_KEY=)`);
|
|
529
|
-
if (dryRun) return;
|
|
530
|
-
const base = current === null ? "" : current.endsWith("\n") || current === "" ? current : `${current}
|
|
531
|
-
`;
|
|
532
|
-
import_node_fs2.default.writeFileSync(file, `${base}G8_API_KEY=
|
|
533
|
-
`, "utf8");
|
|
534
|
-
}
|
|
535
|
-
function runAdd(opts, reg, io, cwd) {
|
|
536
|
-
const root = import_node_path2.default.resolve(cwd, opts.dir);
|
|
537
|
-
const pkg = readPkg(root);
|
|
538
|
-
if (!pkg) {
|
|
539
|
-
io.err(`no package.json in ${root}. Run this from your app's root, or pass --dir.`);
|
|
540
|
-
return 1;
|
|
541
|
-
}
|
|
542
|
-
let parsed;
|
|
543
|
-
let blocks;
|
|
544
|
-
try {
|
|
545
|
-
parsed = parseSpec(opts.spec);
|
|
546
|
-
blocks = resolveWithDeps(reg, [parsed.blockId]);
|
|
547
|
-
const main2 = blocks[blocks.length - 1];
|
|
548
|
-
if (parsed.component && !main2.components.some((c) => c.name === parsed.component)) {
|
|
549
|
-
const names = main2.components.map((c) => c.name).join(", ") || "none";
|
|
550
|
-
throw new Error(`block "${main2.id}" has no component "${parsed.component}" (available: ${names})`);
|
|
551
|
-
}
|
|
552
|
-
} catch (e) {
|
|
553
|
-
io.err(e.message);
|
|
554
|
-
return 1;
|
|
555
|
-
}
|
|
556
|
-
const exists = (p) => import_node_fs2.default.existsSync(import_node_path2.default.join(root, p));
|
|
557
|
-
const target = opts.target ?? (exists("src") && !exists("components") && !exists("app") ? "src/components/g8" : "components/g8");
|
|
558
|
-
const targetAbs = import_node_path2.default.resolve(root, target);
|
|
559
|
-
const isNext = Boolean(pkg.dependencies?.next ?? pkg.devDependencies?.next);
|
|
560
|
-
const dry = opts.dryRun;
|
|
561
|
-
const verb = (a) => a === "unchanged" ? "keep" : a === "skip" ? "skip" : dry ? `would ${a}` : a;
|
|
562
|
-
const entries = /* @__PURE__ */ new Map();
|
|
563
|
-
const only = /* @__PURE__ */ new Map();
|
|
564
|
-
const main = blocks[blocks.length - 1];
|
|
565
|
-
for (const b of blocks) {
|
|
566
|
-
const comp = b === main ? parsed.component : void 0;
|
|
567
|
-
only.set(b.id, comp);
|
|
568
|
-
entries.set(b.id, planBlock(b, comp, reg, targetAbs, root, opts.force));
|
|
569
|
-
}
|
|
570
|
-
const all = [...entries.values()].flat();
|
|
571
|
-
const route = planAppFiles(blocks, reg, targetAbs, root, isNext, opts.force);
|
|
572
|
-
io.out(`${dry ? "Dry run: " : ""}adding ${blocks.map((b) => b.id).join(" + ")} to ${target}`);
|
|
573
|
-
for (const f of [...all, ...route.files]) io.out(`${verb(f.action)} ${f.rel}${f.detail ? ` (${f.detail})` : ""}`);
|
|
574
|
-
if (route.note) route.note.forEach((l) => io.out(l));
|
|
575
|
-
if (!dry) {
|
|
576
|
-
for (const f of [...all, ...route.files]) if (f.action === "create" || f.action === "update") writeFile(root, f);
|
|
577
|
-
}
|
|
578
|
-
ensureEnvExample(root, dry, io.out);
|
|
579
|
-
const ledgerPath = import_node_path2.default.join(root, "graph8.json");
|
|
580
|
-
const next = ledgerContent(readLedger(ledgerPath), posix(import_node_path2.default.relative(root, targetAbs)), entries, only);
|
|
581
|
-
const prevText = import_node_fs2.default.existsSync(ledgerPath) ? import_node_fs2.default.readFileSync(ledgerPath, "utf8") : null;
|
|
582
|
-
if (next !== prevText) {
|
|
583
|
-
io.out(`${dry ? "would " : ""}${prevText === null ? "create" : "update"} graph8.json`);
|
|
584
|
-
if (!dry) import_node_fs2.default.writeFileSync(ledgerPath, next, "utf8");
|
|
585
|
-
} else io.out("keep graph8.json");
|
|
586
|
-
return installPeers(blocks, pkg, root, opts, io);
|
|
587
|
-
}
|
|
588
|
-
function installPeers(blocks, pkg, root, opts, io) {
|
|
589
|
-
const missing = missingPeers(blocks, pkg);
|
|
590
|
-
if (!missing.length) return 0;
|
|
591
|
-
const pm = detectPackageManager(root);
|
|
592
|
-
const { cmd, args } = installCommand(pm, missing);
|
|
593
|
-
const shown = `${cmd} ${args.join(" ")}`;
|
|
594
|
-
if (!opts.install || opts.dryRun) {
|
|
595
|
-
io.out(`${opts.dryRun ? "would run" : "not installing (--no-install). Run"}: ${shown}`);
|
|
596
|
-
return 0;
|
|
597
|
-
}
|
|
598
|
-
io.out(`installing peer dependencies: ${shown}`);
|
|
599
|
-
const code = io.spawn(cmd, args, root);
|
|
600
|
-
if (code !== 0) {
|
|
601
|
-
io.err(`peer dependency install failed (exit ${code}). Run it yourself: ${shown}`);
|
|
602
|
-
return 1;
|
|
603
|
-
}
|
|
604
|
-
return 0;
|
|
605
|
-
}
|
|
606
|
-
function appTargetPath(root, target) {
|
|
607
|
-
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"));
|
|
608
|
-
return useSrc && target.startsWith("app/") ? `src/${target}` : target;
|
|
609
|
-
}
|
|
610
|
-
function planAppFiles(blocks, reg, targetAbs, root, isNext, force) {
|
|
611
|
-
const files = [];
|
|
612
|
-
let skipped = false;
|
|
613
|
-
for (const b of blocks) {
|
|
614
|
-
for (const [file, target] of appTargeted(b)) {
|
|
615
|
-
if (!isNext) {
|
|
616
|
-
skipped = true;
|
|
617
|
-
continue;
|
|
618
|
-
}
|
|
619
|
-
const destAbs = import_node_path2.default.join(root, appTargetPath(root, target));
|
|
620
|
-
const src = import_node_path2.default.join(b.root, file);
|
|
621
|
-
const content = rewriteImports(import_node_fs2.default.readFileSync(src, "utf8"), { reg, targetAbs, destDir: import_node_path2.default.dirname(destAbs) });
|
|
622
|
-
files.push(classify(destAbs, posix(import_node_path2.default.relative(root, destAbs)), content, force));
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
return { files, note: skipped ? PROXY_CONTRACT : void 0 };
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// src/blocks/create.ts
|
|
629
|
-
var import_node_fs3 = __toESM(require("fs"));
|
|
630
|
-
var import_node_path3 = __toESM(require("path"));
|
|
631
|
-
var CREATE_USAGE = `Usage: npx @graph8/sdk create <stack> <dir> [options]
|
|
632
|
-
|
|
633
|
-
Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
|
|
634
|
-
install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
|
|
635
|
-
--package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm
|
|
636
|
-
--no-install write the files and blocks, do not install dependencies
|
|
637
|
-
--dry-run print what would be written, write and install nothing
|
|
638
|
-
--force allow a non-empty <dir> (template files are overwritten, and graph8.json
|
|
639
|
-
is reset: blocks you added to that app earlier are no longer recorded)
|
|
640
|
-
Then: set G8_API_KEY in <dir>/.env.local and run \`npx @graph8/sdk doctor --stack <stack>\`.`;
|
|
641
|
-
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
642
|
-
function parseCreateArgs(argv) {
|
|
643
|
-
const o = { stack: "", dir: "", install: true, dryRun: false, force: false };
|
|
644
|
-
const positional = [];
|
|
645
|
-
for (let i = 0; i < argv.length; i++) {
|
|
646
|
-
const arg = argv[i];
|
|
647
|
-
const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
|
|
648
|
-
const [flag, inline] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [arg, void 0];
|
|
649
|
-
if (flag === "--package-manager") {
|
|
650
|
-
const v = inline ?? argv[++i];
|
|
651
|
-
if (!PACKAGE_MANAGERS.includes(v)) throw new Error(`--package-manager must be one of ${PACKAGE_MANAGERS.join(", ")}
|
|
652
|
-
|
|
653
|
-
${CREATE_USAGE}`);
|
|
654
|
-
o.packageManager = v;
|
|
655
|
-
} else if (arg === "--no-install") o.install = false;
|
|
656
|
-
else if (arg === "--dry-run") o.dryRun = true;
|
|
657
|
-
else if (arg === "--force") o.force = true;
|
|
658
|
-
else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
|
|
659
|
-
|
|
660
|
-
${CREATE_USAGE}`);
|
|
661
|
-
else positional.push(arg);
|
|
662
|
-
}
|
|
663
|
-
if (positional.length !== 2) throw new Error(`create needs a stack and a directory
|
|
664
|
-
|
|
665
|
-
${CREATE_USAGE}`);
|
|
666
|
-
[o.stack, o.dir] = positional;
|
|
667
|
-
return o;
|
|
668
|
-
}
|
|
669
|
-
function packageManagerFromUserAgent(ua) {
|
|
670
|
-
const name = /^(npm|pnpm|yarn|bun)\//.exec(ua ?? "")?.[1];
|
|
671
|
-
return name ?? "npm";
|
|
672
|
-
}
|
|
673
|
-
function appNameFor(dir) {
|
|
674
|
-
const base = import_node_path3.default.basename(import_node_path3.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
|
|
675
|
-
return base || "g8-app";
|
|
676
|
-
}
|
|
677
|
-
function render(template, vars) {
|
|
678
|
-
return template.replace(/\{\{(\w+)\}\}/g, (_m, key) => {
|
|
679
|
-
if (!(key in vars)) throw new Error(`template placeholder {{${key}}} has no value`);
|
|
680
|
-
return vars[key];
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
|
|
684
|
-
var posix2 = (p) => p.split(import_node_path3.default.sep).join("/");
|
|
685
|
-
function walk(dir, base = dir) {
|
|
686
|
-
const out = [];
|
|
687
|
-
for (const e of import_node_fs3.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
688
|
-
const full = import_node_path3.default.join(dir, e.name);
|
|
689
|
-
if (e.isDirectory()) out.push(...walk(full, base));
|
|
690
|
-
else out.push(posix2(import_node_path3.default.relative(base, full)));
|
|
691
|
-
}
|
|
692
|
-
return out;
|
|
693
|
-
}
|
|
694
|
-
function outputName(rel) {
|
|
695
|
-
const noExt = rel.replace(/\.tmpl$/, "");
|
|
696
|
-
const dir = import_node_path3.default.posix.dirname(noExt);
|
|
697
|
-
const base = import_node_path3.default.posix.basename(noExt).replace(/^dot-/, ".");
|
|
698
|
-
return dir === "." ? base : `${dir}/${base}`;
|
|
699
|
-
}
|
|
700
|
-
function pageFileFor(navPath) {
|
|
701
|
-
const segs = navPath.split("/").filter(Boolean);
|
|
702
|
-
return ["app", ...segs, "page.tsx"].join("/");
|
|
703
|
-
}
|
|
704
|
-
function componentImportPath(block, file) {
|
|
705
|
-
const rel = installedRel(file) ?? file;
|
|
706
|
-
return `@/components/g8/${block.dir}/${rel.replace(/\.(tsx|ts|jsx|js)$/, "")}`;
|
|
707
|
-
}
|
|
708
|
-
function stackProblem(reg, stack) {
|
|
709
|
-
const { missing } = stackStatus(reg, stack);
|
|
710
|
-
if (missing.length) {
|
|
711
|
-
const ready = reg.stacks.filter((s) => !stackStatus(reg, s).missing.length).map((s) => s.id);
|
|
712
|
-
return [
|
|
713
|
-
`stack "${stack.id}" is planned: these blocks are not in this version of @graph8/sdk yet: ${missing.join(", ")}.`,
|
|
714
|
-
`Stacks you can create now: ${ready.join(", ") || "none"}. Or create one of those and add blocks as they ship (\`graph8-sdk add <block>\`).`
|
|
715
|
-
].join("\n");
|
|
716
|
-
}
|
|
717
|
-
const seen = /* @__PURE__ */ new Set();
|
|
718
|
-
for (const n of stack.nav) {
|
|
719
|
-
const block = findBlock(reg, n.block);
|
|
720
|
-
if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
|
|
721
|
-
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.`;
|
|
722
|
-
if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
|
|
723
|
-
if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
|
|
724
|
-
seen.add(n.path);
|
|
725
|
-
}
|
|
726
|
-
return null;
|
|
727
|
-
}
|
|
728
|
-
function renderPages(reg, stack, templates) {
|
|
729
|
-
const pageTpl = import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
|
|
730
|
-
const files = stack.nav.map((n) => {
|
|
731
|
-
const block = findBlock(reg, n.block);
|
|
732
|
-
const comp = block.components.find((c) => c.name === n.component);
|
|
733
|
-
const content = render(pageTpl, {
|
|
734
|
-
stackId: stack.id,
|
|
735
|
-
block: block.id,
|
|
736
|
-
blockDir: `components/g8/${block.dir}`,
|
|
737
|
-
component: n.component,
|
|
738
|
-
importPath: componentImportPath(block, comp.file),
|
|
739
|
-
propsJson: JSON.stringify(n.props ?? {}, null, 2)
|
|
740
|
-
});
|
|
741
|
-
return { rel: pageFileFor(n.path), content };
|
|
742
|
-
});
|
|
743
|
-
if (stack.nav.length && !stack.nav.some((n) => n.path === "/")) {
|
|
744
|
-
const redirect = import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
|
|
745
|
-
files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(stack.nav[0].path) }) });
|
|
746
|
-
}
|
|
747
|
-
return files;
|
|
748
|
-
}
|
|
749
|
-
function peerDependenciesFor(blocks, io) {
|
|
750
|
-
const peers = {};
|
|
751
|
-
for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
|
|
752
|
-
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"))));
|
|
753
|
-
if (usesUi && io.sdkPackageJson && import_node_fs3.default.existsSync(io.sdkPackageJson)) {
|
|
754
|
-
const sdk = JSON.parse(import_node_fs3.default.readFileSync(io.sdkPackageJson, "utf8"));
|
|
755
|
-
for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
|
|
756
|
-
}
|
|
757
|
-
return peers;
|
|
758
|
-
}
|
|
759
|
-
function mergedPackageJson(rendered, peers) {
|
|
760
|
-
const pkg = JSON.parse(rendered);
|
|
761
|
-
for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
|
|
762
|
-
pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
763
|
-
return `${JSON.stringify(pkg, null, 2)}
|
|
764
|
-
`;
|
|
765
|
-
}
|
|
766
|
-
function planCreate(reg, stack, appName, pm, io) {
|
|
767
|
-
const templates = import_node_path3.default.join(io.templatesRoot, "next-app");
|
|
768
|
-
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
769
|
-
const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
770
|
-
const vars = {
|
|
771
|
-
appName,
|
|
772
|
-
sdkVersion: SDK_VERSION,
|
|
773
|
-
stackId: stack.id,
|
|
774
|
-
title: stack.title,
|
|
775
|
-
titleJson: JSON.stringify(stack.title),
|
|
776
|
-
navJson: JSON.stringify(stack.nav.map((n) => ({ label: n.label, path: n.path })), null, 2),
|
|
777
|
-
devCommand,
|
|
778
|
-
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>`)"
|
|
779
|
-
};
|
|
780
|
-
const files = [];
|
|
781
|
-
for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
|
|
782
|
-
let content = render(import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, rel), "utf8"), vars);
|
|
783
|
-
if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io));
|
|
784
|
-
files.push({ rel: outputName(rel), content });
|
|
785
|
-
}
|
|
786
|
-
files.push(...renderPages(reg, stack, templates));
|
|
787
|
-
const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, blocks: {} };
|
|
788
|
-
files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
|
|
789
|
-
` });
|
|
790
|
-
return files;
|
|
791
|
-
}
|
|
792
|
-
function dirProblem(abs, force) {
|
|
793
|
-
if (!import_node_fs3.default.existsSync(abs)) return null;
|
|
794
|
-
if (!import_node_fs3.default.statSync(abs).isDirectory()) return `${abs} exists and is not a directory.`;
|
|
795
|
-
if (force || import_node_fs3.default.readdirSync(abs).length === 0) return null;
|
|
796
|
-
return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
|
|
797
|
-
}
|
|
798
|
-
function runCreate(opts, reg, io, cwd) {
|
|
799
|
-
const stack = reg.stacks.find((s) => s.id === opts.stack);
|
|
800
|
-
if (!stack) {
|
|
801
|
-
io.err(`unknown stack "${opts.stack}". Stacks: ${reg.stacks.map((s) => s.id).join(", ") || "none"} (\`graph8-sdk list\`).`);
|
|
802
|
-
return 1;
|
|
803
|
-
}
|
|
804
|
-
const problem = stackProblem(reg, stack);
|
|
805
|
-
if (problem) return io.err(problem), 1;
|
|
806
|
-
const abs = import_node_path3.default.resolve(cwd, opts.dir);
|
|
807
|
-
const dirIssue = dirProblem(abs, opts.force);
|
|
808
|
-
if (dirIssue) return io.err(dirIssue), 1;
|
|
809
|
-
const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
|
|
810
|
-
let files;
|
|
811
|
-
try {
|
|
812
|
-
files = planCreate(reg, stack, appNameFor(opts.dir), pm, io);
|
|
813
|
-
} catch (e) {
|
|
814
|
-
io.err(e.message);
|
|
815
|
-
return 1;
|
|
816
|
-
}
|
|
817
|
-
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
818
|
-
io.out(`${opts.dryRun ? "Dry run: " : ""}creating "${stack.title}" (${stack.id}) in ${opts.dir}`);
|
|
819
|
-
for (const f of files) io.out(`${opts.dryRun ? "would write" : "write"} ${f.rel}`);
|
|
820
|
-
if (opts.dryRun) {
|
|
821
|
-
io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
|
|
822
|
-
io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
|
|
823
|
-
return 0;
|
|
824
|
-
}
|
|
825
|
-
for (const f of files) {
|
|
826
|
-
const dest = import_node_path3.default.join(abs, f.rel);
|
|
827
|
-
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(dest), { recursive: true });
|
|
828
|
-
import_node_fs3.default.writeFileSync(dest, f.content, "utf8");
|
|
829
|
-
}
|
|
830
|
-
return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
|
|
831
|
-
}
|
|
832
|
-
function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
|
|
833
|
-
for (const b of blocks) {
|
|
834
|
-
const code = runAdd({ spec: b.id, dir: abs, target: "components/g8", dryRun: false, force: opts.force, install: false }, reg, io, cwd);
|
|
835
|
-
if (code !== 0) {
|
|
836
|
-
io.err(`adding block "${b.id}" failed; the app in ${opts.dir} is incomplete.`);
|
|
837
|
-
return code;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
if (opts.install) {
|
|
841
|
-
io.out(`installing dependencies: ${pm} install`);
|
|
842
|
-
const code = io.spawn(pm, ["install"], abs);
|
|
843
|
-
if (code !== 0) {
|
|
844
|
-
io.err(`dependency install failed (exit ${code}). Run it yourself: cd ${quoted(opts.dir)} && ${pm} install`);
|
|
845
|
-
return 1;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
const cd = opts.dir === "." ? "" : `cd ${quoted(opts.dir)} && `;
|
|
849
|
-
io.out(
|
|
850
|
-
[
|
|
851
|
-
"",
|
|
852
|
-
`Done. Next:`,
|
|
853
|
-
` 1. set G8_API_KEY in ${import_node_path3.default.posix.join(opts.dir, ".env.local")} (copy .env.local.example)`,
|
|
854
|
-
` 2. ${cd}npx @graph8/sdk doctor --stack ${stack.id}`,
|
|
855
|
-
` 3. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`
|
|
856
|
-
].join("\n")
|
|
857
|
-
);
|
|
858
|
-
return 0;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// src/blocks/doctor.ts
|
|
862
|
-
var import_node_fs4 = __toESM(require("fs"));
|
|
863
|
-
var import_node_path4 = __toESM(require("path"));
|
|
864
|
-
|
|
865
|
-
// src/generated/billable.ts
|
|
866
|
-
var BILLABLE_ROUTES = {
|
|
867
|
-
GET: [
|
|
868
|
-
"/apps/{app_id}/runtime",
|
|
869
|
-
"/audience-syncs/{config_id}/runs",
|
|
870
|
-
"/campaigns/{campaign_id}/dashboard",
|
|
871
|
-
"/companies/{company_id}/next-best-step",
|
|
872
|
-
"/contacts/{contact_id}/next-best-step",
|
|
873
|
-
"/desk/kickoff",
|
|
874
|
-
"/desk/kickoff/audio",
|
|
875
|
-
"/radar/competitors/{competitor_id}/discover-pages",
|
|
876
|
-
"/repos/{repo_id}/scan",
|
|
877
|
-
"/seo/domains/{domain}/backlinks",
|
|
878
|
-
"/seo/domains/{domain}/overview",
|
|
879
|
-
"/voice/campaigns/call-scripts",
|
|
880
|
-
"/voice/campaigns/email-templates",
|
|
881
|
-
"/voice/campaigns/templates",
|
|
882
|
-
"/voice/research/business-data",
|
|
883
|
-
"/voice/research/competitors",
|
|
884
|
-
"/voice/research/faq",
|
|
885
|
-
"/voice/research/keywords",
|
|
886
|
-
"/voice/research/prospect-company",
|
|
887
|
-
"/work/routines/{routine_id}/runs"
|
|
888
|
-
],
|
|
889
|
-
PATCH: [
|
|
890
|
-
"/enrichment/auto-enrichment/configs/{config_id}",
|
|
891
|
-
"/intent/keywords/{keyword_id}",
|
|
892
|
-
"/intent/keywords/{keyword_id}/sync-config",
|
|
893
|
-
"/marketplace/agencies/{slug}",
|
|
894
|
-
"/marketplace/agencies/{slug}/reps/{sdr_id}",
|
|
895
|
-
"/marketplace/agencies/{slug}/service-statements/{statement_id}",
|
|
896
|
-
"/marketplace/applications/{application_id}",
|
|
897
|
-
"/marketplace/conversations/{conversation_id}/state",
|
|
898
|
-
"/marketplace/job-posts/{post_id}",
|
|
899
|
-
"/marketplace/me/meetings/{meeting_id}",
|
|
900
|
-
"/marketplace/me/messages/{conversation_id}/state",
|
|
901
|
-
"/marketplace/me/profile",
|
|
902
|
-
"/marketplace/me/service-statements/{statement_id}",
|
|
903
|
-
"/marketplace/org/meetings/{meeting_id}",
|
|
904
|
-
"/radar/competitors/{competitor_id}",
|
|
905
|
-
"/radar/gaps/{gap_id}",
|
|
906
|
-
"/research-reports/{report_id}",
|
|
907
|
-
"/social-listeners/settings",
|
|
908
|
-
"/social-listeners/{listener_id}"
|
|
909
|
-
],
|
|
910
|
-
POST: [
|
|
911
|
-
"/accounts/{account_id}/generate-outreach-plan",
|
|
912
|
-
"/accounts/{account_id}/security-questionnaires",
|
|
913
|
-
"/accounts/{account_id}/stakeholders/{stakeholder_id}/generate-outreach",
|
|
914
|
-
"/ad-creatives",
|
|
915
|
-
"/ad-creatives/batch",
|
|
916
|
-
"/ad-creatives/concepts",
|
|
917
|
-
"/ad-creatives/video/clips",
|
|
918
|
-
"/ad-creatives/video/clips/prompt-enhance",
|
|
919
|
-
"/ad-creatives/video/clips/{clip_id}/continue",
|
|
920
|
-
"/ad-creatives/video/clips/{clip_id}/extend",
|
|
921
|
-
"/ad-creatives/video/clips/{clip_id}/regenerate",
|
|
922
|
-
"/ad-creatives/{creative_id}/ai-review",
|
|
923
|
-
"/ad-creatives/{creative_id}/chat-edit",
|
|
924
|
-
"/ad-creatives/{creative_id}/image-edit-at-point",
|
|
925
|
-
"/ad-creatives/{creative_id}/persona-lens",
|
|
926
|
-
"/ad-creatives/{creative_id}/regenerate-copy",
|
|
927
|
-
"/ad-creatives/{creative_id}/regenerate-image",
|
|
928
|
-
"/ad-creatives/{creative_id}/retry",
|
|
929
|
-
"/ad-creatives/{creative_id}/rewrite-field",
|
|
930
|
-
"/ad-creatives/{creative_id}/size-set",
|
|
931
|
-
"/agent/chat",
|
|
932
|
-
"/agent/onboarding/discovery/turn",
|
|
933
|
-
"/agent/operators/{operator}/approvals/{task_id}/decide",
|
|
934
|
-
"/agent/sessions/{session_id}/voice",
|
|
935
|
-
"/agent/work-items",
|
|
936
|
-
"/app-pages/{page_id}/generate",
|
|
937
|
-
"/campaigns/generate-ideas",
|
|
938
|
-
"/campaigns/generate-ideas-async",
|
|
939
|
-
"/campaigns/recommend-personas",
|
|
940
|
-
"/campaigns/{campaign_id}/audience-pipeline/accept-filters",
|
|
941
|
-
"/campaigns/{campaign_id}/audience-pipeline/trigger",
|
|
942
|
-
"/campaigns/{campaign_id}/documents/{document_id}/regenerate",
|
|
943
|
-
"/campaigns/{campaign_id}/sequence/steps/{step_id}/generate",
|
|
944
|
-
"/companies/enrich/bulk",
|
|
945
|
-
"/companies/enrich/people/bulk",
|
|
946
|
-
"/companies/enrich/stakeholders/bulk",
|
|
947
|
-
"/companies/import",
|
|
948
|
-
"/companies/{company_id}/documents/types/{doc_type}/generate",
|
|
949
|
-
"/companies/{company_id}/documents/types/{doc_type}/regenerate",
|
|
950
|
-
"/companies/{company_id}/enrich",
|
|
951
|
-
"/companies/{company_id}/retry-intelligence",
|
|
952
|
-
"/companies/{company_id}/retry-synthesis",
|
|
953
|
-
"/companies/{company_id}/workflows/{workflow_type}/start",
|
|
954
|
-
"/company-profile/generate",
|
|
955
|
-
"/company-profile/reindex",
|
|
956
|
-
"/company-profile/retry",
|
|
957
|
-
"/contacts/unlock-info",
|
|
958
|
-
"/contacts/verify-email",
|
|
959
|
-
"/contacts/verify-email/async",
|
|
960
|
-
"/contacts/workflow-executions/run",
|
|
961
|
-
"/contacts/{contact_id}/documents/generate-all",
|
|
962
|
-
"/contacts/{contact_id}/documents/types/{doc_type}/generate",
|
|
963
|
-
"/contacts/{contact_id}/documents/types/{doc_type}/regenerate",
|
|
964
|
-
"/content-workspace/import/crawl",
|
|
965
|
-
"/content/calendar/ideas/{idea_id}/create-content",
|
|
966
|
-
"/content/calendar/mine-signals",
|
|
967
|
-
"/content/calendar/repurpose/ad",
|
|
968
|
-
"/content/calendar/repurpose/linkedin-post",
|
|
969
|
-
"/content/grids/{grid_id}/columns/{column_id}/run",
|
|
970
|
-
"/content/grids/{grid_id}/rows/{row_id}/run/{column_id}",
|
|
971
|
-
"/content/pages/enrich-seo-bulk",
|
|
972
|
-
"/content/pages/{page_id}/enrich-seo",
|
|
973
|
-
"/copilot/chat",
|
|
974
|
-
"/copilot/landing-page-action",
|
|
975
|
-
"/copilot/memory/promote",
|
|
976
|
-
"/customer-success/companies/{company_id}/brief/synthesize",
|
|
977
|
-
"/deals/{deal_id}/documents/generate-all",
|
|
978
|
-
"/deals/{deal_id}/documents/types/{doc_type}/generate",
|
|
979
|
-
"/deals/{deal_id}/documents/types/{doc_type}/regenerate",
|
|
980
|
-
"/deals/{deal_id}/next-best-step",
|
|
981
|
-
"/desk/accounts/{account_id}/committee/{contact_id}/find-linkedin",
|
|
982
|
-
"/desk/accounts/{account_id}/plan/draft",
|
|
983
|
-
"/desk/ask",
|
|
984
|
-
"/desk/decisions/{decision_id}/replan",
|
|
985
|
-
"/desk/email-improve",
|
|
986
|
-
"/desk/email-score",
|
|
987
|
-
"/desk/org-settings/plan-stages/run",
|
|
988
|
-
"/desk/voice-memo",
|
|
989
|
-
"/dialer/parallel-calls/grading/backfill",
|
|
990
|
-
"/dialer/sessions/prewarm-voicemails",
|
|
991
|
-
"/enrichment/ai-formula/generate",
|
|
992
|
-
"/enrichment/ai-formula/generate-condition",
|
|
993
|
-
"/enrichment/ai/batch/enrich",
|
|
994
|
-
"/enrichment/ai/enrich",
|
|
995
|
-
"/enrichment/ai/qualification/generate-templates",
|
|
996
|
-
"/enrichment/ai/qualification/run",
|
|
997
|
-
"/enrichment/auto-enrichment/configs",
|
|
998
|
-
"/enrichment/enrich",
|
|
999
|
-
"/enrichment/find-people/configure",
|
|
1000
|
-
"/enrichment/find-people/search-import",
|
|
1001
|
-
"/enrichment/find-people/{config_id}/import",
|
|
1002
|
-
"/enrichment/find-people/{config_id}/preview",
|
|
1003
|
-
"/enrichment/find-people/{config_id}/run",
|
|
1004
|
-
"/enrichment/generate-prompt",
|
|
1005
|
-
"/enrichment/jobs/{job_id}/resume",
|
|
1006
|
-
"/enrichment/list-routing/generate-filter",
|
|
1007
|
-
"/enrichment/lists/{list_id}/pipelines",
|
|
1008
|
-
"/enrichment/lists/{list_id}/pipelines/ai-draft",
|
|
1009
|
-
"/enrichment/lists/{list_id}/pipelines/{pipeline_id}/enabled",
|
|
1010
|
-
"/enrichment/lists/{list_id}/pipelines/{pipeline_id}/runs",
|
|
1011
|
-
"/enrichment/lookup/company",
|
|
1012
|
-
"/enrichment/lookup/person",
|
|
1013
|
-
"/enrichment/person",
|
|
1014
|
-
"/enrichment/record-sync/{entity_type}/run",
|
|
1015
|
-
"/enrichment/verify-email",
|
|
1016
|
-
"/enrichment/waterfall/configs",
|
|
1017
|
-
"/enrichment/waterfall/enrich",
|
|
1018
|
-
"/global-context/generate-all",
|
|
1019
|
-
"/global-context/generate/{doc_type}",
|
|
1020
|
-
"/hiring-signals/aggregators",
|
|
1021
|
-
"/hiring-signals/companies",
|
|
1022
|
-
"/hiring-signals/companies/job-counts",
|
|
1023
|
-
"/hiring-signals/companies/resolve-ids",
|
|
1024
|
-
"/hiring-signals/decision-makers",
|
|
1025
|
-
"/hiring-signals/job-changes/match",
|
|
1026
|
-
"/hiring-signals/jobs",
|
|
1027
|
-
"/hiring-signals/recruiters",
|
|
1028
|
-
"/icps/generate",
|
|
1029
|
-
"/inbox/drafts/generate",
|
|
1030
|
-
"/intelligence/analyze",
|
|
1031
|
-
"/intelligence/retry",
|
|
1032
|
-
"/intent/contacts/metadata",
|
|
1033
|
-
"/intent/keywords/add",
|
|
1034
|
-
"/intent/keywords/auto-seed-proposals",
|
|
1035
|
-
"/intent/keywords/create-from-domain",
|
|
1036
|
-
"/intent/keywords/create-from-search",
|
|
1037
|
-
"/intent/keywords/list",
|
|
1038
|
-
"/intent/keywords/{keyword_id}/companies",
|
|
1039
|
-
"/intent/keywords/{keyword_id}/contacts",
|
|
1040
|
-
"/intent/keywords/{keyword_id}/filters",
|
|
1041
|
-
"/intent/keywords/{keyword_id}/job-resolution",
|
|
1042
|
-
"/intent/keywords/{keyword_id}/sync",
|
|
1043
|
-
"/intent/keywords/{keyword_id}/urls",
|
|
1044
|
-
"/intent/pages-by-domain",
|
|
1045
|
-
"/intent/pages/contacts",
|
|
1046
|
-
"/intent/pages/search",
|
|
1047
|
-
"/intent/pages/visitor-counts",
|
|
1048
|
-
"/intent/pages/visitors",
|
|
1049
|
-
"/landing-pages",
|
|
1050
|
-
"/landing-pages/clone-homepage",
|
|
1051
|
-
"/landing-pages/clone-html",
|
|
1052
|
-
"/landing-pages/clone-url",
|
|
1053
|
-
"/landing-pages/components/extract",
|
|
1054
|
-
"/landing-pages/ensure-base-template",
|
|
1055
|
-
"/landing-pages/{landing_page_id}/ai-review",
|
|
1056
|
-
"/landing-pages/{landing_page_id}/ai-review/fix-with-ai",
|
|
1057
|
-
"/landing-pages/{landing_page_id}/edit",
|
|
1058
|
-
"/landing-pages/{landing_page_id}/edit-element",
|
|
1059
|
-
"/landing-pages/{landing_page_id}/edit-page",
|
|
1060
|
-
"/landing-pages/{landing_page_id}/generate-seo-metadata",
|
|
1061
|
-
"/landing-pages/{landing_page_id}/rewrite-text",
|
|
1062
|
-
"/leads/generate-content/batches",
|
|
1063
|
-
"/leads/{lead_id}/generate-content",
|
|
1064
|
-
"/library/case-studies/backfill",
|
|
1065
|
-
"/lifecycle-contacts/unlock",
|
|
1066
|
-
"/mailboxes/schedule-buy/plans",
|
|
1067
|
-
"/mailboxes/schedule-buy/plans/{plan_id}/resume",
|
|
1068
|
-
"/mailboxes/uncancel",
|
|
1069
|
-
"/mailboxes/{mailbox_id}/health/placement-test",
|
|
1070
|
-
"/marketplace/agencies/{slug}/intake/upload",
|
|
1071
|
-
"/marketplace/agencies/{slug}/reps/{sdr_id}/unlist",
|
|
1072
|
-
"/marketplace/agencies/{slug}/reps/{sdr_id}/video-intro",
|
|
1073
|
-
"/marketplace/agencies/{slug}/service-statements",
|
|
1074
|
-
"/marketplace/agencies/{slug}/service-statements/{statement_id}/line-items",
|
|
1075
|
-
"/marketplace/agencies/{slug}/service-statements/{statement_id}/submit",
|
|
1076
|
-
"/marketplace/commissions/entries",
|
|
1077
|
-
"/marketplace/commissions/entries/{entry_id}/quality",
|
|
1078
|
-
"/marketplace/commissions/entries/{entry_id}/reversals",
|
|
1079
|
-
"/marketplace/commissions/entries/{entry_id}/statement",
|
|
1080
|
-
"/marketplace/commissions/plans",
|
|
1081
|
-
"/marketplace/commissions/plans/{version_id}/activate",
|
|
1082
|
-
"/marketplace/commissions/preview",
|
|
1083
|
-
"/marketplace/conversations/{conversation_id}/read",
|
|
1084
|
-
"/marketplace/job-posts",
|
|
1085
|
-
"/marketplace/job-posts/{post_id}/jd",
|
|
1086
|
-
"/marketplace/me/headshot",
|
|
1087
|
-
"/marketplace/me/invoices",
|
|
1088
|
-
"/marketplace/me/invoices/{invoice_id}/approve",
|
|
1089
|
-
"/marketplace/me/messages/{conversation_id}/read",
|
|
1090
|
-
"/marketplace/me/offers/{hiring_id}/accept",
|
|
1091
|
-
"/marketplace/me/offers/{hiring_id}/reject",
|
|
1092
|
-
"/marketplace/me/payout-account/w8-certify",
|
|
1093
|
-
"/marketplace/me/resume",
|
|
1094
|
-
"/marketplace/me/service-statements",
|
|
1095
|
-
"/marketplace/me/service-statements/{statement_id}/line-items",
|
|
1096
|
-
"/marketplace/me/service-statements/{statement_id}/revise",
|
|
1097
|
-
"/marketplace/me/service-statements/{statement_id}/submit",
|
|
1098
|
-
"/marketplace/me/video-intro",
|
|
1099
|
-
"/marketplace/me/work-certificates",
|
|
1100
|
-
"/marketplace/opportunities/{post_id}/apply",
|
|
1101
|
-
"/marketplace/org/hirings/{hiring_id}/meetings",
|
|
1102
|
-
"/marketplace/org/hirings/{hiring_id}/terminate",
|
|
1103
|
-
"/marketplace/org/hirings/{hiring_id}/withdraw",
|
|
1104
|
-
"/marketplace/org/invoices/{invoice_id}/approve",
|
|
1105
|
-
"/marketplace/org/invoices/{invoice_id}/dispute",
|
|
1106
|
-
"/marketplace/org/service-statements/{statement_id}/approve",
|
|
1107
|
-
"/marketplace/org/service-statements/{statement_id}/line-items",
|
|
1108
|
-
"/marketplace/org/service-statements/{statement_id}/reject",
|
|
1109
|
-
"/marketplace/org/service-statements/{statement_id}/request-changes",
|
|
1110
|
-
"/marketplace/shortlist/export",
|
|
1111
|
-
"/marketplace/talent/{sdr_id}/hire",
|
|
1112
|
-
"/marketplace/talent/{sdr_id}/match/analyze",
|
|
1113
|
-
"/marketplace/talent/{sdr_id}/match/refresh",
|
|
1114
|
-
"/marketplace/terms-acknowledgements/ack-all",
|
|
1115
|
-
"/meeting-transcripts/{transcript_id}/follow-up-draft",
|
|
1116
|
-
"/meeting-transcripts/{transcript_id}/review/regenerate",
|
|
1117
|
-
"/newsletters/templates/clone",
|
|
1118
|
-
"/newsletters/templates/custom",
|
|
1119
|
-
"/newsletters/templates/layout",
|
|
1120
|
-
"/newsletters/{newsletter_id}/issues/{issue_id}/generate",
|
|
1121
|
-
"/newsletters/{newsletter_id}/issues/{issue_id}/rewrite-text",
|
|
1122
|
-
"/newsletters/{newsletter_id}/subscribers/import-audience",
|
|
1123
|
-
"/newsletters/{newsletter_id}/subscribers/verify",
|
|
1124
|
-
"/org-videos/generate",
|
|
1125
|
-
"/org/brand-snapshot/refresh",
|
|
1126
|
-
"/personas/generate",
|
|
1127
|
-
"/phone-numbers/purchase",
|
|
1128
|
-
"/pipelines/suggest",
|
|
1129
|
-
"/placement/tests",
|
|
1130
|
-
"/provider-domains/add-external",
|
|
1131
|
-
"/provider-domains/check-availability",
|
|
1132
|
-
"/provider-domains/checkout-purchase",
|
|
1133
|
-
"/provider-domains/checkout-transfer",
|
|
1134
|
-
"/provider-domains/{domain_id}/auto-warmup",
|
|
1135
|
-
"/provider-domains/{domain_id}/check-propagation",
|
|
1136
|
-
"/provider-domains/{domain_id}/checkout-mailboxes",
|
|
1137
|
-
"/provider-domains/{domain_id}/connect-provider",
|
|
1138
|
-
"/provider-domains/{domain_id}/dns",
|
|
1139
|
-
"/public/enrich/lookup",
|
|
1140
|
-
"/radar/aeo/run",
|
|
1141
|
-
"/radar/competitors",
|
|
1142
|
-
"/radar/competitors/bulk",
|
|
1143
|
-
"/radar/competitors/{competitor_id}/scan",
|
|
1144
|
-
"/radar/gaps/analyze",
|
|
1145
|
-
"/radar/initiatives/measure",
|
|
1146
|
-
"/radar/opportunities/approve-all",
|
|
1147
|
-
"/radar/opportunities/{gap_id}/approve",
|
|
1148
|
-
"/radar/reports",
|
|
1149
|
-
"/radar/scan-all",
|
|
1150
|
-
"/repos/{repo_id}/scan",
|
|
1151
|
-
"/research-reports/generate",
|
|
1152
|
-
"/research-reports/generate-all",
|
|
1153
|
-
"/research-reports/retry",
|
|
1154
|
-
"/research-reports/workflow/resume",
|
|
1155
|
-
"/revenue/stage-docs/{lifecycle_entry_id}/{entity}/backfill",
|
|
1156
|
-
"/scrape/requests",
|
|
1157
|
-
"/search/companies",
|
|
1158
|
-
"/search/companies/save",
|
|
1159
|
-
"/search/contacts",
|
|
1160
|
-
"/search/contacts/save",
|
|
1161
|
-
"/seo/audits",
|
|
1162
|
-
"/seo/keywords/research",
|
|
1163
|
-
"/sequencer/content/email/generate",
|
|
1164
|
-
"/sequencer/content/instructions/improve",
|
|
1165
|
-
"/sequencer/content/linkedin/generate",
|
|
1166
|
-
"/sequencer/templates/steps/generate",
|
|
1167
|
-
"/social-listeners",
|
|
1168
|
-
"/social-listeners/auto-seed/all",
|
|
1169
|
-
"/social-listeners/auto-seed/brand",
|
|
1170
|
-
"/social-listeners/auto-seed/competitors",
|
|
1171
|
-
"/social-listeners/auto-seed/engagement",
|
|
1172
|
-
"/social-listeners/auto-seed/keywords",
|
|
1173
|
-
"/social-listeners/bulk-from-list",
|
|
1174
|
-
"/social-listeners/integrations/x",
|
|
1175
|
-
"/social-listeners/integrations/x/test",
|
|
1176
|
-
"/social-listeners/posts/{post_id}/engagements/add-to-list",
|
|
1177
|
-
"/social-listeners/posts/{post_id}/mark",
|
|
1178
|
-
"/social-listeners/posts/{post_id}/save-contact",
|
|
1179
|
-
"/social-listeners/queries/generate",
|
|
1180
|
-
"/social/posts/generate",
|
|
1181
|
-
"/tam/generate",
|
|
1182
|
-
"/tam/regenerate",
|
|
1183
|
-
"/tasks/{task_id}/start",
|
|
1184
|
-
"/team-members/me/enrich-linkedin",
|
|
1185
|
-
"/team-members/{team_member_id}/enrich-linkedin",
|
|
1186
|
-
"/team-members/{team_member_id}/generate-post",
|
|
1187
|
-
"/teams/auto-compose/suggest",
|
|
1188
|
-
"/voice/agents/{agent_id}/documents/upload",
|
|
1189
|
-
"/voice/call-flow-examples/transcribe",
|
|
1190
|
-
"/voice/call-scripts/generate",
|
|
1191
|
-
"/voice/campaigns/call-scripts",
|
|
1192
|
-
"/voice/campaigns/email-enhancements",
|
|
1193
|
-
"/voice/campaigns/email-spintax",
|
|
1194
|
-
"/voice/campaigns/emails",
|
|
1195
|
-
"/voice/campaigns/linkedin-messages",
|
|
1196
|
-
"/voice/campaigns/onboarding/messages",
|
|
1197
|
-
"/voice/campaigns/onboarding/threads/actions",
|
|
1198
|
-
"/voice/campaigns/voicemails",
|
|
1199
|
-
"/voice/chat-agent/messages",
|
|
1200
|
-
"/voice/collections/{collection_id}/files/paste-text",
|
|
1201
|
-
"/voice/collections/{collection_id}/files/upload",
|
|
1202
|
-
"/voice/collections/{collection_id}/files/upload-multiple",
|
|
1203
|
-
"/voice/collections/{collection_id}/reindex",
|
|
1204
|
-
"/voice/collections/{collection_id}/scrape-sources",
|
|
1205
|
-
"/voice/collections/{collection_id}/scrape-sources/{source_id}/run",
|
|
1206
|
-
"/voice/email-agent/batch",
|
|
1207
|
-
"/voice/email-agent/compose",
|
|
1208
|
-
"/voice/email-agent/draft-reply",
|
|
1209
|
-
"/voice/icp/queries",
|
|
1210
|
-
"/voice/knowledge-base/files/upload",
|
|
1211
|
-
"/voice/receptionist/messages",
|
|
1212
|
-
"/voice/research/company",
|
|
1213
|
-
"/voice/research/org-chart",
|
|
1214
|
-
"/voice/research/prospect-data",
|
|
1215
|
-
"/voice/research/prospect-profile",
|
|
1216
|
-
"/voice/topics/classify",
|
|
1217
|
-
"/voice/twins",
|
|
1218
|
-
"/voice/twins/extract-linkedin",
|
|
1219
|
-
"/voice/video-recordings/upload",
|
|
1220
|
-
"/voice/voice-library/onboarding-voice",
|
|
1221
|
-
"/voice/voice-library/voices",
|
|
1222
|
-
"/voice/worksheets/calculate",
|
|
1223
|
-
"/work/signals/promote",
|
|
1224
|
-
"/workbench/tables/{table_id}/ai/generate-prompt",
|
|
1225
|
-
"/workbench/tables/{table_id}/ai/groups/{group_id}/run",
|
|
1226
|
-
"/workbench/tables/{table_id}/ai/preview",
|
|
1227
|
-
"/workbench/tables/{table_id}/ai/run",
|
|
1228
|
-
"/workbench/tables/{table_id}/ai/run-all",
|
|
1229
|
-
"/workbench/tables/{table_id}/columns/{col_id}/ai/run",
|
|
1230
|
-
"/workbench/tables/{table_id}/find-people/configure",
|
|
1231
|
-
"/workbench/tables/{table_id}/find-people/{config_id}/run",
|
|
1232
|
-
"/workbench/tables/{table_id}/import/search",
|
|
1233
|
-
"/workbench/tables/{table_id}/import/search/research/preview",
|
|
1234
|
-
"/workbench/tables/{table_id}/pipelines",
|
|
1235
|
-
"/workbench/tables/{table_id}/scraper/providers/{provider}/runs",
|
|
1236
|
-
"/workbench/tables/{table_id}/scraper/runs",
|
|
1237
|
-
"/workbench/tables/{table_id}/suggest-mappings",
|
|
1238
|
-
"/workbench/tables/{table_id}/triggers",
|
|
1239
|
-
"/workbench/tables/{table_id}/unlock-rows",
|
|
1240
|
-
"/workbench/tables/{table_id}/waterfall/configs/{column_id}/run"
|
|
1241
|
-
],
|
|
1242
|
-
PUT: [
|
|
1243
|
-
"/agent/operators/{operator}/enabled",
|
|
1244
|
-
"/agent/operators/{operator}/skills/{skill_key}",
|
|
1245
|
-
"/deliverability/autopilot/preferences",
|
|
1246
|
-
"/enrichment/auto-enrichment/settings/{entity_type}",
|
|
1247
|
-
"/enrichment/lists/{list_id}/pipelines/{pipeline_id}",
|
|
1248
|
-
"/enrichment/record-sync/{entity_type}/policy",
|
|
1249
|
-
"/enrichment/waterfall/configs/{column_id}",
|
|
1250
|
-
"/marketplace/me/invoices/{invoice_id}",
|
|
1251
|
-
"/marketplace/me/rates",
|
|
1252
|
-
"/radar/settings",
|
|
1253
|
-
"/workbench/tables/{table_id}/auto-enrich",
|
|
1254
|
-
"/workbench/tables/{table_id}/pipelines/{pipeline_id}",
|
|
1255
|
-
"/workbench/tables/{table_id}/schedule",
|
|
1256
|
-
"/workbench/tables/{table_id}/triggers/{trigger_id}"
|
|
1257
|
-
]
|
|
1258
|
-
};
|
|
1259
|
-
|
|
1260
|
-
// src/generated/external.ts
|
|
1261
|
-
var EXTERNAL_ROUTES = {
|
|
1262
|
-
GET: [
|
|
1263
|
-
"/dialer/sessions/token",
|
|
1264
|
-
"/marketplace/me/connect/dashboard-link",
|
|
1265
|
-
"/marketplace/me/connect/onboarding-link",
|
|
1266
|
-
"/marketplace/me/payout-account/login-link",
|
|
1267
|
-
"/marketplace/me/payout-account/onboarding-link",
|
|
1268
|
-
"/voice/mcp-servers/{mcp_server_id}/tools"
|
|
1269
|
-
],
|
|
1270
|
-
PATCH: [
|
|
1271
|
-
"/ad-campaigns/{campaign_id}/ads/{ad_id}",
|
|
1272
|
-
"/appointments/bookings/{booking_uid}/location",
|
|
1273
|
-
"/appointments/bookings/{booking_uid}/sequence",
|
|
1274
|
-
"/appointments/workflows/{workflow_id}",
|
|
1275
|
-
"/audience-syncs/{config_id}",
|
|
1276
|
-
"/campaigns/{campaign_id}",
|
|
1277
|
-
"/campaigns/{campaign_id}/full",
|
|
1278
|
-
"/content/calendar/{content_type}/{content_id}/schedule",
|
|
1279
|
-
"/inbox/mailboxes/bulk",
|
|
1280
|
-
"/inbox/mailboxes/{mailbox}/settings",
|
|
1281
|
-
"/integrations/crm/meetings/{event_id}",
|
|
1282
|
-
"/newsletters/{newsletter_id}/subscribers/{subscriber_id}/unsubscribe",
|
|
1283
|
-
"/quotes/{quote_id}",
|
|
1284
|
-
"/sequences/{sequence_id}",
|
|
1285
|
-
"/sequences/{sequence_id}/steps/{step_id}",
|
|
1286
|
-
"/voice/dialer/sessions/{session_id}/status",
|
|
1287
|
-
"/voice/dispositions/{disposition_id}",
|
|
1288
|
-
"/webhooks/{webhook_id}"
|
|
1289
|
-
],
|
|
1290
|
-
POST: [
|
|
1291
|
-
"/ad-campaigns/images",
|
|
1292
|
-
"/ad-campaigns/status-refresh",
|
|
1293
|
-
"/ad-campaigns/{campaign_id}/ads",
|
|
1294
|
-
"/ad-campaigns/{campaign_id}/ads/batch",
|
|
1295
|
-
"/ad-campaigns/{campaign_id}/ads/{ad_id}/status",
|
|
1296
|
-
"/ad-campaigns/{campaign_id}/edit-preview",
|
|
1297
|
-
"/ad-campaigns/{campaign_id}/retry",
|
|
1298
|
-
"/ad-campaigns/{campaign_id}/status-refresh",
|
|
1299
|
-
"/appointments/bookings",
|
|
1300
|
-
"/appointments/bookings/import",
|
|
1301
|
-
"/appointments/bookings/{booking_uid}/confirm",
|
|
1302
|
-
"/appointments/bookings/{booking_uid}/guests",
|
|
1303
|
-
"/appointments/bookings/{booking_uid}/mark-absent",
|
|
1304
|
-
"/appointments/bookings/{booking_uid}/notes",
|
|
1305
|
-
"/appointments/bookings/{booking_uid}/request-reschedule",
|
|
1306
|
-
"/appointments/bookings/{booking_uid}/reschedule",
|
|
1307
|
-
"/appointments/calendars/selected",
|
|
1308
|
-
"/appointments/conferencing/connect/{provider}",
|
|
1309
|
-
"/appointments/conferencing/{provider}/default",
|
|
1310
|
-
"/appointments/workflows",
|
|
1311
|
-
"/appointments/workflows/{workflow_id}/toggle",
|
|
1312
|
-
"/audience-syncs",
|
|
1313
|
-
"/audience-syncs/google/conversion-actions",
|
|
1314
|
-
"/audience-syncs/google/conversions",
|
|
1315
|
-
"/audience-syncs/google/video-assets/library",
|
|
1316
|
-
"/audience-syncs/google/video-assets/links",
|
|
1317
|
-
"/audience-syncs/google/video-assets/upload-urls",
|
|
1318
|
-
"/audience-syncs/google/video-assets/uploads",
|
|
1319
|
-
"/audience-syncs/{config_id}/launch",
|
|
1320
|
-
"/audience-syncs/{config_id}/trigger",
|
|
1321
|
-
"/campaigns",
|
|
1322
|
-
"/campaigns/{campaign_id}/ad-images/{image_id}/hide",
|
|
1323
|
-
"/campaigns/{campaign_id}/documents",
|
|
1324
|
-
"/campaigns/{campaign_id}/launch",
|
|
1325
|
-
"/campaigns/{campaign_id}/sequence/steps",
|
|
1326
|
-
"/content/calendar/{content_type}/{content_id}/retry-publish",
|
|
1327
|
-
"/crm-syncs/{provider}/companies/push",
|
|
1328
|
-
"/crm-syncs/{provider}/contacts/push",
|
|
1329
|
-
"/crm-syncs/{provider}/lists/push",
|
|
1330
|
-
"/customer-success/companies/{company_id}/sync",
|
|
1331
|
-
"/desk/accounts/{account_id}/committee/plays/{action_id}/run",
|
|
1332
|
-
"/desk/accounts/{account_id}/committee/{contact_id}/linkedin-invite",
|
|
1333
|
-
"/desk/decisions/{decision_id}/plays/{action_id}/run",
|
|
1334
|
-
"/desk/decisions/{decision_id}/send-reply",
|
|
1335
|
-
"/desk/decisions/{decision_id}/send-sms",
|
|
1336
|
-
"/desk/plays/approvals/{execution_id}/decide",
|
|
1337
|
-
"/dialer/parallel-calls/calls/{livekit_room_name}/drop-voicemail",
|
|
1338
|
-
"/dialer/parallel-calls/manual-dial",
|
|
1339
|
-
"/dialer/parallel-calls/redial-contact",
|
|
1340
|
-
"/dialer/parallel-calls/sessions/start",
|
|
1341
|
-
"/dialer/sessions/calls/drop-voicemail",
|
|
1342
|
-
"/dialer/sessions/{session_id}/next-batch",
|
|
1343
|
-
"/envelopes/{envelope_id}/resend",
|
|
1344
|
-
"/envelopes/{envelope_id}/send",
|
|
1345
|
-
"/feedback/sending-domains/verify",
|
|
1346
|
-
"/feedback/surveys/{survey_id}/send",
|
|
1347
|
-
"/forms/deliveries/{receipt_id}/retry",
|
|
1348
|
-
"/forms/{form_key}/backfills",
|
|
1349
|
-
"/forms/{form_key}/delivery/pause",
|
|
1350
|
-
"/forms/{form_key}/delivery/resume",
|
|
1351
|
-
"/inbox/emails/compose",
|
|
1352
|
-
"/inbox/emails/{email_id}/send",
|
|
1353
|
-
"/inbox/linkedin/accounts/sync",
|
|
1354
|
-
"/inbox/linkedin/accounts/{account_id}/enable",
|
|
1355
|
-
"/inbox/linkedin/compose",
|
|
1356
|
-
"/inbox/mailboxes/activate",
|
|
1357
|
-
"/inbox/mailboxes/{mailbox}/sync",
|
|
1358
|
-
"/inbox/sms/compose",
|
|
1359
|
-
"/inbox/{reply_id}/assign",
|
|
1360
|
-
"/inbox/{reply_id}/send",
|
|
1361
|
-
"/inbox/{reply_id}/tag",
|
|
1362
|
-
"/integrations/connections",
|
|
1363
|
-
"/integrations/connections/session-token",
|
|
1364
|
-
"/integrations/crm-sync/audiences",
|
|
1365
|
-
"/integrations/crm-sync/contacts/pull",
|
|
1366
|
-
"/integrations/crm-sync/contacts/push",
|
|
1367
|
-
"/integrations/crm-sync/deals/pull",
|
|
1368
|
-
"/integrations/crm-sync/deals/push",
|
|
1369
|
-
"/integrations/crm-sync/leads/previews",
|
|
1370
|
-
"/integrations/crm-sync/leads/pull",
|
|
1371
|
-
"/integrations/crm-sync/leads/push",
|
|
1372
|
-
"/integrations/crm-sync/pull",
|
|
1373
|
-
"/integrations/crm-sync/push",
|
|
1374
|
-
"/integrations/crm/connections/{connection_id}/batch/contacts",
|
|
1375
|
-
"/integrations/crm/connections/{connection_id}/companies",
|
|
1376
|
-
"/integrations/crm/connections/{connection_id}/contacts",
|
|
1377
|
-
"/integrations/crm/connections/{connection_id}/deals",
|
|
1378
|
-
"/integrations/crm/connections/{connection_id}/leads",
|
|
1379
|
-
"/integrations/crm/lists",
|
|
1380
|
-
"/integrations/crm/lists/memberships",
|
|
1381
|
-
"/integrations/crm/meetings",
|
|
1382
|
-
"/integrations/linkedin-outreach/campaigns/{campaign_id}/pause",
|
|
1383
|
-
"/integrations/linkedin-outreach/campaigns/{campaign_id}/resume",
|
|
1384
|
-
"/integrations/linkedin-outreach/connection/reconnect",
|
|
1385
|
-
"/integrations/linkedin-outreach/lists",
|
|
1386
|
-
"/integrations/linkedin-outreach/lists/{list_id}/leads",
|
|
1387
|
-
"/integrations/linkedin-outreach/workspaces/sync",
|
|
1388
|
-
"/integrations/migrations/delta-sync",
|
|
1389
|
-
"/integrations/migrations/discovery",
|
|
1390
|
-
"/integrations/migrations/{migration_id}/confirm",
|
|
1391
|
-
"/integrations/sugarcrm/connect",
|
|
1392
|
-
"/integrations/sugarcrm/reconnect",
|
|
1393
|
-
"/landing-pages/{landing_page_id}/publish",
|
|
1394
|
-
"/landing-pages/{landing_page_id}/unpublish",
|
|
1395
|
-
"/linkedin/accounts/sync",
|
|
1396
|
-
"/marketplace/agencies/{slug}/reps/{sdr_id}/publish",
|
|
1397
|
-
"/marketplace/me/messages/{conversation_id}/reply",
|
|
1398
|
-
"/marketplace/me/payout-account/bank-details",
|
|
1399
|
-
"/marketplace/me/payout-account/methods",
|
|
1400
|
-
"/marketplace/me/payout-account/setup",
|
|
1401
|
-
"/marketplace/me/sync-workbench",
|
|
1402
|
-
"/marketplace/talent/{sdr_id}/messages",
|
|
1403
|
-
"/marketplace/talent/{sdr_id}/request",
|
|
1404
|
-
"/newsletters",
|
|
1405
|
-
"/newsletters/{newsletter_id}/activate",
|
|
1406
|
-
"/newsletters/{newsletter_id}/issues",
|
|
1407
|
-
"/newsletters/{newsletter_id}/issues/{issue_id}/duplicate",
|
|
1408
|
-
"/newsletters/{newsletter_id}/issues/{issue_id}/send",
|
|
1409
|
-
"/newsletters/{newsletter_id}/issues/{issue_id}/send-test",
|
|
1410
|
-
"/newsletters/{newsletter_id}/issues/{issue_id}/send/resume",
|
|
1411
|
-
"/newsletters/{newsletter_id}/resume",
|
|
1412
|
-
"/newsletters/{newsletter_id}/settings/verify-domain",
|
|
1413
|
-
"/newsletters/{newsletter_id}/subscribers",
|
|
1414
|
-
"/nurtures/{nurture_id}/launch",
|
|
1415
|
-
"/org/sso/sync",
|
|
1416
|
-
"/phone-numbers/a2p/register",
|
|
1417
|
-
"/profile/deal-automations/sync-now",
|
|
1418
|
-
"/programmatic-campaigns/{campaign_id}/pages/actions",
|
|
1419
|
-
"/programmatic-campaigns/{campaign_id}/pages/{page_id}/actions",
|
|
1420
|
-
"/provider-domains/{domain_id}/forwarding",
|
|
1421
|
-
"/provider-domains/{domain_id}/mailbox-profile-picture",
|
|
1422
|
-
"/provider-domains/{domain_id}/regenerate-nameservers",
|
|
1423
|
-
"/provider-domains/{domain_id}/resend-transfer-password",
|
|
1424
|
-
"/provider-domains/{domain_id}/resend-verification",
|
|
1425
|
-
"/provider-domains/{domain_id}/set-nameservers",
|
|
1426
|
-
"/provider-usage/refresh",
|
|
1427
|
-
"/provider-usage/sync",
|
|
1428
|
-
"/quote-billing-contact",
|
|
1429
|
-
"/quote-contacts",
|
|
1430
|
-
"/quote-settings/documents",
|
|
1431
|
-
"/quote-templates",
|
|
1432
|
-
"/quotes",
|
|
1433
|
-
"/quotes/batch",
|
|
1434
|
-
"/quotes/{quote_id}/duplicate",
|
|
1435
|
-
"/quotes/{quote_id}/edit-as-draft",
|
|
1436
|
-
"/quotes/{quote_id}/mark-accepted",
|
|
1437
|
-
"/quotes/{quote_id}/send",
|
|
1438
|
-
"/quotes/{quote_id}/unarchive",
|
|
1439
|
-
"/radar/alerts/dispatch",
|
|
1440
|
-
"/repos/{repo_id}/campaigns/{campaign_id}/launch",
|
|
1441
|
-
"/roles/invite",
|
|
1442
|
-
"/sales-coach/interviews/{interview_id}/members",
|
|
1443
|
-
"/sales-coach/interviews/{interview_id}/members/{member_id}/send-invite",
|
|
1444
|
-
"/sequencer/content/voice/test-call",
|
|
1445
|
-
"/sequences",
|
|
1446
|
-
"/sequences/generate-spintax",
|
|
1447
|
-
"/sequences/{sequence_id}/contacts",
|
|
1448
|
-
"/sequences/{sequence_id}/contacts/{contact_id}/resume",
|
|
1449
|
-
"/sequences/{sequence_id}/pause",
|
|
1450
|
-
"/sequences/{sequence_id}/resume",
|
|
1451
|
-
"/sequences/{sequence_id}/run",
|
|
1452
|
-
"/sequences/{sequence_id}/sync",
|
|
1453
|
-
"/settings/scrape-config/crawl",
|
|
1454
|
-
"/skills/{action_id}/execute",
|
|
1455
|
-
"/skills/{action_id}/test-runs",
|
|
1456
|
-
"/social-listeners/posts/{post_id}/add-to-sequence",
|
|
1457
|
-
"/social/posts/{post_id}/engagers/add-to-audience",
|
|
1458
|
-
"/social/posts/{post_id}/engagers/enroll",
|
|
1459
|
-
"/social/posts/{post_id}/publish",
|
|
1460
|
-
"/social/posts/{post_id}/schedule",
|
|
1461
|
-
"/stripe/api-key",
|
|
1462
|
-
"/stripe/sync",
|
|
1463
|
-
"/team-members/sync",
|
|
1464
|
-
"/voice/calls",
|
|
1465
|
-
"/voice/dialer/sessions",
|
|
1466
|
-
"/voice/dialer/sessions/{session_id}/resume",
|
|
1467
|
-
"/voice/dispositions",
|
|
1468
|
-
"/voice/inbound-calls/decline",
|
|
1469
|
-
"/voice/inbound-calls/dialer/{call_id}/decline",
|
|
1470
|
-
"/voice/inbound-calls/{room_name}/claim",
|
|
1471
|
-
"/voice/mcp-servers/{mcp_server_id}/test",
|
|
1472
|
-
"/webhooks",
|
|
1473
|
-
"/webhooks/{webhook_id}/rotate-secret",
|
|
1474
|
-
"/website-editor/publish",
|
|
1475
|
-
"/website-editor/sync",
|
|
1476
|
-
"/workbench/scraper/providers/{provider}/connection/test",
|
|
1477
|
-
"/workbench/tables/{table_id}/import/crm",
|
|
1478
|
-
"/workbench/tables/{table_id}/send-to-sequence",
|
|
1479
|
-
"/workflows",
|
|
1480
|
-
"/workflows/executions/approve",
|
|
1481
|
-
"/workflows/executions/bulk-restart",
|
|
1482
|
-
"/workflows/executions/request-changes",
|
|
1483
|
-
"/workflows/executions/{execution_id}/pause",
|
|
1484
|
-
"/workflows/executions/{execution_id}/restart",
|
|
1485
|
-
"/workflows/executions/{execution_id}/resume",
|
|
1486
|
-
"/workflows/executions/{execution_id}/stop",
|
|
1487
|
-
"/workflows/integrations/{provider_name}/test",
|
|
1488
|
-
"/workflows/validate",
|
|
1489
|
-
"/workflows/workbenches",
|
|
1490
|
-
"/workflows/{action_id}/execute",
|
|
1491
|
-
"/workflows/{action_id}/trigger-reset"
|
|
1492
|
-
],
|
|
1493
|
-
PUT: [
|
|
1494
|
-
"/ad-campaigns/{campaign_id}",
|
|
1495
|
-
"/appointments/calendars/destination",
|
|
1496
|
-
"/appointments/conferencing/{provider}/link",
|
|
1497
|
-
"/campaigns/{campaign_id}/audience",
|
|
1498
|
-
"/campaigns/{campaign_id}/documents/{document_id}",
|
|
1499
|
-
"/campaigns/{campaign_id}/sequence",
|
|
1500
|
-
"/campaigns/{campaign_id}/sequence/steps/{step_id}",
|
|
1501
|
-
"/desk/autonomy",
|
|
1502
|
-
"/desk/org-settings",
|
|
1503
|
-
"/inbox/linkedin/accounts/{account_id}",
|
|
1504
|
-
"/integrations/crm-sync/pull-enabled",
|
|
1505
|
-
"/integrations/crm-sync/push-enabled",
|
|
1506
|
-
"/integrations/crm/connections/{connection_id}/batch/contacts",
|
|
1507
|
-
"/integrations/crm/connections/{connection_id}/companies/{company_id}",
|
|
1508
|
-
"/integrations/crm/connections/{connection_id}/contacts/{contact_id}",
|
|
1509
|
-
"/integrations/crm/connections/{connection_id}/deals/{deal_id}",
|
|
1510
|
-
"/integrations/crm/connections/{connection_id}/leads/{lead_id}",
|
|
1511
|
-
"/integrations/linkedin-outreach/workspaces/active",
|
|
1512
|
-
"/linkedin/accounts/{account_id}/schedule",
|
|
1513
|
-
"/newsletters/{newsletter_id}/issues/{issue_id}",
|
|
1514
|
-
"/quote-settings",
|
|
1515
|
-
"/quote-templates/{template_id}",
|
|
1516
|
-
"/quotes/{quote_id}",
|
|
1517
|
-
"/sms/phone-numbers/{phone_number}",
|
|
1518
|
-
"/social/autopilot/{account_id}",
|
|
1519
|
-
"/workflows/{action_id}"
|
|
1520
|
-
]
|
|
1521
|
-
};
|
|
1522
|
-
|
|
1523
|
-
// src/http.ts
|
|
1524
|
-
var G8Error = class _G8Error extends Error {
|
|
1525
|
-
constructor(args) {
|
|
1526
|
-
super(args.message);
|
|
1527
|
-
this.name = "G8Error";
|
|
1528
|
-
this.status = args.status;
|
|
1529
|
-
this.type = args.type;
|
|
1530
|
-
this.code = args.code;
|
|
1531
|
-
this.requestId = args.requestId;
|
|
1532
|
-
this.detail = args.detail;
|
|
1533
|
-
this.retryable = args.retryable ?? false;
|
|
1534
|
-
Object.setPrototypeOf(this, _G8Error.prototype);
|
|
1535
|
-
}
|
|
1536
|
-
};
|
|
1537
|
-
function isRetryableStatus(status) {
|
|
1538
|
-
return status === 429 || status >= 500 && status <= 599;
|
|
1539
|
-
}
|
|
1540
|
-
function isWriteMethod(method) {
|
|
1541
|
-
const m = method.toUpperCase();
|
|
1542
|
-
return m === "POST" || m === "PUT" || m === "PATCH" || m === "DELETE";
|
|
1543
|
-
}
|
|
1544
|
-
var API_BASE_SEGMENT = "/api/v1";
|
|
1545
|
-
var billableMatchers = null;
|
|
1546
|
-
var externalMatchers = null;
|
|
1547
|
-
function templateToRegexSource(template) {
|
|
1548
|
-
return template.split(/(\{[^}/]+\})/).map((part) => /^\{[^}/]+\}$/.test(part) ? "[^/]+" : part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
|
|
1549
|
-
}
|
|
1550
|
-
function buildMatchers(routes) {
|
|
1551
|
-
const out = /* @__PURE__ */ new Map();
|
|
1552
|
-
for (const [m, templates] of Object.entries(routes)) {
|
|
1553
|
-
if (!templates.length) continue;
|
|
1554
|
-
out.set(m, new RegExp(`^(?:${templates.map(templateToRegexSource).join("|")})/?$`));
|
|
1555
|
-
}
|
|
1556
|
-
return out;
|
|
1557
|
-
}
|
|
1558
|
-
function matcherFor(method) {
|
|
1559
|
-
if (!billableMatchers) billableMatchers = buildMatchers(BILLABLE_ROUTES);
|
|
1560
|
-
return billableMatchers.get(method.toUpperCase());
|
|
1561
|
-
}
|
|
1562
|
-
function externalMatcherFor(method) {
|
|
1563
|
-
if (!externalMatchers) externalMatchers = buildMatchers(EXTERNAL_ROUTES);
|
|
1564
|
-
return externalMatchers.get(method.toUpperCase());
|
|
1565
|
-
}
|
|
1566
|
-
function apiRelativePath(urlOrPath) {
|
|
1567
|
-
let p = urlOrPath.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/]*/, "");
|
|
1568
|
-
p = p.split(/[?#]/, 1)[0];
|
|
1569
|
-
const at = p.indexOf(API_BASE_SEGMENT + "/");
|
|
1570
|
-
if (at < 0) return null;
|
|
1571
|
-
return p.slice(at + API_BASE_SEGMENT.length);
|
|
1572
|
-
}
|
|
1573
|
-
function isBillableOperation(method, urlOrPath) {
|
|
1574
|
-
const matcher = matcherFor(method);
|
|
1575
|
-
if (!matcher) return false;
|
|
1576
|
-
const p = apiRelativePath(urlOrPath);
|
|
1577
|
-
return p != null && matcher.test(p);
|
|
1578
|
-
}
|
|
1579
|
-
function isExternalOperation(method, urlOrPath) {
|
|
1580
|
-
const matcher = externalMatcherFor(method);
|
|
1581
|
-
if (!matcher) return false;
|
|
1582
|
-
const p = apiRelativePath(urlOrPath);
|
|
1583
|
-
return p != null && matcher.test(p);
|
|
1584
|
-
}
|
|
1585
|
-
function isNoBlindRetryOperation(method, urlOrPath) {
|
|
1586
|
-
return isBillableOperation(method, urlOrPath) || isExternalOperation(method, urlOrPath);
|
|
1587
|
-
}
|
|
1588
|
-
var PRE_SEND_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1589
|
-
"ECONNREFUSED",
|
|
1590
|
-
"ENOTFOUND",
|
|
1591
|
-
"EAI_AGAIN",
|
|
1592
|
-
"ENETUNREACH",
|
|
1593
|
-
"EHOSTUNREACH",
|
|
1594
|
-
"UND_ERR_CONNECT_TIMEOUT"
|
|
1595
|
-
]);
|
|
1596
|
-
function isPreSendNetworkError(err) {
|
|
1597
|
-
let cur = err;
|
|
1598
|
-
for (let depth = 0; cur && depth < 4; depth++) {
|
|
1599
|
-
if (typeof cur.code === "string" && PRE_SEND_ERROR_CODES.has(cur.code)) return true;
|
|
1600
|
-
if (Array.isArray(cur.errors) && cur.errors.some((e) => isPreSendNetworkError(e))) return true;
|
|
1601
|
-
cur = cur.cause;
|
|
1602
|
-
}
|
|
1603
|
-
return false;
|
|
1604
|
-
}
|
|
1605
|
-
function newIdempotencyKey() {
|
|
1606
|
-
const c = globalThis.crypto;
|
|
1607
|
-
if (c?.randomUUID) return c.randomUUID();
|
|
1608
|
-
if (c?.getRandomValues) {
|
|
1609
|
-
const bytes = c.getRandomValues(new Uint8Array(16));
|
|
1610
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1611
|
-
}
|
|
1612
|
-
return `g8-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
1613
|
-
}
|
|
1614
|
-
function parseRetryAfter(header, nowMs = Date.now()) {
|
|
1615
|
-
if (!header) return null;
|
|
1616
|
-
const secs = Number(header);
|
|
1617
|
-
if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1e3);
|
|
1618
|
-
const dateMs = Date.parse(header);
|
|
1619
|
-
if (!Number.isNaN(dateMs)) {
|
|
1620
|
-
const delta = dateMs - nowMs;
|
|
1621
|
-
return delta > 0 ? delta : 0;
|
|
1622
|
-
}
|
|
1623
|
-
return null;
|
|
1624
|
-
}
|
|
1625
|
-
function backoffDelayMs(attempt, baseMs = 200, rand = Math.random) {
|
|
1626
|
-
const expo = Math.min(baseMs * 2 ** attempt, 1e4);
|
|
1627
|
-
return Math.floor(expo / 2 + rand() * (expo / 2));
|
|
1628
|
-
}
|
|
1629
|
-
function defaultSleep(ms) {
|
|
1630
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1631
|
-
}
|
|
1632
|
-
function buildQuery(query) {
|
|
1633
|
-
if (!query) return "";
|
|
1634
|
-
const qs = new URLSearchParams();
|
|
1635
|
-
for (const [k, v] of Object.entries(query)) {
|
|
1636
|
-
if (v != null) qs.set(k, String(v));
|
|
1637
|
-
}
|
|
1638
|
-
const s = qs.toString();
|
|
1639
|
-
return s ? `?${s}` : "";
|
|
1640
|
-
}
|
|
1641
|
-
async function toG8Error(resp, retryable = isRetryableStatus(resp.status)) {
|
|
1642
|
-
let body = void 0;
|
|
1643
|
-
try {
|
|
1644
|
-
const text = await resp.text();
|
|
1645
|
-
body = text ? JSON.parse(text) : void 0;
|
|
1646
|
-
} catch {
|
|
1647
|
-
body = void 0;
|
|
1648
|
-
}
|
|
1649
|
-
const env = body ?? {};
|
|
1650
|
-
const message = typeof env.message === "string" && env.message || typeof env.error === "string" && env.error || `Request failed with status ${resp.status}`;
|
|
1651
|
-
return new G8Error({
|
|
1652
|
-
message,
|
|
1653
|
-
status: resp.status,
|
|
1654
|
-
type: typeof env.type === "string" && env.type || typeof env.error === "string" && env.error || "api_error",
|
|
1655
|
-
code: typeof env.code === "string" ? env.code : String(resp.status),
|
|
1656
|
-
requestId: typeof env.request_id === "string" && env.request_id || resp.headers.get("x-request-id") || void 0,
|
|
1657
|
-
detail: env.detail,
|
|
1658
|
-
retryable
|
|
1659
|
-
});
|
|
1660
|
-
}
|
|
1661
|
-
var TARGET_ORG_HEADER = "x-target-org-id";
|
|
1662
|
-
var nonAgencyKeys = /* @__PURE__ */ new Set();
|
|
1663
|
-
var warnedTargetOrgKeys = /* @__PURE__ */ new Set();
|
|
1664
|
-
function keyFingerprint(apiKey) {
|
|
1665
|
-
let h = 2166136261;
|
|
1666
|
-
for (let i = 0; i < apiKey.length; i++) {
|
|
1667
|
-
h ^= apiKey.charCodeAt(i);
|
|
1668
|
-
h = Math.imul(h, 16777619) >>> 0;
|
|
1669
|
-
}
|
|
1670
|
-
return `${apiKey.length}:${h.toString(36)}`;
|
|
1671
|
-
}
|
|
1672
|
-
function warnIfTargetOrgIgnored(apiKey, headers) {
|
|
1673
|
-
if (!Object.keys(headers).some((h) => h.toLowerCase() === TARGET_ORG_HEADER)) return;
|
|
1674
|
-
const fp = keyFingerprint(apiKey);
|
|
1675
|
-
if (!nonAgencyKeys.has(fp) || warnedTargetOrgKeys.has(fp)) return;
|
|
1676
|
-
warnedTargetOrgKeys.add(fp);
|
|
1677
|
-
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
1678
|
-
console.warn(
|
|
1679
|
-
"[@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."
|
|
1680
|
-
);
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
async function request(baseUrl, path6, apiKey, opts = {}) {
|
|
1684
|
-
const {
|
|
1685
|
-
method = "GET",
|
|
1686
|
-
body,
|
|
1687
|
-
headers = {},
|
|
1688
|
-
query,
|
|
1689
|
-
idempotencyKey,
|
|
1690
|
-
maxRetries = 2,
|
|
1691
|
-
retryBaseMs = 200,
|
|
1692
|
-
signal,
|
|
1693
|
-
fetchImpl = fetch,
|
|
1694
|
-
sleepImpl = defaultSleep
|
|
1695
|
-
} = opts;
|
|
1696
|
-
const url = baseUrl.replace(/\/+$/, "") + path6 + buildQuery(query);
|
|
1697
|
-
const finalHeaders = {
|
|
1698
|
-
"Content-Type": "application/json",
|
|
1699
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1700
|
-
...clientIdentityHeaders(headers),
|
|
1701
|
-
...headers
|
|
1702
|
-
};
|
|
1703
|
-
warnIfTargetOrgIgnored(apiKey, headers);
|
|
1704
|
-
const effectiveIdempotencyKey = idempotencyKey ?? (maxRetries > 0 && isWriteMethod(method) ? newIdempotencyKey() : void 0);
|
|
1705
|
-
if (effectiveIdempotencyKey) finalHeaders["Idempotency-Key"] = effectiveIdempotencyKey;
|
|
1706
|
-
const billable = isNoBlindRetryOperation(method, url);
|
|
1707
|
-
let attempt = 0;
|
|
1708
|
-
for (; ; ) {
|
|
1709
|
-
let resp;
|
|
1710
|
-
try {
|
|
1711
|
-
resp = await fetchImpl(url, {
|
|
1712
|
-
method,
|
|
1713
|
-
headers: finalHeaders,
|
|
1714
|
-
body: body == null ? void 0 : JSON.stringify(body),
|
|
1715
|
-
signal
|
|
1716
|
-
});
|
|
1717
|
-
} catch (err) {
|
|
1718
|
-
const safeToRetry = !billable || isPreSendNetworkError(err);
|
|
1719
|
-
if (attempt < maxRetries && safeToRetry) {
|
|
1720
|
-
await sleepImpl(backoffDelayMs(attempt, retryBaseMs));
|
|
1721
|
-
attempt++;
|
|
1722
|
-
continue;
|
|
1723
|
-
}
|
|
1724
|
-
throw new G8Error({
|
|
1725
|
-
message: `Network error: ${err?.message ?? "request failed"}`,
|
|
1726
|
-
status: 0,
|
|
1727
|
-
type: "network_error",
|
|
1728
|
-
retryable: safeToRetry
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
|
-
if (resp.ok) {
|
|
1732
|
-
const text = await resp.text();
|
|
1733
|
-
return text ? JSON.parse(text) : void 0;
|
|
1734
|
-
}
|
|
1735
|
-
const statusRetryable = isRetryableStatus(resp.status) && (!billable || resp.status === 429);
|
|
1736
|
-
if (statusRetryable && attempt < maxRetries) {
|
|
1737
|
-
const retryAfter = parseRetryAfter(resp.headers.get("retry-after"));
|
|
1738
|
-
const delay = retryAfter != null ? retryAfter : backoffDelayMs(attempt, retryBaseMs);
|
|
1739
|
-
await sleepImpl(delay);
|
|
1740
|
-
attempt++;
|
|
1741
|
-
continue;
|
|
1742
|
-
}
|
|
1743
|
-
throw await toG8Error(resp, billable ? statusRetryable : isRetryableStatus(resp.status));
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
function isServerRuntime() {
|
|
1747
|
-
const g = globalThis;
|
|
1748
|
-
if (typeof g.window !== "undefined") return false;
|
|
1749
|
-
return typeof g.process?.versions?.node === "string";
|
|
1750
|
-
}
|
|
1751
|
-
function runtimeUserAgent() {
|
|
1752
|
-
const g = globalThis;
|
|
1753
|
-
const node = typeof g.process?.version === "string" ? ` node/${g.process.version.replace(/^v/, "")}` : "";
|
|
1754
|
-
return `graph8-sdk-js/${SDK_VERSION}${node}`;
|
|
1755
|
-
}
|
|
1756
|
-
function clientIdentityHeaders(callerHeaders = {}) {
|
|
1757
|
-
const taken = new Set(Object.keys(callerHeaders).map((h) => h.toLowerCase()));
|
|
1758
|
-
const out = {};
|
|
1759
|
-
if (!taken.has("x-g8-client")) out["X-G8-Client"] = SDK_CLIENT_ID;
|
|
1760
|
-
if (!taken.has("user-agent") && isServerRuntime()) out["User-Agent"] = runtimeUserAgent();
|
|
1761
|
-
return out;
|
|
1762
|
-
}
|
|
1763
|
-
async function* paginate(fetchPage) {
|
|
1764
|
-
let cursor = void 0;
|
|
1765
|
-
for (; ; ) {
|
|
1766
|
-
const page = await fetchPage(cursor);
|
|
1767
|
-
for (const item of page.data ?? []) yield item;
|
|
1768
|
-
const next = page.pagination?.next_cursor;
|
|
1769
|
-
if (!next) break;
|
|
1770
|
-
cursor = next;
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
172
|
|
|
1774
173
|
// src/generated/operations.ts
|
|
1775
174
|
var API_OPERATIONS = {
|
|
@@ -2113,6 +512,7 @@ var API_OPERATIONS = {
|
|
|
2113
512
|
create_deal_deals_post: ["POST", "/deals", "deals", "createDeal", "write", "deals:write"],
|
|
2114
513
|
create_deal_note_deals__deal_id__notes_post: ["POST", "/deals/{deal_id}/notes", "notes", "createDealNote", "write", "notes:write"],
|
|
2115
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"],
|
|
2116
516
|
create_dialer_session_voice_dialer_sessions_post: ["POST", "/voice/dialer/sessions", "voice", "createDialerSession", "external", "voice:run"],
|
|
2117
517
|
create_dynamic_column_enrichment_columns_post: ["POST", "/enrichment/columns", "enrichment", "createDynamicColumn", "write", "enrichment:write"],
|
|
2118
518
|
create_empty_list_lists_create_empty_post: ["POST", "/lists/create-empty", "lists", "createEmptyList", "write", "lists:write"],
|
|
@@ -5089,7 +3489,2542 @@ var API_OPERATIONS = {
|
|
|
5089
3489
|
withdraw_contacts_from_sequences_contacts_withdraw_from_sequences_post: ["POST", "/contacts/withdraw-from-sequences", "contacts", "withdrawContactsFromSequences", "write", "contacts:write"],
|
|
5090
3490
|
withdraw_org_hiring_marketplace_org_hirings__hiring_id__withdraw_post: ["POST", "/marketplace/org/hirings/{hiring_id}/withdraw", "marketplace", "withdrawOrgHiring", "billable", "marketplace:run"]
|
|
5091
3491
|
};
|
|
5092
|
-
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
|
+
}
|
|
5093
6028
|
|
|
5094
6029
|
// src/api.ts
|
|
5095
6030
|
var DEFAULT_API = "https://be.graph8.com";
|
|
@@ -5173,15 +6108,95 @@ function createApiClient(apiKey, apiUrl) {
|
|
|
5173
6108
|
return client;
|
|
5174
6109
|
}
|
|
5175
6110
|
|
|
6111
|
+
// src/blocks/flows.ts
|
|
6112
|
+
var import_node_fs7 = __toESM(require("fs"));
|
|
6113
|
+
var import_node_path8 = __toESM(require("path"));
|
|
6114
|
+
var IMPORT_RE2 = /(?:^|\n)\s*(?:import|export)\s[^'"`;]*?from\s*["']([^"']+)["']|(?:^|\n)\s*import\s*["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g;
|
|
6115
|
+
var EXTENSIONS = ["", ".tsx", ".ts", "/index.tsx", "/index.ts"];
|
|
6116
|
+
function specifiers(source) {
|
|
6117
|
+
return [...source.matchAll(IMPORT_RE2)].map((m) => m[1] ?? m[2] ?? m[3]);
|
|
6118
|
+
}
|
|
6119
|
+
function resolveFile(base) {
|
|
6120
|
+
for (const ext of EXTENSIONS) {
|
|
6121
|
+
const candidate = base + ext;
|
|
6122
|
+
if (import_node_fs7.default.existsSync(candidate) && import_node_fs7.default.statSync(candidate).isFile()) return candidate;
|
|
6123
|
+
}
|
|
6124
|
+
return null;
|
|
6125
|
+
}
|
|
6126
|
+
function resolveImport(spec, from, reg) {
|
|
6127
|
+
if (spec.startsWith(".")) return resolveFile(import_node_path8.default.resolve(import_node_path8.default.dirname(from), spec));
|
|
6128
|
+
const g8 = /^@\/g8\/([^/]+)\/(.+)$/.exec(spec);
|
|
6129
|
+
if (!g8) return null;
|
|
6130
|
+
const owner = findBlock(reg, g8[1]);
|
|
6131
|
+
if (owner) return resolveFile(import_node_path8.default.join(owner.root, "components", g8[2])) ?? resolveFile(import_node_path8.default.join(owner.root, g8[2]));
|
|
6132
|
+
const foundation = findBlock(reg, "foundation");
|
|
6133
|
+
return foundation ? resolveFile(import_node_path8.default.join(foundation.root, g8[1], g8[2])) : null;
|
|
6134
|
+
}
|
|
6135
|
+
function resolveInstalled(spec, reg) {
|
|
6136
|
+
const m = /^@\/components\/g8\/([^/]+)\/(.+)$/.exec(spec);
|
|
6137
|
+
const block = m && reg.blocks.find((b) => b.dir === m[1]);
|
|
6138
|
+
if (!m || !block) return null;
|
|
6139
|
+
const files = [...block.components, ...block.hooks, ...block.lib].map((x) => x.file);
|
|
6140
|
+
const hit = files.find((f) => (installedRel(f) ?? f).replace(/\.(tsx|ts|jsx|js)$/, "") === m[2]);
|
|
6141
|
+
return hit ? import_node_path8.default.join(block.root, hit) : null;
|
|
6142
|
+
}
|
|
6143
|
+
function seeds(reg, stack) {
|
|
6144
|
+
const out = [];
|
|
6145
|
+
for (const n of stack.nav) {
|
|
6146
|
+
const comp = findBlock(reg, n.block)?.components.find((c) => c.name === n.component);
|
|
6147
|
+
const block = findBlock(reg, n.block);
|
|
6148
|
+
if (!block || !comp) continue;
|
|
6149
|
+
const pageFile = n.page && reg.stacksRoot ? import_node_path8.default.join(reg.stacksRoot, stack.id, n.page) : null;
|
|
6150
|
+
if (!pageFile || !import_node_fs7.default.existsSync(pageFile)) {
|
|
6151
|
+
out.push(import_node_path8.default.join(block.root, comp.file));
|
|
6152
|
+
continue;
|
|
6153
|
+
}
|
|
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));
|
|
6156
|
+
for (const spec of specifiers(source)) {
|
|
6157
|
+
const hit = resolveInstalled(spec, reg);
|
|
6158
|
+
if (hit) out.push(hit);
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
return out;
|
|
6162
|
+
}
|
|
6163
|
+
function reachedFiles(reg, stack) {
|
|
6164
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6165
|
+
const queue = seeds(reg, stack);
|
|
6166
|
+
while (queue.length) {
|
|
6167
|
+
const file = queue.pop();
|
|
6168
|
+
if (seen.has(file)) continue;
|
|
6169
|
+
seen.add(file);
|
|
6170
|
+
if (!/\.(tsx?|jsx?)$/.test(file)) continue;
|
|
6171
|
+
for (const spec of specifiers(import_node_fs7.default.readFileSync(file, "utf8"))) {
|
|
6172
|
+
const next = resolveImport(spec, file, reg);
|
|
6173
|
+
if (next && !seen.has(next)) queue.push(next);
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
return seen;
|
|
6177
|
+
}
|
|
6178
|
+
function unreachedComponents(reg, stack) {
|
|
6179
|
+
const reached = reachedFiles(reg, stack);
|
|
6180
|
+
const blocks = resolveWithDeps(reg, stack.blocks).filter((b) => b.id !== "foundation" && stack.blocks.includes(b.id));
|
|
6181
|
+
return blocks.flatMap((b) => b.components.filter((c) => !reached.has(import_node_path8.default.join(b.root, c.file))).map((c) => ({ block: b.id, component: c.name })));
|
|
6182
|
+
}
|
|
6183
|
+
|
|
5176
6184
|
// src/blocks/doctor.ts
|
|
5177
|
-
var DOCTOR_USAGE = `Usage: npx @graph8/sdk doctor [--stack <id>] [--blocks a,b] [--dir <path>]
|
|
6185
|
+
var DOCTOR_USAGE = `Usage: npx @graph8/sdk doctor [--stack <id>] [--blocks a,b] [--dir <path>] [--strict] [--production] [--fix-prefixes]
|
|
5178
6186
|
|
|
5179
6187
|
Check that this project is ready for graph8 blocks:
|
|
5180
6188
|
- G8_API_KEY (env or .env.local) is valid
|
|
5181
6189
|
- the key's scopes cover the blocks you chose
|
|
5182
6190
|
- the organization's subscription is active
|
|
5183
6191
|
- a checklist of in-app setup each block needs
|
|
6192
|
+
With --stack it also lists "Flows": block components that no page of the stack reaches
|
|
6193
|
+
(a warning; --strict makes them fail the run).
|
|
5184
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.
|
|
5185
6200
|
Optional G8_API_URL points at a non-default API. Exit code 0 when all checks
|
|
5186
6201
|
pass, 1 otherwise.`;
|
|
5187
6202
|
function parseDoctorArgs(argv) {
|
|
@@ -5200,6 +6215,9 @@ ${DOCTOR_USAGE}`);
|
|
|
5200
6215
|
if (flag === "--stack") o.stack = value();
|
|
5201
6216
|
else if (flag === "--blocks") o.blocks.push(...value().split(",").map((s) => s.trim()).filter(Boolean));
|
|
5202
6217
|
else if (flag === "--dir") o.dir = value();
|
|
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;
|
|
5203
6221
|
else throw new Error(`unknown option ${arg}
|
|
5204
6222
|
|
|
5205
6223
|
${DOCTOR_USAGE}`);
|
|
@@ -5218,9 +6236,9 @@ function parseEnvFile(text) {
|
|
|
5218
6236
|
function readSetting(name, root, env) {
|
|
5219
6237
|
if (env[name]) return env[name];
|
|
5220
6238
|
for (const f of [".env.local"]) {
|
|
5221
|
-
const file =
|
|
5222
|
-
if (
|
|
5223
|
-
const v = parseEnvFile(
|
|
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];
|
|
5224
6242
|
if (v) return v;
|
|
5225
6243
|
}
|
|
5226
6244
|
}
|
|
@@ -5241,7 +6259,7 @@ function selectBlocks(opts, reg, root) {
|
|
|
5241
6259
|
}
|
|
5242
6260
|
if (!ids.length) {
|
|
5243
6261
|
try {
|
|
5244
|
-
const ledger = JSON.parse(
|
|
6262
|
+
const ledger = JSON.parse(import_node_fs8.default.readFileSync(import_node_path9.default.join(root, "graph8.json"), "utf8"));
|
|
5245
6263
|
ids = Object.keys(ledger.blocks ?? {});
|
|
5246
6264
|
} catch {
|
|
5247
6265
|
ids = [];
|
|
@@ -5255,7 +6273,7 @@ function errorText(e, key) {
|
|
|
5255
6273
|
return `${status ? `HTTP ${status}: ` : ""}${msg}`.split(key).join("[redacted]");
|
|
5256
6274
|
}
|
|
5257
6275
|
async function runDoctor(opts, reg, io, cwd) {
|
|
5258
|
-
const root =
|
|
6276
|
+
const root = import_node_path9.default.resolve(cwd, opts.dir);
|
|
5259
6277
|
let blocks;
|
|
5260
6278
|
try {
|
|
5261
6279
|
blocks = selectBlocks(opts, reg, root);
|
|
@@ -5263,15 +6281,20 @@ async function runDoctor(opts, reg, io, cwd) {
|
|
|
5263
6281
|
io.err(e.message);
|
|
5264
6282
|
return 1;
|
|
5265
6283
|
}
|
|
6284
|
+
if (opts.fixPrefixes) return fixPrefixes(root, blocks, io);
|
|
5266
6285
|
let failed = false;
|
|
5267
6286
|
const line = (ok, text) => {
|
|
5268
6287
|
if (ok === false) failed = true;
|
|
5269
6288
|
io.out(`${ok === null ? "info" : ok ? "ok " : "FAIL"} ${text}`);
|
|
5270
6289
|
};
|
|
5271
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.");
|
|
5272
|
-
|
|
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
|
+
}
|
|
5273
6296
|
if (!key) {
|
|
5274
|
-
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)");
|
|
5275
6298
|
} else {
|
|
5276
6299
|
const api = createApiClient(key, readSetting("G8_API_URL", root, io.env));
|
|
5277
6300
|
const call = (op) => api.call(op, void 0, {
|
|
@@ -5281,6 +6304,9 @@ async function runDoctor(opts, reg, io, cwd) {
|
|
|
5281
6304
|
await checkKey(call, blocks, key, line);
|
|
5282
6305
|
await checkSubscription(call, key, line);
|
|
5283
6306
|
}
|
|
6307
|
+
checkRoute(root, blocks, opts.production === true, io.out, line);
|
|
6308
|
+
if (opts.stack) printFlows(opts, reg, io.out, line);
|
|
6309
|
+
else if (!opts.blocks.length) printAppScreens(root, reg, io.out);
|
|
5284
6310
|
printChecklist(blocks, io.out);
|
|
5285
6311
|
io.out(failed ? "\nSome checks failed." : "\nAll checks passed.");
|
|
5286
6312
|
return failed ? 1 : 0;
|
|
@@ -5318,6 +6344,33 @@ async function checkSubscription(call, key, line) {
|
|
|
5318
6344
|
else line(false, `could not read subscription status (${errorText(e, key)})`);
|
|
5319
6345
|
}
|
|
5320
6346
|
}
|
|
6347
|
+
function printFlows(opts, reg, out, line) {
|
|
6348
|
+
const stack = reg.stacks.find((s) => s.id === opts.stack);
|
|
6349
|
+
if (!stack) return;
|
|
6350
|
+
const missing = unreachedComponents(reg, stack);
|
|
6351
|
+
out("\nFlows (block components a page of this stack reaches):");
|
|
6352
|
+
if (!missing.length) return line(true, "every component of the stack's blocks is reachable from a page");
|
|
6353
|
+
for (const m of missing) {
|
|
6354
|
+
const text = `(${m.block}) ${m.component} is not reached by any page of "${stack.id}": wire it into a page or a Screen slot`;
|
|
6355
|
+
if (opts.strict) line(false, text);
|
|
6356
|
+
else out(`warn ${text}`);
|
|
6357
|
+
}
|
|
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
|
+
}
|
|
5321
6374
|
function printChecklist(blocks, out) {
|
|
5322
6375
|
const items = blocks.flatMap((b) => b.prerequisites.filter((p) => p.where === "app").map((p) => ({ block: b.id, p })));
|
|
5323
6376
|
if (!items.length) return;
|
|
@@ -5327,6 +6380,73 @@ function printChecklist(blocks, out) {
|
|
|
5327
6380
|
if (p.verify) out(` verify: ${p.verify}`);
|
|
5328
6381
|
}
|
|
5329
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
|
+
}
|
|
5330
6450
|
|
|
5331
6451
|
// src/cli.ts
|
|
5332
6452
|
var LIST_USAGE = `Usage: npx @graph8/sdk list [--json]
|
|
@@ -5342,14 +6462,16 @@ Commands:
|
|
|
5342
6462
|
list list the blocks and stacks this package ships
|
|
5343
6463
|
search find a block by what it does
|
|
5344
6464
|
add copy a block (components, hooks, recipes) into your app
|
|
5345
|
-
create scaffold a Next.js 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)
|
|
5346
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
|
|
5347
6469
|
|
|
5348
6470
|
Run a command with --help for its options.
|
|
5349
6471
|
|
|
5350
6472
|
${INIT_AGENT_USAGE}`;
|
|
5351
6473
|
function registryFor(io) {
|
|
5352
|
-
const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ??
|
|
6474
|
+
const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path10.default.resolve(__dirname, "..", "blocks");
|
|
5353
6475
|
return loadRegistry(root);
|
|
5354
6476
|
}
|
|
5355
6477
|
function defaultSpawn(cmd, args, cwd) {
|
|
@@ -5362,6 +6484,7 @@ function printCatalog(blocks, stacks, io, empty) {
|
|
|
5362
6484
|
if (blocks.length) io.out("Blocks");
|
|
5363
6485
|
for (const b of blocks) {
|
|
5364
6486
|
io.out(` ${b.id} ${b.summary}`);
|
|
6487
|
+
if (b.screens?.length) io.out(` screens (start here): ${b.screens.join(", ")}`);
|
|
5365
6488
|
if (b.dependsOn.length) io.out(` depends on: ${b.dependsOn.join(", ")}`);
|
|
5366
6489
|
}
|
|
5367
6490
|
if (stacks.length) io.out(`${blocks.length ? "\n" : ""}Stacks`);
|
|
@@ -5371,6 +6494,7 @@ function printCatalog(blocks, stacks, io, empty) {
|
|
|
5371
6494
|
if (s.missingBlocks?.length) io.out(` planned (cannot be created yet), missing blocks: ${s.missingBlocks.join(", ")}`);
|
|
5372
6495
|
}
|
|
5373
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)";
|
|
5374
6498
|
function wantsHelp(rest) {
|
|
5375
6499
|
return rest.includes("--help") || rest.includes("-h");
|
|
5376
6500
|
}
|
|
@@ -5388,38 +6512,46 @@ ${LIST_USAGE}`);
|
|
|
5388
6512
|
const blocks = reg.blocks.map(blockSummary);
|
|
5389
6513
|
const stacks = reg.stacks.map((s) => stackSummary(s, reg));
|
|
5390
6514
|
if (rest.includes("--json")) io.out(JSON.stringify({ blocks, stacks }, null, 2));
|
|
5391
|
-
else
|
|
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
|
+
}
|
|
5392
6520
|
return 0;
|
|
5393
6521
|
}
|
|
5394
6522
|
function runSearch(rest, io) {
|
|
5395
6523
|
if (wantsHelp(rest)) return io.out(SEARCH_USAGE), 0;
|
|
5396
|
-
const
|
|
5397
|
-
const bad =
|
|
6524
|
+
const words2 = rest.filter((a) => a !== "--json");
|
|
6525
|
+
const bad = words2.find((a) => a.startsWith("-"));
|
|
5398
6526
|
if (bad) return fail(io, `unknown option ${bad}
|
|
5399
6527
|
|
|
5400
6528
|
${SEARCH_USAGE}`);
|
|
5401
|
-
if (!
|
|
6529
|
+
if (!words2.length) return fail(io, `search needs some text
|
|
5402
6530
|
|
|
5403
6531
|
${SEARCH_USAGE}`);
|
|
5404
6532
|
const reg = registryFor(io);
|
|
5405
|
-
const text =
|
|
6533
|
+
const text = words2.join(" ");
|
|
5406
6534
|
const blocks = searchBlocks(reg, text).map(blockSummary);
|
|
5407
6535
|
const stacks = searchStacks(reg, text).map((s) => stackSummary(s, reg));
|
|
5408
6536
|
if (rest.includes("--json")) io.out(JSON.stringify({ query: text, blocks, stacks }, null, 2));
|
|
5409
|
-
else
|
|
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
|
+
}
|
|
5410
6542
|
return 0;
|
|
5411
6543
|
}
|
|
5412
6544
|
function runCreateCommand(rest, io) {
|
|
5413
6545
|
if (wantsHelp(rest)) return io.out(CREATE_USAGE), 0;
|
|
5414
6546
|
try {
|
|
5415
6547
|
const opts = parseCreateArgs(rest);
|
|
5416
|
-
const packageRoot =
|
|
6548
|
+
const packageRoot = import_node_path10.default.resolve(__dirname, "..");
|
|
5417
6549
|
const cio = {
|
|
5418
6550
|
out: io.out,
|
|
5419
6551
|
err: io.err,
|
|
5420
6552
|
spawn: io.spawn ?? defaultSpawn,
|
|
5421
|
-
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ??
|
|
5422
|
-
sdkPackageJson:
|
|
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"),
|
|
5423
6555
|
userAgent: io.userAgent ?? process.env.npm_config_user_agent
|
|
5424
6556
|
};
|
|
5425
6557
|
return runCreate(opts, registryFor(io), cio, io.cwd);
|
|
@@ -5431,7 +6563,15 @@ function runAddCommand(rest, io) {
|
|
|
5431
6563
|
if (wantsHelp(rest)) return io.out(ADD_USAGE), 0;
|
|
5432
6564
|
try {
|
|
5433
6565
|
const opts = parseAddArgs(rest);
|
|
5434
|
-
|
|
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);
|
|
5435
6575
|
} catch (e) {
|
|
5436
6576
|
return fail(io, e.message);
|
|
5437
6577
|
}
|
|
@@ -5446,6 +6586,14 @@ function runCli(argv, io) {
|
|
|
5446
6586
|
if (command === "search") return runSearch(rest, io);
|
|
5447
6587
|
if (command === "add") return runAddCommand(rest, io);
|
|
5448
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
|
+
}
|
|
5449
6597
|
if (command === "doctor") return fail(io, "doctor makes network calls; run it through runCliAsync (the graph8-sdk bin does).");
|
|
5450
6598
|
if (command !== "init-agent") {
|
|
5451
6599
|
io.err(`unknown command ${command}
|
|
@@ -5465,8 +6613,8 @@ ${USAGE}`);
|
|
|
5465
6613
|
return 1;
|
|
5466
6614
|
}
|
|
5467
6615
|
const read = (rel) => {
|
|
5468
|
-
const full =
|
|
5469
|
-
return
|
|
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;
|
|
5470
6618
|
};
|
|
5471
6619
|
let plans;
|
|
5472
6620
|
try {
|
|
@@ -5477,9 +6625,9 @@ ${USAGE}`);
|
|
|
5477
6625
|
}
|
|
5478
6626
|
for (const plan of plans) {
|
|
5479
6627
|
if (!options.dryRun && plan.action !== "unchanged") {
|
|
5480
|
-
const full =
|
|
5481
|
-
|
|
5482
|
-
|
|
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");
|
|
5483
6631
|
}
|
|
5484
6632
|
io.out(`${options.dryRun ? "would " : ""}${plan.action === "unchanged" ? "keep" : plan.action} ${plan.path} (${plan.target})`);
|
|
5485
6633
|
}
|