Files
tkmind_go/ui/text/src/slashCommands.tsx
T
2026-07-23 10:45:58 -05:00

113 lines
2.5 KiB
TypeScript

import { spawnSync } from "node:child_process";
export interface SlashCommandContext {
cwd: string;
}
export type SlashCommandResult =
| { handled: true; message?: string }
| { handled: true; overlay: "diff"; content: string; truncated: boolean }
| { handled: false };
export interface SlashCommand {
name: string;
description: string;
run: (ctx: SlashCommandContext) => SlashCommandResult;
}
function isGitRepo(cwd: string): boolean {
const result = spawnSync(
"git",
[
"-c",
"safe.bareRepository=explicit",
"-c",
"core.fsmonitor=false",
"rev-parse",
"--is-inside-work-tree",
],
{
cwd,
stdio: ["ignore", "ignore", "ignore"],
},
);
return result.status === 0;
}
const MAX_DIFF_BYTES = 2_000_000;
function readDiff(cwd: string): { text: string; truncated: boolean } | null {
const result = spawnSync(
"git",
[
"-c",
"safe.bareRepository=explicit",
"-c",
"core.fsmonitor=false",
"--no-pager",
"diff",
"--no-color",
],
{
cwd,
encoding: "utf8",
maxBuffer: 32 * 1024 * 1024,
},
);
if (result.status !== 0 && result.status !== null) return null;
const stdout = result.stdout ?? "";
if (stdout.length > MAX_DIFF_BYTES) {
return { text: stdout.slice(0, MAX_DIFF_BYTES), truncated: true };
}
return { text: stdout, truncated: false };
}
const diffCommand: SlashCommand = {
name: "diff",
description: "show unstaged changes",
run: (ctx) => {
if (!isGitRepo(ctx.cwd)) {
return {
handled: true,
message: `not a git repository: ${ctx.cwd}`,
};
}
const diff = readDiff(ctx.cwd);
if (diff === null) {
return { handled: true, message: "failed to run `git diff`" };
}
if (diff.text.trim().length === 0) {
return { handled: true, message: "no unstaged changes" };
}
return {
handled: true,
overlay: "diff",
content: diff.text,
truncated: diff.truncated,
};
},
};
const COMMANDS: Record<string, SlashCommand> = {
diff: diffCommand,
};
export function tryRunSlashCommand(
input: string,
ctx: SlashCommandContext,
): SlashCommandResult {
const trimmed = input.trim();
if (!trimmed.startsWith("/")) return { handled: false };
const name = trimmed.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
const cmd = COMMANDS[name];
if (!cmd) return { handled: false };
return cmd.run(ctx);
}
export function listSlashCommands(): SlashCommand[] {
return Object.values(COMMANDS);
}