writethevision 7.0.7 → 7.0.8
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 +27 -2
- package/package.json +1 -1
- package/src/cli.js +125 -60
- package/src/engine.js +109 -0
package/README.md
CHANGED
|
@@ -296,9 +296,34 @@ The Masterbuilder reads your vision document and coordinates the artisans.
|
|
|
296
296
|
|
|
297
297
|
## Vision Runner (Ralphy-Style)
|
|
298
298
|
|
|
299
|
-
`wtv run`
|
|
299
|
+
`wtv run` (with no arguments) launches the **Vision Runner**, an autonomous loop that executes a project vision until completion.
|
|
300
300
|
|
|
301
|
-
It
|
|
301
|
+
It is designed for "Ralphy-style" execution: give it a vision, and it runs until the vision is a reality.
|
|
302
|
+
|
|
303
|
+
### The Workflow
|
|
304
|
+
|
|
305
|
+
1. **Pick a Vision**: WTV finds `VISION.md` (root) or files in `vision/*.md` and asks you to select one.
|
|
306
|
+
2. **Generate Plan**: It prompts the engine (OpenCode, Codex, or Claude) to generate a detailed `PRD.md` with a task checklist.
|
|
307
|
+
3. **Execute Loop**:
|
|
308
|
+
- Reads `PRD.md` to find the next unchecked task.
|
|
309
|
+
- Creates a focused prompt for the engine.
|
|
310
|
+
- Runs the engine to implement the task.
|
|
311
|
+
- Verifies the task was marked completed in `PRD.md`.
|
|
312
|
+
- Appends a checkpoint to `progress.txt`.
|
|
313
|
+
- Repeats until all tasks are done.
|
|
314
|
+
4. **Finalize**: Creates a single git commit ("feat: complete <vision>") and pushes to remote (unless disabled).
|
|
315
|
+
|
|
316
|
+
### File Contracts
|
|
317
|
+
|
|
318
|
+
The Vision Runner relies on strict file conventions:
|
|
319
|
+
|
|
320
|
+
- **Input**:
|
|
321
|
+
- `VISION.md` (or `vision/*.md`): The source of truth for *what* to build.
|
|
322
|
+
- **Artifacts**:
|
|
323
|
+
- `PRD.md`: The execution checklist. Must use `- [ ]` for incomplete and `- [x]` for complete tasks. The runner stops when no `- [ ]` remain.
|
|
324
|
+
- `progress.txt`: An append-only log of every completed task with timestamps.
|
|
325
|
+
|
|
326
|
+
### Usage
|
|
302
327
|
|
|
303
328
|
```bash
|
|
304
329
|
wtv run
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "writethevision",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.8",
|
|
4
4
|
"description": "Write The Vision (WTV): vision-driven development with the Habakkuk workflow. 10 agents + 21 skills for Claude Code, Codex CLI, and OpenCode.",
|
|
5
5
|
"author": "Christopher Hogg",
|
|
6
6
|
"license": "MIT",
|
package/src/cli.js
CHANGED
|
@@ -1256,15 +1256,23 @@ async function checkForUpdates() {
|
|
|
1256
1256
|
|
|
1257
1257
|
try {
|
|
1258
1258
|
const packageName = getPackageName();
|
|
1259
|
+
const checkEveryRun = process.env.WTV_UPDATE_CHECK_EVERY_RUN === '1';
|
|
1259
1260
|
const notifier = updateNotifier({
|
|
1260
1261
|
pkg: {
|
|
1261
1262
|
name: packageName,
|
|
1262
1263
|
version: getVersion(),
|
|
1263
1264
|
},
|
|
1264
|
-
updateCheckInterval: 1000 * 60 * 60 * 24 * 7,
|
|
1265
|
+
updateCheckInterval: checkEveryRun ? Number.POSITIVE_INFINITY : 1000 * 60 * 60 * 24 * 7,
|
|
1265
1266
|
shouldNotifyInNpmScript: false,
|
|
1266
1267
|
});
|
|
1267
1268
|
|
|
1269
|
+
if (checkEveryRun) {
|
|
1270
|
+
try {
|
|
1271
|
+
notifier.update = await notifier.fetchInfo();
|
|
1272
|
+
} catch {
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1268
1276
|
const update = notifier.update;
|
|
1269
1277
|
if (!update) return;
|
|
1270
1278
|
|
|
@@ -1290,7 +1298,7 @@ Or run latest once:
|
|
|
1290
1298
|
},
|
|
1291
1299
|
});
|
|
1292
1300
|
|
|
1293
|
-
const shouldPrompt = process.stdin.isTTY && process.env.WTV_NO_AUTO_UPDATE !== '1' && !process.env.CI;
|
|
1301
|
+
const shouldPrompt = process.stdin.isTTY && process.env.WTV_NO_AUTO_UPDATE !== '1' && !process.env.CI && !checkEveryRun;
|
|
1294
1302
|
if (!shouldPrompt) return;
|
|
1295
1303
|
|
|
1296
1304
|
const isDevCheckout = existsSync(join(PACKAGE_ROOT, '.git'));
|
|
@@ -3281,13 +3289,12 @@ async function runEngine(engine, promptText) {
|
|
|
3281
3289
|
throw new Error(`Unsupported engine: ${engine}`);
|
|
3282
3290
|
}
|
|
3283
3291
|
|
|
3284
|
-
function ensureProgressHeader(progressPath, { visionPath, engine, startSha }) {
|
|
3292
|
+
export function ensureProgressHeader(progressPath, { visionPath, engine, startSha }) {
|
|
3285
3293
|
if (!existsSync(progressPath)) {
|
|
3286
3294
|
writeFileSync(progressPath, '');
|
|
3287
3295
|
}
|
|
3288
3296
|
|
|
3289
3297
|
const existing = readFileSync(progressPath, 'utf8');
|
|
3290
|
-
if (existing.trim().length > 0) return;
|
|
3291
3298
|
|
|
3292
3299
|
const startedAt = new Date().toISOString();
|
|
3293
3300
|
const header = [
|
|
@@ -3296,16 +3303,19 @@ function ensureProgressHeader(progressPath, { visionPath, engine, startSha }) {
|
|
|
3296
3303
|
`Vision: ${visionPath}`,
|
|
3297
3304
|
`Engine: ${engine}`,
|
|
3298
3305
|
startSha ? `Start SHA: ${startSha}` : `Start SHA: (not a git repo)`,
|
|
3306
|
+
startSha ? `Rollback hint: git reset --hard ${startSha}` : '',
|
|
3299
3307
|
'',
|
|
3300
3308
|
].join('\n');
|
|
3301
3309
|
|
|
3302
|
-
|
|
3310
|
+
if (existing.trim().length > 0) {
|
|
3311
|
+
writeFileSync(progressPath, existing.trimEnd() + '\n\n' + header);
|
|
3312
|
+
} else {
|
|
3313
|
+
writeFileSync(progressPath, header);
|
|
3314
|
+
}
|
|
3303
3315
|
}
|
|
3304
3316
|
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
const promptText = `You are generating a PRD for a software project.
|
|
3317
|
+
export function generatePrdPrompt(visionContent) {
|
|
3318
|
+
return `You are generating a PRD for a software project.
|
|
3309
3319
|
|
|
3310
3320
|
Input vision document:
|
|
3311
3321
|
---
|
|
@@ -3322,6 +3332,12 @@ Requirements:
|
|
|
3322
3332
|
- Do NOT implement any code yet.
|
|
3323
3333
|
- Do NOT run git commit or git push.
|
|
3324
3334
|
- Output is the updated files on disk (PRD.md).`;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
async function generatePrdFromVision({ engine, visionPath, prdPath }) {
|
|
3338
|
+
const visionContent = readFileSync(visionPath, 'utf8');
|
|
3339
|
+
|
|
3340
|
+
const promptText = generatePrdPrompt(visionContent);
|
|
3325
3341
|
|
|
3326
3342
|
const code = await runEngine(engine, promptText);
|
|
3327
3343
|
if (code !== 0) {
|
|
@@ -3338,7 +3354,7 @@ Requirements:
|
|
|
3338
3354
|
}
|
|
3339
3355
|
}
|
|
3340
3356
|
|
|
3341
|
-
async function runVisionTaskIteration({ engine, prdPath, progressPath, taskText, noTests, noLint }) {
|
|
3357
|
+
export async function runVisionTaskIteration({ engine, prdPath, progressPath, taskText, noTests, noLint, _runEngine }) {
|
|
3342
3358
|
const promptText = `You are an autonomous coding agent executing a PRD.
|
|
3343
3359
|
|
|
3344
3360
|
Files:
|
|
@@ -3366,7 +3382,8 @@ Stop after completing this one task.`;
|
|
|
3366
3382
|
|
|
3367
3383
|
const progressBefore = existsSync(progressPath) ? readFileSync(progressPath, 'utf8') : '';
|
|
3368
3384
|
|
|
3369
|
-
const
|
|
3385
|
+
const runFn = _runEngine || runEngine;
|
|
3386
|
+
const code = await runFn(engine, promptText);
|
|
3370
3387
|
if (code !== 0) {
|
|
3371
3388
|
throw new Error(`Engine exited with code ${code}`);
|
|
3372
3389
|
}
|
|
@@ -3388,6 +3405,71 @@ Stop after completing this one task.`;
|
|
|
3388
3405
|
}
|
|
3389
3406
|
}
|
|
3390
3407
|
|
|
3408
|
+
export function finalizeVisionRun({
|
|
3409
|
+
prdPath,
|
|
3410
|
+
visionPath,
|
|
3411
|
+
hasGit,
|
|
3412
|
+
startSha,
|
|
3413
|
+
noPush,
|
|
3414
|
+
_runGit,
|
|
3415
|
+
_readFileSync,
|
|
3416
|
+
_countUncheckedPrdTasks,
|
|
3417
|
+
_getCurrentBranch
|
|
3418
|
+
}) {
|
|
3419
|
+
const runGitFn = _runGit || runGit;
|
|
3420
|
+
const readFileSyncFn = _readFileSync || readFileSync;
|
|
3421
|
+
const countUncheckedFn = _countUncheckedPrdTasks || countUncheckedPrdTasks;
|
|
3422
|
+
const getCurrentBranchFn = _getCurrentBranch || getCurrentBranch;
|
|
3423
|
+
|
|
3424
|
+
if (!hasGit) {
|
|
3425
|
+
console.log(`\n ${c.green}${sym.check}${c.reset} PRD complete (no git repo detected).\n`);
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
const prdFinal = readFileSyncFn(prdPath, 'utf8');
|
|
3430
|
+
if (countUncheckedFn(prdFinal) > 0) {
|
|
3431
|
+
console.log(`\n ${c.yellow}${sym.warn}${c.reset} PRD still has remaining tasks; skipping commit/push.\n`);
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
const visionName = basename(visionPath).replace(/\.md$/, '');
|
|
3436
|
+
const commitMessage = `feat: run vision (${visionName})`;
|
|
3437
|
+
|
|
3438
|
+
const addRes = runGitFn(['add', '-A']);
|
|
3439
|
+
if (addRes.status !== 0) {
|
|
3440
|
+
console.log(`\n ${c.red}${sym.cross}${c.reset} git add failed.\n`);
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
const statusRes = runGitFn(['status', '--porcelain']);
|
|
3445
|
+
if (statusRes.status === 0 && String(statusRes.stdout || '').trim().length === 0) {
|
|
3446
|
+
console.log(`\n ${c.green}${sym.check}${c.reset} Nothing to commit.\n`);
|
|
3447
|
+
return;
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
const commitRes = runGitFn(['commit', '-m', commitMessage]);
|
|
3451
|
+
if (commitRes.status !== 0) {
|
|
3452
|
+
console.log(`\n ${c.red}${sym.cross}${c.reset} git commit failed.\n`);
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
if (!noPush) {
|
|
3457
|
+
const pushRes = runGitFn(['push']);
|
|
3458
|
+
if (pushRes.status !== 0) {
|
|
3459
|
+
const branch = getCurrentBranchFn();
|
|
3460
|
+
if (branch) {
|
|
3461
|
+
runGitFn(['push', '-u', 'origin', branch]);
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
console.log(`\n ${c.green}${sym.check}${c.reset} Vision complete.`);
|
|
3467
|
+
if (startSha) {
|
|
3468
|
+
console.log(` Started from: ${c.dim}${startSha}${c.reset}`);
|
|
3469
|
+
}
|
|
3470
|
+
console.log('');
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3391
3473
|
async function visionRunner(opts) {
|
|
3392
3474
|
const config = loadConfig();
|
|
3393
3475
|
const interactive = process.stdout.isTTY && process.stdin.isTTY;
|
|
@@ -3458,6 +3540,12 @@ async function visionRunner(opts) {
|
|
|
3458
3540
|
const hasGit = isGitRepo();
|
|
3459
3541
|
const startSha = hasGit ? getGitHeadSha() : null;
|
|
3460
3542
|
|
|
3543
|
+
if (!hasGit) {
|
|
3544
|
+
console.log(`\n ${c.yellow}${sym.warn} Warning:${c.reset} Not a git repository. Version control features disabled.\n`);
|
|
3545
|
+
} else if (startSha) {
|
|
3546
|
+
console.log(`\n ${c.blue}${sym.info} Rollback hint:${c.reset} git reset --hard ${startSha}\n`);
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3461
3549
|
if (hasGit && !isWorkingTreeClean() && !opts.dryRun) {
|
|
3462
3550
|
if (!interactive) {
|
|
3463
3551
|
console.log(`\n ${c.red}Error:${c.reset} Working tree is not clean (non-interactive mode).\n`);
|
|
@@ -3554,53 +3642,13 @@ async function visionRunner(opts) {
|
|
|
3554
3642
|
}
|
|
3555
3643
|
}
|
|
3556
3644
|
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
console.log(`\n ${c.yellow}${sym.warn}${c.reset} PRD still has remaining tasks; skipping commit/push.\n`);
|
|
3565
|
-
return;
|
|
3566
|
-
}
|
|
3567
|
-
|
|
3568
|
-
const visionName = basename(visionPath).replace(/\.md$/, '');
|
|
3569
|
-
const commitMessage = `feat: run vision (${visionName})`;
|
|
3570
|
-
|
|
3571
|
-
const addRes = runGit(['add', '-A']);
|
|
3572
|
-
if (addRes.status !== 0) {
|
|
3573
|
-
console.log(`\n ${c.red}${sym.cross}${c.reset} git add failed.\n`);
|
|
3574
|
-
return;
|
|
3575
|
-
}
|
|
3576
|
-
|
|
3577
|
-
const statusRes = runGit(['status', '--porcelain']);
|
|
3578
|
-
if (statusRes.status === 0 && String(statusRes.stdout || '').trim().length === 0) {
|
|
3579
|
-
console.log(`\n ${c.green}${sym.check}${c.reset} Nothing to commit.\n`);
|
|
3580
|
-
return;
|
|
3581
|
-
}
|
|
3582
|
-
|
|
3583
|
-
const commitRes = runGit(['commit', '-m', commitMessage]);
|
|
3584
|
-
if (commitRes.status !== 0) {
|
|
3585
|
-
console.log(`\n ${c.red}${sym.cross}${c.reset} git commit failed.\n`);
|
|
3586
|
-
return;
|
|
3587
|
-
}
|
|
3588
|
-
|
|
3589
|
-
if (!opts.noPush) {
|
|
3590
|
-
const pushRes = runGit(['push']);
|
|
3591
|
-
if (pushRes.status !== 0) {
|
|
3592
|
-
const branch = getCurrentBranch();
|
|
3593
|
-
if (branch) {
|
|
3594
|
-
runGit(['push', '-u', 'origin', branch]);
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
|
|
3599
|
-
console.log(`\n ${c.green}${sym.check}${c.reset} Vision complete.`);
|
|
3600
|
-
if (startSha) {
|
|
3601
|
-
console.log(` Started from: ${c.dim}${startSha}${c.reset}`);
|
|
3602
|
-
}
|
|
3603
|
-
console.log('');
|
|
3645
|
+
finalizeVisionRun({
|
|
3646
|
+
prdPath,
|
|
3647
|
+
visionPath,
|
|
3648
|
+
hasGit,
|
|
3649
|
+
startSha,
|
|
3650
|
+
noPush: opts.noPush
|
|
3651
|
+
});
|
|
3604
3652
|
}
|
|
3605
3653
|
|
|
3606
3654
|
// ============================================================================
|
|
@@ -4570,7 +4618,7 @@ function showHelp() {
|
|
|
4570
4618
|
console.log(`${pad} ${c.cyan}vision${c.reset} Show VISION.md status`);
|
|
4571
4619
|
console.log(`${pad} ${c.cyan}log${c.reset} Show task logs`);
|
|
4572
4620
|
console.log(`${pad} ${c.cyan}meet${c.reset} Meet Paul and the Artisans`);
|
|
4573
|
-
console.log(`${pad} ${c.cyan}run${c.reset} Run the vision (
|
|
4621
|
+
console.log(`${pad} ${c.cyan}run${c.reset} Run the vision (Vision Runner)`);
|
|
4574
4622
|
console.log(`${pad} ${c.cyan}help${c.reset} Show this help\n`);
|
|
4575
4623
|
|
|
4576
4624
|
console.log(`${pad}${c.bold}Agent Commands${c.reset}`);
|
|
@@ -4581,12 +4629,17 @@ function showHelp() {
|
|
|
4581
4629
|
console.log(`${pad} ${c.cyan}agents fav${c.reset} <name> Toggle favorite`);
|
|
4582
4630
|
console.log(`${pad} ${c.cyan}agents rm${c.reset} <name> Remove agent\n`);
|
|
4583
4631
|
|
|
4632
|
+
console.log(`${pad}${c.bold}Vision Runner${c.reset} ${c.dim}"That he may run that readeth it"${c.reset}`);
|
|
4633
|
+
console.log(`${pad} ${c.cyan}run${c.reset} Launch Vision Runner (interactive)`);
|
|
4634
|
+
console.log(`${pad} ${c.cyan}run${c.reset} --vision <f> Execute specific vision file`);
|
|
4635
|
+
console.log(`${pad} ${c.cyan}run${c.reset} --resume Resume existing PRD.md\n`);
|
|
4636
|
+
|
|
4584
4637
|
console.log(`${pad}${c.bold}Habakkuk Workflow${c.reset} ${c.dim}"Write the vision, make it plain"${c.reset}`);
|
|
4585
4638
|
console.log(`${pad} ${c.cyan}board${c.reset} [--all] Show kanban board`);
|
|
4586
4639
|
console.log(`${pad} ${c.cyan}cry${c.reset} "desc" Enter a problem or need`);
|
|
4587
4640
|
console.log(`${pad} ${c.cyan}wait${c.reset} <id> Move to waiting (seeking)`);
|
|
4588
4641
|
console.log(`${pad} ${c.cyan}vision${c.reset} <id> Move to vision (answer received)`);
|
|
4589
|
-
console.log(`${pad} ${c.cyan}run${c.reset}
|
|
4642
|
+
console.log(`${pad} ${c.cyan}run${c.reset} <id> Move to run (execution started)`);
|
|
4590
4643
|
console.log(`${pad} ${c.cyan}worship${c.reset} <id> Move to worship (retrospective)`);
|
|
4591
4644
|
console.log(`${pad} ${c.cyan}note${c.reset} <id> "text" Add note to item`);
|
|
4592
4645
|
console.log(`${pad} ${c.cyan}item${c.reset} <id> Show item details`);
|
|
@@ -4604,6 +4657,18 @@ function showHelp() {
|
|
|
4604
4657
|
console.log(`${pad} ${c.dim}--gemini${c.reset} Target Gemini CLI`);
|
|
4605
4658
|
console.log(`${pad} ${c.dim}--antigravity${c.reset} Target Antigravity\n`);
|
|
4606
4659
|
|
|
4660
|
+
console.log(`${pad}${c.bold}Vision Runner Options${c.reset}`);
|
|
4661
|
+
console.log(`${pad} ${c.dim}--vision <file>${c.reset} Input vision file (default: discover)`);
|
|
4662
|
+
console.log(`${pad} ${c.dim}--engine <name>${c.reset} Execution engine (opencode|codex|claude)`);
|
|
4663
|
+
console.log(`${pad} ${c.dim}--resume${c.reset} Resume existing PRD.md`);
|
|
4664
|
+
console.log(`${pad} ${c.dim}--regenerate-prd${c.reset} Force regeneration of PRD.md from vision`);
|
|
4665
|
+
console.log(`${pad} ${c.dim}--max-iterations <n>${c.reset} Stop after N tasks`);
|
|
4666
|
+
console.log(`${pad} ${c.dim}--dry-run${c.reset} Show what would happen without running`);
|
|
4667
|
+
console.log(`${pad} ${c.dim}--fast${c.reset} Skip tests and linting`);
|
|
4668
|
+
console.log(`${pad} ${c.dim}--no-tests${c.reset} Skip tests`);
|
|
4669
|
+
console.log(`${pad} ${c.dim}--no-lint${c.reset} Skip linting`);
|
|
4670
|
+
console.log(`${pad} ${c.dim}--no-push${c.reset} Skip git push at end\n`);
|
|
4671
|
+
|
|
4607
4672
|
console.log(`${pad}${c.bold}Examples${c.reset}`);
|
|
4608
4673
|
console.log(`${pad} ${c.green}wtv${c.reset} ${c.dim}# Dashboard${c.reset}`);
|
|
4609
4674
|
console.log(`${pad} ${c.green}wtv agents${c.reset} ${c.dim}# List agents${c.reset}`);
|
package/src/engine.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base class for AI CLI engine adapters.
|
|
5
|
+
*/
|
|
6
|
+
class EngineAdapter {
|
|
7
|
+
constructor(cwd = process.cwd()) {
|
|
8
|
+
this.cwd = cwd;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the command and arguments for the engine.
|
|
13
|
+
* @param {string} prompt - The prompt to execute.
|
|
14
|
+
* @returns {{ command: string, args: string[] }}
|
|
15
|
+
*/
|
|
16
|
+
getCommand(prompt) {
|
|
17
|
+
throw new Error('getCommand must be implemented by subclass');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run the engine with the given prompt.
|
|
22
|
+
* @param {string} prompt - The prompt to execute.
|
|
23
|
+
* @param {object} options - Options for spawn.
|
|
24
|
+
* @returns {Promise<number>} - Exit code.
|
|
25
|
+
*/
|
|
26
|
+
async run(prompt, options = {}) {
|
|
27
|
+
const { command, args } = this.getCommand(prompt);
|
|
28
|
+
|
|
29
|
+
console.log(`[Engine] Running: ${command} ${args.join(' ')}`);
|
|
30
|
+
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const child = spawn(command, args, {
|
|
33
|
+
cwd: this.cwd,
|
|
34
|
+
stdio: 'inherit', // Stream output to parent stdout/stderr
|
|
35
|
+
shell: true, // Use shell to resolve command paths
|
|
36
|
+
...options
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
child.on('error', (err) => {
|
|
40
|
+
reject(err);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
child.on('close', (code) => {
|
|
44
|
+
resolve(code);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adapter for OpenCode CLI.
|
|
52
|
+
*/
|
|
53
|
+
class OpenCodeEngine extends EngineAdapter {
|
|
54
|
+
getCommand(prompt) {
|
|
55
|
+
// Assumption: opencode takes the prompt as a single argument string
|
|
56
|
+
return {
|
|
57
|
+
command: 'opencode',
|
|
58
|
+
args: [prompt]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Adapter for Codex CLI.
|
|
65
|
+
*/
|
|
66
|
+
class CodexEngine extends EngineAdapter {
|
|
67
|
+
getCommand(prompt) {
|
|
68
|
+
// Assumption: codex takes the prompt as a single argument string
|
|
69
|
+
return {
|
|
70
|
+
command: 'codex',
|
|
71
|
+
args: [prompt]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Adapter for Claude Code CLI.
|
|
78
|
+
*/
|
|
79
|
+
class ClaudeEngine extends EngineAdapter {
|
|
80
|
+
getCommand(prompt) {
|
|
81
|
+
// Assumption: claude takes the prompt as a single argument string
|
|
82
|
+
// Note: Check if it needs -p or similar flag
|
|
83
|
+
return {
|
|
84
|
+
command: 'claude',
|
|
85
|
+
args: [prompt]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Factory to create the appropriate engine adapter.
|
|
92
|
+
* @param {string} name - The name of the engine ('opencode', 'codex', 'claude').
|
|
93
|
+
* @param {string} cwd - Current working directory.
|
|
94
|
+
* @returns {EngineAdapter}
|
|
95
|
+
*/
|
|
96
|
+
export function createEngine(name, cwd) {
|
|
97
|
+
switch (name.toLowerCase()) {
|
|
98
|
+
case 'opencode':
|
|
99
|
+
return new OpenCodeEngine(cwd);
|
|
100
|
+
case 'codex':
|
|
101
|
+
return new CodexEngine(cwd);
|
|
102
|
+
case 'claude':
|
|
103
|
+
return new ClaudeEngine(cwd);
|
|
104
|
+
default:
|
|
105
|
+
throw new Error(`Unsupported engine: ${name}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { EngineAdapter, OpenCodeEngine, CodexEngine, ClaudeEngine };
|