@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,377 +0,0 @@
1
- import type { Tool, ToolContext } from "@ridit/lens-sdk";
2
- import { TOOL_TAGS } from "@ridit/lens-sdk";
3
- import {
4
- fetchUrl,
5
- searchWeb,
6
- runShell,
7
- openUrl,
8
- readFile,
9
- readFolder,
10
- grepFiles,
11
- writeFile,
12
- deleteFile,
13
- deleteFolder,
14
- generatePdf,
15
- viewImageTool,
16
- registerGitTools,
17
- chartDataTool,
18
- convertImageTool,
19
- } from "../../tools";
20
-
21
- const cleanBody = (body: string) => body.trim().replace(/\\/g, "/");
22
-
23
- export const fetchTool: Tool<string> = {
24
- name: "fetch",
25
- description: "load a URL",
26
- safe: true,
27
- tag: TOOL_TAGS.net,
28
- permissionLabel: "fetch",
29
- systemPromptEntry: (i) =>
30
- `### ${i}. fetch — load a URL\n<fetch>https://example.com</fetch>`,
31
- parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
32
- summariseInput: (url) => url,
33
- execute: async (url) => {
34
- try {
35
- const value = await fetchUrl(url);
36
- return { kind: "text", value };
37
- } catch (err) {
38
- return {
39
- kind: "error",
40
- value: `Fetch failed: ${err instanceof Error ? err.message : String(err)}`,
41
- };
42
- }
43
- },
44
- };
45
-
46
- export const shellTool: Tool<string> = {
47
- name: "shell",
48
- description: "run a terminal command",
49
- safe: false,
50
- tag: TOOL_TAGS.shell,
51
- permissionLabel: "run",
52
- systemPromptEntry: (i) =>
53
- `### ${i}. shell — run a terminal command\n<shell>node -v</shell>`,
54
- parseInput: (body) => body || null,
55
- summariseInput: (cmd) => cmd,
56
- execute: async (cmd, ctx) => {
57
- const value = await runShell(cmd, ctx.repoPath);
58
- return { kind: "text", value };
59
- },
60
- };
61
-
62
- export const readFileTool: Tool<string> = {
63
- name: "read-file",
64
- description: "read a file from the repo",
65
- safe: true,
66
- tag: TOOL_TAGS.read,
67
- permissionLabel: "read",
68
- systemPromptEntry: (i) =>
69
- `### ${i}. read-file — read a file from the repo\n<read-file>src/foo.ts</read-file>`,
70
- parseInput: (body) => cleanBody(body) || null,
71
- summariseInput: (p) => p,
72
- execute: (filePath, ctx) => ({
73
- kind: "text",
74
- value: readFile(filePath, ctx.repoPath),
75
- }),
76
- };
77
-
78
- export const readFolderTool: Tool<string> = {
79
- name: "read-folder",
80
- description: "list contents of a folder (files + subfolders, one level deep)",
81
- tag: TOOL_TAGS.read,
82
- safe: true,
83
- permissionLabel: "folder",
84
- systemPromptEntry: (i) =>
85
- `### ${i}. read-folder — list contents of a folder (files + subfolders, one level deep)\n<read-folder>src/components</read-folder>`,
86
- parseInput: (body) => cleanBody(body) || null,
87
- summariseInput: (p) => p,
88
- execute: (folderPath, ctx) => ({
89
- kind: "text",
90
- value: readFolder(folderPath, ctx.repoPath),
91
- }),
92
- };
93
-
94
- interface GrepInput {
95
- pattern: string;
96
- glob: string;
97
- }
98
-
99
- export const grepTool: Tool<GrepInput> = {
100
- name: "grep",
101
- description: "search for a pattern across files in the repo",
102
- tag: TOOL_TAGS.find,
103
- safe: true,
104
- permissionLabel: "grep",
105
- systemPromptEntry: (i) =>
106
- `### ${i}. grep — search for a pattern across files in the repo (cross-platform, no shell needed)\n<grep>\n{"pattern": "ChatRunner", "glob": "src/**/*.tsx"}\n</grep>`,
107
- parseInput: (body) => {
108
- try {
109
- const parsed = JSON.parse(cleanBody(body)) as {
110
- pattern: string;
111
- glob?: string;
112
- };
113
- return { pattern: parsed.pattern, glob: parsed.glob ?? "**/*" };
114
- } catch {
115
- return { pattern: body, glob: "**/*" };
116
- }
117
- },
118
- summariseInput: ({ pattern, glob }) => `${pattern} — ${glob}`,
119
- execute: ({ pattern, glob }, ctx) => ({
120
- kind: "text",
121
- value: grepFiles(pattern, glob, ctx.repoPath),
122
- }),
123
- };
124
-
125
- interface WriteFileInput {
126
- path: string;
127
- content: string;
128
- }
129
-
130
- export const writeFileTool: Tool<WriteFileInput> = {
131
- name: "write-file",
132
- description: "create or overwrite a file",
133
- tag: TOOL_TAGS.write,
134
- safe: false,
135
- permissionLabel: "write",
136
- systemPromptEntry: (i) =>
137
- `### ${i}. write-file — create or overwrite a file\n<write-file>\n{"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}\n</write-file>`,
138
- parseInput: (body) => {
139
- const tryParse = (s: string) => {
140
- try {
141
- const parsed = JSON.parse(s) as { path: string; content: string };
142
- if (!parsed.path || parsed.content === undefined) return null;
143
- return { ...parsed, path: parsed.path.replace(/\\/g, "/") };
144
- } catch {
145
- return null;
146
- }
147
- };
148
-
149
- const first = tryParse(body.trim());
150
- if (first) return first;
151
-
152
- try {
153
- const sanitized = body
154
- .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "")
155
- .replace(/\n/g, "\\n")
156
- .replace(/\r/g, "\\r")
157
- .replace(/\t/g, "\\t");
158
- const second = tryParse(sanitized);
159
- if (second) return second;
160
- } catch {}
161
-
162
- const pathMatch = body.match(/"path"\s*:\s*"([^"]+)"/);
163
- const contentMatch = body.match(/"content"\s*:\s*"([\s\S]*)"\s*}?\s*$/);
164
- if (pathMatch && contentMatch && contentMatch[1] !== undefined) {
165
- return {
166
- path: pathMatch[1]!.replace(/\\/g, "/"),
167
- content: contentMatch[1]!.replace(/\\n/g, "\n").replace(/\\t/g, "\t"),
168
- };
169
- }
170
-
171
- return null;
172
- },
173
- summariseInput: ({ path, content }) => `${path} (${content.length} bytes)`,
174
- execute: ({ path: filePath, content }, ctx) => ({
175
- kind: "text",
176
- value: writeFile(filePath, content, ctx.repoPath),
177
- }),
178
- };
179
-
180
- export const deleteFileTool: Tool<string> = {
181
- name: "delete-file",
182
- description: "permanently delete a single file",
183
- tag: TOOL_TAGS.delete,
184
- safe: false,
185
- permissionLabel: "delete",
186
- systemPromptEntry: (i) =>
187
- `### ${i}. delete-file — permanently delete a single file\n<delete-file>src/old-component.tsx</delete-file>`,
188
- parseInput: (body) => cleanBody(body) || null,
189
- summariseInput: (p) => p,
190
- execute: (filePath, ctx) => ({
191
- kind: "text",
192
- value: deleteFile(filePath, ctx.repoPath),
193
- }),
194
- };
195
-
196
- export const deleteFolderTool: Tool<string> = {
197
- name: "delete-folder",
198
- description: "permanently delete a folder and all its contents",
199
- tag: TOOL_TAGS.delete,
200
- safe: false,
201
- permissionLabel: "delete folder",
202
- systemPromptEntry: (i) =>
203
- `### ${i}. delete-folder — permanently delete a folder and all its contents\n<delete-folder>src/legacy</delete-folder>`,
204
- parseInput: (body) => cleanBody(body) || null,
205
- summariseInput: (p) => p,
206
- execute: (folderPath, ctx) => ({
207
- kind: "text",
208
- value: deleteFolder(folderPath, ctx.repoPath),
209
- }),
210
- };
211
-
212
- export const openUrlTool: Tool<string> = {
213
- name: "open-url",
214
- description: "open a URL in the user's default browser",
215
- tag: TOOL_TAGS.net,
216
- safe: true,
217
- permissionLabel: "open",
218
- systemPromptEntry: (i) =>
219
- `### ${i}. open-url — open a URL in the user's default browser\n<open-url>https://github.com/owner/repo</open-url>`,
220
- parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
221
- summariseInput: (url) => url,
222
- execute: (url) => ({ kind: "text", value: openUrl(url) }),
223
- };
224
-
225
- interface GeneratePdfInput {
226
- filePath: string;
227
- content: string;
228
- }
229
-
230
- export const generatePdfTool: Tool<GeneratePdfInput> = {
231
- name: "generate-pdf",
232
- description: "generate a PDF file from markdown-style content",
233
- tag: TOOL_TAGS.write,
234
- safe: false,
235
- permissionLabel: "pdf",
236
- systemPromptEntry: (i) =>
237
- `### ${i}. generate-pdf — generate a PDF file from markdown-style content\n<generate-pdf>\n{"path": "output/report.pdf", "content": "# Title\\n\\nSome body text."}\n</generate-pdf>`,
238
- parseInput: (body) => {
239
- try {
240
- const parsed = JSON.parse(cleanBody(body)) as {
241
- path?: string;
242
- filePath?: string;
243
- content?: string;
244
- };
245
- return {
246
- filePath: parsed.path ?? parsed.filePath ?? "output.pdf",
247
- content: parsed.content ?? "",
248
- };
249
- } catch {
250
- return null;
251
- }
252
- },
253
- summariseInput: ({ filePath }) => filePath,
254
- execute: async ({ filePath, content }, ctx) => ({
255
- kind: "text",
256
- value: await generatePdf(filePath, content, ctx.repoPath),
257
- }),
258
- };
259
-
260
- export const searchTool: Tool<string> = {
261
- name: "search",
262
- tag: TOOL_TAGS.net,
263
- description: "search the internet for anything you are unsure about",
264
- safe: true,
265
- permissionLabel: "search",
266
- systemPromptEntry: (i) =>
267
- `### ${i}. search — search the internet for anything you are unsure about\n<search>how to use React useEffect cleanup function</search>`,
268
- parseInput: (body) => body || null,
269
- summariseInput: (q) => `"${q}"`,
270
- execute: async (query) => {
271
- try {
272
- const value = await searchWeb(query);
273
- return { kind: "text", value };
274
- } catch (err) {
275
- return {
276
- kind: "error",
277
- value: `Search failed: ${err instanceof Error ? err.message : String(err)}`,
278
- };
279
- }
280
- },
281
- };
282
-
283
- export const cloneTool: Tool<string> = {
284
- name: "clone",
285
- description: "clone a GitHub repo so you can explore and discuss it",
286
- tag: TOOL_TAGS.write,
287
- safe: false,
288
- permissionLabel: "clone",
289
- systemPromptEntry: (i) =>
290
- `### ${i}. clone — clone a GitHub repo so you can explore and discuss it\n<clone>https://github.com/owner/repo</clone>`,
291
- parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
292
- summariseInput: (url) => url,
293
- execute: (repoUrl) => ({
294
- kind: "text",
295
- value: `Clone of ${repoUrl} was handled by the UI.`,
296
- }),
297
- };
298
-
299
- export interface ChangesInput {
300
- summary: string;
301
- patches: { path: string; content: string; isNew: boolean }[];
302
- }
303
-
304
- export const changesTool: Tool<ChangesInput> = {
305
- name: "changes",
306
- description: "propose code edits (shown as a diff for user approval)",
307
- tag: TOOL_TAGS.write,
308
- safe: false,
309
- permissionLabel: "changes",
310
- systemPromptEntry: (i) =>
311
- `### ${i}. changes — propose code edits (shown as a diff for user approval)\n<changes>\n{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}\n</changes>`,
312
- parseInput: (body) => {
313
- try {
314
- return JSON.parse(cleanBody(body)) as ChangesInput;
315
- } catch {
316
- return null;
317
- }
318
- },
319
- summariseInput: ({ summary }) => summary,
320
- execute: ({ summary }) => ({
321
- kind: "text",
322
- value: `Changes proposed: ${summary}`,
323
- }),
324
- };
325
-
326
- interface ReadFilesInput {
327
- paths: string[];
328
- }
329
-
330
- export const readFilesTool: Tool<ReadFilesInput> = {
331
- name: "read-files",
332
- description: "read multiple files from the repo at once",
333
- tag: TOOL_TAGS.read,
334
- safe: true,
335
- permissionLabel: "read",
336
- systemPromptEntry: (i) =>
337
- `### ${i}. read-files — read multiple files from the repo at once\n<read-files>\n["src/foo.ts", "src/bar.ts"]\n</read-files>`,
338
- parseInput: (body) => {
339
- try {
340
- const parsed = JSON.parse(cleanBody(body)) as string[];
341
- if (!Array.isArray(parsed) || parsed.length === 0) return null;
342
- return { paths: parsed };
343
- } catch {
344
- return null;
345
- }
346
- },
347
- summariseInput: ({ paths }) => paths.join(", "),
348
- execute: ({ paths }, ctx) => ({
349
- kind: "text",
350
- value: paths
351
- .map((p) => `=== ${p} ===\n${readFile(p, ctx.repoPath)}`)
352
- .join("\n\n"),
353
- }),
354
- };
355
-
356
- import { registry } from "./registry";
357
-
358
- export function registerBuiltins(): void {
359
- registry.register(fetchTool);
360
- registry.register(shellTool);
361
- registry.register(readFileTool);
362
- registry.register(readFolderTool);
363
- registry.register(grepTool);
364
- registry.register(writeFileTool);
365
- registry.register(deleteFileTool);
366
- registry.register(deleteFolderTool);
367
- registry.register(openUrlTool);
368
- registry.register(generatePdfTool);
369
- registry.register(searchTool);
370
- registry.register(cloneTool);
371
- registry.register(changesTool);
372
- registry.register(viewImageTool);
373
- registry.register(chartDataTool);
374
- registry.register(convertImageTool);
375
- registry.register(readFilesTool);
376
- registerGitTools();
377
- }
@@ -1,105 +0,0 @@
1
- import type { Tool, ToolTag } from "@ridit/lens-sdk";
2
- import type { Intent } from "../intentClassifier";
3
-
4
- /**
5
- * Broad capability category for a tool.
6
- * Used to filter the system prompt based on classified user intent.
7
- *
8
- * "read" — safe, purely observational (read-file, read-folder, grep, etc.)
9
- * "net" — outbound network (fetch, search, clone, open-url)
10
- * "write" — creates or overwrites file content (write-file, changes, generate-pdf)
11
- * "delete" — destructive removal (delete-file, delete-folder)
12
- * "shell" — arbitrary shell execution
13
- */
14
-
15
- /** Tools allowed for each intent level */
16
- const INTENT_ALLOWED: Record<Intent, ToolTag[]> = {
17
- readonly: ["read", "net"],
18
- mutating: ["read", "net", "write", "delete", "shell"],
19
- any: ["read", "net", "write", "delete", "shell"],
20
- };
21
-
22
- class ToolRegistry {
23
- private tools = new Map<string, Tool<unknown>>();
24
-
25
- register<T>(tool: Tool<T>): void {
26
- if (this.tools.has(tool.name)) {
27
- console.warn(`[ToolRegistry] Overwriting existing tool: "${tool.name}"`);
28
- }
29
- this.tools.set(tool.name, tool as Tool<unknown>);
30
- }
31
-
32
- unregister(name: string): void {
33
- this.tools.delete(name);
34
- }
35
-
36
- get(name: string): Tool<unknown> | undefined {
37
- return this.tools.get(name);
38
- }
39
-
40
- all(): Tool<unknown>[] {
41
- return Array.from(this.tools.values());
42
- }
43
-
44
- names(): string[] {
45
- return Array.from(this.tools.keys());
46
- }
47
-
48
- /**
49
- * Returns tool names that are allowed for the given intent.
50
- * Falls back to all names when a tool has no tag (legacy / addons).
51
- */
52
- namesForIntent(intent: Intent): string[] {
53
- const allowed = new Set(INTENT_ALLOWED[intent]);
54
- return Array.from(this.tools.values())
55
- .filter((t) => {
56
- const tag = (t as any).tag as ToolTag | undefined;
57
- // No tag = addon / unknown → always allow (conservative)
58
- if (!tag) return true;
59
- return allowed.has(tag);
60
- })
61
- .map((t) => t.name);
62
- }
63
-
64
- /**
65
- * Build the TOOLS section of the system prompt from all registered tools,
66
- * optionally scoped to a specific intent.
67
- *
68
- * When intent is "readonly", write/delete/shell tools are omitted entirely
69
- * so the LLM never sees them and can't hallucinate calls to them.
70
- */
71
- buildSystemPromptSection(intent: Intent = "any"): string {
72
- const allowed = new Set(INTENT_ALLOWED[intent]);
73
-
74
- const visible = Array.from(this.tools.values()).filter((t) => {
75
- const tag = (t as any).tag as ToolTag | undefined;
76
- if (!tag) return true; // addon without tag → always show
77
- return allowed.has(tag);
78
- });
79
-
80
- const lines: string[] = ["## TOOLS\n"];
81
-
82
- if (intent === "readonly") {
83
- lines.push(
84
- `You have ${visible.length} tools available for this read-only request. ` +
85
- `Do NOT attempt to write, delete, or run shell commands — ` +
86
- `those tools are not available right now.\n`,
87
- );
88
- } else {
89
- lines.push(
90
- `You have exactly ${visible.length} tools. To use a tool you MUST wrap it ` +
91
- `in the exact XML tags shown below — no other format will work.\n`,
92
- );
93
- }
94
-
95
- let i = 1;
96
- for (const tool of visible) {
97
- lines.push(tool.systemPromptEntry(i++));
98
- }
99
- return lines.join("\n");
100
- }
101
- }
102
-
103
- export const registry = new ToolRegistry();
104
-
105
- (globalThis as any).__lens_registry = registry;