jfl 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/README.md +399 -423
  2. package/clawdbot-plugin/clawdbot.plugin.json +12 -1
  3. package/clawdbot-plugin/index.js +5 -5
  4. package/clawdbot-plugin/index.ts +5 -5
  5. package/dist/commands/context-hub.d.ts +4 -0
  6. package/dist/commands/context-hub.d.ts.map +1 -1
  7. package/dist/commands/context-hub.js +704 -83
  8. package/dist/commands/context-hub.js.map +1 -1
  9. package/dist/commands/digest.d.ts +6 -0
  10. package/dist/commands/digest.d.ts.map +1 -0
  11. package/dist/commands/digest.js +81 -0
  12. package/dist/commands/digest.js.map +1 -0
  13. package/dist/commands/flows.d.ts +7 -0
  14. package/dist/commands/flows.d.ts.map +1 -0
  15. package/dist/commands/flows.js +264 -0
  16. package/dist/commands/flows.js.map +1 -0
  17. package/dist/commands/hooks.d.ts +11 -0
  18. package/dist/commands/hooks.d.ts.map +1 -0
  19. package/dist/commands/hooks.js +303 -0
  20. package/dist/commands/hooks.js.map +1 -0
  21. package/dist/commands/improve.d.ts +11 -0
  22. package/dist/commands/improve.d.ts.map +1 -0
  23. package/dist/commands/improve.js +77 -0
  24. package/dist/commands/improve.js.map +1 -0
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +42 -11
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/peter.d.ts +15 -0
  29. package/dist/commands/peter.d.ts.map +1 -0
  30. package/dist/commands/peter.js +198 -0
  31. package/dist/commands/peter.js.map +1 -0
  32. package/dist/commands/ralph.d.ts +3 -1
  33. package/dist/commands/ralph.d.ts.map +1 -1
  34. package/dist/commands/ralph.js +40 -5
  35. package/dist/commands/ralph.js.map +1 -1
  36. package/dist/commands/scope.d.ts +7 -0
  37. package/dist/commands/scope.d.ts.map +1 -0
  38. package/dist/commands/scope.js +227 -0
  39. package/dist/commands/scope.js.map +1 -0
  40. package/dist/commands/service-validate.js +7 -1
  41. package/dist/commands/service-validate.js.map +1 -1
  42. package/dist/commands/session.d.ts +2 -1
  43. package/dist/commands/session.d.ts.map +1 -1
  44. package/dist/commands/session.js +519 -49
  45. package/dist/commands/session.js.map +1 -1
  46. package/dist/commands/update.d.ts.map +1 -1
  47. package/dist/commands/update.js +25 -6
  48. package/dist/commands/update.js.map +1 -1
  49. package/dist/dashboard/components.d.ts +7 -0
  50. package/dist/dashboard/components.d.ts.map +1 -0
  51. package/dist/dashboard/components.js +163 -0
  52. package/dist/dashboard/components.js.map +1 -0
  53. package/dist/dashboard/index.d.ts +12 -0
  54. package/dist/dashboard/index.d.ts.map +1 -0
  55. package/dist/dashboard/index.js +132 -0
  56. package/dist/dashboard/index.js.map +1 -0
  57. package/dist/dashboard/pages.d.ts +7 -0
  58. package/dist/dashboard/pages.d.ts.map +1 -0
  59. package/dist/dashboard/pages.js +742 -0
  60. package/dist/dashboard/pages.js.map +1 -0
  61. package/dist/dashboard/styles.d.ts +7 -0
  62. package/dist/dashboard/styles.d.ts.map +1 -0
  63. package/dist/dashboard/styles.js +497 -0
  64. package/dist/dashboard/styles.js.map +1 -0
  65. package/dist/index.js +196 -8
  66. package/dist/index.js.map +1 -1
  67. package/dist/lib/flow-engine.d.ts +34 -0
  68. package/dist/lib/flow-engine.d.ts.map +1 -0
  69. package/dist/lib/flow-engine.js +321 -0
  70. package/dist/lib/flow-engine.js.map +1 -0
  71. package/dist/lib/hook-transformer.d.ts +11 -0
  72. package/dist/lib/hook-transformer.d.ts.map +1 -0
  73. package/dist/lib/hook-transformer.js +74 -0
  74. package/dist/lib/hook-transformer.js.map +1 -0
  75. package/dist/lib/map-event-bus.d.ts +50 -0
  76. package/dist/lib/map-event-bus.d.ts.map +1 -0
  77. package/dist/lib/map-event-bus.js +366 -0
  78. package/dist/lib/map-event-bus.js.map +1 -0
  79. package/dist/lib/memory-indexer.d.ts.map +1 -1
  80. package/dist/lib/memory-indexer.js +26 -2
  81. package/dist/lib/memory-indexer.js.map +1 -1
  82. package/dist/lib/model-pricing.d.ts +11 -0
  83. package/dist/lib/model-pricing.d.ts.map +1 -0
  84. package/dist/lib/model-pricing.js +27 -0
  85. package/dist/lib/model-pricing.js.map +1 -0
  86. package/dist/lib/peter-parker-bridge.d.ts +34 -0
  87. package/dist/lib/peter-parker-bridge.d.ts.map +1 -0
  88. package/dist/lib/peter-parker-bridge.js +145 -0
  89. package/dist/lib/peter-parker-bridge.js.map +1 -0
  90. package/dist/lib/peter-parker-config.d.ts +13 -0
  91. package/dist/lib/peter-parker-config.d.ts.map +1 -0
  92. package/dist/lib/peter-parker-config.js +86 -0
  93. package/dist/lib/peter-parker-config.js.map +1 -0
  94. package/dist/lib/service-gtm.d.ts +7 -0
  95. package/dist/lib/service-gtm.d.ts.map +1 -1
  96. package/dist/lib/service-gtm.js.map +1 -1
  97. package/dist/lib/service-utils.d.ts.map +1 -1
  98. package/dist/lib/service-utils.js +33 -17
  99. package/dist/lib/service-utils.js.map +1 -1
  100. package/dist/lib/stratus-client.d.ts +1 -0
  101. package/dist/lib/stratus-client.d.ts.map +1 -1
  102. package/dist/lib/stratus-client.js +33 -2
  103. package/dist/lib/stratus-client.js.map +1 -1
  104. package/dist/lib/stratus-rollout-test.d.ts +10 -0
  105. package/dist/lib/stratus-rollout-test.d.ts.map +1 -0
  106. package/dist/lib/stratus-rollout-test.js +412 -0
  107. package/dist/lib/stratus-rollout-test.js.map +1 -0
  108. package/dist/lib/telemetry-digest.d.ts +10 -0
  109. package/dist/lib/telemetry-digest.d.ts.map +1 -0
  110. package/dist/lib/telemetry-digest.js +359 -0
  111. package/dist/lib/telemetry-digest.js.map +1 -0
  112. package/dist/lib/telemetry.d.ts +35 -0
  113. package/dist/lib/telemetry.d.ts.map +1 -0
  114. package/dist/lib/telemetry.js +320 -0
  115. package/dist/lib/telemetry.js.map +1 -0
  116. package/dist/lib/training-tuples.d.ts +33 -0
  117. package/dist/lib/training-tuples.d.ts.map +1 -0
  118. package/dist/lib/training-tuples.js +273 -0
  119. package/dist/lib/training-tuples.js.map +1 -0
  120. package/dist/mcp/context-hub-mcp.js +139 -22
  121. package/dist/mcp/context-hub-mcp.js.map +1 -1
  122. package/dist/types/flows.d.ts +62 -0
  123. package/dist/types/flows.d.ts.map +1 -0
  124. package/dist/types/flows.js +10 -0
  125. package/dist/types/flows.js.map +1 -0
  126. package/dist/types/map.d.ts +42 -0
  127. package/dist/types/map.d.ts.map +1 -0
  128. package/dist/types/map.js +39 -0
  129. package/dist/types/map.js.map +1 -0
  130. package/dist/types/telemetry-digest.d.ts +73 -0
  131. package/dist/types/telemetry-digest.d.ts.map +1 -0
  132. package/dist/types/telemetry-digest.js +5 -0
  133. package/dist/types/telemetry-digest.js.map +1 -0
  134. package/dist/types/telemetry.d.ts +69 -0
  135. package/dist/types/telemetry.d.ts.map +1 -0
  136. package/dist/types/telemetry.js +5 -0
  137. package/dist/types/telemetry.js.map +1 -0
  138. package/dist/ui/event-dashboard.d.ts +12 -0
  139. package/dist/ui/event-dashboard.d.ts.map +1 -0
  140. package/dist/ui/event-dashboard.js +342 -0
  141. package/dist/ui/event-dashboard.js.map +1 -0
  142. package/dist/utils/jfl-paths.d.ts +1 -0
  143. package/dist/utils/jfl-paths.d.ts.map +1 -1
  144. package/dist/utils/jfl-paths.js +1 -0
  145. package/dist/utils/jfl-paths.js.map +1 -1
  146. package/dist/utils/settings-validator.d.ts +3 -2
  147. package/dist/utils/settings-validator.d.ts.map +1 -1
  148. package/dist/utils/settings-validator.js +25 -6
  149. package/dist/utils/settings-validator.js.map +1 -1
  150. package/package.json +3 -2
  151. package/scripts/session/session-end.sh +10 -0
  152. package/scripts/session/session-init.sh +16 -0
  153. package/scripts/test-map-eventbus.sh +357 -0
@@ -1,53 +1,197 @@
1
1
  import chalk from "chalk";
2
2
  import ora from "ora";
3
3
  import * as p from "@clack/prompts";
4
- import { existsSync } from "fs";
4
+ import { execSync, spawn } from "child_process";
5
+ import { existsSync, readFileSync } from "fs";
5
6
  import { join } from "path";
6
7
  import { homedir } from "os";
7
8
  import Conf from "conf";
8
- import { ensureDayPass, isTrialMode } from "../utils/auth-guard.js";
9
9
  import { ensureContextHub, getContextHubConfig } from "../utils/ensure-context-hub.js";
10
- import { isRunning as getContextHubStatus } from "./context-hub.js";
10
+ import { isRunning as getContextHubStatus, ensureDaemonInstalled } from "./context-hub.js";
11
11
  import { initCommand } from "./init.js";
12
+ import { authenticateWithGitHub, discoverJflProjects, cloneRepository, } from "../utils/github-auth.js";
13
+ import { renderBanner, showHowItWorksNotice, theme } from "../ui/index.js";
12
14
  import axios from "axios";
15
+ import { telemetry } from "../lib/telemetry.js";
13
16
  const config = new Conf({ projectName: "jfl" });
17
+ const AUTONOMOUS_FLAGS = {
18
+ claude: "--dangerously-skip-permissions",
19
+ codex: "--full-auto",
20
+ aider: "--yes-always",
21
+ pi: "--yolo",
22
+ none: undefined,
23
+ };
24
+ const CHROME_FLAG = "--chrome";
14
25
  export async function sessionCommand(options = {}) {
15
26
  const cwd = process.cwd();
16
- // Check if in a JFL project
17
27
  const hasJflConfig = existsSync(join(cwd, "CLAUDE.md")) || existsSync(join(cwd, "knowledge"));
18
28
  if (!hasJflConfig) {
19
29
  await onboardNewUser(cwd);
20
30
  return;
21
31
  }
22
- // Check day pass for paid users (trial mode is free)
23
- if (!isTrialMode()) {
24
- console.log(chalk.yellow("\nšŸ’³ Payment required (teammates detected)\n"));
25
- const dayPass = await ensureDayPass();
26
- if (!dayPass) {
27
- console.log(chalk.red("\nāŒ Cannot start session without active Day Pass\n"));
28
- console.log(chalk.gray("Run: jfl login --x402"));
29
- console.log();
30
- return;
31
- }
32
- console.log(chalk.green("āœ“ Day Pass verified\n"));
33
- }
34
- else {
35
- console.log(chalk.green("šŸŽ Trial Mode") + chalk.gray(" - Free until foundation complete\n"));
36
- }
37
32
  // Track this project
38
33
  const projects = config.get("projects") || [];
39
34
  if (!projects.includes(cwd)) {
40
35
  projects.push(cwd);
41
36
  config.set("projects", projects);
42
37
  }
43
- // Show JFL Gateway Dashboard
44
- await showGatewayDashboard();
38
+ // Auto-install daemon (fire-and-forget, silent)
39
+ ensureDaemonInstalled({ quiet: true }).catch(() => { });
40
+ // Check for saved CLI preference
41
+ let preferredCLI = config.get("aiCLI");
42
+ // Detect available CLIs
43
+ const available = detectAICLIs();
44
+ if (available.length === 0) {
45
+ await onboardAICLI();
46
+ return;
47
+ }
48
+ // If preference is set and still available, use it
49
+ if (preferredCLI) {
50
+ const preferred = available.find(cli => cli.command === preferredCLI);
51
+ if (preferred) {
52
+ await launchCLI(preferred, cwd);
53
+ return;
54
+ }
55
+ }
56
+ // If only one option, use it
57
+ if (available.length === 1) {
58
+ config.set("aiCLI", available[0].command);
59
+ await launchCLI(available[0], cwd);
60
+ return;
61
+ }
62
+ // Multiple options - let them choose
63
+ console.log(chalk.bold("\nšŸ¤– JFL - AI Session\n"));
64
+ console.log(chalk.gray("Multiple AI CLIs detected:\n"));
65
+ const selected = await p.select({
66
+ message: "Which AI do you want to use?",
67
+ options: available.map(cli => ({
68
+ label: `${cli.name}${cli.version ? ` (${cli.version})` : ""}`,
69
+ value: cli.command,
70
+ })),
71
+ });
72
+ if (p.isCancel(selected)) {
73
+ p.cancel("Session cancelled.");
74
+ return;
75
+ }
76
+ const remember = await p.confirm({
77
+ message: "Remember this choice?",
78
+ initialValue: true,
79
+ });
80
+ if (!p.isCancel(remember) && remember) {
81
+ config.set("aiCLI", selected);
82
+ }
83
+ const cli = available.find(c => c.command === selected);
84
+ await launchCLI(cli, cwd);
85
+ }
86
+ function detectAICLIs() {
87
+ const clis = [];
88
+ try {
89
+ const version = execSync("claude --version 2>/dev/null", { encoding: "utf-8" }).trim();
90
+ clis.push({
91
+ name: "Claude Code",
92
+ command: "claude",
93
+ provider: "claude",
94
+ version: version.split("\n")[0],
95
+ });
96
+ }
97
+ catch { }
98
+ try {
99
+ const version = execSync("codex --version 2>/dev/null", { encoding: "utf-8" }).trim();
100
+ clis.push({
101
+ name: "Codex CLI",
102
+ command: "codex",
103
+ provider: "codex",
104
+ version,
105
+ });
106
+ }
107
+ catch { }
108
+ try {
109
+ const version = execSync("aider --version 2>/dev/null", { encoding: "utf-8" }).trim();
110
+ clis.push({
111
+ name: "Aider",
112
+ command: "aider",
113
+ provider: "aider",
114
+ version,
115
+ });
116
+ }
117
+ catch { }
118
+ try {
119
+ const version = execSync("pi --version 2>/dev/null", { encoding: "utf-8" }).trim();
120
+ clis.push({
121
+ name: "Pi",
122
+ command: "pi",
123
+ provider: "pi",
124
+ version: version.split("\n")[0],
125
+ });
126
+ }
127
+ catch { }
128
+ return clis;
129
+ }
130
+ async function promptAutonomousMode(cli) {
131
+ const savedPref = config.get(`autonomous_${cli.provider}`);
132
+ if (savedPref !== undefined)
133
+ return savedPref;
134
+ const flag = AUTONOMOUS_FLAGS[cli.provider];
135
+ if (!flag)
136
+ return false;
137
+ console.log();
138
+ const autonomous = await p.confirm({
139
+ message: `Run in autonomous mode? (${flag})`,
140
+ initialValue: false,
141
+ });
142
+ if (p.isCancel(autonomous))
143
+ return false;
144
+ if (autonomous) {
145
+ console.log(chalk.yellow("\nāš ļø Autonomous mode: AI can execute without asking permission."));
146
+ }
147
+ const remember = await p.confirm({
148
+ message: "Remember this choice?",
149
+ initialValue: true,
150
+ });
151
+ if (!p.isCancel(remember) && remember) {
152
+ config.set(`autonomous_${cli.provider}`, autonomous);
153
+ }
154
+ return autonomous;
155
+ }
156
+ async function promptChromeMode(cli) {
157
+ if (cli.provider !== "claude")
158
+ return false;
159
+ const savedPref = config.get("chrome_claude");
160
+ if (savedPref !== undefined)
161
+ return savedPref;
162
+ console.log();
163
+ const chrome = await p.confirm({
164
+ message: `Enable browser capabilities? (${CHROME_FLAG})`,
165
+ initialValue: false,
166
+ });
167
+ if (p.isCancel(chrome))
168
+ return false;
169
+ if (chrome) {
170
+ console.log(chalk.cyan("\n🌐 Chrome mode: Claude can browse and interact with web pages."));
171
+ }
172
+ const remember = await p.confirm({
173
+ message: "Remember this choice?",
174
+ initialValue: true,
175
+ });
176
+ if (!p.isCancel(remember) && remember) {
177
+ config.set("chrome_claude", chrome);
178
+ }
179
+ return chrome;
180
+ }
181
+ function showBanner() {
182
+ let version;
183
+ try {
184
+ const pkgPath = new URL("../../package.json", import.meta.url);
185
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
186
+ version = pkg.version;
187
+ }
188
+ catch { }
189
+ console.log(renderBanner({ version }));
45
190
  }
46
191
  async function showGatewayDashboard() {
47
192
  console.log(chalk.bold.cyan("\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”"));
48
193
  console.log(chalk.bold.cyan("│ JFL Gateway Dashboard │"));
49
194
  console.log(chalk.bold.cyan("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜\n"));
50
- // 1. Ensure and check Context Hub
51
195
  const spinner1 = ora("Checking Context Hub...").start();
52
196
  await ensureContextHub();
53
197
  const contextHubConfig = getContextHubConfig();
@@ -59,7 +203,6 @@ async function showGatewayDashboard() {
59
203
  else {
60
204
  spinner1.fail(chalk.red("Context Hub not running"));
61
205
  }
62
- // 2. Check Service Manager
63
206
  const spinner2 = ora("Checking Service Manager...").start();
64
207
  const serviceManagerStatus = await checkServiceManager();
65
208
  if (serviceManagerStatus.running) {
@@ -70,31 +213,12 @@ async function showGatewayDashboard() {
70
213
  spinner2.warn(chalk.yellow("Service Manager not running"));
71
214
  console.log(chalk.gray(" Start with: pm2 start ~/.jfl/service-manager/ecosystem.config.js"));
72
215
  }
73
- // 3. Show connection info
74
216
  console.log();
75
217
  console.log(chalk.bold("Gateway Endpoints:"));
76
218
  console.log(chalk.gray(" Context Hub: ") + chalk.cyan(`http://localhost:${contextHubConfig.port}`));
77
219
  console.log(chalk.gray(" Service Manager: ") + chalk.cyan("http://localhost:3401"));
78
220
  console.log(chalk.gray(" MCP Server: ") + chalk.cyan("Connected via MCP"));
79
- // 4. Show how to connect
80
- console.log();
81
- console.log(chalk.bold("AI Tools Connection:"));
82
- console.log(chalk.gray(" Claude Code connects via MCP automatically"));
83
- console.log(chalk.gray(" Cursor connects via MCP automatically"));
84
- console.log(chalk.gray(" Custom tools can use HTTP API"));
85
- // 5. Show available commands
86
- console.log();
87
- console.log(chalk.bold("Manage Services:"));
88
- console.log(chalk.gray(" jfl-services list ") + "# List all services");
89
- console.log(chalk.gray(" jfl-services start <id> ") + "# Start a service");
90
- console.log(chalk.gray(" jfl-services logs <id> ") + "# View service logs");
91
221
  console.log();
92
- console.log(chalk.bold("View Status:"));
93
- console.log(chalk.gray(" jfl status ") + "# Project status");
94
- console.log(chalk.gray(" jfl hud ") + "# Campaign dashboard");
95
- console.log();
96
- console.log(chalk.green("āœ“ JFL Gateway is ready!"));
97
- console.log(chalk.gray(" Open Claude Code or Cursor to connect via MCP\n"));
98
222
  }
99
223
  async function checkServiceManager() {
100
224
  try {
@@ -102,20 +226,366 @@ async function checkServiceManager() {
102
226
  const services = response.data.stats?.total_services || 0;
103
227
  return { running: true, services };
104
228
  }
105
- catch (err) {
229
+ catch {
106
230
  return { running: false };
107
231
  }
108
232
  }
233
+ async function launchCLI(cli, cwd) {
234
+ const autonomous = await promptAutonomousMode(cli);
235
+ let chrome = false;
236
+ if (cli.provider === "claude") {
237
+ chrome = await promptChromeMode(cli);
238
+ }
239
+ // Ensure Context Hub is running and show dashboard before launch
240
+ await showGatewayDashboard();
241
+ // Build args
242
+ const args = [];
243
+ if (autonomous && AUTONOMOUS_FLAGS[cli.provider]) {
244
+ args.push(AUTONOMOUS_FLAGS[cli.provider]);
245
+ }
246
+ if (chrome) {
247
+ args.push(CHROME_FLAG);
248
+ }
249
+ // Show banner + mode indicators
250
+ showBanner();
251
+ const modes = [];
252
+ if (autonomous)
253
+ modes.push("autonomous");
254
+ if (chrome)
255
+ modes.push("chrome");
256
+ const modeStr = modes.length > 0 ? theme.dim(` (${modes.join(", ")})`) : "";
257
+ console.log(theme.accent(`Launching ${cli.name}`) + modeStr);
258
+ console.log(theme.dim("Context loaded from CLAUDE.md + knowledge/\n"));
259
+ console.log(theme.dimmer("─".repeat(50)));
260
+ console.log();
261
+ const sessionStart = Date.now();
262
+ telemetry.track({
263
+ category: 'session',
264
+ event: 'session:started',
265
+ session_event: 'start',
266
+ ai_cli: cli.provider,
267
+ });
268
+ const child = spawn(cli.command, args, {
269
+ cwd,
270
+ stdio: "inherit",
271
+ env: {
272
+ ...process.env,
273
+ JFL_PROJECT: cwd,
274
+ },
275
+ });
276
+ child.on("error", (err) => {
277
+ console.error(chalk.red(`\nFailed to launch ${cli.name}:`), err.message);
278
+ telemetry.track({
279
+ category: 'session',
280
+ event: 'session:crash',
281
+ session_event: 'crash',
282
+ ai_cli: cli.provider,
283
+ error_type: err.constructor.name,
284
+ });
285
+ });
286
+ child.on("exit", (code) => {
287
+ telemetry.track({
288
+ category: 'session',
289
+ event: 'session:ended',
290
+ session_event: 'end',
291
+ ai_cli: cli.provider,
292
+ session_duration_s: Math.floor((Date.now() - sessionStart) / 1000),
293
+ success: code === 0 || code === null,
294
+ });
295
+ if (code !== 0 && code !== null) {
296
+ console.log(chalk.yellow(`\n${cli.name} exited with code ${code}`));
297
+ }
298
+ console.log(chalk.gray("\nJFL session ended.\n"));
299
+ });
300
+ }
109
301
  async function onboardNewUser(cwd) {
110
- console.log(chalk.yellow("\nāš ļø Not in a JFL project\n"));
111
- const shouldInit = await p.confirm({
112
- message: "Initialize this directory as a JFL project?",
113
- initialValue: true,
302
+ showBanner();
303
+ showHowItWorksNotice();
304
+ const knownProjects = config.get("projects") || [];
305
+ const existingProjects = knownProjects.filter((proj) => existsSync(join(proj, "CLAUDE.md")) || existsSync(join(proj, "knowledge")));
306
+ p.intro(chalk.hex("#FFD700")("ā”Œ JFL onboarding"));
307
+ if (existingProjects.length > 0) {
308
+ const selected = await p.select({
309
+ message: "Open a project or create new?",
310
+ options: [
311
+ ...existingProjects.map((proj) => ({
312
+ label: proj.replace(process.env.HOME || "", "~"),
313
+ value: proj,
314
+ })),
315
+ { label: "Join existing project (I was invited)", value: "__join__" },
316
+ { label: "Create new project", value: "__new__" },
317
+ ],
318
+ });
319
+ if (p.isCancel(selected)) {
320
+ p.cancel("Setup cancelled.");
321
+ process.exit(0);
322
+ }
323
+ if (selected === "__join__") {
324
+ await joinExistingProject();
325
+ return;
326
+ }
327
+ if (selected !== "__new__") {
328
+ config.set("projects", existingProjects);
329
+ process.chdir(selected);
330
+ p.outro(chalk.hex("#FFA500")(`Opening ${selected.replace(process.env.HOME || "", "~")}`));
331
+ const available = detectAICLIs();
332
+ if (available.length > 0) {
333
+ await launchCLI(available[0], selected);
334
+ }
335
+ else {
336
+ await onboardAICLI();
337
+ }
338
+ return;
339
+ }
340
+ }
341
+ const action = await p.select({
342
+ message: "What do you want to do?",
343
+ options: [
344
+ { label: "Join existing project (I was invited)", value: "join" },
345
+ { label: "Create a new project", value: "create" },
346
+ ],
114
347
  });
115
- if (p.isCancel(shouldInit) || !shouldInit) {
116
- console.log(chalk.gray("\nRun 'jfl init' when ready\n"));
348
+ if (p.isCancel(action)) {
349
+ p.cancel("Setup cancelled.");
350
+ process.exit(0);
351
+ }
352
+ if (action === "join") {
353
+ await joinExistingProject();
117
354
  return;
118
355
  }
356
+ p.outro(chalk.hex("#FFA500")("Launching project setup..."));
119
357
  await initCommand();
120
358
  }
359
+ async function joinExistingProject() {
360
+ console.log(chalk.cyan("\nšŸ”— Join Existing Project\n"));
361
+ let username;
362
+ try {
363
+ username = await authenticateWithGitHub();
364
+ }
365
+ catch (error) {
366
+ console.error(chalk.red("\nGitHub authentication failed"));
367
+ console.error(chalk.gray(error instanceof Error ? error.message : String(error)));
368
+ return;
369
+ }
370
+ let projects;
371
+ try {
372
+ projects = await discoverJflProjects();
373
+ }
374
+ catch (error) {
375
+ console.error(chalk.red("\nFailed to discover projects"));
376
+ console.error(chalk.gray(error instanceof Error ? error.message : String(error)));
377
+ return;
378
+ }
379
+ if (projects.length === 0) {
380
+ console.log(chalk.yellow("\nNo JFL projects found that you have access to.\n"));
381
+ console.log(chalk.gray("Ask the project owner to add you as a collaborator."));
382
+ console.log(chalk.gray("The project needs a CLAUDE.md file or .jfl/ directory.\n"));
383
+ return;
384
+ }
385
+ const selectedProject = await p.select({
386
+ message: "Which project do you want to join?",
387
+ options: projects.map((proj) => ({
388
+ label: `${proj.fullName}${proj.hasUserSuggestions ? chalk.green(" (you're invited)") : ""}${proj.description ? chalk.gray(` - ${proj.description}`) : ""}`,
389
+ value: proj,
390
+ })),
391
+ });
392
+ if (p.isCancel(selectedProject)) {
393
+ p.cancel("Cancelled.");
394
+ return;
395
+ }
396
+ const project = selectedProject;
397
+ // Check if already cloned locally
398
+ const trackedProjects = config.get("projects") || [];
399
+ const existingLocal = trackedProjects.find((proj) => {
400
+ const projectName = proj.split("/").pop();
401
+ return projectName === project.name &&
402
+ (existsSync(join(proj, "CLAUDE.md")) || existsSync(join(proj, ".jfl")));
403
+ });
404
+ const commonPaths = [
405
+ join(process.env.HOME || "", "Projects", project.name),
406
+ join(process.env.HOME || "", project.name),
407
+ join(process.cwd(), project.name),
408
+ ];
409
+ const existingAtCommonPath = commonPaths.find((proj) => existsSync(proj) && (existsSync(join(proj, "CLAUDE.md")) || existsSync(join(proj, ".jfl"))));
410
+ const alreadyCloned = existingLocal || existingAtCommonPath;
411
+ if (alreadyCloned) {
412
+ console.log(chalk.green(`\nāœ“ Already have ${project.name} at ${alreadyCloned}\n`));
413
+ const openExisting = await p.confirm({
414
+ message: "Open it?",
415
+ initialValue: true,
416
+ });
417
+ if (!p.isCancel(openExisting) && openExisting) {
418
+ if (!trackedProjects.includes(alreadyCloned)) {
419
+ trackedProjects.push(alreadyCloned);
420
+ config.set("projects", trackedProjects);
421
+ }
422
+ process.chdir(alreadyCloned);
423
+ const available = detectAICLIs();
424
+ if (available.length > 0) {
425
+ await launchCLI(available[0], alreadyCloned);
426
+ }
427
+ else {
428
+ await onboardAICLI();
429
+ }
430
+ }
431
+ return;
432
+ }
433
+ // Show project info
434
+ console.log();
435
+ console.log(chalk.bold(`Project: ${project.fullName}`));
436
+ if (project.description) {
437
+ console.log(chalk.gray(project.description));
438
+ }
439
+ if (project.projectConfig?.wallet) {
440
+ console.log(chalk.gray(`Wallet: ${project.projectConfig.wallet.slice(0, 10)}...`));
441
+ }
442
+ if (project.hasUserSuggestions) {
443
+ console.log(chalk.green(`āœ“ Your suggestions file exists (suggestions/${username}.md)`));
444
+ }
445
+ console.log();
446
+ const defaultPath = join(process.env.HOME || "", "Projects", project.name);
447
+ const clonePath = await p.text({
448
+ message: "Where to clone?",
449
+ defaultValue: defaultPath,
450
+ placeholder: defaultPath,
451
+ });
452
+ if (p.isCancel(clonePath)) {
453
+ p.cancel("Cancelled.");
454
+ return;
455
+ }
456
+ const expandedPath = clonePath.replace(/^~/, process.env.HOME || "");
457
+ let clonedPath;
458
+ try {
459
+ clonedPath = await cloneRepository(project, expandedPath);
460
+ }
461
+ catch (error) {
462
+ console.error(chalk.red("\nClone failed"));
463
+ console.error(chalk.gray(error instanceof Error ? error.message : String(error)));
464
+ return;
465
+ }
466
+ // Track this project
467
+ const allProjects = config.get("projects") || [];
468
+ if (!allProjects.includes(clonedPath)) {
469
+ allProjects.push(clonedPath);
470
+ config.set("projects", allProjects);
471
+ }
472
+ config.set("hasOnboarded", true);
473
+ console.log(chalk.green(`\nāœ“ Joined ${project.name}!\n`));
474
+ if (project.projectConfig?.wallet) {
475
+ console.log(chalk.gray("Project Configuration:"));
476
+ console.log(chalk.gray(` Owner: ${project.projectConfig.walletOwner || project.owner}`));
477
+ console.log(chalk.gray(` Wallet: ${project.projectConfig.wallet} (configured)`));
478
+ console.log(chalk.gray(` Your role: Contributor`));
479
+ console.log();
480
+ }
481
+ console.log(chalk.gray(`Location: ${clonedPath}`));
482
+ console.log(chalk.cyan(`\nTo start working:\n`));
483
+ console.log(chalk.white(` cd ${clonedPath}`));
484
+ console.log(chalk.white(` jfl\n`));
485
+ const launchNow = await p.confirm({
486
+ message: "Open project now?",
487
+ initialValue: true,
488
+ });
489
+ if (!p.isCancel(launchNow) && launchNow) {
490
+ process.chdir(clonedPath);
491
+ const available = detectAICLIs();
492
+ if (available.length > 0) {
493
+ await launchCLI(available[0], clonedPath);
494
+ }
495
+ else {
496
+ await onboardAICLI();
497
+ }
498
+ }
499
+ }
500
+ async function onboardAICLI() {
501
+ console.log(chalk.bold("\nšŸ¤– JFL - Setup AI Assistant\n"));
502
+ console.log(chalk.gray("JFL works with your AI of choice. Let's get you set up.\n"));
503
+ const experience = await p.select({
504
+ message: "How technical are you?",
505
+ options: [
506
+ { label: "I'm a developer - comfortable with terminal", value: "dev" },
507
+ { label: "Somewhat technical - can follow instructions", value: "some" },
508
+ { label: "Not technical - need the easy path", value: "none" },
509
+ ],
510
+ });
511
+ if (p.isCancel(experience))
512
+ return;
513
+ if (experience === "none") {
514
+ console.log(chalk.cyan("\nšŸ“± Easiest options for you:\n"));
515
+ console.log(chalk.bold("1. Claude.ai (browser)"));
516
+ console.log(chalk.gray(" Go to claude.ai, sign up, and chat directly"));
517
+ console.log(chalk.gray(" You can paste your project files into the chat\n"));
518
+ console.log(chalk.bold("2. Claude Desktop App"));
519
+ console.log(chalk.gray(" Download from claude.ai/download"));
520
+ console.log(chalk.gray(" Works like ChatGPT but with Claude\n"));
521
+ console.log(chalk.bold("3. Cursor (AI-powered code editor)"));
522
+ console.log(chalk.gray(" Download from cursor.com"));
523
+ console.log(chalk.gray(" Like VS Code but with AI built in\n"));
524
+ console.log(chalk.yellow("For the full JFL experience, Claude Code is best."));
525
+ console.log(chalk.gray("But start with what's comfortable.\n"));
526
+ return;
527
+ }
528
+ console.log(chalk.yellow("\nNo AI CLI detected.\n"));
529
+ const choice = await p.select({
530
+ message: "Which AI do you want to use?",
531
+ options: [
532
+ { label: "Claude Code (recommended for JFL)", value: "claude" },
533
+ { label: "Codex CLI (OpenAI)", value: "codex" },
534
+ { label: "Aider (works with multiple models)", value: "aider" },
535
+ { label: "I'll set it up myself", value: "manual" },
536
+ ],
537
+ });
538
+ if (p.isCancel(choice))
539
+ return;
540
+ if (choice === "manual") {
541
+ console.log(chalk.gray("\nSupported AI CLIs:"));
542
+ console.log(" • Claude Code: npm install -g @anthropic-ai/claude-code");
543
+ console.log(" • Codex CLI: npm install -g @openai/codex");
544
+ console.log(" • Aider: pip install aider-chat");
545
+ console.log(chalk.gray("\nRun 'jfl' again after installing.\n"));
546
+ return;
547
+ }
548
+ const installCommands = {
549
+ claude: { cmd: "npm install -g @anthropic-ai/claude-code", name: "Claude Code" },
550
+ codex: { cmd: "npm install -g @openai/codex", name: "Codex CLI" },
551
+ aider: { cmd: "pip install aider-chat", name: "Aider" },
552
+ };
553
+ const install = installCommands[choice];
554
+ console.log(chalk.cyan(`\nInstalling ${install.name}...\n`));
555
+ console.log(chalk.gray(`$ ${install.cmd}\n`));
556
+ const proceed = await p.confirm({
557
+ message: "Run this command?",
558
+ initialValue: true,
559
+ });
560
+ if (p.isCancel(proceed) || !proceed) {
561
+ console.log(chalk.gray(`\nRun manually: ${install.cmd}`));
562
+ console.log(chalk.gray("Then run 'jfl' again.\n"));
563
+ return;
564
+ }
565
+ try {
566
+ execSync(install.cmd, { stdio: "inherit" });
567
+ console.log(chalk.green(`\nāœ“ ${install.name} installed!\n`));
568
+ if (choice === "claude") {
569
+ console.log(chalk.gray("Set your Anthropic API key:"));
570
+ console.log(chalk.white(" export ANTHROPIC_API_KEY=your-key-here\n"));
571
+ console.log(chalk.gray("Or run 'claude' to authenticate interactively.\n"));
572
+ }
573
+ else if (choice === "codex") {
574
+ console.log(chalk.gray("Set your OpenAI API key:"));
575
+ console.log(chalk.white(" export OPENAI_API_KEY=your-key-here\n"));
576
+ }
577
+ else if (choice === "aider") {
578
+ console.log(chalk.gray("Set your API key (Anthropic or OpenAI):"));
579
+ console.log(chalk.white(" export ANTHROPIC_API_KEY=your-key-here"));
580
+ console.log(chalk.white(" # or"));
581
+ console.log(chalk.white(" export OPENAI_API_KEY=your-key-here\n"));
582
+ }
583
+ console.log(chalk.cyan("Run 'jfl' to start your AI session.\n"));
584
+ }
585
+ catch {
586
+ console.error(chalk.red("\nInstallation failed."));
587
+ console.log(chalk.gray(`\nTry manually: ${install.cmd}\n`));
588
+ }
589
+ }
590
+ export { showGatewayDashboard };
121
591
  //# sourceMappingURL=session.js.map