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