@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.
Files changed (96) hide show
  1. package/dist/index.mjs +105368 -274002
  2. package/package.json +13 -19
  3. package/src/colors.ts +15 -15
  4. package/src/commands/chat.tsx +32 -23
  5. package/src/commands/provider.tsx +11 -238
  6. package/src/commands/repo.tsx +66 -120
  7. package/src/commands/timeline.tsx +11 -22
  8. package/src/components/ChatView.tsx +238 -0
  9. package/src/components/Message.tsx +46 -0
  10. package/src/components/ToolCall.tsx +67 -0
  11. package/src/components/chat/ChatView.tsx +550 -0
  12. package/src/components/chat/Message.tsx +152 -0
  13. package/src/components/chat/StatusBar.tsx +214 -0
  14. package/src/components/chat/TextArea.tsx +173 -176
  15. package/src/components/provider/ApiKeyStep.tsx +207 -199
  16. package/src/components/provider/ModelStep.tsx +90 -88
  17. package/src/components/provider/ProviderSetup.tsx +331 -0
  18. package/src/components/provider/ProviderTypeStep.tsx +53 -61
  19. package/src/components/repo/StepRow.tsx +68 -69
  20. package/src/components/timeline/TimelineView.tsx +840 -0
  21. package/src/components/toolcall-utils.ts +103 -0
  22. package/src/components/watch/RunView.tsx +497 -0
  23. package/src/hooks/useChatInput.ts +49 -0
  24. package/src/hooks/useCommandHandler.ts +117 -0
  25. package/src/index.tsx +386 -139
  26. package/src/utils/git.ts +149 -155
  27. package/src/utils/repo.ts +62 -69
  28. package/src/utils/thinking.tsx +64 -0
  29. package/src/utils/watch.ts +165 -307
  30. package/tests/message.test.ts +38 -0
  31. package/tests/toolcall-utils.test.ts +111 -0
  32. package/tsconfig.json +8 -24
  33. package/CLAUDE.md +0 -50
  34. package/LENS.md +0 -48
  35. package/LICENSE +0 -21
  36. package/README.md +0 -93
  37. package/addons/README.md +0 -55
  38. package/addons/clean-cache.js +0 -48
  39. package/addons/generate-readme.js +0 -67
  40. package/addons/git-stats.js +0 -29
  41. package/addons/run-tests.js +0 -127
  42. package/src/commands/commit.tsx +0 -668
  43. package/src/commands/review.tsx +0 -294
  44. package/src/commands/run.tsx +0 -56
  45. package/src/commands/task.tsx +0 -36
  46. package/src/components/chat/ChatMessage.tsx +0 -195
  47. package/src/components/chat/ChatOverlays.tsx +0 -399
  48. package/src/components/chat/ChatRunner.tsx +0 -517
  49. package/src/components/chat/hooks/useChat.ts +0 -631
  50. package/src/components/chat/hooks/useChatInput.ts +0 -79
  51. package/src/components/chat/hooks/useCommandHandlers.ts +0 -327
  52. package/src/components/provider/ProviderPicker.tsx +0 -76
  53. package/src/components/provider/RemoveProviderStep.tsx +0 -82
  54. package/src/components/repo/DiffViewer.tsx +0 -175
  55. package/src/components/repo/FileReviewer.tsx +0 -70
  56. package/src/components/repo/FileViewer.tsx +0 -60
  57. package/src/components/repo/IssueFixer.tsx +0 -666
  58. package/src/components/repo/LensFileMenu.tsx +0 -115
  59. package/src/components/repo/NoProviderPrompt.tsx +0 -28
  60. package/src/components/repo/PreviewRunner.tsx +0 -217
  61. package/src/components/repo/RepoAnalysis.tsx +0 -534
  62. package/src/components/task/TaskRunner.tsx +0 -396
  63. package/src/components/timeline/CommitDetail.tsx +0 -272
  64. package/src/components/timeline/CommitList.tsx +0 -162
  65. package/src/components/timeline/TimelineChat.tsx +0 -166
  66. package/src/components/timeline/TimelineRunner.tsx +0 -1285
  67. package/src/components/watch/RunRunner.tsx +0 -929
  68. package/src/prompts/fewshot.ts +0 -252
  69. package/src/prompts/index.ts +0 -2
  70. package/src/prompts/system.ts +0 -285
  71. package/src/tools/chart.ts +0 -202
  72. package/src/tools/convert-image.ts +0 -312
  73. package/src/tools/files.ts +0 -253
  74. package/src/tools/git.ts +0 -603
  75. package/src/tools/index.ts +0 -17
  76. package/src/tools/pdf.ts +0 -164
  77. package/src/tools/shell.ts +0 -96
  78. package/src/tools/view-image.ts +0 -335
  79. package/src/tools/web.ts +0 -212
  80. package/src/types/chat.ts +0 -86
  81. package/src/types/config.ts +0 -20
  82. package/src/types/repo.ts +0 -54
  83. package/src/utils/addons/loadAddons.ts +0 -34
  84. package/src/utils/ai.ts +0 -321
  85. package/src/utils/chat.ts +0 -326
  86. package/src/utils/chatHistory.ts +0 -121
  87. package/src/utils/config.ts +0 -61
  88. package/src/utils/files.ts +0 -105
  89. package/src/utils/intentClassifier.ts +0 -58
  90. package/src/utils/lensfile.ts +0 -142
  91. package/src/utils/llm.ts +0 -81
  92. package/src/utils/memory.ts +0 -209
  93. package/src/utils/preview.ts +0 -119
  94. package/src/utils/stats.ts +0 -174
  95. package/src/utils/tools/builtins.ts +0 -377
  96. package/src/utils/tools/registry.ts +0 -105
