@growthub/cli 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/assets/worker-kits/growthub-agency-portal-starter-v1/apps/agency-portal/growthub.config.json +112 -0
  2. package/assets/worker-kits/growthub-agency-portal-starter-v1/apps/agency-portal/package.json +1 -1
  3. package/assets/worker-kits/growthub-agency-portal-starter-v1/bundles/growthub-agency-portal-starter-v1.json +1 -0
  4. package/assets/worker-kits/growthub-agency-portal-starter-v1/kit.json +2 -0
  5. package/assets/worker-kits/growthub-creative-video-pipeline-v1/apps/creative-video-pipeline/package.json +1 -1
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/SKILL.md +35 -1
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/.env.example +41 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +38 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/settings/integrations/route.js +13 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +31 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +717 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/layout.jsx +14 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +117 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +105 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/growthub.config.json +53 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/jsconfig.json +8 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/auth/index.js +21 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/env.js +28 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/growthub-connection-normalizer.js +95 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/index.js +198 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/payments/index.js +13 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/persistence/index.js +13 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/persistence/postgres.js +16 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/persistence/provider-managed.js +16 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/persistence/qstash-kv.js +16 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/domain/integrations.js +185 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/domain/portal.js +150 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/next.config.js +10 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +976 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +17 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/postcss.config.mjs +3 -0
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/vercel.json +5 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/bundles/growthub-custom-workspace-starter-v1.json +13 -0
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/docs/adapter-contracts.md +86 -0
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/docs/vercel-serverless-deployment.md +46 -0
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/growthub.config.json +49 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/check-self-improving-health.sh +60 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/promote-capability.mjs +37 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/propose-capability.mjs +38 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +45 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/deployment-handoff.md +61 -0
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/supabase-setup-plan.md +26 -0
  43. package/dist/index.js +5252 -3460
  44. package/package.json +3 -2
