586487d897
Build / bun-build (push) Has been cancelled
Major CLI redesign introducing a mole-style interactive menu with grouped categories. Added new AI-powered subcommands: explain, review, changelog, and suggest. Includes streaming output, pipe support, and updated brand logo. Refactored codebase into command modules for maintainability. Reviewed-on: #6
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
import { loadConfig } from "../config";
|
|
import { isGitRepo, getRecentCommits } from "../git";
|
|
import { CHANGELOG_SYSTEM_PROMPT, buildChangelogPrompt } from "../prompt";
|
|
import { callAI } from "../ai";
|
|
import { BOLD, RED, DIM, RESET, CYAN } from "../terminal";
|
|
import { isStdinTTY } from "../tty";
|
|
import type { StreamCallbacks } from "../types";
|
|
import type { ParsedArgs } from "../cli";
|
|
|
|
export async function handleChangelog(args: ParsedArgs): Promise<number> {
|
|
const config = await loadConfig();
|
|
|
|
if (!config.apiKey) {
|
|
console.error(`\n ${RED()}Error: API key not set. Run ${BOLD()}gai config${RESET()}${RED()} to configure.${RESET()}\n`);
|
|
return 1;
|
|
}
|
|
|
|
if (!(await isGitRepo())) {
|
|
console.error(`\n ${RED()}Error: Not a git repository.${RESET()}\n`);
|
|
return 1;
|
|
}
|
|
|
|
const from = args.flags["from"] as string | undefined;
|
|
const to = args.flags["to"] as string | undefined;
|
|
const countFlag = args.flags["count"] as number | undefined;
|
|
const verbose = args.flags["verbose"] as boolean;
|
|
|
|
// Collect commits
|
|
let commits: string[];
|
|
|
|
if (from) {
|
|
// Range-based: get commits between from..to
|
|
const range = to ? `${from}..${to}` : `${from}..HEAD`;
|
|
try {
|
|
const result = await Bun.$`git log --oneline ${range}`.quiet().text();
|
|
commits = result.trim().split("\n").filter(Boolean);
|
|
} catch {
|
|
console.error(`\n ${RED()}Error: Invalid range: ${range}${RESET()}\n`);
|
|
return 1;
|
|
}
|
|
} else {
|
|
// Count-based
|
|
const count = typeof countFlag === "number" ? countFlag : 20;
|
|
commits = await getRecentCommits(count);
|
|
}
|
|
|
|
if (commits.length === 0) {
|
|
console.log(` ${DIM()}No commits found for the specified range.${RESET()}`);
|
|
return 0;
|
|
}
|
|
|
|
if (verbose) {
|
|
console.log(` ${DIM()}Processing ${commits.length} commits${RESET()}`);
|
|
console.log(` ${DIM()}Model: ${config.model} | API: ${config.apiBase}${RESET()}`);
|
|
}
|
|
|
|
const userPrompt = buildChangelogPrompt(commits, from, to);
|
|
|
|
const tty = isStdinTTY();
|
|
if (tty) {
|
|
console.log(`\n ${BOLD()}${CYAN()}Generating changelog from ${commits.length} commits...${RESET()}\n`);
|
|
}
|
|
|
|
try {
|
|
const callbacks: StreamCallbacks | undefined = tty ? {
|
|
onToken: (token) => process.stdout.write(token),
|
|
} : undefined;
|
|
|
|
const result = await callAI(config, CHANGELOG_SYSTEM_PROMPT, userPrompt, callbacks);
|
|
if (callbacks) {
|
|
process.stdout.write("\n");
|
|
} else {
|
|
process.stdout.write(result + "\n");
|
|
}
|
|
} catch (err) {
|
|
console.error(`\n ${RED()}AI request failed: ${err instanceof Error ? err.message : err}${RESET()}\n`);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|