@@ -1,534 +0,0 @@
1
- import React from "react";
2
- import { Box, Text, Static, useInput } from "ink";
3
- import Spinner from "ink-spinner";
4
- import figures from "figures";
5
- import { useState, useRef } from "react";
6
- import { writeFileSync } from "fs";
7
- import path from "path";
8
- import { ACCENT } from "../../colors";
9
- import {
10
- requestFileList,
11
- analyzeRepo,
12
- extractToolingPatch,
13
- } from "../../utils/ai";
14
- import { ProviderPicker } from "../provider/ProviderPicker";
15
- import { PreviewRunner } from "./PreviewRunner";
16
- import { IssueFixer } from "./IssueFixer";
17
- import { writeLensFile, patchLensFile } from "../../utils/lensfile";
18
- import { callChat } from "../../utils/chat";
19
- import { StaticMessage } from "../chat/ChatMessage";
20
- import { InputBox, TypewriterText, ShortcutBar } from "../chat/ChatOverlays";
21
- import type { Provider } from "../../types/config";
22
- import type { AnalysisResult, ImportantFile } from "../../types/repo";
23
- import type { Message } from "../../types/chat";
24
- import { useThinkingPhrase } from "../../utils/thinking";
25
-
26
- type AnalysisStage =
27
- | { type: "picking-provider" }
28
- | { type: "requesting-files" }
29
- | { type: "analyzing" }
30
- | { type: "done"; result: AnalysisResult }
31
- | { type: "writing" }
32
- | { type: "written"; filePath: string }
33
- | { type: "previewing" }
34
- | { type: "fixing"; result: AnalysisResult }
35
- | { type: "asking"; result: AnalysisResult }
36
- | { type: "error"; message: string };
37
-
38
- const OUTPUT_FILES = ["CLAUDE.md", "copilot-instructions.md"] as const;
39
- type OutputFile = (typeof OUTPUT_FILES)[number];
40
-
41
- function buildMarkdown(repoUrl: string, result: AnalysisResult): string {
42
- const toolingLines = Object.entries(result.tooling ?? {})
43
- .map(([k, v]) => `- **${k}**: ${v}`)
44
- .join("\n");
45
-
46
- return `# Repository Analysis
47
-
48
- > ${repoUrl}
49
-
50
- ## Overview
51
- ${result.overview}
52
-
53
- ## Architecture
54
- ${result.architecture ?? ""}
55
-
56
- ## Tooling
57
- ${toolingLines || "- Not determined"}
58
-
59
- ## Important Folders
60
- ${result.importantFolders.map((f) => `- ${f}`).join("\n")}
61
-
62
- ## Key Files
63
- ${(result.keyFiles ?? []).map((f) => `- ${f}`).join("\n")}
64
-
65
- ## Patterns & Idioms
66
- ${(result.patterns ?? []).map((p) => `- ${p}`).join("\n")}
67
-
68
- ## Suggestions
69
- ${result.suggestions.map((s) => `- ${s}`).join("\n")}
70
- `;
71
- }
72
-
73
- function buildQASystemPrompt(repoUrl: string, result: AnalysisResult): string {
74
- const toolingLines = Object.entries(result.tooling ?? {})
75
- .map(([k, v]) => `- ${k}: ${v}`)
76
- .join("\n");
77
-
78
- return `You are a codebase assistant for the repository at ${repoUrl}.
79
-
80
- Here is what you know about this codebase:
81
-
82
- Overview:
83
- ${result.overview}
84
-
85
- Architecture:
86
- ${result.architecture ?? "Not determined"}
87
-
88
- Tooling:
89
- ${toolingLines || "Not determined"}
90
-
91
- Important Folders:
92
- ${result.importantFolders.map((f) => `- ${f}`).join("\n")}
93
-
94
- Key Files:
95
- ${(result.keyFiles ?? []).map((f) => `- ${f}`).join("\n")}
96
-
97
- Patterns & Idioms:
98
- ${(result.patterns ?? []).map((p) => `- ${p}`).join("\n")}
99
-
100
- Answer questions about this codebase concisely and accurately. If you're unsure about something not covered in the analysis, say so clearly rather than guessing.`;
101
- }
102
-
103
- function AskingFilesStep() {
104
- const phrase = useThinkingPhrase(true, "model");
105
- return (
106
- <Box gap={1}>
107
- <Text color={ACCENT}>
108
- <Spinner />
109
- </Text>
110
- <Text color={ACCENT}>{phrase}</Text>
111
- </Box>
112
- );
113
- }
114
-
115
- function AnalyzingStep() {
116
- const phrase = useThinkingPhrase(true, "summary");
117
- return (
118
- <Box gap={1}>
119
- <Text color={ACCENT}>
120
- <Spinner />
121
- </Text>
122
- <Text color={ACCENT}>{phrase}</Text>
123
- </Box>
124
- );
125
- }
126
-
127
- // ─── CodebaseQA ──────────────────────────────────────────────────────────────
128
-
129
- type QAStage = "idle" | "thinking";
130
-
131
- function CodebaseQA({
132
- repoUrl,
133
- result,
134
- provider,
135
- onExit,
136
- }: {
137
- repoUrl: string;
138
- result: AnalysisResult;
139
- provider: Provider;
140
- onExit: () => void;
141
- }) {
142
- const [committed, setCommitted] = useState<Message[]>([]);
143
- const [allMessages, setAllMessages] = useState<Message[]>([]);
144
- const [inputValue, setInputValue] = useState("");
145
- const [inputKey, setInputKey] = useState(0);
146
- const [qaStage, setQaStage] = useState<QAStage>("idle");
147
- const abortRef = useRef<AbortController | null>(null);
148
- const systemPrompt = buildQASystemPrompt(repoUrl, result);
149
- const thinkingPhrase = useThinkingPhrase(qaStage === "thinking");
150
-
151
- useInput((_, key) => {
152
- if (key.escape) {
153
- if (qaStage === "thinking") {
154
- abortRef.current?.abort();
155
- abortRef.current = null;
156
- setQaStage("idle");
157
- return;
158
- }
159
- onExit();
160
- }
161
- });
162
-
163
- const sendQuestion = (text: string) => {
164
- const trimmed = text.trim();
165
- if (!trimmed) return;
166
-
167
- const userMsg: Message = { role: "user", type: "text", content: trimmed };
168
- const nextAll = [...allMessages, userMsg];
169
- setCommitted((prev) => [...prev, userMsg]);
170
- setAllMessages(nextAll);
171
- setQaStage("thinking");
172
-
173
- const abort = new AbortController();
174
- abortRef.current = abort;
175
-
176
- callChat(provider, systemPrompt, nextAll, abort.signal)
177
- .then((result) => {
178
- const assistantMsg: Message = {
179
- role: "assistant",
180
- type: "text",
181
- content: result.text,
182
- };
183
- setCommitted((prev) => [...prev, assistantMsg]);
184
- setAllMessages([...nextAll, assistantMsg]);
185
- setQaStage("idle");
186
- })
187
- .catch((err: unknown) => {
188
- if (err instanceof Error && err.name === "AbortError") {
189
- setQaStage("idle");
190
- return;
191
- }
192
- const errMsg: Message = {
193
- role: "assistant",
194
- type: "text",
195
- content: `Error: ${err instanceof Error ? err.message : "Request failed"}`,
196
- };
197
- setCommitted((prev) => [...prev, errMsg]);
198
- setAllMessages([...nextAll, errMsg]);
199
- setQaStage("idle");
200
- });
201
- };
202
-
203
- return (
204
- <Box flexDirection="column">
205
- <Static items={committed}>
206
- {(msg, i) => <StaticMessage key={i} msg={msg} />}
207
- </Static>
208
-
209
- {qaStage === "thinking" && (
210
- <Box gap={1}>
211
- <Text color={ACCENT}>●</Text>
212
- <TypewriterText text={thinkingPhrase} />
213
- <Text color="gray" dimColor>
214
- · esc cancel
215
- </Text>
216
- </Box>
217
- )}
218
-
219
- {qaStage === "idle" && (
220
- <Box flexDirection="column">
221
- <InputBox
222
- value={inputValue}
223
- onChange={setInputValue}
224
- onSubmit={(val) => {
225
- if (val.trim()) sendQuestion(val.trim());
226
- setInputValue("");
227
- setInputKey((k) => k + 1);
228
- }}
229
- inputKey={inputKey}
230
- />
231
- <Text color="gray" dimColor>
232
- enter send · esc back
233
- </Text>
234
- </Box>
235
- )}
236
- </Box>
237
- );
238
- }
239
-
240
- // ─── RepoAnalysis ─────────────────────────────────────────────────────────────
241
-
242
- export const RepoAnalysis = ({
243
- repoUrl,
244
- repoPath,
245
- fileTree,
246
- files: initialFiles,
247
- preloadedResult,
248
- onExit,
249
- }: {
250
- repoUrl: string;
251
- repoPath: string;
252
- fileTree: string[];
253
- files: ImportantFile[];
254
- preloadedResult?: AnalysisResult;
255
- onExit?: () => void;
256
- }) => {
257
- const [stage, setStage] = useState<AnalysisStage>(
258
- preloadedResult
259
- ? { type: "done", result: preloadedResult }
260
- : { type: "picking-provider" },
261
- );
262
- const [selectedOutput, setSelectedOutput] = useState<0 | 1 | 2 | 3 | 4>(0);
263
- const [requestedFiles, setRequestedFiles] = useState<ImportantFile[]>([]);
264
- const [provider, setProvider] = useState<Provider | null>(null);
265
-
266
- const OPTIONS = [
267
- ...OUTPUT_FILES,
268
- "Preview repo",
269
- "Fix issues",
270
- "Ask questions",
271
- ] as const;
272
-
273
- const handleProviderDone = (p: Provider) => {
274
- setProvider(p);
275
- setStage({ type: "requesting-files" });
276
-
277
- requestFileList(repoUrl, repoPath, fileTree, p)
278
- .then((files) => {
279
- setRequestedFiles(files);
280
-
281
- extractToolingPatch(repoUrl, files.length > 0 ? files : initialFiles, p)
282
- .then((patch) => {
283
- if (patch) patchLensFile(repoPath, patch);
284
- })
285
- .catch(() => {});
286
-
287
- setStage({ type: "analyzing" });
288
- return analyzeRepo(repoUrl, files.length > 0 ? files : initialFiles, p);
289
- })
290
- .then((result) => {
291
- writeLensFile(repoPath, result);
292
- setStage({ type: "done", result });
293
- })
294
- .catch((err: unknown) =>
295
- setStage({
296
- type: "error",
297
- message: err instanceof Error ? err.message : "Analysis failed",
298
- }),
299
- );
300
- };
301
-
302
- useInput((_, key) => {
303
- if (stage.type !== "done") return;
304
- if (key.leftArrow)
305
- setSelectedOutput((i) => Math.max(0, i - 1) as 0 | 1 | 2 | 3 | 4);
306
- if (key.rightArrow)
307
- setSelectedOutput(
308
- (i) => Math.min(OPTIONS.length - 1, i + 1) as 0 | 1 | 2 | 3 | 4,
309
- );
310
- if (key.return) {
311
- if (selectedOutput === 2) {
312
- setStage({ type: "previewing" });
313
- return;
314
- }
315
- if (selectedOutput === 3) {
316
- setStage({ type: "fixing", result: stage.result });
317
- return;
318
- }
319
- if (selectedOutput === 4) {
320
- setStage({ type: "asking", result: stage.result });
321
- return;
322
- }
323
- const fileName = OUTPUT_FILES[selectedOutput] as OutputFile;
324
- setStage({ type: "writing" });
325
- try {
326
- const filePath = path.join(repoPath, fileName);
327
- writeFileSync(filePath, buildMarkdown(repoUrl, stage.result), "utf-8");
328
- setStage({ type: "written", filePath });
329
- } catch (err: unknown) {
330
- setStage({
331
- type: "error",
332
- message: err instanceof Error ? err.message : "Write failed",
333
- });
334
- }
335
- }
336
- if (key.escape) setStage({ type: "written", filePath: "" });
337
- });
338
-
339
- if (stage.type === "picking-provider") {
340
- return <ProviderPicker onDone={handleProviderDone} />;
341
- }
342
-
343
- if (stage.type === "requesting-files") {
344
- return <AskingFilesStep />;
345
- }
346
-
347
- if (stage.type === "analyzing") {
348
- return (
349
- <Box flexDirection="column" marginTop={1} gap={1}>
350
- <AnalyzingStep />
351
- {requestedFiles.length > 0 && (
352
- <Box flexDirection="column" marginLeft={2}>
353
- <Text color="gray">Reading {requestedFiles.length} files:</Text>
354
- {requestedFiles.map((f) => (
355
- <Text key={f.path} color="gray">
356
- {figures.bullet} {f.path}
357
- </Text>
358
- ))}
359
- </Box>
360
- )}
361
- </Box>
362
- );
363
- }
364
-
365
- if (stage.type === "writing") {
366
- return (
367
- <Box marginTop={1}>
368
- <Text color={ACCENT}>
369
- <Spinner />
370
- </Text>
371
- <Box marginLeft={1}>
372
- <Text>Writing file...</Text>
373
- </Box>
374
- </Box>
375
- );
376
- }
377
-
378
- if (stage.type === "written") {
379
- setTimeout(() => {
380
- if (onExit) onExit();
381
- else process.exit(0);
382
- }, 100);
383
- return (
384
- <Text color="green">
385
- {figures.tick}{" "}
386
- {stage.filePath ? `Written to ${stage.filePath}` : "Skipped"}
387
- </Text>
388
- );
389
- }
390
-
391
- if (stage.type === "previewing") {
392
- return (
393
- <Box flexDirection="column">
394
- <Text color="cyan" bold>
395
- {figures.play} Preview — {repoPath}
396
- </Text>
397
- <PreviewRunner
398
- repoPath={repoPath}
399
- onExit={() => {
400
- setTimeout(() => {
401
- if (onExit) onExit();
402
- else process.exit(0);
403
- }, 100);
404
- }}
405
- />
406
- </Box>
407
- );
408
- }
409
-
410
- if (stage.type === "fixing") {
411
- return (
412
- <IssueFixer
413
- repoPath={repoPath}
414
- result={stage.result}
415
- requestedFiles={requestedFiles}
416
- provider={provider!}
417
- onDone={() => setStage({ type: "done", result: stage.result })}
418
- />
419
- );
420
- }
421
-
422
- if (stage.type === "asking") {
423
- return (
424
- <CodebaseQA
425
- repoUrl={repoUrl}
426
- result={stage.result}
427
- provider={provider!}
428
- onExit={() => setStage({ type: "done", result: stage.result })}
429
- />
430
- );
431
- }
432
-
433
- if (stage.type === "error") {
434
- return (
435
- <Text color="red">
436
- {figures.cross} {stage.message}
437
- </Text>
438
- );
439
- }
440
-
441
- const { result } = stage;
442
-
443
- return (
444
- <Box flexDirection="column" marginTop={1} gap={1}>
445
- <Box flexDirection="column">
446
- <Text bold color="cyan">
447
- {figures.info} Overview
448
- </Text>
449
- <Text color="white">{result.overview}</Text>
450
- </Box>
451
-
452
- <Box flexDirection="column">
453
- <Text bold color="cyan">
454
- {figures.pointerSmall} Architecture
455
- </Text>
456
- <Text color="white">{result.architecture}</Text>
457
- </Box>
458
-
459
- <Box flexDirection="column">
460
- <Text bold color="cyan">
461
- {figures.pointerSmall} Tooling
462
- </Text>
463
- {Object.entries(result.tooling ?? {}).map(([k, v]) => (
464
- <Text key={k} color="white">
465
- {" "}
466
- {figures.bullet} <Text bold>{k}</Text>: {v}
467
- </Text>
468
- ))}
469
- </Box>
470
-
471
- <Box flexDirection="column">
472
- <Text bold color="cyan">
473
- {figures.pointerSmall} Important Folders
474
- </Text>
475
- {result.importantFolders.map((f) => (
476
- <Text key={f} color="white">
477
- {" "}
478
- {figures.bullet} {f}
479
- </Text>
480
- ))}
481
- </Box>
482
-
483
- <Box flexDirection="column">
484
- <Text bold color="cyan">
485
- {figures.pointerSmall} Key Files
486
- </Text>
487
- {(result.keyFiles ?? []).map((f) => (
488
- <Text key={f} color="white">
489
- {" "}
490
- {figures.bullet} {f}
491
- </Text>
492
- ))}
493
- </Box>
494
-
495
- <Box flexDirection="column">
496
- <Text bold color="cyan">
497
- {figures.pointerSmall} Patterns & Idioms
498
- </Text>
499
- {(result.patterns ?? []).map((p) => (
500
- <Text key={p} color="white">
501
- {" "}
502
- {figures.bullet} {p}
503
- </Text>
504
- ))}
505
- </Box>
506
-
507
- <Box flexDirection="column">
508
- <Text bold color="green">
509
- {figures.tick} Suggestions
510
- </Text>
511
- {result.suggestions.map((s) => (
512
- <Text key={s} color="white">
513
- {" "}
514
- {figures.bullet} {s}
515
- </Text>
516
- ))}
517
- </Box>
518
-
519
- <Box flexDirection="column" marginTop={1} gap={1}>
520
- <Text bold color="cyan">
521
- Actions
522
- </Text>
523
- <Box gap={2}>
524
- {OPTIONS.map((f, i) => (
525
- <Text key={f} color={selectedOutput === i ? "cyan" : "gray"}>
526
- {selectedOutput === i ? figures.arrowRight : " "} {f}
527
- </Text>
528
- ))}
529
- </Box>
530
- <Text color="gray">← → switch · enter to select · esc to skip</Text>
531
- </Box>
532
- </Box>
533
- );
534
- };