@growthub/cli 0.13.4 → 0.13.6
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/assets/worker-kits/growthub-custom-workspace-starter-v1/QUICKSTART.md +19 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/.env.example +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +4 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/action/execute/route.js +60 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/actions/route.js +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/connect-session/route.js +68 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/connection-status/route.js +56 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/proxy/route.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/status/route.js +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +184 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +25 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +161 -50
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +496 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +88 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +41 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +120 -17
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/nango/page.jsx +167 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +67 -11
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +31 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +16 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/env.js +7 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/index.js +38 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-adapter.js +552 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-config-loader.js +202 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-schema.js +303 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +49 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/nango.js +49 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +4 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +14 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +218 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +28 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +43 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +3 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +36 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +53 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +2 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +102 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/bundles/growthub-custom-workspace-starter-v1.json +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +7 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +276 -0
- package/dist/index.js +127 -44
- package/package.json +1 -1
|
@@ -10,6 +10,25 @@ This kit is the canonical v1 starter primitive. Run `growthub starter init --nam
|
|
|
10
10
|
|
|
11
11
|
After that, every customization you make stays yours — the Self-Healing Fork Sync Agent will propose upstream additions non-destructively, honouring your policy.
|
|
12
12
|
|
|
13
|
+
## Official workspace paths
|
|
14
|
+
|
|
15
|
+
Workspace 1 stays the blank governed workspace:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
growthub starter init --name "My Workspace" --out ./my-workspace
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Workspace 2 is the opinionated project-management template path. It builds on the same app and adapter surface, but seeds API Registry, Data Source, Sandbox Environment workflow, and dashboard placeholders for project task deltas. It stores no provider secrets, OAuth connection ids, or provider task data.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
growthub starter init \
|
|
25
|
+
--name "Project Management Workspace" \
|
|
26
|
+
--out ./project-management-workspace \
|
|
27
|
+
--seed-config project-management
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
After export, set `NANGO_SECRET_KEY` in the workspace runtime, complete provider OAuth, then fill `providerConfigKey`, `connectionIds`, and project identifiers in the Data Model before running the workflow.
|
|
31
|
+
|
|
13
32
|
## Run the bundled Vite UI shell
|
|
14
33
|
|
|
15
34
|
```bash
|
|
@@ -10,6 +10,14 @@ GROWTHUB_WORKSPACE_PAYMENT_ADAPTER=none
|
|
|
10
10
|
# - static: local starter catalog only
|
|
11
11
|
GROWTHUB_WORKSPACE_INTEGRATION_ADAPTER=growthub-bridge
|
|
12
12
|
|
|
13
|
+
# Nango thin adapter — row-scoped, not a workspace-wide adapter.
|
|
14
|
+
# Set NANGO_SECRET_KEY when any api-registry row has connectorKind: "nango".
|
|
15
|
+
# Cloud mode reaches Nango Cloud; self-hosted requires NANGO_HOST_URL.
|
|
16
|
+
NANGO_MODE=cloud
|
|
17
|
+
NANGO_HOST_URL=
|
|
18
|
+
NANGO_ENVIRONMENT=dev
|
|
19
|
+
NANGO_SECRET_KEY=
|
|
20
|
+
|
|
13
21
|
# Hosted bridge authority
|
|
14
22
|
GROWTHUB_BRIDGE_BASE_URL=https://www.growthub.ai
|
|
15
23
|
GROWTHUB_BRIDGE_INTEGRATIONS_PATH=/api/mcp/accounts
|
|
@@ -13,6 +13,10 @@ It intentionally depends on adapter contracts:
|
|
|
13
13
|
- `GROWTHUB_BRIDGE_ACCESS_TOKEN`
|
|
14
14
|
- `GROWTHUB_BRIDGE_USER_ID`
|
|
15
15
|
- `GROWTHUB_WORKSPACE_BYO_CONNECTIONS_JSON`
|
|
16
|
+
- `NANGO_SECRET_KEY` (required when any api-registry row uses `connectorKind: "nango"`)
|
|
17
|
+
- `NANGO_HOST_URL` (self-hosted Nango only)
|
|
18
|
+
- `NANGO_ENVIRONMENT` (default `dev`)
|
|
19
|
+
- `NANGO_MODE` (`cloud` | `self-hosted`, default `cloud`)
|
|
16
20
|
|
|
17
21
|
The Growthub local-first operator shell remains at `../../studio`.
|
|
18
22
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workspace/integrations/nango/action/execute
|
|
3
|
+
*
|
|
4
|
+
* Executes a declared Nango action function against the connection
|
|
5
|
+
* identified by `providerConfigKey` + `connectionId`. Used by workspace
|
|
6
|
+
* agents as a tool-call surface.
|
|
7
|
+
*
|
|
8
|
+
* Request body:
|
|
9
|
+
* {
|
|
10
|
+
* providerConfigKey: string,
|
|
11
|
+
* connectionId: string,
|
|
12
|
+
* action: string, // action function name from nango.yaml
|
|
13
|
+
* input?: object | array // arguments to the action
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* Response — success: 200 { ok: true, action, providerConfigKey, connectionId, result, environment }
|
|
17
|
+
* Response — validation failure: 400 { ok: false, error, details }
|
|
18
|
+
* Response — Nango not configured: 503 { ok: false, error, code }
|
|
19
|
+
* Response — upstream failure: 502 { ok: false, error }
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { NextResponse } from "next/server";
|
|
23
|
+
import { executeAction, validateActionExecuteRequest } from "@/lib/adapters/integrations/nango";
|
|
24
|
+
|
|
25
|
+
async function POST(request) {
|
|
26
|
+
let body;
|
|
27
|
+
try {
|
|
28
|
+
body = await request.json();
|
|
29
|
+
} catch {
|
|
30
|
+
return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let validated;
|
|
34
|
+
try {
|
|
35
|
+
validated = validateActionExecuteRequest(body);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return NextResponse.json(
|
|
38
|
+
{ ok: false, error: error.message, details: error.details || null },
|
|
39
|
+
{ status: 400 }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await executeAction(validated);
|
|
45
|
+
return NextResponse.json({ ok: true, ...result });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE" || error.code === "NANGO_SDK_SHAPE") {
|
|
48
|
+
return NextResponse.json(
|
|
49
|
+
{ ok: false, error: error.message, code: error.code },
|
|
50
|
+
{ status: 503 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return NextResponse.json(
|
|
54
|
+
{ ok: false, error: error?.message || "nango action execution failed" },
|
|
55
|
+
{ status: 502 }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { POST };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workspace/integrations/nango/actions
|
|
3
|
+
*
|
|
4
|
+
* Lists the actions declared in `nango.yaml` for a given provider config key
|
|
5
|
+
* (or, when no key is provided, returns the full discoverable set). The
|
|
6
|
+
* response shape is MCP-compatible: each action exposes a name and (when
|
|
7
|
+
* provided by Nango) an input/output schema.
|
|
8
|
+
*
|
|
9
|
+
* Query parameters (optional):
|
|
10
|
+
* - providerConfigKey filter to actions for a single provider
|
|
11
|
+
*
|
|
12
|
+
* Response shape:
|
|
13
|
+
* { ok: true, providerConfigKey, probedShape, actions: Array, hint: string|null }
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { NextResponse } from "next/server";
|
|
17
|
+
import { listActions, validateActionsListInput } from "@/lib/adapters/integrations/nango";
|
|
18
|
+
|
|
19
|
+
async function GET(request) {
|
|
20
|
+
const url = new URL(request.url);
|
|
21
|
+
const raw = {
|
|
22
|
+
providerConfigKey: url.searchParams.get("providerConfigKey") || undefined
|
|
23
|
+
};
|
|
24
|
+
let input;
|
|
25
|
+
try {
|
|
26
|
+
input = validateActionsListInput(raw);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return NextResponse.json(
|
|
29
|
+
{ ok: false, error: error.message, details: error.details || null },
|
|
30
|
+
{ status: 400 }
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const result = await listActions(input);
|
|
35
|
+
return NextResponse.json({ ok: true, ...result });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE") {
|
|
38
|
+
return NextResponse.json(
|
|
39
|
+
{ ok: false, error: error.message, code: error.code },
|
|
40
|
+
{ status: 503 }
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return NextResponse.json(
|
|
44
|
+
{ ok: false, error: error?.message || "nango actions list failed" },
|
|
45
|
+
{ status: 502 }
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { GET };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workspace/integrations/nango/connect-session
|
|
3
|
+
*
|
|
4
|
+
* Creates a Nango Connect Session for the given providerConfigKey and
|
|
5
|
+
* returns a short-lived connect_link the browser opens to start OAuth.
|
|
6
|
+
* The Nango Connect UI mints tokens directly against Nango — the workspace
|
|
7
|
+
* never sees the resulting OAuth credentials. The token returned here is a
|
|
8
|
+
* Connect Session token (handoff only), not a provider credential.
|
|
9
|
+
*
|
|
10
|
+
* Lifecycle: Nango creates the connectionId AFTER the user completes OAuth
|
|
11
|
+
* and delivers it through the auth webhook. A normal Connect Session
|
|
12
|
+
* therefore does NOT need a connectionId as input — only the explicit
|
|
13
|
+
* Reconnect path (`reconnect: true`) operates on an existing connectionId.
|
|
14
|
+
*
|
|
15
|
+
* Request body:
|
|
16
|
+
* {
|
|
17
|
+
* providerConfigKey: string, // required, alphanumeric (+ _.-) <= 64 chars
|
|
18
|
+
* reconnect?: boolean, // true → use the SDK's reconnect-session path
|
|
19
|
+
* connectionId?: string, // REQUIRED when reconnect=true; ignored for a normal Create Connect Session
|
|
20
|
+
* endUser?: { id?: string, email?: string },
|
|
21
|
+
* tags?: Record<string, string> // echoed back in the Nango auth webhook so the workspace can map the eventual connectionId to the originating row
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* Response — success: 200 { ok: true, providerConfigKey, environment, mode, token, connectLink, sdkMethod, tagsEchoed }
|
|
25
|
+
* Response — validation failure: 400 { ok: false, error, details }
|
|
26
|
+
* Response — Nango not configured / SDK missing: 503 { ok: false, error, code }
|
|
27
|
+
* Response — upstream failure: 502 { ok: false, error }
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { NextResponse } from "next/server";
|
|
31
|
+
import { createConnectSession, validateConnectSessionRequest } from "@/lib/adapters/integrations/nango";
|
|
32
|
+
|
|
33
|
+
async function POST(request) {
|
|
34
|
+
let body;
|
|
35
|
+
try {
|
|
36
|
+
body = await request.json();
|
|
37
|
+
} catch {
|
|
38
|
+
return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let validated;
|
|
42
|
+
try {
|
|
43
|
+
validated = validateConnectSessionRequest(body);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return NextResponse.json(
|
|
46
|
+
{ ok: false, error: error.message, details: error.details || null },
|
|
47
|
+
{ status: 400 }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const result = await createConnectSession(validated);
|
|
53
|
+
return NextResponse.json({ ok: true, ...result });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE" || error.code === "NANGO_SDK_SHAPE") {
|
|
56
|
+
return NextResponse.json(
|
|
57
|
+
{ ok: false, error: error.message, code: error.code },
|
|
58
|
+
{ status: 503 }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return NextResponse.json(
|
|
62
|
+
{ ok: false, error: error?.message || "nango connect session failed" },
|
|
63
|
+
{ status: 502 }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { POST };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workspace/integrations/nango/connection-status
|
|
3
|
+
*
|
|
4
|
+
* Returns the safe (non-credential) status summary for a specific Nango
|
|
5
|
+
* connection. Used by the NangoConnectionPanel sidecar to verify a
|
|
6
|
+
* per-row connection without exposing OAuth tokens to the browser.
|
|
7
|
+
*
|
|
8
|
+
* Request body:
|
|
9
|
+
* { providerConfigKey: string, connectionId: string }
|
|
10
|
+
*
|
|
11
|
+
* Response — connected: 200 { ok: true, status: "connected", providerConfigKey, connectionId, environment, connection: { ...safe fields... } }
|
|
12
|
+
* Response — not yet connected: 200 { ok: true, status: "not-connected", providerConfigKey, connectionId, environment, reason }
|
|
13
|
+
* Response — validation failure: 400 { ok: false, error, details }
|
|
14
|
+
* Response — Nango not configured: 503 { ok: false, error, code }
|
|
15
|
+
* Response — upstream failure: 502 { ok: false, error }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { NextResponse } from "next/server";
|
|
19
|
+
import { getConnectionSummary, validateConnectionSummaryRequest } from "@/lib/adapters/integrations/nango";
|
|
20
|
+
|
|
21
|
+
async function POST(request) {
|
|
22
|
+
let body;
|
|
23
|
+
try {
|
|
24
|
+
body = await request.json();
|
|
25
|
+
} catch {
|
|
26
|
+
return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let validated;
|
|
30
|
+
try {
|
|
31
|
+
validated = validateConnectionSummaryRequest(body);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return NextResponse.json(
|
|
34
|
+
{ ok: false, error: error.message, details: error.details || null },
|
|
35
|
+
{ status: 400 }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await getConnectionSummary(validated);
|
|
41
|
+
return NextResponse.json({ ok: true, ...result });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE" || error.code === "NANGO_SDK_SHAPE") {
|
|
44
|
+
return NextResponse.json(
|
|
45
|
+
{ ok: false, error: error.message, code: error.code },
|
|
46
|
+
{ status: 503 }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ ok: false, error: error?.message || "nango connection status failed" },
|
|
51
|
+
{ status: 502 }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { POST };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workspace/integrations/nango/proxy
|
|
3
|
+
*
|
|
4
|
+
* Proxies an API request through Nango using the connection identified by
|
|
5
|
+
* `providerConfigKey` + `connectionId`. The Nango SDK injects credentials
|
|
6
|
+
* server-side; the browser never holds the upstream provider's auth.
|
|
7
|
+
*
|
|
8
|
+
* Request body:
|
|
9
|
+
* {
|
|
10
|
+
* providerConfigKey: string, // Nango integration config key
|
|
11
|
+
* connectionId: string, // Nango connection id
|
|
12
|
+
* method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
|
|
13
|
+
* endpoint: string, // path or absolute URL
|
|
14
|
+
* headers?: object, // forbidden: Authorization, X-API-Key, etc.
|
|
15
|
+
* params?: object,
|
|
16
|
+
* data?: any,
|
|
17
|
+
* retries?: number, // 0..10
|
|
18
|
+
* timeoutMs?: number // 0..60000
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Response — success:
|
|
22
|
+
* { ok: true, status: number, data: any, environment: string }
|
|
23
|
+
*
|
|
24
|
+
* Response — validation failure: 400 { ok: false, error, details }
|
|
25
|
+
* Response — Nango not configured: 503 { ok: false, error, code }
|
|
26
|
+
* Response — upstream failure: 502 { ok: false, error }
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { NextResponse } from "next/server";
|
|
30
|
+
import { proxyRequest, validateProxyRequest } from "@/lib/adapters/integrations/nango";
|
|
31
|
+
|
|
32
|
+
async function POST(request) {
|
|
33
|
+
let body;
|
|
34
|
+
try {
|
|
35
|
+
body = await request.json();
|
|
36
|
+
} catch {
|
|
37
|
+
return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let validated;
|
|
41
|
+
try {
|
|
42
|
+
validated = validateProxyRequest(body);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return NextResponse.json(
|
|
45
|
+
{ ok: false, error: error.message, details: error.details || null },
|
|
46
|
+
{ status: 400 }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const result = await proxyRequest(validated);
|
|
52
|
+
return NextResponse.json({ ok: true, ...result });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE" || error.code === "NANGO_SDK_SHAPE") {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ ok: false, error: error.message, code: error.code },
|
|
57
|
+
{ status: 503 }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return NextResponse.json(
|
|
61
|
+
{ ok: false, error: error?.message || "nango proxy failed" },
|
|
62
|
+
{ status: 502 }
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { POST };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workspace/integrations/nango/status
|
|
3
|
+
*
|
|
4
|
+
* Returns Nango connection health for the no-code Integrations panel.
|
|
5
|
+
*
|
|
6
|
+
* Query parameters (all optional):
|
|
7
|
+
* - providerConfigKey probe a specific provider connection
|
|
8
|
+
* - connectionId probe a specific connection (paired with providerConfigKey)
|
|
9
|
+
* - mode override mode for the probe ("cloud" | "self-hosted")
|
|
10
|
+
* - hostUrl override host URL for self-hosted probes
|
|
11
|
+
*
|
|
12
|
+
* Response shape:
|
|
13
|
+
* { status: "connected" | "disconnected" | "unconfigured", ... }
|
|
14
|
+
*
|
|
15
|
+
* Authority contract: never returns the Nango secret key. Never echoes
|
|
16
|
+
* credential-shaped fields from upstream SDK responses.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { NextResponse } from "next/server";
|
|
20
|
+
import { getStatus, validateConnectionStatusRequest } from "@/lib/adapters/integrations/nango";
|
|
21
|
+
|
|
22
|
+
async function GET(request) {
|
|
23
|
+
const url = new URL(request.url);
|
|
24
|
+
const raw = {
|
|
25
|
+
providerConfigKey: url.searchParams.get("providerConfigKey") || undefined,
|
|
26
|
+
connectionId: url.searchParams.get("connectionId") || undefined,
|
|
27
|
+
mode: url.searchParams.get("mode") || undefined,
|
|
28
|
+
hostUrl: url.searchParams.get("hostUrl") || undefined
|
|
29
|
+
};
|
|
30
|
+
let input;
|
|
31
|
+
try {
|
|
32
|
+
input = validateConnectionStatusRequest(raw);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ error: error.message, details: error.details || null },
|
|
36
|
+
{ status: 400 }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const result = await getStatus(input);
|
|
41
|
+
return NextResponse.json(result);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return NextResponse.json(
|
|
44
|
+
{ error: error?.message || "nango status probe failed" },
|
|
45
|
+
{ status: 500 }
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { GET };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workspace/metadata-graph
|
|
3
|
+
*
|
|
4
|
+
* Growthub Workspace Metadata Graph V1 — read-only projection.
|
|
5
|
+
*
|
|
6
|
+
* Combines the governed workspace config and the live source-record sidecar
|
|
7
|
+
* into a typed metadata store and a node/edge graph. Consumers (the UI
|
|
8
|
+
* inspector, the workspace helper agent, and external operators) use this
|
|
9
|
+
* envelope to ask dependency questions without re-deriving widget/workflow
|
|
10
|
+
* contracts inside every component.
|
|
11
|
+
*
|
|
12
|
+
* Optional query parameters:
|
|
13
|
+
* - staleKind: "object" | "field" | "sourceRecord" | "workflow" | "agentHost" | "widget"
|
|
14
|
+
* - staleId: the corresponding metadata id (for `field`, use
|
|
15
|
+
* "<objectId>::<fieldId>")
|
|
16
|
+
*
|
|
17
|
+
* When both are provided the response `stale.groups` and `stale.reasons`
|
|
18
|
+
* reflect `selectStaleMetadataGroups({ kind, id })`. When omitted the
|
|
19
|
+
* stale section returns the empty baseline.
|
|
20
|
+
*
|
|
21
|
+
* Authority invariants:
|
|
22
|
+
* - GET only. PATCH / POST / PUT / DELETE are not exposed. Writes still
|
|
23
|
+
* flow through the existing governed routes
|
|
24
|
+
* (`PATCH /api/workspace`, `POST /api/workspace/refresh-sources`,
|
|
25
|
+
* `POST /api/workspace/sandbox-run`).
|
|
26
|
+
* - growthub.config.json remains the authoritative artifact.
|
|
27
|
+
* - No secrets are returned. Field metadata derived from secret-shaped
|
|
28
|
+
* column names is marked `isSecret: true` but no value is echoed.
|
|
29
|
+
* - Failures during read OR projection fall back to an empty store with
|
|
30
|
+
* warnings — this route never throws.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { NextResponse } from "next/server";
|
|
34
|
+
import { readWorkspaceConfig, readWorkspaceSourceRecords } from "@/lib/workspace-config";
|
|
35
|
+
import { buildWorkspaceMetadataStore } from "@/lib/workspace-metadata-store";
|
|
36
|
+
import { buildWorkspaceMetadataGraph } from "@/lib/workspace-metadata-graph";
|
|
37
|
+
import { selectStaleMetadataGroups } from "@/lib/workspace-metadata-selectors";
|
|
38
|
+
|
|
39
|
+
const ENVELOPE_KIND = "growthub-workspace-metadata-graph-v1";
|
|
40
|
+
const ENVELOPE_VERSION = 1;
|
|
41
|
+
|
|
42
|
+
function emptyMetadataStore() {
|
|
43
|
+
return {
|
|
44
|
+
kind: "growthub-workspace-metadata-store-v1",
|
|
45
|
+
version: 1,
|
|
46
|
+
objects: [],
|
|
47
|
+
fields: [],
|
|
48
|
+
views: [],
|
|
49
|
+
filters: [],
|
|
50
|
+
sorts: [],
|
|
51
|
+
widgets: [],
|
|
52
|
+
dashboards: [],
|
|
53
|
+
workflows: [],
|
|
54
|
+
workflowNodes: [],
|
|
55
|
+
workflowActions: [],
|
|
56
|
+
runInputs: [],
|
|
57
|
+
agentHosts: [],
|
|
58
|
+
sandboxes: [],
|
|
59
|
+
integrations: [],
|
|
60
|
+
integrationEntities: [],
|
|
61
|
+
sourceRecords: [],
|
|
62
|
+
runs: [],
|
|
63
|
+
outputArtifacts: [],
|
|
64
|
+
workerKits: [],
|
|
65
|
+
pipelineHealth: [],
|
|
66
|
+
warnings: []
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function GET(request) {
|
|
71
|
+
const warnings = [];
|
|
72
|
+
|
|
73
|
+
let workspaceConfig = null;
|
|
74
|
+
try {
|
|
75
|
+
workspaceConfig = await readWorkspaceConfig();
|
|
76
|
+
} catch (error) {
|
|
77
|
+
warnings.push(`Failed to read workspace config: ${error?.message || "unknown error"}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let workspaceSourceRecords = {};
|
|
81
|
+
try {
|
|
82
|
+
workspaceSourceRecords = (await readWorkspaceSourceRecords()) || {};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
warnings.push(`Failed to read source records sidecar: ${error?.message || "unknown error"}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Defensive: helpers are designed to never throw on partial/unknown input,
|
|
88
|
+
// but the route must remain HTTP-200 even if an unexpected exception bubbles
|
|
89
|
+
// up (so the UI inspector and agents always get a typed envelope).
|
|
90
|
+
let metadataStore;
|
|
91
|
+
try {
|
|
92
|
+
metadataStore = buildWorkspaceMetadataStore({
|
|
93
|
+
workspaceConfig: workspaceConfig || {},
|
|
94
|
+
workspaceSourceRecords
|
|
95
|
+
});
|
|
96
|
+
warnings.push(...metadataStore.warnings);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
warnings.push(`Failed to build metadata store: ${error?.message || "unknown error"}`);
|
|
99
|
+
metadataStore = emptyMetadataStore();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let graph;
|
|
103
|
+
try {
|
|
104
|
+
graph = buildWorkspaceMetadataGraph(metadataStore);
|
|
105
|
+
warnings.push(...graph.warnings);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
warnings.push(`Failed to build metadata graph: ${error?.message || "unknown error"}`);
|
|
108
|
+
graph = { kind: "growthub-workspace-metadata-graph-v1", version: 1, nodes: [], edges: [], warnings: [] };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Optional stale-group selector via query params.
|
|
112
|
+
let staleGroups = [];
|
|
113
|
+
let staleReasons = [];
|
|
114
|
+
try {
|
|
115
|
+
const url = request && request.url ? new URL(request.url) : null;
|
|
116
|
+
const staleKind = url ? (url.searchParams.get("staleKind") || "").trim() : "";
|
|
117
|
+
const staleId = url ? (url.searchParams.get("staleId") || "").trim() : "";
|
|
118
|
+
if (staleKind && staleId) {
|
|
119
|
+
const result = selectStaleMetadataGroups(metadataStore, { kind: staleKind, id: staleId });
|
|
120
|
+
staleGroups = Array.isArray(result?.groups) ? result.groups : [];
|
|
121
|
+
staleReasons = Array.isArray(result?.reasons) ? result.reasons : [];
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
warnings.push(`Failed to compute stale groups: ${error?.message || "unknown error"}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return NextResponse.json({
|
|
128
|
+
kind: ENVELOPE_KIND,
|
|
129
|
+
version: ENVELOPE_VERSION,
|
|
130
|
+
authority: {
|
|
131
|
+
config: "growthub.config.json",
|
|
132
|
+
sourceRecords: "growthub.source-records.json",
|
|
133
|
+
readOnlyProjection: true
|
|
134
|
+
},
|
|
135
|
+
metadata: {
|
|
136
|
+
objects: metadataStore.objects,
|
|
137
|
+
fields: metadataStore.fields,
|
|
138
|
+
views: metadataStore.views,
|
|
139
|
+
filters: metadataStore.filters,
|
|
140
|
+
sorts: metadataStore.sorts,
|
|
141
|
+
widgets: metadataStore.widgets,
|
|
142
|
+
dashboards: metadataStore.dashboards,
|
|
143
|
+
workflows: metadataStore.workflows,
|
|
144
|
+
workflowNodes: metadataStore.workflowNodes,
|
|
145
|
+
workflowActions: metadataStore.workflowActions,
|
|
146
|
+
runInputs: metadataStore.runInputs,
|
|
147
|
+
agentHosts: metadataStore.agentHosts,
|
|
148
|
+
sandboxes: metadataStore.sandboxes,
|
|
149
|
+
integrations: metadataStore.integrations,
|
|
150
|
+
integrationEntities: metadataStore.integrationEntities,
|
|
151
|
+
sourceRecords: metadataStore.sourceRecords,
|
|
152
|
+
runs: metadataStore.runs,
|
|
153
|
+
outputArtifacts: metadataStore.outputArtifacts,
|
|
154
|
+
workerKits: metadataStore.workerKits,
|
|
155
|
+
pipelineHealth: metadataStore.pipelineHealth
|
|
156
|
+
},
|
|
157
|
+
graph: {
|
|
158
|
+
nodes: graph.nodes,
|
|
159
|
+
edges: graph.edges
|
|
160
|
+
},
|
|
161
|
+
stale: {
|
|
162
|
+
groups: staleGroups,
|
|
163
|
+
reasons: staleReasons
|
|
164
|
+
},
|
|
165
|
+
warnings,
|
|
166
|
+
selectors: {
|
|
167
|
+
// Manifest of selectors the route honours. Only `selectStaleMetadataGroups`
|
|
168
|
+
// is wired through HTTP (via `?staleKind=&staleId=`). The remaining
|
|
169
|
+
// selectors are exposed as importable helpers for server-side consumers
|
|
170
|
+
// and the read-only inspector; they are NOT toggled through query
|
|
171
|
+
// params in V1.
|
|
172
|
+
httpEnabled: ["selectStaleMetadataGroups"],
|
|
173
|
+
helperOnly: [
|
|
174
|
+
"selectWidgetRequiredFields",
|
|
175
|
+
"selectWorkflowNodeInputSchema",
|
|
176
|
+
"selectObjectFilterableFields",
|
|
177
|
+
"selectObjectSortableFields",
|
|
178
|
+
"selectRunLineage"
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export { GET };
|
|
@@ -392,6 +392,12 @@ function buildRunResponse({
|
|
|
392
392
|
base.runInputs = runInputs;
|
|
393
393
|
base.inputSummary = summarizeRunInputs(runInputs);
|
|
394
394
|
}
|
|
395
|
+
if (result && typeof result === "object" && result.swarm && typeof result.swarm === "object") {
|
|
396
|
+
base.swarm = result.swarm;
|
|
397
|
+
}
|
|
398
|
+
if (result && typeof result === "object" && Array.isArray(result.logTree)) {
|
|
399
|
+
base.logTree = result.logTree;
|
|
400
|
+
}
|
|
395
401
|
base.exports = {
|
|
396
402
|
available: ["download-json", "copy-output", "download-stdout", "download-stderr", "download-log-node"],
|
|
397
403
|
external: []
|
|
@@ -558,11 +564,28 @@ async function POST(request) {
|
|
|
558
564
|
workspaceConfig,
|
|
559
565
|
row: rowForRun,
|
|
560
566
|
timeoutMs,
|
|
561
|
-
runInputs: normalizedRunInputs
|
|
567
|
+
runInputs: normalizedRunInputs,
|
|
568
|
+
executionContext: {
|
|
569
|
+
runId,
|
|
570
|
+
ranAt,
|
|
571
|
+
runtime,
|
|
572
|
+
agentHost,
|
|
573
|
+
adapterId,
|
|
574
|
+
env,
|
|
575
|
+
envRefSlugs,
|
|
576
|
+
envRefsMissing,
|
|
577
|
+
envRefsResolved,
|
|
578
|
+
networkAllow,
|
|
579
|
+
allowList,
|
|
580
|
+
instructions,
|
|
581
|
+
command,
|
|
582
|
+
timeoutMs,
|
|
583
|
+
sandboxName: rowForRun.Name || name
|
|
584
|
+
}
|
|
562
585
|
});
|
|
563
586
|
if (graphResult !== null) {
|
|
564
587
|
result = graphResult;
|
|
565
|
-
effectiveAdapterId = "orchestration-graph";
|
|
588
|
+
effectiveAdapterId = String(graphResult?.adapterMeta?.adapter || "").trim() || "orchestration-graph";
|
|
566
589
|
}
|
|
567
590
|
}
|
|
568
591
|
|