@ridit/lens 0.4.5 → 0.4.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/README.md +27 -1
- package/dist/index.mjs +68 -11
- package/package.json +1 -1
- package/src/commands/chat.tsx +3 -0
- package/src/components/chat/ChatView.tsx +4 -0
- package/src/index.tsx +16 -2
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ lens chat --dev output structured JSON (for SDK/tooling
|
|
|
41
41
|
lens chat --single run one message then exit, resumes latest session
|
|
42
42
|
lens chat --force-all auto-approve all tools including writes and shell
|
|
43
43
|
lens chat --dev --prompt <text> headless mode: JSON output, no UI
|
|
44
|
+
lens chat --runtime-tools <path> load extra tools from a JSON file at runtime
|
|
44
45
|
|
|
45
46
|
lens provider configure AI providers (interactive)
|
|
46
47
|
lens provider --list list configured providers
|
|
@@ -90,7 +91,32 @@ Once inside a `lens chat` session, use slash commands:
|
|
|
90
91
|
|
|
91
92
|
## Extending Lens
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
### Runtime Tools
|
|
95
|
+
|
|
96
|
+
Pass a JSON file to `--runtime-tools` to inject custom tools into any chat session without modifying Lens itself. Each tool declares a name, description, optional parameters, and an HTTP endpoint that Lens will POST to when the AI calls it.
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
[
|
|
100
|
+
{
|
|
101
|
+
"name": "get_weather",
|
|
102
|
+
"description": "Returns current weather for a city",
|
|
103
|
+
"parameters": {
|
|
104
|
+
"city": { "type": "string", "description": "City name" }
|
|
105
|
+
},
|
|
106
|
+
"endpoint": "http://localhost:4242/get_weather"
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
lens chat --runtime-tools ./my-tools.json --prompt "What's the weather in London?"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Lens POSTs the tool arguments as JSON to the endpoint and returns the response body to the model. Runtime tools are always auto-approved in headless mode.
|
|
116
|
+
|
|
117
|
+
### SDK
|
|
118
|
+
|
|
119
|
+
Custom tools can also be built and registered using [`@ridit/lens-sdk`](https://www.npmjs.com/package/@ridit/lens-sdk).
|
|
94
120
|
|
|
95
121
|
## License
|
|
96
122
|
|
package/dist/index.mjs
CHANGED
|
@@ -68823,6 +68823,9 @@ function trimStartOfStream() {
|
|
|
68823
68823
|
}
|
|
68824
68824
|
var HANGING_STREAM_WARNING_TIME_MS = 15 * 1000;
|
|
68825
68825
|
|
|
68826
|
+
// ../core/src/agent/index.ts
|
|
68827
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
68828
|
+
|
|
68826
68829
|
// ../core/src/config/index.ts
|
|
68827
68830
|
import { join } from "path";
|
|
68828
68831
|
import { homedir as homedir2 } from "os";
|
|
@@ -74705,8 +74708,43 @@ var scrape = tool({
|
|
|
74705
74708
|
var tools = { read, write, bash, grep, ls, remember, del, search, scrape };
|
|
74706
74709
|
|
|
74707
74710
|
// ../core/src/agent/index.ts
|
|
74711
|
+
function buildZodSchema(parameters) {
|
|
74712
|
+
const shape = {};
|
|
74713
|
+
for (const [key, val] of Object.entries(parameters)) {
|
|
74714
|
+
let field;
|
|
74715
|
+
if (val.type === "number")
|
|
74716
|
+
field = exports_external.number();
|
|
74717
|
+
else if (val.type === "boolean")
|
|
74718
|
+
field = exports_external.boolean();
|
|
74719
|
+
else
|
|
74720
|
+
field = exports_external.string();
|
|
74721
|
+
if (val.description)
|
|
74722
|
+
field = field.describe(val.description);
|
|
74723
|
+
shape[key] = field;
|
|
74724
|
+
}
|
|
74725
|
+
return exports_external.object(shape);
|
|
74726
|
+
}
|
|
74708
74727
|
async function chat(options2) {
|
|
74709
|
-
|
|
74728
|
+
let extraTools = {};
|
|
74729
|
+
if (options2.runtimeTools && existsSync5(options2.runtimeTools)) {
|
|
74730
|
+
const raw = JSON.parse(readFileSync5(options2.runtimeTools, "utf-8"));
|
|
74731
|
+
for (const t of raw) {
|
|
74732
|
+
extraTools[t.name] = tool({
|
|
74733
|
+
description: t.description,
|
|
74734
|
+
parameters: buildZodSchema(t.parameters ?? {}),
|
|
74735
|
+
execute: async (args) => {
|
|
74736
|
+
const res = await fetch(t.endpoint, {
|
|
74737
|
+
method: "POST",
|
|
74738
|
+
headers: { "Content-Type": "application/json" },
|
|
74739
|
+
body: JSON.stringify(args)
|
|
74740
|
+
});
|
|
74741
|
+
return await res.json();
|
|
74742
|
+
}
|
|
74743
|
+
});
|
|
74744
|
+
}
|
|
74745
|
+
}
|
|
74746
|
+
const allTools = { ...tools, ...extraTools };
|
|
74747
|
+
const activeTools = options2.onBeforeToolCall ? Object.fromEntries(Object.entries(allTools).map(([name17, t]) => [
|
|
74710
74748
|
name17,
|
|
74711
74749
|
{
|
|
74712
74750
|
...t,
|
|
@@ -74717,7 +74755,7 @@ async function chat(options2) {
|
|
|
74717
74755
|
return t.execute(args, opts);
|
|
74718
74756
|
}
|
|
74719
74757
|
}
|
|
74720
|
-
])) :
|
|
74758
|
+
])) : allTools;
|
|
74721
74759
|
const responseMessages = [];
|
|
74722
74760
|
const providerSettings = (() => {
|
|
74723
74761
|
try {
|
|
@@ -74732,8 +74770,12 @@ async function chat(options2) {
|
|
|
74732
74770
|
messages: options2.messages,
|
|
74733
74771
|
system: options2.system,
|
|
74734
74772
|
maxSteps: options2.maxSteps ?? 50,
|
|
74735
|
-
...providerSettings?.maxTokens !== undefined && {
|
|
74736
|
-
|
|
74773
|
+
...providerSettings?.maxTokens !== undefined && {
|
|
74774
|
+
maxTokens: providerSettings.maxTokens
|
|
74775
|
+
},
|
|
74776
|
+
...providerSettings?.temperature !== undefined && {
|
|
74777
|
+
temperature: providerSettings.temperature
|
|
74778
|
+
},
|
|
74737
74779
|
onStepFinish: (step) => {
|
|
74738
74780
|
responseMessages.push(...step.response.messages);
|
|
74739
74781
|
for (const toolResult of step.toolResults) {
|
|
@@ -76188,7 +76230,8 @@ function ChatRunner({
|
|
|
76188
76230
|
initialMessage,
|
|
76189
76231
|
dev = false,
|
|
76190
76232
|
single = false,
|
|
76191
|
-
sessionId
|
|
76233
|
+
sessionId,
|
|
76234
|
+
runtimeTools
|
|
76192
76235
|
}) {
|
|
76193
76236
|
const [stage, setStage] = useState12("idle");
|
|
76194
76237
|
const [showProvider, setShowProvider] = useState12(false);
|
|
@@ -76310,6 +76353,7 @@ function ChatRunner({
|
|
|
76310
76353
|
await chat({
|
|
76311
76354
|
messages: getMessages(sessionRef.current),
|
|
76312
76355
|
system: getSystemPrompt(repoPath),
|
|
76356
|
+
runtimeTools,
|
|
76313
76357
|
onChunk: () => {},
|
|
76314
76358
|
onToolCall: (tool2, args) => {
|
|
76315
76359
|
devTools.push({ tool: tool2, args, result: null });
|
|
@@ -76359,6 +76403,7 @@ function ChatRunner({
|
|
|
76359
76403
|
await chat({
|
|
76360
76404
|
messages: getMessages(sessionRef.current),
|
|
76361
76405
|
system: getSystemPrompt(repoPath),
|
|
76406
|
+
runtimeTools,
|
|
76362
76407
|
onBeforeToolCall: (tool2, args) => {
|
|
76363
76408
|
if (forceApproveRef.current || SAFE_TOOLS.has(tool2))
|
|
76364
76409
|
return Promise.resolve(true);
|
|
@@ -76622,7 +76667,8 @@ function ChatCommand({
|
|
|
76622
76667
|
initialMessage,
|
|
76623
76668
|
dev = false,
|
|
76624
76669
|
single = false,
|
|
76625
|
-
sessionId
|
|
76670
|
+
sessionId,
|
|
76671
|
+
runtimeTools
|
|
76626
76672
|
}) {
|
|
76627
76673
|
return /* @__PURE__ */ jsxDEV15(Box12, {
|
|
76628
76674
|
flexDirection: "column",
|
|
@@ -76632,7 +76678,8 @@ function ChatCommand({
|
|
|
76632
76678
|
initialMessage,
|
|
76633
76679
|
dev,
|
|
76634
76680
|
single,
|
|
76635
|
-
sessionId
|
|
76681
|
+
sessionId,
|
|
76682
|
+
runtimeTools
|
|
76636
76683
|
}, undefined, false, undefined, this)
|
|
76637
76684
|
}, undefined, false, undefined, this);
|
|
76638
76685
|
}
|
|
@@ -78170,12 +78217,21 @@ async function runHeadless(opts) {
|
|
|
78170
78217
|
saveSession(session);
|
|
78171
78218
|
const toolLog = [];
|
|
78172
78219
|
const denied = [];
|
|
78220
|
+
const runtimeToolNames = new Set;
|
|
78221
|
+
if (opts.runtimeTools) {
|
|
78222
|
+
try {
|
|
78223
|
+
const raw = JSON.parse(__require("fs").readFileSync(opts.runtimeTools, "utf-8"));
|
|
78224
|
+
if (Array.isArray(raw))
|
|
78225
|
+
raw.forEach((t) => runtimeToolNames.add(t.name));
|
|
78226
|
+
} catch {}
|
|
78227
|
+
}
|
|
78173
78228
|
await chat({
|
|
78174
78229
|
messages: getMessages(session),
|
|
78175
78230
|
system: getSystemPrompt(repoPath),
|
|
78231
|
+
runtimeTools: opts.runtimeTools,
|
|
78176
78232
|
maxSteps: opts.forceAll ? 50 : 2,
|
|
78177
78233
|
onBeforeToolCall: (tool2, args) => {
|
|
78178
|
-
if (opts.forceAll || HEADLESS_SAFE_TOOLS.has(tool2))
|
|
78234
|
+
if (opts.forceAll || HEADLESS_SAFE_TOOLS.has(tool2) || runtimeToolNames.has(tool2))
|
|
78179
78235
|
return Promise.resolve(true);
|
|
78180
78236
|
const a = args;
|
|
78181
78237
|
const description = tool2 === "bash" ? String(a.command ?? a.cmd ?? "") : tool2 === "write" ? String(a.path ?? a.file_path ?? "") : String(a.path ?? a.file_path ?? "");
|
|
@@ -78208,10 +78264,10 @@ async function runHeadless(opts) {
|
|
|
78208
78264
|
});
|
|
78209
78265
|
}
|
|
78210
78266
|
var program = new Command().enablePositionalOptions();
|
|
78211
|
-
program.command("chat").description("Chat with your codebase — ask questions or make changes").option("-p, --path <path>", "Path to the repo", ".").option("-d, --dev", "Output structured JSON (no UI)").option("--single", "Single-shot: run one message then exit").option("--session <id>", "Resume session by ID, or create one with that ID").option("--id <id>", "Alias for --session").option("--force-all", "Auto-approve all tools").option("--prompt <text>", "Run a prompt non-interactively").action((opts) => {
|
|
78267
|
+
program.command("chat").description("Chat with your codebase — ask questions or make changes").option("-p, --path <path>", "Path to the repo", ".").option("-d, --dev", "Output structured JSON (no UI)").option("--single", "Single-shot: run one message then exit").option("--session <id>", "Resume session by ID, or create one with that ID").option("--id <id>", "Alias for --session").option("--force-all", "Auto-approve all tools").option("--prompt <text>", "Run a prompt non-interactively").option("--runtime-tools <path>", "path to runtime tools JSON file").action((opts) => {
|
|
78212
78268
|
const sessionId = opts.session ?? opts.id;
|
|
78213
78269
|
if (opts.prompt && (opts.dev || opts.single)) {
|
|
78214
|
-
runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll });
|
|
78270
|
+
runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll, runtimeTools: opts.runtimeTools });
|
|
78215
78271
|
return;
|
|
78216
78272
|
}
|
|
78217
78273
|
render(/* @__PURE__ */ jsxDEV21(ChatCommand, {
|
|
@@ -78220,7 +78276,8 @@ program.command("chat").description("Chat with your codebase — ask questions o
|
|
|
78220
78276
|
dev: opts.dev ?? false,
|
|
78221
78277
|
single: opts.single ?? false,
|
|
78222
78278
|
sessionId,
|
|
78223
|
-
initialMessage: opts.prompt
|
|
78279
|
+
initialMessage: opts.prompt,
|
|
78280
|
+
runtimeTools: opts.runtimeTools
|
|
78224
78281
|
}, undefined, false, undefined, this));
|
|
78225
78282
|
});
|
|
78226
78283
|
program.command("commit [files...]").description("Generate a smart conventional commit message from staged changes").option("-p, --path <path>", "Path to the repo", ".").option("--auto", "Stage all changes and commit without confirmation").option("--push", "Push to remote after committing").action((files, opts) => {
|
package/package.json
CHANGED
package/src/commands/chat.tsx
CHANGED
|
@@ -9,6 +9,7 @@ export function ChatCommand({
|
|
|
9
9
|
dev = false,
|
|
10
10
|
single = false,
|
|
11
11
|
sessionId,
|
|
12
|
+
runtimeTools,
|
|
12
13
|
}: {
|
|
13
14
|
path: string;
|
|
14
15
|
autoForce?: boolean;
|
|
@@ -16,6 +17,7 @@ export function ChatCommand({
|
|
|
16
17
|
dev?: boolean;
|
|
17
18
|
single?: boolean;
|
|
18
19
|
sessionId?: string;
|
|
20
|
+
runtimeTools?: string;
|
|
19
21
|
}) {
|
|
20
22
|
return (
|
|
21
23
|
<Box flexDirection="column">
|
|
@@ -26,6 +28,7 @@ export function ChatCommand({
|
|
|
26
28
|
dev={dev}
|
|
27
29
|
single={single}
|
|
28
30
|
sessionId={sessionId}
|
|
31
|
+
runtimeTools={runtimeTools}
|
|
29
32
|
/>
|
|
30
33
|
</Box>
|
|
31
34
|
);
|
|
@@ -128,6 +128,7 @@ export function ChatRunner({
|
|
|
128
128
|
dev = false,
|
|
129
129
|
single = false,
|
|
130
130
|
sessionId,
|
|
131
|
+
runtimeTools,
|
|
131
132
|
}: {
|
|
132
133
|
repoPath: string;
|
|
133
134
|
autoForce?: boolean;
|
|
@@ -135,6 +136,7 @@ export function ChatRunner({
|
|
|
135
136
|
dev?: boolean;
|
|
136
137
|
single?: boolean;
|
|
137
138
|
sessionId?: string;
|
|
139
|
+
runtimeTools?: string;
|
|
138
140
|
}) {
|
|
139
141
|
const [stage, setStage] = useState<"idle" | "thinking">("idle");
|
|
140
142
|
const [showProvider, setShowProvider] = useState(false);
|
|
@@ -292,6 +294,7 @@ export function ChatRunner({
|
|
|
292
294
|
await chat({
|
|
293
295
|
messages: getMessages(sessionRef.current),
|
|
294
296
|
system: getSystemPrompt(repoPath),
|
|
297
|
+
runtimeTools,
|
|
295
298
|
onChunk: () => {},
|
|
296
299
|
onToolCall: (tool, args) => {
|
|
297
300
|
devTools.push({ tool, args, result: null });
|
|
@@ -350,6 +353,7 @@ export function ChatRunner({
|
|
|
350
353
|
await chat({
|
|
351
354
|
messages: getMessages(sessionRef.current),
|
|
352
355
|
system: getSystemPrompt(repoPath),
|
|
356
|
+
runtimeTools,
|
|
353
357
|
onBeforeToolCall: (tool, args) => {
|
|
354
358
|
if (forceApproveRef.current || SAFE_TOOLS.has(tool))
|
|
355
359
|
return Promise.resolve(true);
|
package/src/index.tsx
CHANGED
|
@@ -76,6 +76,7 @@ async function runHeadless(opts: {
|
|
|
76
76
|
sessionId?: string;
|
|
77
77
|
single?: boolean;
|
|
78
78
|
forceAll?: boolean;
|
|
79
|
+
runtimeTools?: string;
|
|
79
80
|
}) {
|
|
80
81
|
const repoPath = opts.path;
|
|
81
82
|
|
|
@@ -101,13 +102,23 @@ async function runHeadless(opts: {
|
|
|
101
102
|
const toolLog: { tool: string; args: unknown; result: unknown }[] = [];
|
|
102
103
|
const denied: { tool: string; description: string }[] = [];
|
|
103
104
|
|
|
105
|
+
// runtime tools are explicitly user-provided — always approve them
|
|
106
|
+
const runtimeToolNames = new Set<string>();
|
|
107
|
+
if (opts.runtimeTools) {
|
|
108
|
+
try {
|
|
109
|
+
const raw = JSON.parse(require("fs").readFileSync(opts.runtimeTools, "utf-8"));
|
|
110
|
+
if (Array.isArray(raw)) raw.forEach((t: { name: string }) => runtimeToolNames.add(t.name));
|
|
111
|
+
} catch { /* ignore parse errors */ }
|
|
112
|
+
}
|
|
113
|
+
|
|
104
114
|
await chat({
|
|
105
115
|
messages: getMessages(session),
|
|
106
116
|
system: getSystemPrompt(repoPath),
|
|
117
|
+
runtimeTools: opts.runtimeTools,
|
|
107
118
|
// 2 steps: 1 tool attempt (or denial) + 1 text response
|
|
108
119
|
maxSteps: opts.forceAll ? 50 : 2,
|
|
109
120
|
onBeforeToolCall: (tool, args) => {
|
|
110
|
-
if (opts.forceAll || HEADLESS_SAFE_TOOLS.has(tool)) return Promise.resolve(true);
|
|
121
|
+
if (opts.forceAll || HEADLESS_SAFE_TOOLS.has(tool) || runtimeToolNames.has(tool)) return Promise.resolve(true);
|
|
111
122
|
// record denial — model will respond naturally explaining what it needs
|
|
112
123
|
const a = args as Record<string, unknown>;
|
|
113
124
|
const description =
|
|
@@ -159,6 +170,7 @@ program
|
|
|
159
170
|
.option("--id <id>", "Alias for --session")
|
|
160
171
|
.option("--force-all", "Auto-approve all tools")
|
|
161
172
|
.option("--prompt <text>", "Run a prompt non-interactively")
|
|
173
|
+
.option("--runtime-tools <path>", "path to runtime tools JSON file")
|
|
162
174
|
.action(
|
|
163
175
|
(opts: {
|
|
164
176
|
path: string;
|
|
@@ -168,11 +180,12 @@ program
|
|
|
168
180
|
id?: string;
|
|
169
181
|
forceAll?: boolean;
|
|
170
182
|
prompt?: string;
|
|
183
|
+
runtimeTools?: string;
|
|
171
184
|
}) => {
|
|
172
185
|
const sessionId = opts.session ?? opts.id;
|
|
173
186
|
// headless: dev+prompt or single+prompt → no UI, output JSON and exit
|
|
174
187
|
if (opts.prompt && (opts.dev || opts.single)) {
|
|
175
|
-
runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll });
|
|
188
|
+
runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll, runtimeTools: opts.runtimeTools });
|
|
176
189
|
return;
|
|
177
190
|
}
|
|
178
191
|
render(
|
|
@@ -183,6 +196,7 @@ program
|
|
|
183
196
|
single={opts.single ?? false}
|
|
184
197
|
sessionId={sessionId}
|
|
185
198
|
initialMessage={opts.prompt}
|
|
199
|
+
runtimeTools={opts.runtimeTools}
|
|
186
200
|
/>,
|
|
187
201
|
);
|
|
188
202
|
},
|