@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,209 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
- import path from "path";
3
- import os from "os";
4
-
5
- export type MemoryEntryKind =
6
- | "file-written"
7
- | "file-read"
8
- | "url-fetched"
9
- | "shell-run"
10
- | "code-applied"
11
- | "code-skipped";
12
-
13
- export type MemoryEntry = {
14
- kind: MemoryEntryKind;
15
- detail: string;
16
- summary: string;
17
- timestamp: string;
18
- repoPath?: string;
19
- };
20
-
21
- export type Memory = {
22
- id: string;
23
- content: string;
24
- timestamp: string;
25
- repoPath?: string;
26
- scope: "repo" | "global";
27
- };
28
-
29
- export type MemoryFile = {
30
- memories: Memory[];
31
- };
32
-
33
- const LENS_DIR = path.join(os.homedir(), ".lens");
34
- const MEMORY_PATH = path.join(LENS_DIR, "memory.json");
35
-
36
- function loadMemoryFile(): MemoryFile {
37
- if (!existsSync(MEMORY_PATH)) return { memories: [] };
38
- try {
39
- const data = JSON.parse(
40
- readFileSync(MEMORY_PATH, "utf-8"),
41
- ) as Partial<MemoryFile>;
42
- return { memories: data.memories ?? [] };
43
- } catch {
44
- return { memories: [] };
45
- }
46
- }
47
-
48
- function saveMemoryFile(m: MemoryFile): void {
49
- if (!existsSync(LENS_DIR)) mkdirSync(LENS_DIR, { recursive: true });
50
- writeFileSync(MEMORY_PATH, JSON.stringify(m, null, 2), "utf-8");
51
- }
52
-
53
- // ── Session-only action entries (in-memory, never written to disk) ────────────
54
-
55
- const sessionEntries: MemoryEntry[] = [];
56
-
57
- export function appendMemory(
58
- entry: Omit<MemoryEntry, "timestamp">,
59
- repoPath?: string,
60
- ): void {
61
- sessionEntries.push({
62
- ...entry,
63
- repoPath,
64
- timestamp: new Date().toISOString(),
65
- });
66
- if (sessionEntries.length > 200)
67
- sessionEntries.splice(0, sessionEntries.length - 200);
68
- }
69
-
70
- export function buildMemorySummary(repoPath: string): string {
71
- const m = loadMemoryFile();
72
-
73
- const globalMemories = m.memories.filter((mem) => mem.scope === "global");
74
- const repoMemories = m.memories.filter(
75
- (mem) => mem.scope === "repo" && mem.repoPath === repoPath,
76
- );
77
-
78
- const relevant = sessionEntries
79
- .filter((e) => !e.repoPath || e.repoPath === repoPath)
80
- .slice(-50);
81
-
82
- const parts: string[] = [];
83
-
84
- if (globalMemories.length > 0) {
85
- parts.push(
86
- `## GLOBAL MEMORIES (apply to all repos)\n\n${globalMemories
87
- .map((mem) => `- [${mem.id}] ${mem.content}`)
88
- .join("\n")}`,
89
- );
90
- }
91
-
92
- if (repoMemories.length > 0) {
93
- parts.push(
94
- `## MEMORIES ABOUT THIS REPO\n\n${repoMemories
95
- .map((mem) => `- [${mem.id}] ${mem.content}`)
96
- .join("\n")}`,
97
- );
98
- }
99
-
100
- if (relevant.length > 0) {
101
- const lines = relevant.map((e) => {
102
- const ts = new Date(e.timestamp).toLocaleString();
103
- return `[${ts}] ${e.kind}: ${e.detail} — ${e.summary}`;
104
- });
105
- parts.push(
106
- `## WHAT YOU HAVE ALREADY DONE THIS SESSION\n\nThe following actions have already been completed. Do NOT repeat them unless the user explicitly asks:\n\n${lines.join("\n")}`,
107
- );
108
- }
109
-
110
- return parts.join("\n\n");
111
- }
112
-
113
- export function getRepoMemory(repoPath: string): MemoryEntry[] {
114
- return sessionEntries.filter((e) => !e.repoPath || e.repoPath === repoPath);
115
- }
116
-
117
- export function clearRepoMemory(repoPath: string): void {
118
- // clear session entries for this repo
119
- const toRemove = sessionEntries
120
- .map((e, i) => (e.repoPath === repoPath ? i : -1))
121
- .filter((i) => i >= 0)
122
- .reverse();
123
- for (const i of toRemove) sessionEntries.splice(i, 1);
124
-
125
- // clear persisted memories for this repo (keep global)
126
- const m = loadMemoryFile();
127
- m.memories = m.memories.filter(
128
- (mem) => mem.scope === "global" || mem.repoPath !== repoPath,
129
- );
130
- saveMemoryFile(m);
131
- }
132
-
133
- function generateId(): string {
134
- return Math.random().toString(36).slice(2, 8);
135
- }
136
-
137
- export function addMemory(content: string, repoPath: string): Memory {
138
- const m = loadMemoryFile();
139
-
140
- const isGlobal = content.startsWith("[global]");
141
- const cleanContent = isGlobal
142
- ? content.replace("[global]", "").trim()
143
- : content;
144
-
145
- const memory: Memory = {
146
- id: generateId(),
147
- content: cleanContent,
148
- repoPath: isGlobal ? undefined : repoPath,
149
- scope: isGlobal ? "global" : "repo",
150
- timestamp: new Date().toISOString(),
151
- };
152
- m.memories.push(memory);
153
- saveMemoryFile(m);
154
- return memory;
155
- }
156
-
157
- export function deleteMemory(id: string, repoPath: string): boolean {
158
- const m = loadMemoryFile();
159
- const before = m.memories.length;
160
- m.memories = m.memories.filter((mem) => mem.id !== id);
161
- if (m.memories.length === before) return false;
162
- saveMemoryFile(m);
163
- return true;
164
- }
165
-
166
- export function listMemories(repoPath: string): Memory[] {
167
- return loadMemoryFile().memories.filter(
168
- (mem) => mem.scope === "global" || mem.repoPath === repoPath,
169
- );
170
- }
171
-
172
- type SessionToolLog = {
173
- toolName: string;
174
- input: string;
175
- resultPreview: string;
176
- timestamp: string;
177
- };
178
-
179
- const sessionToolLog: SessionToolLog[] = [];
180
-
181
- export function logToolCall(
182
- toolName: string,
183
- input: string,
184
- result: string,
185
- repoPath?: string,
186
- ): void {
187
- sessionToolLog.push({
188
- toolName,
189
- input,
190
- resultPreview: result.slice(0, 120),
191
- timestamp: new Date().toISOString(),
192
- });
193
- if (sessionToolLog.length > 100)
194
- sessionToolLog.splice(0, sessionToolLog.length - 100);
195
- }
196
-
197
- export function getSessionToolSummary(repoPath: string): string {
198
- if (sessionToolLog.length === 0) return "";
199
- const recent = sessionToolLog.slice(-30);
200
- const lines = recent.map((e) => {
201
- const input = e.input.length > 60 ? e.input.slice(0, 60) + "…" : e.input;
202
- return `- ${e.toolName}: ${input}`;
203
- });
204
- return `## TOOLS ALREADY USED THIS SESSION\n\nDo NOT call these again unless the user explicitly asks:\n\n${lines.join("\n")}`;
205
- }
206
-
207
- export function clearSessionLog(): void {
208
- sessionToolLog.splice(0, sessionToolLog.length);
209
- }
@@ -1,119 +0,0 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import path from "path";
3
- import { spawn, type ChildProcess } from "child_process";
4
- import type { PackageManager, PreviewInfo } from "../types/repo";
5
-
6
- export function detectPreview(repoPath: string): PreviewInfo | null {
7
-
8
- const pkgPath = path.join(repoPath, "package.json");
9
- if (existsSync(pkgPath)) {
10
- try {
11
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as {
12
- scripts?: Record<string, string>;
13
- };
14
- const scripts = pkg.scripts ?? {};
15
-
16
- const pm: PackageManager = existsSync(
17
- path.join(repoPath, "pnpm-lock.yaml"),
18
- )
19
- ? "pnpm"
20
- : existsSync(path.join(repoPath, "yarn.lock"))
21
- ? "yarn"
22
- : "npm";
23
-
24
- const devScript =
25
- scripts["dev"] ?? scripts["start"] ?? scripts["serve"] ?? null;
26
- if (!devScript) return null;
27
-
28
- const devCmd =
29
- pm === "npm" ? "npm run dev" : pm === "yarn" ? "yarn dev" : "pnpm dev";
30
-
31
-
32
- const portMatch = devScript.match(/--port[= ](\d+)/);
33
- const port = portMatch ? parseInt(portMatch[1]!, 10) : 5173;
34
-
35
- return {
36
- packageManager: pm,
37
- installCmd:
38
- pm === "npm"
39
- ? "npm install"
40
- : pm === "yarn"
41
- ? "yarn install"
42
- : "pnpm install",
43
- devCmd,
44
- port,
45
- };
46
- } catch {
47
- return null;
48
- }
49
- }
50
-
51
-
52
- if (
53
- existsSync(path.join(repoPath, "requirements.txt")) ||
54
- existsSync(path.join(repoPath, "pyproject.toml"))
55
- ) {
56
- return {
57
- packageManager: "pip",
58
- installCmd: "pip install -r requirements.txt",
59
- devCmd: existsSync(path.join(repoPath, "manage.py"))
60
- ? "python manage.py runserver"
61
- : "python main.py",
62
- port: 8000,
63
- };
64
- }
65
-
66
- return null;
67
- }
68
-
69
- export type PreviewProcess = {
70
- kill: () => void;
71
- onLog: (cb: (line: string) => void) => void;
72
- onError: (cb: (line: string) => void) => void;
73
- onExit: (cb: (code: number | null) => void) => void;
74
- };
75
-
76
- export function runInstall(
77
- repoPath: string,
78
- installCmd: string,
79
- ): PreviewProcess {
80
- return spawnProcess(repoPath, installCmd);
81
- }
82
-
83
- export function runDev(repoPath: string, devCmd: string): PreviewProcess {
84
- return spawnProcess(repoPath, devCmd);
85
- }
86
-
87
- function spawnProcess(cwd: string, cmd: string): PreviewProcess {
88
- const [bin, ...args] = cmd.split(" ") as [string, ...string[]];
89
- const child: ChildProcess = spawn(bin, args, {
90
- cwd,
91
- shell: true,
92
- env: { ...process.env },
93
- });
94
-
95
- const logCallbacks: ((line: string) => void)[] = [];
96
- const errorCallbacks: ((line: string) => void)[] = [];
97
- const exitCallbacks: ((code: number | null) => void)[] = [];
98
-
99
- child.stdout?.on("data", (data: Buffer) => {
100
- const lines = data.toString().split("\n").filter(Boolean);
101
- lines.forEach((line) => logCallbacks.forEach((cb) => cb(line)));
102
- });
103
-
104
- child.stderr?.on("data", (data: Buffer) => {
105
- const lines = data.toString().split("\n").filter(Boolean);
106
- lines.forEach((line) => errorCallbacks.forEach((cb) => cb(line)));
107
- });
108
-
109
- child.on("exit", (code) => {
110
- exitCallbacks.forEach((cb) => cb(code));
111
- });
112
-
113
- return {
114
- kill: () => child.kill(),
115
- onLog: (cb) => logCallbacks.push(cb),
116
- onError: (cb) => errorCallbacks.push(cb),
117
- onExit: (cb) => exitCallbacks.push(cb),
118
- };
119
- }
@@ -1,174 +0,0 @@
1
- import { readFileSync, statSync } from "fs";
2
- import path from "path";
3
-
4
- export type CodeStats = {
5
- totalFiles: number;
6
- totalLines: number;
7
- languages: Record<string, number>;
8
- functions: number;
9
- classes: number;
10
- blankLines: number;
11
- commentLines: number;
12
- codeLines: number;
13
- };
14
-
15
- const LANG_MAP: Record<string, string> = {
16
- ".ts": "TS",
17
- ".tsx": "TSX",
18
- ".js": "JS",
19
- ".jsx": "JSX",
20
- ".css": "CSS",
21
- ".scss": "SCSS",
22
- ".json": "JSON",
23
- ".md": "MD",
24
- ".py": "PY",
25
- ".go": "GO",
26
- ".rs": "RS",
27
- ".java": "Java",
28
- ".cpp": "C++",
29
- ".c": "C",
30
- ".html": "HTML",
31
- ".yaml": "YAML",
32
- ".yml": "YAML",
33
- ".sh": "SH",
34
- ".env": "ENV",
35
- };
36
-
37
- const SKIP_DIRS = new Set([
38
- "node_modules",
39
- ".git",
40
- "dist",
41
- "build",
42
- ".next",
43
- "out",
44
- "coverage",
45
- "__pycache__",
46
- ".cache",
47
- "vendor",
48
- ".venv",
49
- "venv",
50
- ]);
51
-
52
- function isBinary(filePath: string): boolean {
53
- const binaryExts = new Set([
54
- ".png",
55
- ".jpg",
56
- ".jpeg",
57
- ".gif",
58
- ".webp",
59
- ".ico",
60
- ".svg",
61
- ".woff",
62
- ".woff2",
63
- ".ttf",
64
- ".eot",
65
- ".mp4",
66
- ".mp3",
67
- ".wav",
68
- ".zip",
69
- ".tar",
70
- ".gz",
71
- ".pdf",
72
- ".lock",
73
- ]);
74
- return binaryExts.has(path.extname(filePath).toLowerCase());
75
- }
76
-
77
- function countFunctionsAndClasses(
78
- content: string,
79
- ext: string,
80
- ): { functions: number; classes: number } {
81
- let functions = 0;
82
- let classes = 0;
83
-
84
- if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
85
- const fnMatches = content.match(
86
- /(?:^|\s)(?:function\s+\w+|const\s+\w+\s*=\s*(?:async\s*)?\(|(?:async\s+)?\w+\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{|\w+\s*=\s*(?:async\s*)?\([^)]*\)\s*=>)/gm,
87
- );
88
- functions = fnMatches?.length ?? 0;
89
-
90
- const classMatches = content.match(/(?:^|\s)class\s+\w+/gm);
91
- classes = classMatches?.length ?? 0;
92
- } else if (ext === ".py") {
93
- const fnMatches = content.match(/^\s*def\s+\w+/gm);
94
- functions = fnMatches?.length ?? 0;
95
- const classMatches = content.match(/^\s*class\s+\w+/gm);
96
- classes = classMatches?.length ?? 0;
97
- } else if ([".go", ".rs"].includes(ext)) {
98
- const fnMatches = content.match(/^\s*(?:func|fn)\s+\w+/gm);
99
- functions = fnMatches?.length ?? 0;
100
- }
101
-
102
- return { functions, classes };
103
- }
104
-
105
- export function computeStats(repoPath: string, files: string[]): CodeStats {
106
- const stats: CodeStats = {
107
- totalFiles: 0,
108
- totalLines: 0,
109
- languages: {},
110
- functions: 0,
111
- classes: 0,
112
- blankLines: 0,
113
- commentLines: 0,
114
- codeLines: 0,
115
- };
116
-
117
- for (const filePath of files) {
118
- const parts = filePath.split(/[/\\]/);
119
- if (parts.some((p) => SKIP_DIRS.has(p))) continue;
120
- if (isBinary(filePath)) continue;
121
-
122
- const ext = path.extname(filePath).toLowerCase();
123
- const lang = LANG_MAP[ext];
124
- if (!lang) continue;
125
-
126
- stats.totalFiles++;
127
- stats.languages[lang] = (stats.languages[lang] ?? 0) + 1;
128
-
129
- try {
130
- const content = readFileSync(path.join(repoPath, filePath), "utf-8");
131
- const lines = content.split("\n");
132
- stats.totalLines += lines.length;
133
-
134
- for (const line of lines) {
135
- const trimmed = line.trim();
136
- if (trimmed === "") {
137
- stats.blankLines++;
138
- } else if (
139
- trimmed.startsWith("//") ||
140
- trimmed.startsWith("#") ||
141
- trimmed.startsWith("*") ||
142
- trimmed.startsWith("/*") ||
143
- trimmed.startsWith("<!--")
144
- ) {
145
- stats.commentLines++;
146
- } else {
147
- stats.codeLines++;
148
- }
149
- }
150
-
151
- const { functions, classes } = countFunctionsAndClasses(content, ext);
152
- stats.functions += functions;
153
- stats.classes += classes;
154
- } catch {}
155
- }
156
-
157
- return stats;
158
- }
159
-
160
- export function formatNumber(n: number): string {
161
- if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
162
- return n.toString();
163
- }
164
-
165
- export function topLanguages(
166
- languages: Record<string, number>,
167
- limit = 5,
168
- ): string {
169
- return Object.entries(languages)
170
- .sort((a, b) => b[1] - a[1])
171
- .slice(0, limit)
172
- .map(([lang]) => lang)
173
- .join(", ");
174
- }