@ridit/lens 0.2.4 → 0.2.6
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/LENS.md +32 -68
- package/README.md +91 -0
- package/addons/README.md +3 -0
- package/addons/run-tests.js +127 -0
- package/dist/index.mjs +226459 -2638
- package/package.json +13 -4
- package/src/colors.ts +5 -0
- package/src/commands/commit.tsx +686 -0
- package/src/commands/provider.tsx +36 -22
- package/src/components/__tests__/Header.test.tsx +9 -0
- package/src/components/chat/ChatMessage.tsx +6 -6
- package/src/components/chat/ChatOverlays.tsx +20 -10
- package/src/components/chat/ChatRunner.tsx +197 -31
- package/src/components/provider/ApiKeyStep.tsx +77 -121
- package/src/components/provider/ModelStep.tsx +35 -20
- package/src/components/{repo → provider}/ProviderPicker.tsx +1 -1
- package/src/components/provider/ProviderTypeStep.tsx +12 -5
- package/src/components/provider/RemoveProviderStep.tsx +7 -8
- package/src/components/repo/RepoAnalysis.tsx +1 -1
- package/src/components/task/TaskRunner.tsx +1 -1
- package/src/components/timeline/CommitDetail.tsx +2 -4
- package/src/components/timeline/CommitList.tsx +2 -14
- package/src/components/timeline/TimelineChat.tsx +1 -2
- package/src/components/timeline/TimelineRunner.tsx +506 -423
- package/src/index.tsx +38 -0
- package/src/prompts/fewshot.ts +144 -47
- package/src/prompts/system.ts +25 -21
- package/src/tools/chart.ts +210 -0
- package/src/tools/convert-image.ts +312 -0
- package/src/tools/files.ts +1 -9
- package/src/tools/git.ts +577 -0
- package/src/tools/index.ts +17 -13
- package/src/tools/pdf.ts +136 -78
- package/src/tools/view-image.ts +335 -0
- package/src/tools/web.ts +0 -4
- package/src/utils/addons/loadAddons.ts +6 -3
- package/src/utils/chat.ts +38 -23
- package/src/utils/thinking.tsx +275 -162
- package/src/utils/tools/builtins.ts +39 -32
- package/src/utils/tools/registry.ts +0 -14
- package/tsconfig.json +2 -2
package/src/index.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { ReviewCommand } from "./commands/review";
|
|
|
8
8
|
import { TaskCommand } from "./commands/task";
|
|
9
9
|
import { ChatCommand } from "./commands/chat";
|
|
10
10
|
import { TimelineCommand } from "./commands/timeline";
|
|
11
|
+
import { CommitCommand } from "./commands/commit";
|
|
11
12
|
import { registerBuiltins } from "./utils/tools/builtins";
|
|
12
13
|
import { loadAddons } from "./utils/addons/loadAddons";
|
|
13
14
|
|
|
@@ -63,4 +64,41 @@ program
|
|
|
63
64
|
render(<TimelineCommand path={opts.path} />);
|
|
64
65
|
});
|
|
65
66
|
|
|
67
|
+
program
|
|
68
|
+
.command("commit [files...]")
|
|
69
|
+
.description(
|
|
70
|
+
"Generate a smart conventional commit message from staged changes or specific files",
|
|
71
|
+
)
|
|
72
|
+
.option("-p, --path <path>", "Path to the repo", ".")
|
|
73
|
+
.option(
|
|
74
|
+
"--auto",
|
|
75
|
+
"Stage all changes (or the given files) and commit without confirmation",
|
|
76
|
+
)
|
|
77
|
+
.option("--confirm", "Show preview before committing even when using --auto")
|
|
78
|
+
.option("--preview", "Show the generated message without committing")
|
|
79
|
+
.option("--push", "Push to remote after committing")
|
|
80
|
+
.action(
|
|
81
|
+
(
|
|
82
|
+
files: string[],
|
|
83
|
+
opts: {
|
|
84
|
+
path: string;
|
|
85
|
+
auto: boolean;
|
|
86
|
+
confirm: boolean;
|
|
87
|
+
preview: boolean;
|
|
88
|
+
push: boolean;
|
|
89
|
+
},
|
|
90
|
+
) => {
|
|
91
|
+
render(
|
|
92
|
+
<CommitCommand
|
|
93
|
+
path={opts.path}
|
|
94
|
+
files={files ?? []}
|
|
95
|
+
auto={opts.auto ?? false}
|
|
96
|
+
confirm={opts.confirm ?? false}
|
|
97
|
+
preview={opts.preview ?? false}
|
|
98
|
+
push={opts.push ?? false}
|
|
99
|
+
/>,
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
66
104
|
program.parse(process.argv);
|
package/src/prompts/fewshot.ts
CHANGED
|
@@ -1,4 +1,134 @@
|
|
|
1
1
|
export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
2
|
+
// ── create new file directly (no read first) ──────────────────────────────
|
|
3
|
+
{
|
|
4
|
+
role: "user",
|
|
5
|
+
content: "this codebase doesn't have a readme",
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
role: "assistant",
|
|
9
|
+
content:
|
|
10
|
+
'<write-file>\n{"path": "README.md", "content": "# Project\\n\\nA brief description of what this project does.\\n\\n## Getting Started\\n\\n```bash\\nbun install\\nbun run dev\\n```"}\n</write-file>',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
role: "user",
|
|
14
|
+
content:
|
|
15
|
+
"Here is the output from write-file to README.md:\n\nWritten: /repo/README.md (10 lines, 128 bytes)\n\nPlease continue your response based on this output.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
role: "assistant",
|
|
19
|
+
content: "Done — README.md has been created.",
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// ── expand existing file (read first, then write) ─────────────────────────
|
|
23
|
+
{
|
|
24
|
+
role: "user",
|
|
25
|
+
content: "add more information to the README",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
role: "assistant",
|
|
29
|
+
content: "<read-file>README.md</read-file>",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
role: "user",
|
|
33
|
+
content:
|
|
34
|
+
"Here is the output from read-file of README.md:\n\nFile: README.md (10 lines)\n\n# Project\n\nA brief description.\n\n## Getting Started\n\n```bash\nbun install\n```\n\nPlease continue your response based on this output.",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
role: "assistant",
|
|
38
|
+
content:
|
|
39
|
+
'<write-file>\n{"path": "README.md", "content": "# Project\\n\\nA brief description.\\n\\n## Getting Started\\n\\n```bash\\nbun install\\nbun run dev\\n```\\n\\n## Features\\n\\n- Feature one\\n- Feature two\\n\\n## Contributing\\n\\nPull requests welcome."}\n</write-file>',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
role: "user",
|
|
43
|
+
content:
|
|
44
|
+
"Here is the output from write-file to README.md:\n\nWritten: /repo/README.md (20 lines, 280 bytes)\n\nPlease continue your response based on this output.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
role: "assistant",
|
|
48
|
+
content: "Done — README.md updated with more detail.",
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// ── update an existing file ───────────────────────────────────────────────
|
|
52
|
+
{
|
|
53
|
+
role: "user",
|
|
54
|
+
content: "update LENS.md",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
role: "assistant",
|
|
58
|
+
content: "<read-file>LENS.md</read-file>",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
role: "user",
|
|
62
|
+
content:
|
|
63
|
+
"Here is the output from read-file of LENS.md:\n\nFile: LENS.md (33 lines)\n\n# Lens Analysis\n\n## Overview\nA CLI tool for codebase analysis.\n\n## Important Folders\n- src/components\n- src/utils\n\n## Suggestions\n- Add tests\n\nPlease continue your response based on this output.",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
role: "assistant",
|
|
67
|
+
content:
|
|
68
|
+
'<write-file>\n{"path": "LENS.md", "content": "# Lens Analysis\\n\\n## Overview\\nA CLI tool for AI-powered codebase analysis, built with React and Ink.\\n\\n## Important Folders\\n- src/components — UI components rendered in the terminal\\n- src/utils — tools, chat, memory, and registry utilities\\n\\n## Suggestions\\n- Add tests\\n- Add more providers\\n- Improve error handling"}\n</write-file>',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
role: "user",
|
|
72
|
+
content:
|
|
73
|
+
"Here is the output from write-file to LENS.md:\n\nWritten: /repo/LENS.md (15 lines, 420 bytes)\n\nPlease continue your response based on this output.",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
role: "assistant",
|
|
77
|
+
content: "Done — LENS.md updated.",
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// ── write a new doc file directly ─────────────────────────────────────────
|
|
81
|
+
{
|
|
82
|
+
role: "user",
|
|
83
|
+
content: "write a contributing guide",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
role: "assistant",
|
|
87
|
+
content:
|
|
88
|
+
'<write-file>\n{"path": "CONTRIBUTING.md", "content": "# Contributing\\n\\nPull requests welcome. Please open an issue first to discuss major changes."}\n</write-file>',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
role: "user",
|
|
92
|
+
content:
|
|
93
|
+
"Here is the output from write-file to CONTRIBUTING.md:\n\nWritten: /repo/CONTRIBUTING.md (4 lines, 96 bytes)\n\nPlease continue your response based on this output.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
role: "assistant",
|
|
97
|
+
content: "Done — CONTRIBUTING.md has been written.",
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// ── scaffolding a project ─────────────────────────────────────────────────
|
|
101
|
+
{
|
|
102
|
+
role: "user",
|
|
103
|
+
content:
|
|
104
|
+
"scaffold a new project called myapp with package.json and src/index.tsx",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
role: "assistant",
|
|
108
|
+
content:
|
|
109
|
+
'<write-file>\n{"path": "myapp/package.json", "content": "{\\n \\"name\\": \\"myapp\\",\\n \\"version\\": \\"1.0.0\\",\\n \\"scripts\\": { \\"dev\\": \\"bun run src/index.tsx\\" },\\n \\"dependencies\\": { \\"react\\": \\"^18.2.0\\", \\"ink\\": \\"^4.4.1\\" },\\n \\"devDependencies\\": { \\"@types/react\\": \\"^18.2.0\\" }\\n}"}\n</write-file>',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
role: "user",
|
|
113
|
+
content:
|
|
114
|
+
"Here is the output from write-file to myapp/package.json:\n\nWritten: /repo/myapp/package.json (10 lines, 220 bytes)\n\nPlease continue your response based on this output.",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
role: "assistant",
|
|
118
|
+
content:
|
|
119
|
+
'<write-file>\n{"path": "myapp/src/index.tsx", "content": "/** @jsxImportSource react */\\nimport React from \'react\';\\nimport { render, Text } from \'ink\';\\n\\nfunction App() {\\n return <Text>Hello from myapp!</Text>;\\n}\\n\\nrender(<App />);"}\n</write-file>',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
role: "user",
|
|
123
|
+
content:
|
|
124
|
+
"Here is the output from write-file to myapp/src/index.tsx:\n\nWritten: /repo/myapp/src/index.tsx (9 lines, 210 bytes)\n\nPlease continue your response based on this output.",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
role: "assistant",
|
|
128
|
+
content:
|
|
129
|
+
"Done — scaffolded myapp/package.json and myapp/src/index.tsx. Run `cd myapp && bun install` to get started.",
|
|
130
|
+
},
|
|
131
|
+
|
|
2
132
|
// ── delete / open / pdf ───────────────────────────────────────────────────
|
|
3
133
|
{
|
|
4
134
|
role: "user",
|
|
@@ -15,7 +145,7 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
15
145
|
},
|
|
16
146
|
{
|
|
17
147
|
role: "assistant",
|
|
18
|
-
content: "Done —
|
|
148
|
+
content: "Done — src/old-component.tsx has been deleted.",
|
|
19
149
|
},
|
|
20
150
|
{
|
|
21
151
|
role: "user",
|
|
@@ -32,8 +162,7 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
32
162
|
},
|
|
33
163
|
{
|
|
34
164
|
role: "assistant",
|
|
35
|
-
content:
|
|
36
|
-
"Done — the `src/legacy` folder and all its contents have been deleted.",
|
|
165
|
+
content: "Done — src/legacy and all its contents have been deleted.",
|
|
37
166
|
},
|
|
38
167
|
{
|
|
39
168
|
role: "user",
|
|
@@ -69,7 +198,7 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
69
198
|
},
|
|
70
199
|
{
|
|
71
200
|
role: "assistant",
|
|
72
|
-
content: "Done —
|
|
201
|
+
content: "Done — docs/report.pdf has been generated.",
|
|
73
202
|
},
|
|
74
203
|
|
|
75
204
|
// ── grep ──────────────────────────────────────────────────────────────────
|
|
@@ -89,7 +218,7 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
89
218
|
{
|
|
90
219
|
role: "assistant",
|
|
91
220
|
content:
|
|
92
|
-
"
|
|
221
|
+
"ChatRunner is defined in src/components/chat/ChatRunner.tsx and imported in src/index.tsx.",
|
|
93
222
|
},
|
|
94
223
|
{
|
|
95
224
|
role: "user",
|
|
@@ -107,7 +236,7 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
107
236
|
{
|
|
108
237
|
role: "assistant",
|
|
109
238
|
content:
|
|
110
|
-
"
|
|
239
|
+
"useInput is used in src/components/chat/ChatRunner.tsx — imported on line 5 and called on line 210.",
|
|
111
240
|
},
|
|
112
241
|
|
|
113
242
|
// ── showing tool usage as an example (bracket notation, NOT real tags) ────
|
|
@@ -147,7 +276,7 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
147
276
|
{
|
|
148
277
|
role: "assistant",
|
|
149
278
|
content:
|
|
150
|
-
"The
|
|
279
|
+
"The src folder contains index.ts, App.tsx, plus subfolders components/ and utils/.",
|
|
151
280
|
},
|
|
152
281
|
{
|
|
153
282
|
role: "user",
|
|
@@ -165,7 +294,7 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
165
294
|
{
|
|
166
295
|
role: "assistant",
|
|
167
296
|
content:
|
|
168
|
-
"
|
|
297
|
+
"src/components has 3 files — Header.tsx, Footer.tsx, Button.tsx — plus two subfolders: ui/ and forms/.",
|
|
169
298
|
},
|
|
170
299
|
{
|
|
171
300
|
role: "user",
|
|
@@ -183,10 +312,10 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
183
312
|
{
|
|
184
313
|
role: "assistant",
|
|
185
314
|
content:
|
|
186
|
-
"The project root has
|
|
315
|
+
"The project root has package.json, tsconfig.json, README.md and the src/ folder for source code.",
|
|
187
316
|
},
|
|
188
317
|
|
|
189
|
-
// ── fetch → write → done
|
|
318
|
+
// ── fetch → write → done ──────────────────────────────────────────────────
|
|
190
319
|
{
|
|
191
320
|
role: "user",
|
|
192
321
|
content: "fetch https://api.github.com/repos/microsoft/typescript",
|
|
@@ -221,7 +350,7 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
221
350
|
},
|
|
222
351
|
{
|
|
223
352
|
role: "assistant",
|
|
224
|
-
content: "Done —
|
|
353
|
+
content: "Done — ts-info.json has been saved.",
|
|
225
354
|
},
|
|
226
355
|
|
|
227
356
|
// ── write it to a file → done ─────────────────────────────────────────────
|
|
@@ -241,10 +370,10 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
241
370
|
},
|
|
242
371
|
{
|
|
243
372
|
role: "assistant",
|
|
244
|
-
content: "Done —
|
|
373
|
+
content: "Done — hello.py has been written.",
|
|
245
374
|
},
|
|
246
375
|
|
|
247
|
-
// ── read before write
|
|
376
|
+
// ── read before write (modifying existing file only) ─────────────────────
|
|
248
377
|
{
|
|
249
378
|
role: "user",
|
|
250
379
|
content: "add a logout button to src/components/Header.tsx",
|
|
@@ -320,42 +449,10 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
320
449
|
{
|
|
321
450
|
role: "assistant",
|
|
322
451
|
content:
|
|
323
|
-
"The
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
// ── scaffolding ───────────────────────────────────────────────────────────
|
|
327
|
-
{
|
|
328
|
-
role: "user",
|
|
329
|
-
content:
|
|
330
|
-
"scaffold a new project called myapp with package.json and src/index.ts",
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
role: "assistant",
|
|
334
|
-
content:
|
|
335
|
-
'<write-file>\n{"path": "myapp/package.json", "content": "{\"name\":\"myapp\",\"version\":\"1.0.0\"}"}\n</write-file>',
|
|
336
|
-
},
|
|
337
|
-
{
|
|
338
|
-
role: "user",
|
|
339
|
-
content:
|
|
340
|
-
"Here is the output from write-file to myapp/package.json:\n\nWritten: /repo/myapp/package.json (1 lines, 38 bytes)\n\nPlease continue your response based on this output.",
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
role: "assistant",
|
|
344
|
-
content:
|
|
345
|
-
'<write-file>\n{"path": "myapp/src/index.ts", "content": "console.log(\'hello\')"}\n</write-file>',
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
role: "user",
|
|
349
|
-
content:
|
|
350
|
-
"Here is the output from write-file to myapp/src/index.ts:\n\nWritten: /repo/myapp/src/index.ts (1 lines, 20 bytes)\n\nPlease continue your response based on this output.",
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
role: "assistant",
|
|
354
|
-
content:
|
|
355
|
-
"Done — scaffolded `myapp/package.json` and `myapp/src/index.ts`. Run `cd myapp && npm install` to get started.",
|
|
452
|
+
"The ?? operator is the nullish coalescing operator. It returns the right side only when the left side is null or undefined.",
|
|
356
453
|
},
|
|
357
454
|
|
|
358
|
-
// ── memory
|
|
455
|
+
// ── memory ────────────────────────────────────────────────────────────────
|
|
359
456
|
{
|
|
360
457
|
role: "user",
|
|
361
458
|
content: "remember that we always use bun in this project",
|
package/src/prompts/system.ts
CHANGED
|
@@ -9,8 +9,6 @@ export function buildSystemPrompt(
|
|
|
9
9
|
.map((f) => `### ${f.path}\n\`\`\`\n${f.content.slice(0, 2000)}\n\`\`\``)
|
|
10
10
|
.join("\n\n");
|
|
11
11
|
|
|
12
|
-
// If a toolsSection is supplied (e.g. from the plugin registry), use it.
|
|
13
|
-
// Otherwise fall back to the static built-in section.
|
|
14
12
|
const tools = toolsSection ?? BUILTIN_TOOLS_SECTION;
|
|
15
13
|
|
|
16
14
|
return `You are an expert software engineer assistant with access to the user's codebase and tools.
|
|
@@ -54,6 +52,7 @@ You may emit multiple memory operations in a single response alongside normal co
|
|
|
54
52
|
11. write-file content field must be the COMPLETE file content, never empty or placeholder
|
|
55
53
|
12. After a write-file succeeds, do NOT repeat it — trust the result and move on
|
|
56
54
|
13. After a write-file succeeds, tell the user it is done immediately — do NOT auto-read the file back to verify
|
|
55
|
+
13a. NEVER read a file you just wrote — the write output confirms success. Reading back is a wasted tool call and will confuse you.
|
|
57
56
|
14. NEVER apologize and redo a tool call you already made — if write-file or shell ran and returned a result, it worked, do not run it again
|
|
58
57
|
15. NEVER say "I made a mistake" and repeat the same tool — one attempt is enough, trust the output
|
|
59
58
|
16. NEVER second-guess yourself mid-response — commit to your answer
|
|
@@ -67,6 +66,9 @@ You may emit multiple memory operations in a single response alongside normal co
|
|
|
67
66
|
24. When explaining how to use a tool in text, use [tag] bracket notation or a fenced code block — NEVER emit a real XML tool tag as part of an explanation or example
|
|
68
67
|
25. NEVER read files, list folders, or run tools that were not asked for in the current user message
|
|
69
68
|
26. NEVER use markdown formatting in plain text responses — no **bold**, no *italics*, no # headings, no bullet points with -, *, or +, no numbered lists, no backtick inline code. Write in plain prose. Only use fenced \`\`\` code blocks when showing actual code.
|
|
69
|
+
27. When the user asks you to CREATE a new file (e.g. "write a README", "create a config", "add a license", "this codebase doesn't have X"), write it IMMEDIATELY — do NOT read first, even if a stub exists.
|
|
70
|
+
28. When a tool result is returned, your response must be directly based on that result — never invent or hallucinate content unrelated to the tool output.
|
|
71
|
+
29. When scaffolding multiple files, emit ONE write-file tag per response — wait for each result before emitting the next. Never chain multiple write-file tags in a single response when file content is complex (more than 20 lines).
|
|
70
72
|
|
|
71
73
|
## SCAFFOLDING — CHAINING WRITE-FILE CALLS
|
|
72
74
|
|
|
@@ -86,25 +88,23 @@ in a single response by chaining the tags back-to-back with no text between them
|
|
|
86
88
|
The system processes each tag sequentially and automatically continues to the next one.
|
|
87
89
|
Do NOT wait for a user message between files — emit all tags at once.
|
|
88
90
|
|
|
89
|
-
##
|
|
91
|
+
## WHEN TO READ BEFORE WRITING
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
Only read a file before writing if ALL of these are true:
|
|
94
|
+
- The file already exists AND has content you need to preserve
|
|
95
|
+
- The user explicitly asked you to modify, edit, or update it (not create it)
|
|
96
|
+
- You do not already have the file content in this conversation
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
4. If you are unsure what other files import from the file you are editing, use read-folder on the parent directory first to see what exists nearby, then read-file the relevant ones
|
|
98
|
+
Never read before writing when:
|
|
99
|
+
- The user asked you to create, write, or add a new file
|
|
100
|
+
- The file is empty, missing, or a stub
|
|
101
|
+
- You already read it earlier in this conversation
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
1. Use read-
|
|
101
|
-
2.
|
|
102
|
-
3.
|
|
103
|
-
|
|
104
|
-
### The golden rule for write-file and changes:
|
|
105
|
-
- The output file must contain EVERYTHING the original had, PLUS your new additions
|
|
106
|
-
- NEVER produce a file that is shorter than the original unless you are explicitly asked to delete things
|
|
107
|
-
- If you catch yourself rewriting a file from scratch, STOP — go back and read the original first
|
|
103
|
+
When modifying an existing file:
|
|
104
|
+
1. Use read-file on the exact file first
|
|
105
|
+
2. Preserve ALL existing content — do not remove anything that was not part of the request
|
|
106
|
+
3. Your write-file must contain EVERYTHING the original had, PLUS your additions
|
|
107
|
+
4. NEVER produce a file shorter than the original unless explicitly asked to delete things
|
|
108
108
|
|
|
109
109
|
## CODEBASE
|
|
110
110
|
|
|
@@ -113,8 +113,6 @@ ${fileList.length > 0 ? fileList : "(no files indexed)"}
|
|
|
113
113
|
${memorySummary}`;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// ── Static fallback tools section (used when registry is not available) ───────
|
|
117
|
-
|
|
118
116
|
const BUILTIN_TOOLS_SECTION = `## TOOLS
|
|
119
117
|
|
|
120
118
|
You have exactly thirteen tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
|
|
@@ -164,4 +162,10 @@ You have exactly thirteen tools. To use a tool you MUST wrap it in the exact XML
|
|
|
164
162
|
### 13. changes — propose code edits (shown as a diff for user approval)
|
|
165
163
|
<changes>
|
|
166
164
|
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
167
|
-
</changes
|
|
165
|
+
</changes>
|
|
166
|
+
|
|
167
|
+
### 14. read-files — read multiple files from the repo at once
|
|
168
|
+
<read-files>
|
|
169
|
+
["src/foo.ts", "src/bar.ts"]
|
|
170
|
+
</read-files>
|
|
171
|
+
`;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
//
|
|
2
|
+
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
//
|
|
6
|
+
|
|
7
|
+
import type { Tool } from "@ridit/lens-sdk";
|
|
8
|
+
|
|
9
|
+
type ChartType = "bar" | "line" | "sparkline";
|
|
10
|
+
|
|
11
|
+
interface ChartInput {
|
|
12
|
+
type: ChartType;
|
|
13
|
+
title?: string;
|
|
14
|
+
labels?: string[];
|
|
15
|
+
values: number[];
|
|
16
|
+
/** For line charts: height in rows. Default 10. */
|
|
17
|
+
height?: number;
|
|
18
|
+
/** Bar fill character. Default "█" */
|
|
19
|
+
fill?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseChartInput(body: string): ChartInput | null {
|
|
23
|
+
const trimmed = body.trim();
|
|
24
|
+
if (!trimmed) return null;
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(trimmed) as Partial<ChartInput> & {
|
|
27
|
+
data?: number[];
|
|
28
|
+
series?: number[];
|
|
29
|
+
};
|
|
30
|
+
const values = parsed.values ?? parsed.data ?? parsed.series ?? [];
|
|
31
|
+
if (!Array.isArray(values) || values.length === 0) return null;
|
|
32
|
+
return {
|
|
33
|
+
type: parsed.type ?? "bar",
|
|
34
|
+
title: parsed.title,
|
|
35
|
+
labels: parsed.labels,
|
|
36
|
+
values: values.map(Number),
|
|
37
|
+
height: parsed.height ?? 10,
|
|
38
|
+
fill: parsed.fill ?? "█",
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const C = {
|
|
46
|
+
reset: "\x1b[0m",
|
|
47
|
+
dim: "\x1b[2m",
|
|
48
|
+
bold: "\x1b[1m",
|
|
49
|
+
orange: "\x1b[38;2;218;119;88m",
|
|
50
|
+
cyan: "\x1b[36m",
|
|
51
|
+
white: "\x1b[37m",
|
|
52
|
+
gray: "\x1b[90m",
|
|
53
|
+
green: "\x1b[32m",
|
|
54
|
+
yellow: "\x1b[33m",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const PALETTE = [C.orange, C.cyan, C.green, C.yellow];
|
|
58
|
+
function color(i: number) {
|
|
59
|
+
return PALETTE[i % PALETTE.length]!;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderBar(input: ChartInput): string {
|
|
63
|
+
const { values, labels, title, fill = "█" } = input;
|
|
64
|
+
const max = Math.max(...values, 1);
|
|
65
|
+
const termW = process.stdout.columns ?? 80;
|
|
66
|
+
const maxLabelLen = labels
|
|
67
|
+
? Math.max(...labels.map((l) => l.length), 0)
|
|
68
|
+
: String(values.length).length + 1;
|
|
69
|
+
const barMaxW = Math.max(20, termW - maxLabelLen - 12);
|
|
70
|
+
|
|
71
|
+
const lines: string[] = [];
|
|
72
|
+
if (title) lines.push(`${C.bold}${C.white}${title}${C.reset}\n`);
|
|
73
|
+
|
|
74
|
+
values.forEach((v, i) => {
|
|
75
|
+
const label = labels?.[i] ?? String(i + 1);
|
|
76
|
+
const barLen = Math.round((v / max) * barMaxW);
|
|
77
|
+
const bar = fill.repeat(barLen);
|
|
78
|
+
const valueStr = String(v);
|
|
79
|
+
lines.push(
|
|
80
|
+
`${C.gray}${label.padStart(maxLabelLen)}${C.reset} ` +
|
|
81
|
+
`${color(i)}${bar}${C.reset} ` +
|
|
82
|
+
`${C.dim}${valueStr}${C.reset}`,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
lines.push(
|
|
87
|
+
`${" ".repeat(maxLabelLen + 1)}${C.gray}${"─".repeat(barMaxW)}${C.reset}`,
|
|
88
|
+
);
|
|
89
|
+
lines.push(
|
|
90
|
+
`${" ".repeat(maxLabelLen + 1)}${C.gray}0${" ".repeat(barMaxW - String(max).length)}${max}${C.reset}`,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const SPARK_CHARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
97
|
+
|
|
98
|
+
function renderSparkline(input: ChartInput): string {
|
|
99
|
+
const { values, title } = input;
|
|
100
|
+
const min = Math.min(...values);
|
|
101
|
+
const max = Math.max(...values, min + 1);
|
|
102
|
+
const range = max - min;
|
|
103
|
+
const spark = values
|
|
104
|
+
.map((v) => {
|
|
105
|
+
const idx = Math.floor(((v - min) / range) * (SPARK_CHARS.length - 1));
|
|
106
|
+
return `${color(0)}${SPARK_CHARS[idx] ?? "▁"}${C.reset}`;
|
|
107
|
+
})
|
|
108
|
+
.join("");
|
|
109
|
+
|
|
110
|
+
const lines: string[] = [];
|
|
111
|
+
if (title) lines.push(`${C.bold}${C.white}${title}${C.reset}`);
|
|
112
|
+
lines.push(spark);
|
|
113
|
+
lines.push(`${C.gray}min ${min} max ${max} n=${values.length}${C.reset}`);
|
|
114
|
+
return lines.join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function renderLine(input: ChartInput): Promise<string> {
|
|
118
|
+
let asciichart: any;
|
|
119
|
+
try {
|
|
120
|
+
asciichart = await import("asciichart");
|
|
121
|
+
asciichart = asciichart.default ?? asciichart;
|
|
122
|
+
} catch {
|
|
123
|
+
return (
|
|
124
|
+
`${C.yellow}asciichart not installed (npm install asciichart). ` +
|
|
125
|
+
`Falling back to sparkline:${C.reset}\n` +
|
|
126
|
+
renderSparkline(input)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const termW = process.stdout.columns ?? 80;
|
|
131
|
+
const height = input.height ?? 10;
|
|
132
|
+
|
|
133
|
+
const lines: string[] = [];
|
|
134
|
+
if (input.title) {
|
|
135
|
+
lines.push(`${C.bold}${C.white}${input.title}${C.reset}\n`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const chart = asciichart.plot(input.values, {
|
|
139
|
+
height,
|
|
140
|
+
width: Math.min(input.values.length, termW - 14),
|
|
141
|
+
colors: [asciichart.cyan],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
lines.push(chart);
|
|
145
|
+
|
|
146
|
+
if (input.labels && input.labels.length === input.values.length) {
|
|
147
|
+
const step = Math.max(
|
|
148
|
+
1,
|
|
149
|
+
Math.floor(input.labels.length / Math.min(input.labels.length, 10)),
|
|
150
|
+
);
|
|
151
|
+
const labelRow = input.labels
|
|
152
|
+
.filter((_, i) => i % step === 0)
|
|
153
|
+
.map((l) => l.slice(0, 6).padEnd(6))
|
|
154
|
+
.join(" ");
|
|
155
|
+
lines.push(`${C.gray}${" ".repeat(8)}${labelRow}${C.reset}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return lines.join("\n");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function renderChart(input: ChartInput): Promise<string> {
|
|
162
|
+
switch (input.type) {
|
|
163
|
+
case "bar":
|
|
164
|
+
return renderBar(input);
|
|
165
|
+
case "sparkline":
|
|
166
|
+
return renderSparkline(input);
|
|
167
|
+
case "line":
|
|
168
|
+
return await renderLine(input);
|
|
169
|
+
default:
|
|
170
|
+
return renderBar(input);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const chartDataTool: Tool<ChartInput> = {
|
|
175
|
+
name: "chart-data",
|
|
176
|
+
description:
|
|
177
|
+
"render a bar, line, or sparkline chart in the terminal from given data",
|
|
178
|
+
safe: true,
|
|
179
|
+
permissionLabel: "chart",
|
|
180
|
+
|
|
181
|
+
systemPromptEntry: (i) =>
|
|
182
|
+
`### ${i}. chart-data — render a chart in the terminal\n` +
|
|
183
|
+
`Types: "bar" (default), "line", "sparkline"\n` +
|
|
184
|
+
`<chart-data>\n` +
|
|
185
|
+
`{"type": "bar", "title": "Commits per month", "labels": ["Jan","Feb","Mar"], "values": [12, 34, 21]}\n` +
|
|
186
|
+
`</chart-data>\n` +
|
|
187
|
+
`<chart-data>\n` +
|
|
188
|
+
`{"type": "line", "title": "Stars over time", "values": [1,3,6,10,15,21,28], "height": 8}\n` +
|
|
189
|
+
`</chart-data>\n` +
|
|
190
|
+
`<chart-data>\n` +
|
|
191
|
+
`{"type": "sparkline", "title": "Daily commits", "values": [2,5,1,8,3,7,4]}\n` +
|
|
192
|
+
`</chart-data>`,
|
|
193
|
+
|
|
194
|
+
parseInput: parseChartInput,
|
|
195
|
+
|
|
196
|
+
summariseInput: ({ type, title }) =>
|
|
197
|
+
title ? `${type} chart — ${title}` : `${type} chart`,
|
|
198
|
+
|
|
199
|
+
execute: async (input, _ctx) => {
|
|
200
|
+
try {
|
|
201
|
+
const rendered = await renderChart(input);
|
|
202
|
+
return { kind: "image" as any, value: rendered };
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return {
|
|
205
|
+
kind: "text",
|
|
206
|
+
value: `Error rendering chart: ${err instanceof Error ? err.message : String(err)}`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
};
|