feat: initial project setup with gai tool

This commit is contained in:
2026-06-09 16:41:02 +08:00
commit a058881b53
17 changed files with 952 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
import type { FileEntry } from "./types";
export async function isGitRepo(): Promise<boolean> {
try {
await Bun.$`git rev-parse --is-inside-work-tree`.quiet();
return true;
} catch {
return false;
}
}
export async function getRepoRoot(): Promise<string> {
const result = await Bun.$`git rev-parse --show-toplevel`.quiet().text();
return result.trim();
}
function statusToLabel(status: string): string {
switch (status[0]) {
case "A":
return "new";
case "D":
return "deleted";
case "R":
return "renamed";
case "C":
return "copied";
case "M":
return "modified";
case "T":
return "type change";
default:
return "modified";
}
}
function parseNameStatus(output: string): FileEntry[] {
return output
.trim()
.split("\n")
.filter(Boolean)
.map((line) => {
const [status, ...pathParts] = line.split("\t");
const path = pathParts[pathParts.length - 1] ?? "";
return { path, status: status!, label: statusToLabel(status!) };
});
}
export async function getStagedFiles(): Promise<FileEntry[]> {
try {
const result =
await Bun.$`git diff --cached --name-status`.quiet().text();
return parseNameStatus(result);
} catch {
return [];
}
}
export async function getUnstagedFiles(): Promise<FileEntry[]> {
const files: FileEntry[] = [];
try {
const result = await Bun.$`git diff --name-status`.quiet().text();
files.push(...parseNameStatus(result));
} catch {}
try {
const result =
await Bun.$`git ls-files --others --exclude-standard`.quiet().text();
for (const path of result.trim().split("\n").filter(Boolean)) {
files.push({ path, status: "??", label: "new" });
}
} catch {}
return files;
}
export async function getStagedDiff(): Promise<string> {
try {
const result = await Bun.$`git diff --staged`.quiet().text();
return result.trim();
} catch {
return "";
}
}
export async function getRecentCommits(count = 10): Promise<string[]> {
try {
const n = String(count);
const result = await Bun.$`git log --oneline -n ${n}`.quiet().text();
return result.trim().split("\n").filter(Boolean);
} catch {
return [];
}
}
export async function stageFiles(paths: string[]): Promise<void> {
if (paths.length === 0) return;
await Bun.$`git add -- ${paths}`;
}
export async function commit(message: string): Promise<void> {
const proc = Bun.spawn(["git", "commit", "-m", message], {
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`git commit failed (exit code ${exitCode})`);
}
}