The pre-confirmation preview for mutating tools (edit_file/multi_edit/write_file/ bash/git_commit) was rendered as plain monochrome text, throwing away the most useful signal in the thing the user evaluates before approving. - src/utils/diff.ts: looksLikeDiff() — distinguishes a unified diff from a plain preview (bash command, 'Create new file ...', JSON args). - src/ui/ink/DiffView.tsx: renders a unified diff with additions green, removals red, hunk/file headers dimmed. Optional side-by-side mode (pairHunk + clip, both exported for testing). Ported from origin/v0.6.0. - src/ui/theme.ts: DIFF_ADD_HEX / DIFF_REMOVE_HEX. - PermissionPrompt.tsx: use DiffView when the preview is a diff, keep the line-by-line render for non-diff previews. Tests: diff.test.ts (4), DiffView.test.ts (8) for pairHunk/clip/looksLikeDiff. Verified: typecheck clean, build 261.50 KB, 275 tests pass (+12).
66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
import { Box, Text } from "ink";
|
||
import SelectInput from "ink-select-input";
|
||
import type { PermissionDecision } from "../../permissions/types.js";
|
||
import { ACCENT_HEX } from "../theme.js";
|
||
import { DiffView } from "./DiffView.js";
|
||
import { looksLikeDiff } from "../../utils/diff.js";
|
||
|
||
interface Props {
|
||
toolName: string;
|
||
args: unknown;
|
||
preview?: string;
|
||
onSelect: (decision: PermissionDecision) => void;
|
||
}
|
||
|
||
const OPTIONS: Array<{ label: string; value: PermissionDecision }> = [
|
||
{ label: "Yes", value: "once" },
|
||
{ label: "Yes, and don't ask again this session", value: "session" },
|
||
{ label: "No, and tell it what to do differently", value: "deny" },
|
||
];
|
||
|
||
// ink-select-input's defaults highlight the selected row in plain `blue`, which reads as
|
||
// low-contrast/invisible on many terminal themes. Use the app's own accent color instead so the
|
||
// current selection is unmistakable, and bold the label too (color alone isn't enough for
|
||
// terminals with limited color support).
|
||
function PermissionIndicator({ isSelected }: { isSelected?: boolean }) {
|
||
return <Text color={ACCENT_HEX}>{isSelected ? "❯ " : " "}</Text>;
|
||
}
|
||
|
||
function PermissionItem({ isSelected, label }: { isSelected?: boolean; label?: string }) {
|
||
return (
|
||
<Text color={isSelected ? ACCENT_HEX : undefined} bold={isSelected}>
|
||
{label}
|
||
</Text>
|
||
);
|
||
}
|
||
|
||
export function PermissionPrompt({ toolName, args, preview, onSelect }: Props) {
|
||
// Color-code the preview when it is a real diff (additions green, removals red, hunk/file
|
||
// headers dimmed) - the diff is the single most important thing the user evaluates before
|
||
// approving a mutating tool, so rendering it as plain monochrome text throws away signal.
|
||
// Falls back to the old line-by-line render for non-diff previews (a bash command, "Create
|
||
// new file ...", JSON args, etc.).
|
||
const isDiff = looksLikeDiff(preview);
|
||
const previewLines = (preview ?? JSON.stringify(args)).split("\n");
|
||
|
||
return (
|
||
<Box borderStyle="round" borderColor={ACCENT_HEX} flexDirection="column" paddingX={1} width="100%">
|
||
<Text bold>{toolName}</Text>
|
||
<Text> </Text>
|
||
{isDiff && preview ? (
|
||
<DiffView diff={preview} />
|
||
) : (
|
||
previewLines.map((line, i) => <Text key={i}>{line}</Text>)
|
||
)}
|
||
<Text> </Text>
|
||
<Text>Do you want to proceed?</Text>
|
||
<SelectInput
|
||
items={OPTIONS}
|
||
indicatorComponent={PermissionIndicator}
|
||
itemComponent={PermissionItem}
|
||
onSelect={(item) => onSelect(item.value)}
|
||
/>
|
||
</Box>
|
||
);
|
||
}
|