@growthub/cli 0.9.9 → 0.9.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 (30) hide show
  1. package/README.md +1 -1
  2. package/assets/worker-kits/creative-strategist-v1/kit.json +5 -2
  3. package/assets/worker-kits/growthub-agency-portal-starter-v1/kit.json +4 -1
  4. package/assets/worker-kits/growthub-ai-website-cloner-v1/kit.json +6 -3
  5. package/assets/worker-kits/growthub-creative-video-pipeline-v1/kit.json +4 -1
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +4 -4
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integration-entities/route.js +50 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +980 -1
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +5 -2
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +4 -5
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +1686 -68
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/growthub-connection-normalizer.js +12 -16
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/index.js +61 -11
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/domain/integrations.js +31 -1
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +236 -9
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +10 -64
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +5 -2
  19. package/assets/worker-kits/growthub-email-marketing-v1/kit.json +5 -2
  20. package/assets/worker-kits/growthub-geo-seo-v1/kit.json +5 -2
  21. package/assets/worker-kits/growthub-hyperframes-studio-v1/kit.json +5 -2
  22. package/assets/worker-kits/growthub-marketing-skills-v1/kit.json +6 -3
  23. package/assets/worker-kits/growthub-open-higgsfield-studio-v1/kit.json +5 -2
  24. package/assets/worker-kits/growthub-open-montage-studio-v1/kit.json +6 -3
  25. package/assets/worker-kits/growthub-postiz-social-v1/kit.json +5 -2
  26. package/assets/worker-kits/growthub-twenty-crm-v1/kit.json +6 -3
  27. package/assets/worker-kits/growthub-video-use-studio-v1/kit.json +5 -2
  28. package/assets/worker-kits/growthub-zernio-social-v1/kit.json +5 -2
  29. package/dist/index.js +1750 -433
  30. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6436,9 +6436,9 @@ async function runDatabaseBackup(opts) {
6436
6436
  WHERE n.nspname = ${schema_name} AND t.relname = ${tablename} AND c.contype = 'p'
6437
6437
  GROUP BY c.conname
6438
6438
  `;
6439
- for (const p39 of pk) {
6440
- const cols = p39.column_names.map((c) => `"${c}"`).join(", ");
6441
- colDefs.push(` CONSTRAINT "${p39.constraint_name}" PRIMARY KEY (${cols})`);
6439
+ for (const p42 of pk) {
6440
+ const cols = p42.column_names.map((c) => `"${c}"`).join(", ");
6441
+ colDefs.push(` CONSTRAINT "${p42.constraint_name}" PRIMARY KEY (${cols})`);
6442
6442
  }
6443
6443
  emit(`CREATE TABLE ${qualifiedTableName} (`);
6444
6444
  emit(colDefs.join(",\n"));
@@ -8182,8 +8182,8 @@ var init_onboard = __esm({
8182
8182
 
8183
8183
  // src/client/http.ts
8184
8184
  import { URL as URL2 } from "node:url";
8185
- function buildUrl(apiBase, path86) {
8186
- const normalizedPath = path86.startsWith("/") ? path86 : `/${path86}`;
8185
+ function buildUrl(apiBase, path91) {
8186
+ const normalizedPath = path91.startsWith("/") ? path91 : `/${path91}`;
8187
8187
  const [pathname, query] = normalizedPath.split("?");
8188
8188
  const url = new URL2(apiBase);
8189
8189
  url.pathname = `${url.pathname.replace(/\/+$/, "")}${pathname}`;
@@ -8245,26 +8245,26 @@ var init_http = __esm({
8245
8245
  this.runId = opts.runId?.trim() || void 0;
8246
8246
  this.userId = opts.userId?.trim() || void 0;
8247
8247
  }
8248
- get(path86, opts) {
8249
- return this.request(path86, { method: "GET" }, opts);
8248
+ get(path91, opts) {
8249
+ return this.request(path91, { method: "GET" }, opts);
8250
8250
  }
8251
- post(path86, body, opts) {
8252
- return this.request(path86, {
8251
+ post(path91, body, opts) {
8252
+ return this.request(path91, {
8253
8253
  method: "POST",
8254
8254
  body: body === void 0 ? void 0 : JSON.stringify(body)
8255
8255
  }, opts);
8256
8256
  }
8257
- patch(path86, body, opts) {
8258
- return this.request(path86, {
8257
+ patch(path91, body, opts) {
8258
+ return this.request(path91, {
8259
8259
  method: "PATCH",
8260
8260
  body: body === void 0 ? void 0 : JSON.stringify(body)
8261
8261
  }, opts);
8262
8262
  }
8263
- delete(path86, opts) {
8264
- return this.request(path86, { method: "DELETE" }, opts);
8263
+ delete(path91, opts) {
8264
+ return this.request(path91, { method: "DELETE" }, opts);
8265
8265
  }
8266
- async request(path86, init, opts) {
8267
- const url = buildUrl(this.apiBase, path86);
8266
+ async request(path91, init, opts) {
8267
+ const url = buildUrl(this.apiBase, path91);
8268
8268
  const headers = {
8269
8269
  accept: "application/json",
8270
8270
  ...toStringRecord(init.headers)
@@ -9297,10 +9297,10 @@ var init_kit_forks_home = __esm({
9297
9297
  import fs19 from "node:fs";
9298
9298
  import path26 from "node:path";
9299
9299
  function readIndex() {
9300
- const p39 = resolveKitForksIndexPath();
9301
- if (!fs19.existsSync(p39)) return { version: 1, entries: [] };
9300
+ const p42 = resolveKitForksIndexPath();
9301
+ if (!fs19.existsSync(p42)) return { version: 1, entries: [] };
9302
9302
  try {
9303
- const parsed = JSON.parse(fs19.readFileSync(p39, "utf8"));
9303
+ const parsed = JSON.parse(fs19.readFileSync(p42, "utf8"));
9304
9304
  if (!parsed || !Array.isArray(parsed.entries)) return { version: 1, entries: [] };
9305
9305
  return parsed;
9306
9306
  } catch {
@@ -9308,9 +9308,9 @@ function readIndex() {
9308
9308
  }
9309
9309
  }
9310
9310
  function writeIndex(index51) {
9311
- const p39 = resolveKitForksIndexPath();
9312
- fs19.mkdirSync(path26.dirname(p39), { recursive: true });
9313
- fs19.writeFileSync(p39, JSON.stringify(index51, null, 2) + "\n", "utf8");
9311
+ const p42 = resolveKitForksIndexPath();
9312
+ fs19.mkdirSync(path26.dirname(p42), { recursive: true });
9313
+ fs19.writeFileSync(p42, JSON.stringify(index51, null, 2) + "\n", "utf8");
9314
9314
  }
9315
9315
  function upsertIndexEntry(entry) {
9316
9316
  const index51 = readIndex();
@@ -9336,10 +9336,10 @@ function generateForkId(forkPath, kitId) {
9336
9336
  return `${base}-${suffix}`;
9337
9337
  }
9338
9338
  function readForkJson(forkPath) {
9339
- const p39 = resolveInForkRegistrationPath(forkPath);
9340
- if (!fs19.existsSync(p39)) return null;
9339
+ const p42 = resolveInForkRegistrationPath(forkPath);
9340
+ if (!fs19.existsSync(p42)) return null;
9341
9341
  try {
9342
- return JSON.parse(fs19.readFileSync(p39, "utf8"));
9342
+ return JSON.parse(fs19.readFileSync(p42, "utf8"));
9343
9343
  } catch {
9344
9344
  return null;
9345
9345
  }
@@ -9446,20 +9446,20 @@ function resolvePolicyPath(forkPath) {
9446
9446
  return path27.resolve(resolveInForkStateDir(forkPath), "policy.json");
9447
9447
  }
9448
9448
  function readKitForkPolicy(forkPath) {
9449
- const p39 = resolvePolicyPath(forkPath);
9450
- if (!fs20.existsSync(p39)) return makeDefaultKitForkPolicy();
9449
+ const p42 = resolvePolicyPath(forkPath);
9450
+ if (!fs20.existsSync(p42)) return makeDefaultKitForkPolicy();
9451
9451
  try {
9452
- const parsed = JSON.parse(fs20.readFileSync(p39, "utf8"));
9452
+ const parsed = JSON.parse(fs20.readFileSync(p42, "utf8"));
9453
9453
  return { ...makeDefaultKitForkPolicy(), ...parsed, version: 1 };
9454
9454
  } catch {
9455
9455
  return makeDefaultKitForkPolicy();
9456
9456
  }
9457
9457
  }
9458
9458
  function writeKitForkPolicy(forkPath, policy) {
9459
- const p39 = resolvePolicyPath(forkPath);
9460
- fs20.mkdirSync(path27.dirname(p39), { recursive: true });
9459
+ const p42 = resolvePolicyPath(forkPath);
9460
+ fs20.mkdirSync(path27.dirname(p42), { recursive: true });
9461
9461
  const body = { ...policy, version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
9462
- fs20.writeFileSync(p39, JSON.stringify(body, null, 2) + "\n", "utf8");
9462
+ fs20.writeFileSync(p42, JSON.stringify(body, null, 2) + "\n", "utf8");
9463
9463
  }
9464
9464
  function matchesAnyPrefix(targetPath, patterns) {
9465
9465
  const normalized = targetPath.replace(/^\/+|\/+$/g, "");
@@ -9491,15 +9491,15 @@ function appendKitForkTraceEvent(forkPath, event) {
9491
9491
  ...event,
9492
9492
  timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
9493
9493
  };
9494
- const p39 = resolveTracePath(forkPath);
9495
- fs21.mkdirSync(path28.dirname(p39), { recursive: true });
9496
- fs21.appendFileSync(p39, JSON.stringify(full) + "\n", "utf8");
9494
+ const p42 = resolveTracePath(forkPath);
9495
+ fs21.mkdirSync(path28.dirname(p42), { recursive: true });
9496
+ fs21.appendFileSync(p42, JSON.stringify(full) + "\n", "utf8");
9497
9497
  return full;
9498
9498
  }
9499
9499
  function readKitForkTrace(forkPath) {
9500
- const p39 = resolveTracePath(forkPath);
9501
- if (!fs21.existsSync(p39)) return [];
9502
- const raw = fs21.readFileSync(p39, "utf8").trim();
9500
+ const p42 = resolveTracePath(forkPath);
9501
+ if (!fs21.existsSync(p42)) return [];
9502
+ const raw = fs21.readFileSync(p42, "utf8").trim();
9503
9503
  if (!raw) return [];
9504
9504
  const events = [];
9505
9505
  for (const line of raw.split("\n")) {
@@ -9654,10 +9654,10 @@ function atomicWrite(filePath, body) {
9654
9654
  }
9655
9655
  }
9656
9656
  function readGithubToken() {
9657
- const p39 = resolveGithubTokenPath();
9658
- if (!fs24.existsSync(p39)) return null;
9657
+ const p42 = resolveGithubTokenPath();
9658
+ if (!fs24.existsSync(p42)) return null;
9659
9659
  try {
9660
- const parsed = JSON.parse(fs24.readFileSync(p39, "utf8"));
9660
+ const parsed = JSON.parse(fs24.readFileSync(p42, "utf8"));
9661
9661
  if (!parsed?.accessToken) return null;
9662
9662
  return parsed;
9663
9663
  } catch {
@@ -9669,8 +9669,8 @@ function writeGithubToken(token) {
9669
9669
  atomicWrite(resolveGithubTokenPath(), JSON.stringify(token, null, 2) + "\n");
9670
9670
  }
9671
9671
  function clearGithubToken() {
9672
- const p39 = resolveGithubTokenPath();
9673
- if (fs24.existsSync(p39)) fs24.rmSync(p39, { force: true });
9672
+ const p42 = resolveGithubTokenPath();
9673
+ if (fs24.existsSync(p42)) fs24.rmSync(p42, { force: true });
9674
9674
  }
9675
9675
  function isGithubTokenExpired(token) {
9676
9676
  if (!token) return true;
@@ -9680,10 +9680,10 @@ function isGithubTokenExpired(token) {
9680
9680
  return Date.now() >= expiresAtMs;
9681
9681
  }
9682
9682
  function readGithubProfile() {
9683
- const p39 = resolveGithubProfilePath();
9684
- if (!fs24.existsSync(p39)) return null;
9683
+ const p42 = resolveGithubProfilePath();
9684
+ if (!fs24.existsSync(p42)) return null;
9685
9685
  try {
9686
- return JSON.parse(fs24.readFileSync(p39, "utf8"));
9686
+ return JSON.parse(fs24.readFileSync(p42, "utf8"));
9687
9687
  } catch {
9688
9688
  return null;
9689
9689
  }
@@ -9693,8 +9693,8 @@ function writeGithubProfile(profile) {
9693
9693
  atomicWrite(resolveGithubProfilePath(), JSON.stringify(profile, null, 2) + "\n");
9694
9694
  }
9695
9695
  function clearGithubProfile() {
9696
- const p39 = resolveGithubProfilePath();
9697
- if (fs24.existsSync(p39)) fs24.rmSync(p39, { force: true });
9696
+ const p42 = resolveGithubProfilePath();
9697
+ if (fs24.existsSync(p42)) fs24.rmSync(p42, { force: true });
9698
9698
  }
9699
9699
  function describeGithubTokenPath() {
9700
9700
  return resolveGithubTokenPath();
@@ -9729,9 +9729,9 @@ async function fetchHostedIntegrations(session) {
9729
9729
  }
9730
9730
  async function fetchHostedIntegrationCredential(session, providerId) {
9731
9731
  const client = toApiClient2(session);
9732
- const path86 = `${DEFAULT_INTEGRATION_CREDENTIAL_PATH}&provider=${encodeURIComponent(providerId)}`;
9732
+ const path91 = `${DEFAULT_INTEGRATION_CREDENTIAL_PATH}&provider=${encodeURIComponent(providerId)}`;
9733
9733
  try {
9734
- return await client.get(path86, { ignoreNotFound: true });
9734
+ return await client.get(path91, { ignoreNotFound: true });
9735
9735
  } catch (err) {
9736
9736
  if (err instanceof ApiRequestError && (err.status === 404 || err.status === 501)) {
9737
9737
  throw new HostedEndpointUnavailableError(err.status, err.message);
@@ -11080,13 +11080,13 @@ async function githubLogin(opts) {
11080
11080
  const startedAt = Date.now();
11081
11081
  const maxMs = opts.timeoutMs ?? start.expiresInSec * 1e3;
11082
11082
  let interval = start.pollIntervalSec;
11083
- const spinner14 = p29.spinner();
11084
- spinner14.start("Waiting for GitHub authorization...");
11083
+ const spinner15 = p29.spinner();
11084
+ spinner15.start("Waiting for GitHub authorization...");
11085
11085
  while (Date.now() - startedAt < maxMs) {
11086
11086
  await sleep2(interval * 1e3);
11087
11087
  const poll = await pollDeviceFlow(start.deviceCode);
11088
11088
  if (poll.status === "authorized" && poll.token) {
11089
- spinner14.stop("Authorization received.");
11089
+ spinner15.stop("Authorization received.");
11090
11090
  const profile = await fetchAuthenticatedUser(poll.token.accessToken);
11091
11091
  const token = {
11092
11092
  ...poll.token,
@@ -11107,15 +11107,15 @@ async function githubLogin(opts) {
11107
11107
  continue;
11108
11108
  }
11109
11109
  if (poll.status === "expired") {
11110
- spinner14.stop("Device code expired.");
11110
+ spinner15.stop("Device code expired.");
11111
11111
  throw new Error("GitHub device code expired \u2014 run `growthub github login` again.");
11112
11112
  }
11113
11113
  if (poll.status === "denied") {
11114
- spinner14.stop("Authorization denied.");
11114
+ spinner15.stop("Authorization denied.");
11115
11115
  throw new Error("GitHub authorization was denied.");
11116
11116
  }
11117
11117
  }
11118
- spinner14.stop("Timed out.");
11118
+ spinner15.stop("Timed out.");
11119
11119
  throw new Error("GitHub login timed out. Re-run `growthub github login` to retry.");
11120
11120
  }
11121
11121
  async function githubWhoami(opts = {}) {
@@ -11279,7 +11279,7 @@ async function initStarterWorkspace(opts) {
11279
11279
  forkPath: absOut,
11280
11280
  kitId: info.id,
11281
11281
  forkId: reg.forkId,
11282
- source: "greenfield",
11282
+ source: "workspace-starter",
11283
11283
  sourceRef: ""
11284
11284
  });
11285
11285
  if (sessionSeed.written) {
@@ -11945,10 +11945,10 @@ var init_skills_source = __esm({
11945
11945
  import fs61 from "node:fs";
11946
11946
  import path71 from "node:path";
11947
11947
  function safeReadPackageJson(dir) {
11948
- const p39 = path71.resolve(dir, "package.json");
11949
- if (!fs61.existsSync(p39)) return null;
11948
+ const p42 = path71.resolve(dir, "package.json");
11949
+ if (!fs61.existsSync(p42)) return null;
11950
11950
  try {
11951
- return JSON.parse(fs61.readFileSync(p39, "utf8"));
11951
+ return JSON.parse(fs61.readFileSync(p42, "utf8"));
11952
11952
  } catch {
11953
11953
  return null;
11954
11954
  }
@@ -12704,10 +12704,10 @@ function movePayloadIntoFork(payloadRoot, forkPath, payloadRelativePath) {
12704
12704
  return target;
12705
12705
  }
12706
12706
  function writeManifest(forkPath, manifest) {
12707
- const p39 = path75.resolve(forkPath, MANIFEST_RELATIVE_PATH);
12708
- fs65.mkdirSync(path75.dirname(p39), { recursive: true });
12709
- fs65.writeFileSync(p39, JSON.stringify(manifest, null, 2) + "\n", "utf8");
12710
- return p39;
12707
+ const p42 = path75.resolve(forkPath, MANIFEST_RELATIVE_PATH);
12708
+ fs65.mkdirSync(path75.dirname(p42), { recursive: true });
12709
+ fs65.writeFileSync(p42, JSON.stringify(manifest, null, 2) + "\n", "utf8");
12710
+ return p42;
12711
12711
  }
12712
12712
  function assertConfirmationsSatisfied(plan, confirmations) {
12713
12713
  const confirmed = new Set(confirmations);
@@ -12906,24 +12906,24 @@ function generateJobId2() {
12906
12906
  return `sij-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
12907
12907
  }
12908
12908
  function writeJob2(job) {
12909
- const p39 = resolveJobPath2(job.jobId);
12910
- fs66.mkdirSync(path76.dirname(p39), { recursive: true });
12911
- fs66.writeFileSync(p39, JSON.stringify(job, null, 2) + "\n", "utf8");
12909
+ const p42 = resolveJobPath2(job.jobId);
12910
+ fs66.mkdirSync(path76.dirname(p42), { recursive: true });
12911
+ fs66.writeFileSync(p42, JSON.stringify(job, null, 2) + "\n", "utf8");
12912
12912
  }
12913
- function readJobFile(p39) {
12914
- if (!fs66.existsSync(p39)) return null;
12913
+ function readJobFile(p42) {
12914
+ if (!fs66.existsSync(p42)) return null;
12915
12915
  try {
12916
- return JSON.parse(fs66.readFileSync(p39, "utf8"));
12916
+ return JSON.parse(fs66.readFileSync(p42, "utf8"));
12917
12917
  } catch {
12918
12918
  return null;
12919
12919
  }
12920
12920
  }
12921
12921
  function patchJob2(jobId, status, patch = {}) {
12922
- const p39 = resolveJobPath2(jobId);
12923
- const job = readJobFile(p39);
12922
+ const p42 = resolveJobPath2(jobId);
12923
+ const job = readJobFile(p42);
12924
12924
  if (!job) return null;
12925
12925
  const updated = { ...job, ...patch, status };
12926
- fs66.writeFileSync(p39, JSON.stringify(updated, null, 2) + "\n", "utf8");
12926
+ fs66.writeFileSync(p42, JSON.stringify(updated, null, 2) + "\n", "utf8");
12927
12927
  return updated;
12928
12928
  }
12929
12929
  function getSourceImportJob(jobId) {
@@ -13097,24 +13097,24 @@ __export(catalog_exports, {
13097
13097
  });
13098
13098
  import fs67 from "node:fs";
13099
13099
  import path77 from "node:path";
13100
- function exists(p39) {
13100
+ function exists(p42) {
13101
13101
  try {
13102
- fs67.accessSync(p39);
13102
+ fs67.accessSync(p42);
13103
13103
  return true;
13104
13104
  } catch {
13105
13105
  return false;
13106
13106
  }
13107
13107
  }
13108
- function isDir3(p39) {
13108
+ function isDir3(p42) {
13109
13109
  try {
13110
- return fs67.statSync(p39).isDirectory();
13110
+ return fs67.statSync(p42).isDirectory();
13111
13111
  } catch {
13112
13112
  return false;
13113
13113
  }
13114
13114
  }
13115
- function safeRead(p39) {
13115
+ function safeRead(p42) {
13116
13116
  try {
13117
- return fs67.readFileSync(p39, "utf8");
13117
+ return fs67.readFileSync(p42, "utf8");
13118
13118
  } catch {
13119
13119
  return null;
13120
13120
  }
@@ -13291,10 +13291,10 @@ __export(source_import_discovery_exports, {
13291
13291
  startSkillsSourceImportFlow: () => startSkillsSourceImportFlow,
13292
13292
  startSourceImportFlow: () => startSourceImportFlow
13293
13293
  });
13294
- import * as p37 from "@clack/prompts";
13295
- import pc55 from "picocolors";
13296
- import fs73 from "node:fs";
13297
- import path84 from "node:path";
13294
+ import * as p40 from "@clack/prompts";
13295
+ import pc60 from "picocolors";
13296
+ import fs78 from "node:fs";
13297
+ import path89 from "node:path";
13298
13298
  import { pathToFileURL as pathToFileURL4 } from "node:url";
13299
13299
  function slugifyWorkspaceName(input) {
13300
13300
  const slug = input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -13316,17 +13316,17 @@ function defaultWorkspaceFolderName(input) {
13316
13316
  }
13317
13317
  async function promptForInteractiveWorkspacePath(input) {
13318
13318
  const suggestedName = defaultWorkspaceFolderName(input);
13319
- const raw = await p37.text({
13319
+ const raw = await p40.text({
13320
13320
  message: "Where should we create the custom workspace?",
13321
13321
  placeholder: `~/Desktop/${suggestedName}`
13322
13322
  });
13323
- if (p37.isCancel(raw) || !raw) return null;
13323
+ if (p40.isCancel(raw) || !raw) return null;
13324
13324
  const trimmed = String(raw).trim();
13325
- const expanded = trimmed.startsWith("~/") ? path84.join(process.env.HOME ?? "~", trimmed.slice(2)) : trimmed;
13326
- const resolved = path84.resolve(expanded);
13327
- if (fs73.existsSync(resolved) && fs73.statSync(resolved).isDirectory()) {
13328
- const finalPath = path84.join(resolved, suggestedName);
13329
- p37.note(
13325
+ const expanded = trimmed.startsWith("~/") ? path89.join(process.env.HOME ?? "~", trimmed.slice(2)) : trimmed;
13326
+ const resolved = path89.resolve(expanded);
13327
+ if (fs78.existsSync(resolved) && fs78.statSync(resolved).isDirectory()) {
13328
+ const finalPath = path89.join(resolved, suggestedName);
13329
+ p40.note(
13330
13330
  [
13331
13331
  `You selected an existing folder: ${resolved}`,
13332
13332
  `Growthub will create the workspace inside it as:`,
@@ -13344,7 +13344,7 @@ function scopeLabel2(scope) {
13344
13344
  return "All Time";
13345
13345
  }
13346
13346
  async function chooseSkillsScope(current) {
13347
- const choice = await p37.select({
13347
+ const choice = await p40.select({
13348
13348
  message: "Leaderboard scope",
13349
13349
  options: [
13350
13350
  { value: "all", label: "All Time", hint: current === "all" ? "current" : "most trusted by install rank" },
@@ -13352,7 +13352,7 @@ async function chooseSkillsScope(current) {
13352
13352
  { value: "hot", label: "Hot", hint: current === "hot" ? "current" : "currently active picks" }
13353
13353
  ]
13354
13354
  });
13355
- if (p37.isCancel(choice)) return null;
13355
+ if (p40.isCancel(choice)) return null;
13356
13356
  return choice;
13357
13357
  }
13358
13358
  async function previewSkillSelection(skillRef) {
@@ -13361,7 +13361,7 @@ async function previewSkillSelection(skillRef) {
13361
13361
  skillRef
13362
13362
  });
13363
13363
  const audits = skill.audits?.length ? skill.audits.map((audit) => `${audit.name}:${audit.status}`).join(", ") : "none";
13364
- p37.note(
13364
+ p40.note(
13365
13365
  [
13366
13366
  `Skill: ${skill.skillId}`,
13367
13367
  `Repo: ${skill.repository ?? "unknown"}`,
@@ -13374,11 +13374,11 @@ async function previewSkillSelection(skillRef) {
13374
13374
  ].join("\n"),
13375
13375
  skill.title
13376
13376
  );
13377
- const confirmed = await p37.confirm({
13377
+ const confirmed = await p40.confirm({
13378
13378
  message: `Use ${skill.title} to build a starter-derived workspace?`,
13379
13379
  initialValue: true
13380
13380
  });
13381
- return !p37.isCancel(confirmed) && confirmed === true;
13381
+ return !p40.isCancel(confirmed) && confirmed === true;
13382
13382
  }
13383
13383
  async function selectSkillFromCatalog() {
13384
13384
  let query = "";
@@ -13414,7 +13414,7 @@ async function selectSkillFromCatalog() {
13414
13414
  );
13415
13415
  const hasNext = (result.total ?? 0) > page * pageSize;
13416
13416
  const hasPrev = page > 1;
13417
- const choice = await p37.select({
13417
+ const choice = await p40.select({
13418
13418
  message: `skills.sh \xB7 ${scopeLabel2(scope)}${query ? ` \xB7 query="${query}"` : ""}`,
13419
13419
  options: [
13420
13420
  ...result.entries.map((entry, index51) => ({
@@ -13430,7 +13430,7 @@ async function selectSkillFromCatalog() {
13430
13430
  { value: "back", label: "\u2190 Back to Starter" }
13431
13431
  ]
13432
13432
  });
13433
- if (p37.isCancel(choice) || choice === "back") return null;
13433
+ if (p40.isCancel(choice) || choice === "back") return null;
13434
13434
  if (choice === "prev") {
13435
13435
  page = Math.max(1, page - 1);
13436
13436
  continue;
@@ -13448,23 +13448,23 @@ async function selectSkillFromCatalog() {
13448
13448
  continue;
13449
13449
  }
13450
13450
  if (choice === "search") {
13451
- const nextQuery = await p37.text({
13451
+ const nextQuery = await p40.text({
13452
13452
  message: "Search the live leaderboard",
13453
13453
  placeholder: "marketing, frontend design, firecrawl, seo",
13454
13454
  defaultValue: query
13455
13455
  });
13456
- if (!p37.isCancel(nextQuery)) {
13456
+ if (!p40.isCancel(nextQuery)) {
13457
13457
  query = String(nextQuery).trim();
13458
13458
  page = 1;
13459
13459
  }
13460
13460
  continue;
13461
13461
  }
13462
13462
  if (choice === "manual") {
13463
- const manual = await p37.text({
13463
+ const manual = await p40.text({
13464
13464
  message: "skills.sh URL or canonical skill id",
13465
13465
  placeholder: "https://skills.sh/anthropics/skills/frontend-design"
13466
13466
  });
13467
- if (!p37.isCancel(manual) && String(manual).trim()) {
13467
+ if (!p40.isCancel(manual) && String(manual).trim()) {
13468
13468
  const skillRef = String(manual).trim();
13469
13469
  if (await previewSkillSelection(skillRef)) {
13470
13470
  return skillRef;
@@ -13495,20 +13495,20 @@ async function startSkillsSourceImportFlow() {
13495
13495
  });
13496
13496
  } catch (err) {
13497
13497
  const msg = err instanceof Error ? err.message : String(err);
13498
- p37.log.error(msg);
13498
+ p40.log.error(msg);
13499
13499
  }
13500
13500
  }
13501
13501
  function renderSuccess(result, jobId) {
13502
13502
  const sourceLine = result.source.kind === "github-repo" ? `${result.source.repo.owner}/${result.source.repo.repo}` : `${result.source.skillId}@${result.source.version}`;
13503
- p37.outro(
13504
- `Imported ${sourceLine} into ${pc55.cyan(result.forkPath)}
13505
- jobId: ${pc55.cyan(jobId)}
13506
- forkId: ${pc55.cyan(result.forkId)}
13503
+ p40.outro(
13504
+ `Imported ${sourceLine} into ${pc60.cyan(result.forkPath)}
13505
+ jobId: ${pc60.cyan(jobId)}
13506
+ forkId: ${pc60.cyan(result.forkId)}
13507
13507
  risk: ${result.security.riskClass} (${result.security.findings.length} findings)
13508
13508
  detection: framework=${result.detection.framework} pm=${result.detection.packageManager}
13509
13509
  open: ${folderOpenLabel3(result.forkPath)}
13510
- summary: ${pc55.dim(result.summaryPath)}
13511
- manifest: ${pc55.dim(result.manifestPath)}`
13510
+ summary: ${pc60.dim(result.summaryPath)}
13511
+ manifest: ${pc60.dim(result.manifestPath)}`
13512
13512
  );
13513
13513
  }
13514
13514
  async function confirmTwice(job) {
@@ -13516,26 +13516,26 @@ async function confirmTwice(job) {
13516
13516
  if (pending.length === 0) return [];
13517
13517
  const sec = job.plan?.security;
13518
13518
  if (sec) {
13519
- p37.log.warn(`Security report \u2014 risk: ${sec.riskClass}`);
13519
+ p40.log.warn(`Security report \u2014 risk: ${sec.riskClass}`);
13520
13520
  for (const line of sec.summaryLines.slice(0, 6)) {
13521
- p37.log.message(` ${line}`);
13521
+ p40.log.message(` ${line}`);
13522
13522
  }
13523
13523
  if (sec.blocked) {
13524
- p37.log.error("Security inspection BLOCKED this payload \u2014 import cannot continue.");
13524
+ p40.log.error("Security inspection BLOCKED this payload \u2014 import cannot continue.");
13525
13525
  return null;
13526
13526
  }
13527
13527
  }
13528
- p37.log.info(`Pending confirmations: ${pending.join(", ")}`);
13529
- const ack = await p37.confirm({
13528
+ p40.log.info(`Pending confirmations: ${pending.join(", ")}`);
13529
+ const ack = await p40.confirm({
13530
13530
  message: "Acknowledge the security report?",
13531
13531
  initialValue: false
13532
13532
  });
13533
- if (p37.isCancel(ack) || ack !== true) return null;
13534
- const go = await p37.confirm({
13533
+ if (p40.isCancel(ack) || ack !== true) return null;
13534
+ const go = await p40.confirm({
13535
13535
  message: "Second confirmation \u2014 materialize the workspace now?",
13536
13536
  initialValue: false
13537
13537
  });
13538
- if (p37.isCancel(go) || go !== true) return null;
13538
+ if (p40.isCancel(go) || go !== true) return null;
13539
13539
  return pending;
13540
13540
  }
13541
13541
  async function startSourceImportFlow(input) {
@@ -13543,12 +13543,12 @@ async function startSourceImportFlow(input) {
13543
13543
  source: { kind: "github-repo", repo: input.repo },
13544
13544
  out: input.out,
13545
13545
  importMode: "wrap",
13546
- onProgress: (step) => p37.log.step(step)
13546
+ onProgress: (step) => p40.log.step(step)
13547
13547
  } : {
13548
13548
  source: { kind: "skills-skill", skillRef: input.skillRef },
13549
13549
  out: input.out,
13550
13550
  importMode: "wrap",
13551
- onProgress: (step) => p37.log.step(step)
13551
+ onProgress: (step) => p40.log.step(step)
13552
13552
  };
13553
13553
  try {
13554
13554
  const { job, result } = await importSourceAsWorkspace(importInput);
@@ -13557,31 +13557,31 @@ async function startSourceImportFlow(input) {
13557
13557
  return;
13558
13558
  }
13559
13559
  if (job.status === "failed") {
13560
- p37.log.error(job.error ?? "Source import failed.");
13560
+ p40.log.error(job.error ?? "Source import failed.");
13561
13561
  return;
13562
13562
  }
13563
13563
  if (job.status !== "awaiting_confirmation") {
13564
- p37.log.warn(`Unexpected job status: ${job.status}`);
13564
+ p40.log.warn(`Unexpected job status: ${job.status}`);
13565
13565
  return;
13566
13566
  }
13567
13567
  const acked = await confirmTwice(job);
13568
13568
  if (!acked) {
13569
- p37.log.warn("Import aborted \u2014 confirmations not provided.");
13569
+ p40.log.warn("Import aborted \u2014 confirmations not provided.");
13570
13570
  return;
13571
13571
  }
13572
13572
  const resumed = await confirmAndResumeSourceImportJob({
13573
13573
  jobId: job.jobId,
13574
13574
  confirmations: acked,
13575
- onProgress: (step) => p37.log.step(step)
13575
+ onProgress: (step) => p40.log.step(step)
13576
13576
  });
13577
13577
  if (!resumed || resumed.status !== "completed" || !resumed.result) {
13578
- p37.log.error(resumed?.error ?? "Import did not complete after confirmation.");
13578
+ p40.log.error(resumed?.error ?? "Import did not complete after confirmation.");
13579
13579
  return;
13580
13580
  }
13581
13581
  renderSuccess(resumed.result, resumed.jobId);
13582
13582
  } catch (err) {
13583
13583
  const msg = err instanceof Error ? err.message : String(err);
13584
- p37.log.error(msg);
13584
+ p40.log.error(msg);
13585
13585
  }
13586
13586
  }
13587
13587
  var init_source_import_discovery = __esm({
@@ -13594,12 +13594,12 @@ var init_source_import_discovery = __esm({
13594
13594
 
13595
13595
  // src/index.ts
13596
13596
  import { Command } from "commander";
13597
- import * as p38 from "@clack/prompts";
13598
- import pc56 from "picocolors";
13599
- import fs74 from "node:fs";
13600
- import path85 from "node:path";
13601
- import { spawnSync as spawnSync9 } from "node:child_process";
13602
- import { fileURLToPath as fileURLToPath7 } from "node:url";
13597
+ import * as p41 from "@clack/prompts";
13598
+ import pc61 from "picocolors";
13599
+ import fs79 from "node:fs";
13600
+ import path90 from "node:path";
13601
+ import { spawnSync as spawnSync11 } from "node:child_process";
13602
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
13603
13603
 
13604
13604
  // src/analytics/posthog.ts
13605
13605
  init_home();
@@ -14398,8 +14398,8 @@ function printItemCompleted(item) {
14398
14398
  const changes = Array.isArray(item.changes) ? item.changes : [];
14399
14399
  const entries = changes.map((changeRaw) => asRecord(changeRaw)).filter((change) => Boolean(change)).map((change) => {
14400
14400
  const kind = asString(change.kind, "update");
14401
- const path86 = asString(change.path, "unknown");
14402
- return `${kind} ${path86}`;
14401
+ const path91 = asString(change.path, "unknown");
14402
+ return `${kind} ${path91}`;
14403
14403
  });
14404
14404
  const preview = entries.length > 0 ? entries.slice(0, 6).join(", ") : "none";
14405
14405
  const more = entries.length > 6 ? ` (+${entries.length - 6} more)` : "";
@@ -16340,11 +16340,11 @@ async function authLogin(opts) {
16340
16340
  p14.log.message(pc19.dim("Paste this URL into a browser:"));
16341
16341
  p14.log.message(pc19.cyan(flow.loginUrl));
16342
16342
  }
16343
- const spinner14 = p14.spinner();
16344
- spinner14.start("Waiting for hosted app to complete the exchange\u2026");
16343
+ const spinner15 = p14.spinner();
16344
+ spinner15.start("Waiting for hosted app to complete the exchange\u2026");
16345
16345
  try {
16346
16346
  const result = await flow.waitForCallback();
16347
- spinner14.stop("Received hosted session token.");
16347
+ spinner15.stop("Received hosted session token.");
16348
16348
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
16349
16349
  writeSession({
16350
16350
  version: 1,
@@ -16402,7 +16402,7 @@ async function authLogin(opts) {
16402
16402
  }
16403
16403
  p14.outro("Done");
16404
16404
  } catch (err) {
16405
- spinner14.stop("Login failed.");
16405
+ spinner15.stop("Login failed.");
16406
16406
  p14.log.error(err instanceof Error ? err.message : String(err));
16407
16407
  process.exit(1);
16408
16408
  } finally {
@@ -16788,8 +16788,8 @@ async function dbBackupCommand(opts) {
16788
16788
  p15.log.message(pc21.dim(`Connection source: ${connection.source}`));
16789
16789
  p15.log.message(pc21.dim(`Backup dir: ${backupDir}`));
16790
16790
  p15.log.message(pc21.dim(`Retention: ${retentionDays} day(s)`));
16791
- const spinner14 = p15.spinner();
16792
- spinner14.start("Creating database backup...");
16791
+ const spinner15 = p15.spinner();
16792
+ spinner15.start("Creating database backup...");
16793
16793
  try {
16794
16794
  const result = await runDatabaseBackup({
16795
16795
  connectionString: connection.value,
@@ -16797,7 +16797,7 @@ async function dbBackupCommand(opts) {
16797
16797
  retentionDays,
16798
16798
  filenamePrefix
16799
16799
  });
16800
- spinner14.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`);
16800
+ spinner15.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`);
16801
16801
  if (opts.json) {
16802
16802
  console.log(
16803
16803
  JSON.stringify(
@@ -16816,7 +16816,7 @@ async function dbBackupCommand(opts) {
16816
16816
  }
16817
16817
  p15.outro(pc21.green("Backup completed."));
16818
16818
  } catch (err) {
16819
- spinner14.stop(pc21.red("Backup failed."));
16819
+ spinner15.stop(pc21.red("Backup failed."));
16820
16820
  throw err;
16821
16821
  }
16822
16822
  }
@@ -17233,8 +17233,8 @@ function registerIssueCommands(program2) {
17233
17233
  if (opts.assigneeAgentId) params.set("assigneeAgentId", opts.assigneeAgentId);
17234
17234
  if (opts.projectId) params.set("projectId", opts.projectId);
17235
17235
  const query = params.toString();
17236
- const path86 = `/api/companies/${ctx.companyId}/issues${query ? `?${query}` : ""}`;
17237
- const rows = await ctx.api.get(path86) ?? [];
17236
+ const path91 = `/api/companies/${ctx.companyId}/issues${query ? `?${query}` : ""}`;
17237
+ const rows = await ctx.api.get(path91) ?? [];
17238
17238
  const filtered = filterIssueRows(rows, opts.match);
17239
17239
  if (ctx.json) {
17240
17240
  printOutput(filtered, { json: true });
@@ -17840,8 +17840,8 @@ function registerActivityCommands(program2) {
17840
17840
  if (opts.entityType) params.set("entityType", opts.entityType);
17841
17841
  if (opts.entityId) params.set("entityId", opts.entityId);
17842
17842
  const query = params.toString();
17843
- const path86 = `/api/companies/${ctx.companyId}/activity${query ? `?${query}` : ""}`;
17844
- const rows = await ctx.api.get(path86) ?? [];
17843
+ const path91 = `/api/companies/${ctx.companyId}/activity${query ? `?${query}` : ""}`;
17844
+ const rows = await ctx.api.get(path91) ?? [];
17845
17845
  if (ctx.json) {
17846
17846
  printOutput(rows, { json: true });
17847
17847
  return;
@@ -18729,8 +18729,8 @@ async function runWorktreeInit(opts) {
18729
18729
  `Cannot seed worktree database because source config was not found at ${sourceConfigPath}. Use --no-seed or provide --from-config.`
18730
18730
  );
18731
18731
  }
18732
- const spinner14 = p16.spinner();
18733
- spinner14.start(`Seeding isolated worktree database from source instance (${seedMode})...`);
18732
+ const spinner15 = p16.spinner();
18733
+ spinner15.start(`Seeding isolated worktree database from source instance (${seedMode})...`);
18734
18734
  try {
18735
18735
  const seeded = await seedWorktreeDatabase({
18736
18736
  sourceConfigPath,
@@ -18742,9 +18742,9 @@ async function runWorktreeInit(opts) {
18742
18742
  });
18743
18743
  seedSummary = seeded.backupSummary;
18744
18744
  reboundWorkspaceSummary = seeded.reboundWorkspaces;
18745
- spinner14.stop(`Seeded isolated worktree database (${seedMode}).`);
18745
+ spinner15.stop(`Seeded isolated worktree database (${seedMode}).`);
18746
18746
  } catch (error) {
18747
- spinner14.stop(pc24.red("Failed to seed worktree database."));
18747
+ spinner15.stop(pc24.red("Failed to seed worktree database."));
18748
18748
  throw error;
18749
18749
  }
18750
18750
  }
@@ -18810,16 +18810,16 @@ async function worktreeMakeCommand(nameArg, opts) {
18810
18810
  branchExists: !startPoint && localBranchExists(sourceCwd, name),
18811
18811
  startPoint
18812
18812
  });
18813
- const spinner14 = p16.spinner();
18814
- spinner14.start(`Creating git worktree at ${targetPath}...`);
18813
+ const spinner15 = p16.spinner();
18814
+ spinner15.start(`Creating git worktree at ${targetPath}...`);
18815
18815
  try {
18816
18816
  execFileSync("git", worktreeArgs, {
18817
18817
  cwd: sourceCwd,
18818
18818
  stdio: ["ignore", "pipe", "pipe"]
18819
18819
  });
18820
- spinner14.stop(`Created git worktree at ${targetPath}.`);
18820
+ spinner15.stop(`Created git worktree at ${targetPath}.`);
18821
18821
  } catch (error) {
18822
- spinner14.stop(pc24.red("Failed to create git worktree."));
18822
+ spinner15.stop(pc24.red("Failed to create git worktree."));
18823
18823
  throw new Error(extractExecSyncErrorMessage(error) ?? String(error));
18824
18824
  }
18825
18825
  const installSpinner = p16.spinner();
@@ -18982,9 +18982,9 @@ async function worktreeCleanupCommand(nameArg, opts) {
18982
18982
  }
18983
18983
  if (linkedWorktree) {
18984
18984
  const worktreeDirExists = existsSync2(linkedWorktree.worktree);
18985
- const spinner14 = p16.spinner();
18985
+ const spinner15 = p16.spinner();
18986
18986
  if (worktreeDirExists) {
18987
- spinner14.start(`Removing git worktree at ${linkedWorktree.worktree}...`);
18987
+ spinner15.start(`Removing git worktree at ${linkedWorktree.worktree}...`);
18988
18988
  try {
18989
18989
  const removeArgs = ["worktree", "remove", linkedWorktree.worktree];
18990
18990
  if (opts.force) removeArgs.push("--force");
@@ -18992,18 +18992,18 @@ async function worktreeCleanupCommand(nameArg, opts) {
18992
18992
  cwd: sourceCwd,
18993
18993
  stdio: ["ignore", "pipe", "pipe"]
18994
18994
  });
18995
- spinner14.stop(`Removed git worktree at ${linkedWorktree.worktree}.`);
18995
+ spinner15.stop(`Removed git worktree at ${linkedWorktree.worktree}.`);
18996
18996
  } catch (error) {
18997
- spinner14.stop(pc24.yellow(`Could not remove worktree cleanly, will prune instead.`));
18997
+ spinner15.stop(pc24.yellow(`Could not remove worktree cleanly, will prune instead.`));
18998
18998
  p16.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
18999
18999
  }
19000
19000
  } else {
19001
- spinner14.start("Pruning stale worktree entry...");
19001
+ spinner15.start("Pruning stale worktree entry...");
19002
19002
  execFileSync("git", ["worktree", "prune"], {
19003
19003
  cwd: sourceCwd,
19004
19004
  stdio: ["ignore", "pipe", "pipe"]
19005
19005
  });
19006
- spinner14.stop("Pruned stale worktree entry.");
19006
+ spinner15.stop("Pruned stale worktree entry.");
19007
19007
  }
19008
19008
  } else {
19009
19009
  execFileSync("git", ["worktree", "prune"], {
@@ -19012,31 +19012,31 @@ async function worktreeCleanupCommand(nameArg, opts) {
19012
19012
  });
19013
19013
  }
19014
19014
  if (existsSync2(targetPath)) {
19015
- const spinner14 = p16.spinner();
19016
- spinner14.start(`Removing worktree directory ${targetPath}...`);
19015
+ const spinner15 = p16.spinner();
19016
+ spinner15.start(`Removing worktree directory ${targetPath}...`);
19017
19017
  rmSync(targetPath, { recursive: true, force: true });
19018
- spinner14.stop(`Removed worktree directory ${targetPath}.`);
19018
+ spinner15.stop(`Removed worktree directory ${targetPath}.`);
19019
19019
  }
19020
19020
  if (localBranchExists(sourceCwd, name)) {
19021
- const spinner14 = p16.spinner();
19022
- spinner14.start(`Deleting local branch "${name}"...`);
19021
+ const spinner15 = p16.spinner();
19022
+ spinner15.start(`Deleting local branch "${name}"...`);
19023
19023
  try {
19024
19024
  const deleteFlag = opts.force ? "-D" : "-d";
19025
19025
  execFileSync("git", ["branch", deleteFlag, name], {
19026
19026
  cwd: sourceCwd,
19027
19027
  stdio: ["ignore", "pipe", "pipe"]
19028
19028
  });
19029
- spinner14.stop(`Deleted local branch "${name}".`);
19029
+ spinner15.stop(`Deleted local branch "${name}".`);
19030
19030
  } catch (error) {
19031
- spinner14.stop(pc24.yellow(`Could not delete branch "${name}".`));
19031
+ spinner15.stop(pc24.yellow(`Could not delete branch "${name}".`));
19032
19032
  p16.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
19033
19033
  }
19034
19034
  }
19035
19035
  if (existsSync2(instanceRoot)) {
19036
- const spinner14 = p16.spinner();
19037
- spinner14.start(`Removing instance data at ${instanceRoot}...`);
19036
+ const spinner15 = p16.spinner();
19037
+ spinner15.start(`Removing instance data at ${instanceRoot}...`);
19038
19038
  rmSync(instanceRoot, { recursive: true, force: true });
19039
- spinner14.stop(`Removed instance data at ${instanceRoot}.`);
19039
+ spinner15.stop(`Removed instance data at ${instanceRoot}.`);
19040
19040
  }
19041
19041
  p16.outro(pc24.green("Cleanup complete."));
19042
19042
  }
@@ -19077,16 +19077,16 @@ function resolvePackageArg(packageArg, isLocal) {
19077
19077
  }
19078
19078
  return path23.resolve(process.cwd(), packageArg);
19079
19079
  }
19080
- function formatPlugin(p39) {
19081
- const statusColor3 = p39.status === "ready" ? pc25.green(p39.status) : p39.status === "error" ? pc25.red(p39.status) : p39.status === "disabled" ? pc25.dim(p39.status) : pc25.yellow(p39.status);
19080
+ function formatPlugin(p42) {
19081
+ const statusColor3 = p42.status === "ready" ? pc25.green(p42.status) : p42.status === "error" ? pc25.red(p42.status) : p42.status === "disabled" ? pc25.dim(p42.status) : pc25.yellow(p42.status);
19082
19082
  const parts = [
19083
- `key=${pc25.bold(p39.pluginKey)}`,
19083
+ `key=${pc25.bold(p42.pluginKey)}`,
19084
19084
  `status=${statusColor3}`,
19085
- `version=${p39.version}`,
19086
- `id=${pc25.dim(p39.id)}`
19085
+ `version=${p42.version}`,
19086
+ `id=${pc25.dim(p42.id)}`
19087
19087
  ];
19088
- if (p39.lastError) {
19089
- parts.push(`error=${pc25.red(p39.lastError.slice(0, 80))}`);
19088
+ if (p42.lastError) {
19089
+ parts.push(`error=${pc25.red(p42.lastError.slice(0, 80))}`);
19090
19090
  }
19091
19091
  return parts.join(" ");
19092
19092
  }
@@ -19107,8 +19107,8 @@ function registerPluginCommands(program2) {
19107
19107
  console.log(pc25.dim("No plugins installed."));
19108
19108
  return;
19109
19109
  }
19110
- for (const p39 of rows) {
19111
- console.log(formatPlugin(p39));
19110
+ for (const p42 of rows) {
19111
+ console.log(formatPlugin(p42));
19112
19112
  }
19113
19113
  } catch (err) {
19114
19114
  handleCommandError(err);
@@ -19316,10 +19316,10 @@ function resolveUpstreamAssetRoot(kitId) {
19316
19316
  }
19317
19317
  throw new Error(`Cannot locate bundled asset root for kit: ${kitId}`);
19318
19318
  }
19319
- function readFileIfExists(p39) {
19320
- if (!fs23.existsSync(p39)) return null;
19319
+ function readFileIfExists(p42) {
19320
+ if (!fs23.existsSync(p42)) return null;
19321
19321
  try {
19322
- return fs23.readFileSync(p39, "utf8");
19322
+ return fs23.readFileSync(p42, "utf8");
19323
19323
  } catch {
19324
19324
  return null;
19325
19325
  }
@@ -19834,10 +19834,10 @@ function resolveOrphanJobPath(jobId) {
19834
19834
  function generateJobId() {
19835
19835
  return `kfj-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
19836
19836
  }
19837
- function parseJobFile(p39) {
19838
- if (!fs25.existsSync(p39)) return null;
19837
+ function parseJobFile(p42) {
19838
+ if (!fs25.existsSync(p42)) return null;
19839
19839
  try {
19840
- return JSON.parse(fs25.readFileSync(p39, "utf8"));
19840
+ return JSON.parse(fs25.readFileSync(p42, "utf8"));
19841
19841
  } catch {
19842
19842
  return null;
19843
19843
  }
@@ -19846,19 +19846,19 @@ function findJobPath(jobId) {
19846
19846
  const orphanPath = resolveOrphanJobPath(jobId);
19847
19847
  if (fs25.existsSync(orphanPath)) return orphanPath;
19848
19848
  for (const reg of listKitForkRegistrations()) {
19849
- const p39 = path32.resolve(resolveInForkJobsDir(reg.forkPath), `${jobId}.json`);
19850
- if (fs25.existsSync(p39)) return p39;
19849
+ const p42 = path32.resolve(resolveInForkJobsDir(reg.forkPath), `${jobId}.json`);
19850
+ if (fs25.existsSync(p42)) return p42;
19851
19851
  }
19852
19852
  return null;
19853
19853
  }
19854
19854
  function readJob(jobId) {
19855
- const p39 = findJobPath(jobId);
19856
- return p39 ? parseJobFile(p39) : null;
19855
+ const p42 = findJobPath(jobId);
19856
+ return p42 ? parseJobFile(p42) : null;
19857
19857
  }
19858
19858
  function writeJob(job) {
19859
- const p39 = resolveJobPath(job.jobId, job.kitId, job.forkId);
19860
- fs25.mkdirSync(path32.dirname(p39), { recursive: true });
19861
- fs25.writeFileSync(p39, JSON.stringify(job, null, 2) + "\n", "utf8");
19859
+ const p42 = resolveJobPath(job.jobId, job.kitId, job.forkId);
19860
+ fs25.mkdirSync(path32.dirname(p42), { recursive: true });
19861
+ fs25.writeFileSync(p42, JSON.stringify(job, null, 2) + "\n", "utf8");
19862
19862
  }
19863
19863
  function patchJob(jobId, status, patch) {
19864
19864
  const existingPath = findJobPath(jobId);
@@ -20696,10 +20696,10 @@ function computePolicyHash(policy) {
20696
20696
  return crypto2.createHash("sha256").update(canonicalize(shape), "utf8").digest("hex");
20697
20697
  }
20698
20698
  function readIssuerRegistry() {
20699
- const p39 = resolveAuthorityIssuersPath();
20700
- if (!fs27.existsSync(p39)) return { version: 1, issuers: [] };
20699
+ const p42 = resolveAuthorityIssuersPath();
20700
+ if (!fs27.existsSync(p42)) return { version: 1, issuers: [] };
20701
20701
  try {
20702
- const parsed = JSON.parse(fs27.readFileSync(p39, "utf8"));
20702
+ const parsed = JSON.parse(fs27.readFileSync(p42, "utf8"));
20703
20703
  if (!parsed || !Array.isArray(parsed.issuers)) return { version: 1, issuers: [] };
20704
20704
  return { version: 1, issuers: parsed.issuers.filter(isValidIssuer) };
20705
20705
  } catch {
@@ -20712,9 +20712,9 @@ function isValidIssuer(x) {
20712
20712
  return typeof o.id === "string" && typeof o.publicKeyPem === "string" && (o.kind === "growthub-hosted" || o.kind === "self-signed" || o.kind === "enterprise");
20713
20713
  }
20714
20714
  function writeIssuerRegistry(registry) {
20715
- const p39 = resolveAuthorityIssuersPath();
20716
- fs27.mkdirSync(path34.dirname(p39), { recursive: true });
20717
- fs27.writeFileSync(p39, JSON.stringify({ version: 1, issuers: registry.issuers }, null, 2) + "\n", "utf8");
20715
+ const p42 = resolveAuthorityIssuersPath();
20716
+ fs27.mkdirSync(path34.dirname(p42), { recursive: true });
20717
+ fs27.writeFileSync(p42, JSON.stringify({ version: 1, issuers: registry.issuers }, null, 2) + "\n", "utf8");
20718
20718
  }
20719
20719
  function upsertIssuer(issuer) {
20720
20720
  if (!isValidIssuer(issuer)) {
@@ -20786,12 +20786,12 @@ function isWellFormedEnvelope(x) {
20786
20786
  return e.version === 1 && typeof e.envelopeId === "string" && typeof e.issuerId === "string" && e.algorithm === "ed25519" && typeof e.signature === "string" && typeof e.issuedAt === "string" && typeof e.nonce === "string" && !!e.subject && typeof e.subject.kitId === "string" && typeof e.subject.forkId === "string" && !!e.grants && Array.isArray(e.grants.capabilities) && typeof e.grants.policyAttested === "boolean";
20787
20787
  }
20788
20788
  function readForkAuthorityState(forkPath) {
20789
- const p39 = resolveInForkAuthorityPath(forkPath);
20790
- if (!fs27.existsSync(p39)) {
20789
+ const p42 = resolveInForkAuthorityPath(forkPath);
20790
+ if (!fs27.existsSync(p42)) {
20791
20791
  return { state: "none", version: 1, updatedAt: (/* @__PURE__ */ new Date(0)).toISOString() };
20792
20792
  }
20793
20793
  try {
20794
- const parsed = JSON.parse(fs27.readFileSync(p39, "utf8"));
20794
+ const parsed = JSON.parse(fs27.readFileSync(p42, "utf8"));
20795
20795
  if (!parsed || parsed.version !== 1) {
20796
20796
  return { state: "none", version: 1, updatedAt: (/* @__PURE__ */ new Date(0)).toISOString() };
20797
20797
  }
@@ -20801,9 +20801,9 @@ function readForkAuthorityState(forkPath) {
20801
20801
  }
20802
20802
  }
20803
20803
  function writeForkAuthorityState(forkPath, state) {
20804
- const p39 = resolveInForkAuthorityPath(forkPath);
20805
- fs27.mkdirSync(path34.dirname(p39), { recursive: true });
20806
- fs27.writeFileSync(p39, JSON.stringify(state, null, 2) + "\n", "utf8");
20804
+ const p42 = resolveInForkAuthorityPath(forkPath);
20805
+ fs27.mkdirSync(path34.dirname(p42), { recursive: true });
20806
+ fs27.writeFileSync(p42, JSON.stringify(state, null, 2) + "\n", "utf8");
20807
20807
  }
20808
20808
  function attachAuthorityEnvelope(forkPath, envelope, options = {}) {
20809
20809
  const verification = verifyAuthorityEnvelope(envelope, options);
@@ -21410,8 +21410,8 @@ async function runRegisterFlow() {
21410
21410
  return;
21411
21411
  }
21412
21412
  const kitVersion = kits.find((k) => k.id === kitChoice)?.version ?? "0.0.0";
21413
- const spinner14 = p18.spinner();
21414
- spinner14.start("Registering fork...");
21413
+ const spinner15 = p18.spinner();
21414
+ spinner15.start("Registering fork...");
21415
21415
  try {
21416
21416
  const reg = registerKitFork({
21417
21417
  forkPath: rawPath.trim(),
@@ -21419,7 +21419,7 @@ async function runRegisterFlow() {
21419
21419
  baseVersion: kitVersion,
21420
21420
  label: labelInput.trim() || void 0
21421
21421
  });
21422
- spinner14.stop(pc29.green("Fork registered."));
21422
+ spinner15.stop(pc29.green("Fork registered."));
21423
21423
  track("fork_registered", { kit_id: reg.kitId });
21424
21424
  p18.note(
21425
21425
  [
@@ -21432,7 +21432,7 @@ async function runRegisterFlow() {
21432
21432
  "Registration complete"
21433
21433
  );
21434
21434
  } catch (err) {
21435
- spinner14.stop(pc29.red("Registration failed."));
21435
+ spinner15.stop(pc29.red("Registration failed."));
21436
21436
  p18.log.error(err.message);
21437
21437
  }
21438
21438
  }
@@ -21462,15 +21462,15 @@ async function runStatusFlow() {
21462
21462
  p18.cancel("Fork not found.");
21463
21463
  return;
21464
21464
  }
21465
- const spinner14 = p18.spinner();
21466
- spinner14.start("Detecting drift...");
21465
+ const spinner15 = p18.spinner();
21466
+ spinner15.start("Detecting drift...");
21467
21467
  try {
21468
21468
  const report = detectKitForkDrift(reg);
21469
- spinner14.stop(pc29.green("Analysis complete."));
21469
+ spinner15.stop(pc29.green("Analysis complete."));
21470
21470
  track("fork_sync_preview_started", { kit_id: reg.kitId, drift_severity: report.overallSeverity });
21471
21471
  printDriftReport(report);
21472
21472
  } catch (err) {
21473
- spinner14.stop(pc29.red("Drift detection failed."));
21473
+ spinner15.stop(pc29.red("Drift detection failed."));
21474
21474
  p18.log.error(err.message);
21475
21475
  }
21476
21476
  }
@@ -22666,16 +22666,16 @@ var KIT_HEALTH_REPORT_VERSION = 1;
22666
22666
  // src/runtime/self-improving/health.ts
22667
22667
  import fs31 from "node:fs";
22668
22668
  import path37 from "node:path";
22669
- function isFile(p39) {
22669
+ function isFile(p42) {
22670
22670
  try {
22671
- return fs31.statSync(p39).isFile();
22671
+ return fs31.statSync(p42).isFile();
22672
22672
  } catch {
22673
22673
  return false;
22674
22674
  }
22675
22675
  }
22676
- function isDir(p39) {
22676
+ function isDir(p42) {
22677
22677
  try {
22678
- return fs31.statSync(p39).isDirectory();
22678
+ return fs31.statSync(p42).isDirectory();
22679
22679
  } catch {
22680
22680
  return false;
22681
22681
  }
@@ -22783,16 +22783,16 @@ function checkSelfImprovingHealth(forkRoot) {
22783
22783
  }
22784
22784
 
22785
22785
  // src/runtime/kit-health/index.ts
22786
- function isFile2(p39) {
22786
+ function isFile2(p42) {
22787
22787
  try {
22788
- return fs32.statSync(p39).isFile();
22788
+ return fs32.statSync(p42).isFile();
22789
22789
  } catch {
22790
22790
  return false;
22791
22791
  }
22792
22792
  }
22793
- function isDir2(p39) {
22793
+ function isDir2(p42) {
22794
22794
  try {
22795
- return fs32.statSync(p39).isDirectory();
22795
+ return fs32.statSync(p42).isDirectory();
22796
22796
  } catch {
22797
22797
  return false;
22798
22798
  }
@@ -25837,31 +25837,31 @@ function resolveManifestBaseUrl(opts = {}) {
25837
25837
  function isRecord(value) {
25838
25838
  return typeof value === "object" && value !== null && !Array.isArray(value);
25839
25839
  }
25840
- function assertRecord(value, path86) {
25840
+ function assertRecord(value, path91) {
25841
25841
  if (!isRecord(value)) {
25842
- throw new ManifestMalformedError(`Expected object at \`${path86}\`.`);
25842
+ throw new ManifestMalformedError(`Expected object at \`${path91}\`.`);
25843
25843
  }
25844
25844
  return value;
25845
25845
  }
25846
- function assertArray(value, path86) {
25846
+ function assertArray(value, path91) {
25847
25847
  if (!Array.isArray(value)) {
25848
- throw new ManifestMalformedError(`Expected array at \`${path86}\`.`);
25848
+ throw new ManifestMalformedError(`Expected array at \`${path91}\`.`);
25849
25849
  }
25850
25850
  return value;
25851
25851
  }
25852
- function assertString(value, path86) {
25852
+ function assertString(value, path91) {
25853
25853
  if (typeof value !== "string" || value.length === 0) {
25854
- throw new ManifestMalformedError(`Expected non-empty string at \`${path86}\`.`);
25854
+ throw new ManifestMalformedError(`Expected non-empty string at \`${path91}\`.`);
25855
25855
  }
25856
25856
  return value;
25857
25857
  }
25858
- function normalizeProvenance(value, path86) {
25859
- const record = assertRecord(value, path86);
25860
- const originType = assertString(record.originType, `${path86}.originType`);
25858
+ function normalizeProvenance(value, path91) {
25859
+ const record = assertRecord(value, path91);
25860
+ const originType = assertString(record.originType, `${path91}.originType`);
25861
25861
  const allowed = ["hosted", "local-extension", "derived-from-workflow"];
25862
25862
  if (!allowed.includes(originType)) {
25863
25863
  throw new ManifestMalformedError(
25864
- `Unknown provenance originType at \`${path86}.originType\`: ${originType}`
25864
+ `Unknown provenance originType at \`${path91}.originType\`: ${originType}`
25865
25865
  );
25866
25866
  }
25867
25867
  return {
@@ -33131,15 +33131,15 @@ async function runOpenAgentsHub(opts) {
33131
33131
  continue;
33132
33132
  }
33133
33133
  if (action === "health") {
33134
- const spinner14 = p25.spinner();
33135
- spinner14.start(`Checking ${config.endpoint}...`);
33134
+ const spinner15 = p25.spinner();
33135
+ spinner15.start(`Checking ${config.endpoint}...`);
33136
33136
  const health = await checkOpenAgentsHealth(config);
33137
33137
  if (health.available) {
33138
- spinner14.stop(
33138
+ spinner15.stop(
33139
33139
  `Backend reachable (${health.latencyMs}ms)` + (health.version ? ` version: ${health.version}` : "")
33140
33140
  );
33141
33141
  } else {
33142
- spinner14.stop(`Backend unavailable (${health.latencyMs}ms)`);
33142
+ spinner15.stop(`Backend unavailable (${health.latencyMs}ms)`);
33143
33143
  p25.note(
33144
33144
  [
33145
33145
  health.error ? `Error: ${health.error}` : "",
@@ -33257,17 +33257,17 @@ async function runSetupFlow(currentConfig) {
33257
33257
  p25.log.success("Configuration saved.");
33258
33258
  }
33259
33259
  async function runSessionListFlow(config) {
33260
- const spinner14 = p25.spinner();
33261
- spinner14.start("Loading sessions...");
33260
+ const spinner15 = p25.spinner();
33261
+ spinner15.start("Loading sessions...");
33262
33262
  let sessions;
33263
33263
  try {
33264
33264
  sessions = await listOpenAgentsSessions(config);
33265
33265
  } catch (err) {
33266
- spinner14.stop("Failed to load sessions.");
33266
+ spinner15.stop("Failed to load sessions.");
33267
33267
  p25.log.error(err.message);
33268
33268
  return "back";
33269
33269
  }
33270
- spinner14.stop(`${sessions.length} session${sessions.length !== 1 ? "s" : ""} found.`);
33270
+ spinner15.stop(`${sessions.length} session${sessions.length !== 1 ? "s" : ""} found.`);
33271
33271
  if (sessions.length === 0) {
33272
33272
  p25.note("No agent sessions found. Create one to get started.", "Nothing found");
33273
33273
  return "back";
@@ -33340,18 +33340,18 @@ async function runCreateSessionFlow(config) {
33340
33340
  initialValue: true
33341
33341
  });
33342
33342
  if (p25.isCancel(confirmed) || !confirmed) return;
33343
- const spinner14 = p25.spinner();
33344
- spinner14.start("Creating session...");
33343
+ const spinner15 = p25.spinner();
33344
+ spinner15.start("Creating session...");
33345
33345
  try {
33346
33346
  const session = await createOpenAgentsSession(config, {
33347
33347
  prompt: String(prompt).trim(),
33348
33348
  repoUrl: String(repoUrl).trim() || void 0,
33349
33349
  branch: String(branch).trim() || void 0
33350
33350
  });
33351
- spinner14.stop("Session created.");
33351
+ spinner15.stop("Session created.");
33352
33352
  printSessionCard(session);
33353
33353
  } catch (err) {
33354
- spinner14.stop("Failed to create session.");
33354
+ spinner15.stop("Failed to create session.");
33355
33355
  p25.log.error(err.message);
33356
33356
  }
33357
33357
  }
@@ -33361,11 +33361,11 @@ async function runResumeSessionFlow(config) {
33361
33361
  placeholder: "Paste the session ID to resume"
33362
33362
  });
33363
33363
  if (p25.isCancel(sessionId) || !String(sessionId).trim()) return;
33364
- const spinner14 = p25.spinner();
33365
- spinner14.start("Resuming session...");
33364
+ const spinner15 = p25.spinner();
33365
+ spinner15.start("Resuming session...");
33366
33366
  try {
33367
33367
  const session = await resumeOpenAgentsSession(config, String(sessionId).trim());
33368
- spinner14.stop("Session resumed.");
33368
+ spinner15.stop("Session resumed.");
33369
33369
  printSessionCard(session);
33370
33370
  const events = await pollSessionEvents(config, session.sessionId);
33371
33371
  if (events.length > 0) {
@@ -33378,7 +33378,7 @@ async function runResumeSessionFlow(config) {
33378
33378
  console.log("");
33379
33379
  }
33380
33380
  } catch (err) {
33381
- spinner14.stop("Failed to resume session.");
33381
+ spinner15.stop("Failed to resume session.");
33382
33382
  p25.log.error(err.message);
33383
33383
  }
33384
33384
  }
@@ -34863,9 +34863,9 @@ var NPM_REGISTRY = "https://registry.npmjs.org";
34863
34863
  function isoNow() {
34864
34864
  return (/* @__PURE__ */ new Date()).toISOString();
34865
34865
  }
34866
- async function withTimeout(p39, ms, label) {
34866
+ async function withTimeout(p42, ms, label) {
34867
34867
  return await Promise.race([
34868
- p39,
34868
+ p42,
34869
34869
  new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} timeout`)), ms))
34870
34870
  ]);
34871
34871
  }
@@ -35021,8 +35021,8 @@ async function probeGithubDirectAuth(_timeoutMs) {
35021
35021
  async function probeKitForksIndex(_timeoutMs) {
35022
35022
  try {
35023
35023
  const { resolveKitForksIndexPath: resolveKitForksIndexPath2 } = await Promise.resolve().then(() => (init_kit_forks_home(), kit_forks_home_exports));
35024
- const p39 = resolveKitForksIndexPath2();
35025
- if (!fs56.existsSync(p39)) {
35024
+ const p42 = resolveKitForksIndexPath2();
35025
+ if (!fs56.existsSync(p42)) {
35026
35026
  return {
35027
35027
  componentId: "kit-forks-index",
35028
35028
  level: "operational",
@@ -35030,14 +35030,14 @@ async function probeKitForksIndex(_timeoutMs) {
35030
35030
  lastCheckedAt: isoNow()
35031
35031
  };
35032
35032
  }
35033
- const parsed = JSON.parse(fs56.readFileSync(p39, "utf8"));
35033
+ const parsed = JSON.parse(fs56.readFileSync(p42, "utf8"));
35034
35034
  const count = Array.isArray(parsed.entries) ? parsed.entries.length : 0;
35035
35035
  return {
35036
35036
  componentId: "kit-forks-index",
35037
35037
  level: "operational",
35038
35038
  summary: `${count} fork(s) registered locally.`,
35039
35039
  lastCheckedAt: isoNow(),
35040
- detail: { indexPath: p39, count }
35040
+ detail: { indexPath: p42, count }
35041
35041
  };
35042
35042
  } catch (err) {
35043
35043
  return {
@@ -35664,10 +35664,10 @@ init_fork_trace();
35664
35664
  init_kit_forks_home();
35665
35665
  init_table_renderer();
35666
35666
  function readLocalForkHead(forkPath) {
35667
- const p39 = resolveInForkRegistrationPath(forkPath);
35668
- if (!fs68.existsSync(p39)) return null;
35667
+ const p42 = resolveInForkRegistrationPath(forkPath);
35668
+ if (!fs68.existsSync(p42)) return null;
35669
35669
  try {
35670
- const parsed = JSON.parse(fs68.readFileSync(p39, "utf8"));
35670
+ const parsed = JSON.parse(fs68.readFileSync(p42, "utf8"));
35671
35671
  if (typeof parsed.forkId !== "string" || typeof parsed.kitId !== "string") return null;
35672
35672
  return { forkId: parsed.forkId, kitId: parsed.kitId };
35673
35673
  } catch {
@@ -36089,33 +36089,33 @@ function buildDriftArtifactSummary(report, plan, policy) {
36089
36089
  }
36090
36090
  }
36091
36091
  for (const action of plan.actions) {
36092
- const p39 = action.targetPath;
36093
- if (isUntouchable(policy, p39)) {
36094
- skippedUntouchable.push({ path: p39, note: "Untouchable per policy.untouchablePaths." });
36092
+ const p42 = action.targetPath;
36093
+ if (isUntouchable(policy, p42)) {
36094
+ skippedUntouchable.push({ path: p42, note: "Untouchable per policy.untouchablePaths." });
36095
36095
  continue;
36096
36096
  }
36097
36097
  if (action.needsConfirmation) {
36098
36098
  needsConfirmation.push({
36099
- path: p39,
36099
+ path: p42,
36100
36100
  note: action.confirmationReason ?? "Policy requires explicit confirmation."
36101
36101
  });
36102
36102
  continue;
36103
36103
  }
36104
36104
  switch (action.actionType) {
36105
36105
  case "add_file":
36106
- safeAdditions.push({ path: p39, note: "Upstream scaffold absent from fork \u2014 safe to add." });
36106
+ safeAdditions.push({ path: p42, note: "Upstream scaffold absent from fork \u2014 safe to add." });
36107
36107
  break;
36108
36108
  case "update_package_json_deps":
36109
- safeUpdates.push({ path: p39, note: "Dependency merge (additive-only)." });
36109
+ safeUpdates.push({ path: p42, note: "Dependency merge (additive-only)." });
36110
36110
  break;
36111
36111
  case "patch_manifest":
36112
- safeUpdates.push({ path: p39, note: "kit.json alignment field patch (safe allow-list)." });
36112
+ safeUpdates.push({ path: p42, note: "kit.json alignment field patch (safe allow-list)." });
36113
36113
  break;
36114
36114
  case "skip_user_modified":
36115
- skippedUserModified.push({ path: p39, note: "Preserved \u2014 user has modified this file." });
36115
+ skippedUserModified.push({ path: p42, note: "Preserved \u2014 user has modified this file." });
36116
36116
  break;
36117
36117
  case "add_custom_skill":
36118
- customSkills.push({ path: p39, note: "User-authored custom skill \u2014 preserved unchanged." });
36118
+ customSkills.push({ path: p42, note: "User-authored custom skill \u2014 preserved unchanged." });
36119
36119
  break;
36120
36120
  }
36121
36121
  }
@@ -36407,8 +36407,8 @@ async function fleetApprovals(opts) {
36407
36407
  p33.log.message(
36408
36408
  ` \xB7 ${pc51.cyan(entry.jobId)} fork=${entry.forkLabel ?? entry.forkId} created=${entry.createdAt.slice(0, 19)}`
36409
36409
  );
36410
- for (const path86 of entry.pendingPaths.slice(0, 6)) {
36411
- p33.log.message(` ${pc51.dim("awaits")} ${path86}`);
36410
+ for (const path91 of entry.pendingPaths.slice(0, 6)) {
36411
+ p33.log.message(` ${pc51.dim("awaits")} ${path91}`);
36412
36412
  }
36413
36413
  if (entry.pendingPaths.length > 6) {
36414
36414
  p33.log.message(` ${pc51.dim(`\u2026 +${entry.pendingPaths.length - 6} more`)}`);
@@ -36979,9 +36979,9 @@ function buildFilename(slug) {
36979
36979
  }
36980
36980
  function readForkMeta(forkPath) {
36981
36981
  try {
36982
- const p39 = path81.resolve(resolveInForkStateDir(forkPath), "fork.json");
36983
- if (fs71.existsSync(p39)) {
36984
- const parsed = JSON.parse(fs71.readFileSync(p39, "utf8"));
36982
+ const p42 = path81.resolve(resolveInForkStateDir(forkPath), "fork.json");
36983
+ if (fs71.existsSync(p42)) {
36984
+ const parsed = JSON.parse(fs71.readFileSync(p42, "utf8"));
36985
36985
  return {
36986
36986
  forkId: parsed.forkId ?? "unknown",
36987
36987
  kitId: parsed.kitId ?? "growthub-custom-workspace-starter-v1"
@@ -37022,8 +37022,8 @@ function listProposals(forkPath, opts = {}) {
37022
37022
  function inspectProposal(forkPath, slug) {
37023
37023
  const proposalPath = resolveProposalPath(forkPath, slug);
37024
37024
  if (proposalPath) return readProposalFile(proposalPath);
37025
- const p39 = path81.resolve(promotedDir(forkPath), slug + ".json");
37026
- if (fs71.existsSync(p39)) return readProposalFile(p39);
37025
+ const p42 = path81.resolve(promotedDir(forkPath), slug + ".json");
37026
+ if (fs71.existsSync(p42)) return readProposalFile(p42);
37027
37027
  return null;
37028
37028
  }
37029
37029
  function proposeCapability(input) {
@@ -37240,11 +37240,11 @@ function runList2(opts) {
37240
37240
  { key: "createdAt", label: "created", maxWidth: 24 },
37241
37241
  { key: "summary", label: "summary", maxWidth: 48 }
37242
37242
  ],
37243
- rows: proposals.map((p39) => ({
37244
- slug: p39.slug,
37245
- status: p39.status,
37246
- createdAt: p39.createdAt.slice(0, 16).replace("T", " "),
37247
- summary: p39.summary
37243
+ rows: proposals.map((p42) => ({
37244
+ slug: p42.slug,
37245
+ status: p42.status,
37246
+ createdAt: p42.createdAt.slice(0, 16).replace("T", " "),
37247
+ summary: p42.summary
37248
37248
  }))
37249
37249
  }));
37250
37250
  console.log("");
@@ -37364,7 +37364,7 @@ function runReject(slug, opts) {
37364
37364
  console.log("");
37365
37365
  }
37366
37366
  function registerWorkspaceImproveCommands(program2) {
37367
- const workspace = program2.command("workspace").description("Governed workspace operations \u2014 deploy status, self-improving loop, capability proposals");
37367
+ const workspace = program2.command("workspace").description("Governed workspace operations \u2014 status, QA, deploy, upstream, surface, portal, self-improving");
37368
37368
  const improve = workspace.command("improve").description("Manage the self-improving capability proposal lifecycle").addHelpText("after", `
37369
37369
  Examples:
37370
37370
  $ growthub workspace improve propose --from-run demo
@@ -37615,24 +37615,1303 @@ async function runDeployChecklist(opts) {
37615
37615
  );
37616
37616
  }
37617
37617
  }
37618
+ function detectAppRoot(forkPath) {
37619
+ for (const rel of ["apps/workspace", "apps/agency-portal", "apps/portal", "studio"]) {
37620
+ if (fs72.existsSync(path83.join(forkPath, rel, "package.json"))) return rel;
37621
+ }
37622
+ return null;
37623
+ }
37624
+ function detectVercelProject(forkPath) {
37625
+ return [
37626
+ path83.join(forkPath, "vercel.json"),
37627
+ path83.join(forkPath, ".vercel/project.json"),
37628
+ path83.join(forkPath, "apps/workspace/vercel.json"),
37629
+ path83.join(forkPath, "apps/workspace/.vercel/project.json")
37630
+ ].some((c) => fs72.existsSync(c));
37631
+ }
37632
+ function detectEnvVarsNeeded(forkPath) {
37633
+ for (const rel of [".env.example", "apps/workspace/.env.example"]) {
37634
+ const p210 = path83.join(forkPath, rel);
37635
+ if (!fs72.existsSync(p210)) continue;
37636
+ try {
37637
+ return fs72.readFileSync(p210, "utf8").split("\n").filter((l) => l.match(/^[A-Z_]+=/) && !l.startsWith("#")).map((l) => l.split("=")[0]);
37638
+ } catch {
37639
+ }
37640
+ }
37641
+ return [];
37642
+ }
37643
+ function runDeployCheck(opts) {
37644
+ const forkPath = opts.fork ? path83.resolve(opts.fork) : process.cwd();
37645
+ const ds = computeDeployStatus(forkPath);
37646
+ const appRoot = detectAppRoot(forkPath);
37647
+ const vercelProjectDetected = detectVercelProject(forkPath);
37648
+ const envVarsNeeded = detectEnvVarsNeeded(forkPath);
37649
+ const recommendedCommands = [];
37650
+ if (!ds.bridge.connected && !ds.github.connected) recommendedCommands.push("growthub auth login");
37651
+ if (!ds.fork.registered) recommendedCommands.push("growthub kit fork register .");
37652
+ if (appRoot) recommendedCommands.push(`cd ${appRoot} && npm install && npm run build`);
37653
+ if (appRoot && !vercelProjectDetected) recommendedCommands.push(`cd ${appRoot} && vercel link`);
37654
+ const status = ds.ready ? "ready" : ds.missingSteps.length > 0 ? "needs_action" : "blocked";
37655
+ const result = {
37656
+ status,
37657
+ forkPath,
37658
+ canDeploy: ds.ready && appRoot !== null,
37659
+ bridge: { connected: ds.bridge.connected, login: ds.bridge.login },
37660
+ github: { connected: ds.github.connected, login: ds.github.login },
37661
+ fork: { registered: ds.fork.registered, forkId: ds.fork.forkId, hasRemote: ds.fork.hasRemote },
37662
+ missingSteps: ds.missingSteps,
37663
+ envVarsNeeded,
37664
+ appRoot,
37665
+ vercelProjectDetected,
37666
+ nextCommand: ds.nextCommand ?? recommendedCommands[0] ?? null,
37667
+ recommendedCommands
37668
+ };
37669
+ if (opts.json) {
37670
+ console.log(JSON.stringify(result, null, 2));
37671
+ return;
37672
+ }
37673
+ const tick = (ok) => ok ? pc54.green("\u2713") : pc54.red("\u2717");
37674
+ const dot = (ok) => ok ? pc54.green("\u25CF") : pc54.dim("\u25CB");
37675
+ console.log("");
37676
+ console.log(pc54.bold("Workspace Deploy Check"));
37677
+ console.log(pc54.dim("\u2500".repeat(60)));
37678
+ console.log(` ${tick(result.bridge.connected)} Bridge/auth ${result.bridge.connected ? pc54.green("connected") + pc54.dim(` \xB7 ${result.bridge.login ?? ""}`) : pc54.dim("not connected")}`);
37679
+ console.log(` ${tick(result.github.connected)} GitHub ${result.github.connected ? pc54.green("connected") + pc54.dim(` \xB7 ${result.github.login ?? ""}`) : pc54.dim("not connected")}`);
37680
+ console.log(` ${tick(result.fork.registered)} Fork registered ${result.fork.registered ? pc54.green("yes") + pc54.dim(` \xB7 ${result.fork.forkId ?? ""}`) : pc54.dim("no")}`);
37681
+ console.log(` ${dot(result.appRoot !== null)} App root ${result.appRoot ?? pc54.dim("not detected")}`);
37682
+ console.log(` ${dot(result.vercelProjectDetected)} Vercel project ${result.vercelProjectDetected ? pc54.green("detected") : pc54.dim("not detected")}`);
37683
+ if (result.envVarsNeeded.length > 0) console.log(` ${dot(false)} Env vars needed ${pc54.dim(`${result.envVarsNeeded.length} from .env.example`)}`);
37684
+ console.log("");
37685
+ const col = { ready: pc54.green, needs_action: pc54.yellow, blocked: pc54.red };
37686
+ console.log(` Status: ${col[result.status](result.status.replace("_", " "))}`);
37687
+ console.log(` Can deploy: ${result.canDeploy ? pc54.green("yes") : pc54.red("no")}`);
37688
+ if (result.missingSteps.length > 0) {
37689
+ console.log("");
37690
+ console.log(pc54.yellow(" Missing:"));
37691
+ for (const s of result.missingSteps) console.log(pc54.dim(` \xB7 ${s}`));
37692
+ }
37693
+ if (result.recommendedCommands.length > 0) {
37694
+ console.log("");
37695
+ console.log(pc54.dim(" Recommended:"));
37696
+ for (const c of result.recommendedCommands) console.log(pc54.dim(` ${pc54.cyan(c)}`));
37697
+ }
37698
+ console.log("");
37699
+ console.log(pc54.dim(" Agent output: growthub workspace deploy check --json"));
37700
+ console.log("");
37701
+ }
37702
+ function runDeployVercel(opts) {
37703
+ const forkPath = opts.fork ? path83.resolve(opts.fork) : process.cwd();
37704
+ const appRoot = detectAppRoot(forkPath);
37705
+ const vercelProjectDetected = detectVercelProject(forkPath);
37706
+ const envVarsNeeded = detectEnvVarsNeeded(forkPath);
37707
+ const result = {
37708
+ forkPath,
37709
+ appRoot,
37710
+ appRootAbsolute: appRoot ? path83.resolve(forkPath, appRoot) : null,
37711
+ vercelProjectDetected,
37712
+ envVarsNeeded,
37713
+ deployCommands: appRoot ? [`cd ${appRoot}`, "npm install", "npm run build", "vercel"] : ["# No app root detected"]
37714
+ };
37715
+ if (opts.json) {
37716
+ console.log(JSON.stringify(result, null, 2));
37717
+ return;
37718
+ }
37719
+ if (opts.printEnv) {
37720
+ console.log("");
37721
+ console.log(pc54.bold("Required Env Vars (from .env.example):"));
37722
+ console.log(pc54.dim("\u2500".repeat(60)));
37723
+ if (envVarsNeeded.length === 0) {
37724
+ console.log(pc54.dim(" No .env.example found."));
37725
+ } else {
37726
+ for (const v of envVarsNeeded) console.log(` ${v}=`);
37727
+ }
37728
+ console.log("");
37729
+ return;
37730
+ }
37731
+ console.log("");
37732
+ console.log(pc54.bold("Vercel Deploy Guide"));
37733
+ console.log(pc54.dim("\u2500".repeat(60)));
37734
+ console.log(` App root: ${appRoot ?? pc54.dim("not detected")}`);
37735
+ console.log(` Vercel project: ${vercelProjectDetected ? pc54.green("detected") : pc54.dim("not detected")}`);
37736
+ console.log(` Env vars: ${envVarsNeeded.length > 0 ? `${envVarsNeeded.length} var(s)` : pc54.dim("none from .env.example")}`);
37737
+ console.log("");
37738
+ console.log(pc54.dim(" Steps:"));
37739
+ for (const cmd of result.deployCommands) console.log(pc54.dim(` ${pc54.cyan(cmd)}`));
37740
+ console.log("");
37741
+ console.log(pc54.dim(" Print env: growthub workspace deploy vercel --print-env --json"));
37742
+ console.log("");
37743
+ }
37618
37744
  function registerWorkspaceDeployCommands(workspaceCommand) {
37619
37745
  const deploy = workspaceCommand.command("deploy").description("Deployment status and readiness check for a governed workspace").addHelpText("after", `
37620
37746
  Examples:
37621
- $ growthub workspace deploy status
37622
37747
  $ growthub workspace deploy status --json
37748
+ $ growthub workspace deploy check --json
37749
+ $ growthub workspace deploy vercel --check --json
37750
+ $ growthub workspace deploy vercel --print-env --json
37623
37751
  $ growthub workspace deploy checklist
37624
- $ growthub workspace deploy status --fork ./my-workspace --json
37625
37752
 
37626
37753
  Docs: docs/WORKSPACE_DEPLOY_FLOW.md
37627
37754
  `);
37628
37755
  deploy.command("status").description("Show deployment readiness status \u2014 Bridge, GitHub, fork, agents, integrations").option("--fork <path>", "Fork root path (default: cwd)").option("--json", "Emit machine-readable JSON (agent-friendly)").action((opts) => {
37629
37756
  runDeployStatus(opts);
37630
37757
  });
37758
+ deploy.command("check").description("Fast deploy-readiness gate \u2014 canDeploy, missingSteps, appRoot, envVarsNeeded").option("--fork <path>", "Fork root path (default: cwd)").option("--json", "Emit machine-readable JSON (agent-friendly)").action((opts) => {
37759
+ runDeployCheck(opts);
37760
+ });
37761
+ deploy.command("vercel").description("Vercel-specific deploy guide and env var printer").option("--fork <path>", "Fork root path (default: cwd)").option("--check", "Run readiness check (alias for deploy check)").option("--print-env", "Print required env var names from .env.example").option("--json", "Emit machine-readable JSON").action((opts) => {
37762
+ if (opts.check) {
37763
+ runDeployCheck({ fork: opts.fork, json: opts.json });
37764
+ return;
37765
+ }
37766
+ runDeployVercel(opts);
37767
+ });
37631
37768
  deploy.command("checklist").description("Interactive step-by-step deployment checklist for humans").option("--fork <path>", "Fork root path (default: cwd)").action(async (opts) => {
37632
37769
  await runDeployChecklist(opts);
37633
37770
  });
37634
37771
  }
37635
37772
 
37773
+ // src/commands/workspace-status.ts
37774
+ init_session_store();
37775
+ init_token_store();
37776
+ init_kit_forks_home();
37777
+ import pc55 from "picocolors";
37778
+ import fs73 from "node:fs";
37779
+ import path84 from "node:path";
37780
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
37781
+ init_fork_registry();
37782
+ function resolveCliVersion() {
37783
+ try {
37784
+ const moduleDir = path84.dirname(fileURLToPath7(import.meta.url));
37785
+ const candidates = [
37786
+ path84.resolve(moduleDir, "../package.json"),
37787
+ path84.resolve(moduleDir, "../../package.json"),
37788
+ path84.resolve(moduleDir, "../../../package.json")
37789
+ ];
37790
+ for (const candidate of candidates) {
37791
+ if (!fs73.existsSync(candidate)) continue;
37792
+ const parsed = JSON.parse(fs73.readFileSync(candidate, "utf8"));
37793
+ if (parsed?.name === "@growthub/cli" && typeof parsed.version === "string") return parsed.version;
37794
+ }
37795
+ } catch {
37796
+ }
37797
+ return "unknown";
37798
+ }
37799
+ function checkBridge2() {
37800
+ const session = readSession();
37801
+ if (!session) return { connected: false };
37802
+ const expired = isSessionExpired(session);
37803
+ return { connected: !expired, email: session.email, expired };
37804
+ }
37805
+ function checkGithub2() {
37806
+ const token = readGithubToken();
37807
+ if (!token) return { connected: false, source: "none" };
37808
+ const profile = readGithubProfile();
37809
+ const expired = isGithubTokenExpired(token);
37810
+ return {
37811
+ connected: !expired,
37812
+ login: profile?.login ?? token.login,
37813
+ source: "direct",
37814
+ expired
37815
+ };
37816
+ }
37817
+ function checkFork2(forkPath) {
37818
+ const stateDir = resolveInForkStateDir(forkPath);
37819
+ const forkJsonPath = path84.resolve(stateDir, "fork.json");
37820
+ if (!fs73.existsSync(forkJsonPath)) return { registered: false, hasRemote: false };
37821
+ try {
37822
+ const parsed = JSON.parse(fs73.readFileSync(forkJsonPath, "utf8"));
37823
+ const policyPath = path84.resolve(stateDir, "policy.json");
37824
+ let remoteSyncMode = "off";
37825
+ if (fs73.existsSync(policyPath)) {
37826
+ try {
37827
+ const policy = JSON.parse(fs73.readFileSync(policyPath, "utf8"));
37828
+ remoteSyncMode = policy.remoteSyncMode ?? "off";
37829
+ } catch {
37830
+ }
37831
+ }
37832
+ const forks = listKitForkRegistrations();
37833
+ const reg = forks.find((f) => f.forkId === parsed.forkId);
37834
+ return {
37835
+ registered: true,
37836
+ forkId: parsed.forkId,
37837
+ kitId: parsed.kitId,
37838
+ label: reg?.label ?? parsed.label,
37839
+ hasRemote: Boolean(parsed.remote),
37840
+ remoteUrl: parsed.remote?.htmlUrl ?? parsed.remote?.cloneUrl,
37841
+ remoteSyncMode
37842
+ };
37843
+ } catch {
37844
+ return { registered: false, hasRemote: false };
37845
+ }
37846
+ }
37847
+ function checkAgentBindings2(forkPath) {
37848
+ const agentsDir = path84.resolve(resolveInForkStateDir(forkPath), "agents");
37849
+ if (!fs73.existsSync(agentsDir)) return { count: 0, agents: [] };
37850
+ const files = fs73.readdirSync(agentsDir).filter((f) => f.endsWith(".json"));
37851
+ const agents2 = [];
37852
+ for (const f of files) {
37853
+ try {
37854
+ const parsed = JSON.parse(fs73.readFileSync(path84.resolve(agentsDir, f), "utf8"));
37855
+ if (parsed.agentSlug) agents2.push(parsed.agentSlug);
37856
+ } catch {
37857
+ }
37858
+ }
37859
+ return { count: agents2.length, agents: agents2 };
37860
+ }
37861
+ function checkConfig(forkPath) {
37862
+ const candidates = [
37863
+ path84.resolve(forkPath, "growthub.config.json"),
37864
+ path84.resolve(forkPath, "apps/workspace/growthub.config.json")
37865
+ ];
37866
+ for (const configPath of candidates) {
37867
+ if (!fs73.existsSync(configPath)) continue;
37868
+ try {
37869
+ const parsed = JSON.parse(fs73.readFileSync(configPath, "utf8"));
37870
+ return {
37871
+ found: true,
37872
+ configPath,
37873
+ workspaceId: parsed.workspaceId,
37874
+ persistenceMode: parsed.persistence?.mode ?? "unknown",
37875
+ valid: true
37876
+ };
37877
+ } catch (err) {
37878
+ return {
37879
+ found: true,
37880
+ configPath,
37881
+ valid: false,
37882
+ error: err instanceof Error ? err.message : String(err)
37883
+ };
37884
+ }
37885
+ }
37886
+ return { found: false, valid: false };
37887
+ }
37888
+ function detectAppPaths(forkPath) {
37889
+ const candidates = ["apps/workspace", "apps/agency-portal", "apps/portal", "studio"];
37890
+ const detected = candidates.filter((rel) => fs73.existsSync(path84.resolve(forkPath, rel)));
37891
+ return { detected };
37892
+ }
37893
+ function computeWorkspaceStatus(forkPath) {
37894
+ const cliVersion = resolveCliVersion();
37895
+ const config = checkConfig(forkPath);
37896
+ const bridge = checkBridge2();
37897
+ const github = checkGithub2();
37898
+ const fork = checkFork2(forkPath);
37899
+ const agentBindings = checkAgentBindings2(forkPath);
37900
+ const apps = detectAppPaths(forkPath);
37901
+ const siHealth = checkSelfImprovingHealth(forkPath);
37902
+ const issues2 = [];
37903
+ const recommendedCommands = [];
37904
+ if (!bridge.connected && !github.connected) {
37905
+ issues2.push("No auth: Growthub Bridge and GitHub are both disconnected");
37906
+ recommendedCommands.push("growthub auth login");
37907
+ }
37908
+ if (!config.found) {
37909
+ issues2.push("No growthub.config.json found \u2014 run workspace init or starter init");
37910
+ recommendedCommands.push("growthub workspace init");
37911
+ } else if (!config.valid) {
37912
+ issues2.push(`growthub.config.json parse error: ${config.error}`);
37913
+ }
37914
+ if (!fork.registered) {
37915
+ issues2.push("Fork not registered \u2014 run: growthub kit fork register .");
37916
+ recommendedCommands.push("growthub kit fork register .");
37917
+ }
37918
+ const overall = issues2.length === 0 ? "healthy" : (bridge.connected || github.connected) && fork.registered ? "needs_action" : "degraded";
37919
+ return {
37920
+ cliVersion,
37921
+ forkPath,
37922
+ config,
37923
+ bridge,
37924
+ github,
37925
+ fork,
37926
+ agentBindings,
37927
+ apps,
37928
+ selfImproving: {
37929
+ detected: siHealth.detected,
37930
+ proposalCount: siHealth.proposalCount,
37931
+ promotedCount: siHealth.promotedCount
37932
+ },
37933
+ overall,
37934
+ issues: issues2,
37935
+ recommendedCommands
37936
+ };
37937
+ }
37938
+ function printWorkspaceStatus(status) {
37939
+ const tick = (ok) => ok ? pc55.green("\u2713") : pc55.red("\u2717");
37940
+ const info = (ok) => ok ? pc55.green("\u25CF") : pc55.dim("\u25CB");
37941
+ console.log("");
37942
+ console.log(pc55.bold("Workspace Status"));
37943
+ console.log(pc55.dim("\u2500".repeat(60)));
37944
+ console.log(` CLI version ${pc55.cyan(status.cliVersion)}`);
37945
+ console.log(` Workspace path ${pc55.dim(status.forkPath)}`);
37946
+ console.log("");
37947
+ const cfgLabel = status.config.found ? status.config.valid ? pc55.green("valid") + pc55.dim(` \xB7 ${status.config.configPath?.replace(status.forkPath, ".")}`) : pc55.red("invalid") + pc55.dim(` \xB7 ${status.config.error}`) : pc55.dim("not found");
37948
+ console.log(` ${tick(status.config.found && status.config.valid)} Config ${cfgLabel}`);
37949
+ if (status.config.workspaceId) {
37950
+ console.log(` ${pc55.dim("workspace_id:")} ${pc55.dim(status.config.workspaceId)}`);
37951
+ }
37952
+ if (status.config.persistenceMode) {
37953
+ console.log(` ${pc55.dim("persistence:")} ${pc55.dim(status.config.persistenceMode)}`);
37954
+ }
37955
+ console.log(` ${tick(status.bridge.connected)} Growthub Bridge ${status.bridge.connected ? pc55.green("connected") + pc55.dim(` \xB7 ${status.bridge.email ?? ""}`) : pc55.dim("not connected \u2014 run: growthub auth login")}`);
37956
+ console.log(` ${tick(status.github.connected)} GitHub ${status.github.connected ? pc55.green("connected") + pc55.dim(` \xB7 ${status.github.login ?? ""}`) : pc55.dim("not connected \u2014 run: growthub github login")}`);
37957
+ console.log(` ${tick(status.fork.registered)} Fork registered ${status.fork.registered ? pc55.green("yes") + pc55.dim(` \xB7 ${status.fork.forkId ?? ""}`) : pc55.dim("no \u2014 run: growthub kit fork register .")}`);
37958
+ console.log(` ${info(status.fork.hasRemote)} GitHub remote ${status.fork.hasRemote ? pc55.green("connected") + pc55.dim(` \xB7 ${status.fork.remoteUrl ?? ""}`) : pc55.dim("none")}`);
37959
+ console.log(` ${info(status.agentBindings.count > 0)} Agent bindings ${status.agentBindings.count > 0 ? pc55.green(`${status.agentBindings.count} bound`) + pc55.dim(` \xB7 ${status.agentBindings.agents.join(", ")}`) : pc55.dim("none")}`);
37960
+ if (status.apps.detected.length > 0) {
37961
+ console.log(` ${info(true)} Apps detected ${pc55.dim(status.apps.detected.join(", "))}`);
37962
+ }
37963
+ console.log(` ${info(status.selfImproving.detected)} Self-improving ${status.selfImproving.detected ? pc55.cyan(`${status.selfImproving.proposalCount} proposals \xB7 ${status.selfImproving.promotedCount} promoted`) : pc55.dim("not active")}`);
37964
+ console.log("");
37965
+ const overallColor = {
37966
+ healthy: pc55.green,
37967
+ needs_action: pc55.yellow,
37968
+ degraded: pc55.red
37969
+ };
37970
+ console.log(` Overall: ${overallColor[status.overall](status.overall.replace("_", " "))}`);
37971
+ if (status.issues.length > 0) {
37972
+ console.log("");
37973
+ console.log(pc55.yellow(" Issues:"));
37974
+ for (const issue of status.issues) {
37975
+ console.log(pc55.dim(` \xB7 ${issue}`));
37976
+ }
37977
+ }
37978
+ if (status.recommendedCommands.length > 0) {
37979
+ console.log("");
37980
+ console.log(pc55.dim(" Recommended next:"));
37981
+ for (const cmd of status.recommendedCommands) {
37982
+ console.log(pc55.dim(` ${pc55.cyan(cmd)}`));
37983
+ }
37984
+ }
37985
+ console.log("");
37986
+ console.log(pc55.dim(" Agent output: growthub workspace status --json"));
37987
+ console.log(pc55.dim(" Deploy check: growthub workspace deploy check --json"));
37988
+ console.log(pc55.dim(" Full QA: growthub workspace qa --json"));
37989
+ console.log("");
37990
+ }
37991
+ function registerWorkspaceStatusCommands(workspaceCmd) {
37992
+ workspaceCmd.command("status").description("Unified workspace health snapshot \u2014 bridge, GitHub, fork, agents, config, apps").option("--fork <path>", "Fork root path (default: cwd)").option("--json", "Emit machine-readable JSON (agent-friendly)").addHelpText("after", `
37993
+ Examples:
37994
+ $ growthub workspace status
37995
+ $ growthub workspace status --json
37996
+ $ growthub workspace status --fork ./my-workspace --json
37997
+
37998
+ JSON shape:
37999
+ { cliVersion, forkPath, config, bridge, github, fork, agentBindings, apps, selfImproving, overall, issues, recommendedCommands }
38000
+
38001
+ Docs: docs/WORKSPACE_DEPLOY_FLOW.md
38002
+ `).action((opts) => {
38003
+ const forkPath = opts.fork ? path84.resolve(opts.fork) : process.cwd();
38004
+ const status = computeWorkspaceStatus(forkPath);
38005
+ if (opts.json) {
38006
+ console.log(JSON.stringify(status, null, 2));
38007
+ return;
38008
+ }
38009
+ printWorkspaceStatus(status);
38010
+ });
38011
+ }
38012
+
38013
+ // src/commands/workspace-qa.ts
38014
+ init_kit_forks_home();
38015
+ import * as p37 from "@clack/prompts";
38016
+ import pc56 from "picocolors";
38017
+ import fs74 from "node:fs";
38018
+ import path85 from "node:path";
38019
+ import { spawnSync as spawnSync9 } from "node:child_process";
38020
+ function checkWorkspaceConfig(forkPath) {
38021
+ const candidates = [
38022
+ path85.resolve(forkPath, "growthub.config.json"),
38023
+ path85.resolve(forkPath, "apps/workspace/growthub.config.json")
38024
+ ];
38025
+ for (const configPath of candidates) {
38026
+ if (!fs74.existsSync(configPath)) continue;
38027
+ try {
38028
+ JSON.parse(fs74.readFileSync(configPath, "utf8"));
38029
+ return { name: "workspace-config", status: "pass", detail: configPath.replace(forkPath, ".") };
38030
+ } catch (err) {
38031
+ return {
38032
+ name: "workspace-config",
38033
+ status: "fail",
38034
+ detail: `Parse error in ${configPath.replace(forkPath, ".")}`,
38035
+ fix: `Fix JSON syntax in ${configPath.replace(forkPath, ".")}`
38036
+ };
38037
+ }
38038
+ }
38039
+ return {
38040
+ name: "workspace-config",
38041
+ status: "warn",
38042
+ detail: "growthub.config.json not found",
38043
+ fix: "growthub workspace init OR growthub starter init --kit growthub-custom-workspace-starter-v1"
38044
+ };
38045
+ }
38046
+ function checkKitType(forkPath) {
38047
+ const hasKitJson = fs74.existsSync(path85.resolve(forkPath, "kit.json"));
38048
+ const hasSkillMd = fs74.existsSync(path85.resolve(forkPath, "SKILL.md"));
38049
+ const hasForkJson = fs74.existsSync(path85.resolve(resolveInForkStateDir(forkPath), "fork.json"));
38050
+ if (hasKitJson || hasSkillMd || hasForkJson) {
38051
+ const types = [
38052
+ ...hasKitJson ? ["kit.json"] : [],
38053
+ ...hasSkillMd ? ["SKILL.md"] : [],
38054
+ ...hasForkJson ? ["fork.json"] : []
38055
+ ];
38056
+ return { name: "workspace-type", status: "pass", detail: `Detected: ${types.join(", ")}` };
38057
+ }
38058
+ return {
38059
+ name: "workspace-type",
38060
+ status: "warn",
38061
+ detail: "No kit.json, SKILL.md, or fork.json found \u2014 may not be a governed workspace root",
38062
+ fix: "Run from inside a workspace directory, or pass --fork <path>"
38063
+ };
38064
+ }
38065
+ function checkEnvFile(forkPath) {
38066
+ const envExamplePaths = [
38067
+ path85.resolve(forkPath, ".env.example"),
38068
+ path85.resolve(forkPath, "apps/workspace/.env.example")
38069
+ ];
38070
+ const envPaths = [
38071
+ path85.resolve(forkPath, ".env"),
38072
+ path85.resolve(forkPath, "apps/workspace/.env"),
38073
+ path85.resolve(forkPath, "apps/workspace/.env.local")
38074
+ ];
38075
+ const examplePath = envExamplePaths.find((p42) => fs74.existsSync(p42));
38076
+ if (!examplePath) {
38077
+ return { name: "env-file", status: "skip", detail: "No .env.example found \u2014 skipping env var check" };
38078
+ }
38079
+ const hasEnv = envPaths.some((p42) => fs74.existsSync(p42));
38080
+ if (!hasEnv) {
38081
+ return {
38082
+ name: "env-file",
38083
+ status: "warn",
38084
+ detail: ".env.example found but no .env / .env.local present",
38085
+ fix: `cp ${examplePath.replace(forkPath, ".")} .env # then fill in required values`
38086
+ };
38087
+ }
38088
+ const exampleContent = fs74.readFileSync(examplePath, "utf8");
38089
+ const requiredKeys = exampleContent.split("\n").filter((line) => line.match(/^[A-Z_]+=/) && !line.startsWith("#")).map((line) => line.split("=")[0]);
38090
+ const envContent = envPaths.filter((p42) => fs74.existsSync(p42)).map((p42) => fs74.readFileSync(p42, "utf8")).join("\n");
38091
+ const missingKeys = requiredKeys.filter((key) => !envContent.includes(`${key}=`) || envContent.match(new RegExp(`${key}=\\s*$`, "m")));
38092
+ if (missingKeys.length > 0) {
38093
+ return {
38094
+ name: "env-file",
38095
+ status: "warn",
38096
+ detail: `${missingKeys.length} env var(s) may be unset: ${missingKeys.slice(0, 5).join(", ")}${missingKeys.length > 5 ? "\u2026" : ""}`,
38097
+ fix: "Fill in required env vars in .env or .env.local"
38098
+ };
38099
+ }
38100
+ return { name: "env-file", status: "pass", detail: `.env aligned with .env.example (${requiredKeys.length} vars)` };
38101
+ }
38102
+ function checkDependencies(forkPath) {
38103
+ const appPaths = [
38104
+ path85.resolve(forkPath, "apps/workspace"),
38105
+ path85.resolve(forkPath, "apps/agency-portal"),
38106
+ forkPath
38107
+ ];
38108
+ for (const appPath of appPaths) {
38109
+ const pkgPath = path85.resolve(appPath, "package.json");
38110
+ const nodeModules = path85.resolve(appPath, "node_modules");
38111
+ if (fs74.existsSync(pkgPath)) {
38112
+ if (!fs74.existsSync(nodeModules)) {
38113
+ return {
38114
+ name: "dependencies",
38115
+ status: "warn",
38116
+ detail: `package.json found at ${appPath.replace(forkPath, ".")} but node_modules missing`,
38117
+ fix: `cd ${appPath.replace(forkPath, ".")} && npm install`
38118
+ };
38119
+ }
38120
+ return { name: "dependencies", status: "pass", detail: `node_modules present at ${appPath.replace(forkPath, ".")}` };
38121
+ }
38122
+ }
38123
+ return { name: "dependencies", status: "skip", detail: "No package.json found in workspace apps" };
38124
+ }
38125
+ function checkForkRegistration(forkPath) {
38126
+ const stateDir = resolveInForkStateDir(forkPath);
38127
+ const forkJsonPath = path85.resolve(stateDir, "fork.json");
38128
+ if (!fs74.existsSync(forkJsonPath)) {
38129
+ return {
38130
+ name: "fork-registration",
38131
+ status: "warn",
38132
+ detail: "Fork not registered \u2014 workspace is untracked",
38133
+ fix: "growthub kit fork register ."
38134
+ };
38135
+ }
38136
+ try {
38137
+ const parsed = JSON.parse(fs74.readFileSync(forkJsonPath, "utf8"));
38138
+ return {
38139
+ name: "fork-registration",
38140
+ status: "pass",
38141
+ detail: `fork-id: ${parsed.forkId ?? "?"} \xB7 kit: ${parsed.kitId ?? "?"}`
38142
+ };
38143
+ } catch {
38144
+ return { name: "fork-registration", status: "fail", detail: "fork.json is malformed", fix: "growthub kit fork register . --force" };
38145
+ }
38146
+ }
38147
+ function checkAppRoutes(forkPath) {
38148
+ const routeCandidates = [
38149
+ path85.resolve(forkPath, "apps/workspace/app/page.jsx"),
38150
+ path85.resolve(forkPath, "apps/workspace/app/page.tsx"),
38151
+ path85.resolve(forkPath, "apps/workspace/src/app/page.tsx"),
38152
+ path85.resolve(forkPath, "apps/workspace/pages/index.jsx"),
38153
+ path85.resolve(forkPath, "apps/workspace/pages/index.tsx")
38154
+ ];
38155
+ const found = routeCandidates.find((r) => fs74.existsSync(r));
38156
+ if (found) {
38157
+ return { name: "app-routes", status: "pass", detail: `Root route: ${found.replace(forkPath, ".")}` };
38158
+ }
38159
+ const appDir = path85.resolve(forkPath, "apps/workspace");
38160
+ if (!fs74.existsSync(appDir)) {
38161
+ return { name: "app-routes", status: "skip", detail: "apps/workspace not present" };
38162
+ }
38163
+ return {
38164
+ name: "app-routes",
38165
+ status: "warn",
38166
+ detail: "apps/workspace exists but no root route page found",
38167
+ fix: "Check apps/workspace/app/page.jsx or pages/index.jsx"
38168
+ };
38169
+ }
38170
+ function checkSkillsValidate(forkPath, skipBuild) {
38171
+ if (skipBuild) return { name: "skills-validate", status: "skip", detail: "--skip-build passed" };
38172
+ const skillMd = path85.resolve(forkPath, "SKILL.md");
38173
+ if (!fs74.existsSync(skillMd)) {
38174
+ return { name: "skills-validate", status: "skip", detail: "No SKILL.md at root \u2014 skipping skills validate" };
38175
+ }
38176
+ const result = spawnSync9("growthub", ["skills", "validate", "--json"], {
38177
+ cwd: forkPath,
38178
+ stdio: "pipe",
38179
+ encoding: "utf8",
38180
+ timeout: 3e4
38181
+ });
38182
+ if (result.error) {
38183
+ return { name: "skills-validate", status: "skip", detail: "growthub not available in PATH for skills validate" };
38184
+ }
38185
+ if (result.status !== 0) {
38186
+ return {
38187
+ name: "skills-validate",
38188
+ status: "warn",
38189
+ detail: `skills validate failed (exit ${result.status ?? "?"})`,
38190
+ fix: "growthub skills validate"
38191
+ };
38192
+ }
38193
+ return { name: "skills-validate", status: "pass", detail: "skills validate passed" };
38194
+ }
38195
+ function computeWorkspaceQa(forkPath, opts = {}) {
38196
+ const checks = [
38197
+ checkWorkspaceConfig(forkPath),
38198
+ checkKitType(forkPath),
38199
+ checkEnvFile(forkPath),
38200
+ checkDependencies(forkPath),
38201
+ checkForkRegistration(forkPath),
38202
+ checkAppRoutes(forkPath),
38203
+ checkSkillsValidate(forkPath, opts.skipBuild ?? false)
38204
+ ];
38205
+ const passCount = checks.filter((c) => c.status === "pass").length;
38206
+ const warnCount = checks.filter((c) => c.status === "warn").length;
38207
+ const failCount = checks.filter((c) => c.status === "fail").length;
38208
+ const skipCount = checks.filter((c) => c.status === "skip").length;
38209
+ const overall = failCount > 0 ? "fail" : warnCount > 0 ? "warn" : "pass";
38210
+ const safeToDeployCheck = failCount === 0;
38211
+ const recommendedCommands = checks.filter((c) => c.fix && (c.status === "fail" || c.status === "warn")).map((c) => c.fix);
38212
+ return {
38213
+ forkPath,
38214
+ checks,
38215
+ passCount,
38216
+ warnCount,
38217
+ failCount,
38218
+ skipCount,
38219
+ overall,
38220
+ safeToDeployCheck,
38221
+ recommendedCommands
38222
+ };
38223
+ }
38224
+ function printQaResult(result) {
38225
+ const icon = (status) => {
38226
+ switch (status) {
38227
+ case "pass":
38228
+ return pc56.green("\u2713");
38229
+ case "fail":
38230
+ return pc56.red("\u2717");
38231
+ case "warn":
38232
+ return pc56.yellow("!");
38233
+ case "skip":
38234
+ return pc56.dim("\u25CB");
38235
+ }
38236
+ };
38237
+ console.log("");
38238
+ console.log(pc56.bold("Workspace QA"));
38239
+ console.log(pc56.dim("\u2500".repeat(60)));
38240
+ console.log(` Path: ${pc56.dim(result.forkPath)}`);
38241
+ console.log("");
38242
+ for (const check3 of result.checks) {
38243
+ const label = check3.name.padEnd(22);
38244
+ const detail = check3.detail ? pc56.dim(` ${check3.detail}`) : "";
38245
+ console.log(` ${icon(check3.status)} ${label}${detail}`);
38246
+ if (check3.fix && check3.status !== "pass" && check3.status !== "skip") {
38247
+ console.log(pc56.dim(` Fix: ${pc56.cyan(check3.fix)}`));
38248
+ }
38249
+ }
38250
+ console.log("");
38251
+ console.log(
38252
+ ` ${pc56.green(String(result.passCount))} pass \xB7 ${pc56.yellow(String(result.warnCount))} warn \xB7 ${pc56.red(String(result.failCount))} fail \xB7 ${pc56.dim(String(result.skipCount))} skip`
38253
+ );
38254
+ const overallLabel = {
38255
+ pass: pc56.green("PASS"),
38256
+ warn: pc56.yellow("WARN"),
38257
+ fail: pc56.red("FAIL")
38258
+ };
38259
+ console.log(` Overall: ${overallLabel[result.overall]}`);
38260
+ console.log(` Safe to run deploy check: ${result.safeToDeployCheck ? pc56.green("yes") : pc56.red("no")}`);
38261
+ if (result.recommendedCommands.length > 0) {
38262
+ console.log("");
38263
+ console.log(pc56.dim(" Recommended:"));
38264
+ for (const cmd of result.recommendedCommands) {
38265
+ console.log(pc56.dim(` ${pc56.cyan(cmd)}`));
38266
+ }
38267
+ }
38268
+ console.log("");
38269
+ console.log(pc56.dim(" Agent output: growthub workspace qa --json"));
38270
+ console.log(pc56.dim(" Deploy check: growthub workspace deploy check --json"));
38271
+ console.log("");
38272
+ }
38273
+ function registerWorkspaceQaCommands(workspaceCmd) {
38274
+ workspaceCmd.command("qa").description("Artifact-first workspace validation \u2014 config, env, deps, fork, routes, skills").option("--fork <path>", "Fork root path (default: cwd)").option("--skip-build", "Skip checks that require build tooling (skills validate, etc.)").option("--json", "Emit machine-readable JSON (agent-friendly)").addHelpText("after", `
38275
+ Examples:
38276
+ $ growthub workspace qa
38277
+ $ growthub workspace qa --json
38278
+ $ growthub workspace qa --skip-build --json
38279
+ $ growthub workspace qa --fork ./my-workspace --json
38280
+
38281
+ JSON shape:
38282
+ { forkPath, checks[], passCount, warnCount, failCount, skipCount, overall, safeToDeployCheck, recommendedCommands }
38283
+
38284
+ Docs: docs/WORKSPACE_DEPLOY_FLOW.md
38285
+ `).action((opts) => {
38286
+ const forkPath = opts.fork ? path85.resolve(opts.fork) : process.cwd();
38287
+ if (!opts.json) {
38288
+ const spinner15 = p37.spinner();
38289
+ spinner15.start("Running workspace QA checks\u2026");
38290
+ const result2 = computeWorkspaceQa(forkPath, { skipBuild: opts.skipBuild });
38291
+ spinner15.stop("QA checks complete.");
38292
+ printQaResult(result2);
38293
+ return;
38294
+ }
38295
+ const result = computeWorkspaceQa(forkPath, { skipBuild: opts.skipBuild });
38296
+ console.log(JSON.stringify(result, null, 2));
38297
+ });
38298
+ }
38299
+
38300
+ // src/commands/workspace-surface.ts
38301
+ import pc57 from "picocolors";
38302
+ import fs75 from "node:fs";
38303
+ import path86 from "node:path";
38304
+ function detectFramework2(appPath) {
38305
+ const nextConfig = ["next.config.js", "next.config.mjs", "next.config.ts"];
38306
+ if (nextConfig.some((f) => fs75.existsSync(path86.resolve(appPath, f)))) return "nextjs";
38307
+ const viteConfig = ["vite.config.js", "vite.config.mjs", "vite.config.ts"];
38308
+ if (viteConfig.some((f) => fs75.existsSync(path86.resolve(appPath, f)))) return "vite";
38309
+ return "unknown";
38310
+ }
38311
+ function detectEntryRoutes(appPath) {
38312
+ const candidates = [
38313
+ "app/page.jsx",
38314
+ "app/page.tsx",
38315
+ "app/page.js",
38316
+ "src/app/page.tsx",
38317
+ "pages/index.jsx",
38318
+ "pages/index.tsx",
38319
+ "pages/index.js",
38320
+ "index.html",
38321
+ "src/main.jsx",
38322
+ "src/main.tsx"
38323
+ ];
38324
+ return candidates.filter((c) => fs75.existsSync(path86.resolve(appPath, c)));
38325
+ }
38326
+ function detectPackageName(appPath) {
38327
+ const pkgPath = path86.resolve(appPath, "package.json");
38328
+ if (!fs75.existsSync(pkgPath)) return void 0;
38329
+ try {
38330
+ const parsed = JSON.parse(fs75.readFileSync(pkgPath, "utf8"));
38331
+ return parsed.name;
38332
+ } catch {
38333
+ return void 0;
38334
+ }
38335
+ }
38336
+ var KNOWN_APP_DIRS = [
38337
+ "apps/workspace",
38338
+ "apps/agency-portal",
38339
+ "apps/portal",
38340
+ "studio",
38341
+ "app",
38342
+ "src"
38343
+ ];
38344
+ function discoverAppSurfaces(forkPath) {
38345
+ const surfaces = [];
38346
+ for (const relPath of KNOWN_APP_DIRS) {
38347
+ const absPath = path86.resolve(forkPath, relPath);
38348
+ if (!fs75.existsSync(absPath)) continue;
38349
+ const hasPackageJson = fs75.existsSync(path86.resolve(absPath, "package.json"));
38350
+ const hasIndex = fs75.existsSync(path86.resolve(absPath, "index.html")) || fs75.existsSync(path86.resolve(absPath, "app")) || fs75.existsSync(path86.resolve(absPath, "pages")) || fs75.existsSync(path86.resolve(absPath, "src"));
38351
+ if (!hasPackageJson && !hasIndex) continue;
38352
+ surfaces.push({
38353
+ name: path86.basename(relPath),
38354
+ relPath,
38355
+ absPath,
38356
+ framework: detectFramework2(absPath),
38357
+ hasEnvExample: fs75.existsSync(path86.resolve(absPath, ".env.example")),
38358
+ hasVercelJson: fs75.existsSync(path86.resolve(absPath, "vercel.json")),
38359
+ hasGrowthubConfig: fs75.existsSync(path86.resolve(absPath, "growthub.config.json")),
38360
+ entryRoutes: detectEntryRoutes(absPath),
38361
+ packageName: detectPackageName(absPath)
38362
+ });
38363
+ }
38364
+ return surfaces;
38365
+ }
38366
+ function runSurfaceList(forkPath, json) {
38367
+ const surfaces = discoverAppSurfaces(forkPath);
38368
+ if (json) {
38369
+ console.log(JSON.stringify({ forkPath, count: surfaces.length, surfaces }, null, 2));
38370
+ return;
38371
+ }
38372
+ console.log("");
38373
+ console.log(pc57.bold("Workspace Surfaces"));
38374
+ console.log(pc57.dim("\u2500".repeat(60)));
38375
+ if (surfaces.length === 0) {
38376
+ console.log(pc57.dim(" No app surfaces detected."));
38377
+ console.log(pc57.dim(" Expected: apps/workspace, apps/agency-portal, or studio/"));
38378
+ console.log("");
38379
+ return;
38380
+ }
38381
+ for (const surface of surfaces) {
38382
+ console.log(` ${pc57.cyan(surface.relPath)} ${pc57.dim(`(${surface.framework})`)}`);
38383
+ if (surface.packageName) console.log(` ${pc57.dim(`package: ${surface.packageName}`)}`);
38384
+ console.log(` env-example: ${surface.hasEnvExample ? pc57.green("yes") : pc57.dim("no")} vercel.json: ${surface.hasVercelJson ? pc57.green("yes") : pc57.dim("no")} growthub.config: ${surface.hasGrowthubConfig ? pc57.green("yes") : pc57.dim("no")}`);
38385
+ if (surface.entryRoutes.length > 0) {
38386
+ console.log(` routes: ${pc57.dim(surface.entryRoutes.join(", "))}`);
38387
+ }
38388
+ console.log("");
38389
+ }
38390
+ console.log(pc57.dim(` Agent output: growthub workspace surface list --json`));
38391
+ console.log(pc57.dim(` Inspect: growthub workspace surface inspect apps/workspace --json`));
38392
+ console.log("");
38393
+ }
38394
+ function runSurfaceInspect(relPath, forkPath, json) {
38395
+ const absPath = path86.resolve(forkPath, relPath);
38396
+ if (!fs75.existsSync(absPath)) {
38397
+ if (json) {
38398
+ console.log(JSON.stringify({ error: `Path not found: ${absPath}` }));
38399
+ process.exitCode = 1;
38400
+ } else {
38401
+ console.error(pc57.red(`Path not found: ${absPath}`));
38402
+ process.exitCode = 1;
38403
+ }
38404
+ return;
38405
+ }
38406
+ const surface = {
38407
+ name: path86.basename(relPath),
38408
+ relPath,
38409
+ absPath,
38410
+ framework: detectFramework2(absPath),
38411
+ hasEnvExample: fs75.existsSync(path86.resolve(absPath, ".env.example")),
38412
+ hasVercelJson: fs75.existsSync(path86.resolve(absPath, "vercel.json")),
38413
+ hasGrowthubConfig: fs75.existsSync(path86.resolve(absPath, "growthub.config.json")),
38414
+ entryRoutes: detectEntryRoutes(absPath),
38415
+ packageName: detectPackageName(absPath)
38416
+ };
38417
+ if (json) {
38418
+ console.log(JSON.stringify(surface, null, 2));
38419
+ return;
38420
+ }
38421
+ console.log("");
38422
+ console.log(pc57.bold(`Surface: ${surface.relPath}`));
38423
+ console.log(pc57.dim("\u2500".repeat(60)));
38424
+ console.log(` Framework: ${surface.framework}`);
38425
+ if (surface.packageName) console.log(` Package: ${surface.packageName}`);
38426
+ console.log(` .env.example: ${surface.hasEnvExample ? pc57.green("yes") : pc57.dim("no")}`);
38427
+ console.log(` vercel.json: ${surface.hasVercelJson ? pc57.green("yes") : pc57.dim("no")}`);
38428
+ console.log(` growthub.config: ${surface.hasGrowthubConfig ? pc57.green("yes") : pc57.dim("no")}`);
38429
+ if (surface.entryRoutes.length > 0) {
38430
+ console.log(` Entry routes:`);
38431
+ for (const r of surface.entryRoutes) {
38432
+ console.log(` ${pc57.dim(r)}`);
38433
+ }
38434
+ }
38435
+ console.log("");
38436
+ }
38437
+ function registerWorkspaceSurfaceCommands(workspaceCmd) {
38438
+ const surface = workspaceCmd.command("surface").description("Discover and inspect app surfaces in the governed workspace");
38439
+ surface.command("list").description("List all detected app surfaces (Next.js apps, Vite studio, etc.)").option("--fork <path>", "Fork root path (default: cwd)").option("--json", "Emit machine-readable JSON").addHelpText("after", `
38440
+ Examples:
38441
+ $ growthub workspace surface list
38442
+ $ growthub workspace surface list --json
38443
+ $ growthub workspace surface list --fork ./my-workspace --json
38444
+ `).action((opts) => {
38445
+ const forkPath = opts.fork ? path86.resolve(opts.fork) : process.cwd();
38446
+ runSurfaceList(forkPath, opts.json ?? false);
38447
+ });
38448
+ surface.command("inspect").description("Inspect a specific app surface path").argument("<app-path>", "Relative path to the app (e.g. apps/workspace)").option("--fork <path>", "Fork root path (default: cwd)").option("--json", "Emit machine-readable JSON").addHelpText("after", `
38449
+ Examples:
38450
+ $ growthub workspace surface inspect apps/workspace
38451
+ $ growthub workspace surface inspect apps/workspace --json
38452
+ $ growthub workspace surface inspect studio --json
38453
+ `).action((appPath, opts) => {
38454
+ const forkPath = opts.fork ? path86.resolve(opts.fork) : process.cwd();
38455
+ runSurfaceInspect(appPath, forkPath, opts.json ?? false);
38456
+ });
38457
+ }
38458
+
38459
+ // src/commands/workspace-upstream.ts
38460
+ init_kit_forks_home();
38461
+ import { spawnSync as spawnSync10 } from "node:child_process";
38462
+ import * as p38 from "@clack/prompts";
38463
+ import pc58 from "picocolors";
38464
+ import fs76 from "node:fs";
38465
+ import path87 from "node:path";
38466
+ function resolveForkInfo(forkPath) {
38467
+ const stateDir = resolveInForkStateDir(forkPath);
38468
+ const forkJsonPath = path87.resolve(stateDir, "fork.json");
38469
+ if (!fs76.existsSync(forkJsonPath)) return null;
38470
+ try {
38471
+ const parsed = JSON.parse(fs76.readFileSync(forkJsonPath, "utf8"));
38472
+ if (!parsed.forkId || !parsed.kitId) return null;
38473
+ const policyPath = path87.resolve(stateDir, "policy.json");
38474
+ let remoteSyncMode = "off";
38475
+ if (fs76.existsSync(policyPath)) {
38476
+ try {
38477
+ const policy = JSON.parse(fs76.readFileSync(policyPath, "utf8"));
38478
+ remoteSyncMode = policy.remoteSyncMode ?? "off";
38479
+ } catch {
38480
+ }
38481
+ }
38482
+ return {
38483
+ forkId: parsed.forkId,
38484
+ kitId: parsed.kitId,
38485
+ label: parsed.label,
38486
+ remoteSyncMode,
38487
+ hasRemote: Boolean(parsed.remote)
38488
+ };
38489
+ } catch {
38490
+ return null;
38491
+ }
38492
+ }
38493
+ function runUpstreamCheck(forkPath, json) {
38494
+ const forkInfo = resolveForkInfo(forkPath);
38495
+ const blockingIssues = [];
38496
+ const safeNextActions = [];
38497
+ if (!forkInfo) {
38498
+ blockingIssues.push("Fork not registered \u2014 run: growthub kit fork register .");
38499
+ safeNextActions.push("growthub kit fork register .");
38500
+ }
38501
+ if (forkInfo && !forkInfo.hasRemote) {
38502
+ blockingIssues.push("No GitHub remote connected \u2014 drift check requires remote");
38503
+ safeNextActions.push(`growthub kit fork connect --fork-id ${forkInfo.forkId} --remote <owner/repo>`);
38504
+ }
38505
+ if (forkInfo && forkInfo.remoteSyncMode === "off") {
38506
+ safeNextActions.push(`growthub kit fork policy ${forkInfo.forkId} --set remoteSyncMode=pr`);
38507
+ }
38508
+ const upstreamCheckCommand = forkInfo ? `growthub kit fork status ${forkInfo.forkId} --json` : "growthub kit fork register . # first register";
38509
+ const healCommand = forkInfo ? `growthub kit fork heal ${forkInfo.forkId} --dry-run --json` : "growthub kit fork register . # first register";
38510
+ const prCommand = forkInfo ? `growthub kit fork heal ${forkInfo.forkId} --json` : "growthub kit fork register . # first register";
38511
+ const result = {
38512
+ forkPath,
38513
+ forkId: forkInfo?.forkId ?? null,
38514
+ kitId: forkInfo?.kitId ?? null,
38515
+ registered: Boolean(forkInfo),
38516
+ hasRemote: forkInfo?.hasRemote ?? false,
38517
+ remoteSyncMode: forkInfo?.remoteSyncMode ?? "off",
38518
+ upstreamCheckCommand,
38519
+ healCommand,
38520
+ prCommand,
38521
+ blockingIssues,
38522
+ safeNextActions
38523
+ };
38524
+ if (json) {
38525
+ console.log(JSON.stringify(result, null, 2));
38526
+ return;
38527
+ }
38528
+ console.log("");
38529
+ console.log(pc58.bold("Workspace Upstream Check"));
38530
+ console.log(pc58.dim("\u2500".repeat(60)));
38531
+ if (forkInfo) {
38532
+ console.log(` Fork ID: ${pc58.cyan(forkInfo.forkId)}`);
38533
+ console.log(` Kit: ${pc58.dim(forkInfo.kitId)}`);
38534
+ console.log(` Remote: ${forkInfo.hasRemote ? pc58.green("connected") : pc58.dim("not connected")}`);
38535
+ console.log(` Sync mode: ${forkInfo.remoteSyncMode === "pr" ? pc58.green("pr") : pc58.dim(forkInfo.remoteSyncMode)}`);
38536
+ console.log("");
38537
+ console.log(` Check drift: ${pc58.cyan(upstreamCheckCommand)}`);
38538
+ console.log(` Dry-run heal: ${pc58.cyan(healCommand)}`);
38539
+ console.log(` Submit PR: ${pc58.cyan(prCommand)}`);
38540
+ } else {
38541
+ console.log(pc58.yellow(" Fork not registered."));
38542
+ }
38543
+ if (blockingIssues.length > 0) {
38544
+ console.log("");
38545
+ console.log(pc58.yellow(" Blocking issues:"));
38546
+ for (const issue of blockingIssues) {
38547
+ console.log(pc58.dim(` \xB7 ${issue}`));
38548
+ }
38549
+ }
38550
+ if (safeNextActions.length > 0) {
38551
+ console.log("");
38552
+ console.log(pc58.dim(" Safe next actions:"));
38553
+ for (const action of safeNextActions) {
38554
+ console.log(pc58.dim(` ${pc58.cyan(action)}`));
38555
+ }
38556
+ }
38557
+ console.log("");
38558
+ console.log(pc58.dim(" Agent output: growthub workspace upstream check --json"));
38559
+ console.log("");
38560
+ }
38561
+ function runUpstreamHeal(forkPath, dryRun, json) {
38562
+ const forkInfo = resolveForkInfo(forkPath);
38563
+ if (!forkInfo) {
38564
+ const err = { error: "Fork not registered. Run: growthub kit fork register ." };
38565
+ if (json) {
38566
+ console.log(JSON.stringify(err));
38567
+ process.exitCode = 1;
38568
+ return;
38569
+ }
38570
+ console.error(pc58.red(err.error));
38571
+ process.exitCode = 1;
38572
+ return;
38573
+ }
38574
+ const args = ["kit", "fork", "heal", forkInfo.forkId, "--json"];
38575
+ if (dryRun) args.push("--dry-run");
38576
+ if (!json) {
38577
+ p38.note(
38578
+ [
38579
+ `Fork ID: ${forkInfo.forkId}`,
38580
+ `Kit: ${forkInfo.kitId}`,
38581
+ `Mode: ${dryRun ? "dry-run" : "live"}`,
38582
+ "",
38583
+ `Command: growthub ${args.join(" ")}`
38584
+ ].join("\n"),
38585
+ "Upstream Heal"
38586
+ );
38587
+ }
38588
+ const result = spawnSync10("growthub", args, { stdio: json ? "pipe" : "inherit", encoding: "utf8" });
38589
+ if (json && result.stdout) {
38590
+ process.stdout.write(result.stdout);
38591
+ }
38592
+ if (result.status !== 0) {
38593
+ process.exitCode = result.status ?? 1;
38594
+ }
38595
+ }
38596
+ function runUpstreamPr(forkPath, json) {
38597
+ const forkInfo = resolveForkInfo(forkPath);
38598
+ if (!forkInfo) {
38599
+ const err = { error: "Fork not registered. Run: growthub kit fork register ." };
38600
+ if (json) {
38601
+ console.log(JSON.stringify(err));
38602
+ process.exitCode = 1;
38603
+ return;
38604
+ }
38605
+ console.error(pc58.red(err.error));
38606
+ process.exitCode = 1;
38607
+ return;
38608
+ }
38609
+ if (forkInfo.remoteSyncMode !== "pr") {
38610
+ const msg = {
38611
+ status: "needs_action",
38612
+ message: `remoteSyncMode is "${forkInfo.remoteSyncMode}", not "pr". Set it first.`,
38613
+ fix: `growthub kit fork policy ${forkInfo.forkId} --set remoteSyncMode=pr`
38614
+ };
38615
+ if (json) {
38616
+ console.log(JSON.stringify(msg));
38617
+ return;
38618
+ }
38619
+ p38.note(
38620
+ [`Remote sync mode is "${forkInfo.remoteSyncMode}", not "pr".`, "", `Fix: growthub kit fork policy ${forkInfo.forkId} --set remoteSyncMode=pr`].join("\n"),
38621
+ "Set Sync Mode First"
38622
+ );
38623
+ return;
38624
+ }
38625
+ runUpstreamHeal(forkPath, false, json);
38626
+ }
38627
+ function registerWorkspaceUpstreamCommands(workspaceCmd) {
38628
+ const upstream = workspaceCmd.command("upstream").description("Fork upstream sync helpers \u2014 check drift, heal, and open sync PRs");
38629
+ upstream.command("check").description("Check upstream drift state and print recommended sync commands").option("--fork <path>", "Fork root path (default: cwd)").option("--json", "Emit machine-readable JSON").addHelpText("after", `
38630
+ Examples:
38631
+ $ growthub workspace upstream check
38632
+ $ growthub workspace upstream check --json
38633
+ $ growthub workspace upstream check --fork ./my-workspace --json
38634
+
38635
+ JSON shape:
38636
+ { forkId, kitId, registered, hasRemote, remoteSyncMode, upstreamCheckCommand, healCommand, prCommand, blockingIssues, safeNextActions }
38637
+ `).action((opts) => {
38638
+ const forkPath = opts.fork ? path87.resolve(opts.fork) : process.cwd();
38639
+ runUpstreamCheck(forkPath, opts.json ?? false);
38640
+ });
38641
+ upstream.command("heal").description("Run upstream heal (wraps growthub kit fork heal) \u2014 preserves customizations").option("--fork <path>", "Fork root path (default: cwd)").option("--dry-run", "Show what would change without applying it").option("--json", "Emit machine-readable JSON (pass-through from kit fork heal)").addHelpText("after", `
38642
+ Examples:
38643
+ $ growthub workspace upstream heal --dry-run
38644
+ $ growthub workspace upstream heal --dry-run --json
38645
+ $ growthub workspace upstream heal --json
38646
+ `).action((opts) => {
38647
+ const forkPath = opts.fork ? path87.resolve(opts.fork) : process.cwd();
38648
+ runUpstreamHeal(forkPath, opts.dryRun ?? false, opts.json ?? false);
38649
+ });
38650
+ upstream.command("pr").description("Submit upstream sync as a PR (requires remoteSyncMode=pr on the fork)").option("--fork <path>", "Fork root path (default: cwd)").option("--json", "Emit machine-readable JSON (pass-through from kit fork heal)").addHelpText("after", `
38651
+ Examples:
38652
+ $ growthub workspace upstream pr
38653
+ $ growthub workspace upstream pr --json
38654
+
38655
+ Note: remoteSyncMode must be set to "pr" first:
38656
+ $ growthub kit fork policy <fork-id> --set remoteSyncMode=pr
38657
+ `).action((opts) => {
38658
+ const forkPath = opts.fork ? path87.resolve(opts.fork) : process.cwd();
38659
+ runUpstreamPr(forkPath, opts.json ?? false);
38660
+ });
38661
+ }
38662
+
38663
+ // src/commands/workspace-portal.ts
38664
+ init_kit_forks_home();
38665
+ import * as p39 from "@clack/prompts";
38666
+ import pc59 from "picocolors";
38667
+ import fs77 from "node:fs";
38668
+ import path88 from "node:path";
38669
+ function slugify(name) {
38670
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
38671
+ }
38672
+ function resolveClientSlug(raw) {
38673
+ return slugify(raw);
38674
+ }
38675
+ function readForkMeta2(forkPath) {
38676
+ const forkJsonPath = path88.resolve(resolveInForkStateDir(forkPath), "fork.json");
38677
+ if (!fs77.existsSync(forkJsonPath)) return {};
38678
+ try {
38679
+ return JSON.parse(fs77.readFileSync(forkJsonPath, "utf8"));
38680
+ } catch {
38681
+ return {};
38682
+ }
38683
+ }
38684
+ function readWorkspaceConfig(forkPath) {
38685
+ const candidates = [
38686
+ path88.resolve(forkPath, "growthub.config.json"),
38687
+ path88.resolve(forkPath, "apps/workspace/growthub.config.json")
38688
+ ];
38689
+ for (const candidate of candidates) {
38690
+ if (!fs77.existsSync(candidate)) continue;
38691
+ try {
38692
+ return JSON.parse(fs77.readFileSync(candidate, "utf8"));
38693
+ } catch {
38694
+ return {};
38695
+ }
38696
+ }
38697
+ return {};
38698
+ }
38699
+ function generateEnvVarNames(clientSlug) {
38700
+ const upper = clientSlug.toUpperCase().replace(/-/g, "_");
38701
+ return [
38702
+ `GROWTHUB_WORKSPACE_ID`,
38703
+ `GROWTHUB_WORKSPACE_SLUG`,
38704
+ `${upper}_BRAND_NAME`,
38705
+ `${upper}_BRAND_LOGO_URL`,
38706
+ `${upper}_BRAND_PRIMARY_COLOR`,
38707
+ `DATABASE_URL`,
38708
+ `GROWTHUB_BRIDGE_ACCESS_TOKEN`
38709
+ ];
38710
+ }
38711
+ function generateHandoffDoc(opts) {
38712
+ return `# Client Portal Handoff \u2014 ${opts.clientName}
38713
+
38714
+ Generated by \`growthub workspace portal prepare --client ${opts.clientSlug}\`
38715
+
38716
+ ---
38717
+
38718
+ ## Workspace Details
38719
+
38720
+ - **Client slug:** \`${opts.clientSlug}\`
38721
+ - **Fork ID:** \`${opts.forkId ?? "not registered"}\`
38722
+ - **Kit:** \`${opts.kitId ?? "unknown"}\`
38723
+ - **Output path:** \`${opts.outputPath}\`
38724
+
38725
+ ---
38726
+
38727
+ ## Required Environment Variables
38728
+
38729
+ Set these in your Vercel project or \`.env.local\`:
38730
+
38731
+ \`\`\`bash
38732
+ ${opts.envVarNames.map((v) => `${v}=`).join("\n")}
38733
+ \`\`\`
38734
+
38735
+ ---
38736
+
38737
+ ## Deploy Checklist
38738
+
38739
+ 1. Fill in all environment variables above
38740
+ 2. \`growthub workspace qa --json\` \u2014 verify workspace is build-ready
38741
+ 3. \`growthub workspace deploy check --json\` \u2014 verify deployment readiness
38742
+ 4. \`cd apps/workspace && npm install && npm run build\` \u2014 verify build passes
38743
+ 5. Deploy to Vercel: \`cd apps/workspace && vercel\`
38744
+ 6. \`growthub integrations status --json\` \u2014 verify integrations are live
38745
+ 7. Hand off dashboard URL + credentials to client
38746
+
38747
+ ---
38748
+
38749
+ ## Fork Sync
38750
+
38751
+ To pull upstream updates:
38752
+ \`\`\`bash
38753
+ growthub workspace upstream check --json
38754
+ growthub workspace upstream heal --dry-run --json
38755
+ growthub workspace upstream pr --json
38756
+ \`\`\`
38757
+
38758
+ ---
38759
+
38760
+ *Generated by Growthub Local CLI \u2014 docs/WORKSPACE_DEPLOY_FLOW.md*
38761
+ `;
38762
+ }
38763
+ function generateBrandConfig(clientSlug, clientName) {
38764
+ return {
38765
+ clientSlug,
38766
+ clientName,
38767
+ branding: {
38768
+ name: clientName,
38769
+ logoUrl: "",
38770
+ primaryColor: "#6366f1",
38771
+ accentColor: "#8b5cf6"
38772
+ },
38773
+ deploy: {
38774
+ platform: "vercel",
38775
+ appRoot: "apps/workspace"
38776
+ },
38777
+ generatedBy: "growthub workspace portal prepare",
38778
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
38779
+ };
38780
+ }
38781
+ async function runPortalPrepare(opts) {
38782
+ const forkPath = opts.fork ? path88.resolve(opts.fork) : process.cwd();
38783
+ const clientSlug = resolveClientSlug(opts.client);
38784
+ const clientName = opts.client;
38785
+ const outputPath = opts.out ? path88.resolve(opts.out) : path88.resolve(forkPath, `client-portals/${clientSlug}`);
38786
+ const forkMeta = readForkMeta2(forkPath);
38787
+ const workspaceConfig = readWorkspaceConfig(forkPath);
38788
+ const envVarNames = generateEnvVarNames(clientSlug);
38789
+ const deployChecklist = [
38790
+ "Fill in all required env vars",
38791
+ "growthub workspace qa --json",
38792
+ "growthub workspace deploy check --json",
38793
+ "npm install && npm run build (in apps/workspace)",
38794
+ "Deploy to Vercel",
38795
+ "growthub integrations status --json"
38796
+ ];
38797
+ try {
38798
+ fs77.mkdirSync(outputPath, { recursive: true });
38799
+ const filesWritten = [];
38800
+ const brandConfigPath = path88.resolve(outputPath, "brand.config.json");
38801
+ fs77.writeFileSync(
38802
+ brandConfigPath,
38803
+ `${JSON.stringify(generateBrandConfig(clientSlug, clientName), null, 2)}
38804
+ `,
38805
+ "utf8"
38806
+ );
38807
+ filesWritten.push(brandConfigPath.replace(forkPath, "."));
38808
+ const envTemplatePath = path88.resolve(outputPath, ".env.template");
38809
+ fs77.writeFileSync(
38810
+ envTemplatePath,
38811
+ `# Environment variables for client: ${clientName} (${clientSlug})
38812
+ # Generated by growthub workspace portal prepare
38813
+
38814
+ ${envVarNames.map((v) => `${v}=`).join("\n")}
38815
+ `,
38816
+ "utf8"
38817
+ );
38818
+ filesWritten.push(envTemplatePath.replace(forkPath, "."));
38819
+ const handoffPath = path88.resolve(outputPath, "HANDOFF.md");
38820
+ fs77.writeFileSync(
38821
+ handoffPath,
38822
+ generateHandoffDoc({
38823
+ clientSlug,
38824
+ clientName,
38825
+ forkId: forkMeta.forkId,
38826
+ kitId: forkMeta.kitId,
38827
+ envVarNames,
38828
+ outputPath
38829
+ }),
38830
+ "utf8"
38831
+ );
38832
+ filesWritten.push(handoffPath.replace(forkPath, "."));
38833
+ const metaPath = path88.resolve(outputPath, "growthub-portal-meta.json");
38834
+ fs77.writeFileSync(
38835
+ metaPath,
38836
+ `${JSON.stringify({
38837
+ clientSlug,
38838
+ clientName,
38839
+ forkId: forkMeta.forkId,
38840
+ kitId: forkMeta.kitId,
38841
+ workspaceId: workspaceConfig.workspaceId ?? null,
38842
+ outputPath,
38843
+ preparedAt: (/* @__PURE__ */ new Date()).toISOString()
38844
+ }, null, 2)}
38845
+ `,
38846
+ "utf8"
38847
+ );
38848
+ filesWritten.push(metaPath.replace(forkPath, "."));
38849
+ const result = {
38850
+ status: "ok",
38851
+ clientSlug,
38852
+ forkPath,
38853
+ outputPath,
38854
+ filesWritten,
38855
+ envVarsNeeded: envVarNames,
38856
+ deployChecklist,
38857
+ nextCommand: `growthub workspace deploy check --fork ${forkPath} --json`
38858
+ };
38859
+ if (opts.json) {
38860
+ console.log(JSON.stringify(result, null, 2));
38861
+ return;
38862
+ }
38863
+ p39.note(
38864
+ [
38865
+ `Client: ${clientName} (${clientSlug})`,
38866
+ `Output: ${outputPath}`,
38867
+ "",
38868
+ "Files written:",
38869
+ ...filesWritten.map((f) => ` ${f}`),
38870
+ "",
38871
+ "Env vars to configure:",
38872
+ ...envVarNames.map((v) => ` ${v}=`),
38873
+ "",
38874
+ "Next: growthub workspace deploy check --json"
38875
+ ].join("\n"),
38876
+ "Portal Prepared"
38877
+ );
38878
+ } catch (err) {
38879
+ const error = err instanceof Error ? err.message : String(err);
38880
+ if (opts.json) {
38881
+ console.log(JSON.stringify({ status: "error", clientSlug, forkPath, outputPath, error }));
38882
+ process.exitCode = 1;
38883
+ return;
38884
+ }
38885
+ console.error(pc59.red(`Failed to prepare portal: ${error}`));
38886
+ process.exitCode = 1;
38887
+ }
38888
+ }
38889
+ async function runPortalExport(opts) {
38890
+ await runPortalPrepare(opts);
38891
+ }
38892
+ function registerWorkspacePortalCommands(workspaceCmd) {
38893
+ const portal = workspaceCmd.command("portal").description("Agency portal helpers \u2014 prepare and export client workspace configs");
38894
+ portal.command("prepare").description("Scaffold a client-specific portal config, env template, and handoff doc").requiredOption("--client <slug>", "Client name or slug (e.g. acme-corp)").option("--fork <path>", "Fork root path (default: cwd)").option("--out <path>", "Output directory (default: client-portals/<slug> under fork root)").option("--json", "Emit machine-readable JSON").addHelpText("after", `
38895
+ Examples:
38896
+ $ growthub workspace portal prepare --client acme-corp
38897
+ $ growthub workspace portal prepare --client acme-corp --json
38898
+ $ growthub workspace portal prepare --client acme-corp --out ./acme-portal --json
38899
+
38900
+ JSON shape:
38901
+ { status, clientSlug, forkPath, outputPath, filesWritten, envVarsNeeded, deployChecklist, nextCommand }
38902
+
38903
+ Docs: docs/WORKSPACE_DEPLOY_FLOW.md
38904
+ `).action(async (opts) => {
38905
+ await runPortalPrepare(opts);
38906
+ });
38907
+ portal.command("export").description("Export client portal scaffold (alias for prepare + handoff summary)").requiredOption("--client <slug>", "Client name or slug").option("--fork <path>", "Fork root path (default: cwd)").option("--out <path>", "Output directory").option("--json", "Emit machine-readable JSON").addHelpText("after", `
38908
+ Examples:
38909
+ $ growthub workspace portal export --client acme-corp --json
38910
+ `).action(async (opts) => {
38911
+ await runPortalExport(opts);
38912
+ });
38913
+ }
38914
+
37636
38915
  // src/index.ts
37637
38916
  init_session_store();
37638
38917
  init_banner();
@@ -37890,16 +39169,16 @@ async function syncMemoriesToHosted(project, options) {
37890
39169
 
37891
39170
  // src/index.ts
37892
39171
  init_llm();
37893
- function resolveCliVersion() {
39172
+ function resolveCliVersion2() {
37894
39173
  try {
37895
- const moduleDir = path85.dirname(fileURLToPath7(import.meta.url));
39174
+ const moduleDir = path90.dirname(fileURLToPath8(import.meta.url));
37896
39175
  const candidates = [
37897
- path85.resolve(moduleDir, "../package.json"),
37898
- path85.resolve(moduleDir, "../../package.json")
39176
+ path90.resolve(moduleDir, "../package.json"),
39177
+ path90.resolve(moduleDir, "../../package.json")
37899
39178
  ];
37900
39179
  for (const candidate of candidates) {
37901
- if (!fs74.existsSync(candidate)) continue;
37902
- const parsed = JSON.parse(fs74.readFileSync(candidate, "utf8"));
39180
+ if (!fs79.existsSync(candidate)) continue;
39181
+ const parsed = JSON.parse(fs79.readFileSync(candidate, "utf8"));
37903
39182
  if (parsed?.name === "@growthub/cli" && typeof parsed.version === "string") return parsed.version;
37904
39183
  }
37905
39184
  } catch {
@@ -37959,6 +39238,11 @@ function registerSharedCommands(target) {
37959
39238
  registerSetupCommands(target);
37960
39239
  const workspaceCmd = registerWorkspaceImproveCommands(target);
37961
39240
  registerWorkspaceDeployCommands(workspaceCmd);
39241
+ registerWorkspaceStatusCommands(workspaceCmd);
39242
+ registerWorkspaceQaCommands(workspaceCmd);
39243
+ registerWorkspaceSurfaceCommands(workspaceCmd);
39244
+ registerWorkspaceUpstreamCommands(workspaceCmd);
39245
+ registerWorkspacePortalCommands(workspaceCmd);
37962
39246
  const auth = target.command("auth").description("Authentication and bootstrap utilities");
37963
39247
  auth.command("bootstrap-ceo").description("Create a one-time bootstrap invite URL for first instance admin").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("--force", "Create new invite even if admin already exists", false).option("--expires-hours <hours>", "Invite expiration window in hours", (value) => Number(value)).option("--base-url <url>", "Public base URL used to print invite link").action(bootstrapCeoInvite);
37964
39248
  auth.command("login").description("Sign in to hosted Growthub and save a CLI session (browser flow)").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("--base-url <url>", "Hosted Growthub base URL (defaults to auth.growthubBaseUrl or GROWTHUB_BASE_URL)").option("--token <token>", "Skip the browser flow by providing a pre-issued hosted token (scripting/CI)").option("--machine-label <label>", "Label identifying this machine in the hosted app").option("--workspace-label <label>", "Label identifying this workspace in the hosted app").option("--timeout-ms <ms>", "How long to wait for the browser callback", (value) => Number(value)).option("--no-browser", "Do not try to launch a browser \u2014 print the URL and wait").option("--json", "Output raw JSON").action(async (opts) => {
@@ -37989,7 +39273,7 @@ async function runNativeIntelligenceHub() {
37989
39273
  const favoriteModel = currentConfig.localModel?.trim() || void 0;
37990
39274
  const defaultModel = currentConfig.localModel?.trim() || process.env.NATIVE_INTELLIGENCE_LOCAL_MODEL?.trim() || process.env.OLLAMA_MODEL?.trim() || recommendedModel;
37991
39275
  const status = await detectLocalIntelligenceStatus(baseUrl, defaultModel);
37992
- const action = await p38.select({
39276
+ const action = await p41.select({
37993
39277
  message: "Local Intelligence",
37994
39278
  options: [
37995
39279
  { value: "setup", label: "Setup helper", hint: "machine detection + install/env guidance" },
@@ -38001,7 +39285,7 @@ async function runNativeIntelligenceHub() {
38001
39285
  { value: "__back_to_hub", label: "\u2190 Back to main menu" }
38002
39286
  ]
38003
39287
  });
38004
- if (p38.isCancel(action) || action === "__back_to_hub") return "back";
39288
+ if (p41.isCancel(action) || action === "__back_to_hub") return "back";
38005
39289
  if (action === "setup") {
38006
39290
  const setupLines = [
38007
39291
  `OS: ${status.osLabel}`,
@@ -38013,7 +39297,7 @@ async function runNativeIntelligenceHub() {
38013
39297
  "",
38014
39298
  ...buildSetupCommands(status.osLabel, baseUrl, recommendedModel)
38015
39299
  ];
38016
- p38.note(setupLines.join("\n"), "Local Intelligence Setup Helper");
39300
+ p41.note(setupLines.join("\n"), "Local Intelligence Setup Helper");
38017
39301
  continue;
38018
39302
  }
38019
39303
  if (action === "models") {
@@ -38026,19 +39310,19 @@ async function runNativeIntelligenceHub() {
38026
39310
  { value: "__custom_model", label: "Enter custom local model id", hint: "for any other local adapter model" },
38027
39311
  { value: "__back_to_local_intel", label: "\u2190 Back to Local Intelligence" }
38028
39312
  ];
38029
- const adapterChoice = await p38.select({
39313
+ const adapterChoice = await p41.select({
38030
39314
  message: "Choose local custom model adapter",
38031
39315
  options: modelOptions
38032
39316
  });
38033
- if (p38.isCancel(adapterChoice) || adapterChoice === "__back_to_local_intel") continue;
39317
+ if (p41.isCancel(adapterChoice) || adapterChoice === "__back_to_local_intel") continue;
38034
39318
  const chosenModel = adapterChoice === "__custom_model" ? await promptForCustomModel(defaultModel) : adapterChoice;
38035
39319
  if (!chosenModel) continue;
38036
- const applyConfirmed = await p38.confirm({
39320
+ const applyConfirmed = await p41.confirm({
38037
39321
  message: `Apply Local Intelligence config for model "${chosenModel}"?`,
38038
39322
  initialValue: true
38039
39323
  });
38040
- if (p38.isCancel(applyConfirmed) || !applyConfirmed) continue;
38041
- const applySpinner = p38.spinner();
39324
+ if (p41.isCancel(applyConfirmed) || !applyConfirmed) continue;
39325
+ const applySpinner = p41.spinner();
38042
39326
  applySpinner.start(`Applying model config (${chosenModel})...`);
38043
39327
  writeIntelligenceConfig({
38044
39328
  ...currentConfig,
@@ -38050,7 +39334,7 @@ async function runNativeIntelligenceHub() {
38050
39334
  const health = await checkBackendHealth(readIntelligenceConfig());
38051
39335
  if (!health.available) {
38052
39336
  applySpinner.stop(`Config saved, backend unavailable (${health.latencyMs}ms).`);
38053
- p38.note(
39337
+ p41.note(
38054
39338
  [...health.error ? [`Error: ${health.error}`] : [], "You can still run prompt flow and retry health later."].join("\n"),
38055
39339
  "Local model status"
38056
39340
  );
@@ -38097,7 +39381,7 @@ async function runNativeIntelligenceHub() {
38097
39381
  modelId: result.modelId,
38098
39382
  endpoint: result.endpoint
38099
39383
  });
38100
- p38.note(
39384
+ p41.note(
38101
39385
  `Provider: ${result.provider}
38102
39386
  Model: ${result.modelId ?? "default"}
38103
39387
  API key: ${result.apiKey ? "configured" : "not needed"}`,
@@ -38109,33 +39393,33 @@ API key: ${result.apiKey ? "configured" : "not needed"}`,
38109
39393
  await runMarketingContextBuilder(baseUrl, defaultModel);
38110
39394
  continue;
38111
39395
  }
38112
- const customPrompt = await p38.text({
39396
+ const customPrompt = await p41.text({
38113
39397
  message: "Enter your local intelligence prompt",
38114
39398
  placeholder: "Describe what you want to create/analyze"
38115
39399
  });
38116
- if (p38.isCancel(customPrompt)) continue;
39400
+ if (p41.isCancel(customPrompt)) continue;
38117
39401
  const prompt = String(customPrompt).trim();
38118
39402
  if (!prompt) {
38119
- p38.note("Prompt was empty. Nothing was run.", "Local Intelligence");
39403
+ p41.note("Prompt was empty. Nothing was run.", "Local Intelligence");
38120
39404
  continue;
38121
39405
  }
38122
39406
  await runNativeIntelligenceFlowSuite(baseUrl, defaultModel, prompt);
38123
39407
  }
38124
39408
  }
38125
39409
  async function runMarketingContextBuilder(baseUrl, model) {
38126
- const projectDir = await p38.text({
39410
+ const projectDir = await p41.text({
38127
39411
  message: "Project directory to scan",
38128
39412
  placeholder: process.cwd(),
38129
39413
  defaultValue: process.cwd()
38130
39414
  });
38131
- if (p38.isCancel(projectDir)) return;
39415
+ if (p41.isCancel(projectDir)) return;
38132
39416
  const dir = String(projectDir).trim() || process.cwd();
38133
- if (!fs74.existsSync(dir)) {
38134
- p38.note(`Directory not found: ${dir}`, "Marketing Context Builder");
39417
+ if (!fs79.existsSync(dir)) {
39418
+ p41.note(`Directory not found: ${dir}`, "Marketing Context Builder");
38135
39419
  return;
38136
39420
  }
38137
- const spinner14 = p38.spinner();
38138
- spinner14.start("Scanning project artifacts and drafting product-marketing context...");
39421
+ const spinner15 = p41.spinner();
39422
+ spinner15.start("Scanning project artifacts and drafting product-marketing context...");
38139
39423
  try {
38140
39424
  const config = readIntelligenceConfig();
38141
39425
  const backend = createNativeIntelligenceBackend({
@@ -38151,36 +39435,36 @@ async function runMarketingContextBuilder(baseUrl, model) {
38151
39435
  } else {
38152
39436
  result = buildDeterministicContext(input);
38153
39437
  }
38154
- spinner14.stop("Product-marketing context drafted.");
39438
+ spinner15.stop("Product-marketing context drafted.");
38155
39439
  const summaryLines = [
38156
39440
  `Mode: ${result.mode}`,
38157
39441
  `Confidence: ${(result.confidence * 100).toFixed(0)}%`,
38158
39442
  `Sources used: ${result.sourcesUsed.join(", ") || "none"}`,
38159
39443
  `Sources missing: ${result.sourcesMissing.join(", ") || "none"}`
38160
39444
  ];
38161
- p38.note(summaryLines.join("\n"), "Marketing Context Builder");
38162
- const saveChoice = await p38.confirm({
39445
+ p41.note(summaryLines.join("\n"), "Marketing Context Builder");
39446
+ const saveChoice = await p41.confirm({
38163
39447
  message: "Save the drafted context to .agents/product-marketing-context.md?"
38164
39448
  });
38165
- if (p38.isCancel(saveChoice) || !saveChoice) {
38166
- p38.note("Draft was not saved. You can copy it from the output above.", "Marketing Context Builder");
39449
+ if (p41.isCancel(saveChoice) || !saveChoice) {
39450
+ p41.note("Draft was not saved. You can copy it from the output above.", "Marketing Context Builder");
38167
39451
  return;
38168
39452
  }
38169
- const outDir = path85.resolve(dir, ".agents");
38170
- fs74.mkdirSync(outDir, { recursive: true });
38171
- const outPath = path85.resolve(outDir, "product-marketing-context.md");
38172
- fs74.writeFileSync(outPath, result.contextMarkdown, "utf-8");
38173
- p38.note(`Saved to: ${outPath}
39453
+ const outDir = path90.resolve(dir, ".agents");
39454
+ fs79.mkdirSync(outDir, { recursive: true });
39455
+ const outPath = path90.resolve(outDir, "product-marketing-context.md");
39456
+ fs79.writeFileSync(outPath, result.contextMarkdown, "utf-8");
39457
+ p41.note(`Saved to: ${outPath}
38174
39458
 
38175
39459
  Review the file and replace [NEEDS INPUT] placeholders with real data.`, "Marketing Context Builder");
38176
39460
  } catch (err) {
38177
- spinner14.stop("Failed to build marketing context.");
38178
- p38.note(String(err), "Marketing Context Builder \u2014 Error");
39461
+ spinner15.stop("Failed to build marketing context.");
39462
+ p41.note(String(err), "Marketing Context Builder \u2014 Error");
38179
39463
  }
38180
39464
  }
38181
39465
  async function detectLocalIntelligenceStatus(baseUrl, model) {
38182
39466
  const osLabel = process.platform === "darwin" ? "macOS" : process.platform === "win32" ? "Windows" : "Linux";
38183
- const ollamaInstalled = spawnSync9("ollama", ["--version"], { stdio: "ignore" }).status === 0;
39467
+ const ollamaInstalled = spawnSync11("ollama", ["--version"], { stdio: "ignore" }).status === 0;
38184
39468
  const modelsUrl = `${baseUrl}/models`;
38185
39469
  try {
38186
39470
  const response = await fetch(modelsUrl, { method: "GET" });
@@ -38233,12 +39517,12 @@ function prioritizeModelOptions(models, favoriteModel, recommendedModel) {
38233
39517
  return unique3;
38234
39518
  }
38235
39519
  async function promptForCustomModel(defaultModel) {
38236
- const input = await p38.text({
39520
+ const input = await p41.text({
38237
39521
  message: "Enter local model id",
38238
39522
  placeholder: "example: gemma3:4b",
38239
39523
  defaultValue: defaultModel
38240
39524
  });
38241
- if (p38.isCancel(input)) return null;
39525
+ if (p41.isCancel(input)) return null;
38242
39526
  const trimmed = String(input).trim();
38243
39527
  return trimmed.length > 0 ? trimmed : null;
38244
39528
  }
@@ -38279,13 +39563,13 @@ async function runLocalPromptChat(baseUrl, defaultModel) {
38279
39563
  }
38280
39564
  infoLines.push("Type your prompt and press Enter.");
38281
39565
  infoLines.push("Use '/back' to return to Local Intelligence menu.");
38282
- p38.note(infoLines.join("\n"), "Local Prompt Flow");
39566
+ p41.note(infoLines.join("\n"), "Local Prompt Flow");
38283
39567
  while (true) {
38284
- const rawPrompt = await p38.text({
39568
+ const rawPrompt = await p41.text({
38285
39569
  message: `Prompt (${activeModel})`,
38286
39570
  placeholder: "Ask anything..."
38287
39571
  });
38288
- if (p38.isCancel(rawPrompt)) {
39572
+ if (p41.isCancel(rawPrompt)) {
38289
39573
  captureSessionSummary(currentProject, sessionId, thread.messages);
38290
39574
  return;
38291
39575
  }
@@ -38306,7 +39590,7 @@ async function runLocalPromptChat(baseUrl, defaultModel) {
38306
39590
  if (semanticCtx.text) {
38307
39591
  systemParts.push(semanticCtx.text);
38308
39592
  }
38309
- const runSpinner = p38.spinner();
39593
+ const runSpinner = p41.spinner();
38310
39594
  runSpinner.start("Invoking local model...");
38311
39595
  try {
38312
39596
  const out = await completeWithRetry(
@@ -38341,7 +39625,7 @@ User: ${prompt}` : prompt,
38341
39625
  }
38342
39626
  } catch (err) {
38343
39627
  runSpinner.stop("Invocation failed.");
38344
- p38.note(err instanceof Error ? err.message : String(err), "Local model error");
39628
+ p41.note(err instanceof Error ? err.message : String(err), "Local model error");
38345
39629
  }
38346
39630
  }
38347
39631
  }
@@ -38378,39 +39662,39 @@ function captureSessionSummary(project, sessionId, messages) {
38378
39662
  }
38379
39663
  }
38380
39664
  function resolveLocalThreadsDir() {
38381
- return path85.resolve(resolvePaperclipHomeDir(), "native-intelligence", "threads");
39665
+ return path90.resolve(resolvePaperclipHomeDir(), "native-intelligence", "threads");
38382
39666
  }
38383
39667
  function loadOrCreateLocalThread() {
38384
39668
  const dir = resolveLocalThreadsDir();
38385
- fs74.mkdirSync(dir, { recursive: true });
38386
- const activePath = path85.resolve(dir, "active-thread.json");
38387
- if (fs74.existsSync(activePath)) {
39669
+ fs79.mkdirSync(dir, { recursive: true });
39670
+ const activePath = path90.resolve(dir, "active-thread.json");
39671
+ if (fs79.existsSync(activePath)) {
38388
39672
  try {
38389
- const parsed = JSON.parse(fs74.readFileSync(activePath, "utf-8"));
39673
+ const parsed = JSON.parse(fs79.readFileSync(activePath, "utf-8"));
38390
39674
  const id2 = typeof parsed.id === "string" && parsed.id.length > 0 ? parsed.id : `thread-${Date.now()}`;
38391
- const threadFile = path85.resolve(dir, `${id2}.json`);
39675
+ const threadFile = path90.resolve(dir, `${id2}.json`);
38392
39676
  const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
38393
39677
  return { id: id2, filePath: threadFile, messages };
38394
39678
  } catch {
38395
39679
  }
38396
39680
  }
38397
39681
  const id = `thread-${Date.now()}`;
38398
- const filePath = path85.resolve(dir, `${id}.json`);
39682
+ const filePath = path90.resolve(dir, `${id}.json`);
38399
39683
  const thread = { id, filePath, messages: [] };
38400
39684
  saveLocalThread(thread);
38401
39685
  return thread;
38402
39686
  }
38403
39687
  function saveLocalThread(thread) {
38404
39688
  const dir = resolveLocalThreadsDir();
38405
- fs74.mkdirSync(dir, { recursive: true });
38406
- fs74.writeFileSync(
39689
+ fs79.mkdirSync(dir, { recursive: true });
39690
+ fs79.writeFileSync(
38407
39691
  thread.filePath,
38408
39692
  `${JSON.stringify({ id: thread.id, messages: thread.messages }, null, 2)}
38409
39693
  `,
38410
39694
  "utf-8"
38411
39695
  );
38412
- const activePath = path85.resolve(dir, "active-thread.json");
38413
- fs74.writeFileSync(
39696
+ const activePath = path90.resolve(dir, "active-thread.json");
39697
+ fs79.writeFileSync(
38414
39698
  activePath,
38415
39699
  `${JSON.stringify({ id: thread.id, messages: thread.messages }, null, 2)}
38416
39700
  `,
@@ -38449,7 +39733,7 @@ async function runNativeIntelligenceFlowSuite(baseUrl, defaultModel, prompt) {
38449
39733
  const primaryContract = contracts.find((contract) => contract.inputs.length > 0) ?? contracts[0];
38450
39734
  const rawBindings = await collectBindingsFromContract(primaryContract, prompt);
38451
39735
  const requiredOutputTypes = primaryContract.outputTypes.length > 0 ? [primaryContract.outputTypes[0]] : void 0;
38452
- const flowSpinner = p38.spinner();
39736
+ const flowSpinner = p41.spinner();
38453
39737
  flowSpinner.start("Running planner/normalizer/recommender/summarizer with your prompt...");
38454
39738
  const plan = await provider.planWorkflow({
38455
39739
  userIntent: prompt,
@@ -38496,7 +39780,7 @@ async function runNativeIntelligenceFlowSuite(baseUrl, defaultModel, prompt) {
38496
39780
  phase: "pre-execution"
38497
39781
  });
38498
39782
  flowSpinner.stop("Flow suite completed.");
38499
- p38.note(
39783
+ p41.note(
38500
39784
  [
38501
39785
  `Prompt: ${prompt}`,
38502
39786
  `Planner nodes: ${plan.proposedNodes.map((n) => n.slug).join(", ")}`,
@@ -38507,7 +39791,7 @@ async function runNativeIntelligenceFlowSuite(baseUrl, defaultModel, prompt) {
38507
39791
  "Native Intelligence Flow Results"
38508
39792
  );
38509
39793
  } catch (err) {
38510
- p38.note(err instanceof Error ? err.message : String(err), "Flow error");
39794
+ p41.note(err instanceof Error ? err.message : String(err), "Flow error");
38511
39795
  }
38512
39796
  }
38513
39797
  async function loadRuntimeContracts() {
@@ -38536,12 +39820,12 @@ async function collectBindingsFromContract(contract, promptSeed) {
38536
39820
  const bindings = {};
38537
39821
  for (const input of contract.inputs) {
38538
39822
  const defaultValue = input.key === "prompt" ? promptSeed : input.defaultValue !== void 0 ? String(input.defaultValue) : "";
38539
- const raw = await p38.text({
39823
+ const raw = await p41.text({
38540
39824
  message: `${contract.slug} \u2192 ${input.key} (${input.type}${input.required ? ", required" : ""})`,
38541
39825
  placeholder: input.required ? `Enter ${input.key}` : `Optional: press Enter to skip ${input.key}`,
38542
39826
  defaultValue
38543
39827
  });
38544
- if (p38.isCancel(raw)) {
39828
+ if (p41.isCancel(raw)) {
38545
39829
  throw new Error("Cancelled while collecting contract input bindings.");
38546
39830
  }
38547
39831
  const value = String(raw).trim();
@@ -38556,7 +39840,7 @@ async function collectBindingsFromContract(contract, promptSeed) {
38556
39840
  return bindings;
38557
39841
  }
38558
39842
  function resolveCurrentProject() {
38559
- return path85.basename(process.cwd());
39843
+ return path90.basename(process.cwd());
38560
39844
  }
38561
39845
  async function runMemoryKnowledgeHub() {
38562
39846
  const project = resolveCurrentProject();
@@ -38564,7 +39848,7 @@ async function runMemoryKnowledgeHub() {
38564
39848
  const stats = getMemoryStats(project);
38565
39849
  const syncStatus = canSync();
38566
39850
  const providerConfig = readProviderConfig();
38567
- const action = await p38.select({
39851
+ const action = await p41.select({
38568
39852
  message: "Memory & Knowledge",
38569
39853
  options: [
38570
39854
  {
@@ -38589,24 +39873,24 @@ async function runMemoryKnowledgeHub() {
38589
39873
  },
38590
39874
  {
38591
39875
  value: "sync",
38592
- label: syncStatus.available ? "Sync to Growthub" : "Sync to Growthub" + pc56.dim(" (unavailable)"),
39876
+ label: syncStatus.available ? "Sync to Growthub" : "Sync to Growthub" + pc61.dim(" (unavailable)"),
38593
39877
  hint: syncStatus.available ? "push memories to hosted account" : syncStatus.reason
38594
39878
  },
38595
39879
  { value: "__back_to_hub", label: "\u2190 Back to main menu" }
38596
39880
  ]
38597
39881
  });
38598
- if (p38.isCancel(action) || action === "__back_to_hub") return "back";
39882
+ if (p41.isCancel(action) || action === "__back_to_hub") return "back";
38599
39883
  if (action === "search") {
38600
- const query = await p38.text({
39884
+ const query = await p41.text({
38601
39885
  message: "Search query",
38602
39886
  placeholder: "what are you looking for?"
38603
39887
  });
38604
- if (p38.isCancel(query)) continue;
39888
+ if (p41.isCancel(query)) continue;
38605
39889
  const text70 = String(query).trim();
38606
39890
  if (!text70) continue;
38607
39891
  const results = searchMemory({ text: text70, project, limit: 15 });
38608
39892
  if (results.totalMatched === 0) {
38609
- p38.note("No matching observations found.", "Search Results");
39893
+ p41.note("No matching observations found.", "Search Results");
38610
39894
  continue;
38611
39895
  }
38612
39896
  const lines = [`Found ${results.totalMatched} match(es), showing top ${results.results.length}:`, ""];
@@ -38627,13 +39911,13 @@ async function runMemoryKnowledgeHub() {
38627
39911
  lines.push(`[${date2}] ${s.request ?? "Session"}: ${s.completed ?? s.learned ?? "(no summary)"}`);
38628
39912
  }
38629
39913
  }
38630
- p38.note(lines.join("\n"), "Search Results");
39914
+ p41.note(lines.join("\n"), "Search Results");
38631
39915
  continue;
38632
39916
  }
38633
39917
  if (action === "timeline") {
38634
39918
  const observations = getObservations(project, { limit: 20 });
38635
39919
  if (observations.length === 0) {
38636
- p38.note(
39920
+ p41.note(
38637
39921
  "No observations stored yet. Start a local prompt chat to begin capturing memories.",
38638
39922
  "Timeline"
38639
39923
  );
@@ -38653,13 +39937,13 @@ async function runMemoryKnowledgeHub() {
38653
39937
  lines.push(` ${obs.narrative.slice(0, 120)}${obs.narrative.length > 120 ? "..." : ""}`);
38654
39938
  }
38655
39939
  }
38656
- p38.note(lines.join("\n"), `Timeline \u2014 ${project} (${observations.length} recent)`);
39940
+ p41.note(lines.join("\n"), `Timeline \u2014 ${project} (${observations.length} recent)`);
38657
39941
  continue;
38658
39942
  }
38659
39943
  if (action === "projects") {
38660
39944
  const projects2 = listMemoryProjects();
38661
39945
  if (projects2.length === 0) {
38662
- p38.note("No memory projects found. Interact with the local prompt chat to start capturing.", "Projects");
39946
+ p41.note("No memory projects found. Interact with the local prompt chat to start capturing.", "Projects");
38663
39947
  continue;
38664
39948
  }
38665
39949
  const lines = [];
@@ -38667,7 +39951,7 @@ async function runMemoryKnowledgeHub() {
38667
39951
  const s = getMemoryStats(proj);
38668
39952
  lines.push(`${proj}: ${s.observationCount} observations, ${s.summaryCount} summaries`);
38669
39953
  }
38670
- p38.note(lines.join("\n"), "Memory Projects");
39954
+ p41.note(lines.join("\n"), "Memory Projects");
38671
39955
  continue;
38672
39956
  }
38673
39957
  if (action === "provider") {
@@ -38679,7 +39963,7 @@ async function runMemoryKnowledgeHub() {
38679
39963
  modelId: result.modelId,
38680
39964
  endpoint: result.endpoint
38681
39965
  });
38682
- p38.note(
39966
+ p41.note(
38683
39967
  `Provider: ${result.provider}
38684
39968
  Model: ${result.modelId ?? "default"}
38685
39969
  API key: ${result.apiKey ? "configured" : "not needed"}`,
@@ -38689,10 +39973,10 @@ API key: ${result.apiKey ? "configured" : "not needed"}`,
38689
39973
  }
38690
39974
  if (action === "sync") {
38691
39975
  if (!syncStatus.available) {
38692
- p38.note(syncStatus.reason ?? "Sync unavailable", "Sync");
39976
+ p41.note(syncStatus.reason ?? "Sync unavailable", "Sync");
38693
39977
  continue;
38694
39978
  }
38695
- const syncSpinner = p38.spinner();
39979
+ const syncSpinner = p41.spinner();
38696
39980
  syncSpinner.start("Syncing memories to Growthub...");
38697
39981
  const syncResult = await syncMemoriesToHosted(project);
38698
39982
  if (syncResult.success) {
@@ -38701,7 +39985,7 @@ API key: ${result.apiKey ? "configured" : "not needed"}`,
38701
39985
  );
38702
39986
  } else {
38703
39987
  syncSpinner.stop("Sync failed.");
38704
- p38.note(syncResult.error ?? "Unknown error", "Sync Error");
39988
+ p41.note(syncResult.error ?? "Unknown error", "Sync Error");
38705
39989
  }
38706
39990
  continue;
38707
39991
  }
@@ -38722,7 +40006,7 @@ async function runGovernedWorkspaceAgentsFlow() {
38722
40006
  async function pickRegisteredFork(message = "Select governed workspace") {
38723
40007
  const forks = listKitForkRegistrations();
38724
40008
  if (forks.length === 0) {
38725
- p38.note(
40009
+ p41.note(
38726
40010
  [
38727
40011
  "No fork-sync registered governed workspaces were found.",
38728
40012
  "",
@@ -38734,22 +40018,22 @@ async function runGovernedWorkspaceAgentsFlow() {
38734
40018
  );
38735
40019
  return null;
38736
40020
  }
38737
- const choice = await p38.select({
40021
+ const choice = await p41.select({
38738
40022
  message,
38739
40023
  options: [
38740
40024
  ...forks.map((fork) => ({
38741
40025
  value: fork.forkId,
38742
- label: `${fork.label ?? fork.forkId} ${pc56.dim(fork.kitId)}`,
40026
+ label: `${fork.label ?? fork.forkId} ${pc61.dim(fork.kitId)}`,
38743
40027
  hint: fork.remote ? "fork-sync + remote" : "fork-sync registered"
38744
40028
  })),
38745
40029
  { value: "__back", label: "\u2190 Back" }
38746
40030
  ]
38747
40031
  });
38748
- if (p38.isCancel(choice) || choice === "__back") return null;
40032
+ if (p41.isCancel(choice) || choice === "__back") return null;
38749
40033
  return forks.find((fork) => fork.forkId === choice) ?? null;
38750
40034
  }
38751
40035
  while (true) {
38752
- const action = await p38.select({
40036
+ const action = await p41.select({
38753
40037
  message: "Governed Workspace Agents",
38754
40038
  options: [
38755
40039
  { value: "list", label: "\u{1F4CB} List agents", hint: "Show hosted agents available to governed workspaces" },
@@ -38761,13 +40045,13 @@ async function runGovernedWorkspaceAgentsFlow() {
38761
40045
  { value: "__back", label: "\u2190 Back to main menu" }
38762
40046
  ]
38763
40047
  });
38764
- if (p38.isCancel(action)) {
38765
- p38.cancel("Cancelled.");
40048
+ if (p41.isCancel(action)) {
40049
+ p41.cancel("Cancelled.");
38766
40050
  process.exit(0);
38767
40051
  }
38768
40052
  if (action === "__back") return "back";
38769
40053
  if (action === "json") {
38770
- p38.note(
40054
+ p41.note(
38771
40055
  [
38772
40056
  "growthub bridge agents list --json",
38773
40057
  "growthub bridge agents inspect <slug> --json",
@@ -38785,7 +40069,7 @@ async function runGovernedWorkspaceAgentsFlow() {
38785
40069
  const result2 = await Promise.resolve().then(() => (init_bridge2(), bridge_exports2)).then(
38786
40070
  (mod) => mod.listHostedAgentBindings({ forkId: fork2.forkId })
38787
40071
  );
38788
- p38.note(
40072
+ p41.note(
38789
40073
  [
38790
40074
  `Workspace: ${fork2.label ?? fork2.forkId}`,
38791
40075
  `Fork ID: ${fork2.forkId}`,
@@ -38804,11 +40088,11 @@ async function runGovernedWorkspaceAgentsFlow() {
38804
40088
  }
38805
40089
  const client = createGrowthubBridgeClient();
38806
40090
  if (action === "list") {
38807
- const spinner15 = p38.spinner();
38808
- spinner15.start("Loading governed workspace agents...");
40091
+ const spinner16 = p41.spinner();
40092
+ spinner16.start("Loading governed workspace agents...");
38809
40093
  const result2 = await client.listHostedAgentManifests();
38810
- spinner15.stop(`Loaded ${result2.agents.length} governed workspace agent(s).`);
38811
- p38.note(
40094
+ spinner16.stop(`Loaded ${result2.agents.length} governed workspace agent(s).`);
40095
+ p41.note(
38812
40096
  [
38813
40097
  `Contract: ${result2.contract ?? "AgentOrchestratorManifest"}`,
38814
40098
  `Diagnostics: ${result2.diagnostics?.status ?? "unknown"}`,
@@ -38824,11 +40108,11 @@ async function runGovernedWorkspaceAgentsFlow() {
38824
40108
  );
38825
40109
  continue;
38826
40110
  }
38827
- const slugRaw = await p38.text({
40111
+ const slugRaw = await p41.text({
38828
40112
  message: action === "bind" ? "Agent slug to bind:" : action === "unbind" ? "Agent slug to unbind:" : "Agent slug to inspect:",
38829
40113
  placeholder: "quick-image-generator-xlq5"
38830
40114
  });
38831
- if (p38.isCancel(slugRaw) || !slugRaw) continue;
40115
+ if (p41.isCancel(slugRaw) || !slugRaw) continue;
38832
40116
  const slug = String(slugRaw);
38833
40117
  if (action === "unbind") {
38834
40118
  const fork2 = await pickRegisteredFork("Select governed workspace to unbind from");
@@ -38836,7 +40120,7 @@ async function runGovernedWorkspaceAgentsFlow() {
38836
40120
  const result2 = await Promise.resolve().then(() => (init_bridge2(), bridge_exports2)).then(
38837
40121
  (mod) => mod.removeHostedAgentBinding({ agentSlug: slug, forkId: fork2.forkId })
38838
40122
  );
38839
- p38.note(
40123
+ p41.note(
38840
40124
  [
38841
40125
  `Agent: ${slug}`,
38842
40126
  `Workspace: ${fork2.label ?? fork2.forkId}`,
@@ -38849,17 +40133,17 @@ async function runGovernedWorkspaceAgentsFlow() {
38849
40133
  );
38850
40134
  continue;
38851
40135
  }
38852
- const spinner14 = p38.spinner();
38853
- spinner14.start(action === "bind" ? "Loading agent before binding..." : "Loading agent manifest...");
40136
+ const spinner15 = p41.spinner();
40137
+ spinner15.start(action === "bind" ? "Loading agent before binding..." : "Loading agent manifest...");
38854
40138
  const result = await client.inspectHostedAgentManifest(slug);
38855
- spinner14.stop("Agent manifest loaded.");
40139
+ spinner15.stop("Agent manifest loaded.");
38856
40140
  const agent = result.agent ?? result.manifest;
38857
40141
  if (!agent) {
38858
- p38.log.error(`Hosted agent manifest not found for slug: ${slug}`);
40142
+ p41.log.error(`Hosted agent manifest not found for slug: ${slug}`);
38859
40143
  continue;
38860
40144
  }
38861
40145
  if (action === "inspect") {
38862
- p38.note(
40146
+ p41.note(
38863
40147
  [
38864
40148
  `Slug: ${agentSlug(agent)}`,
38865
40149
  `Name: ${agentLabel(agent)}`,
@@ -38884,7 +40168,7 @@ async function runGovernedWorkspaceAgentsFlow() {
38884
40168
  warnings: result.warnings,
38885
40169
  resolvedSlug: result.resolvedSlug
38886
40170
  });
38887
- p38.note(
40171
+ p41.note(
38888
40172
  [
38889
40173
  `Agent: ${agentSlug(agent)}`,
38890
40174
  `Workspace: ${fork.label ?? fork.forkId}`,
@@ -38903,63 +40187,59 @@ async function runGovernedWorkspaceAgentsFlow() {
38903
40187
  }
38904
40188
  async function runCreateGovernedWorkspaceFlow(opts) {
38905
40189
  workspaceLoop: while (true) {
38906
- const starterChoice = await p38.select({
38907
- message: opts?.firstRun ? "What do you want to create?" : opts?.title ?? "Create Governed Workspace",
40190
+ const starterChoice = await p41.select({
40191
+ message: opts?.firstRun ? "What do you want to create?" : opts?.title ?? "Custom AI Governed Workspace",
38908
40192
  options: [
38909
40193
  ...opts?.importOnly ? [] : [
38910
40194
  {
38911
- value: "new-greenfield",
38912
- label: opts?.firstRun ? "\u{1F680} New governed workspace" : "\u{1F680} New greenfield workspace",
38913
- hint: "Scaffold a fresh governed workspace"
40195
+ value: "custom-ai-workspace",
40196
+ label: "\u{1F680} Custom AI Governed Workspace"
38914
40197
  }
38915
40198
  ],
38916
40199
  {
38917
40200
  value: "import-github",
38918
- label: opts?.firstRun ? "\u{1F517} Import GitHub repository" : "\u{1F517} Import GitHub repository",
38919
- hint: "Import a public or private repo via the Source Import Agent"
40201
+ label: "\u{1F517} Import GitHub repository"
38920
40202
  },
38921
40203
  {
38922
40204
  value: "import-skill",
38923
- label: opts?.firstRun ? "\u{1F9E0} Import skills.sh skill" : "\u{1F9E0} Import skills.sh skill",
38924
- hint: "Discover live skills, inspect metadata, then import the selected skill"
40205
+ label: "\u{1F9E0} Import skills.sh skill"
38925
40206
  },
38926
40207
  ...opts?.importOnly ? [] : [
38927
40208
  {
38928
40209
  value: "worker-kit",
38929
- label: opts?.firstRun ? "\u{1F9F0} Start from worker kit" : "\u{1F9F0} Start from worker kit",
38930
- hint: "Browse worker kits and materialize one locally"
40210
+ label: "\u{1F9F0} Start from worker kit"
38931
40211
  }
38932
40212
  ],
38933
40213
  opts?.firstRun ? { value: "__full_menu", label: "\u{1F440} Open full discovery menu" } : { value: "__back", label: opts?.backLabel ?? "\u2190 Back" }
38934
40214
  ],
38935
- initialValue: opts?.firstRun ? "new-greenfield" : void 0
40215
+ initialValue: opts?.firstRun ? "custom-ai-workspace" : void 0
38936
40216
  });
38937
- if (p38.isCancel(starterChoice)) {
38938
- p38.cancel("Cancelled.");
40217
+ if (p41.isCancel(starterChoice)) {
40218
+ p41.cancel("Cancelled.");
38939
40219
  process.exit(0);
38940
40220
  }
38941
40221
  if (starterChoice === "__full_menu") return "full-menu";
38942
40222
  if (starterChoice === "__back") return "back";
38943
- if (starterChoice === "new-greenfield") {
38944
- const outRaw = await p38.text({
40223
+ if (starterChoice === "custom-ai-workspace") {
40224
+ const outRaw = await p41.text({
38945
40225
  message: "Destination path for the new workspace (will be created if missing):",
38946
40226
  placeholder: "./my-workspace"
38947
40227
  });
38948
- if (p38.isCancel(outRaw) || !outRaw) continue workspaceLoop;
38949
- const nameRaw = await p38.text({
40228
+ if (p41.isCancel(outRaw) || !outRaw) continue workspaceLoop;
40229
+ const nameRaw = await p41.text({
38950
40230
  message: "Optional label (leave blank to use directory basename):",
38951
40231
  placeholder: ""
38952
40232
  });
38953
- if (p38.isCancel(nameRaw)) continue workspaceLoop;
40233
+ if (p41.isCancel(nameRaw)) continue workspaceLoop;
38954
40234
  await runStarterInit({ out: String(outRaw), name: nameRaw ? String(nameRaw) : void 0 });
38955
40235
  continue workspaceLoop;
38956
40236
  }
38957
40237
  if (starterChoice === "import-github") {
38958
- const repoRaw = await p38.text({
40238
+ const repoRaw = await p41.text({
38959
40239
  message: "GitHub repo (owner/repo or https URL):",
38960
40240
  placeholder: "octocat/Hello-World"
38961
40241
  });
38962
- if (p38.isCancel(repoRaw) || !repoRaw) continue workspaceLoop;
40242
+ if (p41.isCancel(repoRaw) || !repoRaw) continue workspaceLoop;
38963
40243
  const {
38964
40244
  promptForInteractiveWorkspacePath: promptForInteractiveWorkspacePath2,
38965
40245
  startSourceImportFlow: startSourceImportFlow2
@@ -38992,20 +40272,24 @@ async function runCreateGovernedWorkspaceFlow(opts) {
38992
40272
  async function runDiscoveryHub(opts) {
38993
40273
  track("discover_opened");
38994
40274
  printPaperclipCliBanner();
38995
- p38.intro("Growthub Local");
40275
+ p41.intro("Growthub Local");
38996
40276
  if (opts?.start === "create-workspace") {
38997
40277
  const result = await runCreateGovernedWorkspaceFlow({ firstRun: true });
38998
40278
  if (result === "done") return;
38999
40279
  }
39000
40280
  while (true) {
39001
40281
  const workflowAccess = getWorkflowAccess();
39002
- const surfaceChoice = await p38.select({
40282
+ const surfaceChoice = await p41.select({
39003
40283
  message: "What do you want to do first?",
39004
40284
  options: [
39005
40285
  {
39006
40286
  value: "create-workspace",
39007
- label: "\u{1F680} Create Governed Workspace",
39008
- hint: "Start from a repo, skills.sh skill, starter, or worker kit"
40287
+ label: "\u{1F680} Custom AI Governed Workspace"
40288
+ },
40289
+ {
40290
+ value: "workspace-ops",
40291
+ label: "\u{1F3D7}\uFE0F Workspace Operations",
40292
+ hint: "status \xB7 qa \xB7 deploy check \xB7 upstream \xB7 surface \xB7 portal"
39009
40293
  },
39010
40294
  {
39011
40295
  value: "kits",
@@ -39039,15 +40323,15 @@ async function runDiscoveryHub(opts) {
39039
40323
  }
39040
40324
  ]
39041
40325
  });
39042
- if (p38.isCancel(surfaceChoice)) {
39043
- p38.cancel("Cancelled.");
40326
+ if (p41.isCancel(surfaceChoice)) {
40327
+ p41.cancel("Cancelled.");
39044
40328
  process.exit(0);
39045
40329
  }
39046
40330
  if (surfaceChoice === "help") {
39047
- p38.note(
40331
+ p41.note(
39048
40332
  [
39049
40333
  "\u{1F916} Agent Harness: filter by type \u2014 Paperclip Local App (GTM/DX profiles), Open Agents (durable workflow orchestration), Qwen Code CLI, or T3 Code CLI (pingdotgg/t3code).",
39050
- "\u{1F680} Create Governed Workspace: start from a repo, skills.sh skill, greenfield starter, or worker kit.",
40334
+ "Custom AI Governed Workspace: start from a starter, repo, skills.sh skill, or worker kit.",
39051
40335
  "\u{1F9F0} Browse Worker Kits: browse specialized agents and custom workspaces.",
39052
40336
  "\u{1F501} Import Repo or Skill: route directly into the Source Import Agent.",
39053
40337
  "\u2699\uFE0F Settings: GitHub, Fork Sync, workflows, templates, local models, service status, starter, fleet.",
@@ -39083,6 +40367,39 @@ async function runDiscoveryHub(opts) {
39083
40367
  if (result === "done") return;
39084
40368
  continue;
39085
40369
  }
40370
+ if (surfaceChoice === "workspace-ops") {
40371
+ p41.note(
40372
+ [
40373
+ "Workspace commands (run directly):",
40374
+ "",
40375
+ " growthub workspace status --json",
40376
+ " Unified health: bridge, GitHub, fork, agents, config, apps",
40377
+ "",
40378
+ " growthub workspace qa --json",
40379
+ " Validate: config, env, deps, fork, routes, skills",
40380
+ "",
40381
+ " growthub workspace deploy check --json",
40382
+ " Readiness gate: canDeploy, missingSteps, appRoot, envVarsNeeded",
40383
+ "",
40384
+ " growthub workspace deploy vercel --print-env --json",
40385
+ " Print required env var names from .env.example",
40386
+ "",
40387
+ " growthub workspace upstream check --json",
40388
+ " Fork drift state + recommended sync commands",
40389
+ "",
40390
+ " growthub workspace upstream heal --dry-run --json",
40391
+ " Preview upstream heal without applying",
40392
+ "",
40393
+ " growthub workspace surface list --json",
40394
+ " Discover apps/workspace, apps/agency-portal, studio",
40395
+ "",
40396
+ " growthub workspace portal prepare --client <slug> --json",
40397
+ " Scaffold client brand config, env template, handoff doc"
40398
+ ].join("\n"),
40399
+ "Workspace Operations"
40400
+ );
40401
+ continue;
40402
+ }
39086
40403
  if (surfaceChoice === "import-source") {
39087
40404
  const result = await runCreateGovernedWorkspaceFlow({ importOnly: true, title: "Import Repo or Skill" });
39088
40405
  if (result === "done") return;
@@ -39090,7 +40407,7 @@ async function runDiscoveryHub(opts) {
39090
40407
  }
39091
40408
  if (surfaceChoice === "agent-harness") {
39092
40409
  while (true) {
39093
- const harnessType = await p38.select({
40410
+ const harnessType = await p41.select({
39094
40411
  message: "Filter by type",
39095
40412
  options: [
39096
40413
  {
@@ -39119,15 +40436,15 @@ async function runDiscoveryHub(opts) {
39119
40436
  }
39120
40437
  ]
39121
40438
  });
39122
- if (p38.isCancel(harnessType)) {
39123
- p38.cancel("Cancelled.");
40439
+ if (p41.isCancel(harnessType)) {
40440
+ p41.cancel("Cancelled.");
39124
40441
  process.exit(0);
39125
40442
  }
39126
40443
  if (harnessType === "__back_to_hub") break;
39127
40444
  if (harnessType === "paperclip") {
39128
40445
  let paperclipDone = false;
39129
40446
  while (!paperclipDone) {
39130
- const appModeChoice = await p38.select({
40447
+ const appModeChoice = await p41.select({
39131
40448
  message: "How do you want to open Growthub Local?",
39132
40449
  options: [
39133
40450
  {
@@ -39146,18 +40463,18 @@ async function runDiscoveryHub(opts) {
39146
40463
  }
39147
40464
  ]
39148
40465
  });
39149
- if (p38.isCancel(appModeChoice)) {
39150
- p38.cancel("Cancelled.");
40466
+ if (p41.isCancel(appModeChoice)) {
40467
+ p41.cancel("Cancelled.");
39151
40468
  process.exit(0);
39152
40469
  }
39153
40470
  if (appModeChoice === "__back_to_harness") break;
39154
40471
  if (appModeChoice === "load") {
39155
40472
  const existingSurfaces = listLocalSurfaces();
39156
40473
  if (existingSurfaces.length === 0) {
39157
- p38.note("No existing local app profiles were found on this machine.", "Nothing found");
40474
+ p41.note("No existing local app profiles were found on this machine.", "Nothing found");
39158
40475
  continue;
39159
40476
  }
39160
- const existingChoice = await p38.select({
40477
+ const existingChoice = await p41.select({
39161
40478
  message: "Select an existing app surface",
39162
40479
  options: [
39163
40480
  ...existingSurfaces.map((surface) => ({
@@ -39168,8 +40485,8 @@ async function runDiscoveryHub(opts) {
39168
40485
  { value: "__back_to_app_mode", label: "\u2190 Back to app options" }
39169
40486
  ]
39170
40487
  });
39171
- if (p38.isCancel(existingChoice)) {
39172
- p38.cancel("Cancelled.");
40488
+ if (p41.isCancel(existingChoice)) {
40489
+ p41.cancel("Cancelled.");
39173
40490
  process.exit(0);
39174
40491
  }
39175
40492
  if (existingChoice === "__back_to_app_mode") {
@@ -39177,7 +40494,7 @@ async function runDiscoveryHub(opts) {
39177
40494
  }
39178
40495
  const selectedSurface = existingSurfaces.find((surface) => surface.instanceId === existingChoice);
39179
40496
  if (!selectedSurface) {
39180
- p38.cancel("Selected profile not found.");
40497
+ p41.cancel("Selected profile not found.");
39181
40498
  process.exit(1);
39182
40499
  }
39183
40500
  process.env.PAPERCLIP_SURFACE_PROFILE = selectedSurface.profile;
@@ -39189,7 +40506,7 @@ async function runDiscoveryHub(opts) {
39189
40506
  });
39190
40507
  return;
39191
40508
  }
39192
- const profileChoice = await p38.select({
40509
+ const profileChoice = await p41.select({
39193
40510
  message: "Which new app surface do you want to create?",
39194
40511
  options: [
39195
40512
  {
@@ -39208,8 +40525,8 @@ async function runDiscoveryHub(opts) {
39208
40525
  }
39209
40526
  ]
39210
40527
  });
39211
- if (p38.isCancel(profileChoice)) {
39212
- p38.cancel("Cancelled.");
40528
+ if (p41.isCancel(profileChoice)) {
40529
+ p41.cancel("Cancelled.");
39213
40530
  process.exit(0);
39214
40531
  }
39215
40532
  if (profileChoice === "__back_to_app_mode") {
@@ -39247,7 +40564,7 @@ async function runDiscoveryHub(opts) {
39247
40564
  while (true) {
39248
40565
  const hostedSession = readSession();
39249
40566
  const growthubConnected = Boolean(hostedSession && !isSessionExpired(hostedSession));
39250
- const settingsChoice = await p38.select({
40567
+ const settingsChoice = await p41.select({
39251
40568
  message: "Settings",
39252
40569
  options: [
39253
40570
  {
@@ -39277,12 +40594,12 @@ async function runDiscoveryHub(opts) {
39277
40594
  },
39278
40595
  {
39279
40596
  value: "custom-workspace-starter",
39280
- label: "\u{1F680} Create Governed Workspace",
39281
- hint: "Start from a repo, skills.sh skill, starter, or worker kit"
40597
+ label: "Custom AI Governed Workspace",
40598
+ hint: "Start from a starter, repo, skills.sh skill, or worker kit"
39282
40599
  },
39283
40600
  {
39284
40601
  value: "workflows",
39285
- label: workflowAccess.state === "ready" ? "\u{1F517} Workflows" : "\u{1F517} Workflows" + pc56.dim(" (locked)"),
40602
+ label: workflowAccess.state === "ready" ? "\u{1F517} Workflows" : "\u{1F517} Workflows" + pc61.dim(" (locked)"),
39286
40603
  hint: workflowAccess.state === "ready" ? "CMS contracts, dynamic pipelines, and saved workflows" : workflowAccess.reason
39287
40604
  },
39288
40605
  {
@@ -39311,8 +40628,8 @@ async function runDiscoveryHub(opts) {
39311
40628
  }
39312
40629
  ]
39313
40630
  });
39314
- if (p38.isCancel(settingsChoice)) {
39315
- p38.cancel("Cancelled.");
40631
+ if (p41.isCancel(settingsChoice)) {
40632
+ p41.cancel("Cancelled.");
39316
40633
  process.exit(0);
39317
40634
  }
39318
40635
  if (settingsChoice === "__back_to_hub") break;
@@ -39366,11 +40683,11 @@ async function runDiscoveryHub(opts) {
39366
40683
  if (surfaceChoice2 === "skills-catalog") {
39367
40684
  const { readSkillCatalog: readSkillCatalog2 } = await Promise.resolve().then(() => (init_catalog2(), catalog_exports));
39368
40685
  const catalog = readSkillCatalog2({ root: process.cwd() });
39369
- p38.note(
40686
+ p41.note(
39370
40687
  [
39371
- `Root: ${pc56.cyan(catalog.catalog.root ?? process.cwd())}`,
39372
- `Skills discovered: ${pc56.bold(String(catalog.entries.length))}`,
39373
- catalog.warnings.length > 0 ? `Warnings: ${pc56.yellow(String(catalog.warnings.length))}` : `Warnings: 0`,
40688
+ `Root: ${pc61.cyan(catalog.catalog.root ?? process.cwd())}`,
40689
+ `Skills discovered: ${pc61.bold(String(catalog.entries.length))}`,
40690
+ catalog.warnings.length > 0 ? `Warnings: ${pc61.yellow(String(catalog.warnings.length))}` : `Warnings: 0`,
39374
40691
  "",
39375
40692
  "Invoke directly:",
39376
40693
  " growthub skills list --json",
@@ -39402,12 +40719,12 @@ function isInstallerMode() {
39402
40719
  }
39403
40720
  function listLocalSurfaces() {
39404
40721
  const homeDir = resolvePaperclipHomeDir();
39405
- const instancesDir = path85.resolve(homeDir, "instances");
39406
- if (!fs74.existsSync(instancesDir)) return [];
39407
- return fs74.readdirSync(instancesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
40722
+ const instancesDir = path90.resolve(homeDir, "instances");
40723
+ if (!fs79.existsSync(instancesDir)) return [];
40724
+ return fs79.readdirSync(instancesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
39408
40725
  const instanceId = entry.name;
39409
- const configPath = path85.resolve(instancesDir, instanceId, "config.json");
39410
- if (!fs74.existsSync(configPath)) return null;
40726
+ const configPath = path90.resolve(instancesDir, instanceId, "config.json");
40727
+ if (!fs79.existsSync(configPath)) return null;
39411
40728
  try {
39412
40729
  const config = readConfig(configPath);
39413
40730
  if (!config) return null;
@@ -39448,7 +40765,7 @@ applyDataDirOverride(bootstrapOptions, {
39448
40765
  loadPaperclipEnvFile(bootstrapOptions.config);
39449
40766
  var bootstrapConfig = readConfig(resolveConfigPath(bootstrapOptions.config));
39450
40767
  var surfaceRuntime = initializeSurfaceRuntimeContract(resolveSurfaceProfile(bootstrapConfig) ?? void 0);
39451
- program.name("growthub").description("Growthub CLI \u2014 setup, configure, and run your local Growthub instance").version(resolveCliVersion()).addHelpText("after", `
40768
+ program.name("growthub").description("Growthub CLI \u2014 setup, configure, and run your local Growthub instance").version(resolveCliVersion2()).addHelpText("after", `
39452
40769
  Worker Kits (agent execution environments):
39453
40770
 
39454
40771
  Discovery: