New @boocode/ion package (v0.0.1) for inference optimization network. .codesight/ wiki artifacts for codebase documentation. .omo/ work plans for openspec cleanup and enhanced file panel.
207 lines
6.1 KiB
TypeScript
207 lines
6.1 KiB
TypeScript
/**
|
|
* Ion workflow engine CLI entry point.
|
|
*
|
|
* Pure Node.js CLI using process.argv parsing — no external argparse library.
|
|
* Routes subcommands to their respective handler modules.
|
|
*
|
|
* @example
|
|
* node dist/cli/index.js workflow list --json
|
|
* node dist/cli/index.js workflow run deploy --cwd /tmp/project
|
|
*/
|
|
|
|
import { parseArgs, buildCliOptions, printJson } from './utils.js';
|
|
import type { CliOptions } from './utils.js';
|
|
|
|
import { listCommand } from './commands/list.js';
|
|
import { runCommand } from './commands/run.js';
|
|
import { statusCommand } from './commands/status.js';
|
|
import { runsCommand } from './commands/runs.js';
|
|
import { approveCommand } from './commands/approve.js';
|
|
import { rejectCommand } from './commands/reject.js';
|
|
import { resumeCommand } from './commands/resume.js';
|
|
import { abandonCommand } from './commands/abandon.js';
|
|
import { cleanupCommand } from './commands/cleanup.js';
|
|
import { validateCommand } from './commands/validate.js';
|
|
import { convertCommand } from './commands/convert.js';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Command registry
|
|
// ---------------------------------------------------------------------------
|
|
|
|
interface CommandEntry {
|
|
name: string;
|
|
description: string;
|
|
usage: string;
|
|
handler: (args: string[], options: CliOptions) => Promise<void>;
|
|
}
|
|
|
|
const COMMANDS: CommandEntry[] = [
|
|
{
|
|
name: 'list',
|
|
description: 'List all available workflows',
|
|
usage: 'workflow list [--json]',
|
|
handler: listCommand,
|
|
},
|
|
{
|
|
name: 'run',
|
|
description: 'Execute a workflow by name',
|
|
usage: 'workflow run <name> [args...] [--cwd <path>] [--detach] [--json]',
|
|
handler: runCommand,
|
|
},
|
|
{
|
|
name: 'status',
|
|
description: 'Show active (running + paused) workflow runs',
|
|
usage: 'workflow status [--json]',
|
|
handler: statusCommand,
|
|
},
|
|
{
|
|
name: 'runs',
|
|
description: 'List recent workflow runs with filters',
|
|
usage: 'workflow runs [--status <status>] [--limit N] [--all] [--json]',
|
|
handler: runsCommand,
|
|
},
|
|
{
|
|
name: 'approve',
|
|
description: 'Approve a paused workflow run',
|
|
usage: 'workflow approve <run-id> [comment] [--json]',
|
|
handler: approveCommand,
|
|
},
|
|
{
|
|
name: 'reject',
|
|
description: 'Reject a paused workflow run',
|
|
usage: 'workflow reject <run-id> [reason] [--json]',
|
|
handler: rejectCommand,
|
|
},
|
|
{
|
|
name: 'resume',
|
|
description: 'Resume a failed workflow run',
|
|
usage: 'workflow resume <run-id> [--json]',
|
|
handler: resumeCommand,
|
|
},
|
|
{
|
|
name: 'abandon',
|
|
description: 'Cancel a non-terminal workflow run',
|
|
usage: 'workflow abandon <run-id> [--json]',
|
|
handler: abandonCommand,
|
|
},
|
|
{
|
|
name: 'cleanup',
|
|
description: 'Remove old workflow run artifacts',
|
|
usage: 'workflow cleanup [days] [--json]',
|
|
handler: cleanupCommand,
|
|
},
|
|
{
|
|
name: 'validate',
|
|
description: 'Validate a workflow definition without executing',
|
|
usage: 'workflow validate <name> [--json]',
|
|
handler: validateCommand,
|
|
},
|
|
{
|
|
name: 'convert',
|
|
description: 'Convert a .sop.md file to a YAML workflow definition',
|
|
usage: 'workflow convert <file.sop.md> [--output <path>]',
|
|
handler: convertCommand,
|
|
},
|
|
];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Help output
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function printHelp(): void {
|
|
console.log('');
|
|
console.log('Ion — Workflow Engine CLI');
|
|
console.log('');
|
|
console.log('Usage:');
|
|
console.log(' workflow <command> [options]');
|
|
console.log('');
|
|
console.log('Commands:');
|
|
|
|
const maxNameLen = Math.max(...COMMANDS.map((c) => c.name.length));
|
|
for (const cmd of COMMANDS) {
|
|
const padded = cmd.name.padEnd(maxNameLen + 2);
|
|
console.log(` ${padded}${cmd.description}`);
|
|
}
|
|
|
|
console.log('');
|
|
console.log('Global options:');
|
|
console.log(' --json Output as JSON (suppresses all other output)');
|
|
console.log(' --cwd <path> Set working directory');
|
|
console.log(' --store <path> Path to workflow store');
|
|
console.log(' --db-path <p> Path to database file');
|
|
console.log('');
|
|
console.log('Run "workflow <command> --help" for command-specific usage.');
|
|
console.log('');
|
|
}
|
|
|
|
function printCommandHelp(cmd: CommandEntry): void {
|
|
console.log('');
|
|
console.log(`workflow ${cmd.name}`);
|
|
console.log('');
|
|
console.log(` ${cmd.description}`);
|
|
console.log('');
|
|
console.log('Usage:');
|
|
console.log(` ${cmd.usage}`);
|
|
console.log('');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main entry
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function main(argv: string[] = process.argv.slice(2)): Promise<void> {
|
|
const { args, options } = parseArgs(argv);
|
|
const cliOptions = buildCliOptions(options);
|
|
|
|
// --help with no command → general help
|
|
if (args.length === 0 || options.help === true) {
|
|
printHelp();
|
|
process.exit(0);
|
|
}
|
|
|
|
const commandName = args[0];
|
|
const commandArgs = args.slice(1);
|
|
|
|
// --help after a command name → command-specific help
|
|
if (options.help) {
|
|
const cmd = COMMANDS.find((c) => c.name === commandName);
|
|
if (cmd) {
|
|
printCommandHelp(cmd);
|
|
} else {
|
|
console.error(`Unknown command: ${commandName}`);
|
|
printHelp();
|
|
}
|
|
process.exit(0);
|
|
}
|
|
|
|
const command = COMMANDS.find((c) => c.name === commandName);
|
|
|
|
if (!command) {
|
|
console.error(`Unknown command: ${commandName}`);
|
|
printHelp();
|
|
process.exit(1);
|
|
return; // unreachable, but satisfies TS control flow
|
|
}
|
|
|
|
try {
|
|
await command.handler(commandArgs, cliOptions);
|
|
} catch (err: unknown) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
if (cliOptions.json) {
|
|
printJson({ error: message });
|
|
} else {
|
|
console.error(`Error: ${message}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run when executed directly (not imported).
|
|
// In ESM, check import.meta.url to detect direct execution.
|
|
const _directRun = typeof import.meta !== 'undefined' && import.meta.url;
|
|
if (_directRun) {
|
|
main().catch((err: unknown) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|
|
} |