@growthub/cli 0.3.46 → 0.3.49
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.
- package/README.md +51 -3
- package/dist/index.js +1511 -466
- package/dist/utils/banner.js +1 -1
- 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
|
|
6272
|
-
const cols =
|
|
6273
|
-
colDefs.push(` CONSTRAINT "${
|
|
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
|
|
8020
|
-
import
|
|
8021
|
-
import
|
|
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();
|
|
@@ -8677,8 +8677,8 @@ function printItemCompleted(item) {
|
|
|
8677
8677
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
8678
8678
|
const entries = changes.map((changeRaw) => asRecord(changeRaw)).filter((change) => Boolean(change)).map((change) => {
|
|
8679
8679
|
const kind = asString(change.kind, "update");
|
|
8680
|
-
const
|
|
8681
|
-
return `${kind} ${
|
|
8680
|
+
const path28 = asString(change.path, "unknown");
|
|
8681
|
+
return `${kind} ${path28}`;
|
|
8682
8682
|
});
|
|
8683
8683
|
const preview = entries.length > 0 ? entries.slice(0, 6).join(", ") : "none";
|
|
8684
8684
|
const more = entries.length > 6 ? ` (+${entries.length - 6} more)` : "";
|
|
@@ -9696,26 +9696,26 @@ var PaperclipApiClient = class {
|
|
|
9696
9696
|
this.apiKey = opts.apiKey?.trim() || void 0;
|
|
9697
9697
|
this.runId = opts.runId?.trim() || void 0;
|
|
9698
9698
|
}
|
|
9699
|
-
get(
|
|
9700
|
-
return this.request(
|
|
9699
|
+
get(path28, opts) {
|
|
9700
|
+
return this.request(path28, { method: "GET" }, opts);
|
|
9701
9701
|
}
|
|
9702
|
-
post(
|
|
9703
|
-
return this.request(
|
|
9702
|
+
post(path28, body, opts) {
|
|
9703
|
+
return this.request(path28, {
|
|
9704
9704
|
method: "POST",
|
|
9705
9705
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
9706
9706
|
}, opts);
|
|
9707
9707
|
}
|
|
9708
|
-
patch(
|
|
9709
|
-
return this.request(
|
|
9708
|
+
patch(path28, body, opts) {
|
|
9709
|
+
return this.request(path28, {
|
|
9710
9710
|
method: "PATCH",
|
|
9711
9711
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
9712
9712
|
}, opts);
|
|
9713
9713
|
}
|
|
9714
|
-
delete(
|
|
9715
|
-
return this.request(
|
|
9714
|
+
delete(path28, opts) {
|
|
9715
|
+
return this.request(path28, { method: "DELETE" }, opts);
|
|
9716
9716
|
}
|
|
9717
|
-
async request(
|
|
9718
|
-
const url = buildUrl(this.apiBase,
|
|
9717
|
+
async request(path28, init, opts) {
|
|
9718
|
+
const url = buildUrl(this.apiBase, path28);
|
|
9719
9719
|
const headers = {
|
|
9720
9720
|
accept: "application/json",
|
|
9721
9721
|
...toStringRecord(init.headers)
|
|
@@ -9749,8 +9749,8 @@ var PaperclipApiClient = class {
|
|
|
9749
9749
|
return safeParseJson(text58);
|
|
9750
9750
|
}
|
|
9751
9751
|
};
|
|
9752
|
-
function buildUrl(apiBase,
|
|
9753
|
-
const normalizedPath =
|
|
9752
|
+
function buildUrl(apiBase, path28) {
|
|
9753
|
+
const normalizedPath = path28.startsWith("/") ? path28 : `/${path28}`;
|
|
9754
9754
|
const [pathname, query] = normalizedPath.split("?");
|
|
9755
9755
|
const url = new URL2(apiBase);
|
|
9756
9756
|
url.pathname = `${url.pathname.replace(/\/+$/, "")}${pathname}`;
|
|
@@ -10165,14 +10165,1026 @@ function safeParseLogLine(line) {
|
|
|
10165
10165
|
init_run();
|
|
10166
10166
|
init_auth_bootstrap_ceo();
|
|
10167
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, "<");
|
|
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
|
+
|
|
10168
11180
|
// src/commands/db-backup.ts
|
|
10169
11181
|
init_src2();
|
|
10170
11182
|
init_home();
|
|
10171
11183
|
init_store();
|
|
10172
11184
|
init_banner();
|
|
10173
|
-
import
|
|
10174
|
-
import * as
|
|
10175
|
-
import
|
|
11185
|
+
import path14 from "node:path";
|
|
11186
|
+
import * as p15 from "@clack/prompts";
|
|
11187
|
+
import pc20 from "picocolors";
|
|
10176
11188
|
function resolveConnectionString(configPath) {
|
|
10177
11189
|
const envUrl = process.env.DATABASE_URL?.trim();
|
|
10178
11190
|
if (envUrl) return { value: envUrl, source: "DATABASE_URL" };
|
|
@@ -10194,11 +11206,11 @@ function normalizeRetentionDays(value, fallback) {
|
|
|
10194
11206
|
return candidate;
|
|
10195
11207
|
}
|
|
10196
11208
|
function resolveBackupDir(raw) {
|
|
10197
|
-
return
|
|
11209
|
+
return path14.resolve(expandHomePrefix(raw.trim()));
|
|
10198
11210
|
}
|
|
10199
11211
|
async function dbBackupCommand(opts) {
|
|
10200
11212
|
printPaperclipCliBanner();
|
|
10201
|
-
|
|
11213
|
+
p15.intro(pc20.bgCyan(pc20.black(" paperclip db:backup ")));
|
|
10202
11214
|
const configPath = resolveConfigPath(opts.config);
|
|
10203
11215
|
const config = readConfig(opts.config);
|
|
10204
11216
|
const connection = resolveConnectionString(opts.config);
|
|
@@ -10210,12 +11222,12 @@ async function dbBackupCommand(opts) {
|
|
|
10210
11222
|
config?.database.backup.retentionDays ?? 30
|
|
10211
11223
|
);
|
|
10212
11224
|
const filenamePrefix = opts.filenamePrefix?.trim() || "paperclip";
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
const
|
|
10218
|
-
|
|
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...");
|
|
10219
11231
|
try {
|
|
10220
11232
|
const result = await runDatabaseBackup({
|
|
10221
11233
|
connectionString: connection.value,
|
|
@@ -10223,7 +11235,7 @@ async function dbBackupCommand(opts) {
|
|
|
10223
11235
|
retentionDays,
|
|
10224
11236
|
filenamePrefix
|
|
10225
11237
|
});
|
|
10226
|
-
|
|
11238
|
+
spinner5.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`);
|
|
10227
11239
|
if (opts.json) {
|
|
10228
11240
|
console.log(
|
|
10229
11241
|
JSON.stringify(
|
|
@@ -10240,15 +11252,15 @@ async function dbBackupCommand(opts) {
|
|
|
10240
11252
|
)
|
|
10241
11253
|
);
|
|
10242
11254
|
}
|
|
10243
|
-
|
|
11255
|
+
p15.outro(pc20.green("Backup completed."));
|
|
10244
11256
|
} catch (err) {
|
|
10245
|
-
|
|
11257
|
+
spinner5.stop(pc20.red("Backup failed."));
|
|
10246
11258
|
throw err;
|
|
10247
11259
|
}
|
|
10248
11260
|
}
|
|
10249
11261
|
|
|
10250
11262
|
// src/commands/client/context.ts
|
|
10251
|
-
import
|
|
11263
|
+
import pc21 from "picocolors";
|
|
10252
11264
|
function registerContextCommands(program2) {
|
|
10253
11265
|
const context = program2.command("context").description("Manage CLI client context profiles");
|
|
10254
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) => {
|
|
@@ -10277,7 +11289,7 @@ function registerContextCommands(program2) {
|
|
|
10277
11289
|
});
|
|
10278
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) => {
|
|
10279
11291
|
setCurrentProfile(profile, opts.context);
|
|
10280
|
-
console.log(
|
|
11292
|
+
console.log(pc21.green(`Active profile set to '${profile}'.`));
|
|
10281
11293
|
});
|
|
10282
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) => {
|
|
10283
11295
|
const existing = readContext(opts.context);
|
|
@@ -10303,9 +11315,9 @@ function registerContextCommands(program2) {
|
|
|
10303
11315
|
profile: resolved.profile
|
|
10304
11316
|
};
|
|
10305
11317
|
if (!opts.json) {
|
|
10306
|
-
console.log(
|
|
11318
|
+
console.log(pc21.green(`Updated profile '${targetProfile}'.`));
|
|
10307
11319
|
if (opts.use) {
|
|
10308
|
-
console.log(
|
|
11320
|
+
console.log(pc21.green(`Set '${targetProfile}' as active profile.`));
|
|
10309
11321
|
}
|
|
10310
11322
|
}
|
|
10311
11323
|
printOutput(payload, { json: opts.json });
|
|
@@ -10314,7 +11326,7 @@ function registerContextCommands(program2) {
|
|
|
10314
11326
|
|
|
10315
11327
|
// src/commands/client/company.ts
|
|
10316
11328
|
import { mkdir, readFile as readFile3, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
10317
|
-
import
|
|
11329
|
+
import path15 from "node:path";
|
|
10318
11330
|
function isUuidLike2(value) {
|
|
10319
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);
|
|
10320
11332
|
}
|
|
@@ -10348,32 +11360,32 @@ function isGithubUrl(input) {
|
|
|
10348
11360
|
return /^https?:\/\/github\.com\//i.test(input.trim());
|
|
10349
11361
|
}
|
|
10350
11362
|
async function resolveInlineSourceFromPath(inputPath) {
|
|
10351
|
-
const resolved =
|
|
11363
|
+
const resolved = path15.resolve(inputPath);
|
|
10352
11364
|
const resolvedStat = await stat(resolved);
|
|
10353
|
-
const manifestPath = resolvedStat.isDirectory() ?
|
|
10354
|
-
const manifestBaseDir =
|
|
11365
|
+
const manifestPath = resolvedStat.isDirectory() ? path15.join(resolved, "paperclip.manifest.json") : resolved;
|
|
11366
|
+
const manifestBaseDir = path15.dirname(manifestPath);
|
|
10355
11367
|
const manifestRaw = await readFile3(manifestPath, "utf8");
|
|
10356
11368
|
const manifest = JSON.parse(manifestRaw);
|
|
10357
11369
|
const files = {};
|
|
10358
11370
|
if (manifest.company?.path) {
|
|
10359
11371
|
const companyPath = manifest.company.path.replace(/\\/g, "/");
|
|
10360
|
-
files[companyPath] = await readFile3(
|
|
11372
|
+
files[companyPath] = await readFile3(path15.join(manifestBaseDir, companyPath), "utf8");
|
|
10361
11373
|
}
|
|
10362
11374
|
for (const agent of manifest.agents ?? []) {
|
|
10363
11375
|
const agentPath = agent.path.replace(/\\/g, "/");
|
|
10364
|
-
files[agentPath] = await readFile3(
|
|
11376
|
+
files[agentPath] = await readFile3(path15.join(manifestBaseDir, agentPath), "utf8");
|
|
10365
11377
|
}
|
|
10366
11378
|
return { manifest, files };
|
|
10367
11379
|
}
|
|
10368
11380
|
async function writeExportToFolder(outDir, exported) {
|
|
10369
|
-
const root =
|
|
11381
|
+
const root = path15.resolve(outDir);
|
|
10370
11382
|
await mkdir(root, { recursive: true });
|
|
10371
|
-
const manifestPath =
|
|
11383
|
+
const manifestPath = path15.join(root, "paperclip.manifest.json");
|
|
10372
11384
|
await writeFile2(manifestPath, JSON.stringify(exported.manifest, null, 2), "utf8");
|
|
10373
11385
|
for (const [relativePath, content] of Object.entries(exported.files)) {
|
|
10374
11386
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
10375
|
-
const filePath =
|
|
10376
|
-
await mkdir(
|
|
11387
|
+
const filePath = path15.join(root, normalized);
|
|
11388
|
+
await mkdir(path15.dirname(filePath), { recursive: true });
|
|
10377
11389
|
await writeFile2(filePath, content, "utf8");
|
|
10378
11390
|
}
|
|
10379
11391
|
}
|
|
@@ -10496,7 +11508,7 @@ function registerCompanyCommands(program2) {
|
|
|
10496
11508
|
printOutput(
|
|
10497
11509
|
{
|
|
10498
11510
|
ok: true,
|
|
10499
|
-
out:
|
|
11511
|
+
out: path15.resolve(opts.out),
|
|
10500
11512
|
filesWritten: Object.keys(exported.files).length + 1,
|
|
10501
11513
|
warningCount: exported.warnings.length
|
|
10502
11514
|
},
|
|
@@ -10658,8 +11670,8 @@ function registerIssueCommands(program2) {
|
|
|
10658
11670
|
if (opts.assigneeAgentId) params.set("assigneeAgentId", opts.assigneeAgentId);
|
|
10659
11671
|
if (opts.projectId) params.set("projectId", opts.projectId);
|
|
10660
11672
|
const query = params.toString();
|
|
10661
|
-
const
|
|
10662
|
-
const rows = await ctx.api.get(
|
|
11673
|
+
const path28 = `/api/companies/${ctx.companyId}/issues${query ? `?${query}` : ""}`;
|
|
11674
|
+
const rows = await ctx.api.get(path28) ?? [];
|
|
10663
11675
|
const filtered = filterIssueRows(rows, opts.match);
|
|
10664
11676
|
if (ctx.json) {
|
|
10665
11677
|
printOutput(filtered, { json: true });
|
|
@@ -10821,8 +11833,8 @@ function filterIssueRows(rows, match) {
|
|
|
10821
11833
|
}
|
|
10822
11834
|
|
|
10823
11835
|
// ../packages/adapter-utils/src/server-utils.ts
|
|
10824
|
-
import { constants as fsConstants, promises as
|
|
10825
|
-
import
|
|
11836
|
+
import { constants as fsConstants, promises as fs14 } from "node:fs";
|
|
11837
|
+
import path16 from "node:path";
|
|
10826
11838
|
var MAX_CAPTURE_BYTES = 4 * 1024 * 1024;
|
|
10827
11839
|
var MAX_EXCERPT_BYTES = 32 * 1024;
|
|
10828
11840
|
var PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES = [
|
|
@@ -10837,14 +11849,14 @@ function isMaintainerOnlySkillTarget(candidate) {
|
|
|
10837
11849
|
}
|
|
10838
11850
|
async function resolvePaperclipSkillsDir(moduleDir, additionalCandidates = []) {
|
|
10839
11851
|
const candidates = [
|
|
10840
|
-
...PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES.map((relativePath) =>
|
|
10841
|
-
...additionalCandidates.map((candidate) =>
|
|
11852
|
+
...PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES.map((relativePath) => path16.resolve(moduleDir, relativePath)),
|
|
11853
|
+
...additionalCandidates.map((candidate) => path16.resolve(candidate))
|
|
10842
11854
|
];
|
|
10843
11855
|
const seenRoots = /* @__PURE__ */ new Set();
|
|
10844
11856
|
for (const root of candidates) {
|
|
10845
11857
|
if (seenRoots.has(root)) continue;
|
|
10846
11858
|
seenRoots.add(root);
|
|
10847
|
-
const isDirectory = await
|
|
11859
|
+
const isDirectory = await fs14.stat(root).then((stats) => stats.isDirectory()).catch(() => false);
|
|
10848
11860
|
if (isDirectory) return root;
|
|
10849
11861
|
}
|
|
10850
11862
|
return null;
|
|
@@ -10852,20 +11864,20 @@ async function resolvePaperclipSkillsDir(moduleDir, additionalCandidates = []) {
|
|
|
10852
11864
|
async function removeMaintainerOnlySkillSymlinks(skillsHome, allowedSkillNames) {
|
|
10853
11865
|
const allowed = new Set(Array.from(allowedSkillNames));
|
|
10854
11866
|
try {
|
|
10855
|
-
const entries = await
|
|
11867
|
+
const entries = await fs14.readdir(skillsHome, { withFileTypes: true });
|
|
10856
11868
|
const removed = [];
|
|
10857
11869
|
for (const entry of entries) {
|
|
10858
11870
|
if (allowed.has(entry.name)) continue;
|
|
10859
|
-
const target =
|
|
10860
|
-
const existing = await
|
|
11871
|
+
const target = path16.join(skillsHome, entry.name);
|
|
11872
|
+
const existing = await fs14.lstat(target).catch(() => null);
|
|
10861
11873
|
if (!existing?.isSymbolicLink()) continue;
|
|
10862
|
-
const linkedPath = await
|
|
11874
|
+
const linkedPath = await fs14.readlink(target).catch(() => null);
|
|
10863
11875
|
if (!linkedPath) continue;
|
|
10864
|
-
const resolvedLinkedPath =
|
|
11876
|
+
const resolvedLinkedPath = path16.isAbsolute(linkedPath) ? linkedPath : path16.resolve(path16.dirname(target), linkedPath);
|
|
10865
11877
|
if (!isMaintainerOnlySkillTarget(linkedPath) && !isMaintainerOnlySkillTarget(resolvedLinkedPath)) {
|
|
10866
11878
|
continue;
|
|
10867
11879
|
}
|
|
10868
|
-
await
|
|
11880
|
+
await fs14.unlink(target);
|
|
10869
11881
|
removed.push(entry.name);
|
|
10870
11882
|
}
|
|
10871
11883
|
return removed;
|
|
@@ -10875,20 +11887,20 @@ async function removeMaintainerOnlySkillSymlinks(skillsHome, allowedSkillNames)
|
|
|
10875
11887
|
}
|
|
10876
11888
|
|
|
10877
11889
|
// src/commands/client/agent.ts
|
|
10878
|
-
import
|
|
10879
|
-
import
|
|
10880
|
-
import
|
|
11890
|
+
import fs15 from "node:fs/promises";
|
|
11891
|
+
import os4 from "node:os";
|
|
11892
|
+
import path17 from "node:path";
|
|
10881
11893
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10882
|
-
var __moduleDir =
|
|
11894
|
+
var __moduleDir = path17.dirname(fileURLToPath3(import.meta.url));
|
|
10883
11895
|
function codexSkillsHome() {
|
|
10884
11896
|
const fromEnv = process.env.CODEX_HOME?.trim();
|
|
10885
|
-
const base = fromEnv && fromEnv.length > 0 ? fromEnv :
|
|
10886
|
-
return
|
|
11897
|
+
const base = fromEnv && fromEnv.length > 0 ? fromEnv : path17.join(os4.homedir(), ".codex");
|
|
11898
|
+
return path17.join(base, "skills");
|
|
10887
11899
|
}
|
|
10888
11900
|
function claudeSkillsHome() {
|
|
10889
11901
|
const fromEnv = process.env.CLAUDE_HOME?.trim();
|
|
10890
|
-
const base = fromEnv && fromEnv.length > 0 ? fromEnv :
|
|
10891
|
-
return
|
|
11902
|
+
const base = fromEnv && fromEnv.length > 0 ? fromEnv : path17.join(os4.homedir(), ".claude");
|
|
11903
|
+
return path17.join(base, "skills");
|
|
10892
11904
|
}
|
|
10893
11905
|
async function installSkillsForTarget(sourceSkillsDir, targetSkillsDir, tool) {
|
|
10894
11906
|
const summary = {
|
|
@@ -10899,26 +11911,26 @@ async function installSkillsForTarget(sourceSkillsDir, targetSkillsDir, tool) {
|
|
|
10899
11911
|
skipped: [],
|
|
10900
11912
|
failed: []
|
|
10901
11913
|
};
|
|
10902
|
-
await
|
|
10903
|
-
const entries = await
|
|
11914
|
+
await fs15.mkdir(targetSkillsDir, { recursive: true });
|
|
11915
|
+
const entries = await fs15.readdir(sourceSkillsDir, { withFileTypes: true });
|
|
10904
11916
|
summary.removed = await removeMaintainerOnlySkillSymlinks(
|
|
10905
11917
|
targetSkillsDir,
|
|
10906
11918
|
entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name)
|
|
10907
11919
|
);
|
|
10908
11920
|
for (const entry of entries) {
|
|
10909
11921
|
if (!entry.isDirectory()) continue;
|
|
10910
|
-
const source =
|
|
10911
|
-
const target =
|
|
10912
|
-
const existing = await
|
|
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);
|
|
10913
11925
|
if (existing) {
|
|
10914
11926
|
if (existing.isSymbolicLink()) {
|
|
10915
11927
|
let linkedPath = null;
|
|
10916
11928
|
try {
|
|
10917
|
-
linkedPath = await
|
|
11929
|
+
linkedPath = await fs15.readlink(target);
|
|
10918
11930
|
} catch (err) {
|
|
10919
|
-
await
|
|
11931
|
+
await fs15.unlink(target);
|
|
10920
11932
|
try {
|
|
10921
|
-
await
|
|
11933
|
+
await fs15.symlink(source, target);
|
|
10922
11934
|
summary.linked.push(entry.name);
|
|
10923
11935
|
continue;
|
|
10924
11936
|
} catch (linkErr) {
|
|
@@ -10929,10 +11941,10 @@ async function installSkillsForTarget(sourceSkillsDir, targetSkillsDir, tool) {
|
|
|
10929
11941
|
continue;
|
|
10930
11942
|
}
|
|
10931
11943
|
}
|
|
10932
|
-
const resolvedLinkedPath =
|
|
10933
|
-
const linkedTargetExists = await
|
|
11944
|
+
const resolvedLinkedPath = path17.isAbsolute(linkedPath) ? linkedPath : path17.resolve(path17.dirname(target), linkedPath);
|
|
11945
|
+
const linkedTargetExists = await fs15.stat(resolvedLinkedPath).then(() => true).catch(() => false);
|
|
10934
11946
|
if (!linkedTargetExists) {
|
|
10935
|
-
await
|
|
11947
|
+
await fs15.unlink(target);
|
|
10936
11948
|
} else {
|
|
10937
11949
|
summary.skipped.push(entry.name);
|
|
10938
11950
|
continue;
|
|
@@ -10943,7 +11955,7 @@ async function installSkillsForTarget(sourceSkillsDir, targetSkillsDir, tool) {
|
|
|
10943
11955
|
}
|
|
10944
11956
|
}
|
|
10945
11957
|
try {
|
|
10946
|
-
await
|
|
11958
|
+
await fs15.symlink(source, target);
|
|
10947
11959
|
summary.linked.push(entry.name);
|
|
10948
11960
|
} catch (err) {
|
|
10949
11961
|
summary.failed.push({
|
|
@@ -11032,7 +12044,7 @@ function registerAgentCommands(program2) {
|
|
|
11032
12044
|
}
|
|
11033
12045
|
const installSummaries = [];
|
|
11034
12046
|
if (opts.installSkills !== false) {
|
|
11035
|
-
const skillsDir = await resolvePaperclipSkillsDir(__moduleDir, [
|
|
12047
|
+
const skillsDir = await resolvePaperclipSkillsDir(__moduleDir, [path17.resolve(process.cwd(), "skills")]);
|
|
11036
12048
|
if (!skillsDir) {
|
|
11037
12049
|
throw new Error(
|
|
11038
12050
|
"Could not locate local Paperclip skills directory. Expected ./skills in the repo checkout."
|
|
@@ -11265,8 +12277,8 @@ function registerActivityCommands(program2) {
|
|
|
11265
12277
|
if (opts.entityType) params.set("entityType", opts.entityType);
|
|
11266
12278
|
if (opts.entityId) params.set("entityId", opts.entityId);
|
|
11267
12279
|
const query = params.toString();
|
|
11268
|
-
const
|
|
11269
|
-
const rows = await ctx.api.get(
|
|
12280
|
+
const path28 = `/api/companies/${ctx.companyId}/activity${query ? `?${query}` : ""}`;
|
|
12281
|
+
const rows = await ctx.api.get(path28) ?? [];
|
|
11270
12282
|
if (ctx.json) {
|
|
11271
12283
|
printOutput(rows, { json: true });
|
|
11272
12284
|
return;
|
|
@@ -11315,11 +12327,11 @@ function registerDashboardCommands(program2) {
|
|
|
11315
12327
|
|
|
11316
12328
|
// src/config/data-dir.ts
|
|
11317
12329
|
init_home();
|
|
11318
|
-
import
|
|
12330
|
+
import path18 from "node:path";
|
|
11319
12331
|
function applyDataDirOverride(options, support = {}) {
|
|
11320
12332
|
const rawDataDir = options.dataDir?.trim();
|
|
11321
12333
|
if (!rawDataDir) return null;
|
|
11322
|
-
const resolvedDataDir =
|
|
12334
|
+
const resolvedDataDir = path18.resolve(expandHomePrefix(rawDataDir));
|
|
11323
12335
|
process.env.PAPERCLIP_HOME = resolvedDataDir;
|
|
11324
12336
|
if (support.hasConfigOption) {
|
|
11325
12337
|
const hasConfigOverride = Boolean(options.config?.trim()) || Boolean(process.env.PAPERCLIP_CONFIG?.trim());
|
|
@@ -11346,35 +12358,35 @@ init_store();
|
|
|
11346
12358
|
// src/commands/gtm.ts
|
|
11347
12359
|
init_src();
|
|
11348
12360
|
init_home();
|
|
11349
|
-
import
|
|
11350
|
-
import
|
|
12361
|
+
import fs16 from "node:fs";
|
|
12362
|
+
import path19 from "node:path";
|
|
11351
12363
|
import { spawn } from "node:child_process";
|
|
11352
|
-
import
|
|
12364
|
+
import pc22 from "picocolors";
|
|
11353
12365
|
function resolveGtmStatePath() {
|
|
11354
|
-
return
|
|
12366
|
+
return path19.resolve(resolvePaperclipHomeDir(), "gtm", "state.json");
|
|
11355
12367
|
}
|
|
11356
12368
|
function readState() {
|
|
11357
12369
|
const filePath = resolveGtmStatePath();
|
|
11358
|
-
if (!
|
|
11359
|
-
return coerceGtmState(JSON.parse(
|
|
12370
|
+
if (!fs16.existsSync(filePath)) return createDefaultGtmState();
|
|
12371
|
+
return coerceGtmState(JSON.parse(fs16.readFileSync(filePath, "utf-8")));
|
|
11360
12372
|
}
|
|
11361
12373
|
function writeState(state) {
|
|
11362
12374
|
const filePath = resolveGtmStatePath();
|
|
11363
|
-
|
|
11364
|
-
|
|
12375
|
+
fs16.mkdirSync(path19.dirname(filePath), { recursive: true });
|
|
12376
|
+
fs16.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
11365
12377
|
}
|
|
11366
12378
|
function launchWorkflow(state) {
|
|
11367
12379
|
const runnerPath = state.workflow.runnerPath?.trim();
|
|
11368
12380
|
if (!runnerPath) {
|
|
11369
12381
|
throw new Error("No local SDR runner configured.");
|
|
11370
12382
|
}
|
|
11371
|
-
if (!
|
|
12383
|
+
if (!fs16.existsSync(runnerPath)) {
|
|
11372
12384
|
throw new Error(`Runner not found at ${runnerPath}`);
|
|
11373
12385
|
}
|
|
11374
12386
|
const args = runnerPath.endsWith(".mjs") || runnerPath.endsWith(".js") ? [runnerPath] : [];
|
|
11375
12387
|
const command = args.length > 0 ? process.execPath : runnerPath;
|
|
11376
12388
|
const child = spawn(command, args, {
|
|
11377
|
-
cwd:
|
|
12389
|
+
cwd: path19.dirname(runnerPath),
|
|
11378
12390
|
detached: true,
|
|
11379
12391
|
stdio: "ignore"
|
|
11380
12392
|
});
|
|
@@ -11399,7 +12411,7 @@ function printJsonOrMessage(payload, json, message) {
|
|
|
11399
12411
|
console.log(JSON.stringify(payload, null, 2));
|
|
11400
12412
|
return;
|
|
11401
12413
|
}
|
|
11402
|
-
if (message) console.log(
|
|
12414
|
+
if (message) console.log(pc22.green(message));
|
|
11403
12415
|
console.log(payload);
|
|
11404
12416
|
}
|
|
11405
12417
|
function registerGtmCommands(program2) {
|
|
@@ -11412,7 +12424,7 @@ function registerGtmCommands(program2) {
|
|
|
11412
12424
|
if (opts.internalSocialsPath) state.workflow.referenceInterfaces.internalSocialsPath = opts.internalSocialsPath.trim();
|
|
11413
12425
|
if (opts.localSdrPath) {
|
|
11414
12426
|
state.workflow.referenceInterfaces.localSdrPath = opts.localSdrPath.trim();
|
|
11415
|
-
state.workflow.runnerPath =
|
|
12427
|
+
state.workflow.runnerPath = path19.resolve(opts.localSdrPath.trim(), "sdr-bot.mjs");
|
|
11416
12428
|
}
|
|
11417
12429
|
writeState(state);
|
|
11418
12430
|
const view = toGtmViewModel(state);
|
|
@@ -11464,18 +12476,18 @@ import {
|
|
|
11464
12476
|
symlinkSync,
|
|
11465
12477
|
writeFileSync
|
|
11466
12478
|
} from "node:fs";
|
|
11467
|
-
import
|
|
11468
|
-
import
|
|
12479
|
+
import os5 from "node:os";
|
|
12480
|
+
import path21 from "node:path";
|
|
11469
12481
|
import { execFileSync } from "node:child_process";
|
|
11470
|
-
import { createServer } from "node:net";
|
|
11471
|
-
import * as
|
|
11472
|
-
import
|
|
12482
|
+
import { createServer as createServer2 } from "node:net";
|
|
12483
|
+
import * as p16 from "@clack/prompts";
|
|
12484
|
+
import pc23 from "picocolors";
|
|
11473
12485
|
import { eq as eq2 } from "drizzle-orm";
|
|
11474
12486
|
|
|
11475
12487
|
// src/commands/worktree-lib.ts
|
|
11476
12488
|
init_home();
|
|
11477
12489
|
import { randomInt } from "node:crypto";
|
|
11478
|
-
import
|
|
12490
|
+
import path20 from "node:path";
|
|
11479
12491
|
var DEFAULT_WORKTREE_HOME = "~/.paperclip-worktrees";
|
|
11480
12492
|
var WORKTREE_SEED_MODES = ["minimal", "full"];
|
|
11481
12493
|
var MINIMAL_WORKTREE_EXCLUDED_TABLES = [
|
|
@@ -11523,7 +12535,7 @@ function sanitizeWorktreeInstanceId(rawValue) {
|
|
|
11523
12535
|
return normalized || "worktree";
|
|
11524
12536
|
}
|
|
11525
12537
|
function resolveSuggestedWorktreeName(cwd, explicitName) {
|
|
11526
|
-
return nonEmpty(explicitName) ??
|
|
12538
|
+
return nonEmpty(explicitName) ?? path20.basename(path20.resolve(cwd));
|
|
11527
12539
|
}
|
|
11528
12540
|
function hslComponentToHex(n) {
|
|
11529
12541
|
return Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
|
|
@@ -11563,24 +12575,24 @@ function generateWorktreeColor() {
|
|
|
11563
12575
|
return hslToHex(randomInt(0, 360), 68, 56);
|
|
11564
12576
|
}
|
|
11565
12577
|
function resolveWorktreeLocalPaths(opts) {
|
|
11566
|
-
const cwd =
|
|
11567
|
-
const homeDir =
|
|
11568
|
-
const instanceRoot =
|
|
11569
|
-
const repoConfigDir =
|
|
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");
|
|
11570
12582
|
return {
|
|
11571
12583
|
cwd,
|
|
11572
12584
|
repoConfigDir,
|
|
11573
|
-
configPath:
|
|
11574
|
-
envPath:
|
|
12585
|
+
configPath: path20.resolve(repoConfigDir, "config.json"),
|
|
12586
|
+
envPath: path20.resolve(repoConfigDir, ".env"),
|
|
11575
12587
|
homeDir,
|
|
11576
12588
|
instanceId: opts.instanceId,
|
|
11577
12589
|
instanceRoot,
|
|
11578
|
-
contextPath:
|
|
11579
|
-
embeddedPostgresDataDir:
|
|
11580
|
-
backupDir:
|
|
11581
|
-
logDir:
|
|
11582
|
-
secretsKeyFilePath:
|
|
11583
|
-
storageDir:
|
|
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")
|
|
11584
12596
|
};
|
|
11585
12597
|
}
|
|
11586
12598
|
function rewriteLocalUrlPort(rawUrl, port) {
|
|
@@ -11686,7 +12698,7 @@ function isCurrentSourceConfigPath(sourceConfigPath) {
|
|
|
11686
12698
|
if (!currentConfigPath || currentConfigPath.trim().length === 0) {
|
|
11687
12699
|
return false;
|
|
11688
12700
|
}
|
|
11689
|
-
return
|
|
12701
|
+
return path21.resolve(currentConfigPath) === path21.resolve(sourceConfigPath);
|
|
11690
12702
|
}
|
|
11691
12703
|
var WORKTREE_NAME_PREFIX = "paperclip-";
|
|
11692
12704
|
function resolveWorktreeMakeName(name) {
|
|
@@ -11708,7 +12720,7 @@ function resolveWorktreeStartPoint(explicit) {
|
|
|
11708
12720
|
return explicit ?? nonEmpty2(process.env.PAPERCLIP_WORKTREE_START_POINT) ?? void 0;
|
|
11709
12721
|
}
|
|
11710
12722
|
function resolveWorktreeMakeTargetPath(name) {
|
|
11711
|
-
return
|
|
12723
|
+
return path21.resolve(os5.homedir(), resolveWorktreeMakeName(name));
|
|
11712
12724
|
}
|
|
11713
12725
|
function extractExecSyncErrorMessage(error) {
|
|
11714
12726
|
if (!error || typeof error !== "object") {
|
|
@@ -11764,7 +12776,7 @@ function readRunningPostmasterPid(postmasterPidFile) {
|
|
|
11764
12776
|
}
|
|
11765
12777
|
async function isPortAvailable(port) {
|
|
11766
12778
|
return await new Promise((resolve2) => {
|
|
11767
|
-
const server =
|
|
12779
|
+
const server = createServer2();
|
|
11768
12780
|
server.unref();
|
|
11769
12781
|
server.once("error", () => resolve2(false));
|
|
11770
12782
|
server.listen(port, "127.0.0.1", () => {
|
|
@@ -11814,10 +12826,10 @@ function detectGitWorkspaceInfo(cwd) {
|
|
|
11814
12826
|
stdio: ["ignore", "pipe", "ignore"]
|
|
11815
12827
|
}).trim();
|
|
11816
12828
|
return {
|
|
11817
|
-
root:
|
|
11818
|
-
commonDir:
|
|
11819
|
-
gitDir:
|
|
11820
|
-
hooksPath:
|
|
12829
|
+
root: path21.resolve(root),
|
|
12830
|
+
commonDir: path21.resolve(root, commonDirRaw),
|
|
12831
|
+
gitDir: path21.resolve(root, gitDirRaw),
|
|
12832
|
+
hooksPath: path21.resolve(root, hooksPathRaw)
|
|
11821
12833
|
};
|
|
11822
12834
|
} catch {
|
|
11823
12835
|
return null;
|
|
@@ -11830,8 +12842,8 @@ function copyDirectoryContents(sourceDir, targetDir) {
|
|
|
11830
12842
|
mkdirSync2(targetDir, { recursive: true });
|
|
11831
12843
|
let copied = false;
|
|
11832
12844
|
for (const entry of entries) {
|
|
11833
|
-
const sourcePath =
|
|
11834
|
-
const targetPath =
|
|
12845
|
+
const sourcePath = path21.resolve(sourceDir, entry.name);
|
|
12846
|
+
const targetPath = path21.resolve(targetDir, entry.name);
|
|
11835
12847
|
if (entry.isDirectory()) {
|
|
11836
12848
|
mkdirSync2(targetPath, { recursive: true });
|
|
11837
12849
|
copyDirectoryContents(sourcePath, targetPath);
|
|
@@ -11857,7 +12869,7 @@ function copyGitHooksToWorktreeGitDir(cwd) {
|
|
|
11857
12869
|
const workspace = detectGitWorkspaceInfo(cwd);
|
|
11858
12870
|
if (!workspace) return null;
|
|
11859
12871
|
const sourceHooksPath = workspace.hooksPath;
|
|
11860
|
-
const targetHooksPath =
|
|
12872
|
+
const targetHooksPath = path21.resolve(workspace.gitDir, "hooks");
|
|
11861
12873
|
if (sourceHooksPath === targetHooksPath) {
|
|
11862
12874
|
return {
|
|
11863
12875
|
sourceHooksPath,
|
|
@@ -11872,17 +12884,17 @@ function copyGitHooksToWorktreeGitDir(cwd) {
|
|
|
11872
12884
|
};
|
|
11873
12885
|
}
|
|
11874
12886
|
function rebindWorkspaceCwd(input) {
|
|
11875
|
-
const sourceRepoRoot =
|
|
11876
|
-
const targetRepoRoot =
|
|
11877
|
-
const workspaceCwd =
|
|
11878
|
-
const relative =
|
|
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);
|
|
11879
12891
|
if (!relative || relative === "") {
|
|
11880
12892
|
return targetRepoRoot;
|
|
11881
12893
|
}
|
|
11882
|
-
if (relative.startsWith("..") ||
|
|
12894
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative)) {
|
|
11883
12895
|
return null;
|
|
11884
12896
|
}
|
|
11885
|
-
return
|
|
12897
|
+
return path21.resolve(targetRepoRoot, relative);
|
|
11886
12898
|
}
|
|
11887
12899
|
async function rebindSeededProjectWorkspaces(input) {
|
|
11888
12900
|
const targetRepo = detectGitWorkspaceInfo(input.currentCwd);
|
|
@@ -11908,7 +12920,7 @@ async function rebindSeededProjectWorkspaces(input) {
|
|
|
11908
12920
|
workspaceCwd
|
|
11909
12921
|
});
|
|
11910
12922
|
if (!reboundCwd) continue;
|
|
11911
|
-
const normalizedCurrent =
|
|
12923
|
+
const normalizedCurrent = path21.resolve(workspaceCwd);
|
|
11912
12924
|
if (reboundCwd === normalizedCurrent) continue;
|
|
11913
12925
|
if (!existsSync2(reboundCwd)) continue;
|
|
11914
12926
|
await db.update(projectWorkspaces).set({
|
|
@@ -11927,14 +12939,14 @@ async function rebindSeededProjectWorkspaces(input) {
|
|
|
11927
12939
|
}
|
|
11928
12940
|
}
|
|
11929
12941
|
function resolveSourceConfigPath(opts) {
|
|
11930
|
-
if (opts.sourceConfigPathOverride) return
|
|
11931
|
-
if (opts.fromConfig) return
|
|
12942
|
+
if (opts.sourceConfigPathOverride) return path21.resolve(opts.sourceConfigPathOverride);
|
|
12943
|
+
if (opts.fromConfig) return path21.resolve(opts.fromConfig);
|
|
11932
12944
|
if (!opts.fromDataDir && !opts.fromInstance) {
|
|
11933
12945
|
return resolveConfigPath();
|
|
11934
12946
|
}
|
|
11935
|
-
const sourceHome =
|
|
12947
|
+
const sourceHome = path21.resolve(expandHomePrefix(opts.fromDataDir ?? "~/.paperclip"));
|
|
11936
12948
|
const sourceInstanceId = sanitizeWorktreeInstanceId(opts.fromInstance ?? "default");
|
|
11937
|
-
return
|
|
12949
|
+
return path21.resolve(sourceHome, "instances", sourceInstanceId, "config.json");
|
|
11938
12950
|
}
|
|
11939
12951
|
function resolveSourceConnectionString(config, envEntries, portOverride) {
|
|
11940
12952
|
if (config.database.mode === "postgres") {
|
|
@@ -11953,7 +12965,7 @@ function copySeededSecretsKey(input) {
|
|
|
11953
12965
|
if (input.sourceConfig.secrets.provider !== "local_encrypted") {
|
|
11954
12966
|
return;
|
|
11955
12967
|
}
|
|
11956
|
-
mkdirSync2(
|
|
12968
|
+
mkdirSync2(path21.dirname(input.targetKeyFilePath), { recursive: true });
|
|
11957
12969
|
const allowProcessEnvFallback = isCurrentSourceConfigPath(input.sourceConfigPath);
|
|
11958
12970
|
const sourceInlineMasterKey = nonEmpty2(input.sourceEnvEntries.PAPERCLIP_SECRETS_MASTER_KEY) ?? (allowProcessEnvFallback ? nonEmpty2(process.env.PAPERCLIP_SECRETS_MASTER_KEY) : null);
|
|
11959
12971
|
if (sourceInlineMasterKey) {
|
|
@@ -11992,7 +13004,7 @@ async function ensureEmbeddedPostgres(dataDir, preferredPort) {
|
|
|
11992
13004
|
"Embedded PostgreSQL support requires dependency `embedded-postgres`. Reinstall dependencies and try again."
|
|
11993
13005
|
);
|
|
11994
13006
|
}
|
|
11995
|
-
const postmasterPidFile =
|
|
13007
|
+
const postmasterPidFile = path21.resolve(dataDir, "postmaster.pid");
|
|
11996
13008
|
const runningPid = readRunningPostmasterPid(postmasterPidFile);
|
|
11997
13009
|
if (runningPid) {
|
|
11998
13010
|
return {
|
|
@@ -12015,7 +13027,7 @@ async function ensureEmbeddedPostgres(dataDir, preferredPort) {
|
|
|
12015
13027
|
onError: () => {
|
|
12016
13028
|
}
|
|
12017
13029
|
});
|
|
12018
|
-
if (!existsSync2(
|
|
13030
|
+
if (!existsSync2(path21.resolve(dataDir, "PG_VERSION"))) {
|
|
12019
13031
|
await instance.initialise();
|
|
12020
13032
|
}
|
|
12021
13033
|
if (existsSync2(postmasterPidFile)) {
|
|
@@ -12056,7 +13068,7 @@ async function seedWorktreeDatabase(input) {
|
|
|
12056
13068
|
);
|
|
12057
13069
|
const backup = await runDatabaseBackup({
|
|
12058
13070
|
connectionString: sourceConnectionString,
|
|
12059
|
-
backupDir:
|
|
13071
|
+
backupDir: path21.resolve(input.targetPaths.backupDir, "seed"),
|
|
12060
13072
|
retentionDays: 7,
|
|
12061
13073
|
filenamePrefix: `${input.instanceId}-seed`,
|
|
12062
13074
|
includeMigrationJournal: true,
|
|
@@ -12154,8 +13166,8 @@ async function runWorktreeInit(opts) {
|
|
|
12154
13166
|
`Cannot seed worktree database because source config was not found at ${sourceConfigPath}. Use --no-seed or provide --from-config.`
|
|
12155
13167
|
);
|
|
12156
13168
|
}
|
|
12157
|
-
const
|
|
12158
|
-
|
|
13169
|
+
const spinner5 = p16.spinner();
|
|
13170
|
+
spinner5.start(`Seeding isolated worktree database from source instance (${seedMode})...`);
|
|
12159
13171
|
try {
|
|
12160
13172
|
const seeded = await seedWorktreeDatabase({
|
|
12161
13173
|
sourceConfigPath,
|
|
@@ -12167,46 +13179,46 @@ async function runWorktreeInit(opts) {
|
|
|
12167
13179
|
});
|
|
12168
13180
|
seedSummary = seeded.backupSummary;
|
|
12169
13181
|
reboundWorkspaceSummary = seeded.reboundWorkspaces;
|
|
12170
|
-
|
|
13182
|
+
spinner5.stop(`Seeded isolated worktree database (${seedMode}).`);
|
|
12171
13183
|
} catch (error) {
|
|
12172
|
-
|
|
13184
|
+
spinner5.stop(pc23.red("Failed to seed worktree database."));
|
|
12173
13185
|
throw error;
|
|
12174
13186
|
}
|
|
12175
13187
|
}
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
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}`));
|
|
12182
13194
|
if (copiedGitHooks?.copied) {
|
|
12183
|
-
|
|
12184
|
-
|
|
13195
|
+
p16.log.message(
|
|
13196
|
+
pc23.dim(`Mirrored git hooks: ${copiedGitHooks.sourceHooksPath} -> ${copiedGitHooks.targetHooksPath}`)
|
|
12185
13197
|
);
|
|
12186
13198
|
}
|
|
12187
13199
|
if (seedSummary) {
|
|
12188
|
-
|
|
12189
|
-
|
|
13200
|
+
p16.log.message(pc23.dim(`Seed mode: ${seedMode}`));
|
|
13201
|
+
p16.log.message(pc23.dim(`Seed snapshot: ${seedSummary}`));
|
|
12190
13202
|
for (const rebound of reboundWorkspaceSummary) {
|
|
12191
|
-
|
|
12192
|
-
|
|
13203
|
+
p16.log.message(
|
|
13204
|
+
pc23.dim(`Rebound workspace ${rebound.name}: ${rebound.fromCwd} -> ${rebound.toCwd}`)
|
|
12193
13205
|
);
|
|
12194
13206
|
}
|
|
12195
13207
|
}
|
|
12196
|
-
|
|
12197
|
-
|
|
13208
|
+
p16.outro(
|
|
13209
|
+
pc23.green(
|
|
12198
13210
|
`Worktree ready. Run Paperclip inside this repo and the CLI/server will use ${paths.instanceId} automatically.`
|
|
12199
13211
|
)
|
|
12200
13212
|
);
|
|
12201
13213
|
}
|
|
12202
13214
|
async function worktreeInitCommand(opts) {
|
|
12203
13215
|
printPaperclipCliBanner();
|
|
12204
|
-
|
|
13216
|
+
p16.intro(pc23.bgCyan(pc23.black(" paperclipai worktree init ")));
|
|
12205
13217
|
await runWorktreeInit(opts);
|
|
12206
13218
|
}
|
|
12207
13219
|
async function worktreeMakeCommand(nameArg, opts) {
|
|
12208
13220
|
printPaperclipCliBanner();
|
|
12209
|
-
|
|
13221
|
+
p16.intro(pc23.bgCyan(pc23.black(" paperclipai worktree:make ")));
|
|
12210
13222
|
const name = resolveWorktreeMakeName(nameArg);
|
|
12211
13223
|
const startPoint = resolveWorktreeStartPoint(opts.startPoint);
|
|
12212
13224
|
const sourceCwd = process.cwd();
|
|
@@ -12215,7 +13227,7 @@ async function worktreeMakeCommand(nameArg, opts) {
|
|
|
12215
13227
|
if (existsSync2(targetPath)) {
|
|
12216
13228
|
throw new Error(`Target path already exists: ${targetPath}`);
|
|
12217
13229
|
}
|
|
12218
|
-
mkdirSync2(
|
|
13230
|
+
mkdirSync2(path21.dirname(targetPath), { recursive: true });
|
|
12219
13231
|
if (startPoint) {
|
|
12220
13232
|
const [remote] = startPoint.split("/", 1);
|
|
12221
13233
|
try {
|
|
@@ -12235,19 +13247,19 @@ async function worktreeMakeCommand(nameArg, opts) {
|
|
|
12235
13247
|
branchExists: !startPoint && localBranchExists(sourceCwd, name),
|
|
12236
13248
|
startPoint
|
|
12237
13249
|
});
|
|
12238
|
-
const
|
|
12239
|
-
|
|
13250
|
+
const spinner5 = p16.spinner();
|
|
13251
|
+
spinner5.start(`Creating git worktree at ${targetPath}...`);
|
|
12240
13252
|
try {
|
|
12241
13253
|
execFileSync("git", worktreeArgs, {
|
|
12242
13254
|
cwd: sourceCwd,
|
|
12243
13255
|
stdio: ["ignore", "pipe", "pipe"]
|
|
12244
13256
|
});
|
|
12245
|
-
|
|
13257
|
+
spinner5.stop(`Created git worktree at ${targetPath}.`);
|
|
12246
13258
|
} catch (error) {
|
|
12247
|
-
|
|
13259
|
+
spinner5.stop(pc23.red("Failed to create git worktree."));
|
|
12248
13260
|
throw new Error(extractExecSyncErrorMessage(error) ?? String(error));
|
|
12249
13261
|
}
|
|
12250
|
-
const installSpinner =
|
|
13262
|
+
const installSpinner = p16.spinner();
|
|
12251
13263
|
installSpinner.start("Installing dependencies...");
|
|
12252
13264
|
try {
|
|
12253
13265
|
execFileSync("pnpm", ["install"], {
|
|
@@ -12256,8 +13268,8 @@ async function worktreeMakeCommand(nameArg, opts) {
|
|
|
12256
13268
|
});
|
|
12257
13269
|
installSpinner.stop("Installed dependencies.");
|
|
12258
13270
|
} catch (error) {
|
|
12259
|
-
installSpinner.stop(
|
|
12260
|
-
|
|
13271
|
+
installSpinner.stop(pc23.yellow("Failed to install dependencies (continuing anyway)."));
|
|
13272
|
+
p16.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
|
|
12261
13273
|
}
|
|
12262
13274
|
const originalCwd = process.cwd();
|
|
12263
13275
|
try {
|
|
@@ -12272,13 +13284,13 @@ async function worktreeMakeCommand(nameArg, opts) {
|
|
|
12272
13284
|
} finally {
|
|
12273
13285
|
process.chdir(originalCwd);
|
|
12274
13286
|
}
|
|
12275
|
-
const bootstrapScript =
|
|
13287
|
+
const bootstrapScript = path21.resolve(sourceCwd, "scripts/worktree-bootstrap.mjs");
|
|
12276
13288
|
if (existsSync2(bootstrapScript)) {
|
|
12277
|
-
|
|
13289
|
+
p16.log.message(pc23.dim(`Running worktree bootstrap in ${targetPath}...`));
|
|
12278
13290
|
try {
|
|
12279
13291
|
execFileSync("node", [bootstrapScript], { cwd: targetPath, stdio: "inherit" });
|
|
12280
13292
|
} catch (error) {
|
|
12281
|
-
|
|
13293
|
+
p16.log.warning(`Bootstrap failed: ${extractExecSyncErrorMessage(error) ?? String(error)}`);
|
|
12282
13294
|
}
|
|
12283
13295
|
}
|
|
12284
13296
|
}
|
|
@@ -12357,30 +13369,30 @@ function worktreePathHasUncommittedChanges(worktreePath) {
|
|
|
12357
13369
|
}
|
|
12358
13370
|
async function worktreeCleanupCommand(nameArg, opts) {
|
|
12359
13371
|
printPaperclipCliBanner();
|
|
12360
|
-
|
|
13372
|
+
p16.intro(pc23.bgCyan(pc23.black(" paperclipai worktree:cleanup ")));
|
|
12361
13373
|
const name = resolveWorktreeMakeName(nameArg);
|
|
12362
13374
|
const sourceCwd = process.cwd();
|
|
12363
13375
|
const targetPath = resolveWorktreeMakeTargetPath(name);
|
|
12364
13376
|
const instanceId = sanitizeWorktreeInstanceId(opts.instance ?? name);
|
|
12365
|
-
const homeDir =
|
|
12366
|
-
const instanceRoot =
|
|
13377
|
+
const homeDir = path21.resolve(expandHomePrefix(resolveWorktreeHome(opts.home)));
|
|
13378
|
+
const instanceRoot = path21.resolve(homeDir, "instances", instanceId);
|
|
12367
13379
|
const hasBranch = localBranchExists(sourceCwd, name);
|
|
12368
13380
|
const hasTargetDir = existsSync2(targetPath);
|
|
12369
13381
|
const hasInstanceData = existsSync2(instanceRoot);
|
|
12370
13382
|
const worktrees = parseGitWorktreeList(sourceCwd);
|
|
12371
13383
|
const linkedWorktree = worktrees.find(
|
|
12372
|
-
(wt) => wt.branch === `refs/heads/${name}` ||
|
|
13384
|
+
(wt) => wt.branch === `refs/heads/${name}` || path21.resolve(wt.worktree) === path21.resolve(targetPath)
|
|
12373
13385
|
);
|
|
12374
13386
|
if (!hasBranch && !hasTargetDir && !hasInstanceData && !linkedWorktree) {
|
|
12375
|
-
|
|
12376
|
-
|
|
13387
|
+
p16.log.info("Nothing to clean up \u2014 no branch, worktree directory, or instance data found.");
|
|
13388
|
+
p16.outro(pc23.green("Already clean."));
|
|
12377
13389
|
return;
|
|
12378
13390
|
}
|
|
12379
13391
|
const problems = [];
|
|
12380
13392
|
if (hasBranch && branchHasUniqueCommits(sourceCwd, name)) {
|
|
12381
13393
|
const onRemote = branchExistsOnAnyRemote(sourceCwd, name);
|
|
12382
13394
|
if (onRemote) {
|
|
12383
|
-
|
|
13395
|
+
p16.log.info(
|
|
12384
13396
|
`Branch "${name}" has unique local commits, but the branch also exists on a remote \u2014 safe to delete locally.`
|
|
12385
13397
|
);
|
|
12386
13398
|
} else {
|
|
@@ -12396,20 +13408,20 @@ async function worktreeCleanupCommand(nameArg, opts) {
|
|
|
12396
13408
|
}
|
|
12397
13409
|
if (problems.length > 0 && !opts.force) {
|
|
12398
13410
|
for (const problem of problems) {
|
|
12399
|
-
|
|
13411
|
+
p16.log.error(problem);
|
|
12400
13412
|
}
|
|
12401
13413
|
throw new Error("Safety checks failed. Resolve the issues above or re-run with --force.");
|
|
12402
13414
|
}
|
|
12403
13415
|
if (problems.length > 0 && opts.force) {
|
|
12404
13416
|
for (const problem of problems) {
|
|
12405
|
-
|
|
13417
|
+
p16.log.warning(`Overridden by --force: ${problem}`);
|
|
12406
13418
|
}
|
|
12407
13419
|
}
|
|
12408
13420
|
if (linkedWorktree) {
|
|
12409
13421
|
const worktreeDirExists = existsSync2(linkedWorktree.worktree);
|
|
12410
|
-
const
|
|
13422
|
+
const spinner5 = p16.spinner();
|
|
12411
13423
|
if (worktreeDirExists) {
|
|
12412
|
-
|
|
13424
|
+
spinner5.start(`Removing git worktree at ${linkedWorktree.worktree}...`);
|
|
12413
13425
|
try {
|
|
12414
13426
|
const removeArgs = ["worktree", "remove", linkedWorktree.worktree];
|
|
12415
13427
|
if (opts.force) removeArgs.push("--force");
|
|
@@ -12417,18 +13429,18 @@ async function worktreeCleanupCommand(nameArg, opts) {
|
|
|
12417
13429
|
cwd: sourceCwd,
|
|
12418
13430
|
stdio: ["ignore", "pipe", "pipe"]
|
|
12419
13431
|
});
|
|
12420
|
-
|
|
13432
|
+
spinner5.stop(`Removed git worktree at ${linkedWorktree.worktree}.`);
|
|
12421
13433
|
} catch (error) {
|
|
12422
|
-
|
|
12423
|
-
|
|
13434
|
+
spinner5.stop(pc23.yellow(`Could not remove worktree cleanly, will prune instead.`));
|
|
13435
|
+
p16.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
|
|
12424
13436
|
}
|
|
12425
13437
|
} else {
|
|
12426
|
-
|
|
13438
|
+
spinner5.start("Pruning stale worktree entry...");
|
|
12427
13439
|
execFileSync("git", ["worktree", "prune"], {
|
|
12428
13440
|
cwd: sourceCwd,
|
|
12429
13441
|
stdio: ["ignore", "pipe", "pipe"]
|
|
12430
13442
|
});
|
|
12431
|
-
|
|
13443
|
+
spinner5.stop("Pruned stale worktree entry.");
|
|
12432
13444
|
}
|
|
12433
13445
|
} else {
|
|
12434
13446
|
execFileSync("git", ["worktree", "prune"], {
|
|
@@ -12437,33 +13449,33 @@ async function worktreeCleanupCommand(nameArg, opts) {
|
|
|
12437
13449
|
});
|
|
12438
13450
|
}
|
|
12439
13451
|
if (existsSync2(targetPath)) {
|
|
12440
|
-
const
|
|
12441
|
-
|
|
13452
|
+
const spinner5 = p16.spinner();
|
|
13453
|
+
spinner5.start(`Removing worktree directory ${targetPath}...`);
|
|
12442
13454
|
rmSync(targetPath, { recursive: true, force: true });
|
|
12443
|
-
|
|
13455
|
+
spinner5.stop(`Removed worktree directory ${targetPath}.`);
|
|
12444
13456
|
}
|
|
12445
13457
|
if (localBranchExists(sourceCwd, name)) {
|
|
12446
|
-
const
|
|
12447
|
-
|
|
13458
|
+
const spinner5 = p16.spinner();
|
|
13459
|
+
spinner5.start(`Deleting local branch "${name}"...`);
|
|
12448
13460
|
try {
|
|
12449
13461
|
const deleteFlag = opts.force ? "-D" : "-d";
|
|
12450
13462
|
execFileSync("git", ["branch", deleteFlag, name], {
|
|
12451
13463
|
cwd: sourceCwd,
|
|
12452
13464
|
stdio: ["ignore", "pipe", "pipe"]
|
|
12453
13465
|
});
|
|
12454
|
-
|
|
13466
|
+
spinner5.stop(`Deleted local branch "${name}".`);
|
|
12455
13467
|
} catch (error) {
|
|
12456
|
-
|
|
12457
|
-
|
|
13468
|
+
spinner5.stop(pc23.yellow(`Could not delete branch "${name}".`));
|
|
13469
|
+
p16.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
|
|
12458
13470
|
}
|
|
12459
13471
|
}
|
|
12460
13472
|
if (existsSync2(instanceRoot)) {
|
|
12461
|
-
const
|
|
12462
|
-
|
|
13473
|
+
const spinner5 = p16.spinner();
|
|
13474
|
+
spinner5.start(`Removing instance data at ${instanceRoot}...`);
|
|
12463
13475
|
rmSync(instanceRoot, { recursive: true, force: true });
|
|
12464
|
-
|
|
13476
|
+
spinner5.stop(`Removed instance data at ${instanceRoot}.`);
|
|
12465
13477
|
}
|
|
12466
|
-
|
|
13478
|
+
p16.outro(pc23.green("Cleanup complete."));
|
|
12467
13479
|
}
|
|
12468
13480
|
async function worktreeEnvCommand(opts) {
|
|
12469
13481
|
const configPath = resolveConfigPath(opts.config);
|
|
@@ -12491,27 +13503,27 @@ function registerWorktreeCommands(program2) {
|
|
|
12491
13503
|
}
|
|
12492
13504
|
|
|
12493
13505
|
// src/commands/client/plugin.ts
|
|
12494
|
-
import
|
|
12495
|
-
import
|
|
13506
|
+
import path22 from "node:path";
|
|
13507
|
+
import pc24 from "picocolors";
|
|
12496
13508
|
function resolvePackageArg(packageArg, isLocal) {
|
|
12497
13509
|
if (!isLocal) return packageArg;
|
|
12498
|
-
if (
|
|
13510
|
+
if (path22.isAbsolute(packageArg)) return packageArg;
|
|
12499
13511
|
if (packageArg.startsWith("~")) {
|
|
12500
13512
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
12501
|
-
return
|
|
13513
|
+
return path22.resolve(home, packageArg.slice(1).replace(/^[\\/]/, ""));
|
|
12502
13514
|
}
|
|
12503
|
-
return
|
|
13515
|
+
return path22.resolve(process.cwd(), packageArg);
|
|
12504
13516
|
}
|
|
12505
|
-
function formatPlugin(
|
|
12506
|
-
const statusColor =
|
|
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);
|
|
12507
13519
|
const parts = [
|
|
12508
|
-
`key=${
|
|
13520
|
+
`key=${pc24.bold(p20.pluginKey)}`,
|
|
12509
13521
|
`status=${statusColor}`,
|
|
12510
|
-
`version=${
|
|
12511
|
-
`id=${
|
|
13522
|
+
`version=${p20.version}`,
|
|
13523
|
+
`id=${pc24.dim(p20.id)}`
|
|
12512
13524
|
];
|
|
12513
|
-
if (
|
|
12514
|
-
parts.push(`error=${
|
|
13525
|
+
if (p20.lastError) {
|
|
13526
|
+
parts.push(`error=${pc24.red(p20.lastError.slice(0, 80))}`);
|
|
12515
13527
|
}
|
|
12516
13528
|
return parts.join(" ");
|
|
12517
13529
|
}
|
|
@@ -12529,11 +13541,11 @@ function registerPluginCommands(program2) {
|
|
|
12529
13541
|
}
|
|
12530
13542
|
const rows = plugins2 ?? [];
|
|
12531
13543
|
if (rows.length === 0) {
|
|
12532
|
-
console.log(
|
|
13544
|
+
console.log(pc24.dim("No plugins installed."));
|
|
12533
13545
|
return;
|
|
12534
13546
|
}
|
|
12535
|
-
for (const
|
|
12536
|
-
console.log(formatPlugin(
|
|
13547
|
+
for (const p20 of rows) {
|
|
13548
|
+
console.log(formatPlugin(p20));
|
|
12537
13549
|
}
|
|
12538
13550
|
} catch (err) {
|
|
12539
13551
|
handleCommandError(err);
|
|
@@ -12550,7 +13562,7 @@ function registerPluginCommands(program2) {
|
|
|
12550
13562
|
const resolvedPackage = resolvePackageArg(packageArg, isLocal);
|
|
12551
13563
|
if (!ctx.json) {
|
|
12552
13564
|
console.log(
|
|
12553
|
-
|
|
13565
|
+
pc24.dim(
|
|
12554
13566
|
isLocal ? `Installing plugin from local path: ${resolvedPackage}` : `Installing plugin: ${resolvedPackage}${opts.version ? `@${opts.version}` : ""}`
|
|
12555
13567
|
)
|
|
12556
13568
|
);
|
|
@@ -12565,16 +13577,16 @@ function registerPluginCommands(program2) {
|
|
|
12565
13577
|
return;
|
|
12566
13578
|
}
|
|
12567
13579
|
if (!installedPlugin) {
|
|
12568
|
-
console.log(
|
|
13580
|
+
console.log(pc24.dim("Install returned no plugin record."));
|
|
12569
13581
|
return;
|
|
12570
13582
|
}
|
|
12571
13583
|
console.log(
|
|
12572
|
-
|
|
12573
|
-
`\u2713 Installed ${
|
|
13584
|
+
pc24.green(
|
|
13585
|
+
`\u2713 Installed ${pc24.bold(installedPlugin.pluginKey)} v${installedPlugin.version} (${installedPlugin.status})`
|
|
12574
13586
|
)
|
|
12575
13587
|
);
|
|
12576
13588
|
if (installedPlugin.lastError) {
|
|
12577
|
-
console.log(
|
|
13589
|
+
console.log(pc24.red(` Warning: ${installedPlugin.lastError}`));
|
|
12578
13590
|
}
|
|
12579
13591
|
} catch (err) {
|
|
12580
13592
|
handleCommandError(err);
|
|
@@ -12591,7 +13603,7 @@ function registerPluginCommands(program2) {
|
|
|
12591
13603
|
const qs = purge ? "?purge=true" : "";
|
|
12592
13604
|
if (!ctx.json) {
|
|
12593
13605
|
console.log(
|
|
12594
|
-
|
|
13606
|
+
pc24.dim(
|
|
12595
13607
|
purge ? `Uninstalling and purging plugin: ${pluginKey}` : `Uninstalling plugin: ${pluginKey}`
|
|
12596
13608
|
)
|
|
12597
13609
|
);
|
|
@@ -12603,7 +13615,7 @@ function registerPluginCommands(program2) {
|
|
|
12603
13615
|
printOutput(result, { json: true });
|
|
12604
13616
|
return;
|
|
12605
13617
|
}
|
|
12606
|
-
console.log(
|
|
13618
|
+
console.log(pc24.green(`\u2713 Uninstalled ${pc24.bold(pluginKey)}${purge ? " (purged)" : ""}`));
|
|
12607
13619
|
} catch (err) {
|
|
12608
13620
|
handleCommandError(err);
|
|
12609
13621
|
}
|
|
@@ -12620,7 +13632,7 @@ function registerPluginCommands(program2) {
|
|
|
12620
13632
|
printOutput(result, { json: true });
|
|
12621
13633
|
return;
|
|
12622
13634
|
}
|
|
12623
|
-
console.log(
|
|
13635
|
+
console.log(pc24.green(`\u2713 Enabled ${pc24.bold(pluginKey)} \u2014 status: ${result?.status ?? "unknown"}`));
|
|
12624
13636
|
} catch (err) {
|
|
12625
13637
|
handleCommandError(err);
|
|
12626
13638
|
}
|
|
@@ -12637,7 +13649,7 @@ function registerPluginCommands(program2) {
|
|
|
12637
13649
|
printOutput(result, { json: true });
|
|
12638
13650
|
return;
|
|
12639
13651
|
}
|
|
12640
|
-
console.log(
|
|
13652
|
+
console.log(pc24.dim(`Disabled ${pc24.bold(pluginKey)} \u2014 status: ${result?.status ?? "unknown"}`));
|
|
12641
13653
|
} catch (err) {
|
|
12642
13654
|
handleCommandError(err);
|
|
12643
13655
|
}
|
|
@@ -12655,13 +13667,13 @@ function registerPluginCommands(program2) {
|
|
|
12655
13667
|
return;
|
|
12656
13668
|
}
|
|
12657
13669
|
if (!result) {
|
|
12658
|
-
console.log(
|
|
13670
|
+
console.log(pc24.red(`Plugin not found: ${pluginKey}`));
|
|
12659
13671
|
process.exit(1);
|
|
12660
13672
|
}
|
|
12661
13673
|
console.log(formatPlugin(result));
|
|
12662
13674
|
if (result.lastError) {
|
|
12663
13675
|
console.log(`
|
|
12664
|
-
${
|
|
13676
|
+
${pc24.red("Last error:")}
|
|
12665
13677
|
${result.lastError}`);
|
|
12666
13678
|
}
|
|
12667
13679
|
} catch (err) {
|
|
@@ -12680,14 +13692,14 @@ ${result.lastError}`);
|
|
|
12680
13692
|
}
|
|
12681
13693
|
const rows = examples ?? [];
|
|
12682
13694
|
if (rows.length === 0) {
|
|
12683
|
-
console.log(
|
|
13695
|
+
console.log(pc24.dim("No bundled examples available."));
|
|
12684
13696
|
return;
|
|
12685
13697
|
}
|
|
12686
13698
|
for (const ex of rows) {
|
|
12687
13699
|
console.log(
|
|
12688
|
-
`${
|
|
13700
|
+
`${pc24.bold(ex.displayName)} ${pc24.dim(ex.pluginKey)}
|
|
12689
13701
|
${ex.description}
|
|
12690
|
-
${
|
|
13702
|
+
${pc24.cyan(`paperclipai plugin install ${ex.localPath}`)}`
|
|
12691
13703
|
);
|
|
12692
13704
|
}
|
|
12693
13705
|
} catch (err) {
|
|
@@ -12698,15 +13710,15 @@ ${result.lastError}`);
|
|
|
12698
13710
|
}
|
|
12699
13711
|
|
|
12700
13712
|
// src/commands/kit.ts
|
|
12701
|
-
import
|
|
13713
|
+
import path24 from "node:path";
|
|
12702
13714
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
12703
|
-
import * as
|
|
12704
|
-
import
|
|
13715
|
+
import * as p17 from "@clack/prompts";
|
|
13716
|
+
import pc25 from "picocolors";
|
|
12705
13717
|
|
|
12706
13718
|
// src/kits/service.ts
|
|
12707
13719
|
init_home();
|
|
12708
|
-
import
|
|
12709
|
-
import
|
|
13720
|
+
import fs17 from "node:fs";
|
|
13721
|
+
import path23 from "node:path";
|
|
12710
13722
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
12711
13723
|
|
|
12712
13724
|
// src/kits/catalog.ts
|
|
@@ -12799,48 +13811,48 @@ var KIT_ACTIVATION_MODES = [
|
|
|
12799
13811
|
// src/kits/service.ts
|
|
12800
13812
|
var ZIP_TIMESTAMP = /* @__PURE__ */ new Date("2026-04-09T00:00:00.000Z");
|
|
12801
13813
|
function resolveBundledKitAssetsRoot() {
|
|
12802
|
-
const moduleDir =
|
|
13814
|
+
const moduleDir = path23.dirname(fileURLToPath4(import.meta.url));
|
|
12803
13815
|
const candidates = [
|
|
12804
|
-
|
|
12805
|
-
|
|
13816
|
+
path23.resolve(moduleDir, "../../assets/worker-kits"),
|
|
13817
|
+
path23.resolve(moduleDir, "../assets/worker-kits")
|
|
12806
13818
|
];
|
|
12807
13819
|
for (const candidate of candidates) {
|
|
12808
|
-
if (
|
|
13820
|
+
if (fs17.existsSync(candidate)) return candidate;
|
|
12809
13821
|
}
|
|
12810
13822
|
throw new Error("Could not locate bundled worker kit assets.");
|
|
12811
13823
|
}
|
|
12812
13824
|
function resolveRequestedOutputRoot(outDir) {
|
|
12813
13825
|
if (outDir?.trim()) {
|
|
12814
|
-
return
|
|
13826
|
+
return path23.resolve(expandHomePrefix(outDir.trim()));
|
|
12815
13827
|
}
|
|
12816
|
-
return
|
|
13828
|
+
return path23.resolve(resolvePaperclipHomeDir(), "kits", "exports");
|
|
12817
13829
|
}
|
|
12818
13830
|
function readJsonFile(filePath) {
|
|
12819
|
-
return JSON.parse(
|
|
13831
|
+
return JSON.parse(fs17.readFileSync(filePath, "utf8"));
|
|
12820
13832
|
}
|
|
12821
13833
|
function assertRelativePathExists(assetRoot, relativePath, label) {
|
|
12822
|
-
const fullPath =
|
|
12823
|
-
if (!
|
|
13834
|
+
const fullPath = path23.resolve(assetRoot, relativePath);
|
|
13835
|
+
if (!fs17.existsSync(fullPath)) {
|
|
12824
13836
|
throw new Error(`${label} is missing required path: ${relativePath}`);
|
|
12825
13837
|
}
|
|
12826
13838
|
}
|
|
12827
13839
|
function listRelativeFiles(rootDir) {
|
|
12828
13840
|
const files = [];
|
|
12829
13841
|
const walk = (currentDir) => {
|
|
12830
|
-
for (const entry of
|
|
12831
|
-
const fullPath =
|
|
13842
|
+
for (const entry of fs17.readdirSync(currentDir, { withFileTypes: true })) {
|
|
13843
|
+
const fullPath = path23.join(currentDir, entry.name);
|
|
12832
13844
|
if (entry.isDirectory()) {
|
|
12833
13845
|
walk(fullPath);
|
|
12834
13846
|
continue;
|
|
12835
13847
|
}
|
|
12836
|
-
files.push(
|
|
13848
|
+
files.push(path23.relative(rootDir, fullPath).split(path23.sep).join("/"));
|
|
12837
13849
|
}
|
|
12838
13850
|
};
|
|
12839
13851
|
walk(rootDir);
|
|
12840
13852
|
return files.sort();
|
|
12841
13853
|
}
|
|
12842
13854
|
function parseManifest(assetRoot) {
|
|
12843
|
-
const raw = readJsonFile(
|
|
13855
|
+
const raw = readJsonFile(path23.resolve(assetRoot, "kit.json"));
|
|
12844
13856
|
if (!SUPPORTED_SCHEMA_VERSIONS.includes(raw.schemaVersion)) {
|
|
12845
13857
|
throw new Error(`Unsupported kit schema version for ${assetRoot}: ${raw.schemaVersion}`);
|
|
12846
13858
|
}
|
|
@@ -12851,7 +13863,7 @@ function parseBundleManifest(assetRoot, manifest, bundleId) {
|
|
|
12851
13863
|
if (!bundleRef) {
|
|
12852
13864
|
throw new Error(`Kit ${manifest.kit.id} does not declare bundle ${bundleId}.`);
|
|
12853
13865
|
}
|
|
12854
|
-
const raw = readJsonFile(
|
|
13866
|
+
const raw = readJsonFile(path23.resolve(assetRoot, bundleRef.path));
|
|
12855
13867
|
if (!SUPPORTED_SCHEMA_VERSIONS.includes(raw.schemaVersion)) {
|
|
12856
13868
|
throw new Error(
|
|
12857
13869
|
`Unsupported bundle schema version for ${bundleRef.path}: ${raw.schemaVersion}`
|
|
@@ -12916,14 +13928,14 @@ function validateKitDirectory(kitPath) {
|
|
|
12916
13928
|
const warnings = [];
|
|
12917
13929
|
let schemaVersion = 0;
|
|
12918
13930
|
let kitId = "<unknown>";
|
|
12919
|
-
const kitJsonPath =
|
|
12920
|
-
if (!
|
|
13931
|
+
const kitJsonPath = path23.resolve(kitPath, "kit.json");
|
|
13932
|
+
if (!fs17.existsSync(kitJsonPath)) {
|
|
12921
13933
|
errors.push({ field: "kit.json", message: "kit.json not found in kit directory" });
|
|
12922
13934
|
return { valid: false, schemaVersion, kitId, errors, warnings };
|
|
12923
13935
|
}
|
|
12924
13936
|
let raw;
|
|
12925
13937
|
try {
|
|
12926
|
-
raw = JSON.parse(
|
|
13938
|
+
raw = JSON.parse(fs17.readFileSync(kitJsonPath, "utf8"));
|
|
12927
13939
|
} catch {
|
|
12928
13940
|
errors.push({ field: "kit.json", message: "kit.json is not valid JSON" });
|
|
12929
13941
|
return { valid: false, schemaVersion, kitId, errors, warnings };
|
|
@@ -12990,8 +14002,8 @@ function validateKitDirectory(kitPath) {
|
|
|
12990
14002
|
if (typeof entrypoint.path !== "string") {
|
|
12991
14003
|
errors.push({ field: "entrypoint.path", message: "Missing required field 'entrypoint.path'" });
|
|
12992
14004
|
} else {
|
|
12993
|
-
const fullPath =
|
|
12994
|
-
if (!
|
|
14005
|
+
const fullPath = path23.resolve(kitPath, entrypoint.path);
|
|
14006
|
+
if (!fs17.existsSync(fullPath)) {
|
|
12995
14007
|
errors.push({ field: "entrypoint.path", message: `Entrypoint file not found: ${entrypoint.path}` });
|
|
12996
14008
|
}
|
|
12997
14009
|
}
|
|
@@ -12999,16 +14011,16 @@ function validateKitDirectory(kitPath) {
|
|
|
12999
14011
|
if (typeof raw.agentContractPath !== "string") {
|
|
13000
14012
|
errors.push({ field: "agentContractPath", message: "Missing required field 'agentContractPath'" });
|
|
13001
14013
|
} else {
|
|
13002
|
-
const fullPath =
|
|
13003
|
-
if (!
|
|
14014
|
+
const fullPath = path23.resolve(kitPath, raw.agentContractPath);
|
|
14015
|
+
if (!fs17.existsSync(fullPath)) {
|
|
13004
14016
|
errors.push({ field: "agentContractPath", message: `Agent contract not found: ${raw.agentContractPath}` });
|
|
13005
14017
|
}
|
|
13006
14018
|
}
|
|
13007
14019
|
if (typeof raw.brandTemplatePath !== "string") {
|
|
13008
14020
|
errors.push({ field: "brandTemplatePath", message: "Missing required field 'brandTemplatePath'" });
|
|
13009
14021
|
} else {
|
|
13010
|
-
const fullPath =
|
|
13011
|
-
if (!
|
|
14022
|
+
const fullPath = path23.resolve(kitPath, raw.brandTemplatePath);
|
|
14023
|
+
if (!fs17.existsSync(fullPath)) {
|
|
13012
14024
|
errors.push({ field: "brandTemplatePath", message: `Brand template not found: ${raw.brandTemplatePath}` });
|
|
13013
14025
|
}
|
|
13014
14026
|
}
|
|
@@ -13018,8 +14030,8 @@ function validateKitDirectory(kitPath) {
|
|
|
13018
14030
|
} else {
|
|
13019
14031
|
for (const assetPath of frozenAssets) {
|
|
13020
14032
|
if (typeof assetPath !== "string") continue;
|
|
13021
|
-
const fullPath =
|
|
13022
|
-
if (!
|
|
14033
|
+
const fullPath = path23.resolve(kitPath, assetPath);
|
|
14034
|
+
if (!fs17.existsSync(fullPath)) {
|
|
13023
14035
|
errors.push({ field: "frozenAssetPaths", message: `Frozen asset not found: ${assetPath}` });
|
|
13024
14036
|
}
|
|
13025
14037
|
}
|
|
@@ -13037,8 +14049,8 @@ function validateKitDirectory(kitPath) {
|
|
|
13037
14049
|
} else {
|
|
13038
14050
|
for (const reqPath of requiredPaths) {
|
|
13039
14051
|
if (typeof reqPath !== "string") continue;
|
|
13040
|
-
const fullPath =
|
|
13041
|
-
if (!
|
|
14052
|
+
const fullPath = path23.resolve(kitPath, reqPath);
|
|
14053
|
+
if (!fs17.existsSync(fullPath)) {
|
|
13042
14054
|
errors.push({ field: "outputStandard.requiredPaths", message: `Required output path not found: ${reqPath}` });
|
|
13043
14055
|
}
|
|
13044
14056
|
}
|
|
@@ -13054,13 +14066,13 @@ function validateKitDirectory(kitPath) {
|
|
|
13054
14066
|
errors.push({ field: "bundles[].path", message: "Bundle ref missing 'path' field" });
|
|
13055
14067
|
continue;
|
|
13056
14068
|
}
|
|
13057
|
-
const bundlePath =
|
|
13058
|
-
if (!
|
|
14069
|
+
const bundlePath = path23.resolve(kitPath, ref.path);
|
|
14070
|
+
if (!fs17.existsSync(bundlePath)) {
|
|
13059
14071
|
errors.push({ field: "bundles[].path", message: `Bundle manifest not found: ${ref.path}` });
|
|
13060
14072
|
continue;
|
|
13061
14073
|
}
|
|
13062
14074
|
try {
|
|
13063
|
-
const bundleRaw = JSON.parse(
|
|
14075
|
+
const bundleRaw = JSON.parse(fs17.readFileSync(bundlePath, "utf8"));
|
|
13064
14076
|
const bundleBlock = bundleRaw.bundle;
|
|
13065
14077
|
if (!bundleBlock || typeof bundleBlock !== "object") {
|
|
13066
14078
|
errors.push({ field: `bundle(${ref.id})`, message: "Bundle manifest missing 'bundle' block" });
|
|
@@ -13120,7 +14132,7 @@ Available: ${available}`
|
|
|
13120
14132
|
);
|
|
13121
14133
|
}
|
|
13122
14134
|
const catalogEntry = BUNDLED_KIT_CATALOG.find((e) => e.id === resolvedId);
|
|
13123
|
-
const assetRoot =
|
|
14135
|
+
const assetRoot = path23.resolve(resolveBundledKitAssetsRoot(), catalogEntry.packageDirName);
|
|
13124
14136
|
return loadResolvedBundledKit(assetRoot, catalogEntry);
|
|
13125
14137
|
}
|
|
13126
14138
|
function toListItem(resolved) {
|
|
@@ -13140,8 +14152,8 @@ function toListItem(resolved) {
|
|
|
13140
14152
|
}
|
|
13141
14153
|
function resolveOutputPaths(resolved, outDir) {
|
|
13142
14154
|
const outputRoot = resolveRequestedOutputRoot(outDir);
|
|
13143
|
-
const folderPath =
|
|
13144
|
-
const zipPath =
|
|
14155
|
+
const folderPath = path23.resolve(outputRoot, resolved.bundleManifest.export.folderName);
|
|
14156
|
+
const zipPath = path23.resolve(outputRoot, resolved.bundleManifest.export.zipFileName);
|
|
13145
14157
|
return { outputRoot, folderPath, zipPath };
|
|
13146
14158
|
}
|
|
13147
14159
|
function listBundledKits() {
|
|
@@ -13257,7 +14269,7 @@ function reportProgress(onProgress, progress) {
|
|
|
13257
14269
|
function copyDirectoryWithProgress(sourceRoot, targetRoot, onProgress) {
|
|
13258
14270
|
const files = listRelativeFiles(sourceRoot);
|
|
13259
14271
|
const total = Math.max(files.length, 1);
|
|
13260
|
-
|
|
14272
|
+
fs17.mkdirSync(targetRoot, { recursive: true });
|
|
13261
14273
|
reportProgress(onProgress, {
|
|
13262
14274
|
phase: "copying",
|
|
13263
14275
|
completed: 0,
|
|
@@ -13266,10 +14278,10 @@ function copyDirectoryWithProgress(sourceRoot, targetRoot, onProgress) {
|
|
|
13266
14278
|
detail: "Preparing files"
|
|
13267
14279
|
});
|
|
13268
14280
|
files.forEach((relativePath, index51) => {
|
|
13269
|
-
const sourcePath =
|
|
13270
|
-
const targetPath =
|
|
13271
|
-
|
|
13272
|
-
|
|
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);
|
|
13273
14285
|
const completed = index51 + 1;
|
|
13274
14286
|
const percent = 10 + Math.round(completed / total * 55);
|
|
13275
14287
|
reportProgress(onProgress, {
|
|
@@ -13295,8 +14307,8 @@ function buildZipEntriesWithProgress(sourceRoot, exportFolderName, onProgress) {
|
|
|
13295
14307
|
detail: relativePath
|
|
13296
14308
|
});
|
|
13297
14309
|
return {
|
|
13298
|
-
name:
|
|
13299
|
-
data:
|
|
14310
|
+
name: path23.posix.join(exportFolderName, relativePath),
|
|
14311
|
+
data: fs17.readFileSync(path23.resolve(sourceRoot, relativePath))
|
|
13300
14312
|
};
|
|
13301
14313
|
});
|
|
13302
14314
|
}
|
|
@@ -13311,8 +14323,8 @@ function downloadBundledKit(kitId, outDir, options = {}) {
|
|
|
13311
14323
|
percent: 0,
|
|
13312
14324
|
detail: "Resolving export target"
|
|
13313
14325
|
});
|
|
13314
|
-
|
|
13315
|
-
|
|
14326
|
+
fs17.mkdirSync(outputPaths.outputRoot, { recursive: true });
|
|
14327
|
+
fs17.rmSync(outputPaths.folderPath, { recursive: true, force: true });
|
|
13316
14328
|
copyDirectoryWithProgress(resolved.assetRoot, outputPaths.folderPath, onProgress);
|
|
13317
14329
|
const zipBuffer = buildStoredZip(
|
|
13318
14330
|
buildZipEntriesWithProgress(outputPaths.folderPath, resolved.bundleManifest.export.folderName, onProgress)
|
|
@@ -13322,9 +14334,9 @@ function downloadBundledKit(kitId, outDir, options = {}) {
|
|
|
13322
14334
|
completed: 1,
|
|
13323
14335
|
total: 1,
|
|
13324
14336
|
percent: 98,
|
|
13325
|
-
detail:
|
|
14337
|
+
detail: path23.basename(outputPaths.zipPath)
|
|
13326
14338
|
});
|
|
13327
|
-
|
|
14339
|
+
fs17.writeFileSync(outputPaths.zipPath, zipBuffer);
|
|
13328
14340
|
reportProgress(onProgress, {
|
|
13329
14341
|
phase: "done",
|
|
13330
14342
|
completed: 1,
|
|
@@ -13341,9 +14353,9 @@ function downloadBundledKit(kitId, outDir, options = {}) {
|
|
|
13341
14353
|
// src/commands/kit.ts
|
|
13342
14354
|
init_banner();
|
|
13343
14355
|
var TYPE_CONFIG = {
|
|
13344
|
-
studio: { color:
|
|
13345
|
-
specialized_agents: { color:
|
|
13346
|
-
ops: { color:
|
|
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" }
|
|
13347
14359
|
};
|
|
13348
14360
|
function displayTypeForFamily(family) {
|
|
13349
14361
|
if (family === "workflow" || family === "operator") return "specialized_agents";
|
|
@@ -13368,16 +14380,16 @@ function displayKitName(name) {
|
|
|
13368
14380
|
return name.replace(/^Growthub Agent Worker Kit\s+[—-]\s+/u, "").trim();
|
|
13369
14381
|
}
|
|
13370
14382
|
function hr(width = 72) {
|
|
13371
|
-
return
|
|
14383
|
+
return pc25.dim("\u2500".repeat(width));
|
|
13372
14384
|
}
|
|
13373
14385
|
function box(lines) {
|
|
13374
14386
|
const padded = lines.map((l) => " " + l);
|
|
13375
14387
|
const width = Math.max(...padded.map((l) => stripAnsi(l).length)) + 4;
|
|
13376
|
-
const top =
|
|
13377
|
-
const bottom =
|
|
14388
|
+
const top = pc25.dim("\u250C" + "\u2500".repeat(width) + "\u2510");
|
|
14389
|
+
const bottom = pc25.dim("\u2514" + "\u2500".repeat(width) + "\u2518");
|
|
13378
14390
|
const body = padded.map((l) => {
|
|
13379
14391
|
const pad = width - stripAnsi(l).length;
|
|
13380
|
-
return
|
|
14392
|
+
return pc25.dim("\u2502") + l + " ".repeat(pad) + pc25.dim("\u2502");
|
|
13381
14393
|
});
|
|
13382
14394
|
return [top, ...body, bottom].join("\n");
|
|
13383
14395
|
}
|
|
@@ -13398,7 +14410,7 @@ function renderProgressBar(progress) {
|
|
|
13398
14410
|
const filled = Math.max(0, Math.min(width, Math.round(progress.percent / 100 * width)));
|
|
13399
14411
|
const bar = `${"=".repeat(filled)}${"-".repeat(width - filled)}`;
|
|
13400
14412
|
const detail = truncate(progress.detail, 48);
|
|
13401
|
-
const line = `\r${
|
|
14413
|
+
const line = `\r${pc25.cyan("Exporting kit")} ${pc25.dim("[")}${pc25.green(bar)}${pc25.dim("]")} ${String(progress.percent).padStart(3)}% ${pc25.dim(detail)}`;
|
|
13402
14414
|
process.stdout.write(line);
|
|
13403
14415
|
if (progress.phase === "done") {
|
|
13404
14416
|
process.stdout.write("\n");
|
|
@@ -13408,12 +14420,12 @@ function printKitCard(item) {
|
|
|
13408
14420
|
const badge2 = typeBadge(item.family);
|
|
13409
14421
|
console.log("");
|
|
13410
14422
|
console.log(box([
|
|
13411
|
-
`${
|
|
13412
|
-
`${badge2} ${
|
|
14423
|
+
`${pc25.bold(item.name)} ${pc25.dim("v" + item.version)}`,
|
|
14424
|
+
`${badge2} ${pc25.dim(item.id)}`,
|
|
13413
14425
|
"",
|
|
13414
14426
|
truncate(item.description, 62),
|
|
13415
14427
|
"",
|
|
13416
|
-
`${
|
|
14428
|
+
`${pc25.dim("Brief:")} ${pc25.dim(item.briefType)} ${pc25.dim("Mode:")} ${pc25.dim(item.executionMode)}`
|
|
13417
14429
|
]));
|
|
13418
14430
|
}
|
|
13419
14431
|
function getActionLabel(action) {
|
|
@@ -13427,20 +14439,20 @@ async function confirmKitActions(input) {
|
|
|
13427
14439
|
return getActionLabel(action);
|
|
13428
14440
|
});
|
|
13429
14441
|
const summaryLines = [
|
|
13430
|
-
|
|
14442
|
+
pc25.bold("Selected kits"),
|
|
13431
14443
|
...input.kits.map((kit) => `${typeBadge(kit.family)} ${displayKitName(kit.name)}`),
|
|
13432
14444
|
"",
|
|
13433
|
-
|
|
14445
|
+
pc25.bold("Selected actions"),
|
|
13434
14446
|
actionLabels.join(", ")
|
|
13435
14447
|
];
|
|
13436
14448
|
console.log("");
|
|
13437
14449
|
console.log(box(summaryLines));
|
|
13438
|
-
const confirmed = await
|
|
14450
|
+
const confirmed = await p17.confirm({
|
|
13439
14451
|
message: "Continue with these worker kit actions?",
|
|
13440
14452
|
initialValue: false
|
|
13441
14453
|
});
|
|
13442
|
-
if (
|
|
13443
|
-
|
|
14454
|
+
if (p17.isCancel(confirmed)) {
|
|
14455
|
+
p17.cancel("Cancelled.");
|
|
13444
14456
|
process.exit(0);
|
|
13445
14457
|
}
|
|
13446
14458
|
return Boolean(confirmed);
|
|
@@ -13455,39 +14467,39 @@ function printGroupedList(kits) {
|
|
|
13455
14467
|
const totalTypes = types.length;
|
|
13456
14468
|
console.log("");
|
|
13457
14469
|
console.log(
|
|
13458
|
-
|
|
14470
|
+
pc25.bold("Growthub Agent Worker Kits") + pc25.dim(` ${kits.length} kit${kits.length !== 1 ? "s" : ""} \xB7 ${totalTypes} type${totalTypes !== 1 ? "s" : ""}`)
|
|
13459
14471
|
);
|
|
13460
14472
|
console.log(hr());
|
|
13461
14473
|
for (const type of types) {
|
|
13462
14474
|
const groupKits = byType[type];
|
|
13463
14475
|
const header = typeBadge(type);
|
|
13464
14476
|
console.log(`
|
|
13465
|
-
${header} ${
|
|
14477
|
+
${header} ${pc25.dim("(" + groupKits.length + ")")}`);
|
|
13466
14478
|
for (const kit of groupKits) {
|
|
13467
|
-
console.log(` ${typeColor(kit.family,
|
|
13468
|
-
console.log(` ${
|
|
13469
|
-
console.log(` ${
|
|
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)}`);
|
|
13470
14482
|
console.log("");
|
|
13471
14483
|
}
|
|
13472
14484
|
}
|
|
13473
14485
|
console.log(hr());
|
|
13474
|
-
console.log(
|
|
14486
|
+
console.log(pc25.dim(" growthub kit download <id> \xB7 growthub kit inspect <id> \xB7 growthub kit families"));
|
|
13475
14487
|
console.log("");
|
|
13476
14488
|
}
|
|
13477
14489
|
async function runInteractivePicker(opts) {
|
|
13478
14490
|
printPaperclipCliBanner();
|
|
13479
|
-
|
|
14491
|
+
p17.intro(pc25.bold("Growthub Agent Worker Kits"));
|
|
13480
14492
|
let kits;
|
|
13481
14493
|
try {
|
|
13482
14494
|
kits = listBundledKits();
|
|
13483
14495
|
} catch (err) {
|
|
13484
|
-
|
|
14496
|
+
p17.log.error("Failed to load kits: " + err.message);
|
|
13485
14497
|
process.exit(1);
|
|
13486
14498
|
}
|
|
13487
14499
|
const familiesAvailable = [...new Set(kits.map((k) => k.family))].sort();
|
|
13488
14500
|
const typeOptions = Array.from(new Set(familiesAvailable.map((family) => displayTypeForFamily(family))));
|
|
13489
14501
|
while (true) {
|
|
13490
|
-
const typeChoice = await
|
|
14502
|
+
const typeChoice = await p17.select({
|
|
13491
14503
|
message: "Filter by type",
|
|
13492
14504
|
options: [
|
|
13493
14505
|
{ value: "all", label: "All Types" },
|
|
@@ -13501,54 +14513,54 @@ async function runInteractivePicker(opts) {
|
|
|
13501
14513
|
...opts.allowBackToHub ? [{ value: "__back_to_hub", label: "\u2190 Back to main menu" }] : []
|
|
13502
14514
|
]
|
|
13503
14515
|
});
|
|
13504
|
-
if (
|
|
13505
|
-
|
|
14516
|
+
if (p17.isCancel(typeChoice)) {
|
|
14517
|
+
p17.cancel("Cancelled.");
|
|
13506
14518
|
process.exit(0);
|
|
13507
14519
|
}
|
|
13508
14520
|
if (typeChoice === "__back_to_hub") return "back";
|
|
13509
14521
|
const filtered = typeChoice === "all" ? kits : kits.filter((k) => displayTypeForFamily(k.family) === typeChoice);
|
|
13510
14522
|
const showTypeBadgeInKitChoices = typeChoice === "all";
|
|
13511
14523
|
if (filtered.length === 0) {
|
|
13512
|
-
|
|
14524
|
+
p17.note("No kits are available for that type yet.", "Nothing found");
|
|
13513
14525
|
continue;
|
|
13514
14526
|
}
|
|
13515
14527
|
while (true) {
|
|
13516
|
-
const kitChoice = await
|
|
14528
|
+
const kitChoice = await p17.select({
|
|
13517
14529
|
message: "Select kit",
|
|
13518
14530
|
options: [
|
|
13519
14531
|
...filtered.map((k) => ({
|
|
13520
14532
|
value: k.id,
|
|
13521
|
-
label: (showTypeBadgeInKitChoices ? typeBadge(k.family) + " " : "") +
|
|
14533
|
+
label: (showTypeBadgeInKitChoices ? typeBadge(k.family) + " " : "") + pc25.bold(displayKitName(k.name)) + " " + pc25.dim("v" + k.version),
|
|
13522
14534
|
hint: truncate(k.description, 55)
|
|
13523
14535
|
})),
|
|
13524
14536
|
{ value: "__back_to_type", label: "\u2190 Back to type filter" }
|
|
13525
14537
|
]
|
|
13526
14538
|
});
|
|
13527
|
-
if (
|
|
13528
|
-
|
|
14539
|
+
if (p17.isCancel(kitChoice)) {
|
|
14540
|
+
p17.cancel("Cancelled.");
|
|
13529
14541
|
process.exit(0);
|
|
13530
14542
|
}
|
|
13531
14543
|
if (kitChoice === "__back_to_type") break;
|
|
13532
14544
|
const selected = filtered.find((kit) => kit.id === kitChoice);
|
|
13533
14545
|
if (!selected) {
|
|
13534
|
-
|
|
14546
|
+
p17.cancel("Selected kit was not found.");
|
|
13535
14547
|
process.exit(1);
|
|
13536
14548
|
}
|
|
13537
14549
|
printKitCard(selected);
|
|
13538
|
-
const nextStep = await
|
|
14550
|
+
const nextStep = await p17.select({
|
|
13539
14551
|
message: "Next step",
|
|
13540
14552
|
options: [
|
|
13541
14553
|
{ value: "actions", label: "Choose action(s)" },
|
|
13542
14554
|
{ value: "back_to_kits", label: "\u2190 Back to kit list" }
|
|
13543
14555
|
]
|
|
13544
14556
|
});
|
|
13545
|
-
if (
|
|
13546
|
-
|
|
14557
|
+
if (p17.isCancel(nextStep)) {
|
|
14558
|
+
p17.cancel("Cancelled.");
|
|
13547
14559
|
process.exit(0);
|
|
13548
14560
|
}
|
|
13549
14561
|
if (nextStep === "back_to_kits") continue;
|
|
13550
14562
|
while (true) {
|
|
13551
|
-
const action = await
|
|
14563
|
+
const action = await p17.select({
|
|
13552
14564
|
message: "What would you like to do?",
|
|
13553
14565
|
options: [
|
|
13554
14566
|
{ value: "download", label: "\u2B07\uFE0F Download kit", hint: "growthub kit download <id>" },
|
|
@@ -13557,8 +14569,8 @@ async function runInteractivePicker(opts) {
|
|
|
13557
14569
|
{ value: "back_to_kits", label: "\u2190 Back to kit list" }
|
|
13558
14570
|
]
|
|
13559
14571
|
});
|
|
13560
|
-
if (
|
|
13561
|
-
|
|
14572
|
+
if (p17.isCancel(action)) {
|
|
14573
|
+
p17.cancel("Cancelled.");
|
|
13562
14574
|
process.exit(0);
|
|
13563
14575
|
}
|
|
13564
14576
|
if (action === "back_to_kits") break;
|
|
@@ -13567,15 +14579,15 @@ async function runInteractivePicker(opts) {
|
|
|
13567
14579
|
actions: [action]
|
|
13568
14580
|
});
|
|
13569
14581
|
if (!confirmed) {
|
|
13570
|
-
const reviewChoice = await
|
|
14582
|
+
const reviewChoice = await p17.select({
|
|
13571
14583
|
message: "Review selection",
|
|
13572
14584
|
options: [
|
|
13573
14585
|
{ value: "actions", label: `Choose ${getActionLabel(action)} again` },
|
|
13574
14586
|
{ value: "back_to_kits", label: "\u2190 Back to kit list" }
|
|
13575
14587
|
]
|
|
13576
14588
|
});
|
|
13577
|
-
if (
|
|
13578
|
-
|
|
14589
|
+
if (p17.isCancel(reviewChoice)) {
|
|
14590
|
+
p17.cancel("Cancelled.");
|
|
13579
14591
|
process.exit(0);
|
|
13580
14592
|
}
|
|
13581
14593
|
if (reviewChoice === "back_to_kits") break;
|
|
@@ -13583,16 +14595,16 @@ async function runInteractivePicker(opts) {
|
|
|
13583
14595
|
}
|
|
13584
14596
|
if (action === "copy-id") {
|
|
13585
14597
|
console.log(selected.id);
|
|
13586
|
-
|
|
14598
|
+
p17.outro(pc25.dim("Kit ID printed above."));
|
|
13587
14599
|
return "done";
|
|
13588
14600
|
}
|
|
13589
14601
|
if (action === "inspect") {
|
|
13590
14602
|
runInspect(selected.id, opts.out);
|
|
13591
|
-
|
|
14603
|
+
p17.outro(pc25.dim("Done."));
|
|
13592
14604
|
return "done";
|
|
13593
14605
|
}
|
|
13594
14606
|
await runDownload(selected.id, opts);
|
|
13595
|
-
|
|
14607
|
+
p17.outro(pc25.green("Kit exported successfully."));
|
|
13596
14608
|
return "done";
|
|
13597
14609
|
}
|
|
13598
14610
|
}
|
|
@@ -13601,19 +14613,19 @@ async function runInteractivePicker(opts) {
|
|
|
13601
14613
|
async function runDownload(kitId, opts) {
|
|
13602
14614
|
const resolvedId = fuzzyResolveKitId(kitId);
|
|
13603
14615
|
if (!resolvedId) {
|
|
13604
|
-
console.error(
|
|
14616
|
+
console.error(pc25.red("Unknown kit '" + kitId + "'.") + pc25.dim(" Run `growthub kit list` to browse."));
|
|
13605
14617
|
process.exit(1);
|
|
13606
14618
|
}
|
|
13607
14619
|
if (resolvedId !== kitId) {
|
|
13608
|
-
console.log(
|
|
14620
|
+
console.log(pc25.dim("Resolved '" + kitId + "' \u2192 " + resolvedId));
|
|
13609
14621
|
}
|
|
13610
14622
|
const kits = listBundledKits();
|
|
13611
14623
|
const item = kits.find((k) => k.id === resolvedId);
|
|
13612
14624
|
printKitCard(item);
|
|
13613
14625
|
if (!opts.yes) {
|
|
13614
|
-
const confirmed = await
|
|
13615
|
-
if (
|
|
13616
|
-
|
|
14626
|
+
const confirmed = await p17.confirm({ message: "Download " + pc25.bold(displayKitName(item.name)) + "?" });
|
|
14627
|
+
if (p17.isCancel(confirmed) || !confirmed) {
|
|
14628
|
+
p17.cancel("Cancelled.");
|
|
13617
14629
|
process.exit(0);
|
|
13618
14630
|
}
|
|
13619
14631
|
}
|
|
@@ -13621,35 +14633,35 @@ async function runDownload(kitId, opts) {
|
|
|
13621
14633
|
onProgress: renderProgressBar
|
|
13622
14634
|
});
|
|
13623
14635
|
console.log("");
|
|
13624
|
-
console.log(
|
|
14636
|
+
console.log(pc25.green(pc25.bold("Kit exported successfully.")));
|
|
13625
14637
|
console.log("");
|
|
13626
14638
|
const nextSteps = [
|
|
13627
|
-
|
|
14639
|
+
pc25.bold("Next steps"),
|
|
13628
14640
|
"",
|
|
13629
|
-
|
|
13630
|
-
" " +
|
|
14641
|
+
pc25.dim("1.") + " Point Working Directory at:",
|
|
14642
|
+
" " + pc25.cyan(result.folderPath),
|
|
13631
14643
|
"",
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
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",
|
|
13635
14647
|
"",
|
|
13636
|
-
|
|
14648
|
+
pc25.dim("Docs: QUICKSTART.md \xB7 validation-checklist.md")
|
|
13637
14649
|
];
|
|
13638
14650
|
console.log("");
|
|
13639
14651
|
console.log(box(nextSteps));
|
|
13640
14652
|
console.log("");
|
|
13641
|
-
console.log(
|
|
13642
|
-
console.log(
|
|
14653
|
+
console.log(pc25.bold("Open folder: ") + folderOpenLabel(result.folderPath));
|
|
14654
|
+
console.log(pc25.dim("Folder: ") + result.folderPath);
|
|
13643
14655
|
console.log("");
|
|
13644
|
-
console.log(
|
|
14656
|
+
console.log(pc25.dim("Zip: ") + result.zipPath);
|
|
13645
14657
|
console.log("");
|
|
13646
14658
|
}
|
|
13647
14659
|
function runInspect(kitId, outDir) {
|
|
13648
14660
|
const info = inspectBundledKit(kitId, outDir);
|
|
13649
|
-
const kv = (label, value) => console.log(" " +
|
|
14661
|
+
const kv = (label, value) => console.log(" " + pc25.bold(label.padEnd(24)) + " " + value);
|
|
13650
14662
|
console.log("");
|
|
13651
|
-
console.log(
|
|
13652
|
-
console.log(typeBadge(info.family) +
|
|
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));
|
|
13653
14665
|
console.log(hr());
|
|
13654
14666
|
kv("Name:", info.name);
|
|
13655
14667
|
kv("Description:", truncate(info.description, 55));
|
|
@@ -13665,8 +14677,8 @@ function runInspect(kitId, outDir) {
|
|
|
13665
14677
|
kv("Compatibility:", JSON.stringify(info.compatibility));
|
|
13666
14678
|
}
|
|
13667
14679
|
console.log(hr());
|
|
13668
|
-
console.log(
|
|
13669
|
-
for (const rp of info.requiredPaths) console.log(" " +
|
|
14680
|
+
console.log(pc25.bold(" Required Paths:"));
|
|
14681
|
+
for (const rp of info.requiredPaths) console.log(" " + pc25.dim("\xB7") + " " + rp);
|
|
13670
14682
|
console.log("");
|
|
13671
14683
|
}
|
|
13672
14684
|
function registerKitCommands(program2) {
|
|
@@ -13696,8 +14708,8 @@ Examples:
|
|
|
13696
14708
|
const wanted = opts.family.split(",").map((f) => f.trim().toLowerCase());
|
|
13697
14709
|
kits = kits.filter((k) => wanted.includes(k.family));
|
|
13698
14710
|
if (kits.length === 0) {
|
|
13699
|
-
console.error(
|
|
13700
|
-
console.error(
|
|
14711
|
+
console.error(pc25.yellow("No kits found for family: " + opts.family));
|
|
14712
|
+
console.error(pc25.dim("Valid families: studio, workflow, operator, ops"));
|
|
13701
14713
|
process.exitCode = 1;
|
|
13702
14714
|
return;
|
|
13703
14715
|
}
|
|
@@ -13715,7 +14727,7 @@ Examples:
|
|
|
13715
14727
|
`).action((kitId, opts) => {
|
|
13716
14728
|
const resolvedId = fuzzyResolveKitId(kitId);
|
|
13717
14729
|
if (!resolvedId) {
|
|
13718
|
-
console.error(
|
|
14730
|
+
console.error(pc25.red("Unknown kit '" + kitId + "'.") + pc25.dim(" Run `growthub kit list` to browse."));
|
|
13719
14731
|
process.exitCode = 1;
|
|
13720
14732
|
return;
|
|
13721
14733
|
}
|
|
@@ -13739,7 +14751,7 @@ Examples:
|
|
|
13739
14751
|
}
|
|
13740
14752
|
const resolvedId = fuzzyResolveKitId(kitId);
|
|
13741
14753
|
if (!resolvedId) {
|
|
13742
|
-
console.error(
|
|
14754
|
+
console.error(pc25.red("Unknown kit '" + kitId + "'.") + pc25.dim(" Run `growthub kit list` to browse."));
|
|
13743
14755
|
process.exitCode = 1;
|
|
13744
14756
|
return;
|
|
13745
14757
|
}
|
|
@@ -13748,14 +14760,14 @@ Examples:
|
|
|
13748
14760
|
onProgress: renderProgressBar
|
|
13749
14761
|
});
|
|
13750
14762
|
console.log("");
|
|
13751
|
-
console.log(
|
|
13752
|
-
console.log(
|
|
13753
|
-
console.log(
|
|
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));
|
|
13754
14766
|
console.log("");
|
|
13755
|
-
console.log(
|
|
13756
|
-
console.log(" 1. Point Working Directory at: " +
|
|
13757
|
-
console.log(" 2. " +
|
|
13758
|
-
console.log(" 3. " +
|
|
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");
|
|
13759
14771
|
console.log(" 4. Open Growthub local \u2014 the agent loads automatically");
|
|
13760
14772
|
console.log("");
|
|
13761
14773
|
return;
|
|
@@ -13765,7 +14777,7 @@ Examples:
|
|
|
13765
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) => {
|
|
13766
14778
|
const resolvedId = fuzzyResolveKitId(kitId);
|
|
13767
14779
|
if (!resolvedId) {
|
|
13768
|
-
console.error(
|
|
14780
|
+
console.error(pc25.red("Unknown kit '" + kitId + "'."));
|
|
13769
14781
|
process.exitCode = 1;
|
|
13770
14782
|
return;
|
|
13771
14783
|
}
|
|
@@ -13776,23 +14788,23 @@ Examples:
|
|
|
13776
14788
|
$ growthub kit validate ./my-kit
|
|
13777
14789
|
$ growthub kit validate ~/kits/growthub-open-higgsfield-studio-v1
|
|
13778
14790
|
`).action((kitPath) => {
|
|
13779
|
-
const resolvedPath =
|
|
14791
|
+
const resolvedPath = path24.resolve(kitPath);
|
|
13780
14792
|
const result = validateKitDirectory(resolvedPath);
|
|
13781
14793
|
console.log("");
|
|
13782
|
-
console.log(
|
|
14794
|
+
console.log(pc25.bold("Kit: " + result.kitId) + pc25.dim(" schema v" + result.schemaVersion));
|
|
13783
14795
|
console.log(hr());
|
|
13784
14796
|
for (const w of result.warnings) {
|
|
13785
|
-
console.log(
|
|
14797
|
+
console.log(pc25.yellow(" WARN " + w.field + ": " + w.message));
|
|
13786
14798
|
}
|
|
13787
14799
|
for (const e of result.errors) {
|
|
13788
|
-
console.log(
|
|
14800
|
+
console.log(pc25.red(" ERROR " + e.field + ": " + e.message));
|
|
13789
14801
|
}
|
|
13790
14802
|
if (result.errors.length > 0) {
|
|
13791
14803
|
console.log("");
|
|
13792
|
-
console.log(
|
|
14804
|
+
console.log(pc25.red(pc25.bold(" Result: INVALID")) + pc25.dim(" (" + result.errors.length + " error" + (result.errors.length !== 1 ? "s" : "") + ")"));
|
|
13793
14805
|
process.exitCode = 1;
|
|
13794
14806
|
} else {
|
|
13795
|
-
console.log(
|
|
14807
|
+
console.log(pc25.green(pc25.bold(" Result: VALID")));
|
|
13796
14808
|
}
|
|
13797
14809
|
console.log("");
|
|
13798
14810
|
});
|
|
@@ -13804,29 +14816,29 @@ Examples:
|
|
|
13804
14816
|
{ family: "ops", tagline: "Infrastructure / toolchain operator (provider optional)", surfaces: "local-fork (primary)", example: "(coming soon)" }
|
|
13805
14817
|
];
|
|
13806
14818
|
console.log("");
|
|
13807
|
-
console.log(
|
|
14819
|
+
console.log(pc25.bold("Kit Family Taxonomy"));
|
|
13808
14820
|
console.log(hr());
|
|
13809
14821
|
for (const def of defs) {
|
|
13810
14822
|
console.log("\n " + typeBadge(def.family));
|
|
13811
|
-
console.log(" " +
|
|
13812
|
-
console.log(" " +
|
|
13813
|
-
console.log(" " +
|
|
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));
|
|
13814
14826
|
}
|
|
13815
14827
|
console.log("");
|
|
13816
14828
|
console.log(hr());
|
|
13817
|
-
console.log(
|
|
14829
|
+
console.log(pc25.dim(" growthub kit list --family <family> to filter by internal family"));
|
|
13818
14830
|
console.log("");
|
|
13819
14831
|
});
|
|
13820
14832
|
}
|
|
13821
14833
|
|
|
13822
14834
|
// src/commands/template.ts
|
|
13823
|
-
import
|
|
13824
|
-
import * as
|
|
13825
|
-
import
|
|
14835
|
+
import path26 from "node:path";
|
|
14836
|
+
import * as p18 from "@clack/prompts";
|
|
14837
|
+
import pc26 from "picocolors";
|
|
13826
14838
|
|
|
13827
14839
|
// src/templates/service.ts
|
|
13828
|
-
import
|
|
13829
|
-
import
|
|
14840
|
+
import fs18 from "node:fs";
|
|
14841
|
+
import path25 from "node:path";
|
|
13830
14842
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
13831
14843
|
|
|
13832
14844
|
// src/templates/catalog.ts
|
|
@@ -14073,12 +15085,12 @@ var TEMPLATE_CATALOG = [
|
|
|
14073
15085
|
|
|
14074
15086
|
// src/templates/service.ts
|
|
14075
15087
|
function resolveSharedTemplatesRoot() {
|
|
14076
|
-
const moduleDir =
|
|
15088
|
+
const moduleDir = path25.dirname(fileURLToPath5(import.meta.url));
|
|
14077
15089
|
for (const candidate of [
|
|
14078
|
-
|
|
14079
|
-
|
|
15090
|
+
path25.resolve(moduleDir, "../../assets/shared-templates"),
|
|
15091
|
+
path25.resolve(moduleDir, "../assets/shared-templates")
|
|
14080
15092
|
]) {
|
|
14081
|
-
if (
|
|
15093
|
+
if (fs18.existsSync(candidate)) return candidate;
|
|
14082
15094
|
}
|
|
14083
15095
|
throw new Error("Shared template assets not found at cli/assets/shared-templates/");
|
|
14084
15096
|
}
|
|
@@ -14113,15 +15125,15 @@ function getArtifact(slugOrId) {
|
|
|
14113
15125
|
const artifact = resolveSlug(slugOrId);
|
|
14114
15126
|
if (!artifact) throw new Error(`Unknown template '${slugOrId}'. Run 'growthub template list' to browse.`);
|
|
14115
15127
|
const root = resolveSharedTemplatesRoot();
|
|
14116
|
-
const absolutePath =
|
|
14117
|
-
if (!
|
|
14118
|
-
return { artifact, content:
|
|
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 };
|
|
14119
15131
|
}
|
|
14120
15132
|
function copyArtifact(slugOrId, destDir) {
|
|
14121
15133
|
const resolved = getArtifact(slugOrId);
|
|
14122
|
-
|
|
14123
|
-
const destPath =
|
|
14124
|
-
|
|
15134
|
+
fs18.mkdirSync(destDir, { recursive: true });
|
|
15135
|
+
const destPath = path25.resolve(destDir, path25.basename(resolved.absolutePath));
|
|
15136
|
+
fs18.copyFileSync(resolved.absolutePath, destPath);
|
|
14125
15137
|
return destPath;
|
|
14126
15138
|
}
|
|
14127
15139
|
var GROUP_ORDER = ["ad-formats", "scene-modules/hooks", "scene-modules/body", "scene-modules/cta"];
|
|
@@ -14171,7 +15183,7 @@ function stripAnsi2(s) {
|
|
|
14171
15183
|
return s.replace(/\x1B\[[0-9;]*m/g, "");
|
|
14172
15184
|
}
|
|
14173
15185
|
function hr2(w = 72) {
|
|
14174
|
-
return
|
|
15186
|
+
return pc26.dim("\u2500".repeat(w));
|
|
14175
15187
|
}
|
|
14176
15188
|
function truncate2(s, max) {
|
|
14177
15189
|
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
@@ -14179,32 +15191,32 @@ function truncate2(s, max) {
|
|
|
14179
15191
|
function box2(lines) {
|
|
14180
15192
|
const padded = lines.map((l) => " " + l);
|
|
14181
15193
|
const width = Math.max(...padded.map((l) => stripAnsi2(l).length)) + 4;
|
|
14182
|
-
const top =
|
|
14183
|
-
const bottom =
|
|
14184
|
-
const body = padded.map((l) =>
|
|
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"));
|
|
14185
15197
|
return [top, ...body, bottom].join("\n");
|
|
14186
15198
|
}
|
|
14187
15199
|
function badge(a) {
|
|
14188
|
-
if (a.type === "ad-format") return
|
|
15200
|
+
if (a.type === "ad-format") return pc26.cyan("\u{1F3AC} Ad Format");
|
|
14189
15201
|
if (a.type === "scene-module") {
|
|
14190
|
-
if (a.subtype === "hook") return
|
|
14191
|
-
if (a.subtype === "body") return
|
|
14192
|
-
if (a.subtype === "cta") return
|
|
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");
|
|
14193
15205
|
}
|
|
14194
|
-
return
|
|
15206
|
+
return pc26.magenta("\u{1F9E9} Module");
|
|
14195
15207
|
}
|
|
14196
15208
|
function printCard(a) {
|
|
14197
|
-
const compat = a.compatibleFormats.length ?
|
|
15209
|
+
const compat = a.compatibleFormats.length ? pc26.dim("Works with: ") + a.compatibleFormats.map((f) => pc26.cyan(f)).join(", ") : pc26.dim("Works with: any format");
|
|
14198
15210
|
const rows = [
|
|
14199
|
-
|
|
14200
|
-
`${badge(a)} ${
|
|
15211
|
+
pc26.bold(a.name),
|
|
15212
|
+
`${badge(a)} ${pc26.dim(a.id)}`,
|
|
14201
15213
|
"",
|
|
14202
15214
|
truncate2(a.category, 62),
|
|
14203
15215
|
"",
|
|
14204
15216
|
compat
|
|
14205
15217
|
];
|
|
14206
15218
|
if (a.type === "ad-format" && a.scenes != null) {
|
|
14207
|
-
rows.push(
|
|
15219
|
+
rows.push(pc26.dim("Scenes: ") + a.scenes + (a.hookVariations ? pc26.dim(" \xB7 Hook variations: ") + a.hookVariations : ""));
|
|
14208
15220
|
}
|
|
14209
15221
|
console.log("");
|
|
14210
15222
|
console.log(box2(rows));
|
|
@@ -14212,32 +15224,32 @@ function printCard(a) {
|
|
|
14212
15224
|
function printSummary2(filter) {
|
|
14213
15225
|
const artifacts = listArtifacts(filter);
|
|
14214
15226
|
if (!artifacts.length) {
|
|
14215
|
-
console.log(
|
|
15227
|
+
console.log(pc26.yellow("No templates matched. Try: growthub template list"));
|
|
14216
15228
|
return;
|
|
14217
15229
|
}
|
|
14218
15230
|
const stats = getCatalogStats();
|
|
14219
15231
|
const groups = groupArtifacts(artifacts);
|
|
14220
15232
|
console.log("");
|
|
14221
|
-
console.log(
|
|
14222
|
-
console.log(
|
|
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 ")));
|
|
14223
15235
|
console.log(hr2());
|
|
14224
15236
|
for (const g of groups) {
|
|
14225
15237
|
console.log(`
|
|
14226
|
-
${
|
|
14227
|
-
console.log(
|
|
15238
|
+
${pc26.bold(g.label)} ${pc26.dim("(" + g.count + ")")}`);
|
|
15239
|
+
console.log(pc26.dim(" " + g.description));
|
|
14228
15240
|
console.log("");
|
|
14229
15241
|
for (const a of g.artifacts) {
|
|
14230
|
-
const compat = a.compatibleFormats.length ?
|
|
14231
|
-
console.log(` ${
|
|
14232
|
-
console.log(` ${
|
|
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)}`);
|
|
14233
15245
|
console.log("");
|
|
14234
15246
|
}
|
|
14235
15247
|
}
|
|
14236
15248
|
console.log(hr2());
|
|
14237
|
-
console.log(
|
|
14238
|
-
console.log(
|
|
14239
|
-
console.log(
|
|
14240
|
-
console.log(
|
|
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)"));
|
|
14241
15253
|
console.log("");
|
|
14242
15254
|
}
|
|
14243
15255
|
var TEMPLATE_FAMILY_META = {
|
|
@@ -14263,16 +15275,16 @@ var TEMPLATE_FAMILY_META = {
|
|
|
14263
15275
|
}
|
|
14264
15276
|
};
|
|
14265
15277
|
async function runTemplatePicker(opts) {
|
|
14266
|
-
|
|
15278
|
+
p18.intro(pc26.bold("Growthub Shared Template Library"));
|
|
14267
15279
|
let artifacts;
|
|
14268
15280
|
try {
|
|
14269
15281
|
artifacts = listArtifacts();
|
|
14270
15282
|
} catch (err) {
|
|
14271
|
-
|
|
15283
|
+
p18.log.error(err.message);
|
|
14272
15284
|
process.exit(1);
|
|
14273
15285
|
}
|
|
14274
15286
|
const families = [...new Set(artifacts.map((artifact) => artifact.family))];
|
|
14275
|
-
const familyChoice = await
|
|
15287
|
+
const familyChoice = await p18.select({
|
|
14276
15288
|
message: "What template type do you want to browse?",
|
|
14277
15289
|
options: [
|
|
14278
15290
|
...families.map((family) => {
|
|
@@ -14291,14 +15303,14 @@ async function runTemplatePicker(opts) {
|
|
|
14291
15303
|
...opts?.allowBackToHub ? [{ value: "__back_to_hub", label: "\u2190 Back to main menu" }] : []
|
|
14292
15304
|
]
|
|
14293
15305
|
});
|
|
14294
|
-
if (
|
|
14295
|
-
|
|
15306
|
+
if (p18.isCancel(familyChoice)) {
|
|
15307
|
+
p18.cancel("Cancelled.");
|
|
14296
15308
|
process.exit(0);
|
|
14297
15309
|
}
|
|
14298
15310
|
if (familyChoice === "__back_to_hub") return "back";
|
|
14299
15311
|
const filteredArtifacts = artifacts.filter((artifact) => artifact.family === familyChoice);
|
|
14300
15312
|
const groups = groupArtifacts(filteredArtifacts);
|
|
14301
|
-
const groupChoice = await
|
|
15313
|
+
const groupChoice = await p18.select({
|
|
14302
15314
|
message: "What kind of template?",
|
|
14303
15315
|
options: groups.map((g) => ({
|
|
14304
15316
|
value: g.key,
|
|
@@ -14306,26 +15318,26 @@ async function runTemplatePicker(opts) {
|
|
|
14306
15318
|
hint: `${g.count} available \xB7 ${g.description}`
|
|
14307
15319
|
}))
|
|
14308
15320
|
});
|
|
14309
|
-
if (
|
|
14310
|
-
|
|
15321
|
+
if (p18.isCancel(groupChoice)) {
|
|
15322
|
+
p18.cancel("Cancelled.");
|
|
14311
15323
|
process.exit(0);
|
|
14312
15324
|
}
|
|
14313
15325
|
const group = groups.find((g) => g.key === groupChoice);
|
|
14314
|
-
const artifactChoice = await
|
|
15326
|
+
const artifactChoice = await p18.select({
|
|
14315
15327
|
message: `Select from: ${group.label}`,
|
|
14316
15328
|
options: group.artifacts.map((a) => ({
|
|
14317
15329
|
value: a.id,
|
|
14318
|
-
label:
|
|
15330
|
+
label: pc26.bold(a.name),
|
|
14319
15331
|
hint: truncate2(a.category, 52)
|
|
14320
15332
|
}))
|
|
14321
15333
|
});
|
|
14322
|
-
if (
|
|
14323
|
-
|
|
15334
|
+
if (p18.isCancel(artifactChoice)) {
|
|
15335
|
+
p18.cancel("Cancelled.");
|
|
14324
15336
|
process.exit(0);
|
|
14325
15337
|
}
|
|
14326
15338
|
const selected = filteredArtifacts.find((a) => a.id === artifactChoice);
|
|
14327
15339
|
printCard(selected);
|
|
14328
|
-
const action = await
|
|
15340
|
+
const action = await p18.select({
|
|
14329
15341
|
message: "What would you like to do?",
|
|
14330
15342
|
options: [
|
|
14331
15343
|
{ value: "print", label: "\u{1F4C4} Print to terminal" },
|
|
@@ -14334,13 +15346,13 @@ async function runTemplatePicker(opts) {
|
|
|
14334
15346
|
{ value: "cancel", label: "Cancel" }
|
|
14335
15347
|
]
|
|
14336
15348
|
});
|
|
14337
|
-
if (
|
|
14338
|
-
|
|
15349
|
+
if (p18.isCancel(action) || action === "cancel") {
|
|
15350
|
+
p18.cancel("Cancelled.");
|
|
14339
15351
|
process.exit(0);
|
|
14340
15352
|
}
|
|
14341
15353
|
if (action === "slug") {
|
|
14342
15354
|
console.log(selected.slug);
|
|
14343
|
-
|
|
15355
|
+
p18.outro(pc26.dim("Use with: growthub template get " + selected.slug));
|
|
14344
15356
|
return "done";
|
|
14345
15357
|
}
|
|
14346
15358
|
if (action === "print") {
|
|
@@ -14348,22 +15360,22 @@ async function runTemplatePicker(opts) {
|
|
|
14348
15360
|
console.log("\n" + hr2());
|
|
14349
15361
|
console.log(r.content);
|
|
14350
15362
|
console.log(hr2());
|
|
14351
|
-
|
|
15363
|
+
p18.outro(pc26.dim("Source: " + r.absolutePath));
|
|
14352
15364
|
return "done";
|
|
14353
15365
|
}
|
|
14354
15366
|
if (action === "copy") {
|
|
14355
|
-
const destInput = await
|
|
15367
|
+
const destInput = await p18.text({
|
|
14356
15368
|
message: "Output directory:",
|
|
14357
15369
|
placeholder: "~/Downloads/templates",
|
|
14358
15370
|
validate: (v) => !v?.trim() ? "Path is required" : void 0
|
|
14359
15371
|
});
|
|
14360
|
-
if (
|
|
14361
|
-
|
|
15372
|
+
if (p18.isCancel(destInput)) {
|
|
15373
|
+
p18.cancel("Cancelled.");
|
|
14362
15374
|
process.exit(0);
|
|
14363
15375
|
}
|
|
14364
|
-
const destDir =
|
|
15376
|
+
const destDir = path26.resolve(destInput.replace(/^~/, process.env["HOME"] ?? ""));
|
|
14365
15377
|
const destPath = copyArtifact(selected.id, destDir);
|
|
14366
|
-
|
|
15378
|
+
p18.outro(pc26.green("Copied \u2192 ") + destPath);
|
|
14367
15379
|
return "done";
|
|
14368
15380
|
}
|
|
14369
15381
|
return "done";
|
|
@@ -14390,7 +15402,7 @@ Any agent or kit resolves them by slug.
|
|
|
14390
15402
|
if (opts.type) {
|
|
14391
15403
|
const t = opts.type.replace(/s$/, "");
|
|
14392
15404
|
if (t !== "ad-format" && t !== "scene-module") {
|
|
14393
|
-
console.error(
|
|
15405
|
+
console.error(pc26.red(`Unknown --type '${opts.type}'.`) + pc26.dim(" Valid: ad-formats, scene-modules"));
|
|
14394
15406
|
process.exitCode = 1;
|
|
14395
15407
|
return;
|
|
14396
15408
|
}
|
|
@@ -14399,7 +15411,7 @@ Any agent or kit resolves them by slug.
|
|
|
14399
15411
|
if (opts.subtype) {
|
|
14400
15412
|
const sub = opts.subtype.replace(/s$/, "");
|
|
14401
15413
|
if (!["hook", "body", "cta"].includes(sub)) {
|
|
14402
|
-
console.error(
|
|
15414
|
+
console.error(pc26.red(`Unknown --subtype '${opts.subtype}'.`) + pc26.dim(" Valid: hooks, body, cta"));
|
|
14403
15415
|
process.exitCode = 1;
|
|
14404
15416
|
return;
|
|
14405
15417
|
}
|
|
@@ -14415,18 +15427,18 @@ Any agent or kit resolves them by slug.
|
|
|
14415
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) => {
|
|
14416
15428
|
const artifact = resolveSlug(slug);
|
|
14417
15429
|
if (!artifact) {
|
|
14418
|
-
console.error(
|
|
15430
|
+
console.error(pc26.red(`Unknown template '${slug}'.`) + pc26.dim(" Run `growthub template list` to browse."));
|
|
14419
15431
|
process.exitCode = 1;
|
|
14420
15432
|
return;
|
|
14421
15433
|
}
|
|
14422
15434
|
if (artifact.id !== slug && artifact.slug !== slug) {
|
|
14423
|
-
console.error(
|
|
15435
|
+
console.error(pc26.dim(`Resolved '${slug}' \u2192 ${artifact.slug}`));
|
|
14424
15436
|
}
|
|
14425
15437
|
let resolved;
|
|
14426
15438
|
try {
|
|
14427
15439
|
resolved = getArtifact(artifact.id);
|
|
14428
15440
|
} catch (err) {
|
|
14429
|
-
console.error(
|
|
15441
|
+
console.error(pc26.red(err.message));
|
|
14430
15442
|
process.exitCode = 1;
|
|
14431
15443
|
return;
|
|
14432
15444
|
}
|
|
@@ -14435,12 +15447,12 @@ Any agent or kit resolves them by slug.
|
|
|
14435
15447
|
return;
|
|
14436
15448
|
}
|
|
14437
15449
|
if (opts.out) {
|
|
14438
|
-
const destDir =
|
|
15450
|
+
const destDir = path26.resolve(opts.out.replace(/^~/, process.env["HOME"] ?? ""));
|
|
14439
15451
|
try {
|
|
14440
15452
|
const dest = copyArtifact(artifact.id, destDir);
|
|
14441
|
-
console.log(
|
|
15453
|
+
console.log(pc26.green("Copied \u2192 ") + dest);
|
|
14442
15454
|
} catch (err) {
|
|
14443
|
-
console.error(
|
|
15455
|
+
console.error(pc26.red(err.message));
|
|
14444
15456
|
process.exitCode = 1;
|
|
14445
15457
|
}
|
|
14446
15458
|
return;
|
|
@@ -14449,7 +15461,7 @@ Any agent or kit resolves them by slug.
|
|
|
14449
15461
|
console.log(hr2());
|
|
14450
15462
|
console.log(resolved.content);
|
|
14451
15463
|
console.log(hr2());
|
|
14452
|
-
console.log(
|
|
15464
|
+
console.log(pc26.dim("Source: " + resolved.absolutePath));
|
|
14453
15465
|
console.log("");
|
|
14454
15466
|
});
|
|
14455
15467
|
}
|
|
@@ -14501,12 +15513,31 @@ function registerSharedCommands(target) {
|
|
|
14501
15513
|
registerTemplateCommands(target);
|
|
14502
15514
|
const auth = target.command("auth").description("Authentication and bootstrap utilities");
|
|
14503
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
|
+
});
|
|
14504
15535
|
}
|
|
14505
15536
|
async function runDiscoveryHub(opts) {
|
|
14506
15537
|
printPaperclipCliBanner();
|
|
14507
|
-
|
|
15538
|
+
p19.intro("Growthub Local");
|
|
14508
15539
|
while (true) {
|
|
14509
|
-
const surfaceChoice = await
|
|
15540
|
+
const surfaceChoice = await p19.select({
|
|
14510
15541
|
message: "What do you want to do first?",
|
|
14511
15542
|
options: [
|
|
14512
15543
|
{
|
|
@@ -14524,6 +15555,11 @@ async function runDiscoveryHub(opts) {
|
|
|
14524
15555
|
label: "\u{1F4DA} Templates",
|
|
14525
15556
|
hint: "Artifact template library"
|
|
14526
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
|
+
},
|
|
14527
15563
|
{
|
|
14528
15564
|
value: "help",
|
|
14529
15565
|
label: "\u2753 Help CLI",
|
|
@@ -14531,23 +15567,23 @@ async function runDiscoveryHub(opts) {
|
|
|
14531
15567
|
}
|
|
14532
15568
|
]
|
|
14533
15569
|
});
|
|
14534
|
-
if (
|
|
14535
|
-
|
|
15570
|
+
if (p19.isCancel(surfaceChoice)) {
|
|
15571
|
+
p19.cancel("Cancelled.");
|
|
14536
15572
|
process.exit(0);
|
|
14537
15573
|
}
|
|
14538
15574
|
if (surfaceChoice === "help") {
|
|
14539
|
-
|
|
15575
|
+
p19.note(
|
|
14540
15576
|
[
|
|
14541
15577
|
"\u{1F4E6} Full Local App: open an existing local surface or create a new GTM/DX profile.",
|
|
14542
15578
|
"\u{1F9F0} Worker Kits: browse specialized agents and custom workspaces.",
|
|
14543
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.",
|
|
14544
15581
|
"",
|
|
14545
15582
|
"Direct commands:",
|
|
14546
|
-
"growthub
|
|
15583
|
+
"growthub auth login",
|
|
15584
|
+
"growthub auth whoami",
|
|
14547
15585
|
"growthub kit",
|
|
14548
|
-
"growthub template"
|
|
14549
|
-
"growthub doctor",
|
|
14550
|
-
"growthub configure"
|
|
15586
|
+
"growthub template"
|
|
14551
15587
|
].join("\n"),
|
|
14552
15588
|
"Growthub CLI Help"
|
|
14553
15589
|
);
|
|
@@ -14555,7 +15591,7 @@ async function runDiscoveryHub(opts) {
|
|
|
14555
15591
|
}
|
|
14556
15592
|
if (surfaceChoice === "app") {
|
|
14557
15593
|
while (true) {
|
|
14558
|
-
const appModeChoice = await
|
|
15594
|
+
const appModeChoice = await p19.select({
|
|
14559
15595
|
message: "How do you want to open Growthub Local?",
|
|
14560
15596
|
options: [
|
|
14561
15597
|
{
|
|
@@ -14574,18 +15610,18 @@ async function runDiscoveryHub(opts) {
|
|
|
14574
15610
|
}
|
|
14575
15611
|
]
|
|
14576
15612
|
});
|
|
14577
|
-
if (
|
|
14578
|
-
|
|
15613
|
+
if (p19.isCancel(appModeChoice)) {
|
|
15614
|
+
p19.cancel("Cancelled.");
|
|
14579
15615
|
process.exit(0);
|
|
14580
15616
|
}
|
|
14581
15617
|
if (appModeChoice === "__back_to_hub") break;
|
|
14582
15618
|
if (appModeChoice === "load") {
|
|
14583
15619
|
const existingSurfaces = listLocalSurfaces();
|
|
14584
15620
|
if (existingSurfaces.length === 0) {
|
|
14585
|
-
|
|
15621
|
+
p19.note("No existing local app profiles were found on this machine.", "Nothing found");
|
|
14586
15622
|
continue;
|
|
14587
15623
|
}
|
|
14588
|
-
const existingChoice = await
|
|
15624
|
+
const existingChoice = await p19.select({
|
|
14589
15625
|
message: "Select an existing app surface",
|
|
14590
15626
|
options: [
|
|
14591
15627
|
...existingSurfaces.map((surface) => ({
|
|
@@ -14596,8 +15632,8 @@ async function runDiscoveryHub(opts) {
|
|
|
14596
15632
|
{ value: "__back_to_app_mode", label: "\u2190 Back to app options" }
|
|
14597
15633
|
]
|
|
14598
15634
|
});
|
|
14599
|
-
if (
|
|
14600
|
-
|
|
15635
|
+
if (p19.isCancel(existingChoice)) {
|
|
15636
|
+
p19.cancel("Cancelled.");
|
|
14601
15637
|
process.exit(0);
|
|
14602
15638
|
}
|
|
14603
15639
|
if (existingChoice === "__back_to_app_mode") {
|
|
@@ -14605,7 +15641,7 @@ async function runDiscoveryHub(opts) {
|
|
|
14605
15641
|
}
|
|
14606
15642
|
const selectedSurface = existingSurfaces.find((surface) => surface.instanceId === existingChoice);
|
|
14607
15643
|
if (!selectedSurface) {
|
|
14608
|
-
|
|
15644
|
+
p19.cancel("Selected profile not found.");
|
|
14609
15645
|
process.exit(1);
|
|
14610
15646
|
}
|
|
14611
15647
|
process.env.PAPERCLIP_SURFACE_PROFILE = selectedSurface.profile;
|
|
@@ -14617,7 +15653,7 @@ async function runDiscoveryHub(opts) {
|
|
|
14617
15653
|
});
|
|
14618
15654
|
return;
|
|
14619
15655
|
}
|
|
14620
|
-
const profileChoice = await
|
|
15656
|
+
const profileChoice = await p19.select({
|
|
14621
15657
|
message: "Which new app surface do you want to create?",
|
|
14622
15658
|
options: [
|
|
14623
15659
|
{
|
|
@@ -14636,8 +15672,8 @@ async function runDiscoveryHub(opts) {
|
|
|
14636
15672
|
}
|
|
14637
15673
|
]
|
|
14638
15674
|
});
|
|
14639
|
-
if (
|
|
14640
|
-
|
|
15675
|
+
if (p19.isCancel(profileChoice)) {
|
|
15676
|
+
p19.cancel("Cancelled.");
|
|
14641
15677
|
process.exit(0);
|
|
14642
15678
|
}
|
|
14643
15679
|
if (profileChoice === "__back_to_app_mode") {
|
|
@@ -14658,6 +15694,10 @@ async function runDiscoveryHub(opts) {
|
|
|
14658
15694
|
if (result2 === "back") continue;
|
|
14659
15695
|
return;
|
|
14660
15696
|
}
|
|
15697
|
+
if (surfaceChoice === "hosted-auth") {
|
|
15698
|
+
await runHostedBridgeEntry({ config: opts?.config, dataDir: opts?.dataDir });
|
|
15699
|
+
continue;
|
|
15700
|
+
}
|
|
14661
15701
|
const result = await runTemplatePicker({ allowBackToHub: true });
|
|
14662
15702
|
if (result === "back") continue;
|
|
14663
15703
|
return;
|
|
@@ -14668,12 +15708,12 @@ function isInstallerMode() {
|
|
|
14668
15708
|
}
|
|
14669
15709
|
function listLocalSurfaces() {
|
|
14670
15710
|
const homeDir = resolvePaperclipHomeDir();
|
|
14671
|
-
const instancesDir =
|
|
14672
|
-
if (!
|
|
14673
|
-
return
|
|
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) => {
|
|
14674
15714
|
const instanceId = entry.name;
|
|
14675
|
-
const configPath =
|
|
14676
|
-
if (!
|
|
15715
|
+
const configPath = path27.resolve(instancesDir, instanceId, "config.json");
|
|
15716
|
+
if (!fs19.existsSync(configPath)) return null;
|
|
14677
15717
|
try {
|
|
14678
15718
|
const config = readConfig(configPath);
|
|
14679
15719
|
if (!config) return null;
|
|
@@ -14714,7 +15754,7 @@ applyDataDirOverride(bootstrapOptions, {
|
|
|
14714
15754
|
loadPaperclipEnvFile(bootstrapOptions.config);
|
|
14715
15755
|
var bootstrapConfig = readConfig(resolveConfigPath(bootstrapOptions.config));
|
|
14716
15756
|
var surfaceRuntime = initializeSurfaceRuntimeContract(resolveSurfaceProfile(bootstrapConfig) ?? void 0);
|
|
14717
|
-
program.name("growthub").description("Growthub CLI \u2014 setup, configure, and run your local Growthub instance").version("0.3.
|
|
15757
|
+
program.name("growthub").description("Growthub CLI \u2014 setup, configure, and run your local Growthub instance").version("0.3.48").addHelpText("after", `
|
|
14718
15758
|
Worker Kits (agent execution environments):
|
|
14719
15759
|
|
|
14720
15760
|
Discovery:
|
|
@@ -14745,6 +15785,11 @@ Instance setup:
|
|
|
14745
15785
|
$ growthub doctor Diagnose and optionally repair
|
|
14746
15786
|
$ growthub configure Update config sections
|
|
14747
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)
|
|
14748
15793
|
`);
|
|
14749
15794
|
program.action(async () => {
|
|
14750
15795
|
await runDiscoveryHub();
|