aiwg
Version:
Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo
165 lines • 6.9 kB
JavaScript
/**
* Run Command Handler
*
* Implements `aiwg run <script>` — executes user-defined scripts from
* `.aiwg/aiwg.config` `scripts` section.
*
* Modelled on `npm run`. With no arguments, lists available scripts.
*
* Environment variables available in scripts:
* $AIWG_PROJECT — absolute path to the project root
* $AIWG_PROVIDERS — comma-separated list of configured providers
*
* @implements #621
*/
import { spawn } from 'child_process';
import { readAiwgConfig, getProjectDir } from '../../config/aiwg-config.js';
import { handlerResultFromError } from '../errors.js';
import * as ui from '../ui.js';
/**
* Execute a shell command with inherited stdio.
* Returns the exit code.
*/
function runScript(command, env, cwd) {
return new Promise((resolve, reject) => {
const isWindows = process.platform === 'win32';
const shell = isWindows ? 'cmd' : 'sh';
const shellFlag = isWindows ? '/c' : '-c';
const child = spawn(shell, [shellFlag, command], {
stdio: 'inherit',
env: { ...process.env, ...env },
cwd,
});
child.on('error', reject);
child.on('close', (code) => resolve(code ?? 1));
});
}
export const runHandler = {
id: 'run',
name: 'Run Script',
description: 'Run a user-defined script from aiwg.config',
category: 'utility',
aliases: [],
async execute(ctx) {
const scriptName = ctx.args[0];
const projectDir = getProjectDir(ctx, ctx.args);
// #1231 — intercept --help/-h before script lookup so the user sees
// help for both run forms, not "No script named '--help'".
if (scriptName === '--help' || scriptName === '-h') {
printRunUsage();
return { exitCode: 0 };
}
// #1227 — `aiwg run skill <name>` dispatches to the script-bearing
// skill executor in src/skills/run.ts. Routed here so we don't have
// two handlers competing for `id: "run"` in the dispatch table.
if (scriptName === 'skill') {
try {
const { main } = await import('../../skills/run.js');
const exitCode = await main(ctx.args);
return { exitCode };
}
catch (error) {
const result = handlerResultFromError(error);
return { ...result, message: `Skill run failed: ${result.message}` };
}
}
const config = await readAiwgConfig(projectDir);
if (!config) {
return {
exitCode: 1,
message: [
"Error: No .aiwg/aiwg.config found in this project.",
'',
"Run 'aiwg init' to create one and define scripts.",
].join('\n'),
};
}
const scripts = config.scripts ?? {};
// No script name — list available scripts AND mention `run skill <name>`
// (#1231) so users discover the sibling form.
if (!scriptName) {
ui.blank();
console.log(` ${ui.brandMark()} ${ui.bold('Available Scripts')}`);
ui.rule();
const entries = Object.entries(scripts);
if (entries.length === 0) {
ui.dim(' No scripts defined in .aiwg/aiwg.config');
ui.blank();
ui.dim(' Add scripts to the "scripts" section, e.g.:');
ui.dim(' "deploy": "aiwg use all"');
ui.blank();
}
else {
const nameWidth = Math.max(...entries.map(([n]) => n.length), 6);
for (const [name, cmd] of entries) {
console.log(` ${name.padEnd(nameWidth)} ${ui.dimText(cmd)}`);
}
ui.blank();
ui.dim(` Run with: aiwg run <script-name>`);
ui.blank();
}
ui.dim(` Or run a script-bearing skill: aiwg run skill <name>`);
ui.dim(` See \`aiwg run --help\` for both forms.`);
ui.blank();
return { exitCode: 0 };
}
// Look up the script
const command = scripts[scriptName];
if (!command) {
const available = Object.keys(scripts);
const hint = available.length > 0
? `\nAvailable: ${available.join(', ')}`
: '\nNo scripts defined. Run `aiwg init` to add some.';
return {
exitCode: 1,
message: `Error: No script named '${scriptName}'${hint}`,
};
}
// Build environment
const env = {
AIWG_PROJECT: projectDir,
AIWG_PROVIDERS: config.providers.join(','),
};
ui.blank();
console.log(` ${ui.brandMark()} ${ui.bold(`aiwg run ${scriptName}`)}`);
ui.dim(` > ${command}`);
console.log('');
let exitCode;
try {
exitCode = await runScript(command, env, projectDir);
}
catch (error) {
const result = handlerResultFromError(error);
return { ...result, message: `Error executing script: ${result.message}` };
}
if (exitCode !== 0) {
console.log('');
ui.warn(`Script '${scriptName}' exited with code ${exitCode}`);
}
return { exitCode };
},
};
function printRunUsage() {
console.log('Usage: aiwg run <script-name> [args...]');
console.log(' aiwg run skill <skill-name> [--cwd <path>] [-- <args forwarded to script>]');
console.log('');
console.log('Two forms share the `run` namespace:');
console.log('');
console.log(' aiwg run <script-name>');
console.log(' Execute a user-defined script from .aiwg/aiwg.config (modeled on `npm run`).');
console.log(' With no arguments, lists available scripts.');
console.log('');
console.log(' aiwg run skill <skill-name>');
console.log(' Execute a script-bearing skill via the artifact index. Skills with a');
console.log(' `script:` frontmatter declaration are dispatched through the runtime');
console.log(' registry (node/python3/bash/sh/pwsh/ruby/auto). Find skills with');
console.log(' `aiwg discover "<phrase>"` — executable skills are flagged `executable: true`.');
console.log('');
console.log('Examples:');
console.log(' aiwg run # list available user scripts');
console.log(' aiwg run deploy # run user script "deploy"');
console.log(' aiwg run skill voice-apply -- --voice technical-authority --input draft.md');
console.log(' aiwg run skill template-engine -- render adr-template.md --vars vars.yaml');
console.log(' aiwg run skill <name> --cwd <path> # explicit CWD override');
}
//# sourceMappingURL=run.js.map