@ridit/lens 0.3.7 → 0.3.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 +725 -460
- package/package.json +1 -1
- package/src/commands/chat.tsx +14 -20
- package/src/components/chat/ChatMessage.tsx +46 -4
- package/src/components/chat/ChatOverlays.tsx +19 -13
- package/src/components/chat/ChatRunner.tsx +53 -13
- package/src/components/chat/TextArea.tsx +10 -9
- package/src/components/chat/hooks/useChat.ts +373 -273
- package/src/components/chat/hooks/useCommandHandlers.ts +0 -4
- package/src/components/repo/RepoAnalysis.tsx +3 -3
- package/src/components/task/TaskRunner.tsx +3 -3
- package/src/index.tsx +13 -2
- package/src/prompts/system.ts +16 -17
- package/src/types/chat.ts +3 -1
- package/src/utils/chat.ts +57 -4
- package/src/utils/intentClassifier.ts +0 -15
- package/src/utils/thinking.tsx +64 -0
- package/src/utils/tools/builtins.ts +27 -12
package/package.json
CHANGED
package/src/commands/chat.tsx
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box
|
|
3
|
-
import figures from "figures";
|
|
4
|
-
import { existsSync } from "fs";
|
|
5
|
-
import path from "path";
|
|
2
|
+
import { Box } from "ink";
|
|
6
3
|
import { ChatRunner } from "../components/chat/ChatRunner";
|
|
7
|
-
import { ACCENT } from "../colors";
|
|
8
4
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return <ChatRunner repoPath={resolvedPath} />;
|
|
23
|
-
};
|
|
5
|
+
export function ChatCommand({
|
|
6
|
+
path,
|
|
7
|
+
autoForce = false,
|
|
8
|
+
}: {
|
|
9
|
+
path: string;
|
|
10
|
+
autoForce?: boolean;
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<Box flexDirection="column">
|
|
14
|
+
<ChatRunner repoPath={path} autoForce={autoForce} />
|
|
15
|
+
</Box>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import figures from "figures";
|
|
3
4
|
import { ACCENT, GREEN, RED } from "../../colors";
|
|
4
5
|
import type { Message } from "../../types/chat";
|
|
6
|
+
import type { DiffLine } from "../repo/DiffViewer";
|
|
5
7
|
|
|
6
8
|
function InlineText({ text }: { text: string }) {
|
|
7
9
|
const parts = text.split(/(`[^`]+`|\*\*[^*]+\*\*)/g);
|
|
@@ -170,10 +172,12 @@ export function StaticMessage({ msg }: { msg: Message }) {
|
|
|
170
172
|
if (msg.type === "plan") {
|
|
171
173
|
return (
|
|
172
174
|
<Box flexDirection="column" marginBottom={1}>
|
|
173
|
-
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
{msg.content ? (
|
|
176
|
+
<Box gap={1}>
|
|
177
|
+
<Text color={ACCENT}>*</Text>
|
|
178
|
+
<MessageBody content={msg.content} />
|
|
179
|
+
</Box>
|
|
180
|
+
) : null}
|
|
177
181
|
<Box marginLeft={2} gap={1}>
|
|
178
182
|
<Text color={msg.applied ? GREEN : "gray"}>
|
|
179
183
|
{msg.applied ? "✓" : "·"}
|
|
@@ -182,6 +186,44 @@ export function StaticMessage({ msg }: { msg: Message }) {
|
|
|
182
186
|
{msg.applied ? "changes applied" : "changes skipped"}
|
|
183
187
|
</Text>
|
|
184
188
|
</Box>
|
|
189
|
+
{msg.applied && msg.diffLines && msg.diffLines.length > 0 && (
|
|
190
|
+
<Box flexDirection="column" marginLeft={2} marginTop={0}>
|
|
191
|
+
{msg.patches.map((patch, fi) => (
|
|
192
|
+
<Box key={patch.path} flexDirection="column">
|
|
193
|
+
<Text bold color={fi % 2 === 0 ? "cyan" : "magenta"}>
|
|
194
|
+
{figures.bullet} {patch.path}
|
|
195
|
+
{patch.isNew ? " (new)" : ""}
|
|
196
|
+
</Text>
|
|
197
|
+
{(msg.diffLines![fi] ?? []).map((line: DiffLine, li: number) => {
|
|
198
|
+
const prefix =
|
|
199
|
+
line.type === "added"
|
|
200
|
+
? "+"
|
|
201
|
+
: line.type === "removed"
|
|
202
|
+
? "-"
|
|
203
|
+
: " ";
|
|
204
|
+
const color =
|
|
205
|
+
line.type === "added"
|
|
206
|
+
? "green"
|
|
207
|
+
: line.type === "removed"
|
|
208
|
+
? "red"
|
|
209
|
+
: "gray";
|
|
210
|
+
const lineNumStr =
|
|
211
|
+
line.lineNum === -1
|
|
212
|
+
? " "
|
|
213
|
+
: String(line.lineNum).padStart(3, " ");
|
|
214
|
+
return (
|
|
215
|
+
<Box key={li}>
|
|
216
|
+
<Text color="gray">{lineNumStr} </Text>
|
|
217
|
+
<Text color={color}>
|
|
218
|
+
{prefix} {line.content}
|
|
219
|
+
</Text>
|
|
220
|
+
</Box>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</Box>
|
|
224
|
+
))}
|
|
225
|
+
</Box>
|
|
226
|
+
)}
|
|
185
227
|
</Box>
|
|
186
228
|
);
|
|
187
229
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Box, Static, Text, useStdout } from "ink";
|
|
3
3
|
import Spinner from "ink-spinner";
|
|
4
4
|
import { TextArea } from "./TextArea";
|
|
@@ -24,11 +24,6 @@ function Hint({ text }: { text: string }) {
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// ── PermissionPrompt ──────────────────────────────────────────────────────────
|
|
28
|
-
//
|
|
29
|
-
// Works with both the old explicit ToolCall union and the new generic
|
|
30
|
-
// { type, _label, _display } shape produced by the plugin system.
|
|
31
|
-
|
|
32
27
|
export function PermissionPrompt({
|
|
33
28
|
tool,
|
|
34
29
|
onDecide,
|
|
@@ -40,7 +35,6 @@ export function PermissionPrompt({
|
|
|
40
35
|
let label: string;
|
|
41
36
|
let value: string;
|
|
42
37
|
|
|
43
|
-
// Generic plugin tool shape
|
|
44
38
|
if ("_label" in tool) {
|
|
45
39
|
const iconMap: Record<string, string> = {
|
|
46
40
|
run: "$",
|
|
@@ -61,7 +55,6 @@ export function PermissionPrompt({
|
|
|
61
55
|
label = tool._label;
|
|
62
56
|
value = tool._display;
|
|
63
57
|
} else {
|
|
64
|
-
// Legacy explicit ToolCall union
|
|
65
58
|
if (tool.type === "shell") {
|
|
66
59
|
icon = "$";
|
|
67
60
|
label = "run";
|
|
@@ -135,12 +128,23 @@ export function InputBox({
|
|
|
135
128
|
inputKey?: number;
|
|
136
129
|
}) {
|
|
137
130
|
const { stdout } = useStdout();
|
|
138
|
-
const cols = stdout?.columns ?? 80;
|
|
131
|
+
const [cols, setCols] = useState(stdout?.columns ?? 80);
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
const handler = () => setCols(stdout?.columns ?? 80);
|
|
135
|
+
stdout?.on("resize", handler);
|
|
136
|
+
return () => {
|
|
137
|
+
stdout?.off("resize", handler);
|
|
138
|
+
};
|
|
139
|
+
}, [stdout]);
|
|
140
|
+
|
|
139
141
|
const rule = "─".repeat(Math.max(1, cols));
|
|
140
142
|
|
|
141
143
|
return (
|
|
142
144
|
<Box flexDirection="column" marginTop={1}>
|
|
143
|
-
<Text color="gray" dimColor>
|
|
145
|
+
<Text color="gray" dimColor>
|
|
146
|
+
{rule}
|
|
147
|
+
</Text>
|
|
144
148
|
<Box gap={1}>
|
|
145
149
|
<Text color={ACCENT}>{">"}</Text>
|
|
146
150
|
<TextArea
|
|
@@ -151,7 +155,9 @@ export function InputBox({
|
|
|
151
155
|
placeholder="ask anything..."
|
|
152
156
|
/>
|
|
153
157
|
</Box>
|
|
154
|
-
<Text color="gray" dimColor>
|
|
158
|
+
<Text color="gray" dimColor>
|
|
159
|
+
{rule}
|
|
160
|
+
</Text>
|
|
155
161
|
</Box>
|
|
156
162
|
);
|
|
157
163
|
}
|
|
@@ -195,7 +201,7 @@ export function ShortcutBar({
|
|
|
195
201
|
return (
|
|
196
202
|
<Box gap={3} marginTop={0}>
|
|
197
203
|
<Text color="gray" dimColor>
|
|
198
|
-
enter send ·
|
|
204
|
+
enter send · ctrl+enter newline · ctrl+del del word · ^f force · ^c exit
|
|
199
205
|
</Text>
|
|
200
206
|
{forceApprove ? (
|
|
201
207
|
<Text color={RED}>⚡⚡ force-all</Text>
|
|
@@ -243,7 +249,7 @@ export function CloningView({
|
|
|
243
249
|
<History committed={committed} />
|
|
244
250
|
<Box gap={1} marginTop={1}>
|
|
245
251
|
<Text color={ACCENT}>
|
|
246
|
-
<Spinner />
|
|
252
|
+
<Spinner type="line" />
|
|
247
253
|
</Text>
|
|
248
254
|
<Text color="gray">cloning </Text>
|
|
249
255
|
<Text color={ACCENT}>{stage.repoUrl}</Text>
|
|
@@ -8,7 +8,7 @@ import { TextArea } from "./TextArea";
|
|
|
8
8
|
import { ACCENT } from "../../colors";
|
|
9
9
|
import { ProviderPicker } from "../provider/ProviderPicker";
|
|
10
10
|
import { startCloneRepo } from "../../utils/repo";
|
|
11
|
-
import { useThinkingPhrase } from "../../utils/thinking";
|
|
11
|
+
import { useThinkingPhrase, useThinkingTip, useThinkingTimer } from "../../utils/thinking";
|
|
12
12
|
import { walkDir, applyPatches, toCloneUrl } from "../../utils/chat";
|
|
13
13
|
import { appendMemory } from "../../utils/memory";
|
|
14
14
|
import { getChatNameSuggestions, saveChat } from "../../utils/chatHistory";
|
|
@@ -147,9 +147,18 @@ function ForceAllWarning({
|
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
export const ChatRunner = ({
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
export const ChatRunner = ({
|
|
151
|
+
repoPath,
|
|
152
|
+
autoForce = false,
|
|
153
|
+
}: {
|
|
154
|
+
repoPath: string;
|
|
155
|
+
autoForce?: boolean;
|
|
156
|
+
}) => {
|
|
157
|
+
const chat = useChat(repoPath, autoForce);
|
|
158
|
+
const isThinking = chat.stage.type === "thinking";
|
|
159
|
+
const thinkingPhrase = useThinkingPhrase(isThinking);
|
|
160
|
+
const thinkingTip = useThinkingTip(isThinking);
|
|
161
|
+
const thinkingTimer = useThinkingTimer(isThinking);
|
|
153
162
|
|
|
154
163
|
const handleStageKey = (input: string, key: any) => {
|
|
155
164
|
const { stage } = chat;
|
|
@@ -321,10 +330,14 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
321
330
|
if (msg?.type === "plan") {
|
|
322
331
|
chat.applyPatchesAndContinue(msg.patches);
|
|
323
332
|
const applied: Message = { ...msg, applied: true };
|
|
324
|
-
chat.
|
|
325
|
-
|
|
333
|
+
const updatedAll = chat.allMessages.map((m, i) =>
|
|
334
|
+
i === chat.pendingMsgIndex ? applied : m,
|
|
326
335
|
);
|
|
336
|
+
chat.setAllMessages(updatedAll);
|
|
327
337
|
chat.setCommitted((prev) => [...prev, applied]);
|
|
338
|
+
chat.setPendingMsgIndex(null);
|
|
339
|
+
chat.continueAfterChanges(updatedAll, msg.content || "code changes");
|
|
340
|
+
return;
|
|
328
341
|
}
|
|
329
342
|
}
|
|
330
343
|
chat.setPendingMsgIndex(null);
|
|
@@ -352,6 +365,26 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
352
365
|
}
|
|
353
366
|
};
|
|
354
367
|
|
|
368
|
+
useInput(
|
|
369
|
+
(input, key) => {
|
|
370
|
+
if (!(key.ctrl && input === "f")) return;
|
|
371
|
+
if (chat.forceApprove) {
|
|
372
|
+
chat.setForceApprove(false);
|
|
373
|
+
chat.setAutoApprove(false);
|
|
374
|
+
const msg: Message = {
|
|
375
|
+
role: "assistant",
|
|
376
|
+
content: "Force-all mode OFF — tools will ask for permission again.",
|
|
377
|
+
type: "text",
|
|
378
|
+
};
|
|
379
|
+
chat.setCommitted((prev) => [...prev, msg]);
|
|
380
|
+
chat.setAllMessages((prev: Message[]) => [...prev, msg]);
|
|
381
|
+
} else {
|
|
382
|
+
chat.setShowForceWarning(true);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
{ isActive: chat.stage.type === "idle" },
|
|
386
|
+
);
|
|
387
|
+
|
|
355
388
|
const chatInput = useChatInput(
|
|
356
389
|
chat.stage,
|
|
357
390
|
chat.showTimeline,
|
|
@@ -408,7 +441,7 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
408
441
|
<Box gap={1} marginTop={1}>
|
|
409
442
|
<Text color={ACCENT}>*</Text>
|
|
410
443
|
<Text color={ACCENT}>
|
|
411
|
-
<Spinner />
|
|
444
|
+
<Spinner type="arc" />
|
|
412
445
|
</Text>
|
|
413
446
|
<Text color="gray" dimColor>
|
|
414
447
|
indexing codebase…
|
|
@@ -476,12 +509,19 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
476
509
|
)}
|
|
477
510
|
|
|
478
511
|
{!chat.showForceWarning && stage.type === "thinking" && (
|
|
479
|
-
<Box
|
|
480
|
-
<
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
512
|
+
<Box flexDirection="column">
|
|
513
|
+
<Box gap={1}>
|
|
514
|
+
<Text color={ACCENT}>●</Text>
|
|
515
|
+
<TypewriterText text={thinkingPhrase} />
|
|
516
|
+
<Text color="gray" dimColor>
|
|
517
|
+
{thinkingTimer ? `· ${thinkingTimer} ` : ""}· esc cancel
|
|
518
|
+
</Text>
|
|
519
|
+
</Box>
|
|
520
|
+
<Box marginLeft={2}>
|
|
521
|
+
<Text color="gray" dimColor>
|
|
522
|
+
tip: {thinkingTip}
|
|
523
|
+
</Text>
|
|
524
|
+
</Box>
|
|
485
525
|
</Box>
|
|
486
526
|
)}
|
|
487
527
|
|
|
@@ -59,12 +59,17 @@ export function TextArea({
|
|
|
59
59
|
if (key.tab || (key.shift && key.tab)) return;
|
|
60
60
|
if (key.ctrl && input === "c") return;
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
const isShiftEnter =
|
|
63
|
+
(key.return && key.shift) ||
|
|
64
|
+
input === "\x1b[27;2;13~" ||
|
|
65
|
+
input === "\x1b[13;2u";
|
|
66
|
+
|
|
67
|
+
if (key.return && !key.meta && !key.shift && !isShiftEnter) {
|
|
63
68
|
onSubmit(value);
|
|
64
69
|
return;
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
if (
|
|
72
|
+
if (isShiftEnter) {
|
|
68
73
|
const next = value.slice(0, cursor) + "\n" + value.slice(cursor);
|
|
69
74
|
onChange(next);
|
|
70
75
|
setCursor((c) => c + 1);
|
|
@@ -118,19 +123,15 @@ export function TextArea({
|
|
|
118
123
|
return;
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
if (key.ctrl && input === "
|
|
126
|
+
if (key.ctrl && input === "f") return;
|
|
127
|
+
|
|
128
|
+
if ((key.ctrl && key.delete) || input === "\x1b[3;5~") {
|
|
122
129
|
const to = wordBoundaryLeft(value, cursor);
|
|
123
130
|
onChange(value.slice(0, to) + value.slice(cursor));
|
|
124
131
|
setCursor(to);
|
|
125
132
|
return;
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
if (key.ctrl && key.delete) {
|
|
129
|
-
const to = wordBoundaryRight(value, cursor);
|
|
130
|
-
onChange(value.slice(0, cursor) + value.slice(to));
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
135
|
if (key.backspace || key.delete) {
|
|
135
136
|
if (cursor > 0) {
|
|
136
137
|
onChange(value.slice(0, cursor - 1) + value.slice(cursor));
|