Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
20 changes: 16 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Tool {
export type Executor = (command: string, args: string[]) => Promise<void>;
export type Checker = (command: string) => Promise<boolean>;
export type Versioner = (command: string) => Promise<string | null>;
export type Logger = (message: string) => void;

export const AI_TOOLS: Tool[] = [
{ name: 'claude', command: 'claude', args: ['update'], versioned: true },
Expand Down Expand Up @@ -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<boolean> {
const { selectedAITools, includeSkills } = selectTools(targetArgs);

Expand All @@ -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) => ({
Expand All @@ -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);
Expand All @@ -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))) {
Expand Down