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