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

258 lines 11.6 kB
/** * Runtime Info Command Handler * * Runtime environment discovery and tool catalog management. * Provides information about available tools, system resources, and runtime environment. * * @implements @.aiwg/architecture/decisions/ADR-001-unified-extension-system.md * @source @src/cli/router.ts * @issue #33 */ import path from 'path'; import { AiwgError, EXIT_CODES, handlerResultFromError } from '../errors.js'; /** * Runtime info command handler */ export const runtimeInfoHandler = { id: 'runtime-info', name: 'Runtime Info', description: 'Runtime environment discovery and tool catalog', category: 'toolsmith', aliases: [], async execute(ctx) { try { await handleRuntimeInfo(ctx.args); return { exitCode: 0 }; } catch (error) { const err = error; if (err.message.includes('No catalog found')) { console.log(`\nNo runtime catalog found.`); console.log(`Run 'aiwg runtime-info --discover' to create one.`); return { exitCode: 0 }; } // AiwgError exitCode (USAGE=2, CONFIG=78, etc.) now propagates instead // of being flattened to 1. Non-AiwgError throws still map to GENERAL=1. return handlerResultFromError(err); } }, }; /** * Handle runtime info command logic */ async function handleRuntimeInfo(args) { const hasDiscover = args.includes('--discover'); const hasVerify = args.includes('--verify'); const hasJson = args.includes('--json'); const checkIndex = args.indexOf('--check'); const hasCheck = checkIndex >= 0; const hasCapabilities = args.includes('--capabilities'); const featureIndex = args.indexOf('--feature'); const hasFeature = featureIndex >= 0; // --- Capability matrix queries (no RuntimeDiscovery needed) --- if (hasCapabilities || hasFeature) { const { loadCapabilityMatrix, formatCapabilityTable, formatFeatureSupport, getProviderCapabilities, } = await import('../../providers/capability-matrix.js'); if (hasFeature) { const featureName = args[featureIndex + 1]; if (!featureName) { throw new AiwgError({ code: 'ERR_USAGE_MISSING_VALUE', message: '--feature requires a feature name', hint: 'Known: cron, agent_teams, tasks, mcp, behaviors, mission_control', exitCode: EXIT_CODES.USAGE, }); } if (hasJson) { const matrix = loadCapabilityMatrix(); const featureDef = matrix.features[featureName]; const support = {}; for (const [key, caps] of Object.entries(matrix.providers)) { support[key] = { native: caps.native_features[featureName] ?? false, emulation: caps.emulation[featureName] ?? null, }; } console.log(JSON.stringify({ feature: featureName, definition: featureDef, providers: support }, null, 2)); } else { console.log('\n' + formatFeatureSupport(featureName)); } return; } // --capabilities: show full matrix or provider-specific const providerIndex = args.indexOf('--provider'); if (providerIndex >= 0) { const providerName = args[providerIndex + 1]; if (!providerName) { throw new AiwgError({ code: 'ERR_USAGE_MISSING_VALUE', message: '--provider requires a provider name', hint: 'Example: aiwg runtime-info --capabilities --provider claude', exitCode: EXIT_CODES.USAGE, }); } const caps = getProviderCapabilities(providerName); if (!caps) { throw new AiwgError({ code: 'ERR_USAGE_UNKNOWN_PROVIDER', message: `Unknown provider: ${providerName}`, hint: `Known: ${Object.keys(loadCapabilityMatrix().providers).join(', ')}`, exitCode: EXIT_CODES.USAGE, }); } if (hasJson) { console.log(JSON.stringify({ provider: providerName, ...caps }, null, 2)); } else { console.log(`\nProvider: ${caps.display_name} (${caps.status})`); console.log(`Deploy target: ${caps.deploy_target}`); console.log(`Aggregated output: ${caps.aggregated_output}`); console.log(`\nArtifact paths:`); for (const [type, path] of Object.entries(caps.artifact_paths)) { console.log(` ${type}: ${path ?? '(none)'}`); } console.log(`\nFeatures:`); for (const [feat, native] of Object.entries(caps.native_features)) { const emu = caps.emulation[feat]; console.log(` ${feat}: ${native ? 'NATIVE' : emu ? `emulated (${emu})` : '--'}`); } } return; } // Full matrix if (hasJson) { console.log(JSON.stringify(loadCapabilityMatrix(), null, 2)); } else { console.log('\n' + formatCapabilityTable()); } return; } // --- Original RuntimeDiscovery logic --- const { RuntimeDiscovery } = await import('../../smiths/toolsmith/runtime-discovery.mjs'); const discovery = new RuntimeDiscovery(); if (hasDiscover) { // Full discovery const catalog = await discovery.discover(); if (hasJson) { console.log(JSON.stringify(catalog, null, 2)); } else { console.log(`\nDiscovery complete!`); console.log(`Discovered ${catalog.tools.length} tools`); console.log(`Unavailable: ${catalog.unavailable.length}`); console.log(`\nCatalog saved to: ${path.join(discovery.basePath, 'runtime.json')}`); console.log(`Human-readable: ${path.join(discovery.basePath, 'runtime-info.md')}`); } } else if (hasVerify) { // Verify existing catalog const result = await discovery.verify(); if (hasJson) { console.log(JSON.stringify(result, null, 2)); } else { console.log(`\nVerification complete!`); console.log(`Valid: ${result.valid}/${result.total}`); if (result.failed.length > 0) { console.log(`\nFailed tools:`); result.failed.forEach((tool) => { console.log(` - ${tool.name} (${tool.id})`); }); } } } else if (hasCheck) { // Check specific tool const toolName = args[checkIndex + 1]; if (!toolName) { throw new AiwgError({ code: 'ERR_USAGE_MISSING_VALUE', message: '--check requires a tool name', hint: 'Example: aiwg runtime-info --check git', exitCode: EXIT_CODES.USAGE, }); } const check = await discovery.checkTool(toolName); if (hasJson) { console.log(JSON.stringify(check, null, 2)); } else { console.log(`\nTool: ${toolName}`); if (check.available) { console.log(`Status: Available`); console.log(`Version: ${check.version}`); console.log(`Path: ${check.path}`); console.log(`Verified: ${check.lastVerified}`); } else { console.log(`Status: Not Available`); console.log(`Install: ${check.installHint}`); } } } else { // Show summary (default) const summary = await discovery.getSummary(); if (hasJson) { console.log(JSON.stringify(summary, null, 2)); } else { console.log(`\nRuntime Environment Summary`); console.log(`===========================`); console.log(`\nOS: ${summary.environment.os} (${summary.environment.osVersion}) ${summary.environment.arch}`); console.log(`Shell: ${summary.environment.shell}`); console.log(`Memory: ${summary.resources.memoryAvailableGb} GB available / ${summary.resources.memoryTotalGb} GB total`); console.log(`Disk: ${summary.resources.diskFreeGb} GB free`); console.log(`CPU Cores: ${summary.resources.cpuCores}`); console.log(`\nTool Categories:`); console.log(` Core: ${summary.toolCounts.core} tools`); console.log(` Languages: ${summary.toolCounts.languages} tools`); console.log(` Utilities: ${summary.toolCounts.utilities} tools`); console.log(` Custom: ${summary.toolCounts.custom} tools`); console.log(`\nTotal: ${summary.totalTools} verified tools`); console.log(`\nLast Discovery: ${summary.lastDiscovery}`); console.log(`Catalog: ${summary.catalogPath}`); // Scheduler backend detection const { execSync } = await import('child_process'); let schedulerBackend = 'aiwg-cli (daemon)'; let chronyInstalled = false; try { execSync('which chronyc 2>/dev/null || which chronyd 2>/dev/null', { stdio: 'pipe' }); chronyInstalled = true; } catch { // chrony not found } // Native CronCreate is only available when running inside a Claude Code agent context. // The CLI itself cannot call agent tools, so we report based on platform heuristics. // When invoked from within an agent, the schedule skill performs live detection. const isClaudeCodeContext = process.env.CLAUDE_CODE_VERSION !== undefined || process.env.ANTHROPIC_API_KEY !== undefined; if (isClaudeCodeContext) { schedulerBackend = 'native-cron (CronCreate) / aiwg-cli fallback'; } console.log(`\nScheduler:`); console.log(` Backend: ${schedulerBackend}`); console.log(` Chrony: ${chronyInstalled ? '✓ installed (precise NTP)' : '✗ not installed (install for precise timing)'}`); if (!chronyInstalled) { console.log(`\n To install chrony for more precise cron scheduling:`); console.log(` Ubuntu/Debian: sudo apt install chrony`); console.log(` RHEL/Fedora: sudo dnf install chrony`); console.log(` macOS: brew install chrony`); } // Team backend detection const teamBackend = isClaudeCodeContext ? 'native (Claude Code agent teams)' : 'aiwg mc emulation'; console.log(`\nAgent Teams:`); console.log(` Backend: ${teamBackend}`); if (!isClaudeCodeContext) { console.log(` Note: Run 'aiwg team run <name>' to dispatch via Mission Control`); } console.log(`\nRun 'aiwg runtime-info --discover' to refresh catalog`); console.log(`\nRun 'aiwg runtime-info --check <tool>' to check a specific tool`); } } } //# sourceMappingURL=runtime-info.js.map