jfl 0.2.5 → 0.4.2
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 +308 -28
- package/dist/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +428 -27
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/eval.d.ts +6 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +236 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/flows.d.ts +4 -1
- package/dist/commands/flows.d.ts.map +1 -1
- package/dist/commands/flows.js +160 -1
- package/dist/commands/flows.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +272 -145
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/peter.d.ts.map +1 -1
- package/dist/commands/peter.js +220 -1
- package/dist/commands/peter.js.map +1 -1
- package/dist/commands/pi.d.ts +21 -0
- package/dist/commands/pi.d.ts.map +1 -0
- package/dist/commands/pi.js +154 -0
- package/dist/commands/pi.js.map +1 -0
- package/dist/commands/portfolio.d.ts +6 -0
- package/dist/commands/portfolio.d.ts.map +1 -0
- package/dist/commands/portfolio.js +249 -0
- package/dist/commands/portfolio.js.map +1 -0
- package/dist/commands/predict.d.ts +6 -0
- package/dist/commands/predict.d.ts.map +1 -0
- package/dist/commands/predict.js +234 -0
- package/dist/commands/predict.js.map +1 -0
- package/dist/commands/scope.d.ts +1 -0
- package/dist/commands/scope.d.ts.map +1 -1
- package/dist/commands/scope.js +189 -2
- package/dist/commands/scope.js.map +1 -1
- package/dist/commands/synopsis.d.ts +44 -0
- package/dist/commands/synopsis.d.ts.map +1 -1
- package/dist/commands/synopsis.js +1 -1
- package/dist/commands/synopsis.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +49 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/viz.d.ts +7 -0
- package/dist/commands/viz.d.ts.map +1 -0
- package/dist/commands/viz.js +460 -0
- package/dist/commands/viz.js.map +1 -0
- package/dist/commands/voice.js.map +1 -1
- package/dist/dashboard/index.d.ts +4 -5
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +57 -119
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard-static/assets/index-B6kRK9Rq.js +116 -0
- package/dist/dashboard-static/assets/index-BpdKJPLu.css +1 -0
- package/dist/dashboard-static/index.html +16 -0
- package/dist/index.js +120 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/eval-store.d.ts +15 -0
- package/dist/lib/eval-store.d.ts.map +1 -0
- package/dist/lib/eval-store.js +179 -0
- package/dist/lib/eval-store.js.map +1 -0
- package/dist/lib/flow-engine.d.ts +13 -0
- package/dist/lib/flow-engine.d.ts.map +1 -1
- package/dist/lib/flow-engine.js +164 -3
- package/dist/lib/flow-engine.js.map +1 -1
- package/dist/lib/hub-client.d.ts +80 -0
- package/dist/lib/hub-client.d.ts.map +1 -0
- package/dist/lib/hub-client.js +46 -0
- package/dist/lib/hub-client.js.map +1 -0
- package/dist/lib/predictor.d.ts +99 -0
- package/dist/lib/predictor.d.ts.map +1 -0
- package/dist/lib/predictor.js +394 -0
- package/dist/lib/predictor.js.map +1 -0
- package/dist/lib/service-gtm.d.ts +88 -44
- package/dist/lib/service-gtm.d.ts.map +1 -1
- package/dist/lib/service-gtm.js +451 -243
- package/dist/lib/service-gtm.js.map +1 -1
- package/dist/lib/telemetry-agent.d.ts +57 -0
- package/dist/lib/telemetry-agent.d.ts.map +1 -0
- package/dist/lib/telemetry-agent.js +268 -0
- package/dist/lib/telemetry-agent.js.map +1 -0
- package/dist/lib/telemetry-digest.d.ts.map +1 -1
- package/dist/lib/telemetry-digest.js +17 -17
- package/dist/lib/telemetry-digest.js.map +1 -1
- package/dist/lib/telemetry.d.ts +1 -0
- package/dist/lib/telemetry.d.ts.map +1 -1
- package/dist/lib/telemetry.js +14 -6
- package/dist/lib/telemetry.js.map +1 -1
- package/dist/lib/trajectory-loader.d.ts +82 -0
- package/dist/lib/trajectory-loader.d.ts.map +1 -0
- package/dist/lib/trajectory-loader.js +406 -0
- package/dist/lib/trajectory-loader.js.map +1 -0
- package/dist/mcp/context-hub-mcp.js +60 -0
- package/dist/mcp/context-hub-mcp.js.map +1 -1
- package/dist/mcp/service-registry-mcp.js +0 -0
- package/dist/types/eval.d.ts +18 -0
- package/dist/types/eval.d.ts.map +1 -0
- package/dist/types/eval.js +5 -0
- package/dist/types/eval.js.map +1 -0
- package/dist/types/journal.d.ts +133 -0
- package/dist/types/journal.d.ts.map +1 -0
- package/dist/types/journal.js +59 -0
- package/dist/types/journal.js.map +1 -0
- package/dist/types/map.d.ts +1 -1
- package/dist/types/map.d.ts.map +1 -1
- package/dist/types/map.js.map +1 -1
- package/dist/ui/service-dashboard.js.map +1 -1
- package/dist/utils/jfl-paths.d.ts +1 -0
- package/dist/utils/jfl-paths.d.ts.map +1 -1
- package/dist/utils/jfl-paths.js +1 -0
- package/dist/utils/jfl-paths.js.map +1 -1
- package/dist/utils/wallet.js.map +1 -1
- package/package.json +7 -2
- package/scripts/generate-changesets.sh +113 -0
- package/scripts/migrate-to-branch-sessions.sh +201 -0
- package/scripts/pp-branch-pr.sh +115 -0
- package/scripts/session/session-cleanup.sh +29 -14
- package/scripts/session/session-end.sh +0 -10
- package/scripts/session/session-init.sh +0 -16
- package/scripts/session/session-sync.sh +0 -10
- package/template/.jfl/flows-self-driving.yaml +170 -0
- package/template/THEORY.md +26 -0
- package/template/scripts/session/session-cleanup.sh +28 -10
- package/dist/dashboard/components.d.ts +0 -7
- package/dist/dashboard/components.d.ts.map +0 -1
- package/dist/dashboard/components.js +0 -163
- package/dist/dashboard/components.js.map +0 -1
- package/dist/dashboard/pages.d.ts +0 -7
- package/dist/dashboard/pages.d.ts.map +0 -1
- package/dist/dashboard/pages.js +0 -742
- package/dist/dashboard/pages.js.map +0 -1
- package/dist/dashboard/styles.d.ts +0 -7
- package/dist/dashboard/styles.d.ts.map +0 -1
- package/dist/dashboard/styles.js +0 -497
- package/dist/dashboard/styles.js.map +0 -1
package/dist/commands/init.js
CHANGED
|
@@ -17,7 +17,7 @@ import { ensureDaemonInstalled } from "./context-hub.js";
|
|
|
17
17
|
const TEMPLATE_REPO = "https://github.com/402goose/jfl-template.git";
|
|
18
18
|
export async function initCommand(options) {
|
|
19
19
|
// Start Clawdbot-style flow
|
|
20
|
-
p.intro(chalk.hex("#FFD700")("┌ JFL - Initialize
|
|
20
|
+
p.intro(chalk.hex("#FFD700")("┌ JFL - Initialize Workspace"));
|
|
21
21
|
// Check authentication - owner needs to be verified
|
|
22
22
|
let ownerName = "";
|
|
23
23
|
let ownerGithub = "";
|
|
@@ -67,6 +67,19 @@ export async function initCommand(options) {
|
|
|
67
67
|
}
|
|
68
68
|
projectName = name;
|
|
69
69
|
}
|
|
70
|
+
// Ask workspace type
|
|
71
|
+
const workspaceType = await p.select({
|
|
72
|
+
message: "Workspace type:",
|
|
73
|
+
options: [
|
|
74
|
+
{ label: "GTM", value: "gtm", hint: "Single product workspace" },
|
|
75
|
+
{ label: "Portfolio", value: "portfolio", hint: "Manages multiple products" },
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
if (p.isCancel(workspaceType)) {
|
|
79
|
+
p.cancel("Setup cancelled.");
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
const isPortfolio = workspaceType === "portfolio";
|
|
70
83
|
// If cwd already matches the project name, initialize in place
|
|
71
84
|
const cwdBasename = basename(process.cwd());
|
|
72
85
|
const initInPlace = cwdBasename === projectName;
|
|
@@ -95,7 +108,7 @@ export async function initCommand(options) {
|
|
|
95
108
|
return;
|
|
96
109
|
}
|
|
97
110
|
// Clone template to temp directory, copy only template/ folder
|
|
98
|
-
const spinner = ora(
|
|
111
|
+
const spinner = ora(`Downloading ${isPortfolio ? "portfolio" : "GTM"} template...`).start();
|
|
99
112
|
const tempDir = join(tmpdir(), `jfl-init-${Date.now()}`);
|
|
100
113
|
try {
|
|
101
114
|
// Clone to temp
|
|
@@ -150,7 +163,7 @@ export async function initCommand(options) {
|
|
|
150
163
|
catch {
|
|
151
164
|
execSync(`git init`, { cwd: projectPath, stdio: "pipe" });
|
|
152
165
|
}
|
|
153
|
-
spinner.succeed("GTM workspace created
|
|
166
|
+
spinner.succeed(`${isPortfolio ? "Portfolio" : "GTM"} workspace created!`);
|
|
154
167
|
// Validate and fix .claude/settings.json
|
|
155
168
|
const settingsPath = join(projectPath, ".claude", "settings.json");
|
|
156
169
|
if (existsSync(settingsPath)) {
|
|
@@ -181,165 +194,233 @@ export async function initCommand(options) {
|
|
|
181
194
|
process.exit(0);
|
|
182
195
|
}
|
|
183
196
|
let productRepo = null;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
197
|
+
const portfolioChildGtms = [];
|
|
198
|
+
if (isPortfolio) {
|
|
199
|
+
// Portfolio: register child GTM workspaces
|
|
200
|
+
p.note("Register existing GTM workspaces under this portfolio.\n" +
|
|
201
|
+
"Each GTM gets its own eval pipeline, journals, and event bus.\n" +
|
|
202
|
+
"The portfolio aggregates across all of them.", chalk.hex("#FFA500")("Portfolio Setup"));
|
|
203
|
+
const registerGtms = await p.confirm({
|
|
204
|
+
message: "Register GTM workspaces now?",
|
|
205
|
+
initialValue: true,
|
|
206
|
+
});
|
|
207
|
+
if (!p.isCancel(registerGtms) && registerGtms) {
|
|
208
|
+
let adding = true;
|
|
209
|
+
let gtmCount = 0;
|
|
210
|
+
while (adding) {
|
|
211
|
+
const gtmPathInput = await p.text({
|
|
212
|
+
message: gtmCount === 0
|
|
213
|
+
? "GTM workspace path:"
|
|
214
|
+
: "Another GTM path (or Enter to skip):",
|
|
215
|
+
placeholder: "/path/to/my-product-gtm",
|
|
216
|
+
validate: (input) => {
|
|
217
|
+
if (gtmCount === 0 && !input.trim()) {
|
|
218
|
+
return "Enter at least one GTM path";
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
if (p.isCancel(gtmPathInput))
|
|
223
|
+
break;
|
|
224
|
+
if (!gtmPathInput || gtmPathInput.trim() === "") {
|
|
225
|
+
adding = false;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
const resolvedGtmPath = join(process.cwd(), gtmPathInput).replace(/\/+$/, "");
|
|
229
|
+
const absGtmPath = gtmPathInput.startsWith("/") ? gtmPathInput : resolvedGtmPath;
|
|
230
|
+
const gtmConfigPath = join(absGtmPath, ".jfl", "config.json");
|
|
231
|
+
if (!existsSync(gtmConfigPath)) {
|
|
232
|
+
p.log.warning(`No .jfl/config.json at ${absGtmPath} — skipping`);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const gtmConfig = JSON.parse(readFileSync(gtmConfigPath, "utf-8"));
|
|
237
|
+
if (gtmConfig.type !== "gtm") {
|
|
238
|
+
p.log.warning(`${absGtmPath} is type "${gtmConfig.type}", not "gtm" — skipping`);
|
|
239
|
+
continue;
|
|
217
240
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
241
|
+
const { getRegisteredServices } = await import("../lib/service-gtm.js");
|
|
242
|
+
const svcCount = getRegisteredServices(absGtmPath).length;
|
|
243
|
+
portfolioChildGtms.push({
|
|
244
|
+
name: gtmConfig.name,
|
|
245
|
+
path: absGtmPath,
|
|
246
|
+
type: "gtm",
|
|
247
|
+
registered_at: new Date().toISOString(),
|
|
248
|
+
status: "active",
|
|
249
|
+
context_scope: gtmConfig.context_scope,
|
|
250
|
+
});
|
|
251
|
+
// Write portfolio_parent back to child
|
|
252
|
+
gtmConfig.portfolio_parent = projectPath;
|
|
253
|
+
writeFileSync(gtmConfigPath, JSON.stringify(gtmConfig, null, 2));
|
|
254
|
+
p.log.success(`Registered: ${gtmConfig.name} (${svcCount} services)`);
|
|
255
|
+
gtmCount++;
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
p.log.warning(`Failed to read ${absGtmPath}: ${err.message}`);
|
|
259
|
+
}
|
|
223
260
|
}
|
|
224
|
-
productRepo = repoUrl;
|
|
225
|
-
p.log.success(`Product repo registered: ${productRepo}`);
|
|
226
|
-
p.log.info("Use service agents to work on product code directly in its own repo.");
|
|
227
261
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
262
|
+
}
|
|
263
|
+
if (!isPortfolio) {
|
|
264
|
+
// Ask about product repo (registered as service, NOT a submodule)
|
|
265
|
+
const productChoice = await p.select({
|
|
266
|
+
message: "Product repo:",
|
|
267
|
+
options: [
|
|
268
|
+
{
|
|
269
|
+
label: "I have an existing repo",
|
|
270
|
+
value: "existing",
|
|
271
|
+
hint: "Register as a service"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
label: "Create a new repo for me",
|
|
275
|
+
value: "create",
|
|
276
|
+
hint: "Requires: gh CLI"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
label: "I'll add it later",
|
|
280
|
+
value: "later",
|
|
281
|
+
hint: "Recommended if unsure"
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
});
|
|
285
|
+
if (p.isCancel(productChoice)) {
|
|
286
|
+
p.cancel("Setup cancelled.");
|
|
287
|
+
process.exit(0);
|
|
288
|
+
}
|
|
289
|
+
if (productChoice !== "later") {
|
|
290
|
+
if (productChoice === "existing") {
|
|
291
|
+
const repoUrl = await p.text({
|
|
292
|
+
message: "Product repo URL:",
|
|
293
|
+
placeholder: "https://github.com/user/repo.git",
|
|
294
|
+
validate: (input) => {
|
|
295
|
+
if (!input.trim()) {
|
|
296
|
+
return "Please enter a repo URL";
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
if (p.isCancel(repoUrl)) {
|
|
301
|
+
p.cancel("Setup cancelled.");
|
|
302
|
+
process.exit(0);
|
|
303
|
+
}
|
|
304
|
+
productRepo = repoUrl;
|
|
305
|
+
p.log.success(`Product repo registered: ${productRepo}`);
|
|
306
|
+
p.log.info("Use service agents to work on product code directly in its own repo.");
|
|
307
|
+
}
|
|
308
|
+
else if (productChoice === "create") {
|
|
309
|
+
// Check if gh CLI is available
|
|
310
|
+
try {
|
|
311
|
+
execSync("gh --version", { stdio: "pipe" });
|
|
312
|
+
let repoCreated = false;
|
|
313
|
+
let attemptCount = 0;
|
|
314
|
+
const maxAttempts = 3;
|
|
315
|
+
while (!repoCreated && attemptCount < maxAttempts) {
|
|
316
|
+
attemptCount++;
|
|
317
|
+
const repoName = await p.text({
|
|
318
|
+
message: attemptCount > 1 ? "Try a different name:" : "New repo name:",
|
|
319
|
+
placeholder: attemptCount > 1
|
|
320
|
+
? `${projectName.replace(/-gtm$/, "")}-${attemptCount}`
|
|
321
|
+
: projectName.replace(/-gtm$/, ""),
|
|
322
|
+
validate: (input) => {
|
|
323
|
+
if (!input.trim()) {
|
|
324
|
+
return "Please enter a repo name";
|
|
325
|
+
}
|
|
326
|
+
if (/\s/.test(input)) {
|
|
327
|
+
return "Repo names cannot contain spaces. Use hyphens instead (e.g., 'my-project')";
|
|
328
|
+
}
|
|
329
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(input)) {
|
|
330
|
+
return "Repo names can only contain letters, numbers, hyphens, underscores, and dots";
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
if (p.isCancel(repoName)) {
|
|
335
|
+
p.cancel("Setup cancelled.");
|
|
336
|
+
process.exit(0);
|
|
337
|
+
}
|
|
338
|
+
const visibility = await p.select({
|
|
339
|
+
message: "Visibility:",
|
|
340
|
+
options: [
|
|
341
|
+
{ label: "Private", value: "private" },
|
|
342
|
+
{ label: "Public", value: "public" },
|
|
343
|
+
],
|
|
344
|
+
});
|
|
345
|
+
if (p.isCancel(visibility)) {
|
|
346
|
+
p.cancel("Setup cancelled.");
|
|
347
|
+
process.exit(0);
|
|
287
348
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
349
|
+
const createSpinner = ora("Creating product repo...").start();
|
|
350
|
+
try {
|
|
351
|
+
const visFlag = visibility === "private" ? "--private" : "--public";
|
|
352
|
+
const finalRepoName = repoName.trim().replace(/\s+/g, '-');
|
|
353
|
+
try {
|
|
354
|
+
execSync(`gh repo create ${finalRepoName} ${visFlag}`, {
|
|
355
|
+
cwd: projectPath,
|
|
356
|
+
stdio: "pipe",
|
|
357
|
+
encoding: "utf-8",
|
|
358
|
+
});
|
|
359
|
+
// Get the repo URL
|
|
360
|
+
const repoUrl = execSync(`gh repo view ${finalRepoName} --json url -q .url`, {
|
|
361
|
+
cwd: projectPath,
|
|
362
|
+
encoding: "utf-8",
|
|
363
|
+
}).trim();
|
|
364
|
+
createSpinner.succeed(`Product repo created: ${repoUrl}`);
|
|
365
|
+
productRepo = repoUrl;
|
|
366
|
+
repoCreated = true;
|
|
367
|
+
}
|
|
368
|
+
catch (createErr) {
|
|
369
|
+
createSpinner.fail("Failed to create repo");
|
|
294
370
|
console.log("");
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
371
|
+
const errorMsg = createErr.stderr || createErr.message || String(createErr);
|
|
372
|
+
if (errorMsg.includes("already exists") || errorMsg.includes("Name already exists")) {
|
|
373
|
+
console.log(chalk.yellow("That name is already taken on your GitHub"));
|
|
374
|
+
console.log("");
|
|
375
|
+
if (attemptCount < maxAttempts) {
|
|
376
|
+
const retry = await p.confirm({
|
|
377
|
+
message: "Try a different name?",
|
|
378
|
+
initialValue: true,
|
|
379
|
+
});
|
|
380
|
+
if (p.isCancel(retry) || !retry) {
|
|
381
|
+
p.log.info("Skipping repo creation. Register it later with:");
|
|
382
|
+
p.log.info(" jfl services add <repo-url>");
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
p.log.warning("Max attempts reached. Register repo later:");
|
|
302
388
|
p.log.info(" jfl services add <repo-url>");
|
|
303
389
|
break;
|
|
304
390
|
}
|
|
305
391
|
}
|
|
392
|
+
else if (errorMsg.includes("authentication") || errorMsg.includes("not logged in") || errorMsg.includes("HTTP 401")) {
|
|
393
|
+
console.log(chalk.red("GitHub authentication required"));
|
|
394
|
+
console.log("");
|
|
395
|
+
p.log.info("Authenticate with GitHub:");
|
|
396
|
+
p.log.info(" gh auth login");
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
306
399
|
else {
|
|
307
|
-
|
|
308
|
-
|
|
400
|
+
console.log(chalk.red("Error from GitHub CLI:"));
|
|
401
|
+
console.log(chalk.gray(errorMsg));
|
|
402
|
+
console.log("");
|
|
403
|
+
p.log.info(`Try manually: gh repo create ${finalRepoName} ${visFlag}`);
|
|
309
404
|
break;
|
|
310
405
|
}
|
|
311
406
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
p.log.info("Authenticate with GitHub:");
|
|
316
|
-
p.log.info(" gh auth login");
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
console.log(chalk.red("Error from GitHub CLI:"));
|
|
321
|
-
console.log(chalk.gray(errorMsg));
|
|
322
|
-
console.log("");
|
|
323
|
-
p.log.info(`Try manually: gh repo create ${finalRepoName} ${visFlag}`);
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
break;
|
|
326
410
|
}
|
|
327
411
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
p.log.warning("GitHub CLI (gh) not found. Install it to create repos:");
|
|
415
|
+
p.log.info("brew install gh && gh auth login");
|
|
331
416
|
}
|
|
332
417
|
}
|
|
333
|
-
|
|
334
|
-
p.log.
|
|
335
|
-
p.log.info("
|
|
418
|
+
else {
|
|
419
|
+
p.log.info("Register your product repo later:");
|
|
420
|
+
p.log.info(" jfl services add <repo-url>");
|
|
336
421
|
}
|
|
337
422
|
}
|
|
338
|
-
|
|
339
|
-
p.log.info("Register your product repo later:");
|
|
340
|
-
p.log.info(" jfl services add <repo-url>");
|
|
341
|
-
}
|
|
342
|
-
}
|
|
423
|
+
} // end if (!isPortfolio)
|
|
343
424
|
// Update .jfl/config.json
|
|
344
425
|
const configDir = join(projectPath, ".jfl");
|
|
345
426
|
if (!existsSync(configDir)) {
|
|
@@ -348,7 +429,7 @@ export async function initCommand(options) {
|
|
|
348
429
|
const configPath = join(configDir, "config.json");
|
|
349
430
|
const config = {
|
|
350
431
|
name: projectName,
|
|
351
|
-
type: "gtm",
|
|
432
|
+
type: isPortfolio ? "portfolio" : "gtm",
|
|
352
433
|
description: description,
|
|
353
434
|
};
|
|
354
435
|
// Save owner info if authenticated
|
|
@@ -368,6 +449,10 @@ export async function initCommand(options) {
|
|
|
368
449
|
config.product_repo = productRepo;
|
|
369
450
|
config.registered_services = [{ name: "product", repo: productRepo }];
|
|
370
451
|
}
|
|
452
|
+
// Merge portfolio child GTMs collected earlier
|
|
453
|
+
if (isPortfolio && portfolioChildGtms.length > 0) {
|
|
454
|
+
config.registered_services = portfolioChildGtms;
|
|
455
|
+
}
|
|
371
456
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
372
457
|
// Assign deterministic port and write to config + .mcp.json
|
|
373
458
|
const hubPort = persistProjectPort(projectPath);
|
|
@@ -386,7 +471,7 @@ export async function initCommand(options) {
|
|
|
386
471
|
const profile = getProfile();
|
|
387
472
|
if (profile) {
|
|
388
473
|
// Generate CLAUDE.md from profile
|
|
389
|
-
const projectDescription = `GTM workspace for ${projectName}`;
|
|
474
|
+
const projectDescription = isPortfolio ? `Portfolio workspace for ${projectName}` : `GTM workspace for ${projectName}`;
|
|
390
475
|
const claudeContent = generateClaudeMdFromProfile(profile, {
|
|
391
476
|
name: projectName,
|
|
392
477
|
description: projectDescription
|
|
@@ -714,6 +799,48 @@ export async function initCommand(options) {
|
|
|
714
799
|
else {
|
|
715
800
|
p.log.info("Skip service onboarding. Add services later with: jfl onboard <path|url>");
|
|
716
801
|
}
|
|
802
|
+
// Configure HTTP hooks for telemetry
|
|
803
|
+
try {
|
|
804
|
+
const { getProjectPort } = await import("../utils/context-hub-port.js");
|
|
805
|
+
const port = getProjectPort(projectPath);
|
|
806
|
+
if (port) {
|
|
807
|
+
const hookUrl = `http://localhost:${port}/api/hooks`;
|
|
808
|
+
const settingsPath = join(projectPath, ".claude", "settings.json");
|
|
809
|
+
if (existsSync(settingsPath)) {
|
|
810
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
811
|
+
if (!settings.hooks)
|
|
812
|
+
settings.hooks = {};
|
|
813
|
+
const hookEvents = ["PostToolUse", "Stop", "PreCompact", "SubagentStart", "SubagentStop"];
|
|
814
|
+
let added = 0;
|
|
815
|
+
let fixed = 0;
|
|
816
|
+
for (const event of hookEvents) {
|
|
817
|
+
if (!settings.hooks[event])
|
|
818
|
+
settings.hooks[event] = [];
|
|
819
|
+
const existingIdx = settings.hooks[event].findIndex((e) => e.hooks?.some((h) => h.type === "http" && h.url?.includes("/api/hooks")));
|
|
820
|
+
if (existingIdx >= 0) {
|
|
821
|
+
const entry = settings.hooks[event][existingIdx];
|
|
822
|
+
const httpHook = entry.hooks.find((h) => h.type === "http" && h.url?.includes("/api/hooks"));
|
|
823
|
+
if (httpHook && httpHook.url !== hookUrl) {
|
|
824
|
+
httpHook.url = hookUrl;
|
|
825
|
+
fixed++;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
settings.hooks[event].push({ matcher: "", hooks: [{ type: "http", url: hookUrl }] });
|
|
830
|
+
added++;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (added > 0 || fixed > 0) {
|
|
834
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
835
|
+
if (added > 0)
|
|
836
|
+
p.log.success(`HTTP hooks added for ${added} events → ${hookUrl}`);
|
|
837
|
+
if (fixed > 0)
|
|
838
|
+
p.log.success(`HTTP hooks updated for ${fixed} events → ${hookUrl}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
catch { }
|
|
717
844
|
// Success message
|
|
718
845
|
let successMessage = `${projectName}/\n` +
|
|
719
846
|
"├── .claude/skills/ ← JFL skills\n" +
|