fix: fix bugs in core modules

- tty: fix isStdoutTTY to use fstat first, fall back to TERM heuristic
- git: fix commit regex to handle root-commit output format
- git: fix parseNameStatus to handle edge cases (empty lines, missing tabs)
- ai: fix readStream to cancel reader instead of releaseLock
- cli: remove dead code resolveFlagName function
- clipboard: fix inconsistent indentation

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-18 01:53:31 +08:00
parent 586487d897
commit ff4e18ca8b
4 changed files with 44 additions and 33 deletions
+7 -2
View File
@@ -123,7 +123,6 @@ async function readStream(body: ReadableStream<Uint8Array>, callbacks: StreamCal
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
// Keep the last potentially incomplete line
buffer = lines.pop() ?? "";
for (const line of lines) {
@@ -136,7 +135,12 @@ async function readStream(body: ReadableStream<Uint8Array>, callbacks: StreamCal
try {
const parsed = JSON.parse(data) as {
choices?: Array<{ delta?: { content?: string }; finish_reason?: string }>;
error?: { message?: string };
};
if (parsed.error) {
callbacks.onError?.(new Error(`Stream error: ${parsed.error.message ?? "unknown"}`));
continue;
}
const token = parsed.choices?.[0]?.delta?.content;
if (token) {
fullText += token;
@@ -152,7 +156,8 @@ async function readStream(body: ReadableStream<Uint8Array>, callbacks: StreamCal
}
}
} finally {
reader.releaseLock();
try { await reader.cancel(); } catch {}
// releaseLock is not needed after cancel
}
callbacks.onDone?.(fullText);
+23 -21
View File
@@ -1,26 +1,28 @@
export async function copyToClipboard(text: string): Promise<boolean> {
const commands: string[][] = [];
const commands: string[][] = [];
if (process.platform === "darwin") {
commands.push(["pbcopy"]);
} else if (process.platform === "linux") {
commands.push(["xclip", "-selection", "clipboard"]);
commands.push(["xsel", "--clipboard", "--input"]);
}
if (process.platform === "darwin") {
commands.push(["pbcopy"]);
} else if (process.platform === "linux") {
commands.push(["xclip", "-selection", "clipboard"]);
commands.push(["xsel", "--clipboard", "--input"]);
}
for (const cmd of commands) {
try {
const proc = Bun.spawn(cmd, {
stdin: "pipe",
stdout: "ignore",
stderr: "ignore",
});
proc.stdin.write(text);
proc.stdin.end();
const exitCode = await proc.exited;
if (exitCode === 0) return true;
} catch {}
}
for (const cmd of commands) {
try {
const proc = Bun.spawn(cmd, {
stdin: "pipe",
stdout: "ignore",
stderr: "ignore",
});
proc.stdin.write(text);
proc.stdin.end();
const exitCode = await proc.exited;
if (exitCode === 0) return true;
} catch {
// Try next command
}
}
return false;
return false;
}
+11 -6
View File
@@ -37,12 +37,17 @@ function parseNameStatus(output: string): FileEntry[] {
return output
.trim()
.split("\n")
.filter(Boolean)
.filter((line) => line.trim())
.map((line) => {
const [status, ...pathParts] = line.split("\t");
const path = pathParts[pathParts.length - 1] ?? "";
return { path, status: status!, label: statusToLabel(status!) };
});
const tabIdx = line.indexOf("\t");
if (tabIdx === -1) return null;
const status = line.slice(0, tabIdx);
// Join path parts back (paths may contain escaped chars but not tabs)
const path = line.slice(tabIdx + 1);
if (!status || !path) return null;
return { path, status, label: statusToLabel(status) };
})
.filter((entry): entry is FileEntry => entry !== null);
}
export async function getStagedFiles(): Promise<FileEntry[]> {
@@ -140,7 +145,7 @@ export async function commit(
throw new Error(stderr.trim() || `git commit failed (exit code ${exitCode})`);
}
const branchHashMatch = stdout.match(/\[(\S+)\s+([0-9a-f]{7,})/);
const branchHashMatch = stdout.match(/\[(\S+)\s+(?:\(root-commit\)\s+)?([0-9a-f]{7,})/);
const branch = branchHashMatch?.[1] ?? "";
const hash = branchHashMatch?.[2] ?? "";
+3 -4
View File
@@ -23,14 +23,13 @@ export function isStdinTTY(): boolean {
}
export function isStdoutTTY(): boolean {
// Use a heuristic for stdout — check if we're in a terminal
if (process.env.TERM || process.env.TERM_PROGRAM) return true;
if (process.env.NO_COLOR) return false;
// Try fstat on fd 1 (stdout)
// Primary check: fstat on fd 1 (stdout)most reliable
try {
const stat = fstatSync(1);
return stat.isCharacterDevice();
} catch {
// Fall back to TERM heuristic only when fstat fails
if (process.env.TERM || process.env.TERM_PROGRAM) return true;
return false;
}
}