UNPKG

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
/** * 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