@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
package/src/tools/web.ts DELETED
@@ -1,212 +0,0 @@
1
- function stripTags(html: string): string {
2
- return html
3
- .replace(/<[^>]+>/g, " ")
4
- .replace(/&nbsp;/g, " ")
5
- .replace(/&amp;/g, "&")
6
- .replace(/&lt;/g, "<")
7
- .replace(/&gt;/g, ">")
8
- .replace(/&quot;/g, '"')
9
- .replace(/&#\d+;/g, " ")
10
- .replace(/\s+/g, " ")
11
- .trim();
12
- }
13
-
14
- function extractTables(html: string): string {
15
- const tables: string[] = [];
16
- const tableRe = /<table[\s\S]*?<\/table>/gi;
17
- let tMatch: RegExpExecArray | null;
18
-
19
- while ((tMatch = tableRe.exec(html)) !== null) {
20
- const tableHtml = tMatch[0]!;
21
- const rows: string[][] = [];
22
- const rowRe = /<tr[\s\S]*?<\/tr>/gi;
23
- let rMatch: RegExpExecArray | null;
24
- while ((rMatch = rowRe.exec(tableHtml)) !== null) {
25
- const cells: string[] = [];
26
- const cellRe = /<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi;
27
- let cMatch: RegExpExecArray | null;
28
- while ((cMatch = cellRe.exec(rMatch[0]!)) !== null) {
29
- cells.push(stripTags(cMatch[1] ?? ""));
30
- }
31
- if (cells.length > 0) rows.push(cells);
32
- }
33
- if (rows.length < 2) continue;
34
- const cols = Math.max(...rows.map((r) => r.length));
35
- const padded = rows.map((r) => {
36
- while (r.length < cols) r.push("");
37
- return r;
38
- });
39
- const widths = Array.from({ length: cols }, (_, ci) =>
40
- Math.max(...padded.map((r) => (r[ci] ?? "").length), 3),
41
- );
42
- const fmt = (r: string[]) =>
43
- r.map((c, ci) => c.padEnd(widths[ci] ?? 0)).join(" | ");
44
- const header = fmt(padded[0]!);
45
- const sep = widths.map((w) => "-".repeat(w)).join("-|-");
46
- const body = padded.slice(1).map(fmt).join("\n");
47
- tables.push(`${header}\n${sep}\n${body}`);
48
- }
49
-
50
- return tables.length > 0
51
- ? `=== TABLES (${tables.length}) ===\n\n${tables.join("\n\n---\n\n")}`
52
- : "";
53
- }
54
-
55
- function extractLists(html: string): string {
56
- const lists: string[] = [];
57
- const listRe = /<[ou]l[\s\S]*?<\/[ou]l>/gi;
58
- let lMatch: RegExpExecArray | null;
59
- while ((lMatch = listRe.exec(html)) !== null) {
60
- const items: string[] = [];
61
- const itemRe = /<li[^>]*>([\s\S]*?)<\/li>/gi;
62
- let iMatch: RegExpExecArray | null;
63
- while ((iMatch = itemRe.exec(lMatch[0]!)) !== null) {
64
- const text = stripTags(iMatch[1] ?? "");
65
- if (text.length > 2) items.push(`• ${text}`);
66
- }
67
- if (items.length > 1) lists.push(items.join("\n"));
68
- }
69
- return lists.length > 0
70
- ? `=== LISTS ===\n\n${lists.slice(0, 5).join("\n\n")}`
71
- : "";
72
- }
73
-
74
- export async function fetchUrl(url: string): Promise<string> {
75
- const res = await fetch(url, {
76
- headers: {
77
- "User-Agent":
78
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
79
- Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
80
- "Accept-Language": "en-US,en;q=0.5",
81
- },
82
- signal: AbortSignal.timeout(15000),
83
- });
84
- if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
85
-
86
- const contentType = res.headers.get("content-type") ?? "";
87
- if (contentType.includes("application/json")) {
88
- const json = await res.json();
89
- return JSON.stringify(json, null, 2).slice(0, 8000);
90
- }
91
-
92
- const html = await res.text();
93
- const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
94
- const title = titleMatch ? stripTags(titleMatch[1]!) : "No title";
95
-
96
- const tables = extractTables(html);
97
- const lists = extractLists(html);
98
- const bodyText = stripTags(
99
- html
100
- .replace(/<script[\s\S]*?<\/script>/gi, "")
101
- .replace(/<style[\s\S]*?<\/style>/gi, "")
102
- .replace(/<nav[\s\S]*?<\/nav>/gi, "")
103
- .replace(/<footer[\s\S]*?<\/footer>/gi, "")
104
- .replace(/<header[\s\S]*?<\/header>/gi, ""),
105
- )
106
- .replace(/\s{3,}/g, "\n\n")
107
- .slice(0, 3000);
108
-
109
- const parts = [`PAGE: ${title}`, `URL: ${url}`];
110
- if (tables) parts.push(tables);
111
- if (lists) parts.push(lists);
112
- parts.push(`=== TEXT ===\n${bodyText}`);
113
- return parts.join("\n\n");
114
- }
115
-
116
- // ── Search ────────────────────────────────────────────────────────────────────
117
-
118
- export async function searchWeb(query: string): Promise<string> {
119
- const encoded = encodeURIComponent(query);
120
-
121
- const ddgUrl = `https://api.duckduckgo.com/?q=${encoded}&format=json&no_html=1&skip_disambig=1`;
122
- try {
123
- const res = await fetch(ddgUrl, {
124
- headers: { "User-Agent": "Lens/1.0" },
125
- signal: AbortSignal.timeout(8000),
126
- });
127
- if (res.ok) {
128
- const data = (await res.json()) as {
129
- AbstractText?: string;
130
- AbstractURL?: string;
131
- RelatedTopics?: { Text?: string; FirstURL?: string }[];
132
- Answer?: string;
133
- Infobox?: { content?: { label: string; value: string }[] };
134
- };
135
-
136
- const parts: string[] = [`Search: ${query}`];
137
- if (data.Answer) parts.push(`Answer: ${data.Answer}`);
138
- if (data.AbstractText) {
139
- parts.push(`Summary: ${data.AbstractText}`);
140
- if (data.AbstractURL) parts.push(`Source: ${data.AbstractURL}`);
141
- }
142
- if (data.Infobox?.content?.length) {
143
- const fields = data.Infobox.content
144
- .slice(0, 8)
145
- .map((f) => ` ${f.label}: ${f.value}`)
146
- .join("\n");
147
- parts.push(`Info:\n${fields}`);
148
- }
149
- if (data.RelatedTopics?.length) {
150
- const topics = (data.RelatedTopics as { Text?: string }[])
151
- .filter((t) => t.Text)
152
- .slice(0, 5)
153
- .map((t) => ` - ${t.Text}`)
154
- .join("\n");
155
- if (topics) parts.push(`Related:\n${topics}`);
156
- }
157
-
158
- const result = parts.join("\n\n");
159
- if (result.length > 60) return result;
160
- }
161
- } catch {
162
- // fall through to HTML scrape
163
- }
164
-
165
- try {
166
- const htmlUrl = `https://html.duckduckgo.com/html/?q=${encoded}`;
167
- const res = await fetch(htmlUrl, {
168
- headers: {
169
- "User-Agent":
170
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
171
- Accept: "text/html",
172
- },
173
- signal: AbortSignal.timeout(10000),
174
- });
175
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
176
- const html = await res.text();
177
-
178
- const snippets: string[] = [];
179
- const snippetRe = /class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
180
- let m: RegExpExecArray | null;
181
- while ((m = snippetRe.exec(html)) !== null && snippets.length < 6) {
182
- const text = m[1]!
183
- .replace(/<[^>]+>/g, " ")
184
- .replace(/&nbsp;/g, " ")
185
- .replace(/&amp;/g, "&")
186
- .replace(/&lt;/g, "<")
187
- .replace(/&gt;/g, ">")
188
- .replace(/&quot;/g, '"')
189
- .replace(/\s+/g, " ")
190
- .trim();
191
- if (text.length > 20) snippets.push(`- ${text}`);
192
- }
193
-
194
- const links: string[] = [];
195
- const linkRe = /class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g;
196
- while ((m = linkRe.exec(html)) !== null && links.length < 5) {
197
- const title = m[2]!.replace(/<[^>]+>/g, "").trim();
198
- const href = m[1]!;
199
- if (title && href) links.push(` ${title} \u2014 ${href}`);
200
- }
201
-
202
- if (snippets.length === 0 && links.length === 0)
203
- return `No results found for: ${query}`;
204
-
205
- const parts = [`Search results for: ${query}`];
206
- if (snippets.length > 0) parts.push(`Snippets:\n${snippets.join("\n")}`);
207
- if (links.length > 0) parts.push(`Links:\n${links.join("\n")}`);
208
- return parts.join("\n\n");
209
- } catch (err) {
210
- return `Search failed: ${err instanceof Error ? err.message : String(err)}`;
211
- }
212
- }
package/src/types/chat.ts DELETED
@@ -1,86 +0,0 @@
1
- import type { FilePatch, DiffLine } from "../components/repo/DiffViewer";
2
-
3
- // ── Tool calls ────────────────────────────────────────────────────────────────
4
-
5
- export type ToolCall =
6
- | { type: "shell"; command: string }
7
- | { type: "fetch"; url: string }
8
- | { type: "read-file"; filePath: string }
9
- | { type: "read-folder"; folderPath: string }
10
- | { type: "grep"; pattern: string; glob: string }
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 }
16
- | { type: "search"; query: string };
17
-
18
- // ── Messages ──────────────────────────────────────────────────────────────────
19
-
20
- export type Message =
21
- | { role: "user" | "assistant"; type: "text"; content: string }
22
- | {
23
- role: "assistant";
24
- type: "tool";
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";
37
- content: string;
38
- result: string;
39
- approved: boolean;
40
- }
41
- | {
42
- role: "assistant";
43
- type: "plan";
44
- content: string;
45
- patches: FilePatch[];
46
- applied: boolean;
47
- };
48
-
49
- // ── Chat stage ────────────────────────────────────────────────────────────────
50
-
51
- export type ChatStage =
52
- | { type: "picking-provider" }
53
- | { type: "loading" }
54
- | { type: "idle" }
55
- | { type: "thinking" }
56
- | { type: "error"; message: string }
57
- | {
58
- type: "permission";
59
- tool: ToolCall;
60
- pendingMessages: Message[];
61
- resolve: (approved: boolean) => void;
62
- }
63
- | {
64
- type: "preview";
65
- patches: FilePatch[];
66
- diffLines: DiffLine[][];
67
- scrollOffset: number;
68
- pendingMessages: Message[];
69
- }
70
- | {
71
- type: "viewing-file";
72
- file: { path: string; isNew: boolean; patch: FilePatch };
73
- diffLines: DiffLine[];
74
- scrollOffset: number;
75
- }
76
- | { type: "clone-offer"; repoUrl: string; launchAnalysis?: boolean }
77
- | { type: "cloning"; repoUrl: string }
78
- | { type: "clone-exists"; repoUrl: string; repoPath: string }
79
- | {
80
- type: "clone-done";
81
- repoUrl: string;
82
- destPath: string;
83
- fileCount: number;
84
- launchAnalysis?: boolean;
85
- }
86
- | { type: "clone-error"; message: string };
@@ -1,20 +0,0 @@
1
- export type ProviderType =
2
- | "anthropic"
3
- | "gemini"
4
- | "openai"
5
- | "ollama"
6
- | "custom";
7
-
8
- export type Provider = {
9
- id: string;
10
- type: ProviderType;
11
- name: string;
12
- apiKey?: string;
13
- baseUrl?: string;
14
- model: string;
15
- };
16
-
17
- export type Config = {
18
- providers: Provider[];
19
- defaultProviderId?: string;
20
- };
package/src/types/repo.ts DELETED
@@ -1,54 +0,0 @@
1
- export type Step =
2
- | { type: "cloning"; status: "pending" | "done" }
3
- | { type: "folder-exists"; status: "pending"; repoPath: string }
4
- | { type: "fetching-tree"; status: "pending" | "done" }
5
- | { type: "reading-files"; status: "pending" | "done" }
6
- | { type: "error"; message: string };
7
-
8
- export type ReviewStage = "list" | "file";
9
-
10
- export type FileTree = {
11
- name: string;
12
- children?: FileTree[];
13
- };
14
-
15
- export type ImportantFile = {
16
- path: string;
17
- content: string;
18
- };
19
-
20
- export type AIProvider =
21
- | "anthropic"
22
- | "gemini"
23
- | "ollama"
24
- | "openai"
25
- | "custom";
26
-
27
- export type AnalysisResult = {
28
- overview: string;
29
- importantFolders: string[];
30
-
31
- tooling: Record<string, string>;
32
-
33
- keyFiles: string[];
34
-
35
- patterns: string[];
36
-
37
- architecture: string;
38
- suggestions: string[];
39
- };
40
-
41
- export type PackageManager =
42
- | "npm"
43
- | "yarn"
44
- | "pnpm"
45
- | "bun"
46
- | "pip"
47
- | "unknown";
48
-
49
- export type PreviewInfo = {
50
- packageManager: PackageManager;
51
- installCmd: string;
52
- devCmd: string;
53
- port: number | null;
54
- };
@@ -1,34 +0,0 @@
1
- import path from "path";
2
- import os from "os";
3
- import { existsSync, readdirSync } from "fs";
4
- import { pathToFileURL } from "url";
5
-
6
- const ADDONS_DIR = path.join(os.homedir(), ".lens", "addons");
7
-
8
- export async function loadAddons(): Promise<void> {
9
- if (!existsSync(ADDONS_DIR)) {
10
- return;
11
- }
12
-
13
- const files = readdirSync(ADDONS_DIR).filter(
14
- (f) => f.endsWith(".js") && !f.startsWith("_"),
15
- );
16
-
17
- for (let i = 0; i < files.length; i++) {
18
- const file = files[i];
19
- if (!file) return;
20
-
21
- const fullPath = path.join(ADDONS_DIR, file);
22
- const fileUrl = pathToFileURL(fullPath).href;
23
- const isLast = i === files.length - 1;
24
- try {
25
- await import(fileUrl);
26
- console.log(`[addons] loaded: ${file}${isLast ? "\n" : ""}`);
27
- } catch (err) {
28
- console.error(
29
- `[addons] failed to load ${file}:`,
30
- err instanceof Error ? err.message : String(err),
31
- );
32
- }
33
- }
34
- }