@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 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
- Custom tools can be built and registered using [`@ridit/lens-sdk`](https://www.npmjs.com/package/@ridit/lens-sdk).
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
- const activeTools = options2.onBeforeToolCall ? Object.fromEntries(Object.entries(tools).map(([name17, t]) => [
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
- ])) : tools;
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 && { maxTokens: providerSettings.maxTokens },
74736
- ...providerSettings?.temperature !== undefined && { temperature: providerSettings.temperature },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ridit/lens",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Understand your codebase.",
5
5
  "author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",
6
6
  "license": "MIT",
@@ -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
  },