feat: overhaul CLI with new AI commands and mole-style menu #6
@@ -0,0 +1,81 @@
|
|||||||
|
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 = 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { loadConfig } from "../config";
|
||||||
|
import { isGitRepo, getStagedDiff, getUnstagedFiles, getRepoRoot, stageFiles } from "../git";
|
||||||
|
import { selectFiles } from "../selector";
|
||||||
|
import { BACK } from "../menu";
|
||||||
|
import { collectProjectContext } from "../context";
|
||||||
|
import { EXPLAIN_SYSTEM_PROMPT, buildExplainPrompt } from "../prompt";
|
||||||
|
import { callAI } from "../ai";
|
||||||
|
import { BOLD, GREEN, RED, DIM, RESET, CYAN } from "../terminal";
|
||||||
|
import { isStdinTTY } from "../tty";
|
||||||
|
import type { StreamCallbacks } from "../types";
|
||||||
|
import type { ParsedArgs } from "../cli";
|
||||||
|
|
||||||
|
export async function handleExplain(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unstaged = args.flags["unstaged"] as boolean;
|
||||||
|
const staged = args.flags["staged"] as boolean;
|
||||||
|
const verbose = args.flags["verbose"] as boolean;
|
||||||
|
|
||||||
|
// Determine which diff to explain
|
||||||
|
let diff: string;
|
||||||
|
let sourceLabel: string;
|
||||||
|
|
||||||
|
if (unstaged) {
|
||||||
|
if (!(await isGitRepo())) {
|
||||||
|
console.error(`\n ${RED()}Error: Not a git repository.${RESET()}\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
diff = (await Bun.$`git diff`.quiet().text()).trim();
|
||||||
|
} catch {
|
||||||
|
diff = "";
|
||||||
|
}
|
||||||
|
sourceLabel = "unstaged changes";
|
||||||
|
} else {
|
||||||
|
// Default: staged changes (or piped)
|
||||||
|
if (isStdinTTY()) {
|
||||||
|
if (!(await isGitRepo())) {
|
||||||
|
console.error(`\n ${RED()}Error: Not a git repository.${RESET()}\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
diff = await getStagedDiff();
|
||||||
|
sourceLabel = "staged changes";
|
||||||
|
|
||||||
|
// If no staged changes, offer to stage unstaged files
|
||||||
|
if (!diff) {
|
||||||
|
const unstagedFiles = await getUnstagedFiles();
|
||||||
|
if (unstagedFiles.length > 0) {
|
||||||
|
const selected = await selectFiles([], unstagedFiles);
|
||||||
|
if (selected === BACK) return 0;
|
||||||
|
if (selected.length > 0) {
|
||||||
|
await stageFiles(selected);
|
||||||
|
console.log(` ${GREEN()}Staged ${selected.length} file(s).${RESET()}`);
|
||||||
|
diff = await getStagedDiff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Read from pipe
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
for await (const chunk of process.stdin) {
|
||||||
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
||||||
|
}
|
||||||
|
diff = Buffer.concat(chunks).toString("utf-8").trim();
|
||||||
|
sourceLabel = "piped input";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!diff) {
|
||||||
|
console.log(` ${DIM()}No ${sourceLabel} to explain.${RESET()}`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.flags["verbose"]) {
|
||||||
|
console.log(` ${DIM()}Explaining ${sourceLabel} (${diff.length} bytes)${RESET()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_DIFF_SIZE = 15000;
|
||||||
|
const truncatedDiff = diff.length > MAX_DIFF_SIZE
|
||||||
|
? diff.substring(0, MAX_DIFF_SIZE) + "\n... (truncated)"
|
||||||
|
: diff;
|
||||||
|
|
||||||
|
// Collect project context for better explanations
|
||||||
|
let contextPrefix = "";
|
||||||
|
try {
|
||||||
|
if (await isGitRepo()) {
|
||||||
|
const repoRoot = await getRepoRoot();
|
||||||
|
const ctx = await collectProjectContext(repoRoot);
|
||||||
|
if (ctx.packageDescription) {
|
||||||
|
contextPrefix = `Project: ${ctx.packageDescription}\n\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const userPrompt = contextPrefix + buildExplainPrompt(truncatedDiff);
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
console.log(` ${DIM()}Model: ${config.model} | API: ${config.apiBase}${RESET()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tty = isStdinTTY();
|
||||||
|
if (tty) {
|
||||||
|
console.log(`\n ${BOLD()}${CYAN()}Analyzing ${sourceLabel}...${RESET()}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const callbacks: StreamCallbacks = tty ? {
|
||||||
|
onToken: (token) => process.stdout.write(token),
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
const explanation = await callAI(config, EXPLAIN_SYSTEM_PROMPT, userPrompt, callbacks);
|
||||||
|
if (callbacks) {
|
||||||
|
process.stdout.write("\n");
|
||||||
|
} else {
|
||||||
|
// Non-TTY: print the result directly
|
||||||
|
process.stdout.write(explanation + "\n");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`\n ${RED()}AI request failed: ${err instanceof Error ? err.message : err}${RESET()}\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { loadConfig } from "../config";
|
||||||
|
import { isGitRepo, getStagedDiff, getUnstagedFiles, getRepoRoot, stageFiles } from "../git";
|
||||||
|
import { selectFiles } from "../selector";
|
||||||
|
import { BACK } from "../menu";
|
||||||
|
import { collectProjectContext } from "../context";
|
||||||
|
import { REVIEW_SYSTEM_PROMPT, buildReviewPrompt } from "../prompt";
|
||||||
|
import { callAI } from "../ai";
|
||||||
|
import { BOLD, GREEN, YELLOW, RED, DIM, RESET, CYAN } from "../terminal";
|
||||||
|
import { isStdinTTY } from "../tty";
|
||||||
|
import type { StreamCallbacks } from "../types";
|
||||||
|
import type { ParsedArgs } from "../cli";
|
||||||
|
|
||||||
|
export async function handleReview(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strictnessFlag = args.flags["strict"] as boolean
|
||||||
|
? "strict"
|
||||||
|
: args.flags["lenient"] as boolean
|
||||||
|
? "lenient"
|
||||||
|
: "normal";
|
||||||
|
|
||||||
|
const unstaged = args.flags["unstaged"] as boolean;
|
||||||
|
const verbose = args.flags["verbose"] as boolean;
|
||||||
|
|
||||||
|
let diff: string;
|
||||||
|
let sourceLabel: string;
|
||||||
|
|
||||||
|
if (unstaged) {
|
||||||
|
if (!(await isGitRepo())) {
|
||||||
|
console.error(`\n ${RED()}Error: Not a git repository.${RESET()}\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
diff = (await Bun.$`git diff`.quiet().text()).trim();
|
||||||
|
} catch {
|
||||||
|
diff = "";
|
||||||
|
}
|
||||||
|
sourceLabel = "unstaged changes";
|
||||||
|
} else if (!isStdinTTY()) {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
for await (const chunk of process.stdin) {
|
||||||
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
||||||
|
}
|
||||||
|
diff = Buffer.concat(chunks).toString("utf-8").trim();
|
||||||
|
sourceLabel = "piped input";
|
||||||
|
} else {
|
||||||
|
if (!(await isGitRepo())) {
|
||||||
|
console.error(`\n ${RED()}Error: Not a git repository.${RESET()}\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
diff = await getStagedDiff();
|
||||||
|
sourceLabel = "staged changes";
|
||||||
|
|
||||||
|
// If no staged changes, offer to stage unstaged files
|
||||||
|
if (!diff) {
|
||||||
|
const unstagedFiles = await getUnstagedFiles();
|
||||||
|
if (unstagedFiles.length > 0) {
|
||||||
|
const selected = await selectFiles([], unstagedFiles);
|
||||||
|
if (selected === BACK) return 0;
|
||||||
|
if (selected.length > 0) {
|
||||||
|
await stageFiles(selected);
|
||||||
|
console.log(` ${GREEN()}Staged ${selected.length} file(s).${RESET()}`);
|
||||||
|
diff = await getStagedDiff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!diff) {
|
||||||
|
console.log(` ${DIM()}No ${sourceLabel} to review.${RESET()}`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_DIFF_SIZE = 15000;
|
||||||
|
const truncatedDiff = diff.length > MAX_DIFF_SIZE
|
||||||
|
? diff.substring(0, MAX_DIFF_SIZE) + "\n... (truncated)"
|
||||||
|
: diff;
|
||||||
|
|
||||||
|
let contextPrefix = "";
|
||||||
|
try {
|
||||||
|
if (await isGitRepo()) {
|
||||||
|
const repoRoot = await getRepoRoot();
|
||||||
|
const ctx = await collectProjectContext(repoRoot);
|
||||||
|
if (ctx.packageDescription) {
|
||||||
|
contextPrefix = `Project: ${ctx.packageDescription}\n\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const userPrompt = contextPrefix + buildReviewPrompt(truncatedDiff, strictnessFlag);
|
||||||
|
|
||||||
|
const strictnessLabel = strictnessFlag === "strict"
|
||||||
|
? `${RED()}strict${RESET()}`
|
||||||
|
: strictnessFlag === "lenient"
|
||||||
|
? `${GREEN()}lenient${RESET()}`
|
||||||
|
: `${YELLOW()}normal${RESET()}`;
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
console.log(` ${DIM()}Model: ${config.model} | API: ${config.apiBase} | Strictness: ${strictnessFlag}${RESET()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tty = isStdinTTY();
|
||||||
|
if (tty) {
|
||||||
|
console.log(`\n ${BOLD()}${CYAN()}Reviewing ${sourceLabel} (${strictnessLabel})...${RESET()}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const callbacks: StreamCallbacks = tty ? {
|
||||||
|
onToken: (token) => process.stdout.write(token),
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
const result = await callAI(config, REVIEW_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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import { loadConfig } from "../config";
|
||||||
|
import { isGitRepo, getStagedDiff, getUnstagedFiles, stageFiles } from "../git";
|
||||||
|
import { selectFiles } from "../selector";
|
||||||
|
import { BACK } from "../menu";
|
||||||
|
import {
|
||||||
|
SUGGEST_SYSTEM_PROMPT,
|
||||||
|
buildSuggestBranchPrompt,
|
||||||
|
buildSuggestTypePrompt,
|
||||||
|
} from "../prompt";
|
||||||
|
import { callAI } from "../ai";
|
||||||
|
import { BOLD, GREEN, YELLOW, RED, DIM, RESET, CYAN } from "../terminal";
|
||||||
|
import { isStdinTTY } from "../tty";
|
||||||
|
import type { Config } from "../types";
|
||||||
|
import type { ParsedArgs } from "../cli";
|
||||||
|
|
||||||
|
export async function handleSuggest(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = args.positional[0] || "branch";
|
||||||
|
const verbose = args.flags["verbose"] as boolean;
|
||||||
|
|
||||||
|
if (mode !== "branch" && mode !== "type") {
|
||||||
|
console.error(`\n ${RED()}Error: Unknown suggest mode: ${mode}${RESET()}`);
|
||||||
|
console.error(` Try: gai suggest branch | gai suggest type\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get diff (staged, or unstaged if --unstaged, or piped)
|
||||||
|
let diff: string;
|
||||||
|
if (!isStdinTTY()) {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
for await (const chunk of process.stdin) {
|
||||||
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
||||||
|
}
|
||||||
|
diff = Buffer.concat(chunks).toString("utf-8").trim();
|
||||||
|
} else {
|
||||||
|
if (!(await isGitRepo())) {
|
||||||
|
console.error(`\n ${RED()}Error: Not a git repository.${RESET()}\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.flags["unstaged"] as boolean) {
|
||||||
|
try {
|
||||||
|
diff = (await Bun.$`git diff`.quiet().text()).trim();
|
||||||
|
} catch {
|
||||||
|
diff = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
diff = await getStagedDiff();
|
||||||
|
|
||||||
|
// If no staged changes, offer to stage unstaged files
|
||||||
|
if (!diff) {
|
||||||
|
const unstagedFiles = await getUnstagedFiles();
|
||||||
|
if (unstagedFiles.length > 0) {
|
||||||
|
const selected = await selectFiles([], unstagedFiles);
|
||||||
|
if (selected === BACK) return 0;
|
||||||
|
if (selected.length > 0) {
|
||||||
|
await stageFiles(selected);
|
||||||
|
console.log(` ${GREEN()}Staged ${selected.length} file(s).${RESET()}`);
|
||||||
|
diff = await getStagedDiff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!diff) {
|
||||||
|
console.log(` ${DIM()}No changes to suggest from.${RESET()}`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_DIFF_SIZE = 15000;
|
||||||
|
const truncatedDiff = diff.length > MAX_DIFF_SIZE
|
||||||
|
? diff.substring(0, MAX_DIFF_SIZE) + "\n... (truncated)"
|
||||||
|
: diff;
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
console.log(` ${DIM()}Model: ${config.model} | API: ${config.apiBase}${RESET()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "branch") {
|
||||||
|
return handleSuggestBranch(config, truncatedDiff);
|
||||||
|
} else {
|
||||||
|
return handleSuggestType(config, truncatedDiff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSuggestBranch(config: Config, diff: string): Promise<number> {
|
||||||
|
const tty = isStdinTTY();
|
||||||
|
if (tty) {
|
||||||
|
console.log(`\n ${BOLD()}${CYAN()}Suggesting branch names...${RESET()}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw = await callAI(config, SUGGEST_SYSTEM_PROMPT, buildSuggestBranchPrompt(diff));
|
||||||
|
const suggestions = raw
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.replace(/^[\d.\s\-*]+/, "").trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (suggestions.length === 0) {
|
||||||
|
console.log(` ${DIM()}No suggestions generated.${RESET()}`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const s of suggestions) {
|
||||||
|
console.log(` ${GREEN()}${s}${RESET()}`);
|
||||||
|
}
|
||||||
|
console.log("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`\n ${RED()}AI request failed: ${err instanceof Error ? err.message : err}${RESET()}\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSuggestType(config: Config, diff: string): Promise<number> {
|
||||||
|
const tty = isStdinTTY();
|
||||||
|
if (tty) {
|
||||||
|
console.log(`\n ${BOLD()}${CYAN()}Suggesting commit type...${RESET()}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw = await callAI(config, SUGGEST_SYSTEM_PROMPT, buildSuggestTypePrompt(diff));
|
||||||
|
const type = raw.trim().toLowerCase();
|
||||||
|
const validTypes = ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"];
|
||||||
|
|
||||||
|
if (validTypes.includes(type)) {
|
||||||
|
console.log(` Suggested type: ${GREEN()}${BOLD()}${type}${RESET()}\n`);
|
||||||
|
} else {
|
||||||
|
console.log(` Suggested type: ${YELLOW()}${raw.trim()}${RESET()}`);
|
||||||
|
console.log(` ${DIM()}(Not a standard Conventional Commit type)${RESET()}\n`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`\n ${RED()}AI request failed: ${err instanceof Error ? err.message : err}${RESET()}\n`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user