@@ -0,0 +1,117 @@
1
+ import workspaceConfig from "../growthub.config.json";
2
+ import { readAdapterConfig } from "@/lib/adapters/env";
3
+ import { describeIntegrationAdapter } from "@/lib/adapters/integrations";
4
+ import Link from "next/link";
5
+
6
+ function Home() {
7
+ const adapterConfig = readAdapterConfig();
8
+ const integrationAdapter = describeIntegrationAdapter();
9
+ const canvas = workspaceConfig.canvas;
10
+ const dashboards = workspaceConfig.dashboards;
11
+ const widgetTypes = workspaceConfig.widgetTypes;
12
+
13
+ return <main className="workspace-builder">
14
+ <aside className="workspace-rail" aria-label="Workspace navigation">
15
+ <div className="workspace-brand">
16
+ <span className="workspace-mark">G</span>
17
+ <span>Growthub Workspace</span>
18
+ </div>
19
+ <nav className="workspace-nav">
20
+ <a className="active" href="#dashboards">Dashboards</a>
21
+ <a href="#canvas">Canvas</a>
22
+ <a href="#widgets">Widgets</a>
23
+ <a href="#bindings">Bindings</a>
24
+ <Link href="/settings/integrations">Integrations</Link>
25
+ </nav>
26
+ <div className="workspace-rail-status">
27
+ <span className="status-dot" />
28
+ {integrationAdapter.authority}
29
+ </div>
30
+ </aside>
31
+
32
+ <section className="workspace-surface">
33
+ <header className="workspace-toolbar">
34
+ <div>
35
+ <p>Official starter</p>
36
+ <h1>{workspaceConfig.name}</h1>
37
+ </div>
38
+ <div className="workspace-toolbar-actions">
39
+ <button type="button">New Dashboard</button>
40
+ <button type="button">Save</button>
41
+ </div>
42
+ </header>
43
+
44
+ <section className="workspace-table" id="dashboards" aria-label="Dashboards">
45
+ <div className="workspace-table-heading">
46
+ <strong>Dashboards</strong>
47
+ <span>{dashboards.length} template</span>
48
+ </div>
49
+ <div className="workspace-table-row workspace-table-head">
50
+ <span>Title</span>
51
+ <span>Created by</span>
52
+ <span>Last update</span>
53
+ <span>Status</span>
54
+ </div>
55
+ {dashboards.map((dashboard) => <div className="workspace-table-row" key={dashboard.id}>
56
+ <span>{dashboard.name}</span>
57
+ <span>{dashboard.createdBy}</span>
58
+ <span>{dashboard.updatedAt}</span>
59
+ <code>{dashboard.status}</code>
60
+ </div>)}
61
+ </section>
62
+
63
+ <section className="workspace-canvas" id="canvas" aria-label="Composable dashboard canvas">
64
+ <div className="workspace-tabs">
65
+ <button className="active" type="button">{canvas.name}</button>
66
+ <button type="button">New Tab</button>
67
+ </div>
68
+ <div className="workspace-grid" style={{ "--workspace-columns": canvas.layout.columns }}>
69
+ <button className="workspace-add-widget" type="button">
70
+ <span className="workspace-widget-icon" aria-hidden="true"><span /></span>
71
+ <strong>Add widget</strong>
72
+ <small>Click to add your first widget</small>
73
+ </button>
74
+ {Array.from({ length: 96 }).map((_, index) => <span aria-hidden="true" className="workspace-grid-cell" key={index} />)}
75
+ {canvas.widgets.map((widget) => <article className="workspace-widget-preview" key={widget.id} style={{
76
+ gridColumn: `${widget.position.x + 1} / span ${widget.position.w}`,
77
+ gridRow: `${widget.position.y + 1} / span ${widget.position.h}`
78
+ }}>
79
+ <span>{widget.kind}</span>
80
+ <strong>{widget.title}</strong>
81
+ </article>)}
82
+ </div>
83
+ </section>
84
+ </section>
85
+
86
+ <aside className="workspace-widget-panel" id="widgets" aria-label="Widget configuration">
87
+ <div className="workspace-panel-title">
88
+ <button type="button" aria-label="Close widget panel">x</button>
89
+ <strong>New widget</strong>
90
+ </div>
91
+ <section>
92
+ <p className="workspace-panel-label">Widget type</p>
93
+ <div className="workspace-widget-types">
94
+ {widgetTypes.map((widget) => <button type="button" key={widget.kind}>
95
+ <span>{widget.icon}</span>
96
+ {widget.label}
97
+ </button>)}
98
+ </div>
99
+ </section>
100
+ <section className="workspace-bindings" id="bindings">
101
+ <p className="workspace-panel-label">Config bindings</p>
102
+ {Object.entries(canvas.bindings).map(([key, value]) => <div key={key}>
103
+ <span>{key}</span>
104
+ <code>{String(value)}</code>
105
+ </div>)}
106
+ <div>
107
+ <span>integrationAdapter</span>
108
+ <code>{adapterConfig.integrationAdapter}</code>
109
+ </div>
110
+ </section>
111
+ </aside>
112
+ </main>;
113
+ }
114
+
115
+ export {
116
+ Home as default
117
+ };
@@ -0,0 +1,105 @@
1
+ import { readAdapterConfig } from "@/lib/adapters/env";
2
+ import { describeIntegrationAdapter, listAgencyPortalIntegrations } from "@/lib/adapters/integrations";
3
+ import { groupIntegrationsByLane } from "@/lib/domain/integrations";
4
+ import { buildPortalWorkspace } from "@/lib/domain/portal";
5
+ import { describeAuthAdapter } from "@/lib/adapters/auth";
6
+ import { describePaymentAdapter } from "@/lib/adapters/payments";
7
+ import { describePersistenceAdapter } from "@/lib/adapters/persistence";
8
+ import Link from "next/link";
9
+ async function IntegrationsSettingsPage() {
10
+ const config = readAdapterConfig();
11
+ const adapter = describeIntegrationAdapter();
12
+ const grouped = groupIntegrationsByLane(await listAgencyPortalIntegrations());
13
+ const workspace = buildPortalWorkspace({
14
+ config,
15
+ integrations: grouped,
16
+ adapters: {
17
+ persistence: describePersistenceAdapter(),
18
+ auth: describeAuthAdapter(),
19
+ payments: describePaymentAdapter(),
20
+ integrations: adapter
21
+ }
22
+ });
23
+ const rows = [
24
+ ...grouped.dataSources.map((item) => ({ ...item, primitiveGroup: "data-source" })),
25
+ ...grouped.workspaceIntegrations.map((item) => ({ ...item, primitiveGroup: "workspace-integration" }))
26
+ ];
27
+ return <main className="shell">
28
+ <aside className="sidebar">
29
+ <div className="brand">
30
+ <span className="brand-mark">{workspace.identity.mark}</span>
31
+ <span>{workspace.identity.label}</span>
32
+ </div>
33
+ <nav className="nav">
34
+ {workspace.navigation.map((item) => <Link className={item.href === "/settings/integrations" ? "active" : ""} key={item.href} href={item.href.startsWith("#") ? `/${item.href}` : item.href}>
35
+ {item.label}
36
+ </Link>)}
37
+ </nav>
38
+ <div className="sidebar-footer">
39
+ <span className="status-dot" />
40
+ {adapter.authority}
41
+ </div>
42
+ </aside>
43
+ <section className="main">
44
+ <div className="utility-bar">
45
+ <div>
46
+ <strong>{adapter.label}</strong>
47
+ <span>{workspace.identity.primitiveContract}</span>
48
+ </div>
49
+ <div className="utility-actions">
50
+ <span className="pill">{adapter.id}</span>
51
+ <span className="pill">{adapter.authority}</span>
52
+ </div>
53
+ </div>
54
+ <section className="primitive-grid summary" aria-label="Integration adapter primitives">
55
+ <article className="primitive-card">
56
+ <div className="primitive-card-top">
57
+ <p className="card-label">Authority</p>
58
+ <span className="status runtime-derived">{adapter.authority}</span>
59
+ </div>
60
+ <strong>{adapter.id}</strong>
61
+ <div className="primitive-meta">
62
+ {adapter.requiredEnv.map((key) => <code key={key}>{key}</code>)}
63
+ </div>
64
+ </article>
65
+ <article className="primitive-card">
66
+ <div className="primitive-card-top">
67
+ <p className="card-label">Data-source primitives</p>
68
+ <span className="status runtime-derived">{grouped.dataSources.length}</span>
69
+ </div>
70
+ <strong>{grouped.dataSources.filter((item) => item.isConnected).length}/{grouped.dataSources.length}</strong>
71
+ <div className="primitive-meta"><span>{config.reportingAdapter || "reporting-adapter"}</span></div>
72
+ </article>
73
+ <article className="primitive-card">
74
+ <div className="primitive-card-top">
75
+ <p className="card-label">Workspace primitives</p>
76
+ <span className="status runtime-derived">{grouped.workspaceIntegrations.length}</span>
77
+ </div>
78
+ <strong>{grouped.workspaceIntegrations.filter((item) => item.isConnected).length}/{grouped.workspaceIntegrations.length}</strong>
79
+ <div className="primitive-meta"><span>{config.integrationAdapter}</span></div>
80
+ </article>
81
+ </section>
82
+ <section className="integration-board">
83
+ {rows.map((item) => <article className="integration-card" key={item.id}>
84
+ <div className="integration-card-top">
85
+ <div className="provider-mark">{item.icon || item.label.slice(0, 1)}</div>
86
+ <div>
87
+ <strong>{item.label}</strong>
88
+ <p>{item.provider} / {item.objectType} / {item.primitiveGroup}</p>
89
+ </div>
90
+ <span className={`status ${item.status}`}>{item.status}</span>
91
+ </div>
92
+ <div className="integration-card-meta">
93
+ <span>{item.authPath}</span>
94
+ <span>{item.setupMode}</span>
95
+ <span>{item.authType}</span>
96
+ {item.secretEnvName ? <span>{item.secretEnvName}</span> : null}
97
+ </div>
98
+ </article>)}
99
+ </section>
100
+ </section>
101
+ </main>;
102
+ }
103
+ export {
104
+ IntegrationsSettingsPage as default
105
+ };
@@ -0,0 +1,53 @@
1
+ {
2
+ "id": "workspace-builder-default",
3
+ "name": "Workspace Builder",
4
+ "description": "Default no-code composition for the official Growthub Custom Workspace Starter. The interface starts as a configurable dashboard-builder shell and can be customized by editing this file or by future governed UI controls.",
5
+ "capabilities": [
6
+ "dashboards",
7
+ "canvas",
8
+ "widgets",
9
+ "bindings",
10
+ "integrations",
11
+ "settings"
12
+ ],
13
+ "pipelines": [],
14
+ "integrations": [],
15
+ "dashboards": [
16
+ {
17
+ "id": "untitled-dashboard",
18
+ "name": "Untitled",
19
+ "createdBy": "Workspace owner",
20
+ "updatedAt": "new",
21
+ "status": "draft"
22
+ }
23
+ ],
24
+ "widgetTypes": [
25
+ { "kind": "chart", "label": "Chart", "icon": "C" },
26
+ { "kind": "view", "label": "View", "icon": "V" },
27
+ { "kind": "iframe", "label": "iFrame", "icon": "I" },
28
+ { "kind": "rich-text", "label": "Rich Text", "icon": "T" }
29
+ ],
30
+ "canvas": {
31
+ "id": "workspace-canvas",
32
+ "name": "Tab 1",
33
+ "scope": "workspace",
34
+ "layout": {
35
+ "columns": 12,
36
+ "rowHeight": 64,
37
+ "gap": 16,
38
+ "responsive": true
39
+ },
40
+ "widgets": [],
41
+ "bindings": {
42
+ "chatToCanvas": true,
43
+ "workflowOutputsToArtifacts": true,
44
+ "sessionContext": true,
45
+ "configDrivenCanvas": true
46
+ }
47
+ },
48
+ "provenance": {
49
+ "createdBy": "cli",
50
+ "mirrors": "growthub-agency-portal-starter-v1",
51
+ "note": "Shipped with growthub-custom-workspace-starter-v1; safe to edit inside a governed fork and validate through the starter export smoke path."
52
+ }
53
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "@/*": ["./*"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ import { readAdapterConfig } from "../env";
2
+ function describeAuthAdapter() {
3
+ const { authAdapter } = readAdapterConfig();
4
+ if (authAdapter === "oidc") {
5
+ return {
6
+ id: "oidc",
7
+ requiredEnv: ["AUTH_SECRET", "AUTH_ISSUER", "AUTH_CLIENT_ID", "AUTH_CLIENT_SECRET"],
8
+ notes: ["Default portable auth contract for Vercel and local serverless use."]
9
+ };
10
+ }
11
+ if (authAdapter === "clerk") {
12
+ return { id: "clerk", requiredEnv: [], notes: ["Configure Clerk-specific env in the deployment target."] };
13
+ }
14
+ if (authAdapter === "authjs") {
15
+ return { id: "authjs", requiredEnv: ["AUTH_SECRET"], notes: ["Use Auth.js provider configuration in app code."] };
16
+ }
17
+ return { id: "provider-managed", requiredEnv: [], notes: ["Auth is managed outside the kit contract."] };
18
+ }
19
+ export {
20
+ describeAuthAdapter
21
+ };
@@ -0,0 +1,28 @@
1
+ function readAdapterConfig() {
2
+ return {
3
+ deployTarget: "vercel",
4
+ dataAdapter: readEnum("AGENCY_PORTAL_DATA_ADAPTER", ["postgres", "qstash-kv", "provider-managed"], "provider-managed"),
5
+ authAdapter: readEnum("AGENCY_PORTAL_AUTH_ADAPTER", ["oidc", "clerk", "authjs", "provider-managed"], "provider-managed"),
6
+ paymentAdapter: readEnum("AGENCY_PORTAL_PAYMENT_ADAPTER", ["none", "stripe", "polar"], "none"),
7
+ integrationAdapter: readEnum("AGENCY_PORTAL_INTEGRATION_ADAPTER", ["growthub-bridge", "byo-api-key", "static"], "static"),
8
+ reportingAdapter: process.env.AGENCY_PORTAL_REPORTING_ADAPTER || void 0,
9
+ growthubBridge: {
10
+ baseUrl: process.env.GROWTHUB_BRIDGE_BASE_URL || void 0,
11
+ integrationsPath: process.env.GROWTHUB_BRIDGE_INTEGRATIONS_PATH || "/api/mcp/accounts",
12
+ userId: process.env.GROWTHUB_BRIDGE_USER_ID || void 0,
13
+ hasAccessToken: Boolean(process.env.GROWTHUB_BRIDGE_ACCESS_TOKEN)
14
+ },
15
+ dataSources: {
16
+ hasWindsorApiKey: Boolean(process.env.WINDSOR_API_KEY)
17
+ }
18
+ };
19
+ }
20
+ function readEnum(key, allowed, fallback) {
21
+ const value = process.env[key];
22
+ if (!value) return fallback;
23
+ if (allowed.includes(value)) return value;
24
+ throw new Error(`${key} must be one of: ${allowed.join(", ")}`);
25
+ }
26
+ export {
27
+ readAdapterConfig
28
+ };
@@ -0,0 +1,95 @@
1
+ const providerAliases = {
2
+ ga4: "google-analytics",
3
+ google_analytics: "google-analytics",
4
+ google_drive: "google-drive",
5
+ ghl: "go-high-level",
6
+ gohighlevel: "go-high-level",
7
+ meta: "meta-ads",
8
+ meta_ads: "meta-ads"
9
+ };
10
+ function normalizeProviderId(provider) {
11
+ const normalized = provider.trim().toLowerCase().replaceAll("_", "-");
12
+ return providerAliases[normalized] || normalized;
13
+ }
14
+ function isHostedRecord(row) {
15
+ return "provider" in row && ("ready" in row || "connectedAt" in row || "scopes" in row || "handle" in row);
16
+ }
17
+ function normalizeHostedIntegration(row) {
18
+ const provider = normalizeProviderId(row.provider);
19
+ const ready = row.ready !== false;
20
+ return {
21
+ id: provider,
22
+ provider,
23
+ label: row.label,
24
+ name: row.label,
25
+ status: ready ? "connected" : "needs-connection",
26
+ isConnected: ready,
27
+ isActive: ready,
28
+ authPath: "growthub-mcp-bridge",
29
+ setupMode: "hosted-authority",
30
+ connectionMetadata: {
31
+ source: "growthub-cli-profile",
32
+ connectedAt: row.connectedAt,
33
+ scopes: row.scopes,
34
+ handle: row.handle
35
+ }
36
+ };
37
+ }
38
+ function normalizeMcpAccount(account) {
39
+ const provider = normalizeProviderId(account.provider);
40
+ const isActive = account.isActive === true;
41
+ const isVerified = account.isVerified === true;
42
+ const isConnected = isActive;
43
+ return {
44
+ id: provider,
45
+ provider,
46
+ label: account.connectionName || void 0,
47
+ name: account.connectionName || void 0,
48
+ authType: normalizeConnectionType(account.connectionType),
49
+ status: isConnected ? "connected" : "needs-connection",
50
+ isConnected,
51
+ isActive,
52
+ connectionId: account.id,
53
+ authPath: "growthub-mcp-bridge",
54
+ setupMode: "hosted-authority",
55
+ connectionMetadata: {
56
+ source: "growthub-mcp-accounts",
57
+ accountId: account.id,
58
+ connectionName: account.connectionName,
59
+ connectionType: account.connectionType,
60
+ isVerified,
61
+ appSlug: account.appSlug,
62
+ createdAt: account.createdAt,
63
+ updatedAt: account.updatedAt,
64
+ metadata: account.metadata || void 0
65
+ }
66
+ };
67
+ }
68
+ function normalizeConnectionType(connectionType) {
69
+ if (connectionType === "api_token" || connectionType === "api_key") return "api_token";
70
+ if (connectionType === "webhook") return "webhook";
71
+ return "oauth_first_party";
72
+ }
73
+ function normalizeBridgeRow(row) {
74
+ if (isHostedRecord(row)) return normalizeHostedIntegration(row);
75
+ const provider = normalizeProviderId(row.provider || row.id || "");
76
+ return {
77
+ ...row,
78
+ id: row.id || provider,
79
+ provider
80
+ };
81
+ }
82
+ function normalizeGrowthubBridgePayload(payload) {
83
+ if (Array.isArray(payload)) {
84
+ return payload.map(normalizeBridgeRow);
85
+ }
86
+ return [
87
+ ...(payload.integrations || []).map(normalizeBridgeRow),
88
+ ...(payload.accounts || []).map(normalizeMcpAccount),
89
+ ...(payload.dataSources || []).map(normalizeBridgeRow),
90
+ ...(payload.workspaceIntegrations || []).map(normalizeBridgeRow)
91
+ ];
92
+ }
93
+ export {
94
+ normalizeGrowthubBridgePayload
95
+ };
@@ -0,0 +1,198 @@
1
+ import { readAdapterConfig } from "@/lib/adapters/env";
2
+ import {
3
+ agencyPortalIntegrationCatalog
4
+ } from "@/lib/domain/integrations";
5
+ import {
6
+ normalizeGrowthubBridgePayload
7
+ } from "./growthub-connection-normalizer";
8
+ function describeIntegrationAdapter() {
9
+ const config = readAdapterConfig();
10
+ if (config.integrationAdapter === "growthub-bridge") {
11
+ return {
12
+ id: "growthub-bridge",
13
+ label: "Growthub MCP bridge",
14
+ requiredEnv: ["GROWTHUB_BRIDGE_BASE_URL", "GROWTHUB_BRIDGE_ACCESS_TOKEN"],
15
+ authority: "growthub-gh-app"
16
+ };
17
+ }
18
+ if (config.integrationAdapter === "byo-api-key") {
19
+ return {
20
+ id: "byo-api-key",
21
+ label: "Bring your own API key",
22
+ requiredEnv: ["AGENCY_PORTAL_BYO_CONNECTIONS_JSON"],
23
+ authority: "workspace-env"
24
+ };
25
+ }
26
+ return {
27
+ id: "static",
28
+ label: "Static starter catalog",
29
+ requiredEnv: [],
30
+ authority: "local-catalog"
31
+ };
32
+ }
33
+ async function listAgencyPortalIntegrations() {
34
+ const config = readAdapterConfig();
35
+ if (config.integrationAdapter !== "growthub-bridge") {
36
+ if (config.integrationAdapter === "byo-api-key") {
37
+ return mergeBringYourOwnRows(readBringYourOwnRows());
38
+ }
39
+ return agencyPortalIntegrationCatalog;
40
+ }
41
+ if (!config.growthubBridge.baseUrl || !process.env.GROWTHUB_BRIDGE_ACCESS_TOKEN) {
42
+ return agencyPortalIntegrationCatalog;
43
+ }
44
+ const url = new URL(config.growthubBridge.integrationsPath, config.growthubBridge.baseUrl);
45
+ const headers = {
46
+ accept: "application/json",
47
+ authorization: `Bearer ${process.env.GROWTHUB_BRIDGE_ACCESS_TOKEN}`
48
+ };
49
+ if (config.growthubBridge.userId) {
50
+ headers["x-user-id"] = config.growthubBridge.userId;
51
+ }
52
+ const response = await fetch(url, {
53
+ headers,
54
+ next: { revalidate: 30 }
55
+ });
56
+ if (!response.ok) {
57
+ return agencyPortalIntegrationCatalog;
58
+ }
59
+ const payload = await response.json();
60
+ const merged = mergeBridgeRows(normalizeGrowthubBridgePayload(payload));
61
+ return applyApiKeyOverlays(merged, config);
62
+ }
63
+ function applyApiKeyOverlays(integrations, config) {
64
+ if (!config.dataSources.hasWindsorApiKey) return integrations;
65
+ const windsorOverlay = {
66
+ status: "connected",
67
+ isConnected: true,
68
+ isActive: true,
69
+ authPath: "byo-api-key",
70
+ setupMode: "bring-your-own-key",
71
+ authType: "api_token",
72
+ category: "api_key",
73
+ secretEnvName: "WINDSOR_API_KEY",
74
+ connectionMetadata: { source: "workspace-env", secretEnvName: "WINDSOR_API_KEY" }
75
+ };
76
+ return integrations.map((item) => {
77
+ if (item.provider === "windsor-ai") return { ...item, ...windsorOverlay };
78
+ if (item.provider === "google-sheets") return { ...item, ...windsorOverlay, secretEnvName: undefined, connectionMetadata: { source: "windsor-blended-data" } };
79
+ return item;
80
+ });
81
+ }
82
+ function readBringYourOwnRows() {
83
+ const raw = process.env.AGENCY_PORTAL_BYO_CONNECTIONS_JSON;
84
+ const rows = [];
85
+ if (process.env.WINDSOR_API_KEY) {
86
+ rows.push({
87
+ id: "windsor-ai",
88
+ provider: "windsor-ai",
89
+ name: "Windsor AI",
90
+ label: "Windsor AI",
91
+ category: "api_key",
92
+ authType: "api_token",
93
+ status: "connected",
94
+ isConnected: true,
95
+ isActive: true,
96
+ authPath: "byo-api-key",
97
+ setupMode: "bring-your-own-key",
98
+ secretEnvName: "WINDSOR_API_KEY",
99
+ connectionMetadata: {
100
+ source: "workspace-env",
101
+ secretEnvName: "WINDSOR_API_KEY"
102
+ }
103
+ });
104
+ }
105
+ if (!raw) return rows;
106
+ try {
107
+ const parsed = JSON.parse(raw);
108
+ return [...rows, ...normalizeGrowthubBridgePayload(parsed)];
109
+ } catch {
110
+ return rows;
111
+ }
112
+ }
113
+ function mergeBringYourOwnRows(rows) {
114
+ const merged = mergeBridgeRows(rows);
115
+ return merged.map((item) => {
116
+ const row = rows.find((candidate) => {
117
+ const provider = candidate.provider || candidate.id;
118
+ return provider === item.provider || candidate.id === item.id;
119
+ });
120
+ if (!row) return item;
121
+ return {
122
+ ...item,
123
+ authPath: "byo-api-key",
124
+ setupMode: "bring-your-own-key",
125
+ secretEnvName: typeof row.secretEnvName === "string" ? row.secretEnvName : void 0,
126
+ status: row.status || "connected"
127
+ };
128
+ });
129
+ }
130
+ function mergeBridgeRows(rows) {
131
+ const seenProviders = /* @__PURE__ */ new Set();
132
+ const merged = agencyPortalIntegrationCatalog.map((catalogItem) => {
133
+ const row = rows.find((item) => {
134
+ const provider = item.provider || item.id;
135
+ return provider === catalogItem.provider || item.id === catalogItem.id;
136
+ });
137
+ if (!row) return catalogItem;
138
+ seenProviders.add(row.provider || row.id || catalogItem.provider);
139
+ return {
140
+ ...catalogItem,
141
+ label: row.label || row.name || catalogItem.label,
142
+ name: row.name || row.label || catalogItem.name,
143
+ icon: row.icon || catalogItem.icon,
144
+ description: row.description || catalogItem.description,
145
+ category: row.category || catalogItem.category,
146
+ authType: row.authType || catalogItem.authType,
147
+ isConnected: row.isConnected ?? (row.status === "connected" ? true : catalogItem.isConnected),
148
+ isActive: row.isActive ?? (row.status === "connected" ? true : catalogItem.isActive),
149
+ authPath: row.authPath || catalogItem.authPath,
150
+ setupMode: row.setupMode || catalogItem.setupMode,
151
+ status: row.status || (row.isConnected || row.isActive ? "connected" : catalogItem.status),
152
+ connectionId: row.connectionId,
153
+ accountId: row.accountId,
154
+ secretEnvName: row.secretEnvName,
155
+ connectionMetadata: row.connectionMetadata || row.metadata,
156
+ metadata: row.metadata || row.connectionMetadata
157
+ };
158
+ });
159
+ const discoveredRows = rows.filter((row) => {
160
+ const provider = row.provider || row.id;
161
+ if (!provider) return false;
162
+ if (seenProviders.has(provider)) return false;
163
+ return !agencyPortalIntegrationCatalog.some((item) => item.provider === provider || item.id === row.id);
164
+ });
165
+ return [...merged, ...discoveredRows.map(toDiscoveredIntegration)];
166
+ }
167
+ function toDiscoveredIntegration(row) {
168
+ const provider = row.provider || row.id || "unknown-provider";
169
+ const label = row.label || row.name || provider;
170
+ const isDataPipeline = ["windsor-ai", "google-sheets", "google-analytics", "shopify", "meta-ads"].includes(provider);
171
+ const isConnected = row.isConnected ?? row.status === "connected";
172
+ return {
173
+ id: row.id || provider,
174
+ label,
175
+ name: row.name || label,
176
+ icon: row.icon || label.slice(0, 1).toUpperCase(),
177
+ provider,
178
+ description: row.description || "Connected through the Growthub account bridge.",
179
+ category: row.category || "mcp_connector",
180
+ authType: row.authType || "oauth_first_party",
181
+ isConnected,
182
+ isActive: row.isActive ?? isConnected,
183
+ lane: isDataPipeline ? "data-source" : "workspace-integration",
184
+ objectType: isDataPipeline ? "data-pipeline" : "mcp-connection",
185
+ status: row.status || (isConnected ? "connected" : "needs-connection"),
186
+ authPath: row.authPath || "growthub-mcp-bridge",
187
+ setupMode: row.setupMode || "hosted-authority",
188
+ connectionId: row.connectionId,
189
+ accountId: row.accountId,
190
+ secretEnvName: row.secretEnvName,
191
+ connectionMetadata: row.connectionMetadata || row.metadata,
192
+ metadata: row.metadata || row.connectionMetadata
193
+ };
194
+ }
195
+ export {
196
+ describeIntegrationAdapter,
197
+ listAgencyPortalIntegrations
198
+ };
@@ -0,0 +1,13 @@
1
+ import { readAdapterConfig } from "../env";
2
+ function describePaymentAdapter() {
3
+ const { paymentAdapter } = readAdapterConfig();
4
+ if (paymentAdapter === "none") return { id: "none", requiredEnv: [], enabled: false };
5
+ return {
6
+ id: paymentAdapter,
7
+ requiredEnv: ["PAYMENT_SECRET_KEY", "PAYMENT_WEBHOOK_SECRET"],
8
+ enabled: true
9
+ };
10
+ }
11
+ export {
12
+ describePaymentAdapter
13
+ };
@@ -0,0 +1,13 @@
1
+ import { readAdapterConfig } from "../env";
2
+ import { describePostgresAdapter } from "./postgres";
3
+ import { describeProviderManagedAdapter } from "./provider-managed";
4
+ import { describeQstashKvAdapter } from "./qstash-kv";
5
+ function describePersistenceAdapter() {
6
+ const config = readAdapterConfig();
7
+ if (config.dataAdapter === "postgres") return describePostgresAdapter();
8
+ if (config.dataAdapter === "qstash-kv") return describeQstashKvAdapter();
9
+ return describeProviderManagedAdapter();
10
+ }
11
+ export {
12
+ describePersistenceAdapter
13
+ };
@@ -0,0 +1,16 @@
1
+ function describePostgresAdapter() {
2
+ return {
3
+ id: "postgres",
4
+ label: "Postgres",
5
+ requiredEnv: ["DATABASE_URL"],
6
+ mode: "sql",
7
+ notes: [
8
+ "Use any Postgres-compatible provider.",
9
+ "Keep provider-specific pooling, SSL, and migration tooling outside the kit contract.",
10
+ "Application repositories should depend on this descriptor, not a provider SDK directly."
11
+ ]
12
+ };
13
+ }
14
+ export {
15
+ describePostgresAdapter
16
+ };