@ridit/lens 0.1.6 → 0.1.8
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/dist/index.mjs +2028 -207
- package/hello.py +51 -0
- package/package.json +3 -4
- package/src/colors.ts +9 -0
- package/src/components/chat/ChatMessage.tsx +573 -66
- package/src/components/chat/ChatOverlays.tsx +28 -1
- package/src/components/chat/ChatRunner.tsx +250 -69
- package/src/index.tsx +60 -60
- package/src/types/chat.ts +18 -1
- package/src/utils/chat.ts +805 -32
- package/src/utils/thinking.tsx +20 -1
- package/tsconfig.json +1 -1
|
@@ -48,6 +48,30 @@ export function PermissionPrompt({
|
|
|
48
48
|
icon = "r";
|
|
49
49
|
label = "read";
|
|
50
50
|
value = tool.filePath;
|
|
51
|
+
} else if (tool.type === "read-folder") {
|
|
52
|
+
icon = "d";
|
|
53
|
+
label = "folder";
|
|
54
|
+
value = tool.folderPath;
|
|
55
|
+
} else if (tool.type === "grep") {
|
|
56
|
+
icon = "/";
|
|
57
|
+
label = "grep";
|
|
58
|
+
value = `${tool.pattern} ${tool.glob}`;
|
|
59
|
+
} else if (tool.type === "delete-file") {
|
|
60
|
+
icon = "x";
|
|
61
|
+
label = "delete";
|
|
62
|
+
value = tool.filePath;
|
|
63
|
+
} else if (tool.type === "delete-folder") {
|
|
64
|
+
icon = "X";
|
|
65
|
+
label = "delete folder";
|
|
66
|
+
value = tool.folderPath;
|
|
67
|
+
} else if (tool.type === "open-url") {
|
|
68
|
+
icon = "↗";
|
|
69
|
+
label = "open";
|
|
70
|
+
value = tool.url;
|
|
71
|
+
} else if (tool.type === "generate-pdf") {
|
|
72
|
+
icon = "P";
|
|
73
|
+
label = "pdf";
|
|
74
|
+
value = tool.filePath;
|
|
51
75
|
} else if (tool.type === "write-file") {
|
|
52
76
|
icon = "w";
|
|
53
77
|
label = "write";
|
|
@@ -128,12 +152,15 @@ export function TypewriterText({
|
|
|
128
152
|
return <Text color={color}>{displayed}</Text>;
|
|
129
153
|
}
|
|
130
154
|
|
|
131
|
-
export function ShortcutBar() {
|
|
155
|
+
export function ShortcutBar({ autoApprove }: { autoApprove?: boolean }) {
|
|
132
156
|
return (
|
|
133
157
|
<Box gap={3} marginTop={0}>
|
|
134
158
|
<Text color="gray" dimColor>
|
|
135
159
|
enter send · ^v paste · ^c exit
|
|
136
160
|
</Text>
|
|
161
|
+
<Text color={autoApprove ? "green" : "gray"} dimColor={!autoApprove}>
|
|
162
|
+
{autoApprove ? "⚡ auto" : "/auto"}
|
|
163
|
+
</Text>
|
|
137
164
|
</Box>
|
|
138
165
|
);
|
|
139
166
|
}
|
|
@@ -20,6 +20,12 @@ import {
|
|
|
20
20
|
runShell,
|
|
21
21
|
fetchUrl,
|
|
22
22
|
readFile,
|
|
23
|
+
readFolder,
|
|
24
|
+
grepFiles,
|
|
25
|
+
deleteFile,
|
|
26
|
+
deleteFolder,
|
|
27
|
+
openUrl,
|
|
28
|
+
generatePdf,
|
|
23
29
|
writeFile,
|
|
24
30
|
buildSystemPrompt,
|
|
25
31
|
parseResponse,
|
|
@@ -55,6 +61,7 @@ const COMMANDS = [
|
|
|
55
61
|
{ cmd: "/timeline", desc: "browse commit history" },
|
|
56
62
|
{ cmd: "/clear history", desc: "wipe session memory for this repo" },
|
|
57
63
|
{ cmd: "/review", desc: "review current codebsae" },
|
|
64
|
+
{ cmd: "/auto", desc: "toggle auto-approve for read/search tools" },
|
|
58
65
|
];
|
|
59
66
|
|
|
60
67
|
function CommandPalette({
|
|
@@ -98,6 +105,15 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
98
105
|
const [clonedUrls, setClonedUrls] = useState<Set<string>>(new Set());
|
|
99
106
|
const [showTimeline, setShowTimeline] = useState(false);
|
|
100
107
|
const [showReview, setShowReview] = useState(false);
|
|
108
|
+
const [autoApprove, setAutoApprove] = useState(false);
|
|
109
|
+
|
|
110
|
+
// Abort controller for the currently in-flight API call.
|
|
111
|
+
// Pressing ESC while thinking aborts the request and drops the response.
|
|
112
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
113
|
+
|
|
114
|
+
// Cache of tool results within a single conversation turn to prevent
|
|
115
|
+
// the model from re-calling tools it already ran with the same args
|
|
116
|
+
const toolResultCache = useRef<Map<string, string>>(new Map());
|
|
101
117
|
|
|
102
118
|
const inputBuffer = useRef("");
|
|
103
119
|
const flushTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -119,6 +135,11 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
119
135
|
};
|
|
120
136
|
|
|
121
137
|
const handleError = (currentAll: Message[]) => (err: unknown) => {
|
|
138
|
+
// Silently drop aborted requests — user pressed ESC intentionally
|
|
139
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
140
|
+
setStage({ type: "idle" });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
122
143
|
const errMsg: Message = {
|
|
123
144
|
role: "assistant",
|
|
124
145
|
content: `Error: ${err instanceof Error ? err.message : "Something went wrong"}`,
|
|
@@ -129,7 +150,17 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
129
150
|
setStage({ type: "idle" });
|
|
130
151
|
};
|
|
131
152
|
|
|
132
|
-
const processResponse = (
|
|
153
|
+
const processResponse = (
|
|
154
|
+
raw: string,
|
|
155
|
+
currentAll: Message[],
|
|
156
|
+
signal: AbortSignal,
|
|
157
|
+
) => {
|
|
158
|
+
// If ESC was pressed before we got here, silently drop the response
|
|
159
|
+
if (signal.aborted) {
|
|
160
|
+
setStage({ type: "idle" });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
133
164
|
const parsed = parseResponse(raw);
|
|
134
165
|
|
|
135
166
|
if (parsed.kind === "changes") {
|
|
@@ -169,7 +200,13 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
169
200
|
parsed.kind === "shell" ||
|
|
170
201
|
parsed.kind === "fetch" ||
|
|
171
202
|
parsed.kind === "read-file" ||
|
|
203
|
+
parsed.kind === "read-folder" ||
|
|
204
|
+
parsed.kind === "grep" ||
|
|
172
205
|
parsed.kind === "write-file" ||
|
|
206
|
+
parsed.kind === "delete-file" ||
|
|
207
|
+
parsed.kind === "delete-folder" ||
|
|
208
|
+
parsed.kind === "open-url" ||
|
|
209
|
+
parsed.kind === "generate-pdf" ||
|
|
173
210
|
parsed.kind === "search"
|
|
174
211
|
) {
|
|
175
212
|
let tool: Parameters<typeof PermissionPrompt>[0]["tool"];
|
|
@@ -179,6 +216,22 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
179
216
|
tool = { type: "fetch", url: parsed.url };
|
|
180
217
|
} else if (parsed.kind === "read-file") {
|
|
181
218
|
tool = { type: "read-file", filePath: parsed.filePath };
|
|
219
|
+
} else if (parsed.kind === "read-folder") {
|
|
220
|
+
tool = { type: "read-folder", folderPath: parsed.folderPath };
|
|
221
|
+
} else if (parsed.kind === "grep") {
|
|
222
|
+
tool = { type: "grep", pattern: parsed.pattern, glob: parsed.glob };
|
|
223
|
+
} else if (parsed.kind === "delete-file") {
|
|
224
|
+
tool = { type: "delete-file", filePath: parsed.filePath };
|
|
225
|
+
} else if (parsed.kind === "delete-folder") {
|
|
226
|
+
tool = { type: "delete-folder", folderPath: parsed.folderPath };
|
|
227
|
+
} else if (parsed.kind === "open-url") {
|
|
228
|
+
tool = { type: "open-url", url: parsed.url };
|
|
229
|
+
} else if (parsed.kind === "generate-pdf") {
|
|
230
|
+
tool = {
|
|
231
|
+
type: "generate-pdf",
|
|
232
|
+
filePath: parsed.filePath,
|
|
233
|
+
content: parsed.pdfContent,
|
|
234
|
+
};
|
|
182
235
|
} else if (parsed.kind === "search") {
|
|
183
236
|
tool = { type: "search", query: parsed.query };
|
|
184
237
|
} else {
|
|
@@ -199,13 +252,32 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
199
252
|
setCommitted((prev) => [...prev, preambleMsg]);
|
|
200
253
|
}
|
|
201
254
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
255
|
+
// Safe tools that can be auto-approved (no side effects)
|
|
256
|
+
const isSafeTool =
|
|
257
|
+
parsed.kind === "read-file" ||
|
|
258
|
+
parsed.kind === "read-folder" ||
|
|
259
|
+
parsed.kind === "grep" ||
|
|
260
|
+
parsed.kind === "fetch" ||
|
|
261
|
+
parsed.kind === "open-url" ||
|
|
262
|
+
parsed.kind === "search";
|
|
263
|
+
|
|
264
|
+
const executeAndContinue = async (approved: boolean) => {
|
|
265
|
+
let result = "(denied by user)";
|
|
266
|
+
if (approved) {
|
|
267
|
+
const cacheKey =
|
|
268
|
+
parsed.kind === "read-file"
|
|
269
|
+
? `read-file:${parsed.filePath}`
|
|
270
|
+
: parsed.kind === "read-folder"
|
|
271
|
+
? `read-folder:${parsed.folderPath}`
|
|
272
|
+
: parsed.kind === "grep"
|
|
273
|
+
? `grep:${parsed.pattern}:${parsed.glob}`
|
|
274
|
+
: null;
|
|
275
|
+
|
|
276
|
+
if (cacheKey && toolResultCache.current.has(cacheKey)) {
|
|
277
|
+
result =
|
|
278
|
+
toolResultCache.current.get(cacheKey)! +
|
|
279
|
+
"\n\n[NOTE: This result was already retrieved earlier. Do not request it again.]";
|
|
280
|
+
} else {
|
|
209
281
|
try {
|
|
210
282
|
setStage({ type: "thinking" });
|
|
211
283
|
if (parsed.kind === "shell") {
|
|
@@ -214,6 +286,22 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
214
286
|
result = await fetchUrl(parsed.url);
|
|
215
287
|
} else if (parsed.kind === "read-file") {
|
|
216
288
|
result = readFile(parsed.filePath, repoPath);
|
|
289
|
+
} else if (parsed.kind === "read-folder") {
|
|
290
|
+
result = readFolder(parsed.folderPath, repoPath);
|
|
291
|
+
} else if (parsed.kind === "grep") {
|
|
292
|
+
result = grepFiles(parsed.pattern, parsed.glob, repoPath);
|
|
293
|
+
} else if (parsed.kind === "delete-file") {
|
|
294
|
+
result = deleteFile(parsed.filePath, repoPath);
|
|
295
|
+
} else if (parsed.kind === "delete-folder") {
|
|
296
|
+
result = deleteFolder(parsed.folderPath, repoPath);
|
|
297
|
+
} else if (parsed.kind === "open-url") {
|
|
298
|
+
result = openUrl(parsed.url);
|
|
299
|
+
} else if (parsed.kind === "generate-pdf") {
|
|
300
|
+
result = generatePdf(
|
|
301
|
+
parsed.filePath,
|
|
302
|
+
parsed.pdfContent,
|
|
303
|
+
repoPath,
|
|
304
|
+
);
|
|
217
305
|
} else if (parsed.kind === "write-file") {
|
|
218
306
|
result = writeFile(
|
|
219
307
|
parsed.filePath,
|
|
@@ -223,72 +311,133 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
223
311
|
} else if (parsed.kind === "search") {
|
|
224
312
|
result = await searchWeb(parsed.query);
|
|
225
313
|
}
|
|
314
|
+
if (cacheKey) {
|
|
315
|
+
toolResultCache.current.set(cacheKey, result);
|
|
316
|
+
}
|
|
226
317
|
} catch (err: unknown) {
|
|
227
318
|
result = `Error: ${err instanceof Error ? err.message : "failed"}`;
|
|
228
319
|
}
|
|
229
320
|
}
|
|
321
|
+
}
|
|
230
322
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const toolName =
|
|
255
|
-
parsed.kind === "shell"
|
|
256
|
-
? "shell"
|
|
257
|
-
: parsed.kind === "fetch"
|
|
258
|
-
? "fetch"
|
|
259
|
-
: parsed.kind === "read-file"
|
|
260
|
-
? "read-file"
|
|
323
|
+
if (approved && !result.startsWith("Error:")) {
|
|
324
|
+
const kindMap = {
|
|
325
|
+
shell: "shell-run",
|
|
326
|
+
fetch: "url-fetched",
|
|
327
|
+
"read-file": "file-read",
|
|
328
|
+
"read-folder": "file-read",
|
|
329
|
+
grep: "file-read",
|
|
330
|
+
"delete-file": "file-written",
|
|
331
|
+
"delete-folder": "file-written",
|
|
332
|
+
"open-url": "url-fetched",
|
|
333
|
+
"generate-pdf": "file-written",
|
|
334
|
+
"write-file": "file-written",
|
|
335
|
+
search: "url-fetched",
|
|
336
|
+
} as const;
|
|
337
|
+
appendHistory({
|
|
338
|
+
kind: kindMap[parsed.kind as keyof typeof kindMap] ?? "shell-run",
|
|
339
|
+
detail:
|
|
340
|
+
parsed.kind === "shell"
|
|
341
|
+
? parsed.command
|
|
342
|
+
: parsed.kind === "fetch"
|
|
343
|
+
? parsed.url
|
|
261
344
|
: parsed.kind === "search"
|
|
262
|
-
?
|
|
263
|
-
: "
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
345
|
+
? parsed.query
|
|
346
|
+
: parsed.kind === "read-folder"
|
|
347
|
+
? parsed.folderPath
|
|
348
|
+
: parsed.kind === "grep"
|
|
349
|
+
? `${parsed.pattern} ${parsed.glob}`
|
|
350
|
+
: parsed.kind === "delete-file"
|
|
351
|
+
? parsed.filePath
|
|
352
|
+
: parsed.kind === "delete-folder"
|
|
353
|
+
? parsed.folderPath
|
|
354
|
+
: parsed.kind === "open-url"
|
|
355
|
+
? parsed.url
|
|
356
|
+
: parsed.kind === "generate-pdf"
|
|
357
|
+
? parsed.filePath
|
|
358
|
+
: parsed.filePath,
|
|
359
|
+
summary: result.split("\n")[0]?.slice(0, 120) ?? "",
|
|
360
|
+
repoPath,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const toolName =
|
|
365
|
+
parsed.kind === "shell"
|
|
366
|
+
? "shell"
|
|
367
|
+
: parsed.kind === "fetch"
|
|
368
|
+
? "fetch"
|
|
369
|
+
: parsed.kind === "read-file"
|
|
370
|
+
? "read-file"
|
|
371
|
+
: parsed.kind === "read-folder"
|
|
372
|
+
? "read-folder"
|
|
373
|
+
: parsed.kind === "grep"
|
|
374
|
+
? "grep"
|
|
375
|
+
: parsed.kind === "delete-file"
|
|
376
|
+
? "delete-file"
|
|
377
|
+
: parsed.kind === "delete-folder"
|
|
378
|
+
? "delete-folder"
|
|
379
|
+
: parsed.kind === "open-url"
|
|
380
|
+
? "open-url"
|
|
381
|
+
: parsed.kind === "generate-pdf"
|
|
382
|
+
? "generate-pdf"
|
|
383
|
+
: parsed.kind === "search"
|
|
384
|
+
? "search"
|
|
385
|
+
: "write-file";
|
|
386
|
+
|
|
387
|
+
const toolContent =
|
|
388
|
+
parsed.kind === "shell"
|
|
389
|
+
? parsed.command
|
|
390
|
+
: parsed.kind === "fetch"
|
|
391
|
+
? parsed.url
|
|
392
|
+
: parsed.kind === "search"
|
|
393
|
+
? parsed.query
|
|
394
|
+
: parsed.kind === "read-folder"
|
|
395
|
+
? parsed.folderPath
|
|
396
|
+
: parsed.kind === "grep"
|
|
397
|
+
? `${parsed.pattern} — ${parsed.glob}`
|
|
398
|
+
: parsed.kind === "delete-file"
|
|
399
|
+
? parsed.filePath
|
|
400
|
+
: parsed.kind === "delete-folder"
|
|
401
|
+
? parsed.folderPath
|
|
402
|
+
: parsed.kind === "open-url"
|
|
403
|
+
? parsed.url
|
|
404
|
+
: parsed.kind === "generate-pdf"
|
|
405
|
+
? parsed.filePath
|
|
406
|
+
: parsed.filePath;
|
|
407
|
+
|
|
408
|
+
const toolMsg: Message = {
|
|
409
|
+
role: "assistant",
|
|
410
|
+
type: "tool",
|
|
411
|
+
toolName,
|
|
412
|
+
content: toolContent,
|
|
413
|
+
result,
|
|
414
|
+
approved,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const withTool = [...currentAll, toolMsg];
|
|
418
|
+
setAllMessages(withTool);
|
|
419
|
+
setCommitted((prev) => [...prev, toolMsg]);
|
|
282
420
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
421
|
+
// Create a fresh abort controller for the follow-up call
|
|
422
|
+
const nextAbort = new AbortController();
|
|
423
|
+
abortControllerRef.current = nextAbort;
|
|
286
424
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
425
|
+
setStage({ type: "thinking" });
|
|
426
|
+
callChat(provider!, systemPrompt, withTool, nextAbort.signal)
|
|
427
|
+
.then((r: string) => processResponse(r, withTool, nextAbort.signal))
|
|
428
|
+
.catch(handleError(withTool));
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
if (autoApprove && isSafeTool) {
|
|
432
|
+
executeAndContinue(true);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
setStage({
|
|
437
|
+
type: "permission",
|
|
438
|
+
tool,
|
|
439
|
+
pendingMessages: currentAll,
|
|
440
|
+
resolve: executeAndContinue,
|
|
292
441
|
});
|
|
293
442
|
return;
|
|
294
443
|
}
|
|
@@ -349,6 +498,21 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
349
498
|
return;
|
|
350
499
|
}
|
|
351
500
|
|
|
501
|
+
if (text.trim().toLowerCase() === "/auto") {
|
|
502
|
+
const next = !autoApprove;
|
|
503
|
+
setAutoApprove(next);
|
|
504
|
+
const msg: Message = {
|
|
505
|
+
role: "assistant",
|
|
506
|
+
content: next
|
|
507
|
+
? "Auto-approve ON — read, search, grep and folder tools will run without asking. Write and code changes still require approval."
|
|
508
|
+
: "Auto-approve OFF — all tools will ask for permission.",
|
|
509
|
+
type: "text",
|
|
510
|
+
};
|
|
511
|
+
setCommitted((prev) => [...prev, msg]);
|
|
512
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
352
516
|
if (text.trim().toLowerCase() === "/clear history") {
|
|
353
517
|
clearRepoHistory(repoPath);
|
|
354
518
|
const clearedMsg: Message = {
|
|
@@ -365,15 +529,29 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
365
529
|
const nextAll = [...allMessages, userMsg];
|
|
366
530
|
setCommitted((prev) => [...prev, userMsg]);
|
|
367
531
|
setAllMessages(nextAll);
|
|
532
|
+
toolResultCache.current.clear();
|
|
533
|
+
|
|
534
|
+
// Create a fresh abort controller for this request
|
|
535
|
+
const abort = new AbortController();
|
|
536
|
+
abortControllerRef.current = abort;
|
|
537
|
+
|
|
368
538
|
setStage({ type: "thinking" });
|
|
369
|
-
callChat(provider, systemPrompt, nextAll)
|
|
370
|
-
.then((raw: string) => processResponse(raw, nextAll))
|
|
539
|
+
callChat(provider, systemPrompt, nextAll, abort.signal)
|
|
540
|
+
.then((raw: string) => processResponse(raw, nextAll, abort.signal))
|
|
371
541
|
.catch(handleError(nextAll));
|
|
372
542
|
};
|
|
373
543
|
|
|
374
544
|
useInput((input, key) => {
|
|
375
545
|
if (showTimeline) return;
|
|
376
546
|
|
|
547
|
+
// ESC while thinking → abort the in-flight request and go idle
|
|
548
|
+
if (stage.type === "thinking" && key.escape) {
|
|
549
|
+
abortControllerRef.current?.abort();
|
|
550
|
+
abortControllerRef.current = null;
|
|
551
|
+
setStage({ type: "idle" });
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
377
555
|
if (stage.type === "idle") {
|
|
378
556
|
if (key.ctrl && input === "c") {
|
|
379
557
|
process.exit(0);
|
|
@@ -699,6 +877,9 @@ Suggestions: ${lensFile.suggestions.slice(0, 3).join("; ")}`
|
|
|
699
877
|
<Box gap={1}>
|
|
700
878
|
<Text color={ACCENT}>●</Text>
|
|
701
879
|
<TypewriterText text={thinkingPhrase} />
|
|
880
|
+
<Text color="gray" dimColor>
|
|
881
|
+
· esc cancel
|
|
882
|
+
</Text>
|
|
702
883
|
</Box>
|
|
703
884
|
)}
|
|
704
885
|
|
|
@@ -724,7 +905,7 @@ Suggestions: ${lensFile.suggestions.slice(0, 3).join("; ")}`
|
|
|
724
905
|
setInputValue("");
|
|
725
906
|
}}
|
|
726
907
|
/>
|
|
727
|
-
<ShortcutBar />
|
|
908
|
+
<ShortcutBar autoApprove={autoApprove} />
|
|
728
909
|
</Box>
|
|
729
910
|
)}
|
|
730
911
|
</Box>
|
package/src/index.tsx
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { render } from "ink";
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
-
import { RepoCommand } from "./commands/repo";
|
|
5
|
-
import { InitCommand } from "./commands/provider";
|
|
6
|
-
import { ReviewCommand } from "./commands/review";
|
|
7
|
-
import { TaskCommand } from "./commands/task";
|
|
8
|
-
import { ChatCommand } from "./commands/chat";
|
|
9
|
-
import { TimelineCommand } from "./commands/timeline";
|
|
10
|
-
|
|
11
|
-
const program = new Command();
|
|
12
|
-
|
|
13
|
-
program
|
|
14
|
-
.command("repo <url>")
|
|
15
|
-
.description("Analyze a remote repository")
|
|
16
|
-
.action((url) => {
|
|
17
|
-
render(<RepoCommand url={url} />);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
program
|
|
21
|
-
.command("provider")
|
|
22
|
-
.description("Configure AI providers")
|
|
23
|
-
.action(() => {
|
|
24
|
-
render(<InitCommand />);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
program
|
|
28
|
-
.command("review [path]")
|
|
29
|
-
.description("Review a local codebase")
|
|
30
|
-
.action((inputPath) => {
|
|
31
|
-
render(<ReviewCommand path={inputPath ?? "."} />);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
program
|
|
35
|
-
.command("task <text>")
|
|
36
|
-
.description("Apply a natural language change to the codebase")
|
|
37
|
-
.option("-p, --path <path>", "Path to the repo", ".")
|
|
38
|
-
.action((text: string, opts: { path: string }) => {
|
|
39
|
-
render(<TaskCommand prompt={text} path={opts.path} />);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
program
|
|
43
|
-
.command("chat")
|
|
44
|
-
.description("Chat with your codebase — ask questions or make changes")
|
|
45
|
-
.option("-p, --path <path>", "Path to the repo", ".")
|
|
46
|
-
.action((opts: { path: string }) => {
|
|
47
|
-
render(<ChatCommand path={opts.path} />);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
program
|
|
51
|
-
.command("timeline")
|
|
52
|
-
.description(
|
|
53
|
-
"Explore your code history — see commits, changes, and evolution",
|
|
54
|
-
)
|
|
55
|
-
.option("-p, --path <path>", "Path to the repo", ".")
|
|
56
|
-
.action((opts: { path: string }) => {
|
|
57
|
-
render(<TimelineCommand path={opts.path} />);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
program.parse(process.argv);
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "ink";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { RepoCommand } from "./commands/repo";
|
|
5
|
+
import { InitCommand } from "./commands/provider";
|
|
6
|
+
import { ReviewCommand } from "./commands/review";
|
|
7
|
+
import { TaskCommand } from "./commands/task";
|
|
8
|
+
import { ChatCommand } from "./commands/chat";
|
|
9
|
+
import { TimelineCommand } from "./commands/timeline";
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command("repo <url>")
|
|
15
|
+
.description("Analyze a remote repository")
|
|
16
|
+
.action((url) => {
|
|
17
|
+
render(<RepoCommand url={url} />);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command("provider")
|
|
22
|
+
.description("Configure AI providers")
|
|
23
|
+
.action(() => {
|
|
24
|
+
render(<InitCommand />);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command("review [path]")
|
|
29
|
+
.description("Review a local codebase")
|
|
30
|
+
.action((inputPath) => {
|
|
31
|
+
render(<ReviewCommand path={inputPath ?? "."} />);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.command("task <text>")
|
|
36
|
+
.description("Apply a natural language change to the codebase")
|
|
37
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
38
|
+
.action((text: string, opts: { path: string }) => {
|
|
39
|
+
render(<TaskCommand prompt={text} path={opts.path} />);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command("chat")
|
|
44
|
+
.description("Chat with your codebase — ask questions or make changes")
|
|
45
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
46
|
+
.action((opts: { path: string }) => {
|
|
47
|
+
render(<ChatCommand path={opts.path} />);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command("timeline")
|
|
52
|
+
.description(
|
|
53
|
+
"Explore your code history — see commits, changes, and evolution",
|
|
54
|
+
)
|
|
55
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
56
|
+
.action((opts: { path: string }) => {
|
|
57
|
+
render(<TimelineCommand path={opts.path} />);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
program.parse(process.argv);
|
package/src/types/chat.ts
CHANGED
|
@@ -6,7 +6,13 @@ export type ToolCall =
|
|
|
6
6
|
| { type: "shell"; command: string }
|
|
7
7
|
| { type: "fetch"; url: string }
|
|
8
8
|
| { type: "read-file"; filePath: string }
|
|
9
|
+
| { type: "read-folder"; folderPath: string }
|
|
10
|
+
| { type: "grep"; pattern: string; glob: string }
|
|
9
11
|
| { type: "write-file"; filePath: string; fileContent: string }
|
|
12
|
+
| { type: "delete-file"; filePath: string }
|
|
13
|
+
| { type: "delete-folder"; folderPath: string }
|
|
14
|
+
| { type: "open-url"; url: string }
|
|
15
|
+
| { type: "generate-pdf"; filePath: string; content: string }
|
|
10
16
|
| { type: "search"; query: string };
|
|
11
17
|
|
|
12
18
|
// ── Messages ──────────────────────────────────────────────────────────────────
|
|
@@ -16,7 +22,18 @@ export type Message =
|
|
|
16
22
|
| {
|
|
17
23
|
role: "assistant";
|
|
18
24
|
type: "tool";
|
|
19
|
-
toolName:
|
|
25
|
+
toolName:
|
|
26
|
+
| "shell"
|
|
27
|
+
| "fetch"
|
|
28
|
+
| "read-file"
|
|
29
|
+
| "read-folder"
|
|
30
|
+
| "grep"
|
|
31
|
+
| "write-file"
|
|
32
|
+
| "delete-file"
|
|
33
|
+
| "delete-folder"
|
|
34
|
+
| "open-url"
|
|
35
|
+
| "generate-pdf"
|
|
36
|
+
| "search";
|
|
20
37
|
content: string;
|
|
21
38
|
result: string;
|
|
22
39
|
approved: boolean;
|