@growthub/cli 0.13.6 → 0.13.8

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 (23) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +98 -34
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +1 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/swarm-condition/route.js +106 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +189 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceContributionGraph.jsx +119 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +357 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +488 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensWalkthrough.jsx +69 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +37 -2
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +37 -2
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +437 -26
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +44 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +592 -41
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-lens/page.jsx +76 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +148 -4
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +1559 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +3 -3
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +24 -8
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +82 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +8 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +4 -4
  22. package/dist/index.js +5224 -5225
  23. package/package.json +1 -1
@@ -0,0 +1,357 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { X } from "lucide-react";
5
+
6
+ const HELPER_SANDBOX_OBJECT_ID = "workspace-helper-sandbox";
7
+ const HELPER_HANDOFF_DISMISS_FLAG = "workspaceHelperHandoffComplete";
8
+
9
+ const HELPER_AGENT_CHOICES = [
10
+ { id: "codex_local", label: "Codex CLI (local)", body: "Recommended. Uses your local Codex CLI." },
11
+ { id: "claude_local", label: "Claude Code (local)", body: "Uses your local Claude Code session." },
12
+ { id: "cursor", label: "Cursor Agent (local)", body: "Uses Cursor Agent." },
13
+ { id: "gemini_local", label: "Gemini CLI (local)", body: "Uses Gemini CLI." },
14
+ { id: "opencode_local", label: "OpenCode (local)", body: "Uses OpenCode on this machine." },
15
+ { id: "pi_local", label: "Pi (local)", body: "Uses Pi on this machine." },
16
+ { id: "qwen_code_local", label: "Qwen Code (local)", body: "Uses Qwen Code on this machine." },
17
+ ];
18
+
19
+ const HELPER_EXECUTION_ADAPTERS = [
20
+ { id: "local-agent-host", label: "Local agent host (Paperclip thin adapter)" },
21
+ { id: "agent-host", label: "Agent host (Paperclip)" },
22
+ { id: "local-intelligence", label: "Local intelligence (OpenAI-compatible)" },
23
+ { id: "local-process", label: "Local process (default)" },
24
+ ];
25
+
26
+ function getHelperSandboxObject(config) {
27
+ const objects = Array.isArray(config?.dataModel?.objects) ? config.dataModel.objects : [];
28
+ return objects.find((o) => o?.id === HELPER_SANDBOX_OBJECT_ID && o?.objectType === "sandbox-environment") || null;
29
+ }
30
+
31
+ function getHelperSandboxRow(config) {
32
+ const helper = getHelperSandboxObject(config);
33
+ return Array.isArray(helper?.rows) ? helper.rows[0] : null;
34
+ }
35
+
36
+ function isHelperConfigured(config) {
37
+ const row = getHelperSandboxRow(config);
38
+ return Boolean(String(row?.adapter || "").trim() === "local-agent-host" && String(row?.agentHost || "").trim());
39
+ }
40
+
41
+ function readWorkspaceUiCacheFlag(config, key) {
42
+ const objects = Array.isArray(config?.dataModel?.objects) ? config.dataModel.objects : [];
43
+ const cache = objects.find((o) => o?.id === "workspace-ui-cache");
44
+ const row = Array.isArray(cache?.rows) ? cache.rows.find((r) => r?.id === "activation") : null;
45
+ return row?.[key];
46
+ }
47
+
48
+ function withWorkspaceUiCacheFlag(config, key, value) {
49
+ const dataModel = config?.dataModel && typeof config.dataModel === "object" ? config.dataModel : {};
50
+ const objects = Array.isArray(dataModel.objects) ? dataModel.objects : [];
51
+ const existing = objects.find((o) => o?.id === "workspace-ui-cache");
52
+ const cacheObject = existing || {
53
+ id: "workspace-ui-cache",
54
+ label: "Workspace UI Cache",
55
+ source: "Workspace UI Cache",
56
+ objectType: "custom",
57
+ icon: "Settings",
58
+ columns: ["id", key],
59
+ rows: [],
60
+ binding: { mode: "manual", source: "Workspace UI Cache" },
61
+ };
62
+ const columns = Array.from(new Set([...(Array.isArray(cacheObject.columns) ? cacheObject.columns : ["id"]), key]));
63
+ const rows = Array.isArray(cacheObject.rows) ? cacheObject.rows : [];
64
+ const hasRow = rows.some((r) => r?.id === "activation");
65
+ const nextRows = hasRow
66
+ ? rows.map((r) => (r?.id === "activation" ? { ...r, [key]: value } : r))
67
+ : [...rows, { id: "activation", [key]: value }];
68
+ const nextCache = { ...cacheObject, columns, rows: nextRows };
69
+ const nextObjects = existing
70
+ ? objects.map((o) => (o?.id === "workspace-ui-cache" ? nextCache : o))
71
+ : [...objects, nextCache];
72
+ return { ...config, dataModel: { ...dataModel, objects: nextObjects } };
73
+ }
74
+
75
+ function isHelperHandoffDismissed(config) {
76
+ const value = readWorkspaceUiCacheFlag(config, HELPER_HANDOFF_DISMISS_FLAG);
77
+ return value === true || String(value || "") === "true";
78
+ }
79
+
80
+ function upsertHelperSandbox(config, draft) {
81
+ const dataModel = config?.dataModel && typeof config.dataModel === "object" ? config.dataModel : {};
82
+ const objects = Array.isArray(dataModel.objects) ? dataModel.objects : [];
83
+ const COLUMNS = [
84
+ "Name", "lifecycleStatus", "runLocality", "runtime", "adapter", "agentHost", "intelligenceType",
85
+ "workspaceRead", "proposalApply", "sandboxRun", "networkPolicy", "instructions", "timeoutMs", "status",
86
+ ];
87
+ const row = {
88
+ Name: "workspace-helper",
89
+ lifecycleStatus: "live",
90
+ runLocality: draft.runLocality || "local",
91
+ runtime: draft.runtime || "node",
92
+ adapter: draft.adapter || "local-agent-host",
93
+ agentHost: draft.agentHost,
94
+ intelligenceType: "agent-host",
95
+ workspaceRead: draft.workspaceRead ? "enabled" : "disabled",
96
+ proposalApply: draft.proposalApply ? "approval-required" : "disabled",
97
+ sandboxRun: draft.sandboxRun ? "enabled" : "disabled",
98
+ networkPolicy: draft.networkPolicy || "workspace-only",
99
+ instructions: "Use the existing Workspace Helper widget to answer with workspace context and propose safe workspace changes through the governed helper apply flow.",
100
+ timeoutMs: String(draft.timeoutMs || "120000"),
101
+ status: "live",
102
+ };
103
+ const existing = getHelperSandboxObject(config);
104
+ const base = existing || {
105
+ id: HELPER_SANDBOX_OBJECT_ID,
106
+ label: "Workspace Helper Sandbox",
107
+ source: "Workspace Helper Sandbox",
108
+ objectType: "sandbox-environment",
109
+ icon: "Terminal",
110
+ columns: COLUMNS,
111
+ rows: [],
112
+ binding: { mode: "manual", source: "Workspace Helper Sandbox" },
113
+ };
114
+ const next = {
115
+ ...base,
116
+ columns: Array.from(new Set([...(Array.isArray(base.columns) ? base.columns : []), ...COLUMNS])),
117
+ rows: Array.isArray(base.rows) && base.rows.length > 0
118
+ ? base.rows.map((r, i) => (i === 0 ? { ...r, ...row } : r))
119
+ : [row],
120
+ };
121
+ const nextObjects = existing
122
+ ? objects.map((o) => (o?.id === HELPER_SANDBOX_OBJECT_ID ? next : o))
123
+ : [...objects, next];
124
+ return withWorkspaceUiCacheFlag(
125
+ { ...config, dataModel: { ...dataModel, objects: nextObjects } },
126
+ HELPER_HANDOFF_DISMISS_FLAG,
127
+ true,
128
+ );
129
+ }
130
+
131
+ function WorkspaceHelperSetupModal({ workspaceConfig, open, onClose, onSaved }) {
132
+ const helperRow = getHelperSandboxRow(workspaceConfig);
133
+ const [step, setStep] = useState(1);
134
+ const [draft, setDraft] = useState({
135
+ agentHost: helperRow?.agentHost || "codex_local",
136
+ runLocality: helperRow?.runLocality || "local",
137
+ runtime: helperRow?.runtime || "node",
138
+ adapter: helperRow?.adapter || "local-agent-host",
139
+ timeoutMs: helperRow?.timeoutMs || "120000",
140
+ networkPolicy: helperRow?.networkPolicy || "workspace-only",
141
+ workspaceRead: helperRow?.workspaceRead !== "disabled",
142
+ proposalApply: helperRow?.proposalApply !== "disabled",
143
+ sandboxRun: helperRow?.sandboxRun !== "disabled",
144
+ });
145
+ const [saving, setSaving] = useState(false);
146
+ const [error, setError] = useState("");
147
+
148
+ useEffect(() => {
149
+ if (!open) return;
150
+ setStep(1);
151
+ setDraft({
152
+ agentHost: helperRow?.agentHost || "codex_local",
153
+ runLocality: helperRow?.runLocality || "local",
154
+ runtime: helperRow?.runtime || "node",
155
+ adapter: helperRow?.adapter || "local-agent-host",
156
+ timeoutMs: helperRow?.timeoutMs || "120000",
157
+ networkPolicy: helperRow?.networkPolicy || "workspace-only",
158
+ workspaceRead: helperRow?.workspaceRead !== "disabled",
159
+ proposalApply: helperRow?.proposalApply !== "disabled",
160
+ sandboxRun: helperRow?.sandboxRun !== "disabled",
161
+ });
162
+ setSaving(false);
163
+ setError("");
164
+ }, [open, helperRow?.adapter, helperRow?.agentHost, helperRow?.networkPolicy, helperRow?.proposalApply, helperRow?.runLocality, helperRow?.runtime, helperRow?.sandboxRun, helperRow?.timeoutMs, helperRow?.workspaceRead]);
165
+
166
+ if (!open) return null;
167
+
168
+ async function saveSetup() {
169
+ if (saving) return;
170
+ setSaving(true);
171
+ setError("");
172
+ try {
173
+ const next = upsertHelperSandbox(workspaceConfig || {}, draft);
174
+ const res = await fetch("/api/workspace", {
175
+ method: "PATCH",
176
+ headers: { "content-type": "application/json" },
177
+ body: JSON.stringify({ dataModel: next.dataModel }),
178
+ });
179
+ const body = await res.json().catch(() => ({}));
180
+ if (!res.ok) throw new Error(body?.error || "Could not save helper setup");
181
+ onSaved?.(body?.workspaceConfig || next);
182
+ } catch (err) {
183
+ setError(err?.message || "Could not save helper setup");
184
+ setSaving(false);
185
+ }
186
+ }
187
+
188
+ return (
189
+ <div className="workspace-helper-setup-modal-backdrop" role="presentation">
190
+ <div className="workspace-helper-setup-modal" role="dialog" aria-modal="true" aria-label="Set up workspace helper">
191
+ <button type="button" className="workspace-helper-setup-modal-close" onClick={onClose} aria-label="Close setup">
192
+ <X size={16} aria-hidden="true" />
193
+ </button>
194
+ <div className="workspace-helper-setup-breadcrumbs" aria-label="Setup steps">
195
+ <span className={step === 1 ? "active" : ""}>1. Helper</span>
196
+ <span className={step === 2 ? "active" : ""}>2. Agent</span>
197
+ <span className={step === 3 ? "active" : ""}>3. Start</span>
198
+ </div>
199
+ {step === 1 ? (
200
+ <section className="workspace-helper-setup-step">
201
+ <p className="workspace-helper-setup-eyebrow">Helper setup</p>
202
+ <h2>Make the helper the workspace operator</h2>
203
+ <p>The helper uses one sandbox config and the same widget you already use.</p>
204
+ <div className="workspace-helper-setup-card-grid">
205
+ <label className="workspace-helper-setup-toggle">
206
+ <input
207
+ type="checkbox"
208
+ checked={draft.workspaceRead}
209
+ onChange={(e) => setDraft((d) => ({ ...d, workspaceRead: e.target.checked }))}
210
+ />
211
+ <span><strong>Workspace context</strong><small>Read config, lenses, objects, and receipts.</small></span>
212
+ </label>
213
+ <label className="workspace-helper-setup-toggle">
214
+ <input
215
+ type="checkbox"
216
+ checked={draft.proposalApply}
217
+ onChange={(e) => setDraft((d) => ({ ...d, proposalApply: e.target.checked }))}
218
+ />
219
+ <span><strong>Approve applies</strong><small>Draft changes first; user approves writes.</small></span>
220
+ </label>
221
+ <label className="workspace-helper-setup-toggle">
222
+ <input
223
+ type="checkbox"
224
+ checked={draft.sandboxRun}
225
+ onChange={(e) => setDraft((d) => ({ ...d, sandboxRun: e.target.checked }))}
226
+ />
227
+ <span><strong>Sandbox runs</strong><small>Use the governed sandbox-run surface.</small></span>
228
+ </label>
229
+ </div>
230
+ </section>
231
+ ) : step === 2 ? (
232
+ <section className="workspace-helper-setup-step">
233
+ <p className="workspace-helper-setup-eyebrow">Agent</p>
234
+ <h2>Configure the helper sandbox</h2>
235
+ <div className="workspace-helper-setup-radio-group" aria-label="Where it runs">
236
+ <span>Where it runs</span>
237
+ <label>
238
+ <input
239
+ type="radio"
240
+ name="helper-run-locality"
241
+ checked={draft.runLocality === "local"}
242
+ onChange={() => setDraft((d) => ({ ...d, runLocality: "local" }))}
243
+ />
244
+ local
245
+ </label>
246
+ <label>
247
+ <input
248
+ type="radio"
249
+ name="helper-run-locality"
250
+ checked={draft.runLocality === "serverless"}
251
+ onChange={() => setDraft((d) => ({ ...d, runLocality: "serverless" }))}
252
+ />
253
+ serverless
254
+ </label>
255
+ <small>Local uses process sandbox or Paperclip agent host on this machine. Serverless delegates to an API Registry URL.</small>
256
+ </div>
257
+ <div className="workspace-helper-setup-field-stack">
258
+ <label>
259
+ Execution adapter
260
+ <select
261
+ value={draft.adapter}
262
+ onChange={(e) => setDraft((d) => ({ ...d, adapter: e.target.value }))}
263
+ >
264
+ {HELPER_EXECUTION_ADAPTERS.map((adapter) => (
265
+ <option key={adapter.id} value={adapter.id}>{adapter.label}</option>
266
+ ))}
267
+ </select>
268
+ </label>
269
+ <label>
270
+ Agent host (Paperclip)
271
+ <select
272
+ value={draft.agentHost}
273
+ onChange={(e) => setDraft((d) => ({ ...d, agentHost: e.target.value }))}
274
+ >
275
+ {HELPER_AGENT_CHOICES.map((choice) => (
276
+ <option key={choice.id} value={choice.id}>{choice.label}</option>
277
+ ))}
278
+ </select>
279
+ </label>
280
+ <div className="workspace-helper-setup-two-col">
281
+ <label>
282
+ Runtime
283
+ <select
284
+ value={draft.runtime}
285
+ onChange={(e) => setDraft((d) => ({ ...d, runtime: e.target.value }))}
286
+ >
287
+ <option value="node">node</option>
288
+ <option value="shell">shell</option>
289
+ </select>
290
+ </label>
291
+ <label>
292
+ Timeout
293
+ <select
294
+ value={draft.timeoutMs}
295
+ onChange={(e) => setDraft((d) => ({ ...d, timeoutMs: e.target.value }))}
296
+ >
297
+ <option value="60000">1 minute</option>
298
+ <option value="120000">2 minutes</option>
299
+ <option value="300000">5 minutes</option>
300
+ </select>
301
+ </label>
302
+ <label>
303
+ Network
304
+ <select
305
+ value={draft.networkPolicy}
306
+ onChange={(e) => setDraft((d) => ({ ...d, networkPolicy: e.target.value }))}
307
+ >
308
+ <option value="workspace-only">Workspace only</option>
309
+ <option value="local-network">Local network</option>
310
+ <option value="egress-allowed">Allow egress</option>
311
+ </select>
312
+ </label>
313
+ </div>
314
+ </div>
315
+ </section>
316
+ ) : (
317
+ <section className="workspace-helper-setup-step">
318
+ <p className="workspace-helper-setup-eyebrow">Start</p>
319
+ <h2>Save and open helper</h2>
320
+ <dl className="workspace-helper-setup-review">
321
+ <div><dt>Helper</dt><dd>Workspace Helper widget</dd></div>
322
+ <div><dt>Sandbox</dt><dd>workspace-helper-sandbox</dd></div>
323
+ <div><dt>Adapter</dt><dd>{draft.adapter}</dd></div>
324
+ <div><dt>Agent host</dt><dd>{draft.agentHost}</dd></div>
325
+ <div><dt>Access</dt><dd>{draft.workspaceRead ? "workspace context" : "no workspace context"} · {draft.proposalApply ? "approve applies" : "no applies"} · {draft.sandboxRun ? "sandbox runs" : "no runs"}</dd></div>
326
+ <div><dt>Runtime</dt><dd>{draft.runLocality} · {draft.runtime} · {draft.networkPolicy}</dd></div>
327
+ </dl>
328
+ {error ? <p className="workspace-helper-setup-error" role="alert">{error}</p> : null}
329
+ </section>
330
+ )}
331
+ <div className="workspace-helper-setup-actions">
332
+ <button type="button" onClick={() => step === 1 ? onClose?.() : setStep((s) => s - 1)}>
333
+ {step === 1 ? "Cancel" : "Back"}
334
+ </button>
335
+ {step < 3 ? (
336
+ <button type="button" className="primary" onClick={() => setStep((s) => s + 1)}>Next</button>
337
+ ) : (
338
+ <button type="button" className="primary" onClick={saveSetup} disabled={saving || !draft.agentHost}>
339
+ {saving ? "Saving..." : "Save & open helper"}
340
+ </button>
341
+ )}
342
+ </div>
343
+ </div>
344
+ </div>
345
+ );
346
+ }
347
+
348
+ export {
349
+ HELPER_SANDBOX_OBJECT_ID,
350
+ HELPER_HANDOFF_DISMISS_FLAG,
351
+ HELPER_AGENT_CHOICES,
352
+ getHelperSandboxRow,
353
+ isHelperHandoffDismissed,
354
+ isHelperConfigured,
355
+ upsertHelperSandbox,
356
+ WorkspaceHelperSetupModal,
357
+ };