@growthub/cli 0.3.45 → 0.3.48

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 (3) hide show
  1. package/README.md +25 -3
  2. package/dist/index.js +1540 -508
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6268,9 +6268,9 @@ async function runDatabaseBackup(opts) {
6268
6268
  WHERE n.nspname = ${schema_name} AND t.relname = ${tablename} AND c.contype = 'p'
6269
6269
  GROUP BY c.conname
6270
6270
  `;
6271
- for (const p19 of pk) {
6272
- const cols = p19.column_names.map((c) => `"${c}"`).join(", ");
6273
- colDefs.push(` CONSTRAINT "${p19.constraint_name}" PRIMARY KEY (${cols})`);
6271
+ for (const p20 of pk) {
6272
+ const cols = p20.column_names.map((c) => `"${c}"`).join(", ");
6273
+ colDefs.push(` CONSTRAINT "${p20.constraint_name}" PRIMARY KEY (${cols})`);
6274
6274
  }
6275
6275
  emit(`CREATE TABLE ${qualifiedTableName} (`);
6276
6276
  emit(colDefs.join(",\n"));
@@ -8016,9 +8016,9 @@ var init_onboard = __esm({
8016
8016
  init_onboard();
8017
8017
  init_doctor();
8018
8018
  import { Command } from "commander";
8019
- import * as p18 from "@clack/prompts";
8020
- import fs16 from "node:fs";
8021
- import path23 from "node:path";
8019
+ import * as p19 from "@clack/prompts";
8020
+ import fs19 from "node:fs";
8021
+ import path27 from "node:path";
8022
8022
 
8023
8023
  // src/commands/env.ts
8024
8024
  init_store();
@@ -8321,7 +8321,7 @@ var SECTION_LABELS = {
8321
8321
  };
8322
8322
  function defaultConfig() {
8323
8323
  const instanceId = resolvePaperclipInstanceId();
8324
- return {
8324
+ const config = {
8325
8325
  $meta: {
8326
8326
  version: 1,
8327
8327
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8360,6 +8360,7 @@ function defaultConfig() {
8360
8360
  storage: defaultStorageConfig(),
8361
8361
  secrets: defaultSecretsConfig()
8362
8362
  };
8363
+ return config;
8363
8364
  }
8364
8365
  async function configure(opts) {
8365
8366
  printPaperclipCliBanner();
@@ -8676,8 +8677,8 @@ function printItemCompleted(item) {
8676
8677
  const changes = Array.isArray(item.changes) ? item.changes : [];
8677
8678
  const entries = changes.map((changeRaw) => asRecord(changeRaw)).filter((change) => Boolean(change)).map((change) => {
8678
8679
  const kind = asString(change.kind, "update");
8679
- const path24 = asString(change.path, "unknown");
8680
- return `${kind} ${path24}`;
8680
+ const path28 = asString(change.path, "unknown");
8681
+ return `${kind} ${path28}`;
8681
8682
  });
8682
8683
  const preview = entries.length > 0 ? entries.slice(0, 6).join(", ") : "none";
8683
8684
  const more = entries.length > 6 ? ` (+${entries.length - 6} more)` : "";
@@ -9695,26 +9696,26 @@ var PaperclipApiClient = class {
9695
9696
  this.apiKey = opts.apiKey?.trim() || void 0;
9696
9697
  this.runId = opts.runId?.trim() || void 0;
9697
9698
  }
9698
- get(path24, opts) {
9699
- return this.request(path24, { method: "GET" }, opts);
9699
+ get(path28, opts) {
9700
+ return this.request(path28, { method: "GET" }, opts);
9700
9701
  }
9701
- post(path24, body, opts) {
9702
- return this.request(path24, {
9702
+ post(path28, body, opts) {
9703
+ return this.request(path28, {
9703
9704
  method: "POST",
9704
9705
  body: body === void 0 ? void 0 : JSON.stringify(body)
9705
9706
  }, opts);
9706
9707
  }
9707
- patch(path24, body, opts) {
9708
- return this.request(path24, {
9708
+ patch(path28, body, opts) {
9709
+ return this.request(path28, {
9709
9710
  method: "PATCH",
9710
9711
  body: body === void 0 ? void 0 : JSON.stringify(body)
9711
9712
  }, opts);
9712
9713
  }
9713
- delete(path24, opts) {
9714
- return this.request(path24, { method: "DELETE" }, opts);
9714
+ delete(path28, opts) {
9715
+ return this.request(path28, { method: "DELETE" }, opts);
9715
9716
  }
9716
- async request(path24, init, opts) {
9717
- const url = buildUrl(this.apiBase, path24);
9717
+ async request(path28, init, opts) {
9718
+ const url = buildUrl(this.apiBase, path28);
9718
9719
  const headers = {
9719
9720
  accept: "application/json",
9720
9721
  ...toStringRecord(init.headers)
@@ -9748,8 +9749,8 @@ var PaperclipApiClient = class {
9748
9749
  return safeParseJson(text58);
9749
9750
  }
9750
9751
  };
9751
- function buildUrl(apiBase, path24) {
9752
- const normalizedPath = path24.startsWith("/") ? path24 : `/${path24}`;
9752
+ function buildUrl(apiBase, path28) {
9753
+ const normalizedPath = path28.startsWith("/") ? path28 : `/${path28}`;
9753
9754
  const [pathname, query] = normalizedPath.split("?");
9754
9755
  const url = new URL2(apiBase);
9755
9756
  url.pathname = `${url.pathname.replace(/\/+$/, "")}${pathname}`;
@@ -10164,14 +10165,1026 @@ function safeParseLogLine(line) {
10164
10165
  init_run();
10165
10166
  init_auth_bootstrap_ceo();
10166
10167
 
10168
+ // src/commands/auth-login.ts
10169
+ init_store();
10170
+ init_env();
10171
+ import os3 from "node:os";
10172
+ import * as p14 from "@clack/prompts";
10173
+ import pc18 from "picocolors";
10174
+ import open from "open";
10175
+
10176
+ // src/auth/login-flow.ts
10177
+ import { createServer } from "node:http";
10178
+ import { randomBytes as randomBytes5 } from "node:crypto";
10179
+ import os2 from "node:os";
10180
+ import { URL as URL3 } from "node:url";
10181
+ var DEFAULT_HOSTED_LOGIN_PATH = "/cli/login";
10182
+ var CALLBACK_PATH = "/cli-callback";
10183
+ function randomState() {
10184
+ return randomBytes5(16).toString("hex");
10185
+ }
10186
+ function trimSlashes(value) {
10187
+ return value.replace(/\/+$/, "");
10188
+ }
10189
+ function pickParam(url, name) {
10190
+ const value = url.searchParams.get(name);
10191
+ if (value === null) return void 0;
10192
+ const trimmed = value.trim();
10193
+ return trimmed.length > 0 ? trimmed : void 0;
10194
+ }
10195
+ function renderSuccessPage(hostedBaseUrl) {
10196
+ const safeBase = hostedBaseUrl.replace(/"/g, """);
10197
+ return `<!doctype html>
10198
+ <html lang="en">
10199
+ <head>
10200
+ <meta charset="utf-8" />
10201
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
10202
+ <title>Growthub CLI connected</title>
10203
+ <style>
10204
+ body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; background: #0b0f14; color: #f5f7fa; }
10205
+ main { min-height: 100vh; display: grid; place-items: center; padding: 24px; }
10206
+ section { width: min(560px, 100%); background: #121821; border: 1px solid #263244; border-radius: 16px; padding: 24px; box-sizing: border-box; }
10207
+ h1 { margin: 0 0 12px; font-size: 22px; }
10208
+ p { margin: 0 0 12px; line-height: 1.5; color: #c7d2e0; }
10209
+ a { color: #7dd3fc; }
10210
+ </style>
10211
+ </head>
10212
+ <body>
10213
+ <main>
10214
+ <section>
10215
+ <h1>Growthub CLI connected</h1>
10216
+ <p>Your local CLI now has a hosted session token. You can close this tab and return to your terminal.</p>
10217
+ <p>Hosted app: <a href="${safeBase}">${safeBase}</a></p>
10218
+ </section>
10219
+ </main>
10220
+ <script>window.setTimeout(() => { try { window.close(); } catch {} }, 1200);</script>
10221
+ </body>
10222
+ </html>`;
10223
+ }
10224
+ function renderErrorPage(message) {
10225
+ const safeMessage = message.replace(/</g, "&lt;");
10226
+ return `<!doctype html>
10227
+ <html lang="en"><head><meta charset="utf-8" /><title>Growthub CLI login error</title></head>
10228
+ <body style="font-family: ui-sans-serif, system-ui, sans-serif; background: #0b0f14; color: #f5f7fa; padding: 24px;">
10229
+ <h1>Login error</h1>
10230
+ <p>${safeMessage}</p>
10231
+ <p>Return to your terminal and try again.</p>
10232
+ </body></html>`;
10233
+ }
10234
+ function listenOnEphemeralLoopback(server) {
10235
+ return new Promise((resolve2, reject) => {
10236
+ server.once("error", reject);
10237
+ server.listen({ host: "127.0.0.1", port: 0 }, () => {
10238
+ server.off("error", reject);
10239
+ const address = server.address();
10240
+ if (!address || typeof address === "string") {
10241
+ reject(new Error("Failed to bind loopback port for CLI auth callback."));
10242
+ return;
10243
+ }
10244
+ resolve2(address.port);
10245
+ });
10246
+ });
10247
+ }
10248
+ async function startLoginFlow(opts) {
10249
+ const hostedBaseUrl = trimSlashes(opts.hostedBaseUrl);
10250
+ if (!hostedBaseUrl) {
10251
+ throw new Error("hostedBaseUrl is required to start the CLI login flow.");
10252
+ }
10253
+ try {
10254
+ new URL3(hostedBaseUrl);
10255
+ } catch {
10256
+ throw new Error(`Invalid hosted base URL: ${opts.hostedBaseUrl}`);
10257
+ }
10258
+ const state = randomState();
10259
+ const machineLabel = opts.machineLabel?.trim() || os2.hostname();
10260
+ const workspaceLabel = opts.workspaceLabel?.trim();
10261
+ const timeoutMs = Math.max(3e4, opts.timeoutMs ?? 5 * 6e4);
10262
+ let resolver = null;
10263
+ let rejecter = null;
10264
+ const waitPromise = new Promise((resolve2, reject) => {
10265
+ resolver = resolve2;
10266
+ rejecter = reject;
10267
+ });
10268
+ const server = createServer((req, res) => {
10269
+ try {
10270
+ const host = req.headers.host ?? "127.0.0.1";
10271
+ const requestUrl = new URL3(req.url ?? "/", `http://${host}`);
10272
+ if (requestUrl.pathname !== CALLBACK_PATH) {
10273
+ res.statusCode = 404;
10274
+ res.end("Not found");
10275
+ return;
10276
+ }
10277
+ const incomingState = pickParam(requestUrl, "state");
10278
+ if (!incomingState || incomingState !== state) {
10279
+ res.statusCode = 400;
10280
+ res.setHeader("content-type", "text/html; charset=utf-8");
10281
+ res.end(renderErrorPage("State token mismatch. Restart `growthub auth login`."));
10282
+ rejecter?.(new Error("CLI auth callback rejected \u2014 state mismatch."));
10283
+ return;
10284
+ }
10285
+ const error = pickParam(requestUrl, "error");
10286
+ if (error) {
10287
+ res.statusCode = 400;
10288
+ res.setHeader("content-type", "text/html; charset=utf-8");
10289
+ res.end(renderErrorPage(error));
10290
+ rejecter?.(new Error(`Hosted app reported login error: ${error}`));
10291
+ return;
10292
+ }
10293
+ const token = pickParam(requestUrl, "token");
10294
+ if (!token) {
10295
+ res.statusCode = 400;
10296
+ res.setHeader("content-type", "text/html; charset=utf-8");
10297
+ res.end(renderErrorPage("Missing token in callback."));
10298
+ rejecter?.(new Error("CLI auth callback missing token."));
10299
+ return;
10300
+ }
10301
+ const result = {
10302
+ state,
10303
+ token,
10304
+ hostedBaseUrl,
10305
+ expiresAt: pickParam(requestUrl, "expiresAt"),
10306
+ userId: pickParam(requestUrl, "userId"),
10307
+ email: pickParam(requestUrl, "email"),
10308
+ orgId: pickParam(requestUrl, "orgId"),
10309
+ orgName: pickParam(requestUrl, "orgName"),
10310
+ machineLabel: pickParam(requestUrl, "machineLabel") ?? machineLabel
10311
+ };
10312
+ res.statusCode = 200;
10313
+ res.setHeader("content-type", "text/html; charset=utf-8");
10314
+ res.end(renderSuccessPage(hostedBaseUrl));
10315
+ resolver?.(result);
10316
+ } catch (err) {
10317
+ const message = err instanceof Error ? err.message : String(err);
10318
+ res.statusCode = 500;
10319
+ res.end(message);
10320
+ rejecter?.(err instanceof Error ? err : new Error(message));
10321
+ }
10322
+ });
10323
+ const port = await listenOnEphemeralLoopback(server);
10324
+ const callbackUrl = `http://127.0.0.1:${port}${CALLBACK_PATH}`;
10325
+ const hostedLoginPath = opts.hostedLoginPath ?? DEFAULT_HOSTED_LOGIN_PATH;
10326
+ const loginUrl = (() => {
10327
+ const url = new URL3(hostedLoginPath, `${hostedBaseUrl}/`);
10328
+ url.searchParams.set("state", state);
10329
+ url.searchParams.set("callback", callbackUrl);
10330
+ url.searchParams.set("machineLabel", machineLabel);
10331
+ if (workspaceLabel) url.searchParams.set("workspaceLabel", workspaceLabel);
10332
+ url.searchParams.set("source", "cli");
10333
+ return url.toString();
10334
+ })();
10335
+ let timeoutHandle = setTimeout(() => {
10336
+ rejecter?.(new Error(`CLI login timed out after ${Math.round(timeoutMs / 1e3)}s.`));
10337
+ }, timeoutMs);
10338
+ if (typeof timeoutHandle.unref === "function") timeoutHandle.unref();
10339
+ const close = () => {
10340
+ if (timeoutHandle) {
10341
+ clearTimeout(timeoutHandle);
10342
+ timeoutHandle = null;
10343
+ }
10344
+ server.close();
10345
+ };
10346
+ const waitForCallback = async () => {
10347
+ try {
10348
+ const result = await waitPromise;
10349
+ return result;
10350
+ } finally {
10351
+ close();
10352
+ }
10353
+ };
10354
+ return {
10355
+ state,
10356
+ callbackUrl,
10357
+ loginUrl,
10358
+ waitForCallback,
10359
+ close
10360
+ };
10361
+ }
10362
+
10363
+ // src/auth/session-store.ts
10364
+ import fs11 from "node:fs";
10365
+ import path11 from "node:path";
10366
+
10367
+ // src/auth/paths.ts
10368
+ init_home();
10369
+ import path10 from "node:path";
10370
+ function resolveAuthDir() {
10371
+ return path10.resolve(resolvePaperclipHomeDir(), "auth");
10372
+ }
10373
+ function resolveProfilesDir() {
10374
+ return path10.resolve(resolvePaperclipHomeDir(), "profiles");
10375
+ }
10376
+ function resolveSessionPath() {
10377
+ return path10.resolve(resolveAuthDir(), "session.json");
10378
+ }
10379
+ function resolveHostedOverlayPath() {
10380
+ return path10.resolve(resolveProfilesDir(), "hosted-overlay.json");
10381
+ }
10382
+ function resolveEffectiveProfilePath() {
10383
+ return path10.resolve(resolveProfilesDir(), "effective-profile.json");
10384
+ }
10385
+
10386
+ // src/auth/session-store.ts
10387
+ function parseJson3(filePath) {
10388
+ try {
10389
+ return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
10390
+ } catch (err) {
10391
+ throw new Error(
10392
+ `Failed to parse auth session at ${filePath}: ${err instanceof Error ? err.message : String(err)}`
10393
+ );
10394
+ }
10395
+ }
10396
+ function toStringOrUndefined2(value) {
10397
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
10398
+ }
10399
+ function normalizeSession(raw) {
10400
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
10401
+ const record = raw;
10402
+ const accessToken = toStringOrUndefined2(record.accessToken);
10403
+ const hostedBaseUrl = toStringOrUndefined2(record.hostedBaseUrl);
10404
+ if (!accessToken || !hostedBaseUrl) return null;
10405
+ const issuedAt = toStringOrUndefined2(record.issuedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
10406
+ return {
10407
+ version: 1,
10408
+ hostedBaseUrl,
10409
+ accessToken,
10410
+ expiresAt: toStringOrUndefined2(record.expiresAt),
10411
+ userId: toStringOrUndefined2(record.userId),
10412
+ email: toStringOrUndefined2(record.email),
10413
+ orgId: toStringOrUndefined2(record.orgId),
10414
+ orgName: toStringOrUndefined2(record.orgName),
10415
+ machineLabel: toStringOrUndefined2(record.machineLabel),
10416
+ issuedAt
10417
+ };
10418
+ }
10419
+ function readSession() {
10420
+ const filePath = resolveSessionPath();
10421
+ if (!fs11.existsSync(filePath)) return null;
10422
+ const raw = parseJson3(filePath);
10423
+ return normalizeSession(raw);
10424
+ }
10425
+ function writeSession(session) {
10426
+ const filePath = resolveSessionPath();
10427
+ fs11.mkdirSync(resolveAuthDir(), { recursive: true });
10428
+ fs11.writeFileSync(filePath, `${JSON.stringify(session, null, 2)}
10429
+ `, { mode: 384 });
10430
+ try {
10431
+ fs11.chmodSync(filePath, 384);
10432
+ } catch {
10433
+ }
10434
+ }
10435
+ function clearSession() {
10436
+ const filePath = resolveSessionPath();
10437
+ if (!fs11.existsSync(filePath)) return false;
10438
+ fs11.rmSync(filePath, { force: true });
10439
+ return true;
10440
+ }
10441
+ function isSessionExpired(session, now = /* @__PURE__ */ new Date()) {
10442
+ if (!session.expiresAt) return false;
10443
+ const expires = Date.parse(session.expiresAt);
10444
+ if (Number.isNaN(expires)) return false;
10445
+ return expires <= now.getTime();
10446
+ }
10447
+ function describeSessionPath() {
10448
+ return path11.resolve(resolveSessionPath());
10449
+ }
10450
+
10451
+ // src/auth/overlay-store.ts
10452
+ import fs12 from "node:fs";
10453
+ import path12 from "node:path";
10454
+ function parseJson4(filePath) {
10455
+ try {
10456
+ return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
10457
+ } catch (err) {
10458
+ throw new Error(
10459
+ `Failed to parse hosted overlay at ${filePath}: ${err instanceof Error ? err.message : String(err)}`
10460
+ );
10461
+ }
10462
+ }
10463
+ function toStringOrUndefined3(value) {
10464
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
10465
+ }
10466
+ function toStringArray(value) {
10467
+ if (!Array.isArray(value)) return [];
10468
+ const out = [];
10469
+ for (const item of value) {
10470
+ const normalized = toStringOrUndefined3(item);
10471
+ if (normalized && !out.includes(normalized)) out.push(normalized);
10472
+ }
10473
+ return out;
10474
+ }
10475
+ function toRecord(value) {
10476
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
10477
+ return value;
10478
+ }
10479
+ function normalizeExecutionDefaults(value) {
10480
+ const record = toRecord(value) ?? {};
10481
+ const modeRaw = toStringOrUndefined3(record.preferredMode);
10482
+ const preferredMode = modeRaw === "local" || modeRaw === "serverless" || modeRaw === "browser" || modeRaw === "auto" ? modeRaw : "local";
10483
+ return {
10484
+ preferredMode,
10485
+ allowServerlessFallback: typeof record.allowServerlessFallback === "boolean" ? record.allowServerlessFallback : false,
10486
+ allowBrowserBridge: typeof record.allowBrowserBridge === "boolean" ? record.allowBrowserBridge : false
10487
+ };
10488
+ }
10489
+ function defaultExecutionPreferences() {
10490
+ return {
10491
+ preferredMode: "local",
10492
+ allowServerlessFallback: false,
10493
+ allowBrowserBridge: false
10494
+ };
10495
+ }
10496
+ function normalizeOverlay(raw) {
10497
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
10498
+ const record = raw;
10499
+ const hostedBaseUrl = toStringOrUndefined3(record.hostedBaseUrl);
10500
+ if (!hostedBaseUrl) return null;
10501
+ return {
10502
+ version: 1,
10503
+ hostedBaseUrl,
10504
+ userId: toStringOrUndefined3(record.userId),
10505
+ email: toStringOrUndefined3(record.email),
10506
+ displayName: toStringOrUndefined3(record.displayName),
10507
+ orgId: toStringOrUndefined3(record.orgId),
10508
+ orgName: toStringOrUndefined3(record.orgName),
10509
+ entitlements: toStringArray(record.entitlements),
10510
+ gatedKitSlugs: toStringArray(record.gatedKitSlugs),
10511
+ executionDefaults: normalizeExecutionDefaults(record.executionDefaults),
10512
+ linkedInstanceId: toStringOrUndefined3(record.linkedInstanceId),
10513
+ lastPulledAt: toStringOrUndefined3(record.lastPulledAt),
10514
+ lastPushedAt: toStringOrUndefined3(record.lastPushedAt),
10515
+ extra: toRecord(record.extra)
10516
+ };
10517
+ }
10518
+ function readHostedOverlay() {
10519
+ const filePath = resolveHostedOverlayPath();
10520
+ if (!fs12.existsSync(filePath)) return null;
10521
+ return normalizeOverlay(parseJson4(filePath));
10522
+ }
10523
+ function writeHostedOverlay(overlay) {
10524
+ const filePath = resolveHostedOverlayPath();
10525
+ fs12.mkdirSync(resolveProfilesDir(), { recursive: true });
10526
+ fs12.writeFileSync(filePath, `${JSON.stringify(overlay, null, 2)}
10527
+ `, { mode: 384 });
10528
+ try {
10529
+ fs12.chmodSync(filePath, 384);
10530
+ } catch {
10531
+ }
10532
+ }
10533
+ function clearHostedOverlay() {
10534
+ const filePath = resolveHostedOverlayPath();
10535
+ if (!fs12.existsSync(filePath)) return false;
10536
+ fs12.rmSync(filePath, { force: true });
10537
+ return true;
10538
+ }
10539
+ function describeHostedOverlayPath() {
10540
+ return path12.resolve(resolveHostedOverlayPath());
10541
+ }
10542
+ function seedHostedOverlayFromSession(input) {
10543
+ return {
10544
+ version: 1,
10545
+ hostedBaseUrl: input.hostedBaseUrl,
10546
+ userId: input.userId,
10547
+ email: input.email,
10548
+ displayName: input.email,
10549
+ orgId: input.orgId,
10550
+ orgName: input.orgName,
10551
+ entitlements: [],
10552
+ gatedKitSlugs: [],
10553
+ executionDefaults: defaultExecutionPreferences(),
10554
+ linkedInstanceId: input.linkedInstanceId,
10555
+ lastPulledAt: void 0,
10556
+ lastPushedAt: void 0,
10557
+ extra: input.machineLabel ? { machineLabel: input.machineLabel } : void 0
10558
+ };
10559
+ }
10560
+
10561
+ // src/auth/effective-profile.ts
10562
+ init_store();
10563
+ init_home();
10564
+ import fs13 from "node:fs";
10565
+ import path13 from "node:path";
10566
+ function toLocalWorkspaceView(configPath, config) {
10567
+ return {
10568
+ instanceId: resolvePaperclipInstanceId(),
10569
+ configPath,
10570
+ surfaceProfile: config?.surface?.profile === "dx" || config?.surface?.profile === "gtm" ? config.surface.profile : null,
10571
+ serverPort: typeof config?.server?.port === "number" ? config.server.port : null,
10572
+ serverHost: typeof config?.server?.host === "string" ? config.server.host : null,
10573
+ hasConfiguredToken: Boolean(config?.auth?.token?.trim()),
10574
+ growthubBaseUrl: config?.auth?.growthubBaseUrl?.trim() || null,
10575
+ growthubPortalBaseUrl: config?.auth?.growthubPortalBaseUrl?.trim() || null,
10576
+ machineLabel: config?.auth?.growthubMachineLabel?.trim() || null,
10577
+ workspaceLabel: config?.auth?.growthubWorkspaceLabel?.trim() || null
10578
+ };
10579
+ }
10580
+ function toHostedOverlayView(overlay) {
10581
+ if (!overlay) {
10582
+ return {
10583
+ present: false,
10584
+ hostedBaseUrl: null,
10585
+ userId: null,
10586
+ email: null,
10587
+ displayName: null,
10588
+ orgId: null,
10589
+ orgName: null,
10590
+ entitlements: [],
10591
+ gatedKitSlugs: [],
10592
+ executionDefaults: {
10593
+ preferredMode: "local",
10594
+ allowServerlessFallback: false,
10595
+ allowBrowserBridge: false
10596
+ },
10597
+ linkedInstanceId: null,
10598
+ lastPulledAt: null,
10599
+ lastPushedAt: null
10600
+ };
10601
+ }
10602
+ return {
10603
+ present: true,
10604
+ hostedBaseUrl: overlay.hostedBaseUrl || null,
10605
+ userId: overlay.userId ?? null,
10606
+ email: overlay.email ?? null,
10607
+ displayName: overlay.displayName ?? null,
10608
+ orgId: overlay.orgId ?? null,
10609
+ orgName: overlay.orgName ?? null,
10610
+ entitlements: overlay.entitlements,
10611
+ gatedKitSlugs: overlay.gatedKitSlugs,
10612
+ executionDefaults: overlay.executionDefaults,
10613
+ linkedInstanceId: overlay.linkedInstanceId ?? null,
10614
+ lastPulledAt: overlay.lastPulledAt ?? null,
10615
+ lastPushedAt: overlay.lastPushedAt ?? null
10616
+ };
10617
+ }
10618
+ function toSessionView(session, now) {
10619
+ if (!session) {
10620
+ return {
10621
+ present: false,
10622
+ expired: false,
10623
+ expiresAt: null,
10624
+ userId: null,
10625
+ hostedBaseUrl: null
10626
+ };
10627
+ }
10628
+ let expired = false;
10629
+ if (session.expiresAt) {
10630
+ const expires = Date.parse(session.expiresAt);
10631
+ if (!Number.isNaN(expires)) {
10632
+ expired = expires <= now.getTime();
10633
+ }
10634
+ }
10635
+ return {
10636
+ present: true,
10637
+ expired,
10638
+ expiresAt: session.expiresAt ?? null,
10639
+ userId: session.userId ?? null,
10640
+ hostedBaseUrl: session.hostedBaseUrl
10641
+ };
10642
+ }
10643
+ function computeEffectiveProfile(opts = {}) {
10644
+ const configPath = resolveConfigPath(opts.configPath);
10645
+ let config = null;
10646
+ try {
10647
+ config = readConfig(opts.configPath);
10648
+ } catch {
10649
+ config = null;
10650
+ }
10651
+ const overlay = readHostedOverlay();
10652
+ const session = readSession();
10653
+ const now = opts.now ?? /* @__PURE__ */ new Date();
10654
+ const sessionView = toSessionView(session, now);
10655
+ const hostedView = toHostedOverlayView(overlay);
10656
+ const localView = toLocalWorkspaceView(configPath, config);
10657
+ return {
10658
+ version: 1,
10659
+ generatedAt: now.toISOString(),
10660
+ authenticated: sessionView.present && !sessionView.expired,
10661
+ local: localView,
10662
+ hosted: hostedView,
10663
+ session: sessionView,
10664
+ executionDefaults: hostedView.present ? hostedView.executionDefaults : {
10665
+ preferredMode: "local",
10666
+ allowServerlessFallback: false,
10667
+ allowBrowserBridge: false
10668
+ }
10669
+ };
10670
+ }
10671
+ function writeEffectiveProfileSnapshot(profile) {
10672
+ const filePath = resolveEffectiveProfilePath();
10673
+ fs13.mkdirSync(resolveProfilesDir(), { recursive: true });
10674
+ fs13.writeFileSync(filePath, `${JSON.stringify(profile, null, 2)}
10675
+ `, { mode: 384 });
10676
+ return path13.resolve(filePath);
10677
+ }
10678
+
10679
+ // src/commands/auth-login.ts
10680
+ init_home();
10681
+ var DEFAULT_HOSTED_BASE_URL = "https://www.growthub.ai";
10682
+ function trimSlashes2(value) {
10683
+ return value.replace(/\/+$/, "");
10684
+ }
10685
+ function resolveHostedBaseUrl(opts) {
10686
+ const explicit = opts.baseUrl?.trim();
10687
+ if (explicit) return trimSlashes2(explicit);
10688
+ const envBase = process.env.GROWTHUB_BASE_URL?.trim();
10689
+ if (envBase) return trimSlashes2(envBase);
10690
+ try {
10691
+ const config = readConfig(opts.configPath);
10692
+ const configuredBase = config?.auth?.growthubBaseUrl?.trim();
10693
+ if (configuredBase) return trimSlashes2(configuredBase);
10694
+ const portalBase = config?.auth?.growthubPortalBaseUrl?.trim();
10695
+ if (portalBase) return trimSlashes2(portalBase);
10696
+ } catch {
10697
+ }
10698
+ return DEFAULT_HOSTED_BASE_URL;
10699
+ }
10700
+ async function authLogin(opts) {
10701
+ const configPath = resolveConfigPath(opts.config);
10702
+ loadPaperclipEnvFile(configPath);
10703
+ const hostedBaseUrl = resolveHostedBaseUrl({ baseUrl: opts.baseUrl, configPath: opts.config });
10704
+ const machineLabel = opts.machineLabel?.trim() || os3.hostname();
10705
+ const workspaceLabel = opts.workspaceLabel?.trim();
10706
+ const linkedInstanceId = resolvePaperclipInstanceId();
10707
+ const existingSession = readSession();
10708
+ if (!opts.token && existingSession && !isSessionExpired(existingSession) && trimSlashes2(existingSession.hostedBaseUrl) === hostedBaseUrl) {
10709
+ if (opts.json) {
10710
+ console.log(
10711
+ JSON.stringify(
10712
+ {
10713
+ status: "ok",
10714
+ hostedBaseUrl,
10715
+ userId: existingSession.userId ?? null,
10716
+ email: existingSession.email ?? null,
10717
+ reusedSession: true
10718
+ },
10719
+ null,
10720
+ 2
10721
+ )
10722
+ );
10723
+ return;
10724
+ }
10725
+ p14.log.success(
10726
+ `Already connected${existingSession.email ? ` as ${existingSession.email}` : ""}.`
10727
+ );
10728
+ if (existingSession.hostedBaseUrl) {
10729
+ p14.log.message(pc18.dim(`Hosted: ${existingSession.hostedBaseUrl}`));
10730
+ }
10731
+ return;
10732
+ }
10733
+ if (opts.token) {
10734
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10735
+ writeSession({
10736
+ version: 1,
10737
+ hostedBaseUrl,
10738
+ accessToken: opts.token.trim(),
10739
+ issuedAt: now,
10740
+ machineLabel
10741
+ });
10742
+ const existingOverlay = readHostedOverlay();
10743
+ const overlay = existingOverlay ?? seedHostedOverlayFromSession({
10744
+ hostedBaseUrl,
10745
+ machineLabel,
10746
+ linkedInstanceId
10747
+ });
10748
+ writeHostedOverlay({
10749
+ ...overlay,
10750
+ hostedBaseUrl,
10751
+ linkedInstanceId: overlay.linkedInstanceId ?? linkedInstanceId
10752
+ });
10753
+ const effective = computeEffectiveProfile({ configPath });
10754
+ writeEffectiveProfileSnapshot(effective);
10755
+ if (opts.json) {
10756
+ console.log(JSON.stringify({ status: "ok", hostedBaseUrl, mode: "token" }, null, 2));
10757
+ } else {
10758
+ p14.log.success("Saved hosted session from --token.");
10759
+ p14.log.message(pc18.dim(`Session: ${describeSessionPath()}`));
10760
+ p14.log.message(pc18.dim(`Overlay: ${describeHostedOverlayPath()}`));
10761
+ }
10762
+ return;
10763
+ }
10764
+ p14.intro(pc18.bgCyan(pc18.black(" growthub auth login ")));
10765
+ p14.log.message(pc18.dim(`Hosted app: ${hostedBaseUrl}`));
10766
+ const flow = await startLoginFlow({
10767
+ hostedBaseUrl,
10768
+ machineLabel,
10769
+ workspaceLabel,
10770
+ timeoutMs: opts.timeoutMs
10771
+ });
10772
+ if (!opts.noBrowser) {
10773
+ try {
10774
+ p14.log.message("Opening browser to complete sign-in\u2026");
10775
+ await open(flow.loginUrl);
10776
+ } catch (err) {
10777
+ p14.log.warn(`Could not launch browser automatically: ${err instanceof Error ? err.message : String(err)}`);
10778
+ p14.log.message(pc18.dim("Paste this URL into a browser:"));
10779
+ p14.log.message(pc18.cyan(flow.loginUrl));
10780
+ }
10781
+ } else {
10782
+ p14.log.message(pc18.dim("Paste this URL into a browser:"));
10783
+ p14.log.message(pc18.cyan(flow.loginUrl));
10784
+ }
10785
+ const spinner5 = p14.spinner();
10786
+ spinner5.start("Waiting for hosted app to complete the exchange\u2026");
10787
+ try {
10788
+ const result = await flow.waitForCallback();
10789
+ spinner5.stop("Received hosted session token.");
10790
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
10791
+ writeSession({
10792
+ version: 1,
10793
+ hostedBaseUrl: result.hostedBaseUrl,
10794
+ accessToken: result.token,
10795
+ expiresAt: result.expiresAt,
10796
+ userId: result.userId,
10797
+ email: result.email,
10798
+ orgId: result.orgId,
10799
+ orgName: result.orgName,
10800
+ machineLabel: result.machineLabel,
10801
+ issuedAt: nowIso
10802
+ });
10803
+ const existingOverlay = readHostedOverlay();
10804
+ const overlay = existingOverlay ? {
10805
+ ...existingOverlay,
10806
+ hostedBaseUrl: result.hostedBaseUrl,
10807
+ userId: result.userId ?? existingOverlay.userId,
10808
+ email: result.email ?? existingOverlay.email,
10809
+ displayName: result.email ?? existingOverlay.displayName,
10810
+ orgId: result.orgId ?? existingOverlay.orgId,
10811
+ orgName: result.orgName ?? existingOverlay.orgName,
10812
+ linkedInstanceId: existingOverlay.linkedInstanceId ?? linkedInstanceId
10813
+ } : seedHostedOverlayFromSession({
10814
+ hostedBaseUrl: result.hostedBaseUrl,
10815
+ userId: result.userId,
10816
+ email: result.email,
10817
+ orgId: result.orgId,
10818
+ orgName: result.orgName,
10819
+ machineLabel: result.machineLabel,
10820
+ linkedInstanceId
10821
+ });
10822
+ writeHostedOverlay(overlay);
10823
+ const effective = computeEffectiveProfile({ configPath });
10824
+ writeEffectiveProfileSnapshot(effective);
10825
+ if (opts.json) {
10826
+ console.log(
10827
+ JSON.stringify(
10828
+ {
10829
+ status: "ok",
10830
+ hostedBaseUrl: result.hostedBaseUrl,
10831
+ userId: result.userId ?? null,
10832
+ email: result.email ?? null,
10833
+ orgId: result.orgId ?? null
10834
+ },
10835
+ null,
10836
+ 2
10837
+ )
10838
+ );
10839
+ } else {
10840
+ p14.log.success(`Signed in${result.email ? ` as ${result.email}` : ""}.`);
10841
+ p14.log.message(pc18.dim(`Session: ${describeSessionPath()}`));
10842
+ }
10843
+ p14.outro("Done");
10844
+ } catch (err) {
10845
+ spinner5.stop("Login failed.");
10846
+ p14.log.error(err instanceof Error ? err.message : String(err));
10847
+ process.exit(1);
10848
+ } finally {
10849
+ flow.close();
10850
+ }
10851
+ }
10852
+ async function authLogout(opts) {
10853
+ const sessionCleared = clearSession();
10854
+ const overlayCleared = opts.keepOverlay ? false : clearHostedOverlay();
10855
+ const effective = computeEffectiveProfile({ configPath: resolveConfigPath(opts.config) });
10856
+ writeEffectiveProfileSnapshot(effective);
10857
+ if (opts.json) {
10858
+ console.log(
10859
+ JSON.stringify(
10860
+ {
10861
+ status: "ok",
10862
+ sessionCleared,
10863
+ overlayCleared
10864
+ },
10865
+ null,
10866
+ 2
10867
+ )
10868
+ );
10869
+ return;
10870
+ }
10871
+ if (!sessionCleared && !overlayCleared) {
10872
+ console.log(pc18.dim("No hosted session or overlay present. Local workspace profile is untouched."));
10873
+ return;
10874
+ }
10875
+ if (sessionCleared) console.log(pc18.green("Cleared hosted session."));
10876
+ if (overlayCleared) console.log(pc18.green("Cleared hosted overlay."));
10877
+ console.log(pc18.dim("Local workspace profile is untouched."));
10878
+ }
10879
+ async function authWhoami(opts) {
10880
+ const session = readSession();
10881
+ const overlay = readHostedOverlay();
10882
+ const effective = computeEffectiveProfile({ configPath: resolveConfigPath(opts.config) });
10883
+ const payload = {
10884
+ authenticated: effective.authenticated,
10885
+ hostedBaseUrl: session?.hostedBaseUrl ?? overlay?.hostedBaseUrl ?? null,
10886
+ userId: overlay?.userId ?? session?.userId ?? null,
10887
+ email: overlay?.email ?? session?.email ?? null,
10888
+ displayName: overlay?.displayName ?? null,
10889
+ orgId: overlay?.orgId ?? session?.orgId ?? null,
10890
+ orgName: overlay?.orgName ?? session?.orgName ?? null,
10891
+ entitlements: overlay?.entitlements ?? [],
10892
+ linkedInstanceId: overlay?.linkedInstanceId ?? null,
10893
+ session: {
10894
+ present: Boolean(session),
10895
+ expired: effective.session.expired,
10896
+ expiresAt: effective.session.expiresAt
10897
+ }
10898
+ };
10899
+ if (opts.json) {
10900
+ console.log(JSON.stringify(payload, null, 2));
10901
+ return;
10902
+ }
10903
+ if (!payload.authenticated) {
10904
+ console.log(pc18.yellow("Not signed in."));
10905
+ if (effective.session.present && effective.session.expired) {
10906
+ console.log(pc18.dim("Hosted session exists but is expired. Run `growthub auth login` to refresh."));
10907
+ } else {
10908
+ console.log(pc18.dim("Run `growthub auth login` to connect this CLI to hosted Growthub."));
10909
+ }
10910
+ console.log(pc18.dim("Local workspace profile continues to work without authentication."));
10911
+ return;
10912
+ }
10913
+ console.log(pc18.bold(`Signed in${payload.email ? ` as ${payload.email}` : payload.userId ? ` as ${payload.userId}` : ""}.`));
10914
+ if (payload.hostedBaseUrl) console.log(pc18.dim(`Hosted: ${payload.hostedBaseUrl}`));
10915
+ if (payload.orgName || payload.orgId) {
10916
+ console.log(pc18.dim(`Org: ${payload.orgName ?? payload.orgId}`));
10917
+ }
10918
+ if (payload.linkedInstanceId) {
10919
+ console.log(pc18.dim(`Linked local instance: ${payload.linkedInstanceId}`));
10920
+ }
10921
+ if (payload.entitlements.length > 0) {
10922
+ console.log(pc18.dim(`Entitlements: ${payload.entitlements.join(", ")}`));
10923
+ }
10924
+ if (payload.session.expiresAt) {
10925
+ console.log(pc18.dim(`Session expires: ${payload.session.expiresAt}`));
10926
+ }
10927
+ }
10928
+
10929
+ // src/commands/profile.ts
10930
+ init_store();
10931
+ init_env();
10932
+ import pc19 from "picocolors";
10933
+
10934
+ // src/auth/hosted-client.ts
10935
+ var DEFAULT_PULL_PATH = "/api/cli/profile";
10936
+ var DEFAULT_PUSH_PATH = "/api/cli/profile";
10937
+ function toApiClient(session) {
10938
+ return new PaperclipApiClient({
10939
+ apiBase: session.hostedBaseUrl,
10940
+ apiKey: session.accessToken
10941
+ });
10942
+ }
10943
+ var HostedEndpointUnavailableError = class extends Error {
10944
+ status;
10945
+ constructor(status, message) {
10946
+ super(message);
10947
+ this.status = status;
10948
+ }
10949
+ };
10950
+ async function fetchHostedProfile(session) {
10951
+ const client = toApiClient(session);
10952
+ try {
10953
+ return await client.get(DEFAULT_PULL_PATH, { ignoreNotFound: true });
10954
+ } catch (err) {
10955
+ if (err instanceof ApiRequestError && (err.status === 404 || err.status === 501)) {
10956
+ throw new HostedEndpointUnavailableError(err.status, err.message);
10957
+ }
10958
+ throw err;
10959
+ }
10960
+ }
10961
+ async function pushHostedProfile(session, payload) {
10962
+ const client = toApiClient(session);
10963
+ try {
10964
+ return await client.post(DEFAULT_PUSH_PATH, payload, { ignoreNotFound: true });
10965
+ } catch (err) {
10966
+ if (err instanceof ApiRequestError && (err.status === 404 || err.status === 501)) {
10967
+ throw new HostedEndpointUnavailableError(err.status, err.message);
10968
+ }
10969
+ throw err;
10970
+ }
10971
+ }
10972
+
10973
+ // src/commands/profile.ts
10974
+ init_home();
10975
+ function printEffectiveProfileHuman(effective) {
10976
+ console.log(pc19.bold("Effective profile"));
10977
+ console.log(
10978
+ ` Authenticated: ${effective.authenticated ? pc19.green("yes") : pc19.yellow("no")}${effective.session.expired ? pc19.yellow(" (session expired)") : ""}`
10979
+ );
10980
+ console.log(pc19.bold("Local workspace (base layer)"));
10981
+ console.log(` Instance: ${effective.local.instanceId}`);
10982
+ console.log(` Config: ${pc19.dim(effective.local.configPath)}`);
10983
+ console.log(
10984
+ ` Surface: ${effective.local.surfaceProfile ?? pc19.dim("(unset)")} Host: ${effective.local.serverHost ?? pc19.dim("(unset)")} Port: ${effective.local.serverPort ?? pc19.dim("(unset)")}`
10985
+ );
10986
+ console.log(
10987
+ ` Local-linked hosted token: ${effective.local.hasConfiguredToken ? pc19.green("set") : pc19.dim("none")}`
10988
+ );
10989
+ if (effective.local.growthubBaseUrl) {
10990
+ console.log(` Growthub base: ${effective.local.growthubBaseUrl}`);
10991
+ }
10992
+ console.log(pc19.bold("Hosted overlay"));
10993
+ if (!effective.hosted.present) {
10994
+ console.log(pc19.dim(" No hosted overlay present. Run `growthub auth login` to attach one."));
10995
+ } else {
10996
+ if (effective.hosted.email || effective.hosted.userId) {
10997
+ console.log(` User: ${effective.hosted.email ?? effective.hosted.userId}`);
10998
+ }
10999
+ if (effective.hosted.orgName || effective.hosted.orgId) {
11000
+ console.log(` Org: ${effective.hosted.orgName ?? effective.hosted.orgId}`);
11001
+ }
11002
+ if (effective.hosted.hostedBaseUrl) console.log(` Hosted: ${effective.hosted.hostedBaseUrl}`);
11003
+ if (effective.hosted.linkedInstanceId) {
11004
+ console.log(` Linked instance: ${effective.hosted.linkedInstanceId}`);
11005
+ }
11006
+ if (effective.hosted.entitlements.length > 0) {
11007
+ console.log(` Entitlements: ${effective.hosted.entitlements.join(", ")}`);
11008
+ }
11009
+ if (effective.hosted.gatedKitSlugs.length > 0) {
11010
+ console.log(` Gated kits: ${effective.hosted.gatedKitSlugs.join(", ")}`);
11011
+ }
11012
+ if (effective.hosted.lastPulledAt) console.log(pc19.dim(` Last pulled: ${effective.hosted.lastPulledAt}`));
11013
+ if (effective.hosted.lastPushedAt) console.log(pc19.dim(` Last pushed: ${effective.hosted.lastPushedAt}`));
11014
+ }
11015
+ console.log(pc19.bold("Execution defaults"));
11016
+ console.log(
11017
+ ` preferredMode=${effective.executionDefaults.preferredMode} serverlessFallback=${effective.executionDefaults.allowServerlessFallback} browserBridge=${effective.executionDefaults.allowBrowserBridge}`
11018
+ );
11019
+ }
11020
+ async function runProfileStatus(opts) {
11021
+ const configPath = resolveConfigPath(opts.config);
11022
+ loadPaperclipEnvFile(configPath);
11023
+ const effective = computeEffectiveProfile({ configPath });
11024
+ writeEffectiveProfileSnapshot(effective);
11025
+ if (opts.json) {
11026
+ console.log(JSON.stringify(effective, null, 2));
11027
+ return;
11028
+ }
11029
+ printEffectiveProfileHuman(effective);
11030
+ }
11031
+ function normalizeExecutionPrefs(value, fallback) {
11032
+ if (!value) return fallback;
11033
+ const preferredMode = value.preferredMode === "local" || value.preferredMode === "serverless" || value.preferredMode === "browser" || value.preferredMode === "auto" ? value.preferredMode : fallback.preferredMode;
11034
+ return {
11035
+ preferredMode,
11036
+ allowServerlessFallback: typeof value.allowServerlessFallback === "boolean" ? value.allowServerlessFallback : fallback.allowServerlessFallback,
11037
+ allowBrowserBridge: typeof value.allowBrowserBridge === "boolean" ? value.allowBrowserBridge : fallback.allowBrowserBridge
11038
+ };
11039
+ }
11040
+ async function runProfilePull(opts) {
11041
+ const configPath = resolveConfigPath(opts.config);
11042
+ loadPaperclipEnvFile(configPath);
11043
+ const session = readSession();
11044
+ if (!session) {
11045
+ console.error(pc19.red("No hosted session. Run `growthub auth login` first."));
11046
+ process.exit(1);
11047
+ }
11048
+ if (isSessionExpired(session)) {
11049
+ console.error(pc19.red("Hosted session is expired. Run `growthub auth login` to refresh."));
11050
+ process.exit(1);
11051
+ }
11052
+ const existingOverlay = readHostedOverlay() ?? seedHostedOverlayFromSession({
11053
+ hostedBaseUrl: session.hostedBaseUrl,
11054
+ userId: session.userId,
11055
+ email: session.email,
11056
+ orgId: session.orgId,
11057
+ orgName: session.orgName,
11058
+ machineLabel: session.machineLabel,
11059
+ linkedInstanceId: resolvePaperclipInstanceId()
11060
+ });
11061
+ let remote = null;
11062
+ let usedFallback = false;
11063
+ try {
11064
+ remote = await fetchHostedProfile(session);
11065
+ } catch (err) {
11066
+ if (err instanceof HostedEndpointUnavailableError) {
11067
+ usedFallback = true;
11068
+ if (!opts.json) {
11069
+ console.log(
11070
+ pc19.yellow(
11071
+ "Hosted profile endpoint not yet available \u2014 keeping current overlay. (This is expected while gh-app is still shipping its CLI API.)"
11072
+ )
11073
+ );
11074
+ }
11075
+ } else {
11076
+ throw err;
11077
+ }
11078
+ }
11079
+ const merged = {
11080
+ ...existingOverlay,
11081
+ hostedBaseUrl: session.hostedBaseUrl,
11082
+ userId: remote?.userId ?? existingOverlay.userId,
11083
+ email: remote?.email ?? existingOverlay.email,
11084
+ displayName: remote?.displayName ?? existingOverlay.displayName ?? existingOverlay.email,
11085
+ orgId: remote?.orgId ?? existingOverlay.orgId,
11086
+ orgName: remote?.orgName ?? existingOverlay.orgName,
11087
+ entitlements: remote?.entitlements ?? existingOverlay.entitlements,
11088
+ gatedKitSlugs: remote?.gatedKitSlugs ?? existingOverlay.gatedKitSlugs,
11089
+ executionDefaults: normalizeExecutionPrefs(remote?.executionDefaults, existingOverlay.executionDefaults),
11090
+ lastPulledAt: remote ? (/* @__PURE__ */ new Date()).toISOString() : existingOverlay.lastPulledAt,
11091
+ extra: remote?.extra ?? existingOverlay.extra
11092
+ };
11093
+ writeHostedOverlay(merged);
11094
+ const effective = computeEffectiveProfile({ configPath });
11095
+ writeEffectiveProfileSnapshot(effective);
11096
+ if (opts.json) {
11097
+ console.log(JSON.stringify({ status: "ok", usedFallback, overlay: merged }, null, 2));
11098
+ return;
11099
+ }
11100
+ if (!usedFallback) {
11101
+ console.log(pc19.green("Hosted profile pulled and overlay updated."));
11102
+ }
11103
+ console.log(pc19.dim(`Entitlements: ${merged.entitlements.length === 0 ? "(none)" : merged.entitlements.join(", ")}`));
11104
+ console.log(pc19.dim(`Gated kits: ${merged.gatedKitSlugs.length === 0 ? "(none)" : merged.gatedKitSlugs.join(", ")}`));
11105
+ }
11106
+ async function runProfilePush(opts) {
11107
+ const configPath = resolveConfigPath(opts.config);
11108
+ loadPaperclipEnvFile(configPath);
11109
+ const session = readSession();
11110
+ if (!session) {
11111
+ console.error(pc19.red("No hosted session. Run `growthub auth login` first."));
11112
+ process.exit(1);
11113
+ }
11114
+ if (isSessionExpired(session)) {
11115
+ console.error(pc19.red("Hosted session is expired. Run `growthub auth login` to refresh."));
11116
+ process.exit(1);
11117
+ }
11118
+ const effective = computeEffectiveProfile({ configPath });
11119
+ let acknowledged = false;
11120
+ let usedFallback = false;
11121
+ try {
11122
+ await pushHostedProfile(session, {
11123
+ linkedInstanceId: effective.local.instanceId,
11124
+ surfaceProfile: effective.local.surfaceProfile,
11125
+ machineLabel: effective.local.machineLabel,
11126
+ workspaceLabel: effective.local.workspaceLabel
11127
+ });
11128
+ acknowledged = true;
11129
+ } catch (err) {
11130
+ if (err instanceof HostedEndpointUnavailableError) {
11131
+ usedFallback = true;
11132
+ } else {
11133
+ throw err;
11134
+ }
11135
+ }
11136
+ const existingOverlay = readHostedOverlay() ?? seedHostedOverlayFromSession({
11137
+ hostedBaseUrl: session.hostedBaseUrl,
11138
+ userId: session.userId,
11139
+ email: session.email,
11140
+ orgId: session.orgId,
11141
+ orgName: session.orgName,
11142
+ machineLabel: session.machineLabel,
11143
+ linkedInstanceId: effective.local.instanceId
11144
+ });
11145
+ const updatedOverlay = {
11146
+ ...existingOverlay,
11147
+ linkedInstanceId: existingOverlay.linkedInstanceId ?? effective.local.instanceId,
11148
+ lastPushedAt: acknowledged ? (/* @__PURE__ */ new Date()).toISOString() : existingOverlay.lastPushedAt
11149
+ };
11150
+ writeHostedOverlay(updatedOverlay);
11151
+ writeEffectiveProfileSnapshot(computeEffectiveProfile({ configPath }));
11152
+ if (opts.json) {
11153
+ console.log(JSON.stringify({ status: "ok", acknowledged, usedFallback, overlay: updatedOverlay }, null, 2));
11154
+ return;
11155
+ }
11156
+ if (usedFallback) {
11157
+ console.log(
11158
+ pc19.yellow(
11159
+ "Hosted push endpoint not yet available \u2014 linkage recorded locally only. (This is expected while gh-app is still shipping its CLI API.)"
11160
+ )
11161
+ );
11162
+ return;
11163
+ }
11164
+ console.log(pc19.green("Hosted profile push acknowledged."));
11165
+ console.log(pc19.dim(`Linked instance: ${effective.local.instanceId}`));
11166
+ }
11167
+ function registerProfileCommands(program2) {
11168
+ const profile = program2.command("profile").description("Inspect and sync the effective Growthub profile (local workspace + hosted overlay)");
11169
+ profile.command("status").description("Show the merged local + hosted profile the CLI will use at runtime").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", "Growthub data directory root (isolates local instance state)").option("--json", "Output raw JSON").action(async (opts) => {
11170
+ await runProfileStatus(opts);
11171
+ });
11172
+ profile.command("pull").description("Pull hosted Growthub profile metadata into the local overlay").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", "Growthub data directory root (isolates local instance state)").option("--json", "Output raw JSON").action(async (opts) => {
11173
+ await runProfilePull(opts);
11174
+ });
11175
+ profile.command("push").description("Push safe local profile metadata (workspace linkage, labels) upward").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", "Growthub data directory root (isolates local instance state)").option("--json", "Output raw JSON").action(async (opts) => {
11176
+ await runProfilePush(opts);
11177
+ });
11178
+ }
11179
+
10167
11180
  // src/commands/db-backup.ts
10168
11181
  init_src2();
10169
11182
  init_home();
10170
11183
  init_store();
10171
11184
  init_banner();
10172
- import path10 from "node:path";
10173
- import * as p14 from "@clack/prompts";
10174
- import pc18 from "picocolors";
11185
+ import path14 from "node:path";
11186
+ import * as p15 from "@clack/prompts";
11187
+ import pc20 from "picocolors";
10175
11188
  function resolveConnectionString(configPath) {
10176
11189
  const envUrl = process.env.DATABASE_URL?.trim();
10177
11190
  if (envUrl) return { value: envUrl, source: "DATABASE_URL" };
@@ -10193,11 +11206,11 @@ function normalizeRetentionDays(value, fallback) {
10193
11206
  return candidate;
10194
11207
  }
10195
11208
  function resolveBackupDir(raw) {
10196
- return path10.resolve(expandHomePrefix(raw.trim()));
11209
+ return path14.resolve(expandHomePrefix(raw.trim()));
10197
11210
  }
10198
11211
  async function dbBackupCommand(opts) {
10199
11212
  printPaperclipCliBanner();
10200
- p14.intro(pc18.bgCyan(pc18.black(" paperclip db:backup ")));
11213
+ p15.intro(pc20.bgCyan(pc20.black(" paperclip db:backup ")));
10201
11214
  const configPath = resolveConfigPath(opts.config);
10202
11215
  const config = readConfig(opts.config);
10203
11216
  const connection = resolveConnectionString(opts.config);
@@ -10209,12 +11222,12 @@ async function dbBackupCommand(opts) {
10209
11222
  config?.database.backup.retentionDays ?? 30
10210
11223
  );
10211
11224
  const filenamePrefix = opts.filenamePrefix?.trim() || "paperclip";
10212
- p14.log.message(pc18.dim(`Config: ${configPath}`));
10213
- p14.log.message(pc18.dim(`Connection source: ${connection.source}`));
10214
- p14.log.message(pc18.dim(`Backup dir: ${backupDir}`));
10215
- p14.log.message(pc18.dim(`Retention: ${retentionDays} day(s)`));
10216
- const spinner4 = p14.spinner();
10217
- spinner4.start("Creating database backup...");
11225
+ p15.log.message(pc20.dim(`Config: ${configPath}`));
11226
+ p15.log.message(pc20.dim(`Connection source: ${connection.source}`));
11227
+ p15.log.message(pc20.dim(`Backup dir: ${backupDir}`));
11228
+ p15.log.message(pc20.dim(`Retention: ${retentionDays} day(s)`));
11229
+ const spinner5 = p15.spinner();
11230
+ spinner5.start("Creating database backup...");
10218
11231
  try {
10219
11232
  const result = await runDatabaseBackup({
10220
11233
  connectionString: connection.value,
@@ -10222,7 +11235,7 @@ async function dbBackupCommand(opts) {
10222
11235
  retentionDays,
10223
11236
  filenamePrefix
10224
11237
  });
10225
- spinner4.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`);
11238
+ spinner5.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`);
10226
11239
  if (opts.json) {
10227
11240
  console.log(
10228
11241
  JSON.stringify(
@@ -10239,15 +11252,15 @@ async function dbBackupCommand(opts) {
10239
11252
  )
10240
11253
  );
10241
11254
  }
10242
- p14.outro(pc18.green("Backup completed."));
11255
+ p15.outro(pc20.green("Backup completed."));
10243
11256
  } catch (err) {
10244
- spinner4.stop(pc18.red("Backup failed."));
11257
+ spinner5.stop(pc20.red("Backup failed."));
10245
11258
  throw err;
10246
11259
  }
10247
11260
  }
10248
11261
 
10249
11262
  // src/commands/client/context.ts
10250
- import pc19 from "picocolors";
11263
+ import pc21 from "picocolors";
10251
11264
  function registerContextCommands(program2) {
10252
11265
  const context = program2.command("context").description("Manage CLI client context profiles");
10253
11266
  context.command("show").description("Show current context and active profile").option("-d, --data-dir <path>", "Growthub data directory root (isolates local instance state)").option("--context <path>", "Path to CLI context file").option("--profile <name>", "Profile to inspect").option("--json", "Output raw JSON").action((opts) => {
@@ -10276,7 +11289,7 @@ function registerContextCommands(program2) {
10276
11289
  });
10277
11290
  context.command("use").description("Set active context profile").argument("<profile>", "Profile name").option("-d, --data-dir <path>", "Growthub data directory root (isolates local instance state)").option("--context <path>", "Path to CLI context file").action((profile, opts) => {
10278
11291
  setCurrentProfile(profile, opts.context);
10279
- console.log(pc19.green(`Active profile set to '${profile}'.`));
11292
+ console.log(pc21.green(`Active profile set to '${profile}'.`));
10280
11293
  });
10281
11294
  context.command("set").description("Set values on a profile").option("-d, --data-dir <path>", "Growthub data directory root (isolates local instance state)").option("--context <path>", "Path to CLI context file").option("--profile <name>", "Profile name (default: current profile)").option("--api-base <url>", "Default API base URL").option("--company-id <id>", "Default company ID").option("--api-key-env-var-name <name>", "Env var containing API key (recommended)").option("--use", "Set this profile as active").option("--json", "Output raw JSON").action((opts) => {
10282
11295
  const existing = readContext(opts.context);
@@ -10302,9 +11315,9 @@ function registerContextCommands(program2) {
10302
11315
  profile: resolved.profile
10303
11316
  };
10304
11317
  if (!opts.json) {
10305
- console.log(pc19.green(`Updated profile '${targetProfile}'.`));
11318
+ console.log(pc21.green(`Updated profile '${targetProfile}'.`));
10306
11319
  if (opts.use) {
10307
- console.log(pc19.green(`Set '${targetProfile}' as active profile.`));
11320
+ console.log(pc21.green(`Set '${targetProfile}' as active profile.`));
10308
11321
  }
10309
11322
  }
10310
11323
  printOutput(payload, { json: opts.json });
@@ -10313,7 +11326,7 @@ function registerContextCommands(program2) {
10313
11326
 
10314
11327
  // src/commands/client/company.ts
10315
11328
  import { mkdir, readFile as readFile3, stat, writeFile as writeFile2 } from "node:fs/promises";
10316
- import path11 from "node:path";
11329
+ import path15 from "node:path";
10317
11330
  function isUuidLike2(value) {
10318
11331
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
10319
11332
  }
@@ -10347,32 +11360,32 @@ function isGithubUrl(input) {
10347
11360
  return /^https?:\/\/github\.com\//i.test(input.trim());
10348
11361
  }
10349
11362
  async function resolveInlineSourceFromPath(inputPath) {
10350
- const resolved = path11.resolve(inputPath);
11363
+ const resolved = path15.resolve(inputPath);
10351
11364
  const resolvedStat = await stat(resolved);
10352
- const manifestPath = resolvedStat.isDirectory() ? path11.join(resolved, "paperclip.manifest.json") : resolved;
10353
- const manifestBaseDir = path11.dirname(manifestPath);
11365
+ const manifestPath = resolvedStat.isDirectory() ? path15.join(resolved, "paperclip.manifest.json") : resolved;
11366
+ const manifestBaseDir = path15.dirname(manifestPath);
10354
11367
  const manifestRaw = await readFile3(manifestPath, "utf8");
10355
11368
  const manifest = JSON.parse(manifestRaw);
10356
11369
  const files = {};
10357
11370
  if (manifest.company?.path) {
10358
11371
  const companyPath = manifest.company.path.replace(/\\/g, "/");
10359
- files[companyPath] = await readFile3(path11.join(manifestBaseDir, companyPath), "utf8");
11372
+ files[companyPath] = await readFile3(path15.join(manifestBaseDir, companyPath), "utf8");
10360
11373
  }
10361
11374
  for (const agent of manifest.agents ?? []) {
10362
11375
  const agentPath = agent.path.replace(/\\/g, "/");
10363
- files[agentPath] = await readFile3(path11.join(manifestBaseDir, agentPath), "utf8");
11376
+ files[agentPath] = await readFile3(path15.join(manifestBaseDir, agentPath), "utf8");
10364
11377
  }
10365
11378
  return { manifest, files };
10366
11379
  }
10367
11380
  async function writeExportToFolder(outDir, exported) {
10368
- const root = path11.resolve(outDir);
11381
+ const root = path15.resolve(outDir);
10369
11382
  await mkdir(root, { recursive: true });
10370
- const manifestPath = path11.join(root, "paperclip.manifest.json");
11383
+ const manifestPath = path15.join(root, "paperclip.manifest.json");
10371
11384
  await writeFile2(manifestPath, JSON.stringify(exported.manifest, null, 2), "utf8");
10372
11385
  for (const [relativePath, content] of Object.entries(exported.files)) {
10373
11386
  const normalized = relativePath.replace(/\\/g, "/");
10374
- const filePath = path11.join(root, normalized);
10375
- await mkdir(path11.dirname(filePath), { recursive: true });
11387
+ const filePath = path15.join(root, normalized);
11388
+ await mkdir(path15.dirname(filePath), { recursive: true });
10376
11389
  await writeFile2(filePath, content, "utf8");
10377
11390
  }
10378
11391
  }
@@ -10495,7 +11508,7 @@ function registerCompanyCommands(program2) {
10495
11508
  printOutput(
10496
11509
  {
10497
11510
  ok: true,
10498
- out: path11.resolve(opts.out),
11511
+ out: path15.resolve(opts.out),
10499
11512
  filesWritten: Object.keys(exported.files).length + 1,
10500
11513
  warningCount: exported.warnings.length
10501
11514
  },
@@ -10657,8 +11670,8 @@ function registerIssueCommands(program2) {
10657
11670
  if (opts.assigneeAgentId) params.set("assigneeAgentId", opts.assigneeAgentId);
10658
11671
  if (opts.projectId) params.set("projectId", opts.projectId);
10659
11672
  const query = params.toString();
10660
- const path24 = `/api/companies/${ctx.companyId}/issues${query ? `?${query}` : ""}`;
10661
- const rows = await ctx.api.get(path24) ?? [];
11673
+ const path28 = `/api/companies/${ctx.companyId}/issues${query ? `?${query}` : ""}`;
11674
+ const rows = await ctx.api.get(path28) ?? [];
10662
11675
  const filtered = filterIssueRows(rows, opts.match);
10663
11676
  if (ctx.json) {
10664
11677
  printOutput(filtered, { json: true });
@@ -10820,8 +11833,8 @@ function filterIssueRows(rows, match) {
10820
11833
  }
10821
11834
 
10822
11835
  // ../packages/adapter-utils/src/server-utils.ts
10823
- import { constants as fsConstants, promises as fs11 } from "node:fs";
10824
- import path12 from "node:path";
11836
+ import { constants as fsConstants, promises as fs14 } from "node:fs";
11837
+ import path16 from "node:path";
10825
11838
  var MAX_CAPTURE_BYTES = 4 * 1024 * 1024;
10826
11839
  var MAX_EXCERPT_BYTES = 32 * 1024;
10827
11840
  var PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES = [
@@ -10836,14 +11849,14 @@ function isMaintainerOnlySkillTarget(candidate) {
10836
11849
  }
10837
11850
  async function resolvePaperclipSkillsDir(moduleDir, additionalCandidates = []) {
10838
11851
  const candidates = [
10839
- ...PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES.map((relativePath) => path12.resolve(moduleDir, relativePath)),
10840
- ...additionalCandidates.map((candidate) => path12.resolve(candidate))
11852
+ ...PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES.map((relativePath) => path16.resolve(moduleDir, relativePath)),
11853
+ ...additionalCandidates.map((candidate) => path16.resolve(candidate))
10841
11854
  ];
10842
11855
  const seenRoots = /* @__PURE__ */ new Set();
10843
11856
  for (const root of candidates) {
10844
11857
  if (seenRoots.has(root)) continue;
10845
11858
  seenRoots.add(root);
10846
- const isDirectory = await fs11.stat(root).then((stats) => stats.isDirectory()).catch(() => false);
11859
+ const isDirectory = await fs14.stat(root).then((stats) => stats.isDirectory()).catch(() => false);
10847
11860
  if (isDirectory) return root;
10848
11861
  }
10849
11862
  return null;
@@ -10851,20 +11864,20 @@ async function resolvePaperclipSkillsDir(moduleDir, additionalCandidates = []) {
10851
11864
  async function removeMaintainerOnlySkillSymlinks(skillsHome, allowedSkillNames) {
10852
11865
  const allowed = new Set(Array.from(allowedSkillNames));
10853
11866
  try {
10854
- const entries = await fs11.readdir(skillsHome, { withFileTypes: true });
11867
+ const entries = await fs14.readdir(skillsHome, { withFileTypes: true });
10855
11868
  const removed = [];
10856
11869
  for (const entry of entries) {
10857
11870
  if (allowed.has(entry.name)) continue;
10858
- const target = path12.join(skillsHome, entry.name);
10859
- const existing = await fs11.lstat(target).catch(() => null);
11871
+ const target = path16.join(skillsHome, entry.name);
11872
+ const existing = await fs14.lstat(target).catch(() => null);
10860
11873
  if (!existing?.isSymbolicLink()) continue;
10861
- const linkedPath = await fs11.readlink(target).catch(() => null);
11874
+ const linkedPath = await fs14.readlink(target).catch(() => null);
10862
11875
  if (!linkedPath) continue;
10863
- const resolvedLinkedPath = path12.isAbsolute(linkedPath) ? linkedPath : path12.resolve(path12.dirname(target), linkedPath);
11876
+ const resolvedLinkedPath = path16.isAbsolute(linkedPath) ? linkedPath : path16.resolve(path16.dirname(target), linkedPath);
10864
11877
  if (!isMaintainerOnlySkillTarget(linkedPath) && !isMaintainerOnlySkillTarget(resolvedLinkedPath)) {
10865
11878
  continue;
10866
11879
  }
10867
- await fs11.unlink(target);
11880
+ await fs14.unlink(target);
10868
11881
  removed.push(entry.name);
10869
11882
  }
10870
11883
  return removed;
@@ -10874,20 +11887,20 @@ async function removeMaintainerOnlySkillSymlinks(skillsHome, allowedSkillNames)
10874
11887
  }
10875
11888
 
10876
11889
  // src/commands/client/agent.ts
10877
- import fs12 from "node:fs/promises";
10878
- import os2 from "node:os";
10879
- import path13 from "node:path";
11890
+ import fs15 from "node:fs/promises";
11891
+ import os4 from "node:os";
11892
+ import path17 from "node:path";
10880
11893
  import { fileURLToPath as fileURLToPath3 } from "node:url";
10881
- var __moduleDir = path13.dirname(fileURLToPath3(import.meta.url));
11894
+ var __moduleDir = path17.dirname(fileURLToPath3(import.meta.url));
10882
11895
  function codexSkillsHome() {
10883
11896
  const fromEnv = process.env.CODEX_HOME?.trim();
10884
- const base = fromEnv && fromEnv.length > 0 ? fromEnv : path13.join(os2.homedir(), ".codex");
10885
- return path13.join(base, "skills");
11897
+ const base = fromEnv && fromEnv.length > 0 ? fromEnv : path17.join(os4.homedir(), ".codex");
11898
+ return path17.join(base, "skills");
10886
11899
  }
10887
11900
  function claudeSkillsHome() {
10888
11901
  const fromEnv = process.env.CLAUDE_HOME?.trim();
10889
- const base = fromEnv && fromEnv.length > 0 ? fromEnv : path13.join(os2.homedir(), ".claude");
10890
- return path13.join(base, "skills");
11902
+ const base = fromEnv && fromEnv.length > 0 ? fromEnv : path17.join(os4.homedir(), ".claude");
11903
+ return path17.join(base, "skills");
10891
11904
  }
10892
11905
  async function installSkillsForTarget(sourceSkillsDir, targetSkillsDir, tool) {
10893
11906
  const summary = {
@@ -10898,26 +11911,26 @@ async function installSkillsForTarget(sourceSkillsDir, targetSkillsDir, tool) {
10898
11911
  skipped: [],
10899
11912
  failed: []
10900
11913
  };
10901
- await fs12.mkdir(targetSkillsDir, { recursive: true });
10902
- const entries = await fs12.readdir(sourceSkillsDir, { withFileTypes: true });
11914
+ await fs15.mkdir(targetSkillsDir, { recursive: true });
11915
+ const entries = await fs15.readdir(sourceSkillsDir, { withFileTypes: true });
10903
11916
  summary.removed = await removeMaintainerOnlySkillSymlinks(
10904
11917
  targetSkillsDir,
10905
11918
  entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name)
10906
11919
  );
10907
11920
  for (const entry of entries) {
10908
11921
  if (!entry.isDirectory()) continue;
10909
- const source = path13.join(sourceSkillsDir, entry.name);
10910
- const target = path13.join(targetSkillsDir, entry.name);
10911
- const existing = await fs12.lstat(target).catch(() => null);
11922
+ const source = path17.join(sourceSkillsDir, entry.name);
11923
+ const target = path17.join(targetSkillsDir, entry.name);
11924
+ const existing = await fs15.lstat(target).catch(() => null);
10912
11925
  if (existing) {
10913
11926
  if (existing.isSymbolicLink()) {
10914
11927
  let linkedPath = null;
10915
11928
  try {
10916
- linkedPath = await fs12.readlink(target);
11929
+ linkedPath = await fs15.readlink(target);
10917
11930
  } catch (err) {
10918
- await fs12.unlink(target);
11931
+ await fs15.unlink(target);
10919
11932
  try {
10920
- await fs12.symlink(source, target);
11933
+ await fs15.symlink(source, target);
10921
11934
  summary.linked.push(entry.name);
10922
11935
  continue;
10923
11936
  } catch (linkErr) {
@@ -10928,10 +11941,10 @@ async function installSkillsForTarget(sourceSkillsDir, targetSkillsDir, tool) {
10928
11941
  continue;
10929
11942
  }
10930
11943
  }
10931
- const resolvedLinkedPath = path13.isAbsolute(linkedPath) ? linkedPath : path13.resolve(path13.dirname(target), linkedPath);
10932
- const linkedTargetExists = await fs12.stat(resolvedLinkedPath).then(() => true).catch(() => false);
11944
+ const resolvedLinkedPath = path17.isAbsolute(linkedPath) ? linkedPath : path17.resolve(path17.dirname(target), linkedPath);
11945
+ const linkedTargetExists = await fs15.stat(resolvedLinkedPath).then(() => true).catch(() => false);
10933
11946
  if (!linkedTargetExists) {
10934
- await fs12.unlink(target);
11947
+ await fs15.unlink(target);
10935
11948
  } else {
10936
11949
  summary.skipped.push(entry.name);
10937
11950
  continue;
@@ -10942,7 +11955,7 @@ async function installSkillsForTarget(sourceSkillsDir, targetSkillsDir, tool) {
10942
11955
  }
10943
11956
  }
10944
11957
  try {
10945
- await fs12.symlink(source, target);
11958
+ await fs15.symlink(source, target);
10946
11959
  summary.linked.push(entry.name);
10947
11960
  } catch (err) {
10948
11961
  summary.failed.push({
@@ -11031,7 +12044,7 @@ function registerAgentCommands(program2) {
11031
12044
  }
11032
12045
  const installSummaries = [];
11033
12046
  if (opts.installSkills !== false) {
11034
- const skillsDir = await resolvePaperclipSkillsDir(__moduleDir, [path13.resolve(process.cwd(), "skills")]);
12047
+ const skillsDir = await resolvePaperclipSkillsDir(__moduleDir, [path17.resolve(process.cwd(), "skills")]);
11035
12048
  if (!skillsDir) {
11036
12049
  throw new Error(
11037
12050
  "Could not locate local Paperclip skills directory. Expected ./skills in the repo checkout."
@@ -11264,8 +12277,8 @@ function registerActivityCommands(program2) {
11264
12277
  if (opts.entityType) params.set("entityType", opts.entityType);
11265
12278
  if (opts.entityId) params.set("entityId", opts.entityId);
11266
12279
  const query = params.toString();
11267
- const path24 = `/api/companies/${ctx.companyId}/activity${query ? `?${query}` : ""}`;
11268
- const rows = await ctx.api.get(path24) ?? [];
12280
+ const path28 = `/api/companies/${ctx.companyId}/activity${query ? `?${query}` : ""}`;
12281
+ const rows = await ctx.api.get(path28) ?? [];
11269
12282
  if (ctx.json) {
11270
12283
  printOutput(rows, { json: true });
11271
12284
  return;
@@ -11314,11 +12327,11 @@ function registerDashboardCommands(program2) {
11314
12327
 
11315
12328
  // src/config/data-dir.ts
11316
12329
  init_home();
11317
- import path14 from "node:path";
12330
+ import path18 from "node:path";
11318
12331
  function applyDataDirOverride(options, support = {}) {
11319
12332
  const rawDataDir = options.dataDir?.trim();
11320
12333
  if (!rawDataDir) return null;
11321
- const resolvedDataDir = path14.resolve(expandHomePrefix(rawDataDir));
12334
+ const resolvedDataDir = path18.resolve(expandHomePrefix(rawDataDir));
11322
12335
  process.env.PAPERCLIP_HOME = resolvedDataDir;
11323
12336
  if (support.hasConfigOption) {
11324
12337
  const hasConfigOverride = Boolean(options.config?.trim()) || Boolean(process.env.PAPERCLIP_CONFIG?.trim());
@@ -11345,35 +12358,35 @@ init_store();
11345
12358
  // src/commands/gtm.ts
11346
12359
  init_src();
11347
12360
  init_home();
11348
- import fs13 from "node:fs";
11349
- import path15 from "node:path";
12361
+ import fs16 from "node:fs";
12362
+ import path19 from "node:path";
11350
12363
  import { spawn } from "node:child_process";
11351
- import pc20 from "picocolors";
12364
+ import pc22 from "picocolors";
11352
12365
  function resolveGtmStatePath() {
11353
- return path15.resolve(resolvePaperclipHomeDir(), "gtm", "state.json");
12366
+ return path19.resolve(resolvePaperclipHomeDir(), "gtm", "state.json");
11354
12367
  }
11355
12368
  function readState() {
11356
12369
  const filePath = resolveGtmStatePath();
11357
- if (!fs13.existsSync(filePath)) return createDefaultGtmState();
11358
- return coerceGtmState(JSON.parse(fs13.readFileSync(filePath, "utf-8")));
12370
+ if (!fs16.existsSync(filePath)) return createDefaultGtmState();
12371
+ return coerceGtmState(JSON.parse(fs16.readFileSync(filePath, "utf-8")));
11359
12372
  }
11360
12373
  function writeState(state) {
11361
12374
  const filePath = resolveGtmStatePath();
11362
- fs13.mkdirSync(path15.dirname(filePath), { recursive: true });
11363
- fs13.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
12375
+ fs16.mkdirSync(path19.dirname(filePath), { recursive: true });
12376
+ fs16.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
11364
12377
  }
11365
12378
  function launchWorkflow(state) {
11366
12379
  const runnerPath = state.workflow.runnerPath?.trim();
11367
12380
  if (!runnerPath) {
11368
12381
  throw new Error("No local SDR runner configured.");
11369
12382
  }
11370
- if (!fs13.existsSync(runnerPath)) {
12383
+ if (!fs16.existsSync(runnerPath)) {
11371
12384
  throw new Error(`Runner not found at ${runnerPath}`);
11372
12385
  }
11373
12386
  const args = runnerPath.endsWith(".mjs") || runnerPath.endsWith(".js") ? [runnerPath] : [];
11374
12387
  const command = args.length > 0 ? process.execPath : runnerPath;
11375
12388
  const child = spawn(command, args, {
11376
- cwd: path15.dirname(runnerPath),
12389
+ cwd: path19.dirname(runnerPath),
11377
12390
  detached: true,
11378
12391
  stdio: "ignore"
11379
12392
  });
@@ -11398,7 +12411,7 @@ function printJsonOrMessage(payload, json, message) {
11398
12411
  console.log(JSON.stringify(payload, null, 2));
11399
12412
  return;
11400
12413
  }
11401
- if (message) console.log(pc20.green(message));
12414
+ if (message) console.log(pc22.green(message));
11402
12415
  console.log(payload);
11403
12416
  }
11404
12417
  function registerGtmCommands(program2) {
@@ -11411,7 +12424,7 @@ function registerGtmCommands(program2) {
11411
12424
  if (opts.internalSocialsPath) state.workflow.referenceInterfaces.internalSocialsPath = opts.internalSocialsPath.trim();
11412
12425
  if (opts.localSdrPath) {
11413
12426
  state.workflow.referenceInterfaces.localSdrPath = opts.localSdrPath.trim();
11414
- state.workflow.runnerPath = path15.resolve(opts.localSdrPath.trim(), "sdr-bot.mjs");
12427
+ state.workflow.runnerPath = path19.resolve(opts.localSdrPath.trim(), "sdr-bot.mjs");
11415
12428
  }
11416
12429
  writeState(state);
11417
12430
  const view = toGtmViewModel(state);
@@ -11463,18 +12476,18 @@ import {
11463
12476
  symlinkSync,
11464
12477
  writeFileSync
11465
12478
  } from "node:fs";
11466
- import os3 from "node:os";
11467
- import path17 from "node:path";
12479
+ import os5 from "node:os";
12480
+ import path21 from "node:path";
11468
12481
  import { execFileSync } from "node:child_process";
11469
- import { createServer } from "node:net";
11470
- import * as p15 from "@clack/prompts";
11471
- import pc21 from "picocolors";
12482
+ import { createServer as createServer2 } from "node:net";
12483
+ import * as p16 from "@clack/prompts";
12484
+ import pc23 from "picocolors";
11472
12485
  import { eq as eq2 } from "drizzle-orm";
11473
12486
 
11474
12487
  // src/commands/worktree-lib.ts
11475
12488
  init_home();
11476
12489
  import { randomInt } from "node:crypto";
11477
- import path16 from "node:path";
12490
+ import path20 from "node:path";
11478
12491
  var DEFAULT_WORKTREE_HOME = "~/.paperclip-worktrees";
11479
12492
  var WORKTREE_SEED_MODES = ["minimal", "full"];
11480
12493
  var MINIMAL_WORKTREE_EXCLUDED_TABLES = [
@@ -11522,7 +12535,7 @@ function sanitizeWorktreeInstanceId(rawValue) {
11522
12535
  return normalized || "worktree";
11523
12536
  }
11524
12537
  function resolveSuggestedWorktreeName(cwd, explicitName) {
11525
- return nonEmpty(explicitName) ?? path16.basename(path16.resolve(cwd));
12538
+ return nonEmpty(explicitName) ?? path20.basename(path20.resolve(cwd));
11526
12539
  }
11527
12540
  function hslComponentToHex(n) {
11528
12541
  return Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
@@ -11562,24 +12575,24 @@ function generateWorktreeColor() {
11562
12575
  return hslToHex(randomInt(0, 360), 68, 56);
11563
12576
  }
11564
12577
  function resolveWorktreeLocalPaths(opts) {
11565
- const cwd = path16.resolve(opts.cwd);
11566
- const homeDir = path16.resolve(expandHomePrefix(opts.homeDir ?? DEFAULT_WORKTREE_HOME));
11567
- const instanceRoot = path16.resolve(homeDir, "instances", opts.instanceId);
11568
- const repoConfigDir = path16.resolve(cwd, ".paperclip");
12578
+ const cwd = path20.resolve(opts.cwd);
12579
+ const homeDir = path20.resolve(expandHomePrefix(opts.homeDir ?? DEFAULT_WORKTREE_HOME));
12580
+ const instanceRoot = path20.resolve(homeDir, "instances", opts.instanceId);
12581
+ const repoConfigDir = path20.resolve(cwd, ".paperclip");
11569
12582
  return {
11570
12583
  cwd,
11571
12584
  repoConfigDir,
11572
- configPath: path16.resolve(repoConfigDir, "config.json"),
11573
- envPath: path16.resolve(repoConfigDir, ".env"),
12585
+ configPath: path20.resolve(repoConfigDir, "config.json"),
12586
+ envPath: path20.resolve(repoConfigDir, ".env"),
11574
12587
  homeDir,
11575
12588
  instanceId: opts.instanceId,
11576
12589
  instanceRoot,
11577
- contextPath: path16.resolve(homeDir, "context.json"),
11578
- embeddedPostgresDataDir: path16.resolve(instanceRoot, "db"),
11579
- backupDir: path16.resolve(instanceRoot, "data", "backups"),
11580
- logDir: path16.resolve(instanceRoot, "logs"),
11581
- secretsKeyFilePath: path16.resolve(instanceRoot, "secrets", "master.key"),
11582
- storageDir: path16.resolve(instanceRoot, "data", "storage")
12590
+ contextPath: path20.resolve(homeDir, "context.json"),
12591
+ embeddedPostgresDataDir: path20.resolve(instanceRoot, "db"),
12592
+ backupDir: path20.resolve(instanceRoot, "data", "backups"),
12593
+ logDir: path20.resolve(instanceRoot, "logs"),
12594
+ secretsKeyFilePath: path20.resolve(instanceRoot, "secrets", "master.key"),
12595
+ storageDir: path20.resolve(instanceRoot, "data", "storage")
11583
12596
  };
11584
12597
  }
11585
12598
  function rewriteLocalUrlPort(rawUrl, port) {
@@ -11685,7 +12698,7 @@ function isCurrentSourceConfigPath(sourceConfigPath) {
11685
12698
  if (!currentConfigPath || currentConfigPath.trim().length === 0) {
11686
12699
  return false;
11687
12700
  }
11688
- return path17.resolve(currentConfigPath) === path17.resolve(sourceConfigPath);
12701
+ return path21.resolve(currentConfigPath) === path21.resolve(sourceConfigPath);
11689
12702
  }
11690
12703
  var WORKTREE_NAME_PREFIX = "paperclip-";
11691
12704
  function resolveWorktreeMakeName(name) {
@@ -11707,7 +12720,7 @@ function resolveWorktreeStartPoint(explicit) {
11707
12720
  return explicit ?? nonEmpty2(process.env.PAPERCLIP_WORKTREE_START_POINT) ?? void 0;
11708
12721
  }
11709
12722
  function resolveWorktreeMakeTargetPath(name) {
11710
- return path17.resolve(os3.homedir(), resolveWorktreeMakeName(name));
12723
+ return path21.resolve(os5.homedir(), resolveWorktreeMakeName(name));
11711
12724
  }
11712
12725
  function extractExecSyncErrorMessage(error) {
11713
12726
  if (!error || typeof error !== "object") {
@@ -11763,7 +12776,7 @@ function readRunningPostmasterPid(postmasterPidFile) {
11763
12776
  }
11764
12777
  async function isPortAvailable(port) {
11765
12778
  return await new Promise((resolve2) => {
11766
- const server = createServer();
12779
+ const server = createServer2();
11767
12780
  server.unref();
11768
12781
  server.once("error", () => resolve2(false));
11769
12782
  server.listen(port, "127.0.0.1", () => {
@@ -11813,10 +12826,10 @@ function detectGitWorkspaceInfo(cwd) {
11813
12826
  stdio: ["ignore", "pipe", "ignore"]
11814
12827
  }).trim();
11815
12828
  return {
11816
- root: path17.resolve(root),
11817
- commonDir: path17.resolve(root, commonDirRaw),
11818
- gitDir: path17.resolve(root, gitDirRaw),
11819
- hooksPath: path17.resolve(root, hooksPathRaw)
12829
+ root: path21.resolve(root),
12830
+ commonDir: path21.resolve(root, commonDirRaw),
12831
+ gitDir: path21.resolve(root, gitDirRaw),
12832
+ hooksPath: path21.resolve(root, hooksPathRaw)
11820
12833
  };
11821
12834
  } catch {
11822
12835
  return null;
@@ -11829,8 +12842,8 @@ function copyDirectoryContents(sourceDir, targetDir) {
11829
12842
  mkdirSync2(targetDir, { recursive: true });
11830
12843
  let copied = false;
11831
12844
  for (const entry of entries) {
11832
- const sourcePath = path17.resolve(sourceDir, entry.name);
11833
- const targetPath = path17.resolve(targetDir, entry.name);
12845
+ const sourcePath = path21.resolve(sourceDir, entry.name);
12846
+ const targetPath = path21.resolve(targetDir, entry.name);
11834
12847
  if (entry.isDirectory()) {
11835
12848
  mkdirSync2(targetPath, { recursive: true });
11836
12849
  copyDirectoryContents(sourcePath, targetPath);
@@ -11856,7 +12869,7 @@ function copyGitHooksToWorktreeGitDir(cwd) {
11856
12869
  const workspace = detectGitWorkspaceInfo(cwd);
11857
12870
  if (!workspace) return null;
11858
12871
  const sourceHooksPath = workspace.hooksPath;
11859
- const targetHooksPath = path17.resolve(workspace.gitDir, "hooks");
12872
+ const targetHooksPath = path21.resolve(workspace.gitDir, "hooks");
11860
12873
  if (sourceHooksPath === targetHooksPath) {
11861
12874
  return {
11862
12875
  sourceHooksPath,
@@ -11871,17 +12884,17 @@ function copyGitHooksToWorktreeGitDir(cwd) {
11871
12884
  };
11872
12885
  }
11873
12886
  function rebindWorkspaceCwd(input) {
11874
- const sourceRepoRoot = path17.resolve(input.sourceRepoRoot);
11875
- const targetRepoRoot = path17.resolve(input.targetRepoRoot);
11876
- const workspaceCwd = path17.resolve(input.workspaceCwd);
11877
- const relative = path17.relative(sourceRepoRoot, workspaceCwd);
12887
+ const sourceRepoRoot = path21.resolve(input.sourceRepoRoot);
12888
+ const targetRepoRoot = path21.resolve(input.targetRepoRoot);
12889
+ const workspaceCwd = path21.resolve(input.workspaceCwd);
12890
+ const relative = path21.relative(sourceRepoRoot, workspaceCwd);
11878
12891
  if (!relative || relative === "") {
11879
12892
  return targetRepoRoot;
11880
12893
  }
11881
- if (relative.startsWith("..") || path17.isAbsolute(relative)) {
12894
+ if (relative.startsWith("..") || path21.isAbsolute(relative)) {
11882
12895
  return null;
11883
12896
  }
11884
- return path17.resolve(targetRepoRoot, relative);
12897
+ return path21.resolve(targetRepoRoot, relative);
11885
12898
  }
11886
12899
  async function rebindSeededProjectWorkspaces(input) {
11887
12900
  const targetRepo = detectGitWorkspaceInfo(input.currentCwd);
@@ -11907,7 +12920,7 @@ async function rebindSeededProjectWorkspaces(input) {
11907
12920
  workspaceCwd
11908
12921
  });
11909
12922
  if (!reboundCwd) continue;
11910
- const normalizedCurrent = path17.resolve(workspaceCwd);
12923
+ const normalizedCurrent = path21.resolve(workspaceCwd);
11911
12924
  if (reboundCwd === normalizedCurrent) continue;
11912
12925
  if (!existsSync2(reboundCwd)) continue;
11913
12926
  await db.update(projectWorkspaces).set({
@@ -11926,14 +12939,14 @@ async function rebindSeededProjectWorkspaces(input) {
11926
12939
  }
11927
12940
  }
11928
12941
  function resolveSourceConfigPath(opts) {
11929
- if (opts.sourceConfigPathOverride) return path17.resolve(opts.sourceConfigPathOverride);
11930
- if (opts.fromConfig) return path17.resolve(opts.fromConfig);
12942
+ if (opts.sourceConfigPathOverride) return path21.resolve(opts.sourceConfigPathOverride);
12943
+ if (opts.fromConfig) return path21.resolve(opts.fromConfig);
11931
12944
  if (!opts.fromDataDir && !opts.fromInstance) {
11932
12945
  return resolveConfigPath();
11933
12946
  }
11934
- const sourceHome = path17.resolve(expandHomePrefix(opts.fromDataDir ?? "~/.paperclip"));
12947
+ const sourceHome = path21.resolve(expandHomePrefix(opts.fromDataDir ?? "~/.paperclip"));
11935
12948
  const sourceInstanceId = sanitizeWorktreeInstanceId(opts.fromInstance ?? "default");
11936
- return path17.resolve(sourceHome, "instances", sourceInstanceId, "config.json");
12949
+ return path21.resolve(sourceHome, "instances", sourceInstanceId, "config.json");
11937
12950
  }
11938
12951
  function resolveSourceConnectionString(config, envEntries, portOverride) {
11939
12952
  if (config.database.mode === "postgres") {
@@ -11952,7 +12965,7 @@ function copySeededSecretsKey(input) {
11952
12965
  if (input.sourceConfig.secrets.provider !== "local_encrypted") {
11953
12966
  return;
11954
12967
  }
11955
- mkdirSync2(path17.dirname(input.targetKeyFilePath), { recursive: true });
12968
+ mkdirSync2(path21.dirname(input.targetKeyFilePath), { recursive: true });
11956
12969
  const allowProcessEnvFallback = isCurrentSourceConfigPath(input.sourceConfigPath);
11957
12970
  const sourceInlineMasterKey = nonEmpty2(input.sourceEnvEntries.PAPERCLIP_SECRETS_MASTER_KEY) ?? (allowProcessEnvFallback ? nonEmpty2(process.env.PAPERCLIP_SECRETS_MASTER_KEY) : null);
11958
12971
  if (sourceInlineMasterKey) {
@@ -11991,7 +13004,7 @@ async function ensureEmbeddedPostgres(dataDir, preferredPort) {
11991
13004
  "Embedded PostgreSQL support requires dependency `embedded-postgres`. Reinstall dependencies and try again."
11992
13005
  );
11993
13006
  }
11994
- const postmasterPidFile = path17.resolve(dataDir, "postmaster.pid");
13007
+ const postmasterPidFile = path21.resolve(dataDir, "postmaster.pid");
11995
13008
  const runningPid = readRunningPostmasterPid(postmasterPidFile);
11996
13009
  if (runningPid) {
11997
13010
  return {
@@ -12014,7 +13027,7 @@ async function ensureEmbeddedPostgres(dataDir, preferredPort) {
12014
13027
  onError: () => {
12015
13028
  }
12016
13029
  });
12017
- if (!existsSync2(path17.resolve(dataDir, "PG_VERSION"))) {
13030
+ if (!existsSync2(path21.resolve(dataDir, "PG_VERSION"))) {
12018
13031
  await instance.initialise();
12019
13032
  }
12020
13033
  if (existsSync2(postmasterPidFile)) {
@@ -12055,7 +13068,7 @@ async function seedWorktreeDatabase(input) {
12055
13068
  );
12056
13069
  const backup = await runDatabaseBackup({
12057
13070
  connectionString: sourceConnectionString,
12058
- backupDir: path17.resolve(input.targetPaths.backupDir, "seed"),
13071
+ backupDir: path21.resolve(input.targetPaths.backupDir, "seed"),
12059
13072
  retentionDays: 7,
12060
13073
  filenamePrefix: `${input.instanceId}-seed`,
12061
13074
  includeMigrationJournal: true,
@@ -12153,8 +13166,8 @@ async function runWorktreeInit(opts) {
12153
13166
  `Cannot seed worktree database because source config was not found at ${sourceConfigPath}. Use --no-seed or provide --from-config.`
12154
13167
  );
12155
13168
  }
12156
- const spinner4 = p15.spinner();
12157
- spinner4.start(`Seeding isolated worktree database from source instance (${seedMode})...`);
13169
+ const spinner5 = p16.spinner();
13170
+ spinner5.start(`Seeding isolated worktree database from source instance (${seedMode})...`);
12158
13171
  try {
12159
13172
  const seeded = await seedWorktreeDatabase({
12160
13173
  sourceConfigPath,
@@ -12166,46 +13179,46 @@ async function runWorktreeInit(opts) {
12166
13179
  });
12167
13180
  seedSummary = seeded.backupSummary;
12168
13181
  reboundWorkspaceSummary = seeded.reboundWorkspaces;
12169
- spinner4.stop(`Seeded isolated worktree database (${seedMode}).`);
13182
+ spinner5.stop(`Seeded isolated worktree database (${seedMode}).`);
12170
13183
  } catch (error) {
12171
- spinner4.stop(pc21.red("Failed to seed worktree database."));
13184
+ spinner5.stop(pc23.red("Failed to seed worktree database."));
12172
13185
  throw error;
12173
13186
  }
12174
13187
  }
12175
- p15.log.message(pc21.dim(`Repo config: ${paths.configPath}`));
12176
- p15.log.message(pc21.dim(`Repo env: ${paths.envPath}`));
12177
- p15.log.message(pc21.dim(`Isolated home: ${paths.homeDir}`));
12178
- p15.log.message(pc21.dim(`Instance: ${paths.instanceId}`));
12179
- p15.log.message(pc21.dim(`Worktree badge: ${branding.name} (${branding.color})`));
12180
- p15.log.message(pc21.dim(`Server port: ${serverPort} | DB port: ${databasePort}`));
13188
+ p16.log.message(pc23.dim(`Repo config: ${paths.configPath}`));
13189
+ p16.log.message(pc23.dim(`Repo env: ${paths.envPath}`));
13190
+ p16.log.message(pc23.dim(`Isolated home: ${paths.homeDir}`));
13191
+ p16.log.message(pc23.dim(`Instance: ${paths.instanceId}`));
13192
+ p16.log.message(pc23.dim(`Worktree badge: ${branding.name} (${branding.color})`));
13193
+ p16.log.message(pc23.dim(`Server port: ${serverPort} | DB port: ${databasePort}`));
12181
13194
  if (copiedGitHooks?.copied) {
12182
- p15.log.message(
12183
- pc21.dim(`Mirrored git hooks: ${copiedGitHooks.sourceHooksPath} -> ${copiedGitHooks.targetHooksPath}`)
13195
+ p16.log.message(
13196
+ pc23.dim(`Mirrored git hooks: ${copiedGitHooks.sourceHooksPath} -> ${copiedGitHooks.targetHooksPath}`)
12184
13197
  );
12185
13198
  }
12186
13199
  if (seedSummary) {
12187
- p15.log.message(pc21.dim(`Seed mode: ${seedMode}`));
12188
- p15.log.message(pc21.dim(`Seed snapshot: ${seedSummary}`));
13200
+ p16.log.message(pc23.dim(`Seed mode: ${seedMode}`));
13201
+ p16.log.message(pc23.dim(`Seed snapshot: ${seedSummary}`));
12189
13202
  for (const rebound of reboundWorkspaceSummary) {
12190
- p15.log.message(
12191
- pc21.dim(`Rebound workspace ${rebound.name}: ${rebound.fromCwd} -> ${rebound.toCwd}`)
13203
+ p16.log.message(
13204
+ pc23.dim(`Rebound workspace ${rebound.name}: ${rebound.fromCwd} -> ${rebound.toCwd}`)
12192
13205
  );
12193
13206
  }
12194
13207
  }
12195
- p15.outro(
12196
- pc21.green(
13208
+ p16.outro(
13209
+ pc23.green(
12197
13210
  `Worktree ready. Run Paperclip inside this repo and the CLI/server will use ${paths.instanceId} automatically.`
12198
13211
  )
12199
13212
  );
12200
13213
  }
12201
13214
  async function worktreeInitCommand(opts) {
12202
13215
  printPaperclipCliBanner();
12203
- p15.intro(pc21.bgCyan(pc21.black(" paperclipai worktree init ")));
13216
+ p16.intro(pc23.bgCyan(pc23.black(" paperclipai worktree init ")));
12204
13217
  await runWorktreeInit(opts);
12205
13218
  }
12206
13219
  async function worktreeMakeCommand(nameArg, opts) {
12207
13220
  printPaperclipCliBanner();
12208
- p15.intro(pc21.bgCyan(pc21.black(" paperclipai worktree:make ")));
13221
+ p16.intro(pc23.bgCyan(pc23.black(" paperclipai worktree:make ")));
12209
13222
  const name = resolveWorktreeMakeName(nameArg);
12210
13223
  const startPoint = resolveWorktreeStartPoint(opts.startPoint);
12211
13224
  const sourceCwd = process.cwd();
@@ -12214,7 +13227,7 @@ async function worktreeMakeCommand(nameArg, opts) {
12214
13227
  if (existsSync2(targetPath)) {
12215
13228
  throw new Error(`Target path already exists: ${targetPath}`);
12216
13229
  }
12217
- mkdirSync2(path17.dirname(targetPath), { recursive: true });
13230
+ mkdirSync2(path21.dirname(targetPath), { recursive: true });
12218
13231
  if (startPoint) {
12219
13232
  const [remote] = startPoint.split("/", 1);
12220
13233
  try {
@@ -12234,19 +13247,19 @@ async function worktreeMakeCommand(nameArg, opts) {
12234
13247
  branchExists: !startPoint && localBranchExists(sourceCwd, name),
12235
13248
  startPoint
12236
13249
  });
12237
- const spinner4 = p15.spinner();
12238
- spinner4.start(`Creating git worktree at ${targetPath}...`);
13250
+ const spinner5 = p16.spinner();
13251
+ spinner5.start(`Creating git worktree at ${targetPath}...`);
12239
13252
  try {
12240
13253
  execFileSync("git", worktreeArgs, {
12241
13254
  cwd: sourceCwd,
12242
13255
  stdio: ["ignore", "pipe", "pipe"]
12243
13256
  });
12244
- spinner4.stop(`Created git worktree at ${targetPath}.`);
13257
+ spinner5.stop(`Created git worktree at ${targetPath}.`);
12245
13258
  } catch (error) {
12246
- spinner4.stop(pc21.red("Failed to create git worktree."));
13259
+ spinner5.stop(pc23.red("Failed to create git worktree."));
12247
13260
  throw new Error(extractExecSyncErrorMessage(error) ?? String(error));
12248
13261
  }
12249
- const installSpinner = p15.spinner();
13262
+ const installSpinner = p16.spinner();
12250
13263
  installSpinner.start("Installing dependencies...");
12251
13264
  try {
12252
13265
  execFileSync("pnpm", ["install"], {
@@ -12255,8 +13268,8 @@ async function worktreeMakeCommand(nameArg, opts) {
12255
13268
  });
12256
13269
  installSpinner.stop("Installed dependencies.");
12257
13270
  } catch (error) {
12258
- installSpinner.stop(pc21.yellow("Failed to install dependencies (continuing anyway)."));
12259
- p15.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
13271
+ installSpinner.stop(pc23.yellow("Failed to install dependencies (continuing anyway)."));
13272
+ p16.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
12260
13273
  }
12261
13274
  const originalCwd = process.cwd();
12262
13275
  try {
@@ -12271,13 +13284,13 @@ async function worktreeMakeCommand(nameArg, opts) {
12271
13284
  } finally {
12272
13285
  process.chdir(originalCwd);
12273
13286
  }
12274
- const bootstrapScript = path17.resolve(sourceCwd, "scripts/worktree-bootstrap.mjs");
13287
+ const bootstrapScript = path21.resolve(sourceCwd, "scripts/worktree-bootstrap.mjs");
12275
13288
  if (existsSync2(bootstrapScript)) {
12276
- p15.log.message(pc21.dim(`Running worktree bootstrap in ${targetPath}...`));
13289
+ p16.log.message(pc23.dim(`Running worktree bootstrap in ${targetPath}...`));
12277
13290
  try {
12278
13291
  execFileSync("node", [bootstrapScript], { cwd: targetPath, stdio: "inherit" });
12279
13292
  } catch (error) {
12280
- p15.log.warning(`Bootstrap failed: ${extractExecSyncErrorMessage(error) ?? String(error)}`);
13293
+ p16.log.warning(`Bootstrap failed: ${extractExecSyncErrorMessage(error) ?? String(error)}`);
12281
13294
  }
12282
13295
  }
12283
13296
  }
@@ -12356,30 +13369,30 @@ function worktreePathHasUncommittedChanges(worktreePath) {
12356
13369
  }
12357
13370
  async function worktreeCleanupCommand(nameArg, opts) {
12358
13371
  printPaperclipCliBanner();
12359
- p15.intro(pc21.bgCyan(pc21.black(" paperclipai worktree:cleanup ")));
13372
+ p16.intro(pc23.bgCyan(pc23.black(" paperclipai worktree:cleanup ")));
12360
13373
  const name = resolveWorktreeMakeName(nameArg);
12361
13374
  const sourceCwd = process.cwd();
12362
13375
  const targetPath = resolveWorktreeMakeTargetPath(name);
12363
13376
  const instanceId = sanitizeWorktreeInstanceId(opts.instance ?? name);
12364
- const homeDir = path17.resolve(expandHomePrefix(resolveWorktreeHome(opts.home)));
12365
- const instanceRoot = path17.resolve(homeDir, "instances", instanceId);
13377
+ const homeDir = path21.resolve(expandHomePrefix(resolveWorktreeHome(opts.home)));
13378
+ const instanceRoot = path21.resolve(homeDir, "instances", instanceId);
12366
13379
  const hasBranch = localBranchExists(sourceCwd, name);
12367
13380
  const hasTargetDir = existsSync2(targetPath);
12368
13381
  const hasInstanceData = existsSync2(instanceRoot);
12369
13382
  const worktrees = parseGitWorktreeList(sourceCwd);
12370
13383
  const linkedWorktree = worktrees.find(
12371
- (wt) => wt.branch === `refs/heads/${name}` || path17.resolve(wt.worktree) === path17.resolve(targetPath)
13384
+ (wt) => wt.branch === `refs/heads/${name}` || path21.resolve(wt.worktree) === path21.resolve(targetPath)
12372
13385
  );
12373
13386
  if (!hasBranch && !hasTargetDir && !hasInstanceData && !linkedWorktree) {
12374
- p15.log.info("Nothing to clean up \u2014 no branch, worktree directory, or instance data found.");
12375
- p15.outro(pc21.green("Already clean."));
13387
+ p16.log.info("Nothing to clean up \u2014 no branch, worktree directory, or instance data found.");
13388
+ p16.outro(pc23.green("Already clean."));
12376
13389
  return;
12377
13390
  }
12378
13391
  const problems = [];
12379
13392
  if (hasBranch && branchHasUniqueCommits(sourceCwd, name)) {
12380
13393
  const onRemote = branchExistsOnAnyRemote(sourceCwd, name);
12381
13394
  if (onRemote) {
12382
- p15.log.info(
13395
+ p16.log.info(
12383
13396
  `Branch "${name}" has unique local commits, but the branch also exists on a remote \u2014 safe to delete locally.`
12384
13397
  );
12385
13398
  } else {
@@ -12395,20 +13408,20 @@ async function worktreeCleanupCommand(nameArg, opts) {
12395
13408
  }
12396
13409
  if (problems.length > 0 && !opts.force) {
12397
13410
  for (const problem of problems) {
12398
- p15.log.error(problem);
13411
+ p16.log.error(problem);
12399
13412
  }
12400
13413
  throw new Error("Safety checks failed. Resolve the issues above or re-run with --force.");
12401
13414
  }
12402
13415
  if (problems.length > 0 && opts.force) {
12403
13416
  for (const problem of problems) {
12404
- p15.log.warning(`Overridden by --force: ${problem}`);
13417
+ p16.log.warning(`Overridden by --force: ${problem}`);
12405
13418
  }
12406
13419
  }
12407
13420
  if (linkedWorktree) {
12408
13421
  const worktreeDirExists = existsSync2(linkedWorktree.worktree);
12409
- const spinner4 = p15.spinner();
13422
+ const spinner5 = p16.spinner();
12410
13423
  if (worktreeDirExists) {
12411
- spinner4.start(`Removing git worktree at ${linkedWorktree.worktree}...`);
13424
+ spinner5.start(`Removing git worktree at ${linkedWorktree.worktree}...`);
12412
13425
  try {
12413
13426
  const removeArgs = ["worktree", "remove", linkedWorktree.worktree];
12414
13427
  if (opts.force) removeArgs.push("--force");
@@ -12416,18 +13429,18 @@ async function worktreeCleanupCommand(nameArg, opts) {
12416
13429
  cwd: sourceCwd,
12417
13430
  stdio: ["ignore", "pipe", "pipe"]
12418
13431
  });
12419
- spinner4.stop(`Removed git worktree at ${linkedWorktree.worktree}.`);
13432
+ spinner5.stop(`Removed git worktree at ${linkedWorktree.worktree}.`);
12420
13433
  } catch (error) {
12421
- spinner4.stop(pc21.yellow(`Could not remove worktree cleanly, will prune instead.`));
12422
- p15.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
13434
+ spinner5.stop(pc23.yellow(`Could not remove worktree cleanly, will prune instead.`));
13435
+ p16.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
12423
13436
  }
12424
13437
  } else {
12425
- spinner4.start("Pruning stale worktree entry...");
13438
+ spinner5.start("Pruning stale worktree entry...");
12426
13439
  execFileSync("git", ["worktree", "prune"], {
12427
13440
  cwd: sourceCwd,
12428
13441
  stdio: ["ignore", "pipe", "pipe"]
12429
13442
  });
12430
- spinner4.stop("Pruned stale worktree entry.");
13443
+ spinner5.stop("Pruned stale worktree entry.");
12431
13444
  }
12432
13445
  } else {
12433
13446
  execFileSync("git", ["worktree", "prune"], {
@@ -12436,33 +13449,33 @@ async function worktreeCleanupCommand(nameArg, opts) {
12436
13449
  });
12437
13450
  }
12438
13451
  if (existsSync2(targetPath)) {
12439
- const spinner4 = p15.spinner();
12440
- spinner4.start(`Removing worktree directory ${targetPath}...`);
13452
+ const spinner5 = p16.spinner();
13453
+ spinner5.start(`Removing worktree directory ${targetPath}...`);
12441
13454
  rmSync(targetPath, { recursive: true, force: true });
12442
- spinner4.stop(`Removed worktree directory ${targetPath}.`);
13455
+ spinner5.stop(`Removed worktree directory ${targetPath}.`);
12443
13456
  }
12444
13457
  if (localBranchExists(sourceCwd, name)) {
12445
- const spinner4 = p15.spinner();
12446
- spinner4.start(`Deleting local branch "${name}"...`);
13458
+ const spinner5 = p16.spinner();
13459
+ spinner5.start(`Deleting local branch "${name}"...`);
12447
13460
  try {
12448
13461
  const deleteFlag = opts.force ? "-D" : "-d";
12449
13462
  execFileSync("git", ["branch", deleteFlag, name], {
12450
13463
  cwd: sourceCwd,
12451
13464
  stdio: ["ignore", "pipe", "pipe"]
12452
13465
  });
12453
- spinner4.stop(`Deleted local branch "${name}".`);
13466
+ spinner5.stop(`Deleted local branch "${name}".`);
12454
13467
  } catch (error) {
12455
- spinner4.stop(pc21.yellow(`Could not delete branch "${name}".`));
12456
- p15.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
13468
+ spinner5.stop(pc23.yellow(`Could not delete branch "${name}".`));
13469
+ p16.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
12457
13470
  }
12458
13471
  }
12459
13472
  if (existsSync2(instanceRoot)) {
12460
- const spinner4 = p15.spinner();
12461
- spinner4.start(`Removing instance data at ${instanceRoot}...`);
13473
+ const spinner5 = p16.spinner();
13474
+ spinner5.start(`Removing instance data at ${instanceRoot}...`);
12462
13475
  rmSync(instanceRoot, { recursive: true, force: true });
12463
- spinner4.stop(`Removed instance data at ${instanceRoot}.`);
13476
+ spinner5.stop(`Removed instance data at ${instanceRoot}.`);
12464
13477
  }
12465
- p15.outro(pc21.green("Cleanup complete."));
13478
+ p16.outro(pc23.green("Cleanup complete."));
12466
13479
  }
12467
13480
  async function worktreeEnvCommand(opts) {
12468
13481
  const configPath = resolveConfigPath(opts.config);
@@ -12490,27 +13503,27 @@ function registerWorktreeCommands(program2) {
12490
13503
  }
12491
13504
 
12492
13505
  // src/commands/client/plugin.ts
12493
- import path18 from "node:path";
12494
- import pc22 from "picocolors";
13506
+ import path22 from "node:path";
13507
+ import pc24 from "picocolors";
12495
13508
  function resolvePackageArg(packageArg, isLocal) {
12496
13509
  if (!isLocal) return packageArg;
12497
- if (path18.isAbsolute(packageArg)) return packageArg;
13510
+ if (path22.isAbsolute(packageArg)) return packageArg;
12498
13511
  if (packageArg.startsWith("~")) {
12499
13512
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12500
- return path18.resolve(home, packageArg.slice(1).replace(/^[\\/]/, ""));
13513
+ return path22.resolve(home, packageArg.slice(1).replace(/^[\\/]/, ""));
12501
13514
  }
12502
- return path18.resolve(process.cwd(), packageArg);
13515
+ return path22.resolve(process.cwd(), packageArg);
12503
13516
  }
12504
- function formatPlugin(p19) {
12505
- const statusColor = p19.status === "ready" ? pc22.green(p19.status) : p19.status === "error" ? pc22.red(p19.status) : p19.status === "disabled" ? pc22.dim(p19.status) : pc22.yellow(p19.status);
13517
+ function formatPlugin(p20) {
13518
+ const statusColor = p20.status === "ready" ? pc24.green(p20.status) : p20.status === "error" ? pc24.red(p20.status) : p20.status === "disabled" ? pc24.dim(p20.status) : pc24.yellow(p20.status);
12506
13519
  const parts = [
12507
- `key=${pc22.bold(p19.pluginKey)}`,
13520
+ `key=${pc24.bold(p20.pluginKey)}`,
12508
13521
  `status=${statusColor}`,
12509
- `version=${p19.version}`,
12510
- `id=${pc22.dim(p19.id)}`
13522
+ `version=${p20.version}`,
13523
+ `id=${pc24.dim(p20.id)}`
12511
13524
  ];
12512
- if (p19.lastError) {
12513
- parts.push(`error=${pc22.red(p19.lastError.slice(0, 80))}`);
13525
+ if (p20.lastError) {
13526
+ parts.push(`error=${pc24.red(p20.lastError.slice(0, 80))}`);
12514
13527
  }
12515
13528
  return parts.join(" ");
12516
13529
  }
@@ -12528,11 +13541,11 @@ function registerPluginCommands(program2) {
12528
13541
  }
12529
13542
  const rows = plugins2 ?? [];
12530
13543
  if (rows.length === 0) {
12531
- console.log(pc22.dim("No plugins installed."));
13544
+ console.log(pc24.dim("No plugins installed."));
12532
13545
  return;
12533
13546
  }
12534
- for (const p19 of rows) {
12535
- console.log(formatPlugin(p19));
13547
+ for (const p20 of rows) {
13548
+ console.log(formatPlugin(p20));
12536
13549
  }
12537
13550
  } catch (err) {
12538
13551
  handleCommandError(err);
@@ -12549,7 +13562,7 @@ function registerPluginCommands(program2) {
12549
13562
  const resolvedPackage = resolvePackageArg(packageArg, isLocal);
12550
13563
  if (!ctx.json) {
12551
13564
  console.log(
12552
- pc22.dim(
13565
+ pc24.dim(
12553
13566
  isLocal ? `Installing plugin from local path: ${resolvedPackage}` : `Installing plugin: ${resolvedPackage}${opts.version ? `@${opts.version}` : ""}`
12554
13567
  )
12555
13568
  );
@@ -12564,16 +13577,16 @@ function registerPluginCommands(program2) {
12564
13577
  return;
12565
13578
  }
12566
13579
  if (!installedPlugin) {
12567
- console.log(pc22.dim("Install returned no plugin record."));
13580
+ console.log(pc24.dim("Install returned no plugin record."));
12568
13581
  return;
12569
13582
  }
12570
13583
  console.log(
12571
- pc22.green(
12572
- `\u2713 Installed ${pc22.bold(installedPlugin.pluginKey)} v${installedPlugin.version} (${installedPlugin.status})`
13584
+ pc24.green(
13585
+ `\u2713 Installed ${pc24.bold(installedPlugin.pluginKey)} v${installedPlugin.version} (${installedPlugin.status})`
12573
13586
  )
12574
13587
  );
12575
13588
  if (installedPlugin.lastError) {
12576
- console.log(pc22.red(` Warning: ${installedPlugin.lastError}`));
13589
+ console.log(pc24.red(` Warning: ${installedPlugin.lastError}`));
12577
13590
  }
12578
13591
  } catch (err) {
12579
13592
  handleCommandError(err);
@@ -12590,7 +13603,7 @@ function registerPluginCommands(program2) {
12590
13603
  const qs = purge ? "?purge=true" : "";
12591
13604
  if (!ctx.json) {
12592
13605
  console.log(
12593
- pc22.dim(
13606
+ pc24.dim(
12594
13607
  purge ? `Uninstalling and purging plugin: ${pluginKey}` : `Uninstalling plugin: ${pluginKey}`
12595
13608
  )
12596
13609
  );
@@ -12602,7 +13615,7 @@ function registerPluginCommands(program2) {
12602
13615
  printOutput(result, { json: true });
12603
13616
  return;
12604
13617
  }
12605
- console.log(pc22.green(`\u2713 Uninstalled ${pc22.bold(pluginKey)}${purge ? " (purged)" : ""}`));
13618
+ console.log(pc24.green(`\u2713 Uninstalled ${pc24.bold(pluginKey)}${purge ? " (purged)" : ""}`));
12606
13619
  } catch (err) {
12607
13620
  handleCommandError(err);
12608
13621
  }
@@ -12619,7 +13632,7 @@ function registerPluginCommands(program2) {
12619
13632
  printOutput(result, { json: true });
12620
13633
  return;
12621
13634
  }
12622
- console.log(pc22.green(`\u2713 Enabled ${pc22.bold(pluginKey)} \u2014 status: ${result?.status ?? "unknown"}`));
13635
+ console.log(pc24.green(`\u2713 Enabled ${pc24.bold(pluginKey)} \u2014 status: ${result?.status ?? "unknown"}`));
12623
13636
  } catch (err) {
12624
13637
  handleCommandError(err);
12625
13638
  }
@@ -12636,7 +13649,7 @@ function registerPluginCommands(program2) {
12636
13649
  printOutput(result, { json: true });
12637
13650
  return;
12638
13651
  }
12639
- console.log(pc22.dim(`Disabled ${pc22.bold(pluginKey)} \u2014 status: ${result?.status ?? "unknown"}`));
13652
+ console.log(pc24.dim(`Disabled ${pc24.bold(pluginKey)} \u2014 status: ${result?.status ?? "unknown"}`));
12640
13653
  } catch (err) {
12641
13654
  handleCommandError(err);
12642
13655
  }
@@ -12654,13 +13667,13 @@ function registerPluginCommands(program2) {
12654
13667
  return;
12655
13668
  }
12656
13669
  if (!result) {
12657
- console.log(pc22.red(`Plugin not found: ${pluginKey}`));
13670
+ console.log(pc24.red(`Plugin not found: ${pluginKey}`));
12658
13671
  process.exit(1);
12659
13672
  }
12660
13673
  console.log(formatPlugin(result));
12661
13674
  if (result.lastError) {
12662
13675
  console.log(`
12663
- ${pc22.red("Last error:")}
13676
+ ${pc24.red("Last error:")}
12664
13677
  ${result.lastError}`);
12665
13678
  }
12666
13679
  } catch (err) {
@@ -12679,14 +13692,14 @@ ${result.lastError}`);
12679
13692
  }
12680
13693
  const rows = examples ?? [];
12681
13694
  if (rows.length === 0) {
12682
- console.log(pc22.dim("No bundled examples available."));
13695
+ console.log(pc24.dim("No bundled examples available."));
12683
13696
  return;
12684
13697
  }
12685
13698
  for (const ex of rows) {
12686
13699
  console.log(
12687
- `${pc22.bold(ex.displayName)} ${pc22.dim(ex.pluginKey)}
13700
+ `${pc24.bold(ex.displayName)} ${pc24.dim(ex.pluginKey)}
12688
13701
  ${ex.description}
12689
- ${pc22.cyan(`paperclipai plugin install ${ex.localPath}`)}`
13702
+ ${pc24.cyan(`paperclipai plugin install ${ex.localPath}`)}`
12690
13703
  );
12691
13704
  }
12692
13705
  } catch (err) {
@@ -12697,15 +13710,15 @@ ${result.lastError}`);
12697
13710
  }
12698
13711
 
12699
13712
  // src/commands/kit.ts
12700
- import path20 from "node:path";
13713
+ import path24 from "node:path";
12701
13714
  import { pathToFileURL as pathToFileURL2 } from "node:url";
12702
- import * as p16 from "@clack/prompts";
12703
- import pc23 from "picocolors";
13715
+ import * as p17 from "@clack/prompts";
13716
+ import pc25 from "picocolors";
12704
13717
 
12705
13718
  // src/kits/service.ts
12706
13719
  init_home();
12707
- import fs14 from "node:fs";
12708
- import path19 from "node:path";
13720
+ import fs17 from "node:fs";
13721
+ import path23 from "node:path";
12709
13722
  import { fileURLToPath as fileURLToPath4 } from "node:url";
12710
13723
 
12711
13724
  // src/kits/catalog.ts
@@ -12798,48 +13811,48 @@ var KIT_ACTIVATION_MODES = [
12798
13811
  // src/kits/service.ts
12799
13812
  var ZIP_TIMESTAMP = /* @__PURE__ */ new Date("2026-04-09T00:00:00.000Z");
12800
13813
  function resolveBundledKitAssetsRoot() {
12801
- const moduleDir = path19.dirname(fileURLToPath4(import.meta.url));
13814
+ const moduleDir = path23.dirname(fileURLToPath4(import.meta.url));
12802
13815
  const candidates = [
12803
- path19.resolve(moduleDir, "../../assets/worker-kits"),
12804
- path19.resolve(moduleDir, "../assets/worker-kits")
13816
+ path23.resolve(moduleDir, "../../assets/worker-kits"),
13817
+ path23.resolve(moduleDir, "../assets/worker-kits")
12805
13818
  ];
12806
13819
  for (const candidate of candidates) {
12807
- if (fs14.existsSync(candidate)) return candidate;
13820
+ if (fs17.existsSync(candidate)) return candidate;
12808
13821
  }
12809
13822
  throw new Error("Could not locate bundled worker kit assets.");
12810
13823
  }
12811
13824
  function resolveRequestedOutputRoot(outDir) {
12812
13825
  if (outDir?.trim()) {
12813
- return path19.resolve(expandHomePrefix(outDir.trim()));
13826
+ return path23.resolve(expandHomePrefix(outDir.trim()));
12814
13827
  }
12815
- return path19.resolve(resolvePaperclipHomeDir(), "kits", "exports");
13828
+ return path23.resolve(resolvePaperclipHomeDir(), "kits", "exports");
12816
13829
  }
12817
13830
  function readJsonFile(filePath) {
12818
- return JSON.parse(fs14.readFileSync(filePath, "utf8"));
13831
+ return JSON.parse(fs17.readFileSync(filePath, "utf8"));
12819
13832
  }
12820
13833
  function assertRelativePathExists(assetRoot, relativePath, label) {
12821
- const fullPath = path19.resolve(assetRoot, relativePath);
12822
- if (!fs14.existsSync(fullPath)) {
13834
+ const fullPath = path23.resolve(assetRoot, relativePath);
13835
+ if (!fs17.existsSync(fullPath)) {
12823
13836
  throw new Error(`${label} is missing required path: ${relativePath}`);
12824
13837
  }
12825
13838
  }
12826
13839
  function listRelativeFiles(rootDir) {
12827
13840
  const files = [];
12828
13841
  const walk = (currentDir) => {
12829
- for (const entry of fs14.readdirSync(currentDir, { withFileTypes: true })) {
12830
- const fullPath = path19.join(currentDir, entry.name);
13842
+ for (const entry of fs17.readdirSync(currentDir, { withFileTypes: true })) {
13843
+ const fullPath = path23.join(currentDir, entry.name);
12831
13844
  if (entry.isDirectory()) {
12832
13845
  walk(fullPath);
12833
13846
  continue;
12834
13847
  }
12835
- files.push(path19.relative(rootDir, fullPath).split(path19.sep).join("/"));
13848
+ files.push(path23.relative(rootDir, fullPath).split(path23.sep).join("/"));
12836
13849
  }
12837
13850
  };
12838
13851
  walk(rootDir);
12839
13852
  return files.sort();
12840
13853
  }
12841
13854
  function parseManifest(assetRoot) {
12842
- const raw = readJsonFile(path19.resolve(assetRoot, "kit.json"));
13855
+ const raw = readJsonFile(path23.resolve(assetRoot, "kit.json"));
12843
13856
  if (!SUPPORTED_SCHEMA_VERSIONS.includes(raw.schemaVersion)) {
12844
13857
  throw new Error(`Unsupported kit schema version for ${assetRoot}: ${raw.schemaVersion}`);
12845
13858
  }
@@ -12850,7 +13863,7 @@ function parseBundleManifest(assetRoot, manifest, bundleId) {
12850
13863
  if (!bundleRef) {
12851
13864
  throw new Error(`Kit ${manifest.kit.id} does not declare bundle ${bundleId}.`);
12852
13865
  }
12853
- const raw = readJsonFile(path19.resolve(assetRoot, bundleRef.path));
13866
+ const raw = readJsonFile(path23.resolve(assetRoot, bundleRef.path));
12854
13867
  if (!SUPPORTED_SCHEMA_VERSIONS.includes(raw.schemaVersion)) {
12855
13868
  throw new Error(
12856
13869
  `Unsupported bundle schema version for ${bundleRef.path}: ${raw.schemaVersion}`
@@ -12915,14 +13928,14 @@ function validateKitDirectory(kitPath) {
12915
13928
  const warnings = [];
12916
13929
  let schemaVersion = 0;
12917
13930
  let kitId = "<unknown>";
12918
- const kitJsonPath = path19.resolve(kitPath, "kit.json");
12919
- if (!fs14.existsSync(kitJsonPath)) {
13931
+ const kitJsonPath = path23.resolve(kitPath, "kit.json");
13932
+ if (!fs17.existsSync(kitJsonPath)) {
12920
13933
  errors.push({ field: "kit.json", message: "kit.json not found in kit directory" });
12921
13934
  return { valid: false, schemaVersion, kitId, errors, warnings };
12922
13935
  }
12923
13936
  let raw;
12924
13937
  try {
12925
- raw = JSON.parse(fs14.readFileSync(kitJsonPath, "utf8"));
13938
+ raw = JSON.parse(fs17.readFileSync(kitJsonPath, "utf8"));
12926
13939
  } catch {
12927
13940
  errors.push({ field: "kit.json", message: "kit.json is not valid JSON" });
12928
13941
  return { valid: false, schemaVersion, kitId, errors, warnings };
@@ -12989,8 +14002,8 @@ function validateKitDirectory(kitPath) {
12989
14002
  if (typeof entrypoint.path !== "string") {
12990
14003
  errors.push({ field: "entrypoint.path", message: "Missing required field 'entrypoint.path'" });
12991
14004
  } else {
12992
- const fullPath = path19.resolve(kitPath, entrypoint.path);
12993
- if (!fs14.existsSync(fullPath)) {
14005
+ const fullPath = path23.resolve(kitPath, entrypoint.path);
14006
+ if (!fs17.existsSync(fullPath)) {
12994
14007
  errors.push({ field: "entrypoint.path", message: `Entrypoint file not found: ${entrypoint.path}` });
12995
14008
  }
12996
14009
  }
@@ -12998,16 +14011,16 @@ function validateKitDirectory(kitPath) {
12998
14011
  if (typeof raw.agentContractPath !== "string") {
12999
14012
  errors.push({ field: "agentContractPath", message: "Missing required field 'agentContractPath'" });
13000
14013
  } else {
13001
- const fullPath = path19.resolve(kitPath, raw.agentContractPath);
13002
- if (!fs14.existsSync(fullPath)) {
14014
+ const fullPath = path23.resolve(kitPath, raw.agentContractPath);
14015
+ if (!fs17.existsSync(fullPath)) {
13003
14016
  errors.push({ field: "agentContractPath", message: `Agent contract not found: ${raw.agentContractPath}` });
13004
14017
  }
13005
14018
  }
13006
14019
  if (typeof raw.brandTemplatePath !== "string") {
13007
14020
  errors.push({ field: "brandTemplatePath", message: "Missing required field 'brandTemplatePath'" });
13008
14021
  } else {
13009
- const fullPath = path19.resolve(kitPath, raw.brandTemplatePath);
13010
- if (!fs14.existsSync(fullPath)) {
14022
+ const fullPath = path23.resolve(kitPath, raw.brandTemplatePath);
14023
+ if (!fs17.existsSync(fullPath)) {
13011
14024
  errors.push({ field: "brandTemplatePath", message: `Brand template not found: ${raw.brandTemplatePath}` });
13012
14025
  }
13013
14026
  }
@@ -13017,8 +14030,8 @@ function validateKitDirectory(kitPath) {
13017
14030
  } else {
13018
14031
  for (const assetPath of frozenAssets) {
13019
14032
  if (typeof assetPath !== "string") continue;
13020
- const fullPath = path19.resolve(kitPath, assetPath);
13021
- if (!fs14.existsSync(fullPath)) {
14033
+ const fullPath = path23.resolve(kitPath, assetPath);
14034
+ if (!fs17.existsSync(fullPath)) {
13022
14035
  errors.push({ field: "frozenAssetPaths", message: `Frozen asset not found: ${assetPath}` });
13023
14036
  }
13024
14037
  }
@@ -13036,8 +14049,8 @@ function validateKitDirectory(kitPath) {
13036
14049
  } else {
13037
14050
  for (const reqPath of requiredPaths) {
13038
14051
  if (typeof reqPath !== "string") continue;
13039
- const fullPath = path19.resolve(kitPath, reqPath);
13040
- if (!fs14.existsSync(fullPath)) {
14052
+ const fullPath = path23.resolve(kitPath, reqPath);
14053
+ if (!fs17.existsSync(fullPath)) {
13041
14054
  errors.push({ field: "outputStandard.requiredPaths", message: `Required output path not found: ${reqPath}` });
13042
14055
  }
13043
14056
  }
@@ -13053,13 +14066,13 @@ function validateKitDirectory(kitPath) {
13053
14066
  errors.push({ field: "bundles[].path", message: "Bundle ref missing 'path' field" });
13054
14067
  continue;
13055
14068
  }
13056
- const bundlePath = path19.resolve(kitPath, ref.path);
13057
- if (!fs14.existsSync(bundlePath)) {
14069
+ const bundlePath = path23.resolve(kitPath, ref.path);
14070
+ if (!fs17.existsSync(bundlePath)) {
13058
14071
  errors.push({ field: "bundles[].path", message: `Bundle manifest not found: ${ref.path}` });
13059
14072
  continue;
13060
14073
  }
13061
14074
  try {
13062
- const bundleRaw = JSON.parse(fs14.readFileSync(bundlePath, "utf8"));
14075
+ const bundleRaw = JSON.parse(fs17.readFileSync(bundlePath, "utf8"));
13063
14076
  const bundleBlock = bundleRaw.bundle;
13064
14077
  if (!bundleBlock || typeof bundleBlock !== "object") {
13065
14078
  errors.push({ field: `bundle(${ref.id})`, message: "Bundle manifest missing 'bundle' block" });
@@ -13119,7 +14132,7 @@ Available: ${available}`
13119
14132
  );
13120
14133
  }
13121
14134
  const catalogEntry = BUNDLED_KIT_CATALOG.find((e) => e.id === resolvedId);
13122
- const assetRoot = path19.resolve(resolveBundledKitAssetsRoot(), catalogEntry.packageDirName);
14135
+ const assetRoot = path23.resolve(resolveBundledKitAssetsRoot(), catalogEntry.packageDirName);
13123
14136
  return loadResolvedBundledKit(assetRoot, catalogEntry);
13124
14137
  }
13125
14138
  function toListItem(resolved) {
@@ -13139,8 +14152,8 @@ function toListItem(resolved) {
13139
14152
  }
13140
14153
  function resolveOutputPaths(resolved, outDir) {
13141
14154
  const outputRoot = resolveRequestedOutputRoot(outDir);
13142
- const folderPath = path19.resolve(outputRoot, resolved.bundleManifest.export.folderName);
13143
- const zipPath = path19.resolve(outputRoot, resolved.bundleManifest.export.zipFileName);
14155
+ const folderPath = path23.resolve(outputRoot, resolved.bundleManifest.export.folderName);
14156
+ const zipPath = path23.resolve(outputRoot, resolved.bundleManifest.export.zipFileName);
13144
14157
  return { outputRoot, folderPath, zipPath };
13145
14158
  }
13146
14159
  function listBundledKits() {
@@ -13256,7 +14269,7 @@ function reportProgress(onProgress, progress) {
13256
14269
  function copyDirectoryWithProgress(sourceRoot, targetRoot, onProgress) {
13257
14270
  const files = listRelativeFiles(sourceRoot);
13258
14271
  const total = Math.max(files.length, 1);
13259
- fs14.mkdirSync(targetRoot, { recursive: true });
14272
+ fs17.mkdirSync(targetRoot, { recursive: true });
13260
14273
  reportProgress(onProgress, {
13261
14274
  phase: "copying",
13262
14275
  completed: 0,
@@ -13265,10 +14278,10 @@ function copyDirectoryWithProgress(sourceRoot, targetRoot, onProgress) {
13265
14278
  detail: "Preparing files"
13266
14279
  });
13267
14280
  files.forEach((relativePath, index51) => {
13268
- const sourcePath = path19.resolve(sourceRoot, relativePath);
13269
- const targetPath = path19.resolve(targetRoot, relativePath);
13270
- fs14.mkdirSync(path19.dirname(targetPath), { recursive: true });
13271
- fs14.copyFileSync(sourcePath, targetPath);
14281
+ const sourcePath = path23.resolve(sourceRoot, relativePath);
14282
+ const targetPath = path23.resolve(targetRoot, relativePath);
14283
+ fs17.mkdirSync(path23.dirname(targetPath), { recursive: true });
14284
+ fs17.copyFileSync(sourcePath, targetPath);
13272
14285
  const completed = index51 + 1;
13273
14286
  const percent = 10 + Math.round(completed / total * 55);
13274
14287
  reportProgress(onProgress, {
@@ -13294,8 +14307,8 @@ function buildZipEntriesWithProgress(sourceRoot, exportFolderName, onProgress) {
13294
14307
  detail: relativePath
13295
14308
  });
13296
14309
  return {
13297
- name: path19.posix.join(exportFolderName, relativePath),
13298
- data: fs14.readFileSync(path19.resolve(sourceRoot, relativePath))
14310
+ name: path23.posix.join(exportFolderName, relativePath),
14311
+ data: fs17.readFileSync(path23.resolve(sourceRoot, relativePath))
13299
14312
  };
13300
14313
  });
13301
14314
  }
@@ -13310,8 +14323,8 @@ function downloadBundledKit(kitId, outDir, options = {}) {
13310
14323
  percent: 0,
13311
14324
  detail: "Resolving export target"
13312
14325
  });
13313
- fs14.mkdirSync(outputPaths.outputRoot, { recursive: true });
13314
- fs14.rmSync(outputPaths.folderPath, { recursive: true, force: true });
14326
+ fs17.mkdirSync(outputPaths.outputRoot, { recursive: true });
14327
+ fs17.rmSync(outputPaths.folderPath, { recursive: true, force: true });
13315
14328
  copyDirectoryWithProgress(resolved.assetRoot, outputPaths.folderPath, onProgress);
13316
14329
  const zipBuffer = buildStoredZip(
13317
14330
  buildZipEntriesWithProgress(outputPaths.folderPath, resolved.bundleManifest.export.folderName, onProgress)
@@ -13321,9 +14334,9 @@ function downloadBundledKit(kitId, outDir, options = {}) {
13321
14334
  completed: 1,
13322
14335
  total: 1,
13323
14336
  percent: 98,
13324
- detail: path19.basename(outputPaths.zipPath)
14337
+ detail: path23.basename(outputPaths.zipPath)
13325
14338
  });
13326
- fs14.writeFileSync(outputPaths.zipPath, zipBuffer);
14339
+ fs17.writeFileSync(outputPaths.zipPath, zipBuffer);
13327
14340
  reportProgress(onProgress, {
13328
14341
  phase: "done",
13329
14342
  completed: 1,
@@ -13338,10 +14351,11 @@ function downloadBundledKit(kitId, outDir, options = {}) {
13338
14351
  }
13339
14352
 
13340
14353
  // src/commands/kit.ts
14354
+ init_banner();
13341
14355
  var TYPE_CONFIG = {
13342
- studio: { color: pc23.cyan, emoji: "\u{1F6E0}\uFE0F", label: "Custom Workspaces" },
13343
- specialized_agents: { color: pc23.magenta, emoji: "\u{1F9E0}", label: "Specialized Agents" },
13344
- ops: { color: pc23.yellow, emoji: "\u2699\uFE0F ", label: "Ops" }
14356
+ studio: { color: pc25.cyan, emoji: "\u{1F6E0}\uFE0F", label: "Custom Workspaces" },
14357
+ specialized_agents: { color: pc25.magenta, emoji: "\u{1F9E0}", label: "Specialized Agents" },
14358
+ ops: { color: pc25.yellow, emoji: "\u2699\uFE0F ", label: "Ops" }
13345
14359
  };
13346
14360
  function displayTypeForFamily(family) {
13347
14361
  if (family === "workflow" || family === "operator") return "specialized_agents";
@@ -13366,16 +14380,16 @@ function displayKitName(name) {
13366
14380
  return name.replace(/^Growthub Agent Worker Kit\s+[—-]\s+/u, "").trim();
13367
14381
  }
13368
14382
  function hr(width = 72) {
13369
- return pc23.dim("\u2500".repeat(width));
14383
+ return pc25.dim("\u2500".repeat(width));
13370
14384
  }
13371
14385
  function box(lines) {
13372
14386
  const padded = lines.map((l) => " " + l);
13373
14387
  const width = Math.max(...padded.map((l) => stripAnsi(l).length)) + 4;
13374
- const top = pc23.dim("\u250C" + "\u2500".repeat(width) + "\u2510");
13375
- const bottom = pc23.dim("\u2514" + "\u2500".repeat(width) + "\u2518");
14388
+ const top = pc25.dim("\u250C" + "\u2500".repeat(width) + "\u2510");
14389
+ const bottom = pc25.dim("\u2514" + "\u2500".repeat(width) + "\u2518");
13376
14390
  const body = padded.map((l) => {
13377
14391
  const pad = width - stripAnsi(l).length;
13378
- return pc23.dim("\u2502") + l + " ".repeat(pad) + pc23.dim("\u2502");
14392
+ return pc25.dim("\u2502") + l + " ".repeat(pad) + pc25.dim("\u2502");
13379
14393
  });
13380
14394
  return [top, ...body, bottom].join("\n");
13381
14395
  }
@@ -13396,7 +14410,7 @@ function renderProgressBar(progress) {
13396
14410
  const filled = Math.max(0, Math.min(width, Math.round(progress.percent / 100 * width)));
13397
14411
  const bar = `${"=".repeat(filled)}${"-".repeat(width - filled)}`;
13398
14412
  const detail = truncate(progress.detail, 48);
13399
- const line = `\r${pc23.cyan("Exporting kit")} ${pc23.dim("[")}${pc23.green(bar)}${pc23.dim("]")} ${String(progress.percent).padStart(3)}% ${pc23.dim(detail)}`;
14413
+ const line = `\r${pc25.cyan("Exporting kit")} ${pc25.dim("[")}${pc25.green(bar)}${pc25.dim("]")} ${String(progress.percent).padStart(3)}% ${pc25.dim(detail)}`;
13400
14414
  process.stdout.write(line);
13401
14415
  if (progress.phase === "done") {
13402
14416
  process.stdout.write("\n");
@@ -13406,36 +14420,39 @@ function printKitCard(item) {
13406
14420
  const badge2 = typeBadge(item.family);
13407
14421
  console.log("");
13408
14422
  console.log(box([
13409
- `${pc23.bold(item.name)} ${pc23.dim("v" + item.version)}`,
13410
- `${badge2} ${pc23.dim(item.id)}`,
14423
+ `${pc25.bold(item.name)} ${pc25.dim("v" + item.version)}`,
14424
+ `${badge2} ${pc25.dim(item.id)}`,
13411
14425
  "",
13412
14426
  truncate(item.description, 62),
13413
14427
  "",
13414
- `${pc23.dim("Brief:")} ${pc23.dim(item.briefType)} ${pc23.dim("Mode:")} ${pc23.dim(item.executionMode)}`
14428
+ `${pc25.dim("Brief:")} ${pc25.dim(item.briefType)} ${pc25.dim("Mode:")} ${pc25.dim(item.executionMode)}`
13415
14429
  ]));
13416
14430
  }
14431
+ function getActionLabel(action) {
14432
+ if (action === "download") return "download";
14433
+ if (action === "inspect") return "inspect";
14434
+ if (action === "copy-id") return "print id";
14435
+ return action;
14436
+ }
13417
14437
  async function confirmKitActions(input) {
13418
14438
  const actionLabels = input.actions.map((action) => {
13419
- if (action === "download") return "download";
13420
- if (action === "inspect") return "inspect";
13421
- if (action === "copy-id") return "print id";
13422
- return action;
14439
+ return getActionLabel(action);
13423
14440
  });
13424
14441
  const summaryLines = [
13425
- pc23.bold("Selected kits"),
14442
+ pc25.bold("Selected kits"),
13426
14443
  ...input.kits.map((kit) => `${typeBadge(kit.family)} ${displayKitName(kit.name)}`),
13427
14444
  "",
13428
- pc23.bold("Selected actions"),
14445
+ pc25.bold("Selected actions"),
13429
14446
  actionLabels.join(", ")
13430
14447
  ];
13431
14448
  console.log("");
13432
14449
  console.log(box(summaryLines));
13433
- const confirmed = await p16.confirm({
14450
+ const confirmed = await p17.confirm({
13434
14451
  message: "Continue with these worker kit actions?",
13435
14452
  initialValue: false
13436
14453
  });
13437
- if (p16.isCancel(confirmed)) {
13438
- p16.cancel("Cancelled.");
14454
+ if (p17.isCancel(confirmed)) {
14455
+ p17.cancel("Cancelled.");
13439
14456
  process.exit(0);
13440
14457
  }
13441
14458
  return Boolean(confirmed);
@@ -13450,38 +14467,39 @@ function printGroupedList(kits) {
13450
14467
  const totalTypes = types.length;
13451
14468
  console.log("");
13452
14469
  console.log(
13453
- pc23.bold("Growthub Agent Worker Kits") + pc23.dim(` ${kits.length} kit${kits.length !== 1 ? "s" : ""} \xB7 ${totalTypes} type${totalTypes !== 1 ? "s" : ""}`)
14470
+ pc25.bold("Growthub Agent Worker Kits") + pc25.dim(` ${kits.length} kit${kits.length !== 1 ? "s" : ""} \xB7 ${totalTypes} type${totalTypes !== 1 ? "s" : ""}`)
13454
14471
  );
13455
14472
  console.log(hr());
13456
14473
  for (const type of types) {
13457
14474
  const groupKits = byType[type];
13458
14475
  const header = typeBadge(type);
13459
14476
  console.log(`
13460
- ${header} ${pc23.dim("(" + groupKits.length + ")")}`);
14477
+ ${header} ${pc25.dim("(" + groupKits.length + ")")}`);
13461
14478
  for (const kit of groupKits) {
13462
- console.log(` ${typeColor(kit.family, pc23.bold(kit.id))} ${pc23.dim("v" + kit.version)}`);
13463
- console.log(` ${pc23.dim(truncate(kit.description, 62))}`);
13464
- console.log(` ${pc23.dim("\u2192")} ${pc23.cyan("growthub kit download " + kit.id)}`);
14479
+ console.log(` ${typeColor(kit.family, pc25.bold(kit.id))} ${pc25.dim("v" + kit.version)}`);
14480
+ console.log(` ${pc25.dim(truncate(kit.description, 62))}`);
14481
+ console.log(` ${pc25.dim("\u2192")} ${pc25.cyan("growthub kit download " + kit.id)}`);
13465
14482
  console.log("");
13466
14483
  }
13467
14484
  }
13468
14485
  console.log(hr());
13469
- console.log(pc23.dim(" growthub kit download <id> \xB7 growthub kit inspect <id> \xB7 growthub kit families"));
14486
+ console.log(pc25.dim(" growthub kit download <id> \xB7 growthub kit inspect <id> \xB7 growthub kit families"));
13470
14487
  console.log("");
13471
14488
  }
13472
14489
  async function runInteractivePicker(opts) {
13473
- p16.intro(pc23.bold("Growthub Agent Worker Kits"));
14490
+ printPaperclipCliBanner();
14491
+ p17.intro(pc25.bold("Growthub Agent Worker Kits"));
13474
14492
  let kits;
13475
14493
  try {
13476
14494
  kits = listBundledKits();
13477
14495
  } catch (err) {
13478
- p16.log.error("Failed to load kits: " + err.message);
14496
+ p17.log.error("Failed to load kits: " + err.message);
13479
14497
  process.exit(1);
13480
14498
  }
13481
14499
  const familiesAvailable = [...new Set(kits.map((k) => k.family))].sort();
13482
14500
  const typeOptions = Array.from(new Set(familiesAvailable.map((family) => displayTypeForFamily(family))));
13483
14501
  while (true) {
13484
- const typeChoice = await p16.select({
14502
+ const typeChoice = await p17.select({
13485
14503
  message: "Filter by type",
13486
14504
  options: [
13487
14505
  { value: "all", label: "All Types" },
@@ -13495,120 +14513,98 @@ async function runInteractivePicker(opts) {
13495
14513
  ...opts.allowBackToHub ? [{ value: "__back_to_hub", label: "\u2190 Back to main menu" }] : []
13496
14514
  ]
13497
14515
  });
13498
- if (p16.isCancel(typeChoice)) {
13499
- p16.cancel("Cancelled.");
14516
+ if (p17.isCancel(typeChoice)) {
14517
+ p17.cancel("Cancelled.");
13500
14518
  process.exit(0);
13501
14519
  }
13502
14520
  if (typeChoice === "__back_to_hub") return "back";
13503
14521
  const filtered = typeChoice === "all" ? kits : kits.filter((k) => displayTypeForFamily(k.family) === typeChoice);
13504
14522
  const showTypeBadgeInKitChoices = typeChoice === "all";
13505
14523
  if (filtered.length === 0) {
13506
- p16.note("No kits are available for that type yet.", "Nothing found");
14524
+ p17.note("No kits are available for that type yet.", "Nothing found");
13507
14525
  continue;
13508
14526
  }
13509
14527
  while (true) {
13510
- const kitChoice = await p16.select({
14528
+ const kitChoice = await p17.select({
13511
14529
  message: "Select kit",
13512
14530
  options: [
13513
14531
  ...filtered.map((k) => ({
13514
14532
  value: k.id,
13515
- label: (showTypeBadgeInKitChoices ? typeBadge(k.family) + " " : "") + pc23.bold(displayKitName(k.name)) + " " + pc23.dim("v" + k.version),
14533
+ label: (showTypeBadgeInKitChoices ? typeBadge(k.family) + " " : "") + pc25.bold(displayKitName(k.name)) + " " + pc25.dim("v" + k.version),
13516
14534
  hint: truncate(k.description, 55)
13517
14535
  })),
13518
14536
  { value: "__back_to_type", label: "\u2190 Back to type filter" }
13519
14537
  ]
13520
14538
  });
13521
- if (p16.isCancel(kitChoice)) {
13522
- p16.cancel("Cancelled.");
14539
+ if (p17.isCancel(kitChoice)) {
14540
+ p17.cancel("Cancelled.");
13523
14541
  process.exit(0);
13524
14542
  }
13525
14543
  if (kitChoice === "__back_to_type") break;
13526
14544
  const selected = filtered.find((kit) => kit.id === kitChoice);
13527
14545
  if (!selected) {
13528
- p16.cancel("Selected kit was not found.");
14546
+ p17.cancel("Selected kit was not found.");
13529
14547
  process.exit(1);
13530
14548
  }
13531
14549
  printKitCard(selected);
13532
- const nextStep = await p16.select({
14550
+ const nextStep = await p17.select({
13533
14551
  message: "Next step",
13534
14552
  options: [
13535
14553
  { value: "actions", label: "Choose action(s)" },
13536
14554
  { value: "back_to_kits", label: "\u2190 Back to kit list" }
13537
14555
  ]
13538
14556
  });
13539
- if (p16.isCancel(nextStep)) {
13540
- p16.cancel("Cancelled.");
14557
+ if (p17.isCancel(nextStep)) {
14558
+ p17.cancel("Cancelled.");
13541
14559
  process.exit(0);
13542
14560
  }
13543
14561
  if (nextStep === "back_to_kits") continue;
13544
14562
  while (true) {
13545
- const actionOptions = [
13546
- { value: "download", label: "\u2B07\uFE0F Download kit", hint: "growthub kit download <id>" },
13547
- { value: "inspect", label: "\u{1F50D} Inspect manifest", hint: "growthub kit inspect <id>" },
13548
- { value: "copy-id", label: "\u{1F4CB} Print ID to stdout", hint: "echo <kit-id>" }
13549
- ];
13550
- const actions = await p16.multiselect({
14563
+ const action = await p17.select({
13551
14564
  message: "What would you like to do?",
13552
- options: actionOptions,
13553
- required: false
14565
+ options: [
14566
+ { value: "download", label: "\u2B07\uFE0F Download kit", hint: "growthub kit download <id>" },
14567
+ { value: "inspect", label: "\u{1F50D} Inspect manifest", hint: "growthub kit inspect <id>" },
14568
+ { value: "copy-id", label: "\u{1F4CB} Print ID to stdout", hint: "echo <kit-id>" },
14569
+ { value: "back_to_kits", label: "\u2190 Back to kit list" }
14570
+ ]
13554
14571
  });
13555
- if (p16.isCancel(actions)) {
13556
- p16.cancel("Cancelled.");
14572
+ if (p17.isCancel(action)) {
14573
+ p17.cancel("Cancelled.");
13557
14574
  process.exit(0);
13558
14575
  }
13559
- const selectedActions = actions;
13560
- if (selectedActions.length === 0) {
13561
- const emptyActionChoice = await p16.select({
13562
- message: "No actions selected",
13563
- options: [
13564
- { value: "retry", label: "Choose action(s)" },
13565
- { value: "back_to_kits", label: "\u2190 Back to kit list" }
13566
- ]
13567
- });
13568
- if (p16.isCancel(emptyActionChoice)) {
13569
- p16.cancel("Cancelled.");
13570
- process.exit(0);
13571
- }
13572
- if (emptyActionChoice === "back_to_kits") break;
13573
- continue;
13574
- }
14576
+ if (action === "back_to_kits") break;
13575
14577
  const confirmed = await confirmKitActions({
13576
14578
  kits: [selected],
13577
- actions: selectedActions
14579
+ actions: [action]
13578
14580
  });
13579
14581
  if (!confirmed) {
13580
- const reviewChoice = await p16.select({
14582
+ const reviewChoice = await p17.select({
13581
14583
  message: "Review selection",
13582
14584
  options: [
13583
- { value: "actions", label: "Choose action(s) again" },
14585
+ { value: "actions", label: `Choose ${getActionLabel(action)} again` },
13584
14586
  { value: "back_to_kits", label: "\u2190 Back to kit list" }
13585
14587
  ]
13586
14588
  });
13587
- if (p16.isCancel(reviewChoice)) {
13588
- p16.cancel("Cancelled.");
14589
+ if (p17.isCancel(reviewChoice)) {
14590
+ p17.cancel("Cancelled.");
13589
14591
  process.exit(0);
13590
14592
  }
13591
14593
  if (reviewChoice === "back_to_kits") break;
13592
14594
  continue;
13593
14595
  }
13594
- for (const action of selectedActions) {
13595
- if (action === "copy-id") {
13596
- console.log(selected.id);
13597
- continue;
13598
- }
13599
- if (action === "inspect") {
13600
- runInspect(selected.id, opts.out);
13601
- continue;
13602
- }
13603
- if (action === "download") {
13604
- await runDownload(selected.id, opts);
13605
- }
14596
+ if (action === "copy-id") {
14597
+ console.log(selected.id);
14598
+ p17.outro(pc25.dim("Kit ID printed above."));
14599
+ return "done";
13606
14600
  }
13607
- if (selectedActions.includes("copy-id")) {
13608
- p16.outro(pc23.dim("Kit ID printed above."));
14601
+ if (action === "inspect") {
14602
+ runInspect(selected.id, opts.out);
14603
+ p17.outro(pc25.dim("Done."));
13609
14604
  return "done";
13610
14605
  }
13611
- p16.outro(pc23.dim("Done."));
14606
+ await runDownload(selected.id, opts);
14607
+ p17.outro(pc25.green("Kit exported successfully."));
13612
14608
  return "done";
13613
14609
  }
13614
14610
  }
@@ -13617,52 +14613,55 @@ async function runInteractivePicker(opts) {
13617
14613
  async function runDownload(kitId, opts) {
13618
14614
  const resolvedId = fuzzyResolveKitId(kitId);
13619
14615
  if (!resolvedId) {
13620
- console.error(pc23.red("Unknown kit '" + kitId + "'.") + pc23.dim(" Run `growthub kit list` to browse."));
14616
+ console.error(pc25.red("Unknown kit '" + kitId + "'.") + pc25.dim(" Run `growthub kit list` to browse."));
13621
14617
  process.exit(1);
13622
14618
  }
13623
14619
  if (resolvedId !== kitId) {
13624
- console.log(pc23.dim("Resolved '" + kitId + "' \u2192 " + resolvedId));
14620
+ console.log(pc25.dim("Resolved '" + kitId + "' \u2192 " + resolvedId));
13625
14621
  }
13626
14622
  const kits = listBundledKits();
13627
14623
  const item = kits.find((k) => k.id === resolvedId);
13628
14624
  printKitCard(item);
13629
14625
  if (!opts.yes) {
13630
- const confirmed = await p16.confirm({ message: "Download " + pc23.bold(displayKitName(item.name)) + "?" });
13631
- if (p16.isCancel(confirmed) || !confirmed) {
13632
- p16.cancel("Cancelled.");
14626
+ const confirmed = await p17.confirm({ message: "Download " + pc25.bold(displayKitName(item.name)) + "?" });
14627
+ if (p17.isCancel(confirmed) || !confirmed) {
14628
+ p17.cancel("Cancelled.");
13633
14629
  process.exit(0);
13634
14630
  }
13635
14631
  }
13636
14632
  const result = downloadBundledKit(resolvedId, opts.out, {
13637
14633
  onProgress: renderProgressBar
13638
14634
  });
14635
+ console.log("");
14636
+ console.log(pc25.green(pc25.bold("Kit exported successfully.")));
14637
+ console.log("");
13639
14638
  const nextSteps = [
13640
- pc23.bold("Next steps"),
14639
+ pc25.bold("Next steps"),
13641
14640
  "",
13642
- pc23.dim("1.") + " Point Working Directory at:",
13643
- " " + pc23.cyan(result.folderPath),
14641
+ pc25.dim("1.") + " Point Working Directory at:",
14642
+ " " + pc25.cyan(result.folderPath),
13644
14643
  "",
13645
- pc23.dim("2.") + " " + pc23.cyan("cp .env.example .env") + " \u2192 add your API key",
13646
- pc23.dim("3.") + " " + pc23.cyan("bash setup/clone-fork.sh") + " \u2192 boot local studio",
13647
- pc23.dim("4.") + " Open Growthub local \u2014 the agent loads automatically",
14644
+ pc25.dim("2.") + " " + pc25.cyan("cp .env.example .env") + " \u2192 add your API key",
14645
+ pc25.dim("3.") + " " + pc25.cyan("bash setup/clone-fork.sh") + " \u2192 boot local studio",
14646
+ pc25.dim("4.") + " Open Growthub local \u2014 the agent loads automatically",
13648
14647
  "",
13649
- pc23.dim("Docs: QUICKSTART.md \xB7 validation-checklist.md")
14648
+ pc25.dim("Docs: QUICKSTART.md \xB7 validation-checklist.md")
13650
14649
  ];
13651
14650
  console.log("");
13652
14651
  console.log(box(nextSteps));
13653
14652
  console.log("");
13654
- console.log(pc23.bold("Open folder: ") + folderOpenLabel(result.folderPath));
13655
- console.log(pc23.dim("Folder: ") + result.folderPath);
14653
+ console.log(pc25.bold("Open folder: ") + folderOpenLabel(result.folderPath));
14654
+ console.log(pc25.dim("Folder: ") + result.folderPath);
13656
14655
  console.log("");
13657
- console.log(pc23.dim("Zip: ") + result.zipPath);
14656
+ console.log(pc25.dim("Zip: ") + result.zipPath);
13658
14657
  console.log("");
13659
14658
  }
13660
14659
  function runInspect(kitId, outDir) {
13661
14660
  const info = inspectBundledKit(kitId, outDir);
13662
- const kv = (label, value) => console.log(" " + pc23.bold(label.padEnd(24)) + " " + value);
14661
+ const kv = (label, value) => console.log(" " + pc25.bold(label.padEnd(24)) + " " + value);
13663
14662
  console.log("");
13664
- console.log(pc23.bold("Kit: " + info.id) + pc23.dim(" v" + info.version));
13665
- console.log(typeBadge(info.family) + pc23.dim(" schema v" + info.schemaVersion));
14663
+ console.log(pc25.bold("Kit: " + info.id) + pc25.dim(" v" + info.version));
14664
+ console.log(typeBadge(info.family) + pc25.dim(" schema v" + info.schemaVersion));
13666
14665
  console.log(hr());
13667
14666
  kv("Name:", info.name);
13668
14667
  kv("Description:", truncate(info.description, 55));
@@ -13678,8 +14677,8 @@ function runInspect(kitId, outDir) {
13678
14677
  kv("Compatibility:", JSON.stringify(info.compatibility));
13679
14678
  }
13680
14679
  console.log(hr());
13681
- console.log(pc23.bold(" Required Paths:"));
13682
- for (const rp of info.requiredPaths) console.log(" " + pc23.dim("\xB7") + " " + rp);
14680
+ console.log(pc25.bold(" Required Paths:"));
14681
+ for (const rp of info.requiredPaths) console.log(" " + pc25.dim("\xB7") + " " + rp);
13683
14682
  console.log("");
13684
14683
  }
13685
14684
  function registerKitCommands(program2) {
@@ -13709,8 +14708,8 @@ Examples:
13709
14708
  const wanted = opts.family.split(",").map((f) => f.trim().toLowerCase());
13710
14709
  kits = kits.filter((k) => wanted.includes(k.family));
13711
14710
  if (kits.length === 0) {
13712
- console.error(pc23.yellow("No kits found for family: " + opts.family));
13713
- console.error(pc23.dim("Valid families: studio, workflow, operator, ops"));
14711
+ console.error(pc25.yellow("No kits found for family: " + opts.family));
14712
+ console.error(pc25.dim("Valid families: studio, workflow, operator, ops"));
13714
14713
  process.exitCode = 1;
13715
14714
  return;
13716
14715
  }
@@ -13728,7 +14727,7 @@ Examples:
13728
14727
  `).action((kitId, opts) => {
13729
14728
  const resolvedId = fuzzyResolveKitId(kitId);
13730
14729
  if (!resolvedId) {
13731
- console.error(pc23.red("Unknown kit '" + kitId + "'.") + pc23.dim(" Run `growthub kit list` to browse."));
14730
+ console.error(pc25.red("Unknown kit '" + kitId + "'.") + pc25.dim(" Run `growthub kit list` to browse."));
13732
14731
  process.exitCode = 1;
13733
14732
  return;
13734
14733
  }
@@ -13752,7 +14751,7 @@ Examples:
13752
14751
  }
13753
14752
  const resolvedId = fuzzyResolveKitId(kitId);
13754
14753
  if (!resolvedId) {
13755
- console.error(pc23.red("Unknown kit '" + kitId + "'.") + pc23.dim(" Run `growthub kit list` to browse."));
14754
+ console.error(pc25.red("Unknown kit '" + kitId + "'.") + pc25.dim(" Run `growthub kit list` to browse."));
13756
14755
  process.exitCode = 1;
13757
14756
  return;
13758
14757
  }
@@ -13761,14 +14760,14 @@ Examples:
13761
14760
  onProgress: renderProgressBar
13762
14761
  });
13763
14762
  console.log("");
13764
- console.log(pc23.bold("Exported folder:"), pc23.cyan(result.folderPath));
13765
- console.log(pc23.bold("Open folder: "), folderOpenLabel(result.folderPath));
13766
- console.log(pc23.bold("Zip: "), pc23.dim(result.zipPath));
14763
+ console.log(pc25.bold("Exported folder:"), pc25.cyan(result.folderPath));
14764
+ console.log(pc25.bold("Open folder: "), folderOpenLabel(result.folderPath));
14765
+ console.log(pc25.bold("Zip: "), pc25.dim(result.zipPath));
13767
14766
  console.log("");
13768
- console.log(pc23.bold("Next steps:"));
13769
- console.log(" 1. Point Working Directory at: " + pc23.cyan(result.folderPath));
13770
- console.log(" 2. " + pc23.cyan("cp .env.example .env") + " \u2192 add your API key");
13771
- console.log(" 3. " + pc23.cyan("bash setup/clone-fork.sh") + " \u2192 boot local studio");
14767
+ console.log(pc25.bold("Next steps:"));
14768
+ console.log(" 1. Point Working Directory at: " + pc25.cyan(result.folderPath));
14769
+ console.log(" 2. " + pc25.cyan("cp .env.example .env") + " \u2192 add your API key");
14770
+ console.log(" 3. " + pc25.cyan("bash setup/clone-fork.sh") + " \u2192 boot local studio");
13772
14771
  console.log(" 4. Open Growthub local \u2014 the agent loads automatically");
13773
14772
  console.log("");
13774
14773
  return;
@@ -13778,7 +14777,7 @@ Examples:
13778
14777
  kit.command("path").description("Resolve the expected export folder path without exporting").argument("<kit-id>", "Kit id or fuzzy slug").option("--out <path>", "Override the export root").action((kitId, opts) => {
13779
14778
  const resolvedId = fuzzyResolveKitId(kitId);
13780
14779
  if (!resolvedId) {
13781
- console.error(pc23.red("Unknown kit '" + kitId + "'."));
14780
+ console.error(pc25.red("Unknown kit '" + kitId + "'."));
13782
14781
  process.exitCode = 1;
13783
14782
  return;
13784
14783
  }
@@ -13789,23 +14788,23 @@ Examples:
13789
14788
  $ growthub kit validate ./my-kit
13790
14789
  $ growthub kit validate ~/kits/growthub-open-higgsfield-studio-v1
13791
14790
  `).action((kitPath) => {
13792
- const resolvedPath = path20.resolve(kitPath);
14791
+ const resolvedPath = path24.resolve(kitPath);
13793
14792
  const result = validateKitDirectory(resolvedPath);
13794
14793
  console.log("");
13795
- console.log(pc23.bold("Kit: " + result.kitId) + pc23.dim(" schema v" + result.schemaVersion));
14794
+ console.log(pc25.bold("Kit: " + result.kitId) + pc25.dim(" schema v" + result.schemaVersion));
13796
14795
  console.log(hr());
13797
14796
  for (const w of result.warnings) {
13798
- console.log(pc23.yellow(" WARN " + w.field + ": " + w.message));
14797
+ console.log(pc25.yellow(" WARN " + w.field + ": " + w.message));
13799
14798
  }
13800
14799
  for (const e of result.errors) {
13801
- console.log(pc23.red(" ERROR " + e.field + ": " + e.message));
14800
+ console.log(pc25.red(" ERROR " + e.field + ": " + e.message));
13802
14801
  }
13803
14802
  if (result.errors.length > 0) {
13804
14803
  console.log("");
13805
- console.log(pc23.red(pc23.bold(" Result: INVALID")) + pc23.dim(" (" + result.errors.length + " error" + (result.errors.length !== 1 ? "s" : "") + ")"));
14804
+ console.log(pc25.red(pc25.bold(" Result: INVALID")) + pc25.dim(" (" + result.errors.length + " error" + (result.errors.length !== 1 ? "s" : "") + ")"));
13806
14805
  process.exitCode = 1;
13807
14806
  } else {
13808
- console.log(pc23.green(pc23.bold(" Result: VALID")));
14807
+ console.log(pc25.green(pc25.bold(" Result: VALID")));
13809
14808
  }
13810
14809
  console.log("");
13811
14810
  });
@@ -13817,29 +14816,29 @@ Examples:
13817
14816
  { family: "ops", tagline: "Infrastructure / toolchain operator (provider optional)", surfaces: "local-fork (primary)", example: "(coming soon)" }
13818
14817
  ];
13819
14818
  console.log("");
13820
- console.log(pc23.bold("Kit Family Taxonomy"));
14819
+ console.log(pc25.bold("Kit Family Taxonomy"));
13821
14820
  console.log(hr());
13822
14821
  for (const def of defs) {
13823
- console.log("\n " + familyBadge(def.family));
13824
- console.log(" " + pc23.dim(def.tagline));
13825
- console.log(" " + pc23.dim("Surfaces: ") + pc23.dim(def.surfaces));
13826
- console.log(" " + pc23.dim("Example: ") + pc23.cyan(def.example));
14822
+ console.log("\n " + typeBadge(def.family));
14823
+ console.log(" " + pc25.dim(def.tagline));
14824
+ console.log(" " + pc25.dim("Surfaces: ") + pc25.dim(def.surfaces));
14825
+ console.log(" " + pc25.dim("Example: ") + pc25.cyan(def.example));
13827
14826
  }
13828
14827
  console.log("");
13829
14828
  console.log(hr());
13830
- console.log(pc23.dim(" growthub kit list --family <family> to filter by internal family"));
14829
+ console.log(pc25.dim(" growthub kit list --family <family> to filter by internal family"));
13831
14830
  console.log("");
13832
14831
  });
13833
14832
  }
13834
14833
 
13835
14834
  // src/commands/template.ts
13836
- import path22 from "node:path";
13837
- import * as p17 from "@clack/prompts";
13838
- import pc24 from "picocolors";
14835
+ import path26 from "node:path";
14836
+ import * as p18 from "@clack/prompts";
14837
+ import pc26 from "picocolors";
13839
14838
 
13840
14839
  // src/templates/service.ts
13841
- import fs15 from "node:fs";
13842
- import path21 from "node:path";
14840
+ import fs18 from "node:fs";
14841
+ import path25 from "node:path";
13843
14842
  import { fileURLToPath as fileURLToPath5 } from "node:url";
13844
14843
 
13845
14844
  // src/templates/catalog.ts
@@ -14086,12 +15085,12 @@ var TEMPLATE_CATALOG = [
14086
15085
 
14087
15086
  // src/templates/service.ts
14088
15087
  function resolveSharedTemplatesRoot() {
14089
- const moduleDir = path21.dirname(fileURLToPath5(import.meta.url));
15088
+ const moduleDir = path25.dirname(fileURLToPath5(import.meta.url));
14090
15089
  for (const candidate of [
14091
- path21.resolve(moduleDir, "../../assets/shared-templates"),
14092
- path21.resolve(moduleDir, "../assets/shared-templates")
15090
+ path25.resolve(moduleDir, "../../assets/shared-templates"),
15091
+ path25.resolve(moduleDir, "../assets/shared-templates")
14093
15092
  ]) {
14094
- if (fs15.existsSync(candidate)) return candidate;
15093
+ if (fs18.existsSync(candidate)) return candidate;
14095
15094
  }
14096
15095
  throw new Error("Shared template assets not found at cli/assets/shared-templates/");
14097
15096
  }
@@ -14126,15 +15125,15 @@ function getArtifact(slugOrId) {
14126
15125
  const artifact = resolveSlug(slugOrId);
14127
15126
  if (!artifact) throw new Error(`Unknown template '${slugOrId}'. Run 'growthub template list' to browse.`);
14128
15127
  const root = resolveSharedTemplatesRoot();
14129
- const absolutePath = path21.resolve(root, artifact.path);
14130
- if (!fs15.existsSync(absolutePath)) throw new Error(`Template file missing: ${absolutePath}`);
14131
- return { artifact, content: fs15.readFileSync(absolutePath, "utf8"), absolutePath };
15128
+ const absolutePath = path25.resolve(root, artifact.path);
15129
+ if (!fs18.existsSync(absolutePath)) throw new Error(`Template file missing: ${absolutePath}`);
15130
+ return { artifact, content: fs18.readFileSync(absolutePath, "utf8"), absolutePath };
14132
15131
  }
14133
15132
  function copyArtifact(slugOrId, destDir) {
14134
15133
  const resolved = getArtifact(slugOrId);
14135
- fs15.mkdirSync(destDir, { recursive: true });
14136
- const destPath = path21.resolve(destDir, path21.basename(resolved.absolutePath));
14137
- fs15.copyFileSync(resolved.absolutePath, destPath);
15134
+ fs18.mkdirSync(destDir, { recursive: true });
15135
+ const destPath = path25.resolve(destDir, path25.basename(resolved.absolutePath));
15136
+ fs18.copyFileSync(resolved.absolutePath, destPath);
14138
15137
  return destPath;
14139
15138
  }
14140
15139
  var GROUP_ORDER = ["ad-formats", "scene-modules/hooks", "scene-modules/body", "scene-modules/cta"];
@@ -14184,7 +15183,7 @@ function stripAnsi2(s) {
14184
15183
  return s.replace(/\x1B\[[0-9;]*m/g, "");
14185
15184
  }
14186
15185
  function hr2(w = 72) {
14187
- return pc24.dim("\u2500".repeat(w));
15186
+ return pc26.dim("\u2500".repeat(w));
14188
15187
  }
14189
15188
  function truncate2(s, max) {
14190
15189
  return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
@@ -14192,32 +15191,32 @@ function truncate2(s, max) {
14192
15191
  function box2(lines) {
14193
15192
  const padded = lines.map((l) => " " + l);
14194
15193
  const width = Math.max(...padded.map((l) => stripAnsi2(l).length)) + 4;
14195
- const top = pc24.dim("\u250C" + "\u2500".repeat(width) + "\u2510");
14196
- const bottom = pc24.dim("\u2514" + "\u2500".repeat(width) + "\u2518");
14197
- const body = padded.map((l) => pc24.dim("\u2502") + l + " ".repeat(width - stripAnsi2(l).length) + pc24.dim("\u2502"));
15194
+ const top = pc26.dim("\u250C" + "\u2500".repeat(width) + "\u2510");
15195
+ const bottom = pc26.dim("\u2514" + "\u2500".repeat(width) + "\u2518");
15196
+ const body = padded.map((l) => pc26.dim("\u2502") + l + " ".repeat(width - stripAnsi2(l).length) + pc26.dim("\u2502"));
14198
15197
  return [top, ...body, bottom].join("\n");
14199
15198
  }
14200
15199
  function badge(a) {
14201
- if (a.type === "ad-format") return pc24.cyan("\u{1F3AC} Ad Format");
15200
+ if (a.type === "ad-format") return pc26.cyan("\u{1F3AC} Ad Format");
14202
15201
  if (a.type === "scene-module") {
14203
- if (a.subtype === "hook") return pc24.yellow("\u{1FA9D} Hook");
14204
- if (a.subtype === "body") return pc24.blue("\u{1F9E9} Body");
14205
- if (a.subtype === "cta") return pc24.green("\u{1F3AF} CTA");
15202
+ if (a.subtype === "hook") return pc26.yellow("\u{1FA9D} Hook");
15203
+ if (a.subtype === "body") return pc26.blue("\u{1F9E9} Body");
15204
+ if (a.subtype === "cta") return pc26.green("\u{1F3AF} CTA");
14206
15205
  }
14207
- return pc24.magenta("\u{1F9E9} Module");
15206
+ return pc26.magenta("\u{1F9E9} Module");
14208
15207
  }
14209
15208
  function printCard(a) {
14210
- const compat = a.compatibleFormats.length ? pc24.dim("Works with: ") + a.compatibleFormats.map((f) => pc24.cyan(f)).join(", ") : pc24.dim("Works with: any format");
15209
+ const compat = a.compatibleFormats.length ? pc26.dim("Works with: ") + a.compatibleFormats.map((f) => pc26.cyan(f)).join(", ") : pc26.dim("Works with: any format");
14211
15210
  const rows = [
14212
- pc24.bold(a.name),
14213
- `${badge(a)} ${pc24.dim(a.id)}`,
15211
+ pc26.bold(a.name),
15212
+ `${badge(a)} ${pc26.dim(a.id)}`,
14214
15213
  "",
14215
15214
  truncate2(a.category, 62),
14216
15215
  "",
14217
15216
  compat
14218
15217
  ];
14219
15218
  if (a.type === "ad-format" && a.scenes != null) {
14220
- rows.push(pc24.dim("Scenes: ") + a.scenes + (a.hookVariations ? pc24.dim(" \xB7 Hook variations: ") + a.hookVariations : ""));
15219
+ rows.push(pc26.dim("Scenes: ") + a.scenes + (a.hookVariations ? pc26.dim(" \xB7 Hook variations: ") + a.hookVariations : ""));
14221
15220
  }
14222
15221
  console.log("");
14223
15222
  console.log(box2(rows));
@@ -14225,32 +15224,32 @@ function printCard(a) {
14225
15224
  function printSummary2(filter) {
14226
15225
  const artifacts = listArtifacts(filter);
14227
15226
  if (!artifacts.length) {
14228
- console.log(pc24.yellow("No templates matched. Try: growthub template list"));
15227
+ console.log(pc26.yellow("No templates matched. Try: growthub template list"));
14229
15228
  return;
14230
15229
  }
14231
15230
  const stats = getCatalogStats();
14232
15231
  const groups = groupArtifacts(artifacts);
14233
15232
  console.log("");
14234
- console.log(pc24.bold("Growthub Shared Template Library") + pc24.dim(` ${artifacts.length} of ${stats.total} artifacts`));
14235
- console.log(pc24.dim(" " + Object.entries(stats.byFamily).map(([f, n]) => `${f} (${n})`).join(" \xB7 ")));
15233
+ console.log(pc26.bold("Growthub Shared Template Library") + pc26.dim(` ${artifacts.length} of ${stats.total} artifacts`));
15234
+ console.log(pc26.dim(" " + Object.entries(stats.byFamily).map(([f, n]) => `${f} (${n})`).join(" \xB7 ")));
14236
15235
  console.log(hr2());
14237
15236
  for (const g of groups) {
14238
15237
  console.log(`
14239
- ${pc24.bold(g.label)} ${pc24.dim("(" + g.count + ")")}`);
14240
- console.log(pc24.dim(" " + g.description));
15238
+ ${pc26.bold(g.label)} ${pc26.dim("(" + g.count + ")")}`);
15239
+ console.log(pc26.dim(" " + g.description));
14241
15240
  console.log("");
14242
15241
  for (const a of g.artifacts) {
14243
- const compat = a.compatibleFormats.length ? pc24.dim(" \xB7 " + a.compatibleFormats.join(", ")) : "";
14244
- console.log(` ${pc24.cyan(pc24.bold(a.name))}${compat}`);
14245
- console.log(` ${pc24.dim("growthub template get " + a.slug)}`);
15242
+ const compat = a.compatibleFormats.length ? pc26.dim(" \xB7 " + a.compatibleFormats.join(", ")) : "";
15243
+ console.log(` ${pc26.cyan(pc26.bold(a.name))}${compat}`);
15244
+ console.log(` ${pc26.dim("growthub template get " + a.slug)}`);
14246
15245
  console.log("");
14247
15246
  }
14248
15247
  }
14249
15248
  console.log(hr2());
14250
- console.log(pc24.dim(" growthub template get <slug>"));
14251
- console.log(pc24.dim(" growthub template list --type ad-formats"));
14252
- console.log(pc24.dim(" growthub template list --type scene-modules --subtype hooks"));
14253
- console.log(pc24.dim(" growthub template (interactive picker)"));
15249
+ console.log(pc26.dim(" growthub template get <slug>"));
15250
+ console.log(pc26.dim(" growthub template list --type ad-formats"));
15251
+ console.log(pc26.dim(" growthub template list --type scene-modules --subtype hooks"));
15252
+ console.log(pc26.dim(" growthub template (interactive picker)"));
14254
15253
  console.log("");
14255
15254
  }
14256
15255
  var TEMPLATE_FAMILY_META = {
@@ -14276,16 +15275,16 @@ var TEMPLATE_FAMILY_META = {
14276
15275
  }
14277
15276
  };
14278
15277
  async function runTemplatePicker(opts) {
14279
- p17.intro(pc24.bold("Growthub Shared Template Library"));
15278
+ p18.intro(pc26.bold("Growthub Shared Template Library"));
14280
15279
  let artifacts;
14281
15280
  try {
14282
15281
  artifacts = listArtifacts();
14283
15282
  } catch (err) {
14284
- p17.log.error(err.message);
15283
+ p18.log.error(err.message);
14285
15284
  process.exit(1);
14286
15285
  }
14287
15286
  const families = [...new Set(artifacts.map((artifact) => artifact.family))];
14288
- const familyChoice = await p17.select({
15287
+ const familyChoice = await p18.select({
14289
15288
  message: "What template type do you want to browse?",
14290
15289
  options: [
14291
15290
  ...families.map((family) => {
@@ -14304,14 +15303,14 @@ async function runTemplatePicker(opts) {
14304
15303
  ...opts?.allowBackToHub ? [{ value: "__back_to_hub", label: "\u2190 Back to main menu" }] : []
14305
15304
  ]
14306
15305
  });
14307
- if (p17.isCancel(familyChoice)) {
14308
- p17.cancel("Cancelled.");
15306
+ if (p18.isCancel(familyChoice)) {
15307
+ p18.cancel("Cancelled.");
14309
15308
  process.exit(0);
14310
15309
  }
14311
15310
  if (familyChoice === "__back_to_hub") return "back";
14312
15311
  const filteredArtifacts = artifacts.filter((artifact) => artifact.family === familyChoice);
14313
15312
  const groups = groupArtifacts(filteredArtifacts);
14314
- const groupChoice = await p17.select({
15313
+ const groupChoice = await p18.select({
14315
15314
  message: "What kind of template?",
14316
15315
  options: groups.map((g) => ({
14317
15316
  value: g.key,
@@ -14319,26 +15318,26 @@ async function runTemplatePicker(opts) {
14319
15318
  hint: `${g.count} available \xB7 ${g.description}`
14320
15319
  }))
14321
15320
  });
14322
- if (p17.isCancel(groupChoice)) {
14323
- p17.cancel("Cancelled.");
15321
+ if (p18.isCancel(groupChoice)) {
15322
+ p18.cancel("Cancelled.");
14324
15323
  process.exit(0);
14325
15324
  }
14326
15325
  const group = groups.find((g) => g.key === groupChoice);
14327
- const artifactChoice = await p17.select({
15326
+ const artifactChoice = await p18.select({
14328
15327
  message: `Select from: ${group.label}`,
14329
15328
  options: group.artifacts.map((a) => ({
14330
15329
  value: a.id,
14331
- label: pc24.bold(a.name),
15330
+ label: pc26.bold(a.name),
14332
15331
  hint: truncate2(a.category, 52)
14333
15332
  }))
14334
15333
  });
14335
- if (p17.isCancel(artifactChoice)) {
14336
- p17.cancel("Cancelled.");
15334
+ if (p18.isCancel(artifactChoice)) {
15335
+ p18.cancel("Cancelled.");
14337
15336
  process.exit(0);
14338
15337
  }
14339
15338
  const selected = filteredArtifacts.find((a) => a.id === artifactChoice);
14340
15339
  printCard(selected);
14341
- const action = await p17.select({
15340
+ const action = await p18.select({
14342
15341
  message: "What would you like to do?",
14343
15342
  options: [
14344
15343
  { value: "print", label: "\u{1F4C4} Print to terminal" },
@@ -14347,13 +15346,13 @@ async function runTemplatePicker(opts) {
14347
15346
  { value: "cancel", label: "Cancel" }
14348
15347
  ]
14349
15348
  });
14350
- if (p17.isCancel(action) || action === "cancel") {
14351
- p17.cancel("Cancelled.");
15349
+ if (p18.isCancel(action) || action === "cancel") {
15350
+ p18.cancel("Cancelled.");
14352
15351
  process.exit(0);
14353
15352
  }
14354
15353
  if (action === "slug") {
14355
15354
  console.log(selected.slug);
14356
- p17.outro(pc24.dim("Use with: growthub template get " + selected.slug));
15355
+ p18.outro(pc26.dim("Use with: growthub template get " + selected.slug));
14357
15356
  return "done";
14358
15357
  }
14359
15358
  if (action === "print") {
@@ -14361,22 +15360,22 @@ async function runTemplatePicker(opts) {
14361
15360
  console.log("\n" + hr2());
14362
15361
  console.log(r.content);
14363
15362
  console.log(hr2());
14364
- p17.outro(pc24.dim("Source: " + r.absolutePath));
15363
+ p18.outro(pc26.dim("Source: " + r.absolutePath));
14365
15364
  return "done";
14366
15365
  }
14367
15366
  if (action === "copy") {
14368
- const destInput = await p17.text({
15367
+ const destInput = await p18.text({
14369
15368
  message: "Output directory:",
14370
15369
  placeholder: "~/Downloads/templates",
14371
15370
  validate: (v) => !v?.trim() ? "Path is required" : void 0
14372
15371
  });
14373
- if (p17.isCancel(destInput)) {
14374
- p17.cancel("Cancelled.");
15372
+ if (p18.isCancel(destInput)) {
15373
+ p18.cancel("Cancelled.");
14375
15374
  process.exit(0);
14376
15375
  }
14377
- const destDir = path22.resolve(destInput.replace(/^~/, process.env["HOME"] ?? ""));
15376
+ const destDir = path26.resolve(destInput.replace(/^~/, process.env["HOME"] ?? ""));
14378
15377
  const destPath = copyArtifact(selected.id, destDir);
14379
- p17.outro(pc24.green("Copied \u2192 ") + destPath);
15378
+ p18.outro(pc26.green("Copied \u2192 ") + destPath);
14380
15379
  return "done";
14381
15380
  }
14382
15381
  return "done";
@@ -14403,7 +15402,7 @@ Any agent or kit resolves them by slug.
14403
15402
  if (opts.type) {
14404
15403
  const t = opts.type.replace(/s$/, "");
14405
15404
  if (t !== "ad-format" && t !== "scene-module") {
14406
- console.error(pc24.red(`Unknown --type '${opts.type}'.`) + pc24.dim(" Valid: ad-formats, scene-modules"));
15405
+ console.error(pc26.red(`Unknown --type '${opts.type}'.`) + pc26.dim(" Valid: ad-formats, scene-modules"));
14407
15406
  process.exitCode = 1;
14408
15407
  return;
14409
15408
  }
@@ -14412,7 +15411,7 @@ Any agent or kit resolves them by slug.
14412
15411
  if (opts.subtype) {
14413
15412
  const sub = opts.subtype.replace(/s$/, "");
14414
15413
  if (!["hook", "body", "cta"].includes(sub)) {
14415
- console.error(pc24.red(`Unknown --subtype '${opts.subtype}'.`) + pc24.dim(" Valid: hooks, body, cta"));
15414
+ console.error(pc26.red(`Unknown --subtype '${opts.subtype}'.`) + pc26.dim(" Valid: hooks, body, cta"));
14416
15415
  process.exitCode = 1;
14417
15416
  return;
14418
15417
  }
@@ -14428,18 +15427,18 @@ Any agent or kit resolves them by slug.
14428
15427
  cmd.command("get").description("Print or copy a template \u2014 fuzzy slug resolution").argument("<slug>", "Artifact slug (e.g. villain-animation, meme-overlay)").option("--out <path>", "Copy to this directory").option("--json", "Artifact metadata + content as JSON").action((slug, opts) => {
14429
15428
  const artifact = resolveSlug(slug);
14430
15429
  if (!artifact) {
14431
- console.error(pc24.red(`Unknown template '${slug}'.`) + pc24.dim(" Run `growthub template list` to browse."));
15430
+ console.error(pc26.red(`Unknown template '${slug}'.`) + pc26.dim(" Run `growthub template list` to browse."));
14432
15431
  process.exitCode = 1;
14433
15432
  return;
14434
15433
  }
14435
15434
  if (artifact.id !== slug && artifact.slug !== slug) {
14436
- console.error(pc24.dim(`Resolved '${slug}' \u2192 ${artifact.slug}`));
15435
+ console.error(pc26.dim(`Resolved '${slug}' \u2192 ${artifact.slug}`));
14437
15436
  }
14438
15437
  let resolved;
14439
15438
  try {
14440
15439
  resolved = getArtifact(artifact.id);
14441
15440
  } catch (err) {
14442
- console.error(pc24.red(err.message));
15441
+ console.error(pc26.red(err.message));
14443
15442
  process.exitCode = 1;
14444
15443
  return;
14445
15444
  }
@@ -14448,12 +15447,12 @@ Any agent or kit resolves them by slug.
14448
15447
  return;
14449
15448
  }
14450
15449
  if (opts.out) {
14451
- const destDir = path22.resolve(opts.out.replace(/^~/, process.env["HOME"] ?? ""));
15450
+ const destDir = path26.resolve(opts.out.replace(/^~/, process.env["HOME"] ?? ""));
14452
15451
  try {
14453
15452
  const dest = copyArtifact(artifact.id, destDir);
14454
- console.log(pc24.green("Copied \u2192 ") + dest);
15453
+ console.log(pc26.green("Copied \u2192 ") + dest);
14455
15454
  } catch (err) {
14456
- console.error(pc24.red(err.message));
15455
+ console.error(pc26.red(err.message));
14457
15456
  process.exitCode = 1;
14458
15457
  }
14459
15458
  return;
@@ -14462,7 +15461,7 @@ Any agent or kit resolves them by slug.
14462
15461
  console.log(hr2());
14463
15462
  console.log(resolved.content);
14464
15463
  console.log(hr2());
14465
- console.log(pc24.dim("Source: " + resolved.absolutePath));
15464
+ console.log(pc26.dim("Source: " + resolved.absolutePath));
14466
15465
  console.log("");
14467
15466
  });
14468
15467
  }
@@ -14514,12 +15513,31 @@ function registerSharedCommands(target) {
14514
15513
  registerTemplateCommands(target);
14515
15514
  const auth = target.command("auth").description("Authentication and bootstrap utilities");
14516
15515
  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);
15516
+ 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) => {
15517
+ await authLogin({
15518
+ ...opts,
15519
+ noBrowser: opts.browser === false
15520
+ });
15521
+ });
15522
+ auth.command("logout").description("Clear the hosted CLI session (local workspace profile is preserved)").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("--keep-overlay", "Keep cached hosted overlay metadata; only drop the session token").option("--json", "Output raw JSON").action(async (opts) => {
15523
+ await authLogout(opts);
15524
+ });
15525
+ auth.command("whoami").description("Print the authenticated hosted identity and linked local workspace").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("--json", "Output raw JSON").action(async (opts) => {
15526
+ await authWhoami(opts);
15527
+ });
15528
+ registerProfileCommands(target);
15529
+ }
15530
+ async function runHostedBridgeEntry(opts) {
15531
+ await authLogin({
15532
+ config: opts?.config,
15533
+ dataDir: opts?.dataDir
15534
+ });
14517
15535
  }
14518
15536
  async function runDiscoveryHub(opts) {
14519
15537
  printPaperclipCliBanner();
14520
- p18.intro("Growthub Local");
15538
+ p19.intro("Growthub Local");
14521
15539
  while (true) {
14522
- const surfaceChoice = await p18.select({
15540
+ const surfaceChoice = await p19.select({
14523
15541
  message: "What do you want to do first?",
14524
15542
  options: [
14525
15543
  {
@@ -14537,6 +15555,11 @@ async function runDiscoveryHub(opts) {
14537
15555
  label: "\u{1F4DA} Templates",
14538
15556
  hint: "Artifact template library"
14539
15557
  },
15558
+ {
15559
+ value: "hosted-auth",
15560
+ label: "\u{1F510} Connect Growthub Account",
15561
+ hint: "Attach this CLI to the hosted Growthub user through the canonical browser flow"
15562
+ },
14540
15563
  {
14541
15564
  value: "help",
14542
15565
  label: "\u2753 Help CLI",
@@ -14544,23 +15567,23 @@ async function runDiscoveryHub(opts) {
14544
15567
  }
14545
15568
  ]
14546
15569
  });
14547
- if (p18.isCancel(surfaceChoice)) {
14548
- p18.cancel("Cancelled.");
15570
+ if (p19.isCancel(surfaceChoice)) {
15571
+ p19.cancel("Cancelled.");
14549
15572
  process.exit(0);
14550
15573
  }
14551
15574
  if (surfaceChoice === "help") {
14552
- p18.note(
15575
+ p19.note(
14553
15576
  [
14554
15577
  "\u{1F4E6} Full Local App: open an existing local surface or create a new GTM/DX profile.",
14555
15578
  "\u{1F9F0} Worker Kits: browse specialized agents and custom workspaces.",
14556
15579
  "\u{1F4DA} Templates: browse reusable artifact templates by library type.",
15580
+ "\u{1F510} Connect Growthub Account: open the canonical hosted auth flow for this CLI.",
14557
15581
  "",
14558
15582
  "Direct commands:",
14559
- "growthub run",
15583
+ "growthub auth login",
15584
+ "growthub auth whoami",
14560
15585
  "growthub kit",
14561
- "growthub template",
14562
- "growthub doctor",
14563
- "growthub configure"
15586
+ "growthub template"
14564
15587
  ].join("\n"),
14565
15588
  "Growthub CLI Help"
14566
15589
  );
@@ -14568,7 +15591,7 @@ async function runDiscoveryHub(opts) {
14568
15591
  }
14569
15592
  if (surfaceChoice === "app") {
14570
15593
  while (true) {
14571
- const appModeChoice = await p18.select({
15594
+ const appModeChoice = await p19.select({
14572
15595
  message: "How do you want to open Growthub Local?",
14573
15596
  options: [
14574
15597
  {
@@ -14587,18 +15610,18 @@ async function runDiscoveryHub(opts) {
14587
15610
  }
14588
15611
  ]
14589
15612
  });
14590
- if (p18.isCancel(appModeChoice)) {
14591
- p18.cancel("Cancelled.");
15613
+ if (p19.isCancel(appModeChoice)) {
15614
+ p19.cancel("Cancelled.");
14592
15615
  process.exit(0);
14593
15616
  }
14594
15617
  if (appModeChoice === "__back_to_hub") break;
14595
15618
  if (appModeChoice === "load") {
14596
15619
  const existingSurfaces = listLocalSurfaces();
14597
15620
  if (existingSurfaces.length === 0) {
14598
- p18.note("No existing local app profiles were found on this machine.", "Nothing found");
15621
+ p19.note("No existing local app profiles were found on this machine.", "Nothing found");
14599
15622
  continue;
14600
15623
  }
14601
- const existingChoice = await p18.select({
15624
+ const existingChoice = await p19.select({
14602
15625
  message: "Select an existing app surface",
14603
15626
  options: [
14604
15627
  ...existingSurfaces.map((surface) => ({
@@ -14609,8 +15632,8 @@ async function runDiscoveryHub(opts) {
14609
15632
  { value: "__back_to_app_mode", label: "\u2190 Back to app options" }
14610
15633
  ]
14611
15634
  });
14612
- if (p18.isCancel(existingChoice)) {
14613
- p18.cancel("Cancelled.");
15635
+ if (p19.isCancel(existingChoice)) {
15636
+ p19.cancel("Cancelled.");
14614
15637
  process.exit(0);
14615
15638
  }
14616
15639
  if (existingChoice === "__back_to_app_mode") {
@@ -14618,7 +15641,7 @@ async function runDiscoveryHub(opts) {
14618
15641
  }
14619
15642
  const selectedSurface = existingSurfaces.find((surface) => surface.instanceId === existingChoice);
14620
15643
  if (!selectedSurface) {
14621
- p18.cancel("Selected profile not found.");
15644
+ p19.cancel("Selected profile not found.");
14622
15645
  process.exit(1);
14623
15646
  }
14624
15647
  process.env.PAPERCLIP_SURFACE_PROFILE = selectedSurface.profile;
@@ -14630,7 +15653,7 @@ async function runDiscoveryHub(opts) {
14630
15653
  });
14631
15654
  return;
14632
15655
  }
14633
- const profileChoice = await p18.select({
15656
+ const profileChoice = await p19.select({
14634
15657
  message: "Which new app surface do you want to create?",
14635
15658
  options: [
14636
15659
  {
@@ -14649,8 +15672,8 @@ async function runDiscoveryHub(opts) {
14649
15672
  }
14650
15673
  ]
14651
15674
  });
14652
- if (p18.isCancel(profileChoice)) {
14653
- p18.cancel("Cancelled.");
15675
+ if (p19.isCancel(profileChoice)) {
15676
+ p19.cancel("Cancelled.");
14654
15677
  process.exit(0);
14655
15678
  }
14656
15679
  if (profileChoice === "__back_to_app_mode") {
@@ -14671,6 +15694,10 @@ async function runDiscoveryHub(opts) {
14671
15694
  if (result2 === "back") continue;
14672
15695
  return;
14673
15696
  }
15697
+ if (surfaceChoice === "hosted-auth") {
15698
+ await runHostedBridgeEntry({ config: opts?.config, dataDir: opts?.dataDir });
15699
+ continue;
15700
+ }
14674
15701
  const result = await runTemplatePicker({ allowBackToHub: true });
14675
15702
  if (result === "back") continue;
14676
15703
  return;
@@ -14681,12 +15708,12 @@ function isInstallerMode() {
14681
15708
  }
14682
15709
  function listLocalSurfaces() {
14683
15710
  const homeDir = resolvePaperclipHomeDir();
14684
- const instancesDir = path23.resolve(homeDir, "instances");
14685
- if (!fs16.existsSync(instancesDir)) return [];
14686
- return fs16.readdirSync(instancesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
15711
+ const instancesDir = path27.resolve(homeDir, "instances");
15712
+ if (!fs19.existsSync(instancesDir)) return [];
15713
+ return fs19.readdirSync(instancesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
14687
15714
  const instanceId = entry.name;
14688
- const configPath = path23.resolve(instancesDir, instanceId, "config.json");
14689
- if (!fs16.existsSync(configPath)) return null;
15715
+ const configPath = path27.resolve(instancesDir, instanceId, "config.json");
15716
+ if (!fs19.existsSync(configPath)) return null;
14690
15717
  try {
14691
15718
  const config = readConfig(configPath);
14692
15719
  if (!config) return null;
@@ -14727,7 +15754,7 @@ applyDataDirOverride(bootstrapOptions, {
14727
15754
  loadPaperclipEnvFile(bootstrapOptions.config);
14728
15755
  var bootstrapConfig = readConfig(resolveConfigPath(bootstrapOptions.config));
14729
15756
  var surfaceRuntime = initializeSurfaceRuntimeContract(resolveSurfaceProfile(bootstrapConfig) ?? void 0);
14730
- program.name("growthub").description("Growthub CLI \u2014 setup, configure, and run your local Growthub instance").version("0.3.45").addHelpText("after", `
15757
+ program.name("growthub").description("Growthub CLI \u2014 setup, configure, and run your local Growthub instance").version("0.3.48").addHelpText("after", `
14731
15758
  Worker Kits (agent execution environments):
14732
15759
 
14733
15760
  Discovery:
@@ -14758,6 +15785,11 @@ Instance setup:
14758
15785
  $ growthub doctor Diagnose and optionally repair
14759
15786
  $ growthub configure Update config sections
14760
15787
  $ growthub Interactive discovery hub
15788
+
15789
+ Hosted account bridge:
15790
+ $ growthub auth login Sign in via the hosted app (browser flow)
15791
+ $ growthub auth whoami Show signed-in identity + linked local workspace
15792
+ $ growthub auth logout Clear the hosted session (local workspace preserved)
14761
15793
  `);
14762
15794
  program.action(async () => {
14763
15795
  await runDiscoveryHub();