@ridit/lens 0.4.5 → 0.4.7

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
  }
@@ -78158,24 +78205,59 @@ function getLastDeniedAction(messages) {
78158
78205
  async function runHeadless(opts) {
78159
78206
  const repoPath = opts.path;
78160
78207
  let session = opts.sessionId ? loadSession(opts.sessionId) ?? createSessionWithId(opts.sessionId, repoPath) : opts.single ? getLatestSession(repoPath) ?? createSession(repoPath) : createSession(repoPath);
78161
- let prompt = opts.prompt;
78162
- if (opts.forceAll && APPROVAL_WORDS.has(prompt.trim().toLowerCase())) {
78163
- const pending = getLastDeniedAction(getMessages(session));
78164
- if (pending) {
78165
- prompt = `Proceed with the previously denied operation: use the ${pending.tool} tool on "${pending.description}".`;
78208
+ if (opts.resume) {
78209
+ const msgs = getMessages(session);
78210
+ let trimAt = -1;
78211
+ for (let i = msgs.length - 1;i >= 0; i--) {
78212
+ const msg = msgs[i];
78213
+ if (!msg || msg.role !== "tool")
78214
+ continue;
78215
+ const content = Array.isArray(msg.content) ? msg.content : [];
78216
+ const isDenied = content.some((p) => typeof p === "object" && p !== null && ("type" in p) && p.type === "tool-result" && ("result" in p) && typeof p.result === "string" && p.result.includes("Permission denied"));
78217
+ if (isDenied) {
78218
+ for (let j = i - 1;j >= 0; j--) {
78219
+ if (msgs[j]?.role === "assistant") {
78220
+ trimAt = j;
78221
+ break;
78222
+ }
78223
+ }
78224
+ break;
78225
+ }
78226
+ }
78227
+ if (trimAt >= 0) {
78228
+ session = { ...session, messages: msgs.slice(0, trimAt) };
78166
78229
  }
78230
+ if (!opts.single || opts.sessionId)
78231
+ saveSession(session);
78232
+ } else {
78233
+ let prompt = opts.prompt;
78234
+ if (opts.forceAll && APPROVAL_WORDS.has(prompt.trim().toLowerCase())) {
78235
+ const pending = getLastDeniedAction(getMessages(session));
78236
+ if (pending) {
78237
+ prompt = `Proceed with the previously denied operation: use the ${pending.tool} tool on "${pending.description}".`;
78238
+ }
78239
+ }
78240
+ session = addMessage(session, "user", prompt);
78241
+ if (!opts.single || opts.sessionId)
78242
+ saveSession(session);
78167
78243
  }
78168
- session = addMessage(session, "user", prompt);
78169
- if (!opts.single || opts.sessionId)
78170
- saveSession(session);
78171
78244
  const toolLog = [];
78172
78245
  const denied = [];
78246
+ const runtimeToolNames = new Set;
78247
+ if (opts.runtimeTools) {
78248
+ try {
78249
+ const raw = JSON.parse(__require("fs").readFileSync(opts.runtimeTools, "utf-8"));
78250
+ if (Array.isArray(raw))
78251
+ raw.forEach((t) => runtimeToolNames.add(t.name));
78252
+ } catch {}
78253
+ }
78173
78254
  await chat({
78174
78255
  messages: getMessages(session),
78175
78256
  system: getSystemPrompt(repoPath),
78257
+ runtimeTools: opts.runtimeTools,
78176
78258
  maxSteps: opts.forceAll ? 50 : 2,
78177
78259
  onBeforeToolCall: (tool2, args) => {
78178
- if (opts.forceAll || HEADLESS_SAFE_TOOLS.has(tool2))
78260
+ if (opts.forceAll || HEADLESS_SAFE_TOOLS.has(tool2) || runtimeToolNames.has(tool2))
78179
78261
  return Promise.resolve(true);
78180
78262
  const a = args;
78181
78263
  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 +78290,10 @@ async function runHeadless(opts) {
78208
78290
  });
78209
78291
  }
78210
78292
  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) => {
78293
+ 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("--resume", "Resume from last permission-denied tool call (no new prompt needed)").option("--runtime-tools <path>", "path to runtime tools JSON file").action((opts) => {
78212
78294
  const sessionId = opts.session ?? opts.id;
78213
- if (opts.prompt && (opts.dev || opts.single)) {
78214
- runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll });
78295
+ if ((opts.prompt || opts.resume) && (opts.dev || opts.single)) {
78296
+ runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll ?? opts.resume, runtimeTools: opts.runtimeTools, resume: opts.resume });
78215
78297
  return;
78216
78298
  }
78217
78299
  render(/* @__PURE__ */ jsxDEV21(ChatCommand, {
@@ -78220,7 +78302,8 @@ program.command("chat").description("Chat with your codebase — ask questions o
78220
78302
  dev: opts.dev ?? false,
78221
78303
  single: opts.single ?? false,
78222
78304
  sessionId,
78223
- initialMessage: opts.prompt
78305
+ initialMessage: opts.prompt,
78306
+ runtimeTools: opts.runtimeTools
78224
78307
  }, undefined, false, undefined, this));
78225
78308
  });
78226
78309
  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) => {
@@ -78308,13 +78391,13 @@ program.command("run <cmd>").description("Run your dev server. Lens watches and
78308
78391
  });
78309
78392
  var firstArg = process.argv[2];
78310
78393
  if (!firstArg || firstArg.startsWith("-")) {
78311
- const defaultFlags = new Command().option("-p, --path <path>", "Path to the repo", ".").option("--session <id>", "Resume session by ID").option("--single", "Single-shot mode").option("--prompt <text>", "Run a prompt").option("-d, --dev", "Output JSON (no UI)").option("--force-all", "Auto-approve all tools").allowUnknownOption().exitOverride();
78394
+ const defaultFlags = new Command().option("-p, --path <path>", "Path to the repo", ".").option("--session <id>", "Resume session by ID").option("--single", "Single-shot mode").option("--prompt <text>", "Run a prompt").option("-d, --dev", "Output JSON (no UI)").option("--force-all", "Auto-approve all tools").option("--resume", "Resume from last permission-denied tool call").allowUnknownOption().exitOverride();
78312
78395
  try {
78313
78396
  defaultFlags.parse(process.argv);
78314
78397
  } catch {}
78315
78398
  const opts = defaultFlags.opts();
78316
- if (opts.prompt && (opts.dev || opts.single)) {
78317
- runHeadless({ path: opts.path ?? ".", prompt: opts.prompt, sessionId: opts.session, single: opts.single, forceAll: opts.forceAll });
78399
+ if ((opts.prompt || opts.resume) && (opts.dev || opts.single)) {
78400
+ runHeadless({ path: opts.path ?? ".", prompt: opts.prompt, sessionId: opts.session, single: opts.single, forceAll: opts.forceAll ?? opts.resume, resume: opts.resume });
78318
78401
  } else {
78319
78402
  render(/* @__PURE__ */ jsxDEV21(ChatCommand, {
78320
78403
  path: opts.path ?? ".",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ridit/lens",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
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
@@ -72,10 +72,12 @@ function getLastDeniedAction(messages: ReturnType<typeof getMessages>): { tool:
72
72
 
73
73
  async function runHeadless(opts: {
74
74
  path: string;
75
- prompt: string;
75
+ prompt?: string;
76
76
  sessionId?: string;
77
77
  single?: boolean;
78
78
  forceAll?: boolean;
79
+ runtimeTools?: string;
80
+ resume?: boolean;
79
81
  }) {
80
82
  const repoPath = opts.path;
81
83
 
@@ -85,29 +87,75 @@ async function runHeadless(opts: {
85
87
  ? (getLatestSession(repoPath) ?? createSession(repoPath))
86
88
  : createSession(repoPath);
87
89
 
88
- // if user is approving a prior denial, make the intent unambiguous
89
- let prompt = opts.prompt;
90
- if (opts.forceAll && APPROVAL_WORDS.has(prompt.trim().toLowerCase())) {
91
- const pending = getLastDeniedAction(getMessages(session));
92
- if (pending) {
93
- prompt = `Proceed with the previously denied operation: use the ${pending.tool} tool on "${pending.description}".`;
90
+ if (opts.resume) {
91
+ // Rewind the session: strip the last denied tool-call (assistant message),
92
+ // its "Permission denied" tool result, and the assistant's text response after it.
93
+ // This leaves the session ending at the last successful state so the agent
94
+ // can re-attempt the denied tool with forceAll: true.
95
+ const msgs = getMessages(session);
96
+ let trimAt = -1;
97
+ for (let i = msgs.length - 1; i >= 0; i--) {
98
+ const msg = msgs[i];
99
+ if (!msg || msg.role !== "tool") continue;
100
+ const content = Array.isArray(msg.content) ? msg.content : [];
101
+ const isDenied = content.some(
102
+ (p: unknown) =>
103
+ typeof p === "object" && p !== null &&
104
+ "type" in p && (p as { type: string }).type === "tool-result" &&
105
+ "result" in p && typeof (p as { result: unknown }).result === "string" &&
106
+ ((p as { result: string }).result).includes("Permission denied"),
107
+ );
108
+ if (isDenied) {
109
+ // Find the assistant message immediately before this tool result (the tool-call message)
110
+ for (let j = i - 1; j >= 0; j--) {
111
+ if (msgs[j]?.role === "assistant") {
112
+ trimAt = j;
113
+ break;
114
+ }
115
+ }
116
+ break;
117
+ }
118
+ }
119
+ if (trimAt >= 0) {
120
+ session = { ...session, messages: msgs.slice(0, trimAt) };
121
+ }
122
+ // Save trimmed session so context is clean for this run
123
+ if (!opts.single || opts.sessionId) saveSession(session);
124
+ } else {
125
+ // if user is approving a prior denial, make the intent unambiguous
126
+ let prompt = opts.prompt!;
127
+ if (opts.forceAll && APPROVAL_WORDS.has(prompt.trim().toLowerCase())) {
128
+ const pending = getLastDeniedAction(getMessages(session));
129
+ if (pending) {
130
+ prompt = `Proceed with the previously denied operation: use the ${pending.tool} tool on "${pending.description}".`;
131
+ }
94
132
  }
95
- }
96
133
 
97
- session = addMessage(session, "user", prompt);
98
- // save now so context is available on follow-up messages even if we exit early
99
- if (!opts.single || opts.sessionId) saveSession(session);
134
+ session = addMessage(session, "user", prompt);
135
+ // save now so context is available on follow-up messages even if we exit early
136
+ if (!opts.single || opts.sessionId) saveSession(session);
137
+ }
100
138
 
101
139
  const toolLog: { tool: string; args: unknown; result: unknown }[] = [];
102
140
  const denied: { tool: string; description: string }[] = [];
103
141
 
142
+ // runtime tools are explicitly user-provided — always approve them
143
+ const runtimeToolNames = new Set<string>();
144
+ if (opts.runtimeTools) {
145
+ try {
146
+ const raw = JSON.parse(require("fs").readFileSync(opts.runtimeTools, "utf-8"));
147
+ if (Array.isArray(raw)) raw.forEach((t: { name: string }) => runtimeToolNames.add(t.name));
148
+ } catch { /* ignore parse errors */ }
149
+ }
150
+
104
151
  await chat({
105
152
  messages: getMessages(session),
106
153
  system: getSystemPrompt(repoPath),
154
+ runtimeTools: opts.runtimeTools,
107
155
  // 2 steps: 1 tool attempt (or denial) + 1 text response
108
156
  maxSteps: opts.forceAll ? 50 : 2,
109
157
  onBeforeToolCall: (tool, args) => {
110
- if (opts.forceAll || HEADLESS_SAFE_TOOLS.has(tool)) return Promise.resolve(true);
158
+ if (opts.forceAll || HEADLESS_SAFE_TOOLS.has(tool) || runtimeToolNames.has(tool)) return Promise.resolve(true);
111
159
  // record denial — model will respond naturally explaining what it needs
112
160
  const a = args as Record<string, unknown>;
113
161
  const description =
@@ -159,6 +207,8 @@ program
159
207
  .option("--id <id>", "Alias for --session")
160
208
  .option("--force-all", "Auto-approve all tools")
161
209
  .option("--prompt <text>", "Run a prompt non-interactively")
210
+ .option("--resume", "Resume from last permission-denied tool call (no new prompt needed)")
211
+ .option("--runtime-tools <path>", "path to runtime tools JSON file")
162
212
  .action(
163
213
  (opts: {
164
214
  path: string;
@@ -168,11 +218,13 @@ program
168
218
  id?: string;
169
219
  forceAll?: boolean;
170
220
  prompt?: string;
221
+ resume?: boolean;
222
+ runtimeTools?: string;
171
223
  }) => {
172
224
  const sessionId = opts.session ?? opts.id;
173
- // headless: dev+prompt or single+prompt → no UI, output JSON and exit
174
- if (opts.prompt && (opts.dev || opts.single)) {
175
- runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll });
225
+ // headless: dev+prompt, single+prompt, or --resume → no UI, output JSON and exit
226
+ if ((opts.prompt || opts.resume) && (opts.dev || opts.single)) {
227
+ runHeadless({ path: opts.path, prompt: opts.prompt, sessionId, single: opts.single, forceAll: opts.forceAll ?? opts.resume, runtimeTools: opts.runtimeTools, resume: opts.resume });
176
228
  return;
177
229
  }
178
230
  render(
@@ -183,6 +235,7 @@ program
183
235
  single={opts.single ?? false}
184
236
  sessionId={sessionId}
185
237
  initialMessage={opts.prompt}
238
+ runtimeTools={opts.runtimeTools}
186
239
  />,
187
240
  );
188
241
  },
@@ -353,6 +406,7 @@ if (!firstArg || firstArg.startsWith("-")) {
353
406
  .option("--prompt <text>", "Run a prompt")
354
407
  .option("-d, --dev", "Output JSON (no UI)")
355
408
  .option("--force-all", "Auto-approve all tools")
409
+ .option("--resume", "Resume from last permission-denied tool call")
356
410
  .allowUnknownOption()
357
411
  .exitOverride();
358
412
 
@@ -365,10 +419,11 @@ if (!firstArg || firstArg.startsWith("-")) {
365
419
  prompt?: string;
366
420
  dev?: boolean;
367
421
  forceAll?: boolean;
422
+ resume?: boolean;
368
423
  }>();
369
424
 
370
- if (opts.prompt && (opts.dev || opts.single)) {
371
- runHeadless({ path: opts.path ?? ".", prompt: opts.prompt, sessionId: opts.session, single: opts.single, forceAll: opts.forceAll });
425
+ if ((opts.prompt || opts.resume) && (opts.dev || opts.single)) {
426
+ runHeadless({ path: opts.path ?? ".", prompt: opts.prompt, sessionId: opts.session, single: opts.single, forceAll: opts.forceAll ?? opts.resume, resume: opts.resume });
372
427
  } else {
373
428
  render(
374
429
  <ChatCommand