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
336 lines (332 loc) • 13.9 kB
JavaScript
/**
* Team Command Handler
*
* Extends Claude Code's native agent teams feature to all 9 AIWG providers.
* Provides a provider-agnostic abstraction for declaring, deploying, and
* invoking agent teams. On Claude Code, delegates to native team mechanisms.
* On all other providers, emulates via aiwg mc (Mission Control) orchestration.
*
* Subcommands: run, list, info
*
* @issue #598
*/
import * as ui from '../ui.js';
import { promises as fs } from 'node:fs';
import { join, basename } from 'node:path';
// ── Provider Detection ───────────────────────────────────────
function detectProvider(args) {
const providerFlag = parseFlag(args, '--provider');
if (providerFlag)
return providerFlag;
// Claude Code sets CLAUDE_CODE_VERSION; API key presence is secondary heuristic
const isClaudeCode = process.env.CLAUDE_CODE_VERSION !== undefined ||
process.env.ANTHROPIC_API_KEY !== undefined;
return isClaudeCode ? 'claude' : 'unknown';
}
function isNativeTeamsProvider(provider) {
// Only Claude Code has native agent team support
return provider === 'claude';
}
// ── Helpers ──────────────────────────────────────────────────
function parseFlag(args, flag) {
const idx = args.indexOf(flag);
if (idx === -1 || idx + 1 >= args.length)
return undefined;
return args[idx + 1];
}
function hasFlag(args, flag) {
return args.includes(flag);
}
function getPositionalArgs(args) {
const positional = [];
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--')) {
if (i + 1 < args.length && !args[i + 1].startsWith('--'))
i++;
continue;
}
positional.push(args[i]);
}
return positional;
}
async function loadTeam(slug, frameworkRoot, cwd) {
const searchPaths = [
join(cwd, '.aiwg', 'teams', `${slug}.json`),
join(frameworkRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'teams', `${slug}.json`),
];
for (const path of searchPaths) {
try {
const raw = await fs.readFile(path, 'utf-8');
const team = JSON.parse(raw);
return { ...team, slug: team.slug || slug };
}
catch {
// Try next location
}
}
return null;
}
async function listAllTeams(frameworkRoot, cwd) {
const teams = [];
const seen = new Set();
const searchDirs = [
join(cwd, '.aiwg', 'teams'),
join(frameworkRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'teams'),
];
for (const dir of searchDirs) {
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isFile() || !entry.name.endsWith('.json'))
continue;
if (entry.name === 'manifest.json' || entry.name === 'schema.json')
continue;
const slug = basename(entry.name, '.json');
if (seen.has(slug))
continue;
try {
const raw = await fs.readFile(join(dir, entry.name), 'utf-8');
const team = JSON.parse(raw);
teams.push({ ...team, slug: team.slug || slug });
seen.add(slug);
}
catch {
// Skip invalid team files
}
}
}
catch {
// Directory doesn't exist — skip
}
}
return teams;
}
// ── Subcommand: run ──────────────────────────────────────────
async function teamRun(ctx) {
const positional = getPositionalArgs(ctx.args);
const slug = positional[0];
if (!slug) {
ui.error('Usage: aiwg team run <name> [--provider <p>] [--objective "<text>"]');
return { exitCode: 1 };
}
const provider = detectProvider(ctx.args);
const objective = parseFlag(ctx.args, '--objective') || `Execute ${slug} team workflow`;
const team = await loadTeam(slug, ctx.frameworkRoot, ctx.cwd);
if (!team) {
ui.error(`Team not found: ${slug}`);
ui.info('Run `aiwg team list` to see available teams.');
return { exitCode: 1 };
}
const dispatchMode = team.dispatch || 'sequential';
ui.blank();
console.log(` ${ui.brandMark()} ${ui.bold('Team')} — ${ui.accent(team.name)}`);
ui.rule();
console.log(` ${ui.dimText('Provider:')} ${provider}`);
console.log(` ${ui.dimText('Dispatch:')} ${dispatchMode}`);
console.log(` ${ui.dimText('Agents:')} ${team.agents.map(a => a.agent).join(', ')}`);
ui.blank();
if (isNativeTeamsProvider(provider)) {
// Claude Code: delegate to native agent teams
console.log(` ${ui.bold('Native team dispatch (Claude Code)')}:`);
ui.blank();
console.log(` Invoke team members using @agent-name syntax:`);
ui.blank();
for (const member of team.agents) {
const roleIcon = member.role === 'lead' ? '★' : member.role === 'reviewer' ? '◎' : member.role === 'advisor' ? '◇' : '○';
console.log(` ${roleIcon} @${member.agent} ${ui.dimText('[' + member.role + ']')}`);
if (member.responsibilities && member.responsibilities.length > 0) {
console.log(` ${ui.dimText(member.responsibilities[0])}`);
}
}
ui.blank();
if (dispatchMode === 'parallel') {
console.log(` ${ui.dimText('Tip:')} Launch all agents in a single message for parallel execution.`);
}
}
else {
// Other providers: emulate via aiwg mc
console.log(` ${ui.bold('Team dispatch via Mission Control (aiwg mc)')}:`);
ui.blank();
const lead = team.agents.find(a => a.role === 'lead') || team.agents[0];
const others = team.agents.filter(a => a.agent !== lead.agent);
console.log(` ${ui.dimText('# Step 1: Start a session')}`);
console.log(` aiwg mc start --name "${team.name}"`);
ui.blank();
console.log(` ${ui.dimText('# Step 2: Dispatch agents')}`);
console.log(` aiwg mc dispatch <session-id> "Run ${lead.agent}: ${objective}" --completion "${lead.agent} deliverables complete"`);
for (const member of others) {
console.log(` aiwg mc dispatch <session-id> "Run ${member.agent}: ${objective}" --completion "${member.agent} review complete"`);
}
ui.blank();
console.log(` ${ui.dimText('# Step 3: Monitor')}`);
console.log(` aiwg mc watch`);
ui.blank();
ui.info(`Provider '${provider}' uses aiwg mc emulation (no native team support).`);
}
return { exitCode: 0 };
}
// ── Subcommand: list ─────────────────────────────────────────
async function teamList(ctx) {
const json = hasFlag(ctx.args, '--json');
const provider = detectProvider(ctx.args);
const teams = await listAllTeams(ctx.frameworkRoot, ctx.cwd);
if (json) {
console.log(JSON.stringify(teams.map(t => ({
slug: t.slug,
name: t.name,
description: t.description,
agents: t.agents.map(a => a.agent),
dispatch: t.dispatch || 'sequential',
sdlc_phases: t.sdlc_phases || [],
})), null, 2));
return { exitCode: 0 };
}
if (teams.length === 0) {
ui.info('No teams found. Deploy the sdlc-complete framework with: aiwg use sdlc');
return { exitCode: 0 };
}
const backend = isNativeTeamsProvider(provider)
? 'native (Claude Code)'
: `aiwg mc emulation (${provider})`;
ui.blank();
console.log(` ${ui.brandMark()} ${ui.bold('Available Teams')}`);
ui.rule();
console.log(` ${ui.dimText('Team backend:')} ${backend}`);
ui.blank();
const header = ` ${'Team'.padEnd(24)} ${'Agents'.padEnd(8)} ${'Dispatch'.padEnd(12)} Description`;
console.log(header);
ui.rule(70);
for (const team of teams) {
const slug = (team.slug || '').padEnd(24);
const agentCount = String(team.agents.length).padEnd(8);
const dispatch = (team.dispatch || 'sequential').padEnd(12);
console.log(` ${slug} ${agentCount} ${dispatch} ${team.description}`);
}
ui.blank();
console.log(` ${ui.dimText('Run:')} aiwg team run <name> ${ui.dimText('|')} aiwg team info <name>`);
ui.blank();
return { exitCode: 0 };
}
// ── Subcommand: info ─────────────────────────────────────────
async function teamInfo(ctx) {
const positional = getPositionalArgs(ctx.args);
const slug = positional[0];
const json = hasFlag(ctx.args, '--json');
if (!slug) {
ui.error('Usage: aiwg team info <name> [--json]');
return { exitCode: 1 };
}
const team = await loadTeam(slug, ctx.frameworkRoot, ctx.cwd);
if (!team) {
ui.error(`Team not found: ${slug}`);
ui.info('Run `aiwg team list` to see available teams.');
return { exitCode: 1 };
}
if (json) {
console.log(JSON.stringify(team, null, 2));
return { exitCode: 0 };
}
ui.blank();
console.log(` ${ui.brandMark()} ${ui.bold(team.name)}`);
ui.rule();
console.log(` ${ui.dimText('Slug:')} ${team.slug}`);
console.log(` ${ui.dimText('Dispatch:')} ${team.dispatch || 'sequential'}`);
console.log(` ${ui.dimText('Phases:')} ${(team.sdlc_phases || []).join(', ') || 'all'}`);
ui.blank();
console.log(` ${ui.bold('Description')}`);
console.log(` ${team.description}`);
ui.blank();
console.log(` ${ui.bold('Agents')}`);
for (const member of team.agents) {
const roleIcon = member.role === 'lead' ? '★' :
member.role === 'reviewer' ? '◎' :
member.role === 'advisor' ? '◇' : '○';
console.log(` ${roleIcon} ${member.agent.padEnd(30)} ${ui.dimText('[' + member.role + ']')}`);
if (member.responsibilities) {
for (const r of member.responsibilities) {
console.log(` ${ui.dimText('•')} ${r}`);
}
}
}
if (team.use_cases && team.use_cases.length > 0) {
ui.blank();
console.log(` ${ui.bold('Use Cases')}`);
for (const uc of team.use_cases) {
console.log(` ${ui.dimText('•')} ${uc}`);
}
}
if (team.handoffs && team.handoffs.length > 0) {
ui.blank();
console.log(` ${ui.bold('Handoffs')}`);
for (const h of team.handoffs) {
console.log(` ${h.from} → ${h.to}`);
console.log(` ${ui.dimText('Artifact:')} ${h.artifact}`);
console.log(` ${ui.dimText('Gate:')} ${h.gate}`);
}
}
ui.blank();
return { exitCode: 0 };
}
// ── Help ─────────────────────────────────────────────────────
function showTeamHelp() {
ui.blank();
console.log(` ${ui.brandMark()} ${ui.bold('Agent Teams')} — multi-agent team orchestration across all providers`);
ui.rule();
console.log(`
${ui.bold('Usage:')} aiwg team <subcommand> [options]
${ui.bold('Subcommands:')}
run <name> Execute a team (native on Claude Code, aiwg mc on others)
list List available teams
info <name> Show team definition and agent roster
${ui.bold('Options:')}
--provider <p> Override provider: claude|warp|copilot|cursor|windsurf|opencode|factory|codex|openclaw
--objective "<text>" Set objective passed to mc dispatch agents
--json Machine-readable output
${ui.bold('Provider Routing:')}
Claude Code Native agent team dispatch (@agent-name invocation)
All others aiwg mc emulation (Mission Control sequential/parallel dispatch)
${ui.bold('Examples:')}
aiwg team run sdlc-review
aiwg team run sdlc-review --provider cursor --objective "Phase gate review"
aiwg team run security-review --objective "Pre-release audit"
aiwg team list
aiwg team list --json
aiwg team info sdlc-review
`);
}
// ── Subcommand router ─────────────────────────────────────────
const subcommands = {
run: teamRun,
list: teamList,
info: teamInfo,
};
// ── Exported handler ──────────────────────────────────────────
export const teamHandler = {
id: 'team',
name: 'Agent Teams',
description: 'Multi-agent team orchestration across all providers (run, list, info)',
category: 'orchestration',
aliases: ['teams'],
async execute(ctx) {
const subcmd = ctx.args[0];
if (!subcmd || subcmd === '--help' || subcmd === '-h') {
showTeamHelp();
return { exitCode: 0 };
}
const handler = subcommands[subcmd];
if (!handler) {
ui.error(`Unknown subcommand: ${subcmd}. Run 'aiwg team --help' for usage.`);
return { exitCode: 1 };
}
const subCtx = {
...ctx,
args: ctx.args.slice(1),
};
return handler(subCtx);
},
};
/**
* All team-related handlers for bulk registration
*/
export const teamHandlers = [teamHandler];
//# sourceMappingURL=team.js.map