UNPKG

erosolar-cli

Version:

Unified AI agent framework for the command line - Multi-provider support with schema-driven tools, code intelligence, and transparent reasoning

283 lines 11 kB
import { exec } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { promisify } from 'node:util'; const execAsync = promisify(exec); export function createDependencyTools(workingDir) { return [ { name: 'summarize_dependencies', description: 'Summarize dependency counts, categories, and notable packages from package.json.', parameters: { type: 'object', properties: { detail: { type: 'string', enum: ['basic', 'full'], description: 'Detail level for the summary (default: basic).', }, }, additionalProperties: false, }, handler: async (args) => { try { const pkg = readPackageJson(workingDir); if (!pkg) { return 'Error: package.json not found.'; } const detail = args['detail'] === 'full' ? 'full' : 'basic'; return formatDependencySummary(pkg, detail); } catch (error) { return `Error summarizing dependencies: ${error instanceof Error ? error.message : String(error)}`; } }, }, { name: 'scan_dependency_health', description: 'Run npm audit to surface known vulnerabilities (requires npm registry access).', parameters: { type: 'object', properties: { timeout: { type: 'number', description: 'Timeout in milliseconds (default: 180000).', }, }, additionalProperties: false, }, handler: async (args) => { const timeoutArg = args['timeout']; const timeout = typeof timeoutArg === 'number' && Number.isFinite(timeoutArg) && timeoutArg > 0 ? timeoutArg : 180000; try { const { stdout } = await execAsync('npm audit --json', { cwd: workingDir, timeout, maxBuffer: 1024 * 1024 * 15, }); return formatAuditReport(stdout); } catch (error) { if (error.killed) { return `Error: npm audit timed out after ${timeout}ms.`; } const stdout = error.stdout; if (stdout && stdout.trim()) { try { return formatAuditReport(stdout); } catch (parseError) { // fall through to generic error } } return `Error running npm audit: ${error.message}. stderr: ${error.stderr ?? 'none'}`; } }, }, { name: 'inspect_dependency_tree', description: 'Analyze package-lock.json for resolved versions and duplicate dependency instances.', parameters: { type: 'object', properties: {}, additionalProperties: false, }, handler: async () => { try { const pkg = readPackageJson(workingDir); if (!pkg) { return 'Error: package.json not found.'; } const lockPath = join(workingDir, 'package-lock.json'); if (!existsSync(lockPath)) { return 'package-lock.json not found. Run npm install to generate it.'; } const lock = JSON.parse(readFileSync(lockPath, 'utf-8')); return formatLockSummary(pkg, lock); } catch (error) { return `Error inspecting dependency tree: ${error instanceof Error ? error.message : String(error)}`; } }, }, ]; } function readPackageJson(workingDir) { const packageJsonPath = join(workingDir, 'package.json'); if (!existsSync(packageJsonPath)) { return null; } return JSON.parse(readFileSync(packageJsonPath, 'utf-8')); } function formatDependencySummary(pkg, detail) { const deps = Object.entries(pkg.dependencies ?? {}); const devDeps = Object.entries(pkg.devDependencies ?? {}); const optionalDeps = Object.entries(pkg.optionalDependencies ?? {}); const output = []; output.push(`# Dependency summary for ${pkg.name ?? 'package'} v${pkg.version ?? '0.0.0'}`); output.push(''); output.push(`- Production dependencies: ${deps.length}`); output.push(`- Dev dependencies: ${devDeps.length}`); output.push(`- Optional dependencies: ${optionalDeps.length}`); output.push(''); if (detail === 'full') { if (deps.length > 0) { output.push('## Production dependencies'); deps .sort(([a], [b]) => a.localeCompare(b)) .forEach(([name, version]) => { output.push(`- ${name}: ${version}`); }); output.push(''); } if (devDeps.length > 0) { output.push('## Dev dependencies'); devDeps .sort(([a], [b]) => a.localeCompare(b)) .forEach(([name, version]) => { output.push(`- ${name}: ${version}`); }); output.push(''); } if (optionalDeps.length > 0) { output.push('## Optional dependencies'); optionalDeps .sort(([a], [b]) => a.localeCompare(b)) .forEach(([name, version]) => { output.push(`- ${name}: ${version}`); }); output.push(''); } } else { if (deps.length > 0) { output.push('Top production dependencies:'); deps .sort(([a], [b]) => a.localeCompare(b)) .slice(0, 10) .forEach(([name, version]) => { output.push(`- ${name}: ${version}`); }); output.push(''); } } return output.join('\n'); } function formatAuditReport(jsonText) { const report = JSON.parse(jsonText); const metadata = report.metadata ?? {}; const vulnerabilityCounts = metadata.vulnerabilities ?? report.vulnerabilities ?? {}; const output = []; output.push('# npm audit report'); output.push(''); if (Object.keys(vulnerabilityCounts).length === 0) { output.push('No vulnerabilities reported.'); } else { output.push('## Totals by severity'); for (const [severity, count] of Object.entries(vulnerabilityCounts)) { output.push(`- ${severity}: ${count}`); } output.push(''); } const vulnerabilities = report.vulnerabilities ?? report.advisories ?? {}; const entries = Object.entries(vulnerabilities); if (entries.length > 0) { output.push('## Notable vulnerabilities'); entries.slice(0, 10).forEach(([name, info]) => { const data = info; const severity = data.severity ?? data.metadata?.severity ?? 'unknown'; const via = Array.isArray(data.via) ? data.via.map((item) => (typeof item === 'string' ? item : item.title)).join(', ') : ''; output.push(`- ${name}: severity ${severity}${via ? ` (via ${via})` : ''}`); if (data.range) { output.push(` Affected versions: ${data.range}`); } else if (data.vulnerable_versions) { output.push(` Affected versions: ${data.vulnerable_versions}`); } if (data.patch_available || data.fixAvailable) { output.push(` Fix available: ${JSON.stringify(data.patch_available ?? data.fixAvailable)}`); } }); } else { output.push('No detailed vulnerability entries were returned by npm audit.'); } return output.join('\n'); } function formatLockSummary(pkg, lock) { const deps = Object.keys(pkg.dependencies ?? {}); const devDeps = Object.keys(pkg.devDependencies ?? {}); const output = []; output.push(`# Dependency tree (${pkg.name ?? 'package'})`); if (lock.lockfileVersion) { output.push(`Lockfile version: ${lock.lockfileVersion}`); } output.push(''); if (deps.length > 0) { output.push('## Resolved production dependencies'); deps.forEach((dep) => { const version = resolveLockVersion(lock, dep); output.push(`- ${dep}: ${version ?? 'unknown version'}`); }); output.push(''); } if (devDeps.length > 0) { output.push('## Resolved dev dependencies'); devDeps.forEach((dep) => { const version = resolveLockVersion(lock, dep); output.push(`- ${dep}: ${version ?? 'unknown version'}`); }); output.push(''); } const duplicates = detectDuplicateVersions(lock); if (duplicates.length > 0) { output.push('## Duplicate packages detected'); duplicates.forEach(({ name, versions }) => { output.push(`- ${name}: ${Array.from(versions).join(', ')}`); }); } else { output.push('No duplicate package versions detected across the lockfile.'); } return output.join('\n'); } function resolveLockVersion(lock, name) { if (lock.dependencies && lock.dependencies[name]?.version) { return lock.dependencies[name].version ?? null; } if (lock.packages) { const key = name.startsWith('node_modules/') ? name : `node_modules/${name}`; const entry = lock.packages[key]; if (entry?.version) { return entry.version; } } return null; } function detectDuplicateVersions(lock) { const versionMap = new Map(); if (!lock.packages) { return []; } for (const [key, entry] of Object.entries(lock.packages)) { if (!key.startsWith('node_modules/')) { continue; } const name = key.replace(/^node_modules\//, ''); if (!versionMap.has(name)) { versionMap.set(name, new Set()); } if (entry.version) { versionMap.get(name).add(entry.version); } } return [...versionMap.entries()] .filter(([, versions]) => versions.size > 1) .map(([name, versions]) => ({ name, versions })); } //# sourceMappingURL=dependencyTools.js.map