diff --git a/src/cli.test.ts b/src/cli.test.ts index 0f878f0..3391165 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -197,4 +197,24 @@ describe('run', () => { expect(calls).toHaveLength(0); }); + + it('logs a summary naming every failed tool', async () => { + const logs: string[] = []; + const executor = async (cmd: string) => { + if (cmd === 'claude') throw new Error('[unauthenticated] Error'); + }; + + await run(['claude', 'codex'], executor, allInstalled, undefined, (msg) => logs.push(msg)); + + expect(logs.join('\n')).toContain('claude: Error: [unauthenticated] Error'); + }); + + it('does not log a summary when everything succeeds', async () => { + const logs: string[] = []; + const executor = async () => {}; + + await run(['claude'], executor, allInstalled, undefined, (msg) => logs.push(msg)); + + expect(logs).toHaveLength(0); + }); }); diff --git a/src/cli.ts b/src/cli.ts index e4b31a3..e23319c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -14,6 +14,7 @@ interface Tool { export type Executor = (command: string, args: string[]) => Promise; export type Checker = (command: string) => Promise; export type Versioner = (command: string) => Promise; +export type Logger = (message: string) => void; export const AI_TOOLS: Tool[] = [ { name: 'claude', command: 'claude', args: ['update'], versioned: true }, @@ -74,6 +75,7 @@ export async function run( executor: Executor = async (cmd, args) => { await execa(cmd, args); }, checker: Checker = defaultChecker, versioner: Versioner = getVersion, + logger: Logger = (message) => console.error(message), ): Promise { const { selectedAITools, includeSkills } = selectTools(targetArgs); @@ -83,7 +85,7 @@ export async function run( ); const allTools = checks.filter((c) => c.installed).map((c) => c.tool); - let hasFailures = false; + const failures: { name: string; message: string }[] = []; const runner = new Listr( allTools.map((tool) => ({ @@ -93,9 +95,10 @@ export async function run( try { await executor(tool.command, tool.args); } catch (err) { - hasFailures = true; const e = err as ExecaError; - throw new Error(e.stderr || e.shortMessage || String(e)); + const message = e.stderr || e.shortMessage || String(e); + failures.push({ name: tool.name, message }); + throw new Error(message); } if (before) { const after = await versioner(tool.command); @@ -107,7 +110,16 @@ export async function run( ); await runner.run().catch(() => {}); - return !hasFailures; + + if (failures.length > 0) { + logger(''); + logger(`Errors (${failures.length}):`); + for (const failure of failures) { + logger(` - ${failure.name}: ${failure.message}`); + } + } + + return failures.length === 0; } if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {