@growthub/cli 0.14.10 → 0.14.11

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 (52) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/callback/route.js +35 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/failure/route.js +35 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/schedule/route.js +423 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/connect/route.js +78 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/credentials/route.js +276 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/products/[productId]/resources/route.js +173 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/products/sync/route.js +347 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/sync/route.js +293 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/provider/connect/route.js +7 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/provider/sync/route.js +7 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/sync/route.js +197 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/apps/route.js +1 -1
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +1 -49
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +3 -20
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-api-record/route.js +3 -20
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflow/publish/route.js +407 -290
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflows/[providerId]/route.js +209 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceAddOnsMarketplace.jsx +806 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +141 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +15 -3
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +42 -5
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +5 -1
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +86 -20
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ScheduleCockpit.jsx +363 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +8 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +322 -1
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +2 -2
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/add-ons/add-ons-client.jsx +197 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/add-ons/page.jsx +23 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/settings-shell.jsx +1 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +734 -61
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +15 -10
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +2 -7
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +2 -19
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +8 -4
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/schedule-cockpit-console.js +287 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/scheduler-orchestration.js +449 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-secrets.js +77 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-readiness.js +583 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-on-callback.js +63 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-on-scheduler.js +519 -0
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-ons.js +957 -0
  43. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +607 -63
  44. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +21 -0
  45. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-operator-auth.js +32 -0
  46. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/provider.png +0 -0
  47. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/qstash.png +0 -0
  48. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/redis.png +0 -0
  49. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/search.png +0 -0
  50. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/vector.png +0 -0
  51. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/scripts/scheduler-ingress-smoke.mjs +26 -0
  52. package/package.json +1 -1
@@ -0,0 +1,197 @@
1
+ import { NextResponse } from "next/server";
2
+ import { readWorkspaceConfig, writeWorkspaceConfig } from "@/lib/workspace-config";
3
+ import {
4
+ getUpstashProduct,
5
+ listUpstashProductReadiness,
6
+ UPSTASH_REGION_OPTIONS,
7
+ withUpstashProductRegistry
8
+ } from "@/lib/workspace-add-ons";
9
+ import { appendOutcomeReceipt } from "@/lib/workspace-outcome-receipts";
10
+ import { readEnvVar, resolveRequiredEnv } from "@/lib/server-secrets";
11
+
12
+ const PROBE_TIMEOUT_MS = 8000;
13
+
14
+ function jsonError(message, status = 400, extra = {}) {
15
+ return NextResponse.json({ error: message, ...extra }, { status });
16
+ }
17
+
18
+ function clean(value) {
19
+ return String(value == null ? "" : value).trim();
20
+ }
21
+
22
+ // Canonical concrete-key read — same contract as readiness + schedule runtime.
23
+ function envValue(key) {
24
+ return clean(readEnvVar(key, process.env)?.value || "");
25
+ }
26
+
27
+ function selectedQstashRegion(region) {
28
+ return UPSTASH_REGION_OPTIONS.find((option) => option.id === region) || UPSTASH_REGION_OPTIONS[0];
29
+ }
30
+
31
+ async function fetchWithTimeout(url, init = {}) {
32
+ const controller = new AbortController();
33
+ const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
34
+ try {
35
+ return await fetch(url, { ...init, signal: controller.signal, cache: "no-store" });
36
+ } finally {
37
+ clearTimeout(timeout);
38
+ }
39
+ }
40
+
41
+ async function readProbeText(response) {
42
+ try {
43
+ return clean(await response.text()).slice(0, 240);
44
+ } catch {
45
+ return "";
46
+ }
47
+ }
48
+
49
+ function safeUrl(baseUrl, path) {
50
+ const base = clean(baseUrl).replace(/\/+$/, "");
51
+ const suffix = clean(path).startsWith("/") ? clean(path) : `/${clean(path)}`;
52
+ return `${base}${suffix}`;
53
+ }
54
+
55
+ async function probeJsonPaths({ baseUrl, token, paths, label }) {
56
+ let last = null;
57
+ for (const path of paths) {
58
+ const url = safeUrl(baseUrl, path);
59
+ const response = await fetchWithTimeout(url, {
60
+ method: "GET",
61
+ headers: { authorization: `Bearer ${token}` },
62
+ });
63
+ const text = await readProbeText(response);
64
+ last = { status: response.status, path, text };
65
+ if (response.ok) {
66
+ return {
67
+ ok: true,
68
+ baseUrl,
69
+ testedAt: new Date().toISOString(),
70
+ proof: `${label} probe ${path} returned HTTP ${response.status}`,
71
+ summary: `${label} sync verified with a read-only REST probe (${path}).`,
72
+ };
73
+ }
74
+ }
75
+ const details = last ? `${last.path} returned HTTP ${last.status}` : "no endpoint returned";
76
+ return {
77
+ ok: false,
78
+ baseUrl,
79
+ testedAt: new Date().toISOString(),
80
+ proof: `${label} probe failed: ${details}`,
81
+ summary: `${label} REST probe failed: ${details}.`,
82
+ };
83
+ }
84
+
85
+ async function probeUpstashProduct(productId, region) {
86
+ const product = getUpstashProduct(productId);
87
+ if (!product) {
88
+ return { ok: false, status: 400, error: "unknown Upstash product" };
89
+ }
90
+ const readiness = listUpstashProductReadiness(process.env).find((item) => item.productId === product.productId);
91
+ const requiredEnv = resolveRequiredEnv(product.requiredEnv, process.env);
92
+ if (!readiness?.configured || !requiredEnv.ok) {
93
+ return {
94
+ ok: false,
95
+ status: 422,
96
+ error: `${product.label} provider credentials are not connected`,
97
+ missingEnv: requiredEnv.missing.length ? requiredEnv.missing : (readiness?.missingEnv || product.requiredEnv),
98
+ resolvedEnv: requiredEnv.resolvedKeys,
99
+ summary: `${product.label} provider credentials are not connected. Complete provider setup, then sync again.`,
100
+ };
101
+ }
102
+
103
+ const probe = product.probe || {};
104
+ if (!probe.baseUrlEnv || !probe.tokenEnv || !Array.isArray(probe.paths) || !probe.paths.length) {
105
+ return { ok: false, status: 400, error: "unsupported Upstash product probe" };
106
+ }
107
+ const regionOption = selectedQstashRegion(region);
108
+ const configuredUrl = envValue(probe.baseUrlEnv) || (probe.fallbackRegionBaseUrl ? regionOption.baseUrl : "");
109
+ const result = await probeJsonPaths({
110
+ baseUrl: configuredUrl,
111
+ token: envValue(probe.tokenEnv),
112
+ paths: probe.paths,
113
+ label: product.label,
114
+ });
115
+ return {
116
+ ...result,
117
+ resolvedEnv: requiredEnv.resolvedKeys,
118
+ };
119
+ }
120
+
121
+ async function POST(request) {
122
+ let body = {};
123
+ try {
124
+ body = await request.json();
125
+ } catch {
126
+ return jsonError("invalid json body", 400);
127
+ }
128
+
129
+ const productId = clean(body.productId || "upstash-qstash");
130
+ const region = clean(body.region || "us-east-1");
131
+ const plan = clean(body.plan || "free");
132
+ const product = getUpstashProduct(productId);
133
+ if (!product) return jsonError("unknown Upstash product", 400, { productId });
134
+
135
+ const syncResult = await probeUpstashProduct(product.productId, region);
136
+ if (!syncResult.ok) {
137
+ await appendOutcomeReceipt({
138
+ kind: "workspace-add-on-sync",
139
+ lane: "server-authoritative",
140
+ outcomeStatus: "blocked",
141
+ actor: "workspace-marketplace",
142
+ objectRefs: [{ objectId: "api-registry", objectType: "api-registry", rowName: product.label }],
143
+ summary: syncResult.summary || syncResult.error || `${product.label} sync failed`,
144
+ policyVerdict: { ok: false, violationCodes: syncResult.missingEnv?.length ? ["provider_product_not_connected"] : ["provider_probe_failed"] },
145
+ nextActions: syncResult.missingEnv?.length
146
+ ? [`Complete ${product.label} setup from the provider marketplace flow, then sync again.`]
147
+ : [`Open the ${product.label} provider console, verify the product connection, then retry sync.`]
148
+ });
149
+ return jsonError(syncResult.error || syncResult.summary || "Upstash sync failed", syncResult.status || 502, {
150
+ productId: product.productId,
151
+ missingEnv: syncResult.missingEnv || [],
152
+ sync: {
153
+ ok: false,
154
+ proof: syncResult.proof || "",
155
+ summary: syncResult.summary || "",
156
+ }
157
+ });
158
+ }
159
+
160
+ const currentConfig = await readWorkspaceConfig();
161
+ const nextConfig = withUpstashProductRegistry(currentConfig, {
162
+ productId: product.productId,
163
+ region,
164
+ plan,
165
+ syncResult,
166
+ });
167
+ const persisted = await writeWorkspaceConfig({ dataModel: nextConfig.dataModel });
168
+ const { receipt } = await appendOutcomeReceipt({
169
+ kind: "workspace-add-on-sync",
170
+ lane: "server-authoritative",
171
+ outcomeStatus: "published",
172
+ actor: "workspace-marketplace",
173
+ objectRefs: [{ objectId: "api-registry", objectType: "api-registry", rowName: product.label }],
174
+ changedFields: ["dataModel.api-registry"],
175
+ policyVerdict: { ok: true },
176
+ schemaVerdict: { ok: true },
177
+ summary: `${product.label} installed after provider sync probe.`,
178
+ nextActions: product.productId === "upstash-qstash"
179
+ ? ["Workflow Canvas can now bind QStash/Workflow from the installed product card."]
180
+ : ["Use this workspace add-on from the relevant data/retrieval surfaces."]
181
+ });
182
+
183
+ return NextResponse.json({
184
+ ok: true,
185
+ productId: product.productId,
186
+ workspaceConfig: persisted,
187
+ sync: {
188
+ ok: true,
189
+ proof: syncResult.proof,
190
+ summary: syncResult.summary,
191
+ testedAt: syncResult.testedAt,
192
+ },
193
+ receiptId: receipt.receiptId,
194
+ });
195
+ }
196
+
197
+ export { POST };
@@ -68,7 +68,7 @@ function safeRuntime(warnings) {
68
68
 
69
69
  // Mirrors the CLI probe (cli/src/commands/workspace-surface.ts) so detection
70
70
  // lives inside the artifact too — the bridge roadmap Item 4 called for.
71
- const KNOWN_APP_DIRS = ["apps/workspace"];
71
+ const KNOWN_APP_DIRS = ["apps/workspace", "apps/agency-portal", "apps/portal", "studio", "app", "src"];
72
72
 
73
73
  function detectFramework(absPath) {
74
74
  try {
@@ -35,11 +35,6 @@ import { readWorkspaceConfig, readWorkspaceSourceRecords } from "@/lib/workspace
35
35
  import { buildWorkspaceMetadataStore } from "@/lib/workspace-metadata-store";
36
36
  import { buildWorkspaceMetadataGraph } from "@/lib/workspace-metadata-graph";
37
37
  import { selectStaleMetadataGroups } from "@/lib/workspace-metadata-selectors";
38
- import { deriveBlastRadius } from "@/lib/workspace-metadata-impact";
39
- import { deriveStaleSurfaces } from "@/lib/workspace-stale-surfaces";
40
- import { deriveWorkflowImpact } from "@/lib/workspace-workflow-impact";
41
- import { deriveProvenanceLineage } from "@/lib/workspace-provenance-lineage";
42
- import { deriveAppReadiness } from "@/lib/workspace-app-readiness";
43
38
 
44
39
  const ENVELOPE_KIND = "growthub-workspace-metadata-graph-v1";
45
40
  const ENVELOPE_VERSION = 1;
@@ -116,17 +111,10 @@ async function GET(request) {
116
111
  // Optional stale-group selector via query params.
117
112
  let staleGroups = [];
118
113
  let staleReasons = [];
119
- // Parse all causal query params from one URL read.
120
- let impactId = "";
121
- let lineageId = "";
122
- let lineageDirection = "both";
123
114
  try {
124
115
  const url = request && request.url ? new URL(request.url) : null;
125
116
  const staleKind = url ? (url.searchParams.get("staleKind") || "").trim() : "";
126
117
  const staleId = url ? (url.searchParams.get("staleId") || "").trim() : "";
127
- impactId = url ? (url.searchParams.get("impactId") || "").trim() : "";
128
- lineageId = url ? (url.searchParams.get("lineageId") || "").trim() : "";
129
- lineageDirection = url ? (url.searchParams.get("lineageDirection") || "both").trim() : "both";
130
118
  if (staleKind && staleId) {
131
119
  const result = selectStaleMetadataGroups(metadataStore, { kind: staleKind, id: staleId });
132
120
  staleGroups = Array.isArray(result?.groups) ? result.groups : [];
@@ -136,30 +124,6 @@ async function GET(request) {
136
124
  warnings.push(`Failed to compute stale groups: ${error?.message || "unknown error"}`);
137
125
  }
138
126
 
139
- // Causal derivations over the same read-only graph (Mutation → Law →
140
- // Intelligence). All pure, all bounded, all secret-free. `staleSurfaces` is
141
- // the unconditional freshness baseline (timestamps already in the graph);
142
- // `impact` and `lineage` are computed on demand for one node.
143
- let staleSurfaces = null;
144
- let readiness = null;
145
- let impact = null;
146
- let lineage = null;
147
- try {
148
- staleSurfaces = deriveStaleSurfaces(graph);
149
- readiness = deriveAppReadiness(graph);
150
- if (impactId) {
151
- impact = {
152
- blastRadius: deriveBlastRadius(graph, impactId),
153
- workflowImpact: deriveWorkflowImpact(graph, impactId)
154
- };
155
- }
156
- if (lineageId) {
157
- lineage = deriveProvenanceLineage(graph, lineageId, { direction: lineageDirection });
158
- }
159
- } catch (error) {
160
- warnings.push(`Failed to compute causal derivations: ${error?.message || "unknown error"}`);
161
- }
162
-
163
127
  return NextResponse.json({
164
128
  kind: ENVELOPE_KIND,
165
129
  version: ENVELOPE_VERSION,
@@ -199,11 +163,6 @@ async function GET(request) {
199
163
  groups: staleGroups,
200
164
  reasons: staleReasons
201
165
  },
202
- // Causal intelligence layer — read-only derivations over `graph` above.
203
- staleSurfaces,
204
- readiness,
205
- ...(impact ? { impact } : {}),
206
- ...(lineage ? { lineage } : {}),
207
166
  warnings,
208
167
  selectors: {
209
168
  // Manifest of selectors the route honours. Only `selectStaleMetadataGroups`
@@ -211,14 +170,7 @@ async function GET(request) {
211
170
  // selectors are exposed as importable helpers for server-side consumers
212
171
  // and the read-only inspector; they are NOT toggled through query
213
172
  // params in V1.
214
- httpEnabled: [
215
- "selectStaleMetadataGroups",
216
- "deriveStaleSurfaces",
217
- "deriveBlastRadius",
218
- "deriveWorkflowImpact",
219
- "deriveProvenanceLineage",
220
- "deriveAppReadiness"
221
- ],
173
+ httpEnabled: ["selectStaleMetadataGroups"],
222
174
  helperOnly: [
223
175
  "selectWidgetRequiredFields",
224
176
  "selectWorkflowNodeInputSchema",
@@ -88,26 +88,9 @@ import {
88
88
  validateRunInputsEnvelope,
89
89
  summarizeRunInputs
90
90
  } from "@/lib/orchestration-run-inputs";
91
-
92
- function envKeyCandidates(ref) {
93
- const token = String(ref || "")
94
- .trim()
95
- .replace(/[^a-z0-9]+/gi, "_")
96
- .replace(/^_+|_+$/g, "")
97
- .toUpperCase();
98
- return Array.from(new Set([
99
- token,
100
- token ? `${token}_API_KEY` : "",
101
- token ? `${token}_TOKEN` : ""
102
- ].filter(Boolean)));
103
- }
104
-
105
- function readServerSecret(authRef) {
106
- for (const key of envKeyCandidates(authRef)) {
107
- if (process.env[key]) return { key, value: process.env[key] };
108
- }
109
- return null;
110
- }
91
+ // Single canonical secret resolver — shared with the serverless add-on lane so
92
+ // both run lanes resolve stored env tokens identically (no copy-pasted logic).
93
+ import { readServerSecret } from "@/lib/server-secrets";
111
94
 
112
95
  function coerceBoolean(value) {
113
96
  if (value === true || value === false) return value;
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { readWorkspaceConfig } from "@/lib/workspace-config";
3
+ import { readServerSecret } from "@/lib/server-secrets";
3
4
 
4
5
  const DEFAULT_TIMEOUT_MS = 15000;
5
6
 
@@ -18,25 +19,7 @@ function buildUrl(record) {
18
19
  return `${baseUrl.replace(/\/+$/, "")}/${endpoint.replace(/^\/+/, "")}`;
19
20
  }
20
21
 
21
- function envKeyCandidates(ref) {
22
- const token = String(ref || "")
23
- .trim()
24
- .replace(/[^a-z0-9]+/gi, "_")
25
- .replace(/^_+|_+$/g, "")
26
- .toUpperCase();
27
- return Array.from(new Set([
28
- token,
29
- token ? `${token}_API_KEY` : "",
30
- token ? `${token}_TOKEN` : "",
31
- ].filter(Boolean)));
32
- }
33
-
34
- function readServerSecret(authRef) {
35
- for (const key of envKeyCandidates(authRef)) {
36
- if (process.env[key]) return process.env[key];
37
- }
38
- return "";
39
- }
22
+ // Secret resolution uses the single canonical resolver from lib/server-secrets.js.
40
23
 
41
24
  function findRegistryRecord(workspaceConfig, registryId) {
42
25
  const id = String(registryId || "").trim();
@@ -86,7 +69,7 @@ async function POST(request) {
86
69
 
87
70
  const method = normalizeMethod(record.method);
88
71
  const authRef = record.authRef || record.integrationId || dataSourceRecord?.registryId;
89
- const secret = readServerSecret(authRef);
72
+ const secret = readServerSecret(authRef)?.value || "";
90
73
  const controller = new AbortController();
91
74
  const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
92
75