@growthub/cli 0.13.1 → 0.13.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 (33) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +24 -2
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +14 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +74 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +67 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +77 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +48 -3
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +123 -27
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +136 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +713 -92
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +224 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +32 -1
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +514 -9
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +8 -1
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +10 -7
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/RunSetupPanel.jsx +261 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +72 -7
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +778 -140
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +91 -14
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +35 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +15 -3
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +384 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +323 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +32 -3
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-eligibility.js +50 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-redaction.js +64 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +629 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-host-catalog.js +168 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +542 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +164 -7
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +11 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +111 -1
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +9 -0
  33. package/package.json +1 -1
@@ -265,10 +265,152 @@ function normalizeFieldSettings(fieldSettings, columns) {
265
265
  };
266
266
  }
267
267
 
268
- function deriveManualObjectTable(object) {
268
+ /**
269
+ * Resolve a sidecar source-records entry for a live-backed Data Model object.
270
+ *
271
+ * The sidecar (`growthub.source-records.json`) is keyed by `sourceId` — the
272
+ * canonical workspace-source-records key — and the live-backed object can
273
+ * carry that key in three places. We try them in safe fallback order so
274
+ * older configs continue to hydrate without any migration:
275
+ *
276
+ * 1. `object.id` ← refresh-sources writes use object.id as the key
277
+ * 2. `object.sourceId` ← explicit column on the live-backed row
278
+ * 3. `object.binding.sourceId`← canonical binding-level reference
279
+ *
280
+ * Returns `null` when no records are available, so callers fall back to the
281
+ * config-owned `object.rows[]`.
282
+ */
283
+ function findSourceRecordsForObject(object, sourceRecords) {
284
+ if (!sourceRecords || typeof sourceRecords !== "object" || Array.isArray(sourceRecords)) return null;
285
+ const candidates = [object?.id, object?.sourceId, object?.binding?.sourceId]
286
+ .map((value) => (typeof value === "string" ? value.trim() : ""))
287
+ .filter(Boolean);
288
+ for (const key of candidates) {
289
+ const entry = sourceRecords[key];
290
+ if (entry && Array.isArray(entry.records)) return { key, entry };
291
+ }
292
+ return null;
293
+ }
294
+
295
+ /**
296
+ * Infer a lightweight field type for a column from existing rows.
297
+ *
298
+ * This is a runtime projection — it does not persist anywhere and does not
299
+ * require a schema migration. The Chart Hydration Inspector and Twenty-style
300
+ * field-type-aware controls (date granularity, ratio mode, prefix/suffix)
301
+ * read it to decide which UI affordances to show.
302
+ *
303
+ * Returns one of: text | number | boolean | date | select | multi-value |
304
+ * relation-like | json.
305
+ *
306
+ * `existingTypeHints` is the per-table `fieldSettings.types` map — when
307
+ * present it wins over inference, so operators who have already declared a
308
+ * type on the Data Model object don't get overridden by the runtime guesser.
309
+ */
310
+ function inferFieldType(column, rows, existingTypeHints) {
311
+ const hint = existingTypeHints && typeof existingTypeHints === "object" ? existingTypeHints[column] : "";
312
+ if (typeof hint === "string" && hint.trim()) return hint.trim();
313
+ if (!Array.isArray(rows) || rows.length === 0) return "text";
314
+
315
+ const lower = String(column || "").toLowerCase();
316
+ if (/(^|_)(id|_id)$/.test(lower) || lower.endsWith("ref") || lower === "registryid") return "relation-like";
317
+ if (lower.includes("date") || lower.includes("_at") || lower === "created" || lower === "updated" || lower.endsWith("at")) return "date";
318
+
319
+ let numeric = 0;
320
+ let boolean = 0;
321
+ let date = 0;
322
+ let multi = 0;
323
+ let json = 0;
324
+ let nonEmpty = 0;
325
+ const distinct = new Set();
326
+ for (const row of rows) {
327
+ const value = row?.[column];
328
+ if (value === undefined || value === null || value === "") continue;
329
+ nonEmpty += 1;
330
+ if (typeof value === "boolean") boolean += 1;
331
+ if (typeof value === "number" && Number.isFinite(value)) numeric += 1;
332
+ if (typeof value === "string") {
333
+ const trimmed = value.trim();
334
+ const asNumber = Number(trimmed.replace(/,/g, ""));
335
+ if (Number.isFinite(asNumber) && /^-?\d+(\.\d+)?$/.test(trimmed.replace(/,/g, ""))) numeric += 1;
336
+ if (trimmed === "true" || trimmed === "false") boolean += 1;
337
+ if (!Number.isNaN(Date.parse(trimmed)) && /\d{4}/.test(trimmed)) date += 1;
338
+ if (trimmed.includes(",") && trimmed.split(",").length > 1) multi += 1;
339
+ if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) json += 1;
340
+ distinct.add(trimmed);
341
+ } else if (Array.isArray(value)) {
342
+ multi += 1;
343
+ } else if (typeof value === "object") {
344
+ json += 1;
345
+ }
346
+ }
347
+ if (!nonEmpty) return "text";
348
+ if (json / nonEmpty > 0.5) return "json";
349
+ if (date / nonEmpty > 0.6) return "date";
350
+ if (boolean / nonEmpty > 0.6) return "boolean";
351
+ if (numeric / nonEmpty > 0.6) return "number";
352
+ if (multi / nonEmpty > 0.5) return "multi-value";
353
+ // Low-cardinality string columns look like select fields.
354
+ if (nonEmpty >= 3 && distinct.size > 0 && distinct.size <= Math.max(2, Math.floor(nonEmpty / 2))) return "select";
355
+ return "text";
356
+ }
357
+
358
+ function buildFieldMetadata(columns, rows, existingTypeHints) {
359
+ return (Array.isArray(columns) ? columns : []).map((column) => {
360
+ const type = inferFieldType(column, rows, existingTypeHints);
361
+ return {
362
+ id: column,
363
+ label: column,
364
+ type,
365
+ isNumeric: type === "number",
366
+ isDate: type === "date",
367
+ isBoolean: type === "boolean",
368
+ isSelectLike: type === "select",
369
+ isMultiValue: type === "multi-value",
370
+ isRelationLike: type === "relation-like",
371
+ isJson: type === "json"
372
+ };
373
+ });
374
+ }
375
+
376
+ function deriveManualObjectTable(object, options = {}) {
269
377
  const columns = Array.isArray(object.columns) ? object.columns.filter(Boolean) : [];
270
- const rows = Array.isArray(object.rows) ? object.rows.filter((row) => row && typeof row === "object" && !Array.isArray(row)) : [];
378
+ const configRows = Array.isArray(object.rows)
379
+ ? object.rows.filter((row) => row && typeof row === "object" && !Array.isArray(row))
380
+ : [];
271
381
  const source = object.source || object.label || object.name || "Manual object";
382
+
383
+ let hydratedRows = configRows;
384
+ let hydratedColumns = columns;
385
+ let liveSource = null;
386
+ const isLiveBacked = object?.binding?.sourceStorage === "workspace-source-records";
387
+ if (isLiveBacked) {
388
+ const sourceRecords = options?.sourceRecords;
389
+ const match = findSourceRecordsForObject(object, sourceRecords);
390
+ if (match) {
391
+ const records = match.entry.records.filter((row) => row && typeof row === "object" && !Array.isArray(row));
392
+ hydratedRows = records;
393
+ if (!columns.length && records.length) {
394
+ const inferred = new Set();
395
+ records.forEach((row) => Object.keys(row).forEach((key) => inferred.add(key)));
396
+ hydratedColumns = Array.from(inferred);
397
+ }
398
+ liveSource = {
399
+ sourceRecordKey: match.key,
400
+ fetchedAt: typeof match.entry.fetchedAt === "string" ? match.entry.fetchedAt : null,
401
+ recordCount: Number.isFinite(match.entry.recordCount) ? match.entry.recordCount : records.length,
402
+ integrationId: typeof match.entry.integrationId === "string" ? match.entry.integrationId : null
403
+ };
404
+ }
405
+ }
406
+
407
+ const fieldSettings = normalizeFieldSettings(object.fieldSettings, hydratedColumns);
408
+ const sourceBadge = (() => {
409
+ if (isLiveBacked) return "live";
410
+ if (object?.objectType === "data-source") return "api";
411
+ if (object?.objectType === "api-registry") return "api";
412
+ return "manual";
413
+ })();
272
414
  return {
273
415
  id: `manual-object:${object.id || source}`,
274
416
  label: object.label || object.name || source,
@@ -276,15 +418,18 @@ function deriveManualObjectTable(object) {
276
418
  objectType: object.objectType || "custom",
277
419
  icon: object.icon || null,
278
420
  pickerHidden: Boolean(object.pickerHidden),
279
- columns,
280
- rows,
421
+ columns: hydratedColumns,
422
+ rows: hydratedRows,
281
423
  binding: object.binding || { mode: "manual", source: "Data Model" },
282
424
  relations: Array.isArray(object.relations) ? object.relations : [],
283
425
  mutable: true,
284
426
  storage: "manual-object",
285
427
  objectId: object.id,
286
428
  widgetRefs: [],
287
- fieldSettings: normalizeFieldSettings(object.fieldSettings, columns)
429
+ fieldSettings,
430
+ liveSource,
431
+ sourceBadge,
432
+ fieldMetadata: buildFieldMetadata(hydratedColumns, hydratedRows, fieldSettings?.types)
288
433
  };
289
434
  }
290
435
 
@@ -304,7 +449,19 @@ const HIDDEN_HELPER_OBJECT_IDS = new Set([
304
449
  "nav-folders",
305
450
  ]);
306
451
 
307
- function listWorkspaceDataModelTables(workspaceConfig) {
452
+ /**
453
+ * List the workspace Data Model tables — the union of manual `dataModel.objects[]`
454
+ * and the widget-derived tables surfaced for the Data Model browser.
455
+ *
456
+ * @param {object} workspaceConfig — the persisted `growthub.config.json` shape.
457
+ * @param {object} [options]
458
+ * @param {object} [options.sourceRecords] — the sidecar map read from
459
+ * `growthub.source-records.json`. Live-backed objects (binding.sourceStorage
460
+ * === "workspace-source-records") use this to hydrate runtime rows without
461
+ * mutating the persisted config. Pass `undefined` to disable hydration; the
462
+ * config-owned `object.rows[]` is the fallback in either case.
463
+ */
464
+ function listWorkspaceDataModelTables(workspaceConfig, options = {}) {
308
465
  const widgetEntries = listWidgetEntries(workspaceConfig);
309
466
  const refsByObjectId = widgetEntries.reduce((map, { widget, location }) => {
310
467
  const binding = widget?.config?.binding;
@@ -321,7 +478,7 @@ function listWorkspaceDataModelTables(workspaceConfig) {
321
478
  const manualObjects = normalizeManualObjects(workspaceConfig)
322
479
  .filter((object) => !HIDDEN_HELPER_OBJECT_IDS.has(object?.id))
323
480
  .map((object) => {
324
- const table = deriveManualObjectTable(object);
481
+ const table = deriveManualObjectTable(object, options);
325
482
  return { ...table, widgetRefs: refsByObjectId.get(object.id) || [] };
326
483
  });
327
484
  const widgetTables = widgetEntries
@@ -185,6 +185,17 @@ function buildStableSystemPrompt(intent) {
185
185
  `Known object types: ${KNOWN_OBJECT_TYPES.join(", ")}`,
186
186
  `PATCH allowlist (only these top-level keys can be mutated): ${PATCH_ALLOWLIST.join(", ")}`,
187
187
  "",
188
+ "## When configuring dashboard widgets",
189
+ "- Use Data Model objects as source authority.",
190
+ "- Bind widgets by objectId and source metadata.",
191
+ "- For charts, compute widget.config.values from rows.",
192
+ "- Never copy source rows into chart widget config.",
193
+ "- Never store secrets in widget config, Data Model rows, source records, browser state, localStorage, or exported templates.",
194
+ "- Use source records for live-backed data.",
195
+ "- Use filters, grouping, aggregation, style, and values projection as nested widget config only.",
196
+ "- Reset invalid axis, filter, group, and sort settings when source changes.",
197
+ "- Mark recomputed values as unsaved unless PATCH succeeds.",
198
+ "",
188
199
  "## Valid proposal types and their target patch field",
189
200
  WORKSPACE_HELPER_PROPOSAL_TYPES.map(
190
201
  (t) => ` ${t} → ${PROPOSAL_TYPE_TO_PATCH_FIELD[t]}`
@@ -46,7 +46,25 @@ const KNOWN_CHART_TYPES = ["bar-vertical", "bar-horizontal", "line", "pie", "sum
46
46
  const KNOWN_FILTER_OPERATORS = ["eq", "ne", "contains", "gt", "lt", "isEmpty", "isNotEmpty"];
47
47
  const KNOWN_FILTER_CONJUNCTIONS = ["and", "or"];
48
48
  const KNOWN_SORT_DIRECTIONS = ["asc", "desc"];
49
- const KNOWN_AGGREGATIONS = ["sum", "avg", "count", "min", "max"];
49
+ // Aggregation vocabulary kept in sync with `lib/workspace-chart-values.js`.
50
+ // V1 charts use the first five; the Twenty-style row-presence operations
51
+ // (countAll/countEmpty/countNotEmpty/countUnique, percentEmpty/percentNotEmpty)
52
+ // are valid both as `yAxis.aggregation` (legacy key) and `yAxis.operation`
53
+ // (preferred key going forward). The validator accepts both for back-compat.
54
+ const KNOWN_AGGREGATIONS = [
55
+ "sum",
56
+ "avg",
57
+ "count",
58
+ "countAll",
59
+ "countEmpty",
60
+ "countNotEmpty",
61
+ "countUnique",
62
+ "percentEmpty",
63
+ "percentNotEmpty",
64
+ "min",
65
+ "max"
66
+ ];
67
+ const KNOWN_DATE_GRANULARITIES = ["day", "week", "month", "quarter", "year"];
50
68
  const KNOWN_SANDBOX_RUNTIMES = ["python", "node", "bash"];
51
69
  /** Where execution is delegated: locally (process / agent-host CLI) or to a scheduler webhook (Supabase Edge, QStash, Vercel cron hitting your URL, etc.). */
52
70
  const KNOWN_SANDBOX_RUN_LOCALITY = ["local", "serverless"];
@@ -612,12 +630,26 @@ function validateChartAxis(axis, path, errors) {
612
630
  if (axis.aggregation !== undefined && !KNOWN_AGGREGATIONS.includes(axis.aggregation)) {
613
631
  errors.push(`${path}.aggregation must be one of ${KNOWN_AGGREGATIONS.join(", ")}`);
614
632
  }
633
+ // `operation` is the Twenty-style preferred key; accepted in addition to
634
+ // `aggregation` so older configs round-trip cleanly.
635
+ if (axis.operation !== undefined && !KNOWN_AGGREGATIONS.includes(axis.operation)) {
636
+ errors.push(`${path}.operation must be one of ${KNOWN_AGGREGATIONS.join(", ")}`);
637
+ }
615
638
  if (axis.groupBy !== undefined && typeof axis.groupBy !== "string") {
616
639
  errors.push(`${path}.groupBy must be a string`);
617
640
  }
618
641
  if (axis.omitZero !== undefined && typeof axis.omitZero !== "boolean") {
619
642
  errors.push(`${path}.omitZero must be a boolean`);
620
643
  }
644
+ if (axis.cumulative !== undefined && typeof axis.cumulative !== "boolean") {
645
+ errors.push(`${path}.cumulative must be a boolean`);
646
+ }
647
+ if (axis.splitMultiValueFields !== undefined && typeof axis.splitMultiValueFields !== "boolean") {
648
+ errors.push(`${path}.splitMultiValueFields must be a boolean`);
649
+ }
650
+ if (axis.dateGranularity !== undefined && !KNOWN_DATE_GRANULARITIES.includes(axis.dateGranularity)) {
651
+ errors.push(`${path}.dateGranularity must be one of ${KNOWN_DATE_GRANULARITIES.join(", ")}`);
652
+ }
621
653
  if (axis.min !== undefined && typeof axis.min !== "string" && typeof axis.min !== "number") {
622
654
  errors.push(`${path}.min must be a string or number`);
623
655
  }
@@ -635,12 +667,33 @@ function validateChartStyle(style, path, errors) {
635
667
  if (style.colors !== undefined && typeof style.colors !== "string") {
636
668
  errors.push(`${path}.colors must be a string`);
637
669
  }
670
+ if (style.manualColor !== undefined && typeof style.manualColor !== "string") {
671
+ errors.push(`${path}.manualColor must be a string`);
672
+ }
638
673
  if (style.axisName !== undefined && typeof style.axisName !== "string") {
639
674
  errors.push(`${path}.axisName must be a string`);
640
675
  }
641
676
  if (style.dataLabels !== undefined && typeof style.dataLabels !== "boolean") {
642
677
  errors.push(`${path}.dataLabels must be a boolean`);
643
678
  }
679
+ if (style.legend !== undefined && typeof style.legend !== "boolean") {
680
+ errors.push(`${path}.legend must be a boolean`);
681
+ }
682
+ if (style.stacked !== undefined && typeof style.stacked !== "boolean") {
683
+ errors.push(`${path}.stacked must be a boolean`);
684
+ }
685
+ if (style.compact !== undefined && typeof style.compact !== "boolean") {
686
+ errors.push(`${path}.compact must be a boolean`);
687
+ }
688
+ if (style.prefix !== undefined && typeof style.prefix !== "string") {
689
+ errors.push(`${path}.prefix must be a string`);
690
+ }
691
+ if (style.suffix !== undefined && typeof style.suffix !== "string") {
692
+ errors.push(`${path}.suffix must be a string`);
693
+ }
694
+ if (style.centerValue !== undefined && typeof style.centerValue !== "string") {
695
+ errors.push(`${path}.centerValue must be a string`);
696
+ }
644
697
  }
645
698
 
646
699
  function validateWidgetConfig(kind, config, path, errors) {
@@ -1003,6 +1056,63 @@ function validateSandboxEnvironmentRow(row, path, errors) {
1003
1056
  errors.push(`${path}.${traceField} must be a string when present`);
1004
1057
  }
1005
1058
  }
1059
+ // Sandbox Local Agent Auth Onboarding V1 — governance for the safe auth
1060
+ // metadata fields stamped by the auth helper / API routes.
1061
+ const KNOWN_AGENT_AUTH_STATUSES_INLINE = [
1062
+ "active",
1063
+ "reachable",
1064
+ "stale",
1065
+ "missing",
1066
+ "checking",
1067
+ "unknown"
1068
+ ];
1069
+ if (row.agentAuthStatus !== undefined && row.agentAuthStatus !== "" && row.agentAuthStatus !== null) {
1070
+ const authStatus = String(row.agentAuthStatus).trim().toLowerCase();
1071
+ if (!KNOWN_AGENT_AUTH_STATUSES_INLINE.includes(authStatus)) {
1072
+ errors.push(`${path}.agentAuthStatus must be one of ${KNOWN_AGENT_AUTH_STATUSES_INLINE.join(", ")}`);
1073
+ }
1074
+ }
1075
+ for (const authField of [
1076
+ "agentAuthProvider",
1077
+ "agentAuthLastChecked",
1078
+ "agentAuthLastMessage",
1079
+ "agentAuthLastLoginUrl"
1080
+ ]) {
1081
+ const value = row[authField];
1082
+ if (value !== undefined && value !== null && value !== "" && typeof value !== "string") {
1083
+ errors.push(`${path}.${authField} must be a string when present`);
1084
+ }
1085
+ }
1086
+ if (
1087
+ row.agentAuthLastExitCode !== undefined
1088
+ && row.agentAuthLastExitCode !== null
1089
+ && row.agentAuthLastExitCode !== ""
1090
+ ) {
1091
+ const code = Number(row.agentAuthLastExitCode);
1092
+ if (!Number.isFinite(code)) {
1093
+ errors.push(`${path}.agentAuthLastExitCode must be a finite number when present`);
1094
+ }
1095
+ }
1096
+ // Defensive: refuse to ever persist token-shaped field names on a sandbox
1097
+ // row. The helper only writes the SAFE_ROW_PATCH_FIELDS whitelist, but
1098
+ // this guard catches any out-of-band PATCH that tries to stash a secret.
1099
+ const FORBIDDEN_AUTH_ROW_FIELDS = [
1100
+ "token",
1101
+ "apiKey",
1102
+ "authToken",
1103
+ "accessToken",
1104
+ "refreshToken",
1105
+ "bearer",
1106
+ "password",
1107
+ "secret",
1108
+ "sessionKey",
1109
+ "claudeToken"
1110
+ ];
1111
+ for (const forbidden of FORBIDDEN_AUTH_ROW_FIELDS) {
1112
+ if (Object.prototype.hasOwnProperty.call(row, forbidden)) {
1113
+ errors.push(`${path}.${forbidden} is not allowed on a sandbox row — auth secrets must stay in the local CLI's own store`);
1114
+ }
1115
+ }
1006
1116
  }
1007
1117
 
1008
1118
  const NAV_FOLDERS_OBJECT_ID = "nav-folders";
@@ -88,9 +88,17 @@
88
88
  "apps/workspace/app/api/workspace/resolvers/route.js",
89
89
  "apps/workspace/app/api/workspace/sandbox-adapters/route.js",
90
90
  "apps/workspace/app/api/workspace/sandbox-run/route.js",
91
+ "apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js",
92
+ "apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js",
93
+ "apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js",
91
94
  "apps/workspace/app/api/settings/integrations/route.js",
92
95
  "apps/workspace/lib/workspace-schema.js",
93
96
  "apps/workspace/lib/workspace-config.js",
97
+ "apps/workspace/lib/workspace-chart-values.js",
98
+ "apps/workspace/lib/sandbox-agent-auth.js",
99
+ "apps/workspace/lib/sandbox-agent-auth-eligibility.js",
100
+ "apps/workspace/lib/sandbox-agent-auth-redaction.js",
101
+ "apps/workspace/lib/sandbox-agent-host-catalog.js",
94
102
  "apps/workspace/lib/domain/portal.js",
95
103
  "apps/workspace/lib/domain/integrations.js",
96
104
  "apps/workspace/lib/adapters/env.js",
@@ -115,6 +123,7 @@
115
123
  "apps/workspace/app/data-model/page.jsx",
116
124
  "apps/workspace/app/data-model/components/DataModelShell.jsx",
117
125
  "apps/workspace/app/data-model/components/HelperSidecar.jsx",
126
+ "apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx",
118
127
  "apps/workspace/app/views/[viewId]/page.jsx",
119
128
  "apps/workspace/lib/adapters/payments/index.js",
120
129
  "apps/workspace/lib/adapters/persistence/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growthub/cli",
3
- "version": "0.13.1",
3
+ "version": "0.13.4",
4
4
  "description": "CLI control plane for Growthub Local and Agent Workspace as Code: export, fork, inspect, operate, sync, and optionally activate governed AI workspaces.",
5
5
  "type": "module",
6
6
  "bin": {