chore: add ion package, codesight wiki, work plans, ascli config
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.
This commit is contained in:
207
packages/ion/src/cli/index.ts
Normal file
207
packages/ion/src/cli/index.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user