@ridit/lens 0.3.7 → 0.3.9
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 +105368 -274002
- package/package.json +13 -19
- package/src/colors.ts +15 -15
- package/src/commands/chat.tsx +32 -23
- package/src/commands/provider.tsx +11 -238
- package/src/commands/repo.tsx +66 -120
- package/src/commands/timeline.tsx +11 -22
- package/src/components/ChatView.tsx +238 -0
- package/src/components/Message.tsx +46 -0
- package/src/components/ToolCall.tsx +67 -0
- package/src/components/chat/ChatView.tsx +550 -0
- package/src/components/chat/Message.tsx +152 -0
- package/src/components/chat/StatusBar.tsx +214 -0
- package/src/components/chat/TextArea.tsx +173 -176
- package/src/components/provider/ApiKeyStep.tsx +207 -199
- package/src/components/provider/ModelStep.tsx +90 -88
- package/src/components/provider/ProviderSetup.tsx +331 -0
- package/src/components/provider/ProviderTypeStep.tsx +53 -61
- package/src/components/repo/StepRow.tsx +68 -69
- package/src/components/timeline/TimelineView.tsx +840 -0
- package/src/components/toolcall-utils.ts +103 -0
- package/src/components/watch/RunView.tsx +497 -0
- package/src/hooks/useChatInput.ts +49 -0
- package/src/hooks/useCommandHandler.ts +117 -0
- package/src/index.tsx +386 -139
- package/src/utils/git.ts +149 -155
- package/src/utils/repo.ts +62 -69
- package/src/utils/thinking.tsx +64 -0
- package/src/utils/watch.ts +165 -307
- package/tests/message.test.ts +38 -0
- package/tests/toolcall-utils.test.ts +111 -0
- package/tsconfig.json +8 -24
- package/CLAUDE.md +0 -50
- package/LENS.md +0 -48
- package/LICENSE +0 -21
- package/README.md +0 -93
- package/addons/README.md +0 -55
- package/addons/clean-cache.js +0 -48
- package/addons/generate-readme.js +0 -67
- package/addons/git-stats.js +0 -29
- package/addons/run-tests.js +0 -127
- package/src/commands/commit.tsx +0 -668
- package/src/commands/review.tsx +0 -294
- package/src/commands/run.tsx +0 -56
- package/src/commands/task.tsx +0 -36
- package/src/components/chat/ChatMessage.tsx +0 -195
- package/src/components/chat/ChatOverlays.tsx +0 -399
- package/src/components/chat/ChatRunner.tsx +0 -517
- package/src/components/chat/hooks/useChat.ts +0 -631
- package/src/components/chat/hooks/useChatInput.ts +0 -79
- package/src/components/chat/hooks/useCommandHandlers.ts +0 -327
- package/src/components/provider/ProviderPicker.tsx +0 -76
- package/src/components/provider/RemoveProviderStep.tsx +0 -82
- package/src/components/repo/DiffViewer.tsx +0 -175
- package/src/components/repo/FileReviewer.tsx +0 -70
- package/src/components/repo/FileViewer.tsx +0 -60
- package/src/components/repo/IssueFixer.tsx +0 -666
- package/src/components/repo/LensFileMenu.tsx +0 -115
- package/src/components/repo/NoProviderPrompt.tsx +0 -28
- package/src/components/repo/PreviewRunner.tsx +0 -217
- package/src/components/repo/RepoAnalysis.tsx +0 -534
- package/src/components/task/TaskRunner.tsx +0 -396
- package/src/components/timeline/CommitDetail.tsx +0 -272
- package/src/components/timeline/CommitList.tsx +0 -162
- package/src/components/timeline/TimelineChat.tsx +0 -166
- package/src/components/timeline/TimelineRunner.tsx +0 -1285
- package/src/components/watch/RunRunner.tsx +0 -929
- package/src/prompts/fewshot.ts +0 -252
- package/src/prompts/index.ts +0 -2
- package/src/prompts/system.ts +0 -285
- package/src/tools/chart.ts +0 -202
- package/src/tools/convert-image.ts +0 -312
- package/src/tools/files.ts +0 -253
- package/src/tools/git.ts +0 -603
- package/src/tools/index.ts +0 -17
- package/src/tools/pdf.ts +0 -164
- package/src/tools/shell.ts +0 -96
- package/src/tools/view-image.ts +0 -335
- package/src/tools/web.ts +0 -212
- package/src/types/chat.ts +0 -86
- package/src/types/config.ts +0 -20
- package/src/types/repo.ts +0 -54
- package/src/utils/addons/loadAddons.ts +0 -34
- package/src/utils/ai.ts +0 -321
- package/src/utils/chat.ts +0 -326
- package/src/utils/chatHistory.ts +0 -121
- package/src/utils/config.ts +0 -61
- package/src/utils/files.ts +0 -105
- package/src/utils/intentClassifier.ts +0 -58
- package/src/utils/lensfile.ts +0 -142
- package/src/utils/llm.ts +0 -81
- package/src/utils/memory.ts +0 -209
- package/src/utils/preview.ts +0 -119
- package/src/utils/stats.ts +0 -174
- package/src/utils/tools/builtins.ts +0 -377
- package/src/utils/tools/registry.ts +0 -105
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import type { Message } from "../../../types/chat";
|
|
2
|
-
import {
|
|
3
|
-
saveChat,
|
|
4
|
-
loadChat,
|
|
5
|
-
listChats,
|
|
6
|
-
deleteChat,
|
|
7
|
-
} from "../../../utils/chatHistory";
|
|
8
|
-
import {
|
|
9
|
-
clearRepoMemory,
|
|
10
|
-
addMemory,
|
|
11
|
-
deleteMemory,
|
|
12
|
-
listMemories,
|
|
13
|
-
} from "../../../utils/memory";
|
|
14
|
-
|
|
15
|
-
export const COMMANDS = [
|
|
16
|
-
{ cmd: "/timeline", desc: "browse commit history" },
|
|
17
|
-
{ cmd: "/clear history", desc: "wipe session memory for this repo" },
|
|
18
|
-
{ cmd: "/review", desc: "review current codebase" },
|
|
19
|
-
{ cmd: "/auto", desc: "toggle auto-approve for read/search tools" },
|
|
20
|
-
{
|
|
21
|
-
cmd: "/auto --force-all",
|
|
22
|
-
desc: "auto-approve ALL tools including shell and writes (⚠ dangerous)",
|
|
23
|
-
},
|
|
24
|
-
{ cmd: "/chat", desc: "chat history commands" },
|
|
25
|
-
{ cmd: "/chat list", desc: "list saved chats for this repo" },
|
|
26
|
-
{ cmd: "/chat load", desc: "load a saved chat by name" },
|
|
27
|
-
{ cmd: "/chat rename", desc: "rename the current chat" },
|
|
28
|
-
{ cmd: "/chat delete", desc: "delete a saved chat by name" },
|
|
29
|
-
{ cmd: "/memory", desc: "memory commands" },
|
|
30
|
-
{ cmd: "/memory list", desc: "list all memories for this repo" },
|
|
31
|
-
{ cmd: "/memory add", desc: "add a memory" },
|
|
32
|
-
{ cmd: "/memory delete", desc: "delete a memory by id" },
|
|
33
|
-
{ cmd: "/memory clear", desc: "clear all memories for this repo" },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
type CommandContext = {
|
|
37
|
-
repoPath: string;
|
|
38
|
-
allMessages: Message[];
|
|
39
|
-
autoApprove: boolean;
|
|
40
|
-
forceApprove: boolean;
|
|
41
|
-
chatName: string | null;
|
|
42
|
-
chatNameRef: React.MutableRefObject<string | null>;
|
|
43
|
-
setShowTimeline: (v: boolean) => void;
|
|
44
|
-
setShowReview: (v: boolean) => void;
|
|
45
|
-
setShowForceWarning: (v: boolean) => void;
|
|
46
|
-
setForceApprove: (v: boolean) => void;
|
|
47
|
-
setAutoApprove: (v: boolean) => void;
|
|
48
|
-
setAllMessages: (fn: (prev: Message[]) => Message[]) => void;
|
|
49
|
-
setCommitted: (fn: (prev: Message[]) => Message[]) => void;
|
|
50
|
-
setRecentChats: (fn: (prev: string[]) => string[]) => void;
|
|
51
|
-
updateChatName: (name: string) => void;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
import React from "react";
|
|
55
|
-
|
|
56
|
-
function pushMsg(
|
|
57
|
-
msg: Message,
|
|
58
|
-
setCommitted: CommandContext["setCommitted"],
|
|
59
|
-
setAllMessages: CommandContext["setAllMessages"],
|
|
60
|
-
) {
|
|
61
|
-
setCommitted((prev) => [...prev, msg]);
|
|
62
|
-
setAllMessages((prev) => [...prev, msg]);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function makeMsg(content: string): Message {
|
|
66
|
-
return { role: "assistant", content, type: "text" };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Returns true if the command was handled, false if it should fall through
|
|
71
|
-
* to the normal message flow.
|
|
72
|
-
*/
|
|
73
|
-
export function handleCommand(text: string, ctx: CommandContext): boolean {
|
|
74
|
-
const t = text.trim().toLowerCase();
|
|
75
|
-
|
|
76
|
-
if (t === "/timeline") {
|
|
77
|
-
ctx.setShowTimeline(true);
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (t === "/review") {
|
|
82
|
-
ctx.setShowReview(true);
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (t === "/auto --force-all") {
|
|
87
|
-
if (ctx.forceApprove) {
|
|
88
|
-
ctx.setForceApprove(false);
|
|
89
|
-
ctx.setAutoApprove(false);
|
|
90
|
-
pushMsg(
|
|
91
|
-
makeMsg("Force-all mode OFF — tools will ask for permission again."),
|
|
92
|
-
ctx.setCommitted,
|
|
93
|
-
ctx.setAllMessages,
|
|
94
|
-
);
|
|
95
|
-
} else {
|
|
96
|
-
ctx.setShowForceWarning(true);
|
|
97
|
-
}
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (t === "/auto") {
|
|
102
|
-
if (ctx.forceApprove) {
|
|
103
|
-
ctx.setForceApprove(false);
|
|
104
|
-
ctx.setAutoApprove(true);
|
|
105
|
-
pushMsg(
|
|
106
|
-
makeMsg(
|
|
107
|
-
"Force-all mode OFF — switched to normal auto-approve (safe tools only).",
|
|
108
|
-
),
|
|
109
|
-
ctx.setCommitted,
|
|
110
|
-
ctx.setAllMessages,
|
|
111
|
-
);
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
const next = !ctx.autoApprove;
|
|
115
|
-
ctx.setAutoApprove(next);
|
|
116
|
-
pushMsg(
|
|
117
|
-
makeMsg(
|
|
118
|
-
next
|
|
119
|
-
? "Auto-approve ON — safe tools (read, search, fetch) will run without asking."
|
|
120
|
-
: "Auto-approve OFF — all tools will ask for permission.",
|
|
121
|
-
),
|
|
122
|
-
ctx.setCommitted,
|
|
123
|
-
ctx.setAllMessages,
|
|
124
|
-
);
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (t === "/clear history") {
|
|
129
|
-
clearRepoMemory(ctx.repoPath);
|
|
130
|
-
pushMsg(
|
|
131
|
-
makeMsg("History cleared for this repo."),
|
|
132
|
-
ctx.setCommitted,
|
|
133
|
-
ctx.setAllMessages,
|
|
134
|
-
);
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (t === "/chat") {
|
|
139
|
-
pushMsg(
|
|
140
|
-
makeMsg(
|
|
141
|
-
"Chat commands: `/chat list` · `/chat load <n>` · `/chat rename <n>` · `/chat delete <n>`",
|
|
142
|
-
),
|
|
143
|
-
ctx.setCommitted,
|
|
144
|
-
ctx.setAllMessages,
|
|
145
|
-
);
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (t.startsWith("/chat rename")) {
|
|
150
|
-
const parts = text.trim().split(/\s+/);
|
|
151
|
-
const newName = parts.slice(2).join("-");
|
|
152
|
-
if (!newName) {
|
|
153
|
-
pushMsg(
|
|
154
|
-
makeMsg("Usage: `/chat rename <new-name>`"),
|
|
155
|
-
ctx.setCommitted,
|
|
156
|
-
ctx.setAllMessages,
|
|
157
|
-
);
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
const oldName = ctx.chatNameRef.current;
|
|
161
|
-
if (oldName) deleteChat(oldName);
|
|
162
|
-
ctx.updateChatName(newName);
|
|
163
|
-
saveChat(newName, ctx.repoPath, ctx.allMessages);
|
|
164
|
-
ctx.setRecentChats((prev) =>
|
|
165
|
-
[newName, ...prev.filter((n) => n !== newName && n !== oldName)].slice(
|
|
166
|
-
0,
|
|
167
|
-
10,
|
|
168
|
-
),
|
|
169
|
-
);
|
|
170
|
-
pushMsg(
|
|
171
|
-
makeMsg(`Chat renamed to **${newName}**.`),
|
|
172
|
-
ctx.setCommitted,
|
|
173
|
-
ctx.setAllMessages,
|
|
174
|
-
);
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (t.startsWith("/chat delete")) {
|
|
179
|
-
const parts = text.trim().split(/\s+/);
|
|
180
|
-
const name = parts.slice(2).join("-");
|
|
181
|
-
if (!name) {
|
|
182
|
-
pushMsg(
|
|
183
|
-
makeMsg("Usage: `/chat delete <n>`"),
|
|
184
|
-
ctx.setCommitted,
|
|
185
|
-
ctx.setAllMessages,
|
|
186
|
-
);
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
const deleted = deleteChat(name);
|
|
190
|
-
if (!deleted) {
|
|
191
|
-
pushMsg(
|
|
192
|
-
makeMsg(`Chat **${name}** not found.`),
|
|
193
|
-
ctx.setCommitted,
|
|
194
|
-
ctx.setAllMessages,
|
|
195
|
-
);
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
if (ctx.chatNameRef.current === name) {
|
|
199
|
-
ctx.chatNameRef.current = null;
|
|
200
|
-
ctx.updateChatName("");
|
|
201
|
-
}
|
|
202
|
-
ctx.setRecentChats((prev) => prev.filter((n) => n !== name));
|
|
203
|
-
pushMsg(
|
|
204
|
-
makeMsg(`Chat **${name}** deleted.`),
|
|
205
|
-
ctx.setCommitted,
|
|
206
|
-
ctx.setAllMessages,
|
|
207
|
-
);
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (t === "/chat list") {
|
|
212
|
-
const chats = listChats(ctx.repoPath);
|
|
213
|
-
const content =
|
|
214
|
-
chats.length === 0
|
|
215
|
-
? "No saved chats for this repo yet."
|
|
216
|
-
: `Saved chats:\n\n${chats
|
|
217
|
-
.map(
|
|
218
|
-
(c) =>
|
|
219
|
-
`- **${c.name}** · ${c.userMessageCount} messages · ${new Date(c.savedAt).toLocaleString()}`,
|
|
220
|
-
)
|
|
221
|
-
.join("\n")}`;
|
|
222
|
-
pushMsg(makeMsg(content), ctx.setCommitted, ctx.setAllMessages);
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (t.startsWith("/chat load")) {
|
|
227
|
-
const parts = text.trim().split(/\s+/);
|
|
228
|
-
const name = parts.slice(2).join("-");
|
|
229
|
-
if (!name) {
|
|
230
|
-
const chats = listChats(ctx.repoPath);
|
|
231
|
-
const content =
|
|
232
|
-
chats.length === 0
|
|
233
|
-
? "No saved chats found."
|
|
234
|
-
: `Specify a chat name. Recent chats:\n\n${chats
|
|
235
|
-
.slice(0, 10)
|
|
236
|
-
.map((c) => `- **${c.name}**`)
|
|
237
|
-
.join("\n")}`;
|
|
238
|
-
pushMsg(makeMsg(content), ctx.setCommitted, ctx.setAllMessages);
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
const saved = loadChat(name);
|
|
242
|
-
if (!saved) {
|
|
243
|
-
pushMsg(
|
|
244
|
-
makeMsg(
|
|
245
|
-
`Chat **${name}** not found. Use \`/chat list\` to see saved chats.`,
|
|
246
|
-
),
|
|
247
|
-
ctx.setCommitted,
|
|
248
|
-
ctx.setAllMessages,
|
|
249
|
-
);
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
ctx.updateChatName(name);
|
|
253
|
-
|
|
254
|
-
ctx.setAllMessages(() => saved.messages);
|
|
255
|
-
ctx.setCommitted(() => saved.messages);
|
|
256
|
-
const notice = makeMsg(
|
|
257
|
-
`Loaded chat **${name}** · ${saved.userMessageCount} messages · saved ${new Date(saved.savedAt).toLocaleString()}`,
|
|
258
|
-
);
|
|
259
|
-
ctx.setCommitted((prev) => [...prev, notice]);
|
|
260
|
-
ctx.setAllMessages((prev) => [...prev, notice]);
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (t === "/memory list" || t === "/memory") {
|
|
265
|
-
const mems = listMemories(ctx.repoPath);
|
|
266
|
-
const content =
|
|
267
|
-
mems.length === 0
|
|
268
|
-
? "No memories stored for this repo yet."
|
|
269
|
-
: `Memories for this repo:\n\n${mems.map((m) => `- [${m.id}] ${m.content}`).join("\n")}`;
|
|
270
|
-
pushMsg(makeMsg(content), ctx.setCommitted, ctx.setAllMessages);
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (t.startsWith("/memory add")) {
|
|
275
|
-
const content = text.trim().slice("/memory add".length).trim();
|
|
276
|
-
if (!content) {
|
|
277
|
-
pushMsg(
|
|
278
|
-
makeMsg("Usage: `/memory add <content>`"),
|
|
279
|
-
ctx.setCommitted,
|
|
280
|
-
ctx.setAllMessages,
|
|
281
|
-
);
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
const mem = addMemory(content, ctx.repoPath);
|
|
285
|
-
pushMsg(
|
|
286
|
-
makeMsg(`Memory saved **[${mem.id}]**: ${mem.content}`),
|
|
287
|
-
ctx.setCommitted,
|
|
288
|
-
ctx.setAllMessages,
|
|
289
|
-
);
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (t.startsWith("/memory delete")) {
|
|
294
|
-
const id = text.trim().split(/\s+/)[2];
|
|
295
|
-
if (!id) {
|
|
296
|
-
pushMsg(
|
|
297
|
-
makeMsg("Usage: `/memory delete <id>`"),
|
|
298
|
-
ctx.setCommitted,
|
|
299
|
-
ctx.setAllMessages,
|
|
300
|
-
);
|
|
301
|
-
return true;
|
|
302
|
-
}
|
|
303
|
-
const deleted = deleteMemory(id, ctx.repoPath);
|
|
304
|
-
pushMsg(
|
|
305
|
-
makeMsg(
|
|
306
|
-
deleted
|
|
307
|
-
? `Memory **[${id}]** deleted.`
|
|
308
|
-
: `Memory **[${id}]** not found.`,
|
|
309
|
-
),
|
|
310
|
-
ctx.setCommitted,
|
|
311
|
-
ctx.setAllMessages,
|
|
312
|
-
);
|
|
313
|
-
return true;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (t === "/memory clear") {
|
|
317
|
-
clearRepoMemory(ctx.repoPath);
|
|
318
|
-
pushMsg(
|
|
319
|
-
makeMsg("All memories cleared for this repo."),
|
|
320
|
-
ctx.setCommitted,
|
|
321
|
-
ctx.setAllMessages,
|
|
322
|
-
);
|
|
323
|
-
return true;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import figures from "figures";
|
|
4
|
-
import { useEffect, useState } from "react";
|
|
5
|
-
import { loadConfig } from "../../utils/config";
|
|
6
|
-
import type { Provider } from "../../types/config";
|
|
7
|
-
|
|
8
|
-
export const ProviderPicker = ({
|
|
9
|
-
onDone,
|
|
10
|
-
}: {
|
|
11
|
-
onDone: (provider: Provider) => void;
|
|
12
|
-
}) => {
|
|
13
|
-
const [index, setIndex] = useState(0);
|
|
14
|
-
const config = loadConfig();
|
|
15
|
-
const providers = config.providers;
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
if (providers.length === 1) {
|
|
19
|
-
onDone(providers[0]!);
|
|
20
|
-
}
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
useInput((_, key) => {
|
|
24
|
-
if (providers.length <= 1) return;
|
|
25
|
-
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
26
|
-
if (key.downArrow) setIndex((i) => Math.min(providers.length - 1, i + 1));
|
|
27
|
-
if (key.return) {
|
|
28
|
-
onDone(providers[index]!);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (providers.length === 0) {
|
|
33
|
-
return (
|
|
34
|
-
<Box marginTop={1}>
|
|
35
|
-
<Text color="red">
|
|
36
|
-
{figures.cross} No providers configured. Run{" "}
|
|
37
|
-
<Text color="cyan">lens provider</Text> first.
|
|
38
|
-
</Text>
|
|
39
|
-
</Box>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (providers.length === 1) {
|
|
44
|
-
return (
|
|
45
|
-
<Box marginTop={1}>
|
|
46
|
-
<Text color="gray">
|
|
47
|
-
{figures.arrowRight} Using{" "}
|
|
48
|
-
<Text color="cyan">{providers[0]!.name}</Text>
|
|
49
|
-
</Text>
|
|
50
|
-
</Box>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<Box flexDirection="column" marginTop={1} gap={1}>
|
|
56
|
-
<Text bold color="cyan">
|
|
57
|
-
Select provider
|
|
58
|
-
</Text>
|
|
59
|
-
{providers.map((p, i) => (
|
|
60
|
-
<Box key={p.id} marginLeft={1}>
|
|
61
|
-
<Text color={i === index ? "cyan" : "white"}>
|
|
62
|
-
{i === index ? figures.arrowRight : " "}
|
|
63
|
-
{" "}
|
|
64
|
-
<Text bold={i === index}>{p.name}</Text>
|
|
65
|
-
<Text color="gray">
|
|
66
|
-
{" "}
|
|
67
|
-
{p.type} · {p.model}
|
|
68
|
-
{config.defaultProviderId === p.id ? " · default" : ""}
|
|
69
|
-
</Text>
|
|
70
|
-
</Text>
|
|
71
|
-
</Box>
|
|
72
|
-
))}
|
|
73
|
-
<Text color="gray">↑↓ navigate · enter to select</Text>
|
|
74
|
-
</Box>
|
|
75
|
-
);
|
|
76
|
-
};
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Box, Text, useInput } from "ink";
|
|
2
|
-
import figures from "figures";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { loadConfig, saveConfig } from "../../utils/config";
|
|
5
|
-
import type { Provider } from "../../types/config";
|
|
6
|
-
import { RED, TEXT } from "../../colors";
|
|
7
|
-
|
|
8
|
-
export const RemoveProviderStep = ({ onDone }: { onDone: () => void }) => {
|
|
9
|
-
const config = loadConfig();
|
|
10
|
-
const providers = config.providers;
|
|
11
|
-
const [index, setIndex] = useState(0);
|
|
12
|
-
const [confirming, setConfirming] = useState(false);
|
|
13
|
-
|
|
14
|
-
useInput((input, key) => {
|
|
15
|
-
if (confirming) {
|
|
16
|
-
if (input === "y" || input === "Y") {
|
|
17
|
-
const updated = {
|
|
18
|
-
...config,
|
|
19
|
-
providers: providers.filter((_, i) => i !== index),
|
|
20
|
-
defaultProviderId:
|
|
21
|
-
config.defaultProviderId === providers[index]?.id
|
|
22
|
-
? providers.find((_, i) => i !== index)?.id
|
|
23
|
-
: config.defaultProviderId,
|
|
24
|
-
};
|
|
25
|
-
saveConfig(updated);
|
|
26
|
-
onDone();
|
|
27
|
-
} else {
|
|
28
|
-
setConfirming(false);
|
|
29
|
-
}
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
34
|
-
if (key.downArrow) setIndex((i) => Math.min(providers.length - 1, i + 1));
|
|
35
|
-
if (key.return) setConfirming(true);
|
|
36
|
-
if (key.escape) onDone();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
if (providers.length === 0) {
|
|
40
|
-
return (
|
|
41
|
-
<Box marginTop={1}>
|
|
42
|
-
<Text color="gray">No providers configured.</Text>
|
|
43
|
-
</Box>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const selected = providers[index];
|
|
48
|
-
|
|
49
|
-
if (confirming && selected) {
|
|
50
|
-
return (
|
|
51
|
-
<Box flexDirection="column" gap={1} marginTop={1}>
|
|
52
|
-
<Text color={RED}>
|
|
53
|
-
{figures.warning} Remove <Text>{selected.name}</Text>? (y/n)
|
|
54
|
-
</Text>
|
|
55
|
-
</Box>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<Box flexDirection="column" gap={1} marginTop={1}>
|
|
61
|
-
<Text color={TEXT}>Remove a provider</Text>
|
|
62
|
-
{providers.map((p, i) => {
|
|
63
|
-
const isSelected = i === index;
|
|
64
|
-
return (
|
|
65
|
-
<Box key={p.id} marginLeft={1}>
|
|
66
|
-
<Text color={isSelected ? RED : "white"}>
|
|
67
|
-
{isSelected ? figures.arrowRight : " "}
|
|
68
|
-
{" "}
|
|
69
|
-
<Text>{p.name}</Text>
|
|
70
|
-
<Text color="gray">
|
|
71
|
-
{" "}
|
|
72
|
-
{p.type} · {p.model}
|
|
73
|
-
{config.defaultProviderId === p.id ? " · default" : ""}
|
|
74
|
-
</Text>
|
|
75
|
-
</Text>
|
|
76
|
-
</Box>
|
|
77
|
-
);
|
|
78
|
-
})}
|
|
79
|
-
<Text color="gray">↑↓ navigate · enter to remove · esc to cancel</Text>
|
|
80
|
-
</Box>
|
|
81
|
-
);
|
|
82
|
-
};
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import figures from "figures";
|
|
4
|
-
|
|
5
|
-
export type DiffLine = {
|
|
6
|
-
type: "added" | "removed" | "unchanged";
|
|
7
|
-
content: string;
|
|
8
|
-
lineNum: number;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type FilePatch = {
|
|
12
|
-
path: string;
|
|
13
|
-
content: string;
|
|
14
|
-
isNew: boolean;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export function computeDiff(
|
|
18
|
-
oldContent: string,
|
|
19
|
-
newContent: string,
|
|
20
|
-
): DiffLine[] {
|
|
21
|
-
const oldLines = oldContent.split("\n");
|
|
22
|
-
const newLines = newContent.split("\n");
|
|
23
|
-
const result: DiffLine[] = [];
|
|
24
|
-
|
|
25
|
-
const m = oldLines.length;
|
|
26
|
-
const n = newLines.length;
|
|
27
|
-
|
|
28
|
-
const dp: number[][] = Array.from({ length: m + 1 }, () =>
|
|
29
|
-
new Array(n + 1).fill(0),
|
|
30
|
-
);
|
|
31
|
-
for (let i = 1; i <= m; i++) {
|
|
32
|
-
for (let j = 1; j <= n; j++) {
|
|
33
|
-
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
34
|
-
dp[i]![j] = dp[i - 1]![j - 1]! + 1;
|
|
35
|
-
} else {
|
|
36
|
-
dp[i]![j] = Math.max(dp[i - 1]![j]!, dp[i]![j - 1]!);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const raw: DiffLine[] = [];
|
|
42
|
-
let i = m;
|
|
43
|
-
let j = n;
|
|
44
|
-
|
|
45
|
-
while (i > 0 || j > 0) {
|
|
46
|
-
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
47
|
-
raw.unshift({ type: "unchanged", content: oldLines[i - 1]!, lineNum: j });
|
|
48
|
-
i--;
|
|
49
|
-
j--;
|
|
50
|
-
} else if (j > 0 && (i === 0 || dp[i]![j - 1]! >= dp[i - 1]![j]!)) {
|
|
51
|
-
raw.unshift({ type: "added", content: newLines[j - 1]!, lineNum: j });
|
|
52
|
-
j--;
|
|
53
|
-
} else {
|
|
54
|
-
raw.unshift({ type: "removed", content: oldLines[i - 1]!, lineNum: i });
|
|
55
|
-
i--;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const CONTEXT = 3;
|
|
60
|
-
const changedIndices = new Set<number>();
|
|
61
|
-
raw.forEach((line, idx) => {
|
|
62
|
-
if (line.type !== "unchanged") {
|
|
63
|
-
for (
|
|
64
|
-
let k = Math.max(0, idx - CONTEXT);
|
|
65
|
-
k <= Math.min(raw.length - 1, idx + CONTEXT);
|
|
66
|
-
k++
|
|
67
|
-
) {
|
|
68
|
-
changedIndices.add(k);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
let lastIncluded = -1;
|
|
74
|
-
raw.forEach((line, idx) => {
|
|
75
|
-
if (!changedIndices.has(idx)) return;
|
|
76
|
-
if (lastIncluded !== -1 && idx > lastIncluded + 1) {
|
|
77
|
-
result.push({ type: "unchanged", content: "...", lineNum: -1 });
|
|
78
|
-
}
|
|
79
|
-
result.push(line);
|
|
80
|
-
lastIncluded = idx;
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function buildDiffs(
|
|
87
|
-
repoPath: string,
|
|
88
|
-
patches: FilePatch[],
|
|
89
|
-
): DiffLine[][] {
|
|
90
|
-
const { readFileSync } = require("fs") as typeof import("fs");
|
|
91
|
-
const path = require("path") as typeof import("path");
|
|
92
|
-
|
|
93
|
-
return patches.map((patch) => {
|
|
94
|
-
if (patch.isNew) {
|
|
95
|
-
return patch.content.split("\n").map((line, i) => ({
|
|
96
|
-
type: "added" as const,
|
|
97
|
-
content: line,
|
|
98
|
-
lineNum: i + 1,
|
|
99
|
-
}));
|
|
100
|
-
}
|
|
101
|
-
const fullPath = path.join(repoPath, patch.path);
|
|
102
|
-
let oldContent = "";
|
|
103
|
-
try {
|
|
104
|
-
oldContent = readFileSync(fullPath, "utf-8");
|
|
105
|
-
} catch {}
|
|
106
|
-
return computeDiff(oldContent, patch.content);
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export const DiffViewer = ({
|
|
111
|
-
patches,
|
|
112
|
-
diffs,
|
|
113
|
-
scrollOffset,
|
|
114
|
-
maxVisible = 20,
|
|
115
|
-
}: {
|
|
116
|
-
patches: FilePatch[];
|
|
117
|
-
diffs: DiffLine[][];
|
|
118
|
-
scrollOffset: number;
|
|
119
|
-
maxVisible?: number;
|
|
120
|
-
}) => {
|
|
121
|
-
const allLines: {
|
|
122
|
-
fileIdx: number;
|
|
123
|
-
fileName: string;
|
|
124
|
-
line: DiffLine | null;
|
|
125
|
-
}[] = [];
|
|
126
|
-
|
|
127
|
-
patches.forEach((patch, fi) => {
|
|
128
|
-
allLines.push({ fileIdx: fi, fileName: patch.path, line: null });
|
|
129
|
-
(diffs[fi] ?? []).forEach((line) => {
|
|
130
|
-
allLines.push({ fileIdx: fi, fileName: patch.path, line });
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const visible = allLines.slice(scrollOffset, scrollOffset + maxVisible);
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<Box flexDirection="column" gap={0}>
|
|
138
|
-
{visible.map((entry, i) => {
|
|
139
|
-
if (!entry.line) {
|
|
140
|
-
return (
|
|
141
|
-
<Box key={`header-${entry.fileIdx}-${i}`}>
|
|
142
|
-
<Text bold color={entry.fileIdx % 2 === 0 ? "cyan" : "magenta"}>
|
|
143
|
-
{figures.bullet} {entry.fileName}
|
|
144
|
-
{patches[entry.fileIdx]?.isNew ? " (new file)" : ""}
|
|
145
|
-
</Text>
|
|
146
|
-
</Box>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const { type, content, lineNum } = entry.line;
|
|
151
|
-
const prefix = type === "added" ? "+" : type === "removed" ? "-" : " ";
|
|
152
|
-
const color =
|
|
153
|
-
type === "added" ? "green" : type === "removed" ? "red" : "gray";
|
|
154
|
-
const lineNumStr =
|
|
155
|
-
lineNum === -1 ? " " : String(lineNum).padStart(3, " ");
|
|
156
|
-
|
|
157
|
-
return (
|
|
158
|
-
<Box key={`line-${entry.fileIdx}-${i}`}>
|
|
159
|
-
<Text color="gray">{lineNumStr} </Text>
|
|
160
|
-
<Text color={color}>
|
|
161
|
-
{prefix} {content}
|
|
162
|
-
</Text>
|
|
163
|
-
</Box>
|
|
164
|
-
);
|
|
165
|
-
})}
|
|
166
|
-
{allLines.length > maxVisible && (
|
|
167
|
-
<Text color="gray">
|
|
168
|
-
{scrollOffset + maxVisible < allLines.length
|
|
169
|
-
? `↓ ${allLines.length - scrollOffset - maxVisible} more lines`
|
|
170
|
-
: "end of diff"}
|
|
171
|
-
</Text>
|
|
172
|
-
)}
|
|
173
|
-
</Box>
|
|
174
|
-
);
|
|
175
|
-
};
|