@ridit/lens 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LENS.md +32 -68
- package/README.md +91 -0
- package/dist/index.mjs +69211 -2205
- package/package.json +6 -3
- package/src/commands/commit.tsx +713 -0
- package/src/components/chat/ChatOverlays.tsx +14 -4
- package/src/components/chat/ChatRunner.tsx +197 -30
- package/src/components/timeline/CommitDetail.tsx +2 -4
- package/src/components/timeline/CommitList.tsx +2 -14
- package/src/components/timeline/TimelineChat.tsx +1 -2
- package/src/components/timeline/TimelineRunner.tsx +505 -422
- package/src/index.tsx +38 -0
- package/src/prompts/fewshot.ts +100 -3
- package/src/prompts/system.ts +16 -20
- package/src/tools/chart.ts +210 -0
- package/src/tools/convert-image.ts +312 -0
- package/src/tools/files.ts +1 -9
- package/src/tools/git.ts +577 -0
- package/src/tools/index.ts +17 -13
- package/src/tools/view-image.ts +335 -0
- package/src/tools/web.ts +0 -4
- package/src/utils/chat.ts +7 -17
- package/src/utils/thinking.tsx +275 -162
- package/src/utils/tools/builtins.ts +8 -31
|
@@ -186,15 +186,25 @@ export function TypewriterText({
|
|
|
186
186
|
return <Text color={color}>{displayed}</Text>;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
export function ShortcutBar({
|
|
189
|
+
export function ShortcutBar({
|
|
190
|
+
autoApprove,
|
|
191
|
+
forceApprove,
|
|
192
|
+
}: {
|
|
193
|
+
autoApprove?: boolean;
|
|
194
|
+
forceApprove?: boolean;
|
|
195
|
+
}) {
|
|
190
196
|
return (
|
|
191
197
|
<Box gap={3} marginTop={0}>
|
|
192
198
|
<Text color="gray" dimColor>
|
|
193
199
|
enter send · ^v paste · ^c exit
|
|
194
200
|
</Text>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
201
|
+
{forceApprove ? (
|
|
202
|
+
<Text color="red">⚡⚡ force-all</Text>
|
|
203
|
+
) : (
|
|
204
|
+
<Text color={autoApprove ? "green" : "gray"} dimColor={!autoApprove}>
|
|
205
|
+
{autoApprove ? "⚡ auto" : "/auto"}
|
|
206
|
+
</Text>
|
|
207
|
+
)}
|
|
198
208
|
</Box>
|
|
199
209
|
);
|
|
200
210
|
}
|
|
@@ -62,6 +62,10 @@ const COMMANDS = [
|
|
|
62
62
|
{ cmd: "/clear history", desc: "wipe session memory for this repo" },
|
|
63
63
|
{ cmd: "/review", desc: "review current codebase" },
|
|
64
64
|
{ cmd: "/auto", desc: "toggle auto-approve for read/search tools" },
|
|
65
|
+
{
|
|
66
|
+
cmd: "/auto --force-all",
|
|
67
|
+
desc: "auto-approve ALL tools including shell and writes (⚠ dangerous)",
|
|
68
|
+
},
|
|
65
69
|
{ cmd: "/chat", desc: "chat history commands" },
|
|
66
70
|
{ cmd: "/chat list", desc: "list saved chats for this repo" },
|
|
67
71
|
{ cmd: "/chat load", desc: "load a saved chat by name" },
|
|
@@ -128,6 +132,71 @@ function CommandPalette({
|
|
|
128
132
|
);
|
|
129
133
|
}
|
|
130
134
|
|
|
135
|
+
function ForceAllWarning({
|
|
136
|
+
onConfirm,
|
|
137
|
+
}: {
|
|
138
|
+
onConfirm: (confirmed: boolean) => void;
|
|
139
|
+
}) {
|
|
140
|
+
const [input, setInput] = useState("");
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Box flexDirection="column" marginY={1} gap={1}>
|
|
144
|
+
<Box gap={1}>
|
|
145
|
+
<Text color="red" bold>
|
|
146
|
+
⚠ WARNING
|
|
147
|
+
</Text>
|
|
148
|
+
</Box>
|
|
149
|
+
<Box flexDirection="column" marginLeft={2} gap={1}>
|
|
150
|
+
<Text color="yellow">
|
|
151
|
+
Force-all mode auto-approves EVERY tool without asking — including:
|
|
152
|
+
</Text>
|
|
153
|
+
<Text color="red" dimColor>
|
|
154
|
+
{" "}
|
|
155
|
+
· shell commands (rm, git, npm, anything)
|
|
156
|
+
</Text>
|
|
157
|
+
<Text color="red" dimColor>
|
|
158
|
+
{" "}
|
|
159
|
+
· file writes and deletes
|
|
160
|
+
</Text>
|
|
161
|
+
<Text color="red" dimColor>
|
|
162
|
+
{" "}
|
|
163
|
+
· folder deletes
|
|
164
|
+
</Text>
|
|
165
|
+
<Text color="red" dimColor>
|
|
166
|
+
{" "}
|
|
167
|
+
· external fetches and URL opens
|
|
168
|
+
</Text>
|
|
169
|
+
<Text color="yellow" dimColor>
|
|
170
|
+
The AI can modify or delete files without any confirmation.
|
|
171
|
+
</Text>
|
|
172
|
+
<Text color="yellow" dimColor>
|
|
173
|
+
Only use this in throwaway environments or when you fully trust the
|
|
174
|
+
task.
|
|
175
|
+
</Text>
|
|
176
|
+
</Box>
|
|
177
|
+
<Box gap={1} marginTop={1}>
|
|
178
|
+
<Text color="gray">Type </Text>
|
|
179
|
+
<Text color="white" bold>
|
|
180
|
+
yes
|
|
181
|
+
</Text>
|
|
182
|
+
<Text color="gray"> to enable, or press </Text>
|
|
183
|
+
<Text color="white" bold>
|
|
184
|
+
esc
|
|
185
|
+
</Text>
|
|
186
|
+
<Text color="gray"> to cancel: </Text>
|
|
187
|
+
<TextInput
|
|
188
|
+
value={input}
|
|
189
|
+
onChange={setInput}
|
|
190
|
+
onSubmit={(v) => onConfirm(v.trim().toLowerCase() === "yes")}
|
|
191
|
+
placeholder="yes / esc to cancel"
|
|
192
|
+
/>
|
|
193
|
+
</Box>
|
|
194
|
+
</Box>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
import TextInput from "ink-text-input";
|
|
199
|
+
|
|
131
200
|
export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
132
201
|
const [stage, setStage] = useState<ChatStage>({ type: "picking-provider" });
|
|
133
202
|
const [committed, setCommitted] = useState<Message[]>([]);
|
|
@@ -140,6 +209,8 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
140
209
|
const [showTimeline, setShowTimeline] = useState(false);
|
|
141
210
|
const [showReview, setShowReview] = useState(false);
|
|
142
211
|
const [autoApprove, setAutoApprove] = useState(false);
|
|
212
|
+
const [forceApprove, setForceApprove] = useState(false);
|
|
213
|
+
const [showForceWarning, setShowForceWarning] = useState(false);
|
|
143
214
|
const [chatName, setChatName] = useState<string | null>(null);
|
|
144
215
|
const chatNameRef = useRef<string | null>(null);
|
|
145
216
|
const [recentChats, setRecentChats] = useState<string[]>([]);
|
|
@@ -154,11 +225,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
154
225
|
|
|
155
226
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
156
227
|
const toolResultCache = useRef<Map<string, string>>(new Map());
|
|
157
|
-
|
|
158
|
-
// When the user approves a tool that has chained remainder calls, we
|
|
159
|
-
// automatically approve subsequent tools in the same chain so the user
|
|
160
|
-
// doesn't have to press y for every file in a 10-file scaffold.
|
|
161
|
-
// This ref is set to true on the first approval and cleared when the chain ends.
|
|
162
228
|
const batchApprovedRef = useRef(false);
|
|
163
229
|
|
|
164
230
|
const thinkingPhrase = useThinkingPhrase(stage.type === "thinking");
|
|
@@ -190,6 +256,28 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
190
256
|
setStage({ type: "idle" });
|
|
191
257
|
};
|
|
192
258
|
|
|
259
|
+
const TOOL_TAG_NAMES = [
|
|
260
|
+
"shell",
|
|
261
|
+
"fetch",
|
|
262
|
+
"read-file",
|
|
263
|
+
"read-folder",
|
|
264
|
+
"grep",
|
|
265
|
+
"write-file",
|
|
266
|
+
"delete-file",
|
|
267
|
+
"delete-folder",
|
|
268
|
+
"open-url",
|
|
269
|
+
"generate-pdf",
|
|
270
|
+
"search",
|
|
271
|
+
"clone",
|
|
272
|
+
"changes",
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
function isLikelyTruncated(text: string): boolean {
|
|
276
|
+
return TOOL_TAG_NAMES.some(
|
|
277
|
+
(tag) => text.includes(`<${tag}>`) && !text.includes(`</${tag}>`),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
193
281
|
const processResponse = (
|
|
194
282
|
raw: string,
|
|
195
283
|
currentAll: Message[],
|
|
@@ -201,7 +289,20 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
201
289
|
return;
|
|
202
290
|
}
|
|
203
291
|
|
|
204
|
-
//
|
|
292
|
+
// Guard: response cut off mid-tool-tag (context limit hit during generation)
|
|
293
|
+
if (isLikelyTruncated(raw)) {
|
|
294
|
+
const truncMsg: Message = {
|
|
295
|
+
role: "assistant",
|
|
296
|
+
content:
|
|
297
|
+
"(response cut off — the model hit its output limit mid-tool-call. Try asking it to continue, or simplify the request.)",
|
|
298
|
+
type: "text",
|
|
299
|
+
};
|
|
300
|
+
setAllMessages([...currentAll, truncMsg]);
|
|
301
|
+
setCommitted((prev) => [...prev, truncMsg]);
|
|
302
|
+
setStage({ type: "idle" });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
205
306
|
const memAddMatches = [
|
|
206
307
|
...raw.matchAll(/<memory-add>([\s\S]*?)<\/memory-add>/g),
|
|
207
308
|
];
|
|
@@ -223,8 +324,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
223
324
|
|
|
224
325
|
const parsed = parseResponse(cleanRaw);
|
|
225
326
|
|
|
226
|
-
// ── changes (diff preview UI) ──────────────────────────────────────────
|
|
227
|
-
|
|
228
327
|
if (parsed.kind === "changes") {
|
|
229
328
|
batchApprovedRef.current = false;
|
|
230
329
|
if (parsed.patches.length === 0) {
|
|
@@ -259,8 +358,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
259
358
|
return;
|
|
260
359
|
}
|
|
261
360
|
|
|
262
|
-
// ── clone (git clone UI flow) ──────────────────────────────────────────
|
|
263
|
-
|
|
264
361
|
if (parsed.kind === "clone") {
|
|
265
362
|
batchApprovedRef.current = false;
|
|
266
363
|
if (parsed.content) {
|
|
@@ -280,10 +377,22 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
280
377
|
return;
|
|
281
378
|
}
|
|
282
379
|
|
|
283
|
-
// ── text ──────────────────────────────────────────────────────────────
|
|
284
|
-
|
|
285
380
|
if (parsed.kind === "text") {
|
|
286
381
|
batchApprovedRef.current = false;
|
|
382
|
+
|
|
383
|
+
if (!parsed.content.trim()) {
|
|
384
|
+
const stallMsg: Message = {
|
|
385
|
+
role: "assistant",
|
|
386
|
+
content:
|
|
387
|
+
'(no response — the model may have stalled. Try sending a short follow-up like "continue" or start a new message.)',
|
|
388
|
+
type: "text",
|
|
389
|
+
};
|
|
390
|
+
setAllMessages([...currentAll, stallMsg]);
|
|
391
|
+
setCommitted((prev) => [...prev, stallMsg]);
|
|
392
|
+
setStage({ type: "idle" });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
287
396
|
const msg: Message = {
|
|
288
397
|
role: "assistant",
|
|
289
398
|
content: parsed.content,
|
|
@@ -309,8 +418,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
309
418
|
return;
|
|
310
419
|
}
|
|
311
420
|
|
|
312
|
-
// ── generic tool ──────────────────────────────────────────────────────
|
|
313
|
-
|
|
314
421
|
const tool = registry.get(parsed.toolName);
|
|
315
422
|
if (!tool) {
|
|
316
423
|
batchApprovedRef.current = false;
|
|
@@ -332,8 +439,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
332
439
|
const isSafe = tool.safe ?? false;
|
|
333
440
|
|
|
334
441
|
const executeAndContinue = async (approved: boolean) => {
|
|
335
|
-
// If the user approved this tool and there are more in the chain,
|
|
336
|
-
// mark the batch as approved so subsequent tools skip the prompt.
|
|
337
442
|
if (approved && remainder) {
|
|
338
443
|
batchApprovedRef.current = true;
|
|
339
444
|
}
|
|
@@ -372,7 +477,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
372
477
|
? String(tool.summariseInput(parsed.input))
|
|
373
478
|
: parsed.rawInput,
|
|
374
479
|
summary: result.split("\n")[0]?.slice(0, 120) ?? "",
|
|
375
|
-
repoPath,
|
|
376
480
|
});
|
|
377
481
|
}
|
|
378
482
|
|
|
@@ -393,13 +497,11 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
393
497
|
setAllMessages(withTool);
|
|
394
498
|
setCommitted((prev) => [...prev, toolMsg]);
|
|
395
499
|
|
|
396
|
-
// Chain: process remainder immediately, no API round-trip needed.
|
|
397
500
|
if (approved && remainder && remainder.length > 0) {
|
|
398
501
|
processResponse(remainder, withTool, signal);
|
|
399
502
|
return;
|
|
400
503
|
}
|
|
401
504
|
|
|
402
|
-
// Chain ended (or was never chained) — clear batch approval.
|
|
403
505
|
batchApprovedRef.current = false;
|
|
404
506
|
|
|
405
507
|
const nextAbort = new AbortController();
|
|
@@ -410,9 +512,7 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
410
512
|
.catch(handleError(withTool));
|
|
411
513
|
};
|
|
412
514
|
|
|
413
|
-
|
|
414
|
-
// already inside a user-approved batch chain.
|
|
415
|
-
if ((autoApprove && isSafe) || batchApprovedRef.current) {
|
|
515
|
+
if (forceApprove || (autoApprove && isSafe) || batchApprovedRef.current) {
|
|
416
516
|
executeAndContinue(true);
|
|
417
517
|
return;
|
|
418
518
|
}
|
|
@@ -446,7 +546,41 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
446
546
|
return;
|
|
447
547
|
}
|
|
448
548
|
|
|
549
|
+
// /auto --force-all — show warning first
|
|
550
|
+
if (text.trim().toLowerCase() === "/auto --force-all") {
|
|
551
|
+
if (forceApprove) {
|
|
552
|
+
// Toggle off immediately, no warning needed
|
|
553
|
+
setForceApprove(false);
|
|
554
|
+
setAutoApprove(false);
|
|
555
|
+
const msg: Message = {
|
|
556
|
+
role: "assistant",
|
|
557
|
+
content: "Force-all mode OFF — tools will ask for permission again.",
|
|
558
|
+
type: "text",
|
|
559
|
+
};
|
|
560
|
+
setCommitted((prev) => [...prev, msg]);
|
|
561
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
562
|
+
} else {
|
|
563
|
+
setShowForceWarning(true);
|
|
564
|
+
}
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
449
568
|
if (text.trim().toLowerCase() === "/auto") {
|
|
569
|
+
// /auto never enables force-all, only toggles safe auto-approve
|
|
570
|
+
if (forceApprove) {
|
|
571
|
+
// Step down from force-all to normal auto
|
|
572
|
+
setForceApprove(false);
|
|
573
|
+
setAutoApprove(true);
|
|
574
|
+
const msg: Message = {
|
|
575
|
+
role: "assistant",
|
|
576
|
+
content:
|
|
577
|
+
"Force-all mode OFF — switched to normal auto-approve (safe tools only).",
|
|
578
|
+
type: "text",
|
|
579
|
+
};
|
|
580
|
+
setCommitted((prev) => [...prev, msg]);
|
|
581
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
450
584
|
const next = !autoApprove;
|
|
451
585
|
setAutoApprove(next);
|
|
452
586
|
const msg: Message = {
|
|
@@ -696,7 +830,8 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
696
830
|
const nextAll = [...allMessages, userMsg];
|
|
697
831
|
setCommitted((prev) => [...prev, userMsg]);
|
|
698
832
|
setAllMessages(nextAll);
|
|
699
|
-
toolResultCache
|
|
833
|
+
// Do NOT clear toolResultCache here — safe tool results (read-file, read-folder, grep)
|
|
834
|
+
// persist across the whole session so the model never re-reads the same resource twice.
|
|
700
835
|
batchApprovedRef.current = false;
|
|
701
836
|
|
|
702
837
|
inputHistoryRef.current = [
|
|
@@ -728,6 +863,12 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
728
863
|
useInput((input, key) => {
|
|
729
864
|
if (showTimeline) return;
|
|
730
865
|
|
|
866
|
+
// Esc cancels the force-all warning
|
|
867
|
+
if (showForceWarning && key.escape) {
|
|
868
|
+
setShowForceWarning(false);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
|
|
731
872
|
if (stage.type === "thinking" && key.escape) {
|
|
732
873
|
abortControllerRef.current?.abort();
|
|
733
874
|
abortControllerRef.current = null;
|
|
@@ -786,7 +927,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
786
927
|
kind: "url-fetched",
|
|
787
928
|
detail: repoUrl,
|
|
788
929
|
summary: `Cloned ${repoName} — ${fileCount} files`,
|
|
789
|
-
repoPath,
|
|
790
930
|
});
|
|
791
931
|
setClonedUrls((prev) => new Set([...prev, repoUrl]));
|
|
792
932
|
setStage({
|
|
@@ -924,7 +1064,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
924
1064
|
.map((p: { path: string }) => p.path)
|
|
925
1065
|
.join(", "),
|
|
926
1066
|
summary: `Skipped changes to ${msg.patches.length} file(s)`,
|
|
927
|
-
repoPath,
|
|
928
1067
|
});
|
|
929
1068
|
}
|
|
930
1069
|
}
|
|
@@ -939,7 +1078,6 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
939
1078
|
kind: "code-applied",
|
|
940
1079
|
detail: stage.patches.map((p) => p.path).join(", "),
|
|
941
1080
|
summary: `Applied changes to ${stage.patches.length} file(s)`,
|
|
942
|
-
repoPath,
|
|
943
1081
|
});
|
|
944
1082
|
} catch {
|
|
945
1083
|
/* non-fatal */
|
|
@@ -1054,7 +1192,36 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
1054
1192
|
{(msg, i) => <StaticMessage key={i} msg={msg} />}
|
|
1055
1193
|
</Static>
|
|
1056
1194
|
|
|
1057
|
-
{
|
|
1195
|
+
{/* Force-all warning overlay */}
|
|
1196
|
+
{showForceWarning && (
|
|
1197
|
+
<ForceAllWarning
|
|
1198
|
+
onConfirm={(confirmed) => {
|
|
1199
|
+
setShowForceWarning(false);
|
|
1200
|
+
if (confirmed) {
|
|
1201
|
+
setForceApprove(true);
|
|
1202
|
+
setAutoApprove(true);
|
|
1203
|
+
const msg: Message = {
|
|
1204
|
+
role: "assistant",
|
|
1205
|
+
content:
|
|
1206
|
+
"⚡⚡ Force-all mode ON — ALL tools auto-approved including shell and writes. Type /auto --force-all again to disable.",
|
|
1207
|
+
type: "text",
|
|
1208
|
+
};
|
|
1209
|
+
setCommitted((prev) => [...prev, msg]);
|
|
1210
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
1211
|
+
} else {
|
|
1212
|
+
const msg: Message = {
|
|
1213
|
+
role: "assistant",
|
|
1214
|
+
content: "Force-all cancelled.",
|
|
1215
|
+
type: "text",
|
|
1216
|
+
};
|
|
1217
|
+
setCommitted((prev) => [...prev, msg]);
|
|
1218
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
1219
|
+
}
|
|
1220
|
+
}}
|
|
1221
|
+
/>
|
|
1222
|
+
)}
|
|
1223
|
+
|
|
1224
|
+
{!showForceWarning && stage.type === "thinking" && (
|
|
1058
1225
|
<Box gap={1}>
|
|
1059
1226
|
<Text color={ACCENT}>●</Text>
|
|
1060
1227
|
<TypewriterText text={thinkingPhrase} />
|
|
@@ -1064,11 +1231,11 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
1064
1231
|
</Box>
|
|
1065
1232
|
)}
|
|
1066
1233
|
|
|
1067
|
-
{stage.type === "permission" && (
|
|
1234
|
+
{!showForceWarning && stage.type === "permission" && (
|
|
1068
1235
|
<PermissionPrompt tool={stage.tool} onDecide={stage.resolve} />
|
|
1069
1236
|
)}
|
|
1070
1237
|
|
|
1071
|
-
{stage.type === "idle" && (
|
|
1238
|
+
{!showForceWarning && stage.type === "idle" && (
|
|
1072
1239
|
<Box flexDirection="column">
|
|
1073
1240
|
{inputValue.startsWith("/") && (
|
|
1074
1241
|
<CommandPalette
|
|
@@ -1089,7 +1256,7 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
1089
1256
|
}}
|
|
1090
1257
|
inputKey={inputKey}
|
|
1091
1258
|
/>
|
|
1092
|
-
<ShortcutBar autoApprove={autoApprove} />
|
|
1259
|
+
<ShortcutBar autoApprove={autoApprove} forceApprove={forceApprove} />
|
|
1093
1260
|
</Box>
|
|
1094
1261
|
)}
|
|
1095
1262
|
</Box>
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import type { Commit, DiffFile } from "../../utils/git";
|
|
4
|
-
|
|
5
|
-
const ACCENT = "#FF8C00";
|
|
4
|
+
import { ACCENT } from "../../colors";
|
|
6
5
|
|
|
7
6
|
type Props = {
|
|
8
7
|
commit: Commit | null;
|
|
@@ -73,7 +72,6 @@ export function CommitDetail({
|
|
|
73
72
|
|
|
74
73
|
const divider = "─".repeat(Math.max(0, width - 2));
|
|
75
74
|
|
|
76
|
-
// Build all diff lines for scrolling
|
|
77
75
|
const allDiffLines: Array<{
|
|
78
76
|
type: string;
|
|
79
77
|
content: string;
|
|
@@ -177,7 +175,7 @@ export function CommitDetail({
|
|
|
177
175
|
{/* stats bar */}
|
|
178
176
|
<Box paddingX={1} marginTop={1} gap={3}>
|
|
179
177
|
<Text color="green">+{commit.insertions} insertions</Text>
|
|
180
|
-
<Text color="red">-{commit.deletions}
|
|
178
|
+
<Text color="red">-{commit.deletions}</Text>
|
|
181
179
|
<Text color="gray" dimColor>
|
|
182
180
|
{commit.filesChanged} file{commit.filesChanged !== 1 ? "s" : ""}{" "}
|
|
183
181
|
changed
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import type { Commit } from "../../utils/git";
|
|
4
|
-
|
|
5
|
-
const ACCENT = "#FF8C00";
|
|
4
|
+
import { ACCENT } from "../../colors";
|
|
6
5
|
|
|
7
6
|
type Props = {
|
|
8
7
|
commits: Commit[];
|
|
@@ -29,7 +28,6 @@ function formatRefs(refs: string): string {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
function shortDate(dateStr: string): string {
|
|
32
|
-
// "2026-03-12 14:22:01 +0530" → "Mar 12"
|
|
33
31
|
try {
|
|
34
32
|
const d = new Date(dateStr);
|
|
35
33
|
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
@@ -82,8 +80,7 @@ export function CommitList({
|
|
|
82
80
|
const refs = formatRefs(commit.refs);
|
|
83
81
|
const date = shortDate(commit.date);
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
const prefixLen = 14; // symbol + hash + date
|
|
83
|
+
const prefixLen = 14;
|
|
87
84
|
const maxMsg = Math.max(10, width - prefixLen - 3);
|
|
88
85
|
const msg =
|
|
89
86
|
commit.message.length > maxMsg
|
|
@@ -92,22 +89,18 @@ export function CommitList({
|
|
|
92
89
|
|
|
93
90
|
return (
|
|
94
91
|
<Box key={commit.hash} paddingX={1} flexDirection="column">
|
|
95
|
-
{/* graph line above (not first) */}
|
|
96
92
|
{i > 0 && (
|
|
97
93
|
<Text color="gray" dimColor>
|
|
98
94
|
{"│"}
|
|
99
95
|
</Text>
|
|
100
96
|
)}
|
|
101
97
|
<Box gap={1}>
|
|
102
|
-
{/* selection indicator */}
|
|
103
98
|
<Text color={isSelected ? ACCENT : "gray"}>
|
|
104
99
|
{isSelected ? "▶" : " "}
|
|
105
100
|
</Text>
|
|
106
101
|
|
|
107
|
-
{/* graph node */}
|
|
108
102
|
<Text color={isSelected ? ACCENT : color}>{symbol}</Text>
|
|
109
103
|
|
|
110
|
-
{/* short hash */}
|
|
111
104
|
<Text
|
|
112
105
|
color={isSelected ? "white" : "gray"}
|
|
113
106
|
dimColor={!isSelected}
|
|
@@ -115,12 +108,10 @@ export function CommitList({
|
|
|
115
108
|
{commit.shortHash}
|
|
116
109
|
</Text>
|
|
117
110
|
|
|
118
|
-
{/* date */}
|
|
119
111
|
<Text color="cyan" dimColor={!isSelected}>
|
|
120
112
|
{date}
|
|
121
113
|
</Text>
|
|
122
114
|
|
|
123
|
-
{/* message */}
|
|
124
115
|
<Text
|
|
125
116
|
color={isSelected ? "white" : "gray"}
|
|
126
117
|
bold={isSelected}
|
|
@@ -130,14 +121,12 @@ export function CommitList({
|
|
|
130
121
|
</Text>
|
|
131
122
|
</Box>
|
|
132
123
|
|
|
133
|
-
{/* refs on selected */}
|
|
134
124
|
{isSelected && refs && (
|
|
135
125
|
<Box paddingLeft={4}>
|
|
136
126
|
<Text color="yellow">{refs}</Text>
|
|
137
127
|
</Box>
|
|
138
128
|
)}
|
|
139
129
|
|
|
140
|
-
{/* stat summary on selected */}
|
|
141
130
|
{isSelected && (
|
|
142
131
|
<Box paddingLeft={4} gap={2}>
|
|
143
132
|
<Text color="gray" dimColor>
|
|
@@ -159,7 +148,6 @@ export function CommitList({
|
|
|
159
148
|
);
|
|
160
149
|
})}
|
|
161
150
|
|
|
162
|
-
{/* scroll hint */}
|
|
163
151
|
<Box paddingX={1} marginTop={1}>
|
|
164
152
|
<Text color="gray" dimColor>
|
|
165
153
|
{scrollOffset > 0 ? "↑ more above" : ""}
|
|
@@ -5,8 +5,7 @@ import type { Commit } from "../../utils/git";
|
|
|
5
5
|
import { summarizeTimeline } from "../../utils/git";
|
|
6
6
|
import type { Provider } from "../../types/config";
|
|
7
7
|
import { callChat } from "../../utils/chat";
|
|
8
|
-
|
|
9
|
-
const ACCENT = "#FF8C00";
|
|
8
|
+
import { ACCENT } from "../../colors";
|
|
10
9
|
|
|
11
10
|
type TLMessage = { role: "user" | "assistant"; content: string; type: "text" };
|
|
12
11
|
|