#!/usr/bin/env node
import path from 'node:path';
import process from 'node:process';
import { Command } from 'commander';
import fsExtra from 'fs-extra';
import { architect } from '../src/orchestrator.mjs';
const { pathExists, readFile } = fsExtra;
async function readStdin() {
if (process.stdin.isTTY) return null;
let data = '';
process.stdin.setEncoding('utf8');
for await (const chunk of process.stdin) data += chunk;
return data.trim() || null;
}
function slugify(input) {
return input
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 60) || 'unnamed';
}
const program = new Command();
program
.name('p3x-architect')
.description('Multi-agent RUP pipeline (OpenAI + Claude) — generates a full design and an implementation under agents/<slug>/.')
.argument('[input]', 'path to a Markdown file containing the requirement (or omit to use --text or stdin)')
.option('-t, --text <requirement>', 'inline requirement text (alternative to a file)')
.option('-n, --name <slug>', 'feature slug (folder name under agents/) — derived from input filename if omitted')
.option('-o, --output <dir>', 'override the output directory (default: <cwd>/agents/<slug>)')
.option('-r, --max-rounds <n>', 'maximum critic↔reviser rounds in Construction', (v) => Number(v), 2)
.option('-b, --budget <usd>', 'cumulative USD budget; 0 = unlimited', (v) => Number(v), 5)
.option('--cwd <dir>', 'project root for the agents/<slug>/ output (defaults to current dir)')
.action(async (input, opts) => {
let requirement = opts.text;
let derivedName = null;
if (input) {
if (!(await pathExists(input))) {
console.error(`Input file not found: ${input}`);
process.exit(2);
}
requirement = await readFile(input, 'utf8');
derivedName = path.basename(input, path.extname(input));
} else if (!requirement) {
const stdinText = await readStdin();
if (stdinText) {
requirement = stdinText;
}
}
if (!requirement) {
console.error('No requirement provided. Pass a file path, --text "...", or pipe via stdin.');
process.exit(2);
}
const slug = opts.name ?? (derivedName ? slugify(derivedName) : slugify(requirement.slice(0, 40)));
const cwd = opts.cwd ?? process.cwd();
try {
const result = await architect({
requirement,
slug,
outputDir: opts.output,
projectRoot: cwd,
maxRounds: opts.maxRounds,
budgetUsd: opts.budget,
log: (msg) => console.log(msg),
});
console.log('');
console.log(`✔ done — ${result.baseDir}`);
console.log(` verdict: ${result.verdict}`);
console.log(` files: ${result.files.length}`);
console.log(` cost: $${result.usage.totalUsd.toFixed(4)}`);
} catch (err) {
console.error('');
console.error(`✖ pipeline failed: ${err.message}`);
if (process.env.ARCHITECT_DEBUG) console.error(err.stack);
process.exit(1);
}
});
await program.parseAsync(process.argv);