ralphflow 0.3.0 → 0.4.0

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/dist/ralphflow.js CHANGED
@@ -1,8 +1,20 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getDb,
4
+ incrementIteration,
5
+ isLoopComplete,
6
+ listFlows,
7
+ loadConfig,
8
+ markLoopComplete,
9
+ markLoopRunning,
10
+ resolveFlowDir,
11
+ resolveLoop,
12
+ showStatus
13
+ } from "./chunk-GVOJO5IN.js";
2
14
 
3
15
  // src/cli/index.ts
4
- import { Command as Command4 } from "commander";
5
- import chalk7 from "chalk";
16
+ import { Command as Command6 } from "commander";
17
+ import chalk8 from "chalk";
6
18
 
7
19
  // src/cli/init.ts
8
20
  import { Command } from "commander";
@@ -72,7 +84,7 @@ async function initProject(cwd, options = {}) {
72
84
  return;
73
85
  }
74
86
  if (existsSync2(ralphFlowDir)) {
75
- const existing = listFlows(ralphFlowDir);
87
+ const existing = listFlows2(ralphFlowDir);
76
88
  if (existing.length > 0 && !options.template) {
77
89
  console.log();
78
90
  console.log(chalk.bold(" Existing flows:"));
@@ -115,7 +127,7 @@ async function initProject(cwd, options = {}) {
115
127
  console.log(chalk.dim(` Next: npx ralphflow run story --flow ${flowName}`));
116
128
  console.log();
117
129
  }
118
- function listFlows(ralphFlowDir) {
130
+ function listFlows2(ralphFlowDir) {
119
131
  try {
120
132
  return readdirSync(ralphFlowDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name);
121
133
  } catch {
@@ -141,91 +153,10 @@ import { Command as Command2 } from "commander";
141
153
  import chalk4 from "chalk";
142
154
 
143
155
  // src/core/runner.ts
144
- import { readFileSync as readFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync, readdirSync as readdirSync3, unlinkSync } from "fs";
145
- import { join as join4 } from "path";
156
+ import { readFileSync as readFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync, readdirSync as readdirSync2, unlinkSync } from "fs";
157
+ import { join as join3, basename } from "path";
146
158
  import chalk3 from "chalk";
147
159
 
148
- // src/core/config.ts
149
- import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
150
- import { join as join3 } from "path";
151
- import { parse as parseYaml } from "yaml";
152
- var LOOP_ALIASES = {
153
- // code-implementation aliases
154
- story: "story-loop",
155
- stories: "story-loop",
156
- tasks: "tasks-loop",
157
- task: "tasks-loop",
158
- delivery: "delivery-loop",
159
- deliver: "delivery-loop",
160
- // research aliases
161
- discovery: "discovery-loop",
162
- discover: "discovery-loop",
163
- research: "research-loop",
164
- document: "document-loop",
165
- doc: "document-loop"
166
- };
167
- function listFlows2(cwd) {
168
- const baseDir = join3(cwd, ".ralph-flow");
169
- if (!existsSync3(baseDir)) return [];
170
- return readdirSync2(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).filter((d) => existsSync3(join3(baseDir, d.name, "ralphflow.yaml"))).map((d) => d.name);
171
- }
172
- function resolveFlowDir(cwd, flowName) {
173
- const baseDir = join3(cwd, ".ralph-flow");
174
- if (!existsSync3(baseDir)) {
175
- throw new Error("No .ralph-flow/ found. Run `npx ralphflow init` first.");
176
- }
177
- const flows = listFlows2(cwd);
178
- if (flows.length === 0) {
179
- throw new Error("No flows found in .ralph-flow/. Run `npx ralphflow init` first.");
180
- }
181
- if (flowName) {
182
- if (!flows.includes(flowName)) {
183
- throw new Error(`Flow "${flowName}" not found. Available: ${flows.join(", ")}`);
184
- }
185
- return join3(baseDir, flowName);
186
- }
187
- if (flows.length === 1) {
188
- return join3(baseDir, flows[0]);
189
- }
190
- throw new Error(
191
- `Multiple flows found: ${flows.join(", ")}. Use --flow <name> to specify which one.`
192
- );
193
- }
194
- function loadConfig(flowDir) {
195
- const configPath = join3(flowDir, "ralphflow.yaml");
196
- if (!existsSync3(configPath)) {
197
- throw new Error(`No ralphflow.yaml found in ${flowDir}`);
198
- }
199
- const raw = readFileSync2(configPath, "utf-8");
200
- const config = parseYaml(raw);
201
- if (!config.name) {
202
- throw new Error('ralphflow.yaml: missing required field "name"');
203
- }
204
- if (!config.loops || Object.keys(config.loops).length === 0) {
205
- throw new Error('ralphflow.yaml: missing required field "loops"');
206
- }
207
- if (!config.dir) {
208
- config.dir = ".ralph-flow";
209
- }
210
- return config;
211
- }
212
- function resolveLoop(config, name) {
213
- if (config.loops[name]) {
214
- return { key: name, loop: config.loops[name] };
215
- }
216
- const aliased = LOOP_ALIASES[name.toLowerCase()];
217
- if (aliased && config.loops[aliased]) {
218
- return { key: aliased, loop: config.loops[aliased] };
219
- }
220
- for (const [key, loop] of Object.entries(config.loops)) {
221
- if (key.startsWith(name) || loop.name.toLowerCase().includes(name.toLowerCase())) {
222
- return { key, loop };
223
- }
224
- }
225
- const available = Object.keys(config.loops).join(", ");
226
- throw new Error(`Unknown loop "${name}". Available: ${available}`);
227
- }
228
-
229
160
  // src/core/claude.ts
230
161
  import { spawn } from "child_process";
231
162
  async function spawnClaude(options) {
@@ -255,8 +186,8 @@ async function spawnClaude(options) {
255
186
 
256
187
  // src/core/runner.ts
257
188
  function agentsDir(flowDir, loop) {
258
- const loopDir = join4(flowDir, loop.tracker, "..");
259
- return join4(loopDir, ".agents");
189
+ const loopDir = join3(flowDir, loop.tracker, "..");
190
+ return join3(loopDir, ".agents");
260
191
  }
261
192
  function isProcessAlive(pid) {
262
193
  try {
@@ -267,13 +198,13 @@ function isProcessAlive(pid) {
267
198
  }
268
199
  }
269
200
  function cleanStaleAgents(dir) {
270
- if (!existsSync4(dir)) return;
271
- for (const file of readdirSync3(dir)) {
201
+ if (!existsSync3(dir)) return;
202
+ for (const file of readdirSync2(dir)) {
272
203
  if (!file.endsWith(".lock")) continue;
273
- const pidStr = readFileSync3(join4(dir, file), "utf-8").trim();
204
+ const pidStr = readFileSync2(join3(dir, file), "utf-8").trim();
274
205
  const pid = parseInt(pidStr, 10);
275
206
  if (isNaN(pid) || !isProcessAlive(pid)) {
276
- unlinkSync(join4(dir, file));
207
+ unlinkSync(join3(dir, file));
277
208
  }
278
209
  }
279
210
  }
@@ -281,8 +212,8 @@ function acquireAgentId(dir, maxAgents) {
281
212
  mkdirSync2(dir, { recursive: true });
282
213
  cleanStaleAgents(dir);
283
214
  for (let n = 1; n <= maxAgents; n++) {
284
- const lockFile = join4(dir, `agent-${n}.lock`);
285
- if (!existsSync4(lockFile)) {
215
+ const lockFile = join3(dir, `agent-${n}.lock`);
216
+ if (!existsSync3(lockFile)) {
286
217
  writeFileSync(lockFile, String(process.pid));
287
218
  return `agent-${n}`;
288
219
  }
@@ -290,17 +221,39 @@ function acquireAgentId(dir, maxAgents) {
290
221
  throw new Error(`All ${maxAgents} agent slots are occupied. Wait for one to finish or increase max_agents.`);
291
222
  }
292
223
  function releaseAgentId(dir, agentName) {
293
- const lockFile = join4(dir, `${agentName}.lock`);
224
+ const lockFile = join3(dir, `${agentName}.lock`);
294
225
  try {
295
226
  unlinkSync(lockFile);
296
227
  } catch {
297
228
  }
298
229
  }
299
230
  function checkTrackerForCompletion(flowDir, loop) {
300
- const trackerPath = join4(flowDir, loop.tracker);
301
- if (!existsSync4(trackerPath)) return false;
302
- const content = readFileSync3(trackerPath, "utf-8");
303
- return content.includes(`<promise>${loop.completion}</promise>`);
231
+ const trackerPath = join3(flowDir, loop.tracker);
232
+ if (!existsSync3(trackerPath)) return false;
233
+ const content = readFileSync2(trackerPath, "utf-8");
234
+ return content.includes(`<promise>${loop.completion}</promise>`) || content.includes(loop.completion);
235
+ }
236
+ function checkTrackerMetadataCompletion(flowDir, loop) {
237
+ const trackerPath = join3(flowDir, loop.tracker);
238
+ if (!existsSync3(trackerPath)) return false;
239
+ const content = readFileSync2(trackerPath, "utf-8");
240
+ const completedMatch = content.match(/^- completed_(?:tasks|stories): \[(.+)\]$/m);
241
+ if (!completedMatch) return false;
242
+ const completedItems = completedMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
243
+ if (completedItems.length === 0) return false;
244
+ const inProgressLines = content.match(/\{[^}]*status:\s*in_progress[^}]*\}/g);
245
+ if (inProgressLines && inProgressLines.length > 0) return false;
246
+ const pendingLines = content.match(/\{[^}]*status:\s*pending[^}]*\}/g);
247
+ if (pendingLines && pendingLines.length > 0) return false;
248
+ return true;
249
+ }
250
+ function checkTrackerCheckboxes(flowDir, loop) {
251
+ const trackerPath = join3(flowDir, loop.tracker);
252
+ if (!existsSync3(trackerPath)) return false;
253
+ const content = readFileSync2(trackerPath, "utf-8");
254
+ const checked = (content.match(/- \[x\]/gi) || []).length;
255
+ const unchecked = (content.match(/- \[ \]/g) || []).length;
256
+ return checked > 0 && unchecked === 0;
304
257
  }
305
258
  async function runLoop(loopName, options) {
306
259
  const flowDir = resolveFlowDir(options.cwd, options.flow);
@@ -341,8 +294,18 @@ async function runLoop(loopName, options) {
341
294
  cleanup();
342
295
  }
343
296
  }
344
- async function iterationLoop(loop, flowDir, options, agentName) {
297
+ async function iterationLoop(loop, flowDir, options, agentName, db, flowName) {
298
+ const loopKey = loop.name;
345
299
  for (let i = 1; i <= options.maxIterations; i++) {
300
+ if (db && flowName && isLoopComplete(db, flowName, loopKey)) {
301
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 already complete`));
302
+ return;
303
+ }
304
+ if (checkTrackerForCompletion(flowDir, loop) || checkTrackerCheckboxes(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop)) {
305
+ if (db && flowName) markLoopComplete(db, flowName, loopKey);
306
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete`));
307
+ return;
308
+ }
346
309
  const label = agentName ? chalk3.dim(` [${agentName}] Iteration ${i}/${options.maxIterations}`) : chalk3.dim(` Iteration ${i}/${options.maxIterations}`);
347
310
  console.log(label);
348
311
  const prompt = readPrompt(loop, flowDir, agentName);
@@ -351,7 +314,9 @@ async function iterationLoop(loop, flowDir, options, agentName) {
351
314
  model: options.model,
352
315
  cwd: options.cwd
353
316
  });
354
- if (checkTrackerForCompletion(flowDir, loop)) {
317
+ if (db && flowName) incrementIteration(db, flowName, loopKey);
318
+ if (checkTrackerForCompletion(flowDir, loop) || checkTrackerCheckboxes(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop)) {
319
+ if (db && flowName) markLoopComplete(db, flowName, loopKey);
355
320
  console.log();
356
321
  console.log(chalk3.green(` Loop complete: ${loop.completion}`));
357
322
  return;
@@ -371,9 +336,56 @@ async function iterationLoop(loop, flowDir, options, agentName) {
371
336
  }
372
337
  console.log(chalk3.yellow(` Max iterations (${options.maxIterations}) reached.`));
373
338
  }
339
+ async function runAllLoops(options) {
340
+ const flowDir = resolveFlowDir(options.cwd, options.flow);
341
+ const config = loadConfig(flowDir);
342
+ const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
343
+ console.log();
344
+ console.log(chalk3.bold(" RalphFlow \u2014 Running all loops"));
345
+ console.log();
346
+ for (const [key, loop] of sortedLoops) {
347
+ console.log(chalk3.bold(` Starting: ${loop.name}`));
348
+ await iterationLoop(loop, flowDir, options);
349
+ }
350
+ console.log(chalk3.green(" All loops complete."));
351
+ }
352
+ async function runE2E(options) {
353
+ const flowDir = resolveFlowDir(options.cwd, options.flow);
354
+ const config = loadConfig(flowDir);
355
+ const flowName = options.flow || basename(flowDir);
356
+ const db = getDb(options.cwd);
357
+ const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
358
+ console.log();
359
+ console.log(chalk3.bold(" RalphFlow \u2014 E2E"));
360
+ console.log();
361
+ for (const [key, loop] of sortedLoops) {
362
+ const loopKey = loop.name;
363
+ if (isLoopComplete(db, flowName, loopKey)) {
364
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete, skipping`));
365
+ continue;
366
+ }
367
+ if (checkTrackerForCompletion(flowDir, loop) || checkTrackerCheckboxes(flowDir, loop) || checkTrackerMetadataCompletion(flowDir, loop)) {
368
+ markLoopComplete(db, flowName, loopKey);
369
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 complete, skipping`));
370
+ continue;
371
+ }
372
+ markLoopRunning(db, flowName, loopKey);
373
+ console.log(chalk3.bold(` \u2192 ${loop.name}`));
374
+ await iterationLoop(loop, flowDir, options, void 0, db, flowName);
375
+ if (isLoopComplete(db, flowName, loopKey)) {
376
+ console.log(chalk3.green(` \u2713 ${loop.name} \u2014 done`));
377
+ } else {
378
+ console.log(chalk3.yellow(` \u26A0 ${loop.name} \u2014 max iterations, advancing`));
379
+ }
380
+ console.log();
381
+ }
382
+ console.log(chalk3.green(" \u2713 E2E complete"));
383
+ }
374
384
  function readPrompt(loop, flowDir, agentName) {
375
- const promptPath = join4(flowDir, loop.prompt);
376
- let prompt = readFileSync3(promptPath, "utf-8");
385
+ const promptPath = join3(flowDir, loop.prompt);
386
+ let prompt = readFileSync2(promptPath, "utf-8");
387
+ const appName = basename(flowDir);
388
+ prompt = prompt.replaceAll("{{APP_NAME}}", appName);
377
389
  if (agentName && loop.multi_agent !== false) {
378
390
  const ma = loop.multi_agent;
379
391
  if (ma.agent_placeholder) {
@@ -384,8 +396,13 @@ function readPrompt(loop, flowDir, agentName) {
384
396
  }
385
397
 
386
398
  // src/cli/run.ts
387
- var runCommand = new Command2("run").description("Run a loop").argument("<loop>", "Loop to run (story, tasks, delivery, discovery, research, document)").option("--multi-agent", "Run as a multi-agent instance (auto-assigns agent ID)").option("-m, --model <model>", "Claude model to use").option("-n, --max-iterations <n>", "Maximum iterations", "30").option("-f, --flow <name>", "Which flow to run (auto-detected if only one)").action(async (loop, opts) => {
399
+ var runCommand = new Command2("run").description("Run a loop").argument("<loop>", "Loop to run (story, tasks, delivery, discovery, research, document)").option("--multi-agent", "Run as a multi-agent instance (auto-assigns agent ID)").option("-m, --model <model>", "Claude model to use").option("-n, --max-iterations <n>", "Maximum iterations", "30").option("-f, --flow <name>", "Which flow to run (auto-detected if only one)").option("--ui", "Start web dashboard alongside execution").action(async (loop, opts) => {
388
400
  try {
401
+ let dashboardHandle;
402
+ if (opts.ui) {
403
+ const { startDashboard } = await import("./server-O6J52DZT.js");
404
+ dashboardHandle = await startDashboard({ cwd: process.cwd() });
405
+ }
389
406
  await runLoop(loop, {
390
407
  multiAgent: !!opts.multiAgent,
391
408
  model: opts.model,
@@ -402,137 +419,36 @@ var runCommand = new Command2("run").description("Run a loop").argument("<loop>"
402
419
  }
403
420
  });
404
421
 
405
- // src/cli/status.ts
422
+ // src/cli/e2e.ts
406
423
  import { Command as Command3 } from "commander";
407
- import chalk6 from "chalk";
408
-
409
- // src/core/status.ts
410
- import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
411
- import { join as join5 } from "path";
412
424
  import chalk5 from "chalk";
413
- import Table from "cli-table3";
414
- async function showStatus(cwd, flowName) {
415
- const flows = flowName ? [flowName] : listFlows2(cwd);
416
- if (flows.length === 0) {
417
- console.log();
418
- console.log(chalk5.yellow(" No flows found. Run `npx ralphflow init` first."));
419
- console.log();
420
- return;
421
- }
422
- for (const flow of flows) {
423
- const flowDir = resolveFlowDir(cwd, flow);
424
- const config = loadConfig(flowDir);
425
- console.log();
426
- console.log(chalk5.bold(` RalphFlow \u2014 ${flow}`));
427
- console.log();
428
- const table = new Table({
429
- chars: {
430
- top: "",
431
- "top-mid": "",
432
- "top-left": "",
433
- "top-right": "",
434
- bottom: "",
435
- "bottom-mid": "",
436
- "bottom-left": "",
437
- "bottom-right": "",
438
- left: " ",
439
- "left-mid": "",
440
- mid: "",
441
- "mid-mid": "",
442
- right: "",
443
- "right-mid": "",
444
- middle: " "
445
- },
446
- style: { "padding-left": 0, "padding-right": 1 },
447
- head: [
448
- chalk5.dim("Loop"),
449
- chalk5.dim("Stage"),
450
- chalk5.dim("Active"),
451
- chalk5.dim("Progress")
452
- ]
453
- });
454
- const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
455
- for (const [key, loop] of sortedLoops) {
456
- const status = parseTracker(loop.tracker, flowDir, loop.name);
457
- table.push([
458
- loop.name,
459
- status.stage,
460
- status.active,
461
- `${status.completed}/${status.total}`
462
- ]);
463
- if (status.agents && status.agents.length > 0) {
464
- for (const agent of status.agents) {
465
- table.push([
466
- chalk5.dim(` ${agent.name}`),
467
- chalk5.dim(agent.stage),
468
- chalk5.dim(agent.activeTask),
469
- chalk5.dim(agent.lastHeartbeat)
470
- ]);
471
- }
472
- }
473
- }
474
- console.log(table.toString());
475
- }
476
- console.log();
477
- }
478
- function parseTracker(trackerPath, flowDir, loopName) {
479
- const fullPath = join5(flowDir, trackerPath);
480
- const status = {
481
- loop: loopName,
482
- stage: "\u2014",
483
- active: "none",
484
- completed: 0,
485
- total: 0
486
- };
487
- if (!existsSync5(fullPath)) {
488
- return status;
489
- }
490
- const content = readFileSync4(fullPath, "utf-8");
491
- const lines = content.split("\n");
492
- for (const line of lines) {
493
- const metaMatch = line.match(/^- (\w[\w_]*): (.+)$/);
494
- if (metaMatch) {
495
- const [, key, value] = metaMatch;
496
- if (key === "stage") status.stage = value.trim();
497
- if (key === "active_story" || key === "active_task") status.active = value.trim();
498
- if (key === "completed_stories" || key === "completed_tasks") {
499
- const arrayMatch = value.match(/\[(.+)\]/);
500
- if (arrayMatch) {
501
- status.completed = arrayMatch[1].split(",").filter((s) => s.trim()).length;
502
- }
503
- }
504
- }
505
- }
506
- const unchecked = (content.match(/- \[ \]/g) || []).length;
507
- const checked = (content.match(/- \[x\]/gi) || []).length;
508
- if (unchecked + checked > 0) {
509
- status.total = unchecked + checked;
510
- status.completed = checked;
511
- }
512
- const agentTableMatch = content.match(/\| agent \|.*\n\|[-|]+\n((?:\|.*\n)*)/);
513
- if (agentTableMatch) {
514
- const agentRows = agentTableMatch[1].trim().split("\n");
515
- status.agents = [];
516
- for (const row of agentRows) {
517
- const cells = row.split("|").map((s) => s.trim()).filter(Boolean);
518
- if (cells.length >= 4) {
519
- status.agents.push({
520
- name: cells[0],
521
- activeTask: cells[1],
522
- stage: cells[2],
523
- lastHeartbeat: cells[3]
524
- });
525
- }
526
- }
527
- if (status.agents.length === 0) {
528
- status.agents = void 0;
425
+ var e2eCommand = new Command3("e2e").description("Run all loops end-to-end with SQLite orchestration (skips completed loops)").option("-m, --model <model>", "Claude model to use").option("-n, --max-iterations <n>", "Maximum iterations per loop", "30").option("-f, --flow <name>", "Which flow to run (auto-detected if only one)").option("--ui", "Start web dashboard alongside execution").action(async (opts) => {
426
+ try {
427
+ let dashboardHandle;
428
+ if (opts.ui) {
429
+ const { startDashboard } = await import("./server-O6J52DZT.js");
430
+ dashboardHandle = await startDashboard({ cwd: process.cwd() });
529
431
  }
432
+ await runE2E({
433
+ multiAgent: false,
434
+ model: opts.model,
435
+ maxIterations: parseInt(opts.maxIterations, 10),
436
+ flow: opts.flow,
437
+ cwd: process.cwd()
438
+ });
439
+ } catch (err) {
440
+ const msg = err instanceof Error ? err.message : String(err);
441
+ console.error(chalk5.red(`
442
+ ${msg}
443
+ `));
444
+ process.exit(1);
530
445
  }
531
- return status;
532
- }
446
+ });
533
447
 
534
448
  // src/cli/status.ts
535
- var statusCommand = new Command3("status").description("Show pipeline status").option("-f, --flow <name>", "Show status for a specific flow").action(async (opts) => {
449
+ import { Command as Command4 } from "commander";
450
+ import chalk6 from "chalk";
451
+ var statusCommand = new Command4("status").description("Show pipeline status").option("-f, --flow <name>", "Show status for a specific flow").action(async (opts) => {
536
452
  try {
537
453
  await showStatus(process.cwd(), opts.flow);
538
454
  } catch (err) {
@@ -544,18 +460,170 @@ var statusCommand = new Command3("status").description("Show pipeline status").o
544
460
  }
545
461
  });
546
462
 
463
+ // src/cli/dashboard.ts
464
+ import { Command as Command5 } from "commander";
465
+ var dashboardCommand = new Command5("dashboard").alias("ui").description("Start the web dashboard").option("-p, --port <port>", "Port number", "4242").action(async (opts) => {
466
+ const { startDashboard } = await import("./server-O6J52DZT.js");
467
+ await startDashboard({ cwd: process.cwd(), port: parseInt(opts.port, 10) });
468
+ });
469
+
470
+ // src/cli/menu.ts
471
+ import { select, input, confirm } from "@inquirer/prompts";
472
+ import chalk7 from "chalk";
473
+ async function interactiveMenu(cwd) {
474
+ console.log();
475
+ console.log(chalk7.bold(" RalphFlow"));
476
+ console.log();
477
+ try {
478
+ const flows = listFlows(cwd);
479
+ if (flows.length === 0) {
480
+ const action = await select({
481
+ message: "What would you like to do?",
482
+ choices: [
483
+ { name: "Initialize a new app", value: "init" }
484
+ ]
485
+ });
486
+ if (action === "init") {
487
+ await handleInit(cwd);
488
+ }
489
+ } else {
490
+ const action = await select({
491
+ message: "What would you like to do?",
492
+ choices: [
493
+ { name: "Run end-to-end", value: "e2e" },
494
+ { name: "Run a loop", value: "run" },
495
+ { name: "Run all loops in sequence", value: "run-all" },
496
+ { name: "Initialize a new app", value: "init" },
497
+ { name: "Check status", value: "status" },
498
+ { name: "Open dashboard", value: "dashboard" }
499
+ ]
500
+ });
501
+ switch (action) {
502
+ case "e2e":
503
+ await handleE2E(cwd, flows);
504
+ break;
505
+ case "run":
506
+ await handleRunLoop(cwd, flows);
507
+ break;
508
+ case "run-all":
509
+ await handleRunAll(cwd, flows);
510
+ break;
511
+ case "init":
512
+ await handleInit(cwd);
513
+ break;
514
+ case "status":
515
+ await handleStatus(cwd);
516
+ break;
517
+ case "dashboard":
518
+ await handleDashboard(cwd);
519
+ break;
520
+ }
521
+ }
522
+ } catch (err) {
523
+ if (err && typeof err === "object" && "name" in err && err.name === "ExitPromptError") {
524
+ console.log();
525
+ console.log(chalk7.dim(" Cancelled."));
526
+ process.exit(0);
527
+ }
528
+ throw err;
529
+ }
530
+ }
531
+ var TEMPLATES2 = ["code-implementation", "research"];
532
+ async function handleInit(cwd) {
533
+ const template = await select({
534
+ message: "Which template?",
535
+ choices: TEMPLATES2.map((t) => ({ name: t, value: t }))
536
+ });
537
+ const name = await input({
538
+ message: "Flow name?",
539
+ default: template
540
+ });
541
+ await initProject(cwd, { template, name });
542
+ const shouldRun = await confirm({
543
+ message: "Run the first loop now?",
544
+ default: true
545
+ });
546
+ if (shouldRun) {
547
+ const flowDir = resolveFlowDir(cwd, name);
548
+ const config = loadConfig(flowDir);
549
+ const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
550
+ if (sortedLoops.length > 0) {
551
+ const [firstKey] = sortedLoops[0];
552
+ await runLoop(firstKey, {
553
+ multiAgent: false,
554
+ maxIterations: 30,
555
+ cwd,
556
+ flow: name
557
+ });
558
+ }
559
+ }
560
+ }
561
+ async function selectFlow(flows) {
562
+ if (flows.length === 1) return flows[0];
563
+ return await select({
564
+ message: "Which flow?",
565
+ choices: flows.map((f) => ({ name: f, value: f }))
566
+ });
567
+ }
568
+ async function handleRunLoop(cwd, flows) {
569
+ const flow = await selectFlow(flows);
570
+ const flowDir = resolveFlowDir(cwd, flow);
571
+ const config = loadConfig(flowDir);
572
+ const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
573
+ const loopKey = await select({
574
+ message: "Which loop?",
575
+ choices: sortedLoops.map(([key, loop]) => ({
576
+ name: loop.name,
577
+ value: key
578
+ }))
579
+ });
580
+ await runLoop(loopKey, {
581
+ multiAgent: false,
582
+ maxIterations: 30,
583
+ cwd,
584
+ flow
585
+ });
586
+ }
587
+ async function handleRunAll(cwd, flows) {
588
+ const flow = await selectFlow(flows);
589
+ await runAllLoops({
590
+ multiAgent: false,
591
+ maxIterations: 30,
592
+ cwd,
593
+ flow
594
+ });
595
+ }
596
+ async function handleE2E(cwd, flows) {
597
+ const flow = await selectFlow(flows);
598
+ await runE2E({
599
+ multiAgent: false,
600
+ maxIterations: 30,
601
+ cwd,
602
+ flow
603
+ });
604
+ }
605
+ async function handleStatus(cwd) {
606
+ await showStatus(cwd);
607
+ }
608
+ async function handleDashboard(cwd) {
609
+ const { startDashboard } = await import("./server-O6J52DZT.js");
610
+ await startDashboard({ cwd });
611
+ }
612
+
547
613
  // src/cli/index.ts
548
- var program = new Command4().name("ralphflow").description("Multi-agent AI workflow orchestration for Claude Code").version("0.1.0").addCommand(initCommand).addCommand(runCommand).addCommand(statusCommand);
614
+ var program = new Command6().name("ralphflow").description("Multi-agent AI workflow orchestration for Claude Code").version("0.1.0").addCommand(initCommand).addCommand(runCommand).addCommand(e2eCommand).addCommand(statusCommand).addCommand(dashboardCommand).action(async () => {
615
+ await interactiveMenu(process.cwd());
616
+ });
549
617
  process.on("SIGINT", () => {
550
618
  console.log();
551
- console.log(chalk7.dim(" Interrupted."));
619
+ console.log(chalk8.dim(" Interrupted."));
552
620
  process.exit(130);
553
621
  });
554
622
  program.configureOutput({
555
623
  writeErr: (str) => {
556
624
  const clean = str.replace(/^error: /, "");
557
625
  if (clean.trim()) {
558
- console.error(chalk7.red(` ${clean.trim()}`));
626
+ console.error(chalk8.red(` ${clean.trim()}`));
559
627
  }
560
628
  }
561
629
  });