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

334 lines (331 loc) 14.8 kB
/** * Steward Command Handler * * Provider capability awareness and command routing intelligence for the AIWG Steward. * Reads the canonical capability matrix and answers: * - What does my current provider support natively? * - What command should I use for feature X? * - Which providers support feature Y? * * Subcommands: * aiwg steward capabilities --provider <name> Show capabilities for a provider * aiwg steward capabilities --feature <name> Show provider support for a feature * aiwg steward capabilities --all Full capability matrix * aiwg steward find --capability <name> Routing advice for current provider * * @source @src/cli/router.ts * @issue #599 #1261 #1262 */ import { AiwgError, EXIT_CODES, handlerResultFromError } from '../errors.js'; import { loadCapabilityMatrix, } from '../../providers/capability-matrix.js'; import { getProjectDir, readAiwgConfig } from '../../config/aiwg-config.js'; const BASELINE_PROVIDER = 'claude-code'; // ── Feature name normalization ──────────────────────────────────────────────── /** * Accept both hyphenated (agent-teams) and underscored (agent_teams) feature * names; the canonical YAML keys use underscores. */ function normalizeFeatureKey(input) { return input.trim().toLowerCase().replace(/-/g, '_'); } // ── Detect current provider ──────────────────────────────────────────────────── function normalizeProviderId(provider) { if (!provider) return null; const normalized = provider.trim().toLowerCase(); if (!normalized) return null; if (normalized === 'claude') return 'claude-code'; if (normalized === 'openai') return 'codex'; return normalized; } async function detectProvider(ctx) { if (process.env.CLAUDE_CODE_VERSION || process.env.ANTHROPIC_API_KEY) return 'claude-code'; if (process.env.OPENAI_API_KEY && !process.env.CURSOR_TRACE_ID) return 'codex'; if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_VERSION) return 'cursor'; if (process.env.WINDSURF_VERSION) return 'windsurf'; if (process.env.WARP_SESSION_ID || process.env.WARP_TERMINAL) return 'warp'; if (process.env.COPILOT_AGENT || process.env.GITHUB_COPILOT_TOKEN) return 'copilot'; if (process.env.OPENCLAW_VERSION) return 'openclaw'; if (process.env.FACTORY_AGENT_ID) return 'factory'; if (process.env.OPENCODE_VERSION) return 'opencode'; if (ctx) { try { const cfg = await readAiwgConfig(getProjectDir(ctx, ctx.args)); const configured = normalizeProviderId(cfg?.providers?.[0]); if (configured) return configured; } catch { // Environment detection should stay best-effort. } } return null; } // ── Formatters ──────────────────────────────────────────────────────────────── const NATIVE_MARK = '✓ native'; const EMULATED_MARK = '~ emulated'; const UNSUPPORTED_MARK = '- not supported'; function emulationLabel(strategy) { if (!strategy) return 'none'; if (strategy === 'native') return 'native (no fallback needed)'; return strategy; } function formatProvider(id, provider, matrix) { const lines = []; lines.push(`\n Provider: ${provider.display_name} (${id})`); lines.push(` Status: ${provider.status}`); lines.push(` Daemon: ${provider.daemon_tier}${provider.daemon_pty_adapter ? ' (+ pty-adapter)' : ''}`); lines.push(` ${'─'.repeat(60)}`); const featureKeys = Object.keys(matrix.features); for (const featureId of featureKeys) { const isNative = provider.native_features?.[featureId] === true; const emulation = provider.emulation?.[featureId] ?? null; const status = isNative ? NATIVE_MARK : (emulation ? EMULATED_MARK : UNSUPPORTED_MARK); const feat = matrix.features[featureId]; lines.push(`\n ${featureId}${status}`); if (feat?.description) lines.push(` ${feat.description}`); if (isNative) { if (feat?.native_example) lines.push(` example: ${feat.native_example}`); } else if (emulation) { lines.push(` fallback: ${emulationLabel(emulation)}`); } } return lines.join('\n'); } function printFullMatrix(matrix) { const { providers, features } = matrix; const providerIds = Object.keys(providers); const featureIds = Object.keys(features); console.log(`\n Provider Capability Matrix (v${matrix.version})`); console.log(` ✓ = native ~ = AIWG emulation - = not supported\n`); const featureColW = 20; const provColW = 14; const header = ' ' + 'Feature'.padEnd(featureColW) + providerIds.map(p => p.padEnd(provColW)).join(''); console.log(header); console.log(' ' + '─'.repeat(featureColW + providerIds.length * provColW)); for (const featureId of featureIds) { const feat = features[featureId]; let row = ' ' + featureId.padEnd(featureColW); for (const providerId of providerIds) { const provider = providers[providerId]; const isNative = provider?.native_features?.[featureId] === true; const emulation = provider?.emulation?.[featureId] ?? null; const mark = isNative ? '✓' : (emulation ? '~' : '-'); row += mark.padEnd(provColW); } console.log(row); if (feat?.description) { console.log(' ' + ' '.repeat(featureColW) + feat.description); } console.log(''); } } // ── Main execution ───────────────────────────────────────────────────────────── async function handleSteward(args, ctx) { const subcommand = args[0]; if (!subcommand || subcommand === '--help' || subcommand === 'help') { console.log(` aiwg steward — Provider capability awareness and command routing Usage: aiwg steward capabilities --provider <name> Capabilities for a specific provider aiwg steward capabilities --feature <name> Provider support matrix for a feature aiwg steward capabilities --all Full matrix (all providers x features) aiwg steward find --capability <name> Routing advice for your current provider Providers: claude-code, codex, copilot, cursor, factory, opencode, warp, windsurf, hermes, openclaw Features: cron, agent_teams, tasks, mcp, behaviors, mission_control, daemon (hyphens accepted: agent-teams → agent_teams) `); return; } const matrix = loadCapabilityMatrix(); if (subcommand === 'capabilities') { const providerFlag = args.indexOf('--provider'); const featureFlag = args.indexOf('--feature'); const allFlag = args.includes('--all'); if (allFlag) { printFullMatrix(matrix); return; } if (providerFlag >= 0) { const providerId = normalizeProviderId(args[providerFlag + 1]) ?? ''; if (!providerId) throw new AiwgError({ code: 'ERR_USAGE_MISSING_VALUE', message: '--provider requires a provider name', hint: 'Example: aiwg steward capabilities --provider claude-code', exitCode: EXIT_CODES.USAGE, }); const provider = matrix.providers[providerId]; if (!provider) { const known = Object.keys(matrix.providers).join(', '); throw new AiwgError({ code: 'ERR_USAGE_UNKNOWN_PROVIDER', message: `Unknown provider: ${providerId}`, hint: `Known providers: ${known}`, exitCode: EXIT_CODES.USAGE, }); } console.log(formatProvider(providerId, provider, matrix)); return; } if (featureFlag >= 0) { const rawFeatureId = args[featureFlag + 1]; if (!rawFeatureId) throw new AiwgError({ code: 'ERR_USAGE_MISSING_VALUE', message: '--feature requires a feature name', hint: "Example: aiwg steward capabilities --feature cron", exitCode: EXIT_CODES.USAGE, }); const featureId = normalizeFeatureKey(rawFeatureId); const feat = matrix.features[featureId]; if (!feat) { const known = Object.keys(matrix.features).join(', '); throw new AiwgError({ code: 'ERR_USAGE_UNKNOWN_FEATURE', message: `Unknown feature: ${rawFeatureId}`, hint: `Known features: ${known}`, exitCode: EXIT_CODES.USAGE, }); } console.log(`\n Feature: ${featureId}`); if (feat.description) console.log(` ${feat.description}`); if (feat.native_example) console.log(` Native example: ${feat.native_example}`); console.log(`\n Provider support:\n`); for (const [providerId, provider] of Object.entries(matrix.providers)) { const isNative = provider.native_features?.[featureId] === true; const emulation = provider.emulation?.[featureId] ?? null; const status = isNative ? `✓ native` : (emulation ? `~ emulated (${emulationLabel(emulation)})` : `- not supported`); console.log(` ${provider.display_name.padEnd(20)} (${providerId.padEnd(12)}) ${status}`); } console.log(''); return; } // No flag — show current provider const detected = await detectProvider(ctx); if (!detected) { console.log(` Could not detect active provider. Specify with --provider <name>.`); console.log(` Detection checks environment variables first, then .aiwg/aiwg.config providers[0].`); console.log(` Run 'aiwg runtime-info' to verify provider detection.`); return; } const provider = matrix.providers[detected]; if (!provider) { console.log(` Detected provider ${detected} not found in capability matrix.`); return; } console.log(` (Detected provider: ${detected})`); console.log(formatProvider(detected, provider, matrix)); return; } if (subcommand === 'find') { const capFlag = args.indexOf('--capability'); if (capFlag < 0) throw new AiwgError({ code: 'ERR_USAGE_MISSING_FLAG', message: "'aiwg steward find' requires --capability <name>", hint: 'Example: aiwg steward find --capability cron', exitCode: EXIT_CODES.USAGE, }); const rawCapId = args[capFlag + 1]; if (!rawCapId) throw new AiwgError({ code: 'ERR_USAGE_MISSING_VALUE', message: '--capability requires a feature name', hint: 'Example: aiwg steward find --capability cron', exitCode: EXIT_CODES.USAGE, }); const capabilityId = normalizeFeatureKey(rawCapId); const feat = matrix.features[capabilityId]; if (!feat) { const known = Object.keys(matrix.features).join(', '); throw new AiwgError({ code: 'ERR_USAGE_UNKNOWN_CAPABILITY', message: `Unknown capability: ${rawCapId}`, hint: `Known capabilities: ${known}`, exitCode: EXIT_CODES.USAGE, }); } const detected = await detectProvider(ctx) ?? BASELINE_PROVIDER; const provider = matrix.providers[detected]; console.log(`\n Routing advice for: ${capabilityId}`); console.log(` Provider: ${provider?.display_name ?? detected}`); console.log(''); if (!provider) { console.log(` No capability data for provider ${detected}.`); return; } const isNative = provider.native_features?.[capabilityId] === true; const emulation = provider.emulation?.[capabilityId] ?? null; if (isNative) { console.log(` ✓ Native support available`); if (feat.native_example) console.log(` Example: ${feat.native_example}`); } else if (emulation) { console.log(` ~ Use AIWG emulation`); console.log(` Strategy: ${emulationLabel(emulation)}`); const strategyDetail = feat.emulation_strategies?.[emulation]; if (strategyDetail) console.log(` Detail: ${strategyDetail}`); } else { console.log(` - Not supported on this provider`); const strategies = feat.emulation_strategies ?? {}; const available = Object.keys(strategies); if (available.length) { console.log(` Available strategies in matrix: ${available.join(', ')}`); } } return; } throw new AiwgError({ code: 'ERR_USAGE_UNKNOWN_SUBCOMMAND', message: `Unknown steward subcommand: ${subcommand}`, hint: "Run 'aiwg steward --help' for usage", exitCode: EXIT_CODES.USAGE, }); } // ── Handler export ──────────────────────────────────────────────────────────── export const stewardHandler = { id: 'steward', name: 'Steward', description: 'Provider capability awareness and command routing (capabilities, find)', category: 'maintenance', aliases: [], async execute(ctx) { try { await handleSteward(ctx.args, ctx); return { exitCode: 0 }; } catch (error) { // Preserve AiwgError.exitCode (USAGE=2, etc.) through the catch. return handlerResultFromError(error); } }, }; export const stewardHandlers = [stewardHandler]; //# sourceMappingURL=steward.js.map