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