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
178 lines • 6.89 kB
JavaScript
/**
* Agent Spawn Utility
*
* Centralized utility for spawning agentic CLI tools across providers.
* Handles --dangerous, --provider, and --params passthrough flags uniformly
* across all commands that invoke an underlying agent system.
*
* Usage pattern:
* const { opts, remaining } = parseAgentSpawnFlags(ctx.args);
* const config = getProviderConfig(opts.provider);
* const spawnArgs = buildAgentArgs(prompt, opts);
* spawn(config.binary, spawnArgs, { stdio: 'inherit' });
*/
export const PROVIDER_CONFIGS = {
claude: {
binary: 'claude',
dangerousFlag: '--dangerously-skip-permissions',
name: 'Claude Code',
},
opencode: {
binary: 'opencode',
// OpenCode uses `opencode run "<prompt>"` for non-interactive execution.
// Dangerous mode flag not yet confirmed; omit rather than guess.
promptPrefix: ['run'],
dangerousFlag: null,
name: 'OpenCode',
},
codex: {
binary: 'codex',
// Codex supports --full-auto (no approval prompts) and --approval-mode full-auto
dangerousFlag: '--full-auto',
name: 'OpenAI Codex',
},
hermes: {
// Hermes is a model series (NousResearch), not a confirmed standalone CLI.
// Treat as IDE/runtime-integrated until a CLI is confirmed.
binary: null,
dangerousFlag: null,
name: 'Hermes',
guidanceMessage: 'Hermes does not have a confirmed standalone CLI.\n' +
'Run via Ollama (`ollama run hermes3`) or through your configured MCP sidecar.',
},
copilot: {
binary: null,
dangerousFlag: null,
name: 'GitHub Copilot',
guidanceMessage: 'GitHub Copilot is IDE-integrated and cannot be spawned from the CLI.\n' +
'Open your IDE, navigate to the Copilot chat panel, and run the skill manually.',
},
cursor: {
binary: null,
dangerousFlag: null,
name: 'Cursor',
guidanceMessage: 'Cursor is IDE-integrated and cannot be spawned from the CLI.\n' +
'For unrestricted AIWG tool access, use the MCP sidecar:\n' +
' aiwg mcp install cursor && aiwg mcp serve\n' +
'See: docs/integrations/cursor-mcp-sidecar.md',
},
factory: {
binary: null,
dangerousFlag: null,
name: 'Factory AI',
guidanceMessage: 'Factory AI is cloud-based and cannot be spawned from the CLI.\n' +
'Use the Factory AI dashboard to dispatch the workflow.',
},
warp: {
binary: null,
dangerousFlag: null,
name: 'Warp Terminal',
guidanceMessage: 'Warp Terminal cannot be spawned programmatically.\n' +
'For unrestricted AIWG tool access, use the MCP sidecar:\n' +
' aiwg mcp install warp && aiwg mcp serve\n' +
'See: docs/integrations/warp-mcp-sidecar.md',
},
windsurf: {
binary: null,
dangerousFlag: null,
name: 'Windsurf',
guidanceMessage: 'Windsurf is IDE-integrated and cannot be spawned from the CLI.\n' +
'For unrestricted AIWG tool access, use the MCP sidecar:\n' +
' aiwg mcp install windsurf && aiwg mcp serve\n' +
'See: docs/integrations/windsurf-mcp-sidecar.md',
},
};
/**
* Extract --provider, --dangerous, and --params from an args array.
* Returns the parsed options and the remaining args with those flags removed.
*
* This is non-destructive — the original array is not modified.
*/
export function parseAgentSpawnFlags(args) {
const opts = {};
const remaining = [];
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--provider' && i + 1 < args.length) {
opts.provider = args[++i];
}
else if (arg === '--dangerous') {
opts.dangerous = true;
}
else if (arg === '--params' && i + 1 < args.length) {
opts.params = args[++i];
}
else {
remaining.push(arg);
}
}
return { opts, remaining };
}
// ── Arg construction ──────────────────────────────────────────
/**
* Build the final args array for spawning a provider binary.
*
* Order: [prompt, dangerous-flag?, ...raw-params]
*/
export function buildAgentArgs(prompt, opts) {
const config = getProviderConfig(opts.provider ?? 'claude');
const args = [];
// Dangerous flag must precede the prompt — it is a CLI flag to the binary,
// not content to pass to the agent. e.g. `claude --dangerously-skip-permissions "<prompt>"`
if (opts.dangerous && config.dangerousFlag) {
args.push(config.dangerousFlag);
// If provider has no dangerous flag, silently ignored —
// callers should warn via dangerousWarning().
}
// Some providers require a subcommand before the prompt (e.g. `opencode run "<prompt>"`)
args.push(...(config.promptPrefix ?? []));
// The prompt
args.push(prompt);
// User passthrough params — appended after the prompt verbatim
if (opts.params) {
args.push(...splitParams(opts.params));
}
return args;
}
/**
* Split a params string into individual args, respecting double/single quotes.
* "hello world" → ['hello world']
* --flag value → ['--flag', 'value']
*/
export function splitParams(params) {
const result = [];
const re = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g;
let match;
while ((match = re.exec(params)) !== null) {
// Strip surrounding quotes from individual tokens
result.push(match[0].replace(/^["']|["']$/g, ''));
}
return result;
}
// ── Provider helpers ──────────────────────────────────────────
/** Get config for a provider, falling back to claude for unknown values. */
export function getProviderConfig(provider) {
return PROVIDER_CONFIGS[provider] ?? PROVIDER_CONFIGS['claude'];
}
/** Returns true if the provider has a CLI binary that can be spawned. */
export function isSpawnableProvider(provider) {
return getProviderConfig(provider).binary !== null;
}
/**
* Warn string if --dangerous was requested but the provider has no
* corresponding flag. Returns null if the combination is valid.
*/
export function dangerousWarning(provider) {
const config = getProviderConfig(provider);
if (!config.dangerousFlag) {
return `--dangerous is not supported for provider '${config.name}' and will be ignored.`;
}
return null;
}
/** List all spawnable provider names. */
export function spawnableProviders() {
return Object.entries(PROVIDER_CONFIGS)
.filter(([, c]) => c.binary !== null)
.map(([k]) => k);
}
//# sourceMappingURL=agent-spawn.js.map