opencode-readseek 0.10.7 → 0.11.0

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 (4) hide show
  1. package/README.md +45 -5
  2. package/dist/index.js +1057 -1062
  3. package/dist/v2.js +23 -0
  4. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,19 +1,11 @@
1
1
  // index.ts
2
- import path7 from "node:path";
2
+ import path8 from "node:path";
3
3
 
4
4
  // src/client.ts
5
5
  import { createRequire as createRequire2 } from "node:module";
6
6
  import { stat } from "node:fs/promises";
7
7
 
8
8
  // ../readseek-api/src/contract.ts
9
- var INSPECT_FACETS = [
10
- "metadata",
11
- "content",
12
- "map",
13
- "diagnostics",
14
- "identity"
15
- ];
16
- var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
17
9
  var VISION_LEVELS = ["low", "medium", "high"];
18
10
  var EDIT_VARIANTS = [
19
11
  "set_line",
@@ -24,15 +16,22 @@ var EDIT_VARIANTS = [
24
16
  ];
25
17
  var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
26
18
  var TOOL_NAMES = [
27
- "inspect",
28
- "edit",
29
- "write",
30
- "grep",
31
- "extract",
32
- "search",
33
- "def",
34
- "refs",
35
- "rename"
19
+ "show_content",
20
+ "show_structure",
21
+ "show_metadata",
22
+ "show_assets",
23
+ "check_syntax",
24
+ "check_rules",
25
+ "generate_caption",
26
+ "detect_objects",
27
+ "recognize_text",
28
+ "find_nodes",
29
+ "find_text",
30
+ "find_definitions",
31
+ "find_references",
32
+ "edit_text",
33
+ "rename_symbol",
34
+ "write_file"
36
35
  ];
37
36
  var PARAM_DESCRIPTIONS = {
38
37
  path: "Absolute or relative file path",
@@ -43,31 +42,30 @@ var PARAM_DESCRIPTIONS = {
43
42
  languageOverride: "Language override",
44
43
  languageSymbol: "Language override for symbol replacement",
45
44
  pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
45
+ patternContext: "Surrounding code context for sub-statement patterns",
46
+ selector: "Target AST node kind to extract from pattern context",
46
47
  grepPattern: "Regex pattern; set literal for exact text",
47
48
  name: "Qualified or unqualified symbol name",
48
49
  refsName: "Identifier to find references for",
49
- scope: "Restrict results to the binding at the cursor",
50
- line: "One-based cursor line; required with scope",
51
- renameLine: "One-based cursor line of the symbol to rename",
52
- column: "One-based cursor byte column for disambiguation",
53
50
  gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
54
- select: "Facets: metadata, content, map, diagnostics, identity (default content)",
55
- selectComma: "Comma-separated facets: metadata, content, map, diagnostics, identity",
56
- selector: "Positional selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID. Columns require the identity facet, which in turn rejects symbol selectors",
57
- assetSelector: "Document asset selector in the form asset:ID",
58
- query: "KDL selection query, @path, or '-' for standard input",
51
+ at: "Location selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
52
+ assetAt: "Document selector: page:N, node:ID, or asset:ID",
53
+ rule: "KDL node selection rule, @path, or '-' for standard input",
54
+ rules: "KDL structural checks, @path, or '-' for standard input",
59
55
  stdinName: "File name hint when the target is '-'",
60
56
  end: "Last line to include",
61
57
  limit: "Maximum number of lines to include",
62
- inspectDepth: "Maximum structural depth for map output; defaults to 1 in agent tools",
58
+ depth: "Maximum structural depth for structure output; defaults to 1 in agent tools",
63
59
  offset: "One-based starting line",
64
60
  readLimit: "Maximum lines to return",
65
61
  symbolRead: "Symbol name to read",
66
62
  bundle: "Include related symbols from the same file; requires symbol",
67
- image: "Standalone image mode: none, all, ocr, caption, objects. Required for standalone images.",
68
- visionModeInspect: "Standalone image mode: none, all, caption, objects, or ocr",
69
- visionLevel: "Vision inference level: low (default), medium, or high",
70
- output: "Destination file for extracted binary data; stdout is not supported",
63
+ level: "Explicit inference level: low (default), medium, or high",
64
+ include: "Additional caption tasks: text and/or objects",
65
+ kind: "Restrict structure to this node kind",
66
+ outline: "Return a symbol outline",
67
+ noAnchors: "Omit source line anchors",
68
+ text: "Literal native text query; adapter find_text also supports regex via pattern",
71
69
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
72
70
  editVariant: "Exactly one nested edit variant",
73
71
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -104,15 +102,22 @@ var PARAM_DESCRIPTIONS = {
104
102
  scopeContext: "Context lines within each symbol"
105
103
  };
106
104
  var TOOL_DESCRIPTIONS = {
107
- inspect: "Inspect source content, images, and documents through the native CLI envelope. Use it before edits to obtain fresh `LINE:HASH` anchors; use shallow maps for structural overviews, diagnostics for syntax checks, and identity for cursor context.",
108
- edit: 'Edit existing text files with fresh `LINE:HASH` anchors. Read or search first when no fresh anchors are available. Each `edits[]` item must use exactly one nested object: `{"set_line":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}`, `{"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_symbol":{"symbol":"name","new_body":"..."}}`, or `{"replace":{"old_text":"...","new_text":"..."}}`.',
109
- write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
110
- grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
111
- extract: "Extract a selected document asset into a destination file. Use an `asset:ID` selector discovered through `inspect`; extracted binary data is never returned on stdout.",
112
- search: "Search code by AST structure using ast-grep-style patterns. Results include line hashes usable as `LINE:HASH` anchors. Use `grep` for identifiers, strings, comments, configuration, documentation, or other plain text and regex.",
113
- def: "Go to a symbol definition or declaration by name. Accepts qualified or unqualified names; use this instead of text search for functions, methods, classes, types, and other declarations.",
114
- refs: "Find references or usages of an identifier by name or cursor binding. Results include enclosing symbols and line hashes usable as `LINE:HASH` anchors. Without cursor scope, results are identifier-name matches; with scope, they follow one binding and exclude shadows. Use text search for literals, and use this before renaming or deleting a symbol.",
115
- rename: "Rename a symbol at a source location. Resolvable same-named bindings are preserved; verified edits apply by default. Set `apply: false` to preview the plan before changing files."
105
+ show_content: "Read source, document content, or image descriptors without inference. Obtain fresh `LINE:HASH` anchors before edits; use at with a column for cursor context. Document results include typed nodes and cached asset paths.",
106
+ show_structure: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
107
+ show_metadata: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
108
+ show_assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
109
+ check_syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
110
+ check_rules: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
111
+ generate_caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
112
+ detect_objects: "Explicitly infer object labels and bounding boxes using an installed model.",
113
+ recognize_text: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
114
+ find_nodes: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `find_text` for text and regex.",
115
+ find_text: "Search text or regex and return edit-ready `LINE:HASH` anchors. Native CLI text is literal; adapters preserve regex, glob, context, summary, and symbol grouping.",
116
+ find_definitions: "Find a symbol definition or declaration by qualified or unqualified name.",
117
+ find_references: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
118
+ edit_text: "Edit existing files with fresh `LINE:HASH` anchors. Each edits item contains exactly one set_line, replace_lines, insert_after, replace_symbol, or replace object. Preview before applying a verified plan.",
119
+ rename_symbol: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
120
+ write_file: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit_text` for small changes."
116
121
  };
117
122
  function renderToolDescription(name, aliases = {}) {
118
123
  const desc = TOOL_DESCRIPTIONS[name];
@@ -188,228 +193,62 @@ var editItemSchema = {
188
193
  },
189
194
  description: PARAM_DESCRIPTIONS.editVariant
190
195
  };
196
+ var string = (description) => ({ type: "string", description });
197
+ var integer = (description, minimum = 1) => ({ type: "integer", minimum, description });
198
+ var path = { path: string(PARAM_DESCRIPTIONS.path) };
199
+ var at = { at: string(PARAM_DESCRIPTIONS.at) };
200
+ var lang = { language: string(PARAM_DESCRIPTIONS.language) };
201
+ var stdin = { stdinName: string(PARAM_DESCRIPTIONS.stdinName) };
202
+ var noAnchors = { noAnchors: { type: "boolean", description: PARAM_DESCRIPTIONS.noAnchors } };
203
+ var level = { level: { type: "string", enum: VISION_LEVELS, description: PARAM_DESCRIPTIONS.level } };
204
+ var limit = { limit: integer(PARAM_DESCRIPTIONS.matchLimit) };
205
+ var apply = { apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply }, planHash: string(PARAM_DESCRIPTIONS.planHash) };
206
+ function tool(name, title, properties, required) {
207
+ return { name, title, description: TOOL_DESCRIPTIONS[name], inputSchema: { type: "object", properties, required, additionalProperties: false } };
208
+ }
191
209
  var TOOL_CONTRACTS = [
192
- {
193
- name: "inspect",
194
- title: "Inspect input",
195
- description: TOOL_DESCRIPTIONS.inspect,
196
- inputSchema: {
197
- type: "object",
198
- properties: {
199
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
200
- selector: { type: "string", description: PARAM_DESCRIPTIONS.selector },
201
- select: { type: "string", description: PARAM_DESCRIPTIONS.select },
202
- query: { type: "string", description: PARAM_DESCRIPTIONS.query },
203
- end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
204
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
205
- depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.inspectDepth },
206
- language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
207
- visionMode: {
208
- type: "string",
209
- enum: [...VISION_MODES],
210
- description: PARAM_DESCRIPTIONS.visionModeInspect
211
- },
212
- visionLevel: {
213
- type: "string",
214
- enum: [...VISION_LEVELS],
215
- description: PARAM_DESCRIPTIONS.visionLevel
216
- },
217
- stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
218
- },
219
- required: ["path"],
220
- additionalProperties: false
221
- }
222
- },
223
- {
224
- name: "edit",
225
- title: "Hash-verified edit",
226
- description: TOOL_DESCRIPTIONS.edit,
227
- inputSchema: {
228
- type: "object",
229
- properties: {
230
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
231
- edits: {
232
- type: "array",
233
- minItems: 1,
234
- description: PARAM_DESCRIPTIONS.edits,
235
- items: editItemSchema
236
- },
237
- language: { type: "string", description: PARAM_DESCRIPTIONS.languageSymbol },
238
- apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
239
- planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash },
240
- postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify }
241
- },
242
- required: ["path", "edits"],
243
- additionalProperties: false
244
- }
245
- },
246
- {
247
- name: "write",
248
- title: "Write file",
249
- description: TOOL_DESCRIPTIONS.write,
250
- inputSchema: {
251
- type: "object",
252
- properties: {
253
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
254
- content: { type: "string", description: PARAM_DESCRIPTIONS.content }
255
- },
256
- required: ["path", "content"],
257
- additionalProperties: false
258
- }
259
- },
260
- {
261
- name: "grep",
262
- title: "Text search",
263
- description: TOOL_DESCRIPTIONS.grep,
264
- inputSchema: {
265
- type: "object",
266
- properties: {
267
- pattern: { type: "string", description: PARAM_DESCRIPTIONS.grepPattern },
268
- path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
269
- glob: { type: "string", description: PARAM_DESCRIPTIONS.glob },
270
- ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase },
271
- literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal },
272
- context: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.context },
273
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
274
- summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary },
275
- scope: {
276
- type: "string",
277
- enum: ["symbol"],
278
- description: PARAM_DESCRIPTIONS.grepScope
279
- },
280
- scopeContext: {
281
- type: "integer",
282
- minimum: 0,
283
- description: PARAM_DESCRIPTIONS.scopeContext
284
- }
285
- },
286
- required: ["pattern"],
287
- additionalProperties: false
288
- }
289
- },
290
- {
291
- name: "extract",
292
- title: "Extract asset",
293
- description: TOOL_DESCRIPTIONS.extract,
294
- inputSchema: {
295
- type: "object",
296
- properties: {
297
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
298
- selector: {
299
- type: "string",
300
- pattern: "^asset:.+",
301
- description: PARAM_DESCRIPTIONS.assetSelector
302
- },
303
- output: {
304
- type: "string",
305
- minLength: 1,
306
- pattern: "^(?!-$).+",
307
- description: PARAM_DESCRIPTIONS.output
308
- },
309
- stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
310
- },
311
- required: ["path", "selector", "output"],
312
- additionalProperties: false
313
- }
314
- },
315
- {
316
- name: "search",
317
- title: "AST search",
318
- description: TOOL_DESCRIPTIONS.search,
319
- inputSchema: {
320
- type: "object",
321
- properties: {
322
- pattern: { type: "string", description: PARAM_DESCRIPTIONS.pattern },
323
- path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
324
- language: { type: "string", description: PARAM_DESCRIPTIONS.language },
325
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
326
- ...gitScopeSchema
327
- },
328
- required: ["pattern"],
329
- additionalProperties: false
330
- }
331
- },
332
- {
333
- name: "def",
334
- title: "Find definition",
335
- description: TOOL_DESCRIPTIONS.def,
336
- inputSchema: {
337
- type: "object",
338
- properties: {
339
- name: { type: "string", description: PARAM_DESCRIPTIONS.name },
340
- path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
341
- language: { type: "string", description: PARAM_DESCRIPTIONS.language },
342
- ...gitScopeSchema
343
- },
344
- required: ["name"],
345
- additionalProperties: false
346
- }
347
- },
348
- {
349
- name: "refs",
350
- title: "Find references",
351
- description: TOOL_DESCRIPTIONS.refs,
352
- inputSchema: {
353
- type: "object",
354
- properties: {
355
- name: { type: "string", description: PARAM_DESCRIPTIONS.refsName },
356
- path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
357
- language: { type: "string", description: PARAM_DESCRIPTIONS.language },
358
- scope: { type: "boolean", description: PARAM_DESCRIPTIONS.scope },
359
- line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.line },
360
- column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
361
- ...gitScopeSchema
362
- },
363
- additionalProperties: false
364
- }
365
- },
366
- {
367
- name: "rename",
368
- title: "Rename symbol",
369
- description: TOOL_DESCRIPTIONS.rename,
370
- inputSchema: {
371
- type: "object",
372
- properties: {
373
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
374
- line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.renameLine },
375
- column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
376
- to: { type: "string", description: PARAM_DESCRIPTIONS.to },
377
- workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace },
378
- apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
379
- planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash },
380
- language: { type: "string", description: PARAM_DESCRIPTIONS.language },
381
- ...gitScopeSchema
382
- },
383
- required: ["path", "line", "to"],
384
- additionalProperties: false
385
- }
386
- }
210
+ tool("show_content", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
211
+ tool("show_structure", "Show structure", { ...path, ...at, ...lang, ...stdin, depth: integer(PARAM_DESCRIPTIONS.depth, 0), kind: string(PARAM_DESCRIPTIONS.kind), outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline } }, ["path"]),
212
+ tool("show_metadata", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
213
+ tool("show_assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
214
+ tool("check_syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
215
+ tool("check_rules", "Check rules", { ...path, ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
216
+ tool("generate_caption", "Generate caption", { ...path, ...at, ...stdin, ...level, include: { type: "array", items: { type: "string", enum: ["text", "objects"] }, minItems: 1, uniqueItems: true, description: PARAM_DESCRIPTIONS.include } }, ["path"]),
217
+ tool("detect_objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
218
+ tool("recognize_text", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
219
+ tool("find_nodes", "Find nodes", { ...path, ...lang, ...stdin, ...limit, ...gitScopeSchema, ...noAnchors, pattern: string(PARAM_DESCRIPTIONS.pattern), context: string(PARAM_DESCRIPTIONS.patternContext), selector: string(PARAM_DESCRIPTIONS.selector), rule: string(PARAM_DESCRIPTIONS.rule) }, []),
220
+ tool("find_text", "Find text", { ...path, ...limit, ...gitScopeSchema, text: string(PARAM_DESCRIPTIONS.text), pattern: string(PARAM_DESCRIPTIONS.grepPattern), glob: string(PARAM_DESCRIPTIONS.glob), ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase }, literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal }, context: integer(PARAM_DESCRIPTIONS.context, 0), summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary }, scope: { type: "string", enum: ["symbol"], description: PARAM_DESCRIPTIONS.grepScope }, scopeContext: integer(PARAM_DESCRIPTIONS.scopeContext, 0) }, []),
221
+ tool("find_definitions", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
222
+ tool("find_references", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
223
+ tool("edit_text", "Hash-verified edit", { ...path, ...lang, ...gitScopeSchema, ...apply, edits: { type: "array", minItems: 1, items: editItemSchema, description: PARAM_DESCRIPTIONS.edits }, postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify } }, ["path", "edits"]),
224
+ tool("rename_symbol", "Rename symbol", { ...path, ...at, ...lang, ...gitScopeSchema, ...apply, to: string(PARAM_DESCRIPTIONS.to), workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace } }, ["path", "at", "to"]),
225
+ tool("write_file", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
387
226
  ];
388
227
  // ../readseek-api/src/policy.ts
389
228
  var BUILTIN_FALLBACKS = {
390
- read: "inspect",
391
- edit: "edit",
392
- write: "write",
393
- grep: "grep",
394
- apply_patch: "edit"
229
+ read: "show_content",
230
+ edit: "edit_text",
231
+ write: "write_file",
232
+ grep: "find_text",
233
+ apply_patch: "edit_text"
395
234
  };
396
235
  var PREFERENCE_LEADS = {
397
- inspect: "Preferred for reading source with anchors and facets.",
398
- edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
399
- write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
400
- grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
401
- search: "Preferred for AST or structural code search with ast-grep-style patterns.",
402
- def: "Preferred for go-to-definition or declaration lookup by name.",
403
- refs: "Preferred for finding identifier-name matches or binding-specific references before rename or delete.",
404
- rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
405
- extract: "Preferred for saving a selected document asset to a file."
236
+ show_content: "Preferred for reading source with anchors.",
237
+ edit_text: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
238
+ write_file: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
239
+ find_text: "Preferred text/regex find_nodes; returns edit_text-ready LINE:HASH anchors.",
240
+ find_nodes: "Preferred for AST or structural code find_nodes with ast-find_text-style patterns.",
241
+ find_definitions: "Preferred for go-to-definition or declaration lookup by name.",
242
+ find_references: "Preferred for finding identifier-name matches or binding-specific references before rename_symbol or delete.",
243
+ rename_symbol: "Preferred for renaming a symbol with binding-aware, verified edits.",
244
+ show_assets: "Preferred for saving a selected document asset to a file."
406
245
  };
407
246
  var FALLBACK_TEMPLATES = {
408
- read: "Prefer `inspect` with fresh LINE:HASH anchors.",
409
- edit: "Prefer `edit` with fresh LINE:HASH anchors.",
410
- write: "Prefer `write` so the result returns LINE:HASH anchors.",
411
- grep: "Prefer `grep` for text/regex search with edit-ready anchors.",
412
- apply_patch: "Prefer `edit` for existing text files."
247
+ read: "Prefer `show_content` with fresh LINE:HASH anchors.",
248
+ edit: "Prefer `edit_text` with fresh LINE:HASH anchors.",
249
+ write: "Prefer `write_file` so the result returns LINE:HASH anchors.",
250
+ grep: "Prefer `find_text` for text/regex search with edit-ready anchors.",
251
+ apply_patch: "Prefer `edit_text` for existing text files."
413
252
  };
414
253
  function resolveToolName(name, aliases = {}) {
415
254
  return aliases[name] ?? name;
@@ -418,7 +257,7 @@ function rewriteBacktickedToolNames(text, aliases = {}) {
418
257
  return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${resolveToolName(toolName, aliases)}\``), text);
419
258
  }
420
259
  function renderPreferredToolDescription(name, aliases = {}) {
421
- return rewriteBacktickedToolNames(PREFERENCE_LEADS[name], aliases);
260
+ return rewriteBacktickedToolNames(PREFERENCE_LEADS[name] ?? `Preferred for ${name.replaceAll("_", " ")} with ReadSeek.`, aliases);
422
261
  }
423
262
  function renderFallbackToolDescription(builtIn, aliases = {}) {
424
263
  const template = FALLBACK_TEMPLATES[builtIn];
@@ -428,20 +267,20 @@ function renderFallbackToolDescription(builtIn, aliases = {}) {
428
267
  }
429
268
  function renderToolRoutingPolicy(options = {}) {
430
269
  const aliases = options.aliases ?? {};
431
- const inspect = resolveToolName("inspect", aliases);
432
- const edit = resolveToolName("edit", aliases);
433
- const write = resolveToolName("write", aliases);
434
- const grep = resolveToolName("grep", aliases);
435
- const search = resolveToolName("search", aliases);
436
- const def = resolveToolName("def", aliases);
437
- const refs = resolveToolName("refs", aliases);
438
- const rename = resolveToolName("rename", aliases);
439
- const extract = resolveToolName("extract", aliases);
270
+ const show_content = resolveToolName("show_content", aliases);
271
+ const edit_text = resolveToolName("edit_text", aliases);
272
+ const write_file = resolveToolName("write_file", aliases);
273
+ const find_text = resolveToolName("find_text", aliases);
274
+ const find_nodes = resolveToolName("find_nodes", aliases);
275
+ const find_definitions = resolveToolName("find_definitions", aliases);
276
+ const find_references = resolveToolName("find_references", aliases);
277
+ const rename_symbol = resolveToolName("rename_symbol", aliases);
278
+ const show_assets = resolveToolName("show_assets", aliases);
440
279
  if (options.style === "server") {
441
280
  return [
442
281
  "Structural code navigation, document inspection, and hash-verified edits with readseek.",
443
- "For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST search; shallow inspect maps; diagnostics and identity; and document inspection.",
444
- `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
282
+ "For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST patterns; shallow show_structure views; check_syntax diagnostics and show_content cursor context; and document inspection.",
283
+ `Use ${edit_text} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
445
284
  "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
446
285
  "call MCP tools for active navigation, document questions, or anchored edits."
447
286
  ].join(" ");
@@ -451,7 +290,7 @@ function renderToolRoutingPolicy(options = {}) {
451
290
  if (options.preferOverBuiltIns) {
452
291
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
453
292
  }
454
- bullets.push(`- Read with ${inspect} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${inspect} maps only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${inspect} for document structure; run ${inspect} with select diagnostics after source edits for a quick syntax check.`, `- Use ${extract} to save a selected document asset.`);
293
+ bullets.push(`- Read with ${show_content} first; ${edit_text} needs fresh LINE:HASH anchors.`, `- Use ${find_text} for text/regex, ${find_nodes} for AST patterns, ${find_definitions}/${find_references} for targeted symbols, and shallow ${resolveToolName("show_structure", aliases)} structures only for file overviews.`, `- Use ${edit_text} for existing files, ${write_file} for whole-file creation or replacement, and ${rename_symbol} for symbol renames.`, `- Use ${resolveToolName("show_structure", aliases)} for document structure; run ${resolveToolName("check_syntax", aliases)} after source edits for a quick syntax check.`, `- Use ${show_assets} to materialize selected document assets and read their returned paths.`);
455
294
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
456
295
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
457
296
  }
@@ -486,25 +325,9 @@ function tryParseLineAnchor(anchor) {
486
325
  }
487
326
 
488
327
  // ../readseek-api/src/validation.ts
489
- function validateRefsNameVsScope(input) {
490
- if (input.scope) {
491
- if (input.line === undefined) {
492
- return { ok: false, error: "scope requires line" };
493
- }
494
- return { ok: true };
495
- }
496
- if (input.line !== undefined || input.column !== undefined) {
497
- return { ok: false, error: "line/column require scope=true" };
498
- }
499
- if (typeof input.name !== "string" || input.name.length === 0) {
500
- return { ok: false, error: "name is required when scope is not set" };
501
- }
502
- return { ok: true };
503
- }
504
- function validateVisionLevelVsMode(input) {
505
- if (input.visionLevel !== undefined && !["all", "ocr", "caption", "objects"].includes(input.visionMode ?? "")) {
506
- return { ok: false, error: "visionLevel requires an analysis visionMode" };
507
- }
328
+ function validateReferencesSelection(input) {
329
+ if (Boolean(input.name) === Boolean(input.at))
330
+ return { ok: false, error: "exactly one of name or at is required" };
508
331
  return { ok: true };
509
332
  }
510
333
  var VARIANT_FIELDS = {
@@ -574,6 +397,597 @@ function validateExactlyOneEditVariant(edits) {
574
397
  }
575
398
  var MIN_SAFE_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
576
399
  var MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
400
+ // ../readseek-api/src/argv.ts
401
+ var DEFAULT_STRUCTURE_DEPTH = 1;
402
+ function appendGitScopeArgs(args, input) {
403
+ if (input.gitScope !== undefined)
404
+ args.push("--git-scope", input.gitScope);
405
+ return args;
406
+ }
407
+ function pushOptionalString(args, flag, value) {
408
+ if (value !== undefined)
409
+ args.push(flag, value);
410
+ }
411
+ function pushOptionalNumber(args, flag, value) {
412
+ if (value !== undefined)
413
+ args.push(flag, String(value));
414
+ }
415
+ function appendApplyAndPlanHash(args, input) {
416
+ const order = input.flagOrder ?? "apply-first";
417
+ if (order === "plan-hash-first") {
418
+ pushOptionalString(args, "--plan-hash", input.planHash);
419
+ if (input.apply)
420
+ args.push("--apply");
421
+ return args;
422
+ }
423
+ if (input.apply)
424
+ args.push("--apply");
425
+ pushOptionalString(args, "--plan-hash", input.planHash);
426
+ return args;
427
+ }
428
+ function validatePositiveInteger(value, name) {
429
+ if (!Number.isSafeInteger(value) || value < 1) {
430
+ throw new Error(`${name} must be a positive integer`);
431
+ }
432
+ }
433
+ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
434
+ function isSelectorKind(value) {
435
+ return SELECTOR_KINDS.includes(value);
436
+ }
437
+ function parseSelector(text) {
438
+ const separator = text.indexOf(":");
439
+ if (separator < 0) {
440
+ throw new Error("selector must start with line:, hash:, symbol:, page:, node:, or asset:");
441
+ }
442
+ const kind = text.slice(0, separator);
443
+ const value = text.slice(separator + 1);
444
+ if (value.length === 0)
445
+ throw new Error(`${kind} selector must not be empty`);
446
+ if (!isSelectorKind(kind)) {
447
+ throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
448
+ }
449
+ if (kind === "line" || kind === "hash") {
450
+ const parts = value.split(":");
451
+ if (parts.length > 2)
452
+ throw new Error("selector has too many components");
453
+ if (parts.length === 2) {
454
+ const column = Number(parts[1]);
455
+ if (!Number.isSafeInteger(column))
456
+ throw new Error(`invalid column \`${parts[1]}\``);
457
+ if (column < 1)
458
+ throw new Error("column must be greater than zero");
459
+ return { kind, value: parts[0], column };
460
+ }
461
+ }
462
+ if (kind === "page") {
463
+ if (value.includes(":"))
464
+ throw new Error("page selector has too many components");
465
+ const page = Number(value);
466
+ if (!Number.isSafeInteger(page))
467
+ throw new Error(`invalid page \`${value}\``);
468
+ if (page < 1)
469
+ throw new Error("page must be greater than zero");
470
+ }
471
+ return { kind, value };
472
+ }
473
+ function start(verb, object, input) {
474
+ if (!input.path)
475
+ throw new Error(`${verb} ${object} requires a path`);
476
+ if (input.stdinName !== undefined && input.path !== "-")
477
+ throw new Error("stdinName requires '-' as the target");
478
+ const args = [];
479
+ pushOptionalString(args, "--readseek-dir", input.readseekDir);
480
+ args.push(verb, object, input.path);
481
+ pushOptionalString(args, "--stdin-name", input.stdinName);
482
+ return args;
483
+ }
484
+ function at2(args, value) {
485
+ if (value !== undefined) {
486
+ parseSelector(value);
487
+ args.push("--at", value);
488
+ }
489
+ }
490
+ function finish(args) {
491
+ args.push("--kdl");
492
+ return args;
493
+ }
494
+ function language(args, input) {
495
+ pushOptionalString(args, "--language", input.language);
496
+ }
497
+ function limit2(args, value) {
498
+ if (value !== undefined)
499
+ validatePositiveInteger(value, "limit");
500
+ pushOptionalNumber(args, "--limit", value);
501
+ }
502
+ function anchors(args, input) {
503
+ if (input.noAnchors)
504
+ args.push("--no-anchors");
505
+ }
506
+ function buildShowContentArgs(input) {
507
+ if (input.end !== undefined)
508
+ validatePositiveInteger(input.end, "end");
509
+ if (input.end !== undefined && input.limit !== undefined)
510
+ throw new Error("cannot combine end with limit");
511
+ const args = start("show", "content", input);
512
+ at2(args, input.at);
513
+ pushOptionalNumber(args, "--end", input.end);
514
+ limit2(args, input.limit);
515
+ language(args, input);
516
+ anchors(args, input);
517
+ return finish(args);
518
+ }
519
+ function buildShowStructureArgs(input) {
520
+ if (input.depth !== undefined && (!Number.isSafeInteger(input.depth) || input.depth < 0))
521
+ throw new Error("depth must be a non-negative integer");
522
+ const args = start("show", "structure", input);
523
+ at2(args, input.at);
524
+ pushOptionalNumber(args, "--depth", input.depth);
525
+ pushOptionalString(args, "--kind", input.kind);
526
+ language(args, input);
527
+ if (input.outline)
528
+ args.push("--outline");
529
+ return finish(args);
530
+ }
531
+ function buildShowMetadataArgs(input) {
532
+ const args = start("show", "metadata", input);
533
+ language(args, input);
534
+ return finish(args);
535
+ }
536
+ function buildShowAssetsArgs(input) {
537
+ if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
538
+ throw new Error("assets requires a page, node, or asset selector");
539
+ const args = start("show", "assets", input);
540
+ at2(args, input.at);
541
+ return finish(args);
542
+ }
543
+ function buildCheckSyntaxArgs(input) {
544
+ const args = start("check", "syntax", input);
545
+ language(args, input);
546
+ return finish(args);
547
+ }
548
+ function visionArgs(verb, object, input) {
549
+ if (input.level !== undefined && !VISION_LEVELS.includes(input.level))
550
+ throw new Error("invalid vision level");
551
+ if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
552
+ throw new Error("vision requires a document selector");
553
+ const args = start(verb, object, input);
554
+ at2(args, input.at);
555
+ pushOptionalString(args, "--level", input.level);
556
+ return args;
557
+ }
558
+ function buildGenerateCaptionArgs(input) {
559
+ const args = visionArgs("generate", "caption", input);
560
+ if (input.include !== undefined) {
561
+ if (input.include.length === 0 || input.include.some((task) => task !== "text" && task !== "objects"))
562
+ throw new Error("include requires text or objects");
563
+ args.push("--include", input.include.join(","));
564
+ }
565
+ return finish(args);
566
+ }
567
+ function buildDetectObjectsArgs(input) {
568
+ return finish(visionArgs("detect", "objects", input));
569
+ }
570
+ function buildRecognizeTextArgs(input) {
571
+ return finish(visionArgs("recognize", "text", input));
572
+ }
573
+ function buildFindNodesArgs(input) {
574
+ const hasPattern = Boolean(input.pattern);
575
+ const hasRule = Boolean(input.rule);
576
+ const hasContext = Boolean(input.context);
577
+ if (hasRule) {
578
+ if (hasPattern || hasContext || input.selector)
579
+ throw new Error("rule cannot be combined with pattern, context, or selector");
580
+ } else if (!hasPattern && !hasContext) {
581
+ throw new Error("find nodes requires pattern, context, or rule");
582
+ }
583
+ if (hasContext && !input.selector) {
584
+ throw new Error("context requires selector");
585
+ }
586
+ if (hasPattern && hasContext) {
587
+ throw new Error("pattern and context cannot be combined");
588
+ }
589
+ const args = start("find", "nodes", input);
590
+ pushOptionalString(args, "--pattern", input.pattern);
591
+ pushOptionalString(args, "--context", input.context);
592
+ pushOptionalString(args, "--selector", input.selector);
593
+ pushOptionalString(args, "--rule", input.rule);
594
+ language(args, input);
595
+ limit2(args, input.limit);
596
+ appendGitScopeArgs(args, input);
597
+ anchors(args, input);
598
+ return finish(args);
599
+ }
600
+ function buildFindTextArgs(input) {
601
+ const args = start("find", "text", input);
602
+ args.push("--text", input.text);
603
+ limit2(args, input.limit);
604
+ appendGitScopeArgs(args, input);
605
+ return finish(args);
606
+ }
607
+ function buildFindDefinitionsArgs(input) {
608
+ const args = start("find", "definitions", input);
609
+ args.push("--name", input.name);
610
+ language(args, input);
611
+ appendGitScopeArgs(args, input);
612
+ anchors(args, input);
613
+ return finish(args);
614
+ }
615
+ function buildFindReferencesArgs(input) {
616
+ const validated = validateReferencesSelection(input);
617
+ if (!validated.ok)
618
+ throw new Error(validated.error);
619
+ if (input.at !== undefined && !["line", "hash"].includes(parseSelector(input.at).kind))
620
+ throw new Error("references requires a line or hash selector");
621
+ const args = start("find", "references", input);
622
+ pushOptionalString(args, "--name", input.name);
623
+ at2(args, input.at);
624
+ language(args, input);
625
+ appendGitScopeArgs(args, input);
626
+ anchors(args, input);
627
+ return finish(args);
628
+ }
629
+ function buildCheckRulesArgs(input) {
630
+ const args = start("check", "rules", input);
631
+ args.push("--rules", input.rules);
632
+ appendGitScopeArgs(args, input);
633
+ return finish(args);
634
+ }
635
+ function buildEditTextArgs(input) {
636
+ const args = start("edit", "text", input);
637
+ appendApplyAndPlanHash(args, input);
638
+ language(args, input);
639
+ appendGitScopeArgs(args, input);
640
+ if (input.requestFromStdin)
641
+ args.push("--edits", "-");
642
+ return finish(args);
643
+ }
644
+ function buildRenameSymbolArgs(input) {
645
+ if (!["line", "hash"].includes(parseSelector(input.at).kind))
646
+ throw new Error("rename requires a line or hash selector");
647
+ const args = start("rename", "symbol", input);
648
+ at2(args, input.at);
649
+ args.push("--to", input.to);
650
+ if (input.workspace !== undefined)
651
+ args.push(`--workspace=${input.workspace}`);
652
+ appendApplyAndPlanHash(args, { ...input, flagOrder: input.flagOrder ?? "plan-hash-first" });
653
+ language(args, input);
654
+ appendGitScopeArgs(args, input);
655
+ return finish(args);
656
+ }
657
+ // ../readseek-api/src/client-output.ts
658
+ import { Type } from "@sinclair/typebox";
659
+ import { AssertError, Value } from "@sinclair/typebox/value";
660
+ var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
661
+ var Str = Type.String();
662
+ var Bool = Type.Boolean();
663
+ var OptStr = Type.Optional(Str);
664
+ var OptInt = Type.Optional(Int);
665
+ var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
666
+ var Hashlines = Type.Array(Hashline);
667
+ var ReadOutput = Type.Object({
668
+ file: Str,
669
+ language: Str,
670
+ line_count: Int,
671
+ file_hash: Str,
672
+ start_line: Int,
673
+ end_line: Int,
674
+ hashlines: Hashlines,
675
+ truncated: Type.Optional(Bool)
676
+ });
677
+ var SymbolOutput = Type.Object({
678
+ kind: Str,
679
+ name: Str,
680
+ qualified_name: OptStr,
681
+ start_line: Int,
682
+ end_line: Int,
683
+ start_hash: OptStr,
684
+ end_hash: OptStr,
685
+ start_anchor: OptStr,
686
+ end_anchor: OptStr
687
+ });
688
+ function mapNodeShape(child) {
689
+ return Type.Object({
690
+ kind: Str,
691
+ name: Str,
692
+ start_line: Int,
693
+ end_line: Int,
694
+ start_anchor: Str,
695
+ end_anchor: Str,
696
+ children: Type.Array(child)
697
+ });
698
+ }
699
+ function recursiveStructureNode() {
700
+ return Type.Recursive((self) => mapNodeShape(self));
701
+ }
702
+ var StructureNode = typeof Type.Recursive === "function" ? recursiveStructureNode() : Type.Cyclic({ StructureNode: mapNodeShape(Type.Ref("StructureNode")) }, "StructureNode");
703
+ var StructureOutput = Type.Object({
704
+ file: Str,
705
+ language: Str,
706
+ line_count: Int,
707
+ file_hash: Str,
708
+ root: Type.Optional(StructureNode),
709
+ symbols: Type.Array(SymbolOutput)
710
+ });
711
+ var SearchSpan = {
712
+ start_byte: OptInt,
713
+ end_byte: OptInt,
714
+ text: OptStr,
715
+ start_line: Int,
716
+ end_line: Int,
717
+ start_hash: Str,
718
+ end_hash: Str,
719
+ hashlines: Hashlines
720
+ };
721
+ var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
722
+ var SearchMatch = Type.Object({
723
+ pattern_index: Type.Integer({ default: 0 }),
724
+ ...SearchSpan,
725
+ captures: Type.Array(SearchCapture)
726
+ });
727
+ var SearchFileOutput = Type.Object({
728
+ file: Str,
729
+ language: Str,
730
+ file_hash: Str,
731
+ matches: Type.Array(SearchMatch)
732
+ });
733
+ var SearchEnvelope = Type.Object({
734
+ results: Type.Array(SearchFileOutput),
735
+ returned_matches: OptInt,
736
+ truncated: Type.Optional(Bool),
737
+ byte_basis: OptStr
738
+ });
739
+ var RefsEnvelope = Type.Object({
740
+ references: Type.Array(Type.Object({
741
+ file: Str,
742
+ line: Int,
743
+ column: Int,
744
+ line_hash: Str,
745
+ text: Str,
746
+ symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
747
+ }))
748
+ });
749
+ var Diagnostic = Type.Object({
750
+ kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
751
+ start_line: Int,
752
+ end_line: Int
753
+ });
754
+ var CheckEnvelope = Type.Object({
755
+ file_hash: OptStr,
756
+ error_count: Int,
757
+ missing_count: Int,
758
+ diagnostics: Type.Array(Diagnostic)
759
+ });
760
+ var DetectedObject = Type.Object({
761
+ label: Str,
762
+ bbox: Type.Tuple([Int, Int, Int, Int])
763
+ });
764
+ var SourceDetection = Type.Object({
765
+ kind: Type.Literal("source"),
766
+ type: Str,
767
+ file: Str,
768
+ language: Str,
769
+ engine: OptStr,
770
+ supported: Bool,
771
+ mime: OptStr,
772
+ syntax: OptStr
773
+ });
774
+ var ImageDetection = Type.Object({
775
+ kind: Type.Literal("image"),
776
+ type: Str,
777
+ file: Str,
778
+ mime: OptStr,
779
+ format: Str,
780
+ width: Int,
781
+ height: Int,
782
+ animated: Bool,
783
+ ocr: OptStr,
784
+ caption: OptStr,
785
+ objects: Type.Optional(Type.Array(DetectedObject))
786
+ });
787
+ var TextDetection = Type.Object({
788
+ kind: Type.Literal("text"),
789
+ type: Str,
790
+ file: Str,
791
+ mime: OptStr
792
+ });
793
+ var PdfDetection = Type.Object({
794
+ kind: Type.Literal("pdf"),
795
+ type: Type.Literal("application/pdf"),
796
+ file: Str,
797
+ mime: OptStr,
798
+ format: Type.Literal("pdf"),
799
+ pages: Int
800
+ });
801
+ var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
802
+ var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
803
+ var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
804
+ var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
805
+ var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
806
+ var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
807
+ var PdfMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
808
+ var DocumentCache = Type.Object({
809
+ content_hash: Str,
810
+ state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
811
+ });
812
+ var Identifier = Type.Object({
813
+ text: Str,
814
+ start_column: Int,
815
+ end_column: Int,
816
+ start_byte: Int,
817
+ end_byte: Int
818
+ });
819
+ var CursorEnvelope = Type.Object({
820
+ file: Str,
821
+ language: Str,
822
+ engine: OptStr,
823
+ line_count: Int,
824
+ file_hash: Str,
825
+ line: Int,
826
+ column: Int,
827
+ line_hash: Str,
828
+ identifier: Type.Optional(Identifier),
829
+ symbol: Type.Optional(Type.Object({
830
+ name: Str,
831
+ kind: Str,
832
+ qualified_name: Str,
833
+ start_line: Int,
834
+ end_line: Int
835
+ }))
836
+ });
837
+ var EditOutputSchema = Type.Object({
838
+ file: Str,
839
+ before_hash: Str,
840
+ file_hash: Str,
841
+ plan_hash: Str,
842
+ applied: Bool,
843
+ changed: Bool,
844
+ edit_count: Int,
845
+ before_content: OptStr,
846
+ content: OptStr
847
+ });
848
+ var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
849
+ var RenameEditSchema = Type.Object({
850
+ line: Int,
851
+ start_column: Int,
852
+ end_column: Int,
853
+ start_byte: Int,
854
+ end_byte: Int,
855
+ occurrence: Str,
856
+ line_hash: Str,
857
+ text: Str
858
+ });
859
+ var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
860
+ var Edits = Type.Array(RenameEditSchema, { default: [] });
861
+ var RenameFileOutputSchema = Type.Object({
862
+ file: Str,
863
+ language: Str,
864
+ engine: OptStr,
865
+ file_hash: Str,
866
+ conflicts: Conflicts,
867
+ edits: Edits
868
+ });
869
+ var RenameOutputSchema = Type.Object({
870
+ file: Str,
871
+ language: Str,
872
+ engine: OptStr,
873
+ file_hash: Str,
874
+ old_name: Str,
875
+ new_name: Str,
876
+ plan_hash: Str,
877
+ applied: Bool,
878
+ conflicts: Conflicts,
879
+ edits: Edits,
880
+ others: Type.Array(RenameFileOutputSchema, { default: [] })
881
+ });
882
+ var DefEnvelope = Type.Object({
883
+ locations: Type.Array(Type.Object({
884
+ file: Str,
885
+ line: Int,
886
+ column: Int,
887
+ line_hash: Str,
888
+ text: Str,
889
+ kind: OptStr,
890
+ name: OptStr,
891
+ qualified_name: OptStr
892
+ }))
893
+ });
894
+ var Bbox = Type.Object({ x: Type.Number(), y: Type.Number(), width: Type.Number(), height: Type.Number() });
895
+ var Position = { page: OptInt, destination: OptStr, bbox: Type.Optional(Bbox) };
896
+ var Provenance = { inferred: Type.Optional(Bool), model: OptStr, level: OptStr, task: OptStr };
897
+ var ImageDescriptor = Type.Object({
898
+ id: OptStr,
899
+ kind: Type.Optional(Type.Literal("image")),
900
+ content_identity: OptStr,
901
+ path: Str,
902
+ mime: Str,
903
+ width: Int,
904
+ height: Int,
905
+ caption: OptStr,
906
+ ocr: OptStr,
907
+ objects: Type.Optional(Type.Array(DetectedObject)),
908
+ ...Provenance
909
+ });
910
+ var DocumentNode = Type.Object({
911
+ id: Str,
912
+ kind: Str,
913
+ parent_id: OptStr,
914
+ title: OptStr,
915
+ level: OptInt,
916
+ column: OptInt,
917
+ text: OptStr,
918
+ ...Position
919
+ });
920
+ var DocumentAsset = Type.Object({
921
+ id: Str,
922
+ kind: Str,
923
+ mime: Str,
924
+ path: OptStr,
925
+ content_identity: OptStr,
926
+ bytes: OptInt,
927
+ ...Position
928
+ });
929
+ var DocumentVision = Type.Object({
930
+ asset_id: Str,
931
+ mode: OptStr,
932
+ caption: OptStr,
933
+ ocr: OptStr,
934
+ objects: Type.Optional(Type.Array(DetectedObject)),
935
+ caption_requested: Type.Optional(Bool),
936
+ objects_requested: Type.Optional(Bool),
937
+ ocr_requested: Type.Optional(Bool),
938
+ ...Provenance
939
+ });
940
+ var DocumentOutput = Type.Object({
941
+ id: OptStr,
942
+ format: Str,
943
+ source: Str,
944
+ title: OptStr,
945
+ pages: Int,
946
+ nodes: Type.Array(DocumentNode, { default: [] }),
947
+ assets: Type.Array(DocumentAsset, { default: [] }),
948
+ vision: Type.Array(DocumentVision, { default: [] })
949
+ });
950
+ var TextHit = Type.Object({
951
+ line: Int,
952
+ column: Int,
953
+ text: Str,
954
+ start_line: Int,
955
+ end_line: Int,
956
+ start_byte: Int,
957
+ end_byte: Int,
958
+ hashlines: Hashlines
959
+ });
960
+ var DocumentHit = Type.Object({ node_id: Str, kind: Str, snippet: Str, page: OptInt });
961
+ var TextSearchOutput = Type.Object({
962
+ text: Str,
963
+ mode: Type.Literal("literal"),
964
+ total_matches: Int,
965
+ returned_matches: Int,
966
+ truncated: Bool,
967
+ results: Type.Array(Type.Object({ file: Str, file_hash: Str, matches: Type.Array(TextHit) })),
968
+ documents: Type.Array(Type.Object({ file: Str, content_hash: Str, hits: Type.Array(DocumentHit) }))
969
+ });
970
+ var RuleFinding = Type.Object({
971
+ name: Str,
972
+ message: Str,
973
+ path: Str,
974
+ language: Str,
975
+ start_byte: Int,
976
+ end_byte: Int,
977
+ line: Int,
978
+ column: Int,
979
+ end_line: Int,
980
+ end_column: Int
981
+ });
982
+ var RulesOutput = Type.Object({
983
+ complete: Bool,
984
+ check_count: Int,
985
+ file_count: Int,
986
+ violation_count: Int,
987
+ diagnostics: Type.Array(RuleFinding)
988
+ });
989
+ var SourceAssetsOutput = Type.Object({ file: OptStr, metadata: Type.Optional(Detection), assets: Type.Array(DocumentAsset, { default: [] }) });
990
+
577
991
  // ../readseek-api/src/edit-plan.ts
578
992
  import { Document, Node, format } from "@bgotink/kdl";
579
993
  function node(name, properties, children = []) {
@@ -614,268 +1028,11 @@ function serializeEditPlan(edits) {
614
1028
  throw new Error(validation.error);
615
1029
  return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
616
1030
  }
617
- // ../readseek-api/src/argv.ts
618
- var DEFAULT_INSPECT_MAP_DEPTH = 1;
619
- function buildLineLocation(line, column) {
620
- return column === undefined ? `line:${line}` : `line:${line}:${column}`;
621
- }
622
- function appendGitScopeArgs(args, input) {
623
- if (input.gitScope !== undefined)
624
- args.push("--git-scope", input.gitScope);
625
- return args;
626
- }
627
- function pushOptionalString(args, flag, value) {
628
- if (value !== undefined)
629
- args.push(flag, value);
630
- }
631
- function pushOptionalNumber(args, flag, value) {
632
- if (value !== undefined)
633
- args.push(flag, String(value));
634
- }
635
- function appendApplyAndPlanHash(args, input) {
636
- const order = input.flagOrder ?? "apply-first";
637
- if (order === "plan-hash-first") {
638
- pushOptionalString(args, "--plan-hash", input.planHash);
639
- if (input.apply)
640
- args.push("--apply");
641
- return args;
642
- }
643
- if (input.apply)
644
- args.push("--apply");
645
- pushOptionalString(args, "--plan-hash", input.planHash);
646
- return args;
647
- }
648
- function selectArg(select) {
649
- if (select === undefined)
650
- return;
651
- return Array.isArray(select) ? select.join(",") : select;
652
- }
653
- function selectedInspectFacets(select) {
654
- const facets = (selectArg(select) ?? "content").split(",").map((facet) => facet.trim());
655
- for (const facet of facets) {
656
- if (!INSPECT_FACETS.includes(facet)) {
657
- throw new Error(`unknown inspect facet: ${facet}`);
658
- }
659
- }
660
- return facets;
661
- }
662
- function hasInspectFacet(facets, facet) {
663
- return facets.includes(facet);
664
- }
665
- function validateTarget(command, target, stdinName) {
666
- if (target.length === 0)
667
- throw new Error(`${command} requires a target file path or '-'`);
668
- if (stdinName !== undefined && stdinName.length > 0 && target !== "-") {
669
- throw new Error("stdinName requires '-' as the target");
670
- }
671
- }
672
- function validatePositiveInteger(value, name) {
673
- if (!Number.isSafeInteger(value) || value < 1) {
674
- throw new Error(`${name} must be a positive integer`);
675
- }
676
- }
677
- var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
678
- function isSelectorKind(value) {
679
- return SELECTOR_KINDS.includes(value);
680
- }
681
- function parseSelector(text) {
682
- const separator = text.indexOf(":");
683
- if (separator < 0) {
684
- throw new Error("selector must start with line:, hash:, symbol:, page:, node:, or asset:");
685
- }
686
- const kind = text.slice(0, separator);
687
- const value = text.slice(separator + 1);
688
- if (value.length === 0)
689
- throw new Error(`${kind} selector must not be empty`);
690
- if (!isSelectorKind(kind)) {
691
- throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
692
- }
693
- if (kind === "line" || kind === "hash") {
694
- const parts = value.split(":");
695
- if (parts.length > 2)
696
- throw new Error("selector has too many components");
697
- if (parts.length === 2) {
698
- const column = Number(parts[1]);
699
- if (!Number.isSafeInteger(column))
700
- throw new Error(`invalid column \`${parts[1]}\``);
701
- if (column < 1)
702
- throw new Error("column must be greater than zero");
703
- return { kind, value: parts[0], column };
704
- }
705
- }
706
- if (kind === "page") {
707
- if (value.includes(":"))
708
- throw new Error("page selector has too many components");
709
- const page = Number(value);
710
- if (!Number.isSafeInteger(page))
711
- throw new Error(`invalid page \`${value}\``);
712
- if (page < 1)
713
- throw new Error("page must be greater than zero");
714
- }
715
- return { kind, value };
716
- }
717
- function buildInspectArgs(input) {
718
- validateTarget("inspect", input.path, input.stdinName);
719
- if (input.selector !== undefined && input.selector.length === 0) {
720
- throw new Error("selector must not be empty");
721
- }
722
- const vision = validateVisionLevelVsMode({
723
- visionMode: input.visionMode,
724
- visionLevel: input.visionLevel
725
- });
726
- if (!vision.ok)
727
- throw new Error(vision.error);
728
- const facets = selectedInspectFacets(input.select);
729
- const hasContent = hasInspectFacet(facets, "content");
730
- const hasIdentity = hasInspectFacet(facets, "identity");
731
- const hasMap = hasInspectFacet(facets, "map");
732
- const hasSourceFacet = hasMap || hasInspectFacet(facets, "diagnostics") || hasIdentity;
733
- if (input.end !== undefined)
734
- validatePositiveInteger(input.end, "end");
735
- if (input.limit !== undefined)
736
- validatePositiveInteger(input.limit, "limit");
737
- if (input.end !== undefined && input.limit !== undefined) {
738
- throw new Error("cannot combine end with limit");
739
- }
740
- if (input.depth !== undefined) {
741
- if (!Number.isSafeInteger(input.depth) || input.depth < 0) {
742
- throw new Error("depth must be a non-negative integer");
743
- }
744
- if (!hasMap)
745
- throw new Error("depth requires the map facet");
746
- }
747
- if (input.selector !== undefined && input.query !== undefined) {
748
- throw new Error("cannot combine selector with query");
749
- }
750
- if (!hasContent && (input.end !== undefined || input.limit !== undefined || input.query !== undefined)) {
751
- throw new Error("ranges and queries require the content facet");
752
- }
753
- if (hasIdentity && input.selector === undefined) {
754
- throw new Error("the identity facet requires a selector");
755
- }
756
- if (input.selector !== undefined && !hasContent && !hasIdentity) {
757
- throw new Error("selectors require the content or identity facet");
758
- }
759
- if (input.selector !== undefined) {
760
- const selector = parseSelector(input.selector);
761
- if (selector.kind === "symbol" && hasIdentity) {
762
- throw new Error("the identity facet does not accept symbol selectors");
763
- }
764
- if (selector.column !== undefined && !hasIdentity) {
765
- throw new Error("selector columns require the identity facet");
766
- }
767
- }
768
- const hasVision = input.visionMode !== undefined || input.visionLevel !== undefined;
769
- if (hasVision && !hasContent) {
770
- throw new Error("vision options require the content facet");
771
- }
772
- if (hasVision && hasSourceFacet) {
773
- throw new Error("vision options cannot be combined with source facets");
774
- }
775
- const args = [];
776
- if (input.readseekDir !== undefined)
777
- args.push("--readseek-dir", input.readseekDir);
778
- args.push("inspect", input.path);
779
- if (input.selector !== undefined)
780
- args.push(input.selector);
781
- const select = selectArg(input.select);
782
- if (select !== undefined)
783
- args.push("--select", select);
784
- pushOptionalString(args, "--query", input.query);
785
- pushOptionalNumber(args, "--end", input.end);
786
- pushOptionalNumber(args, "--limit", input.limit);
787
- pushOptionalNumber(args, "--depth", input.depth);
788
- pushOptionalString(args, "--language", input.language);
789
- pushOptionalString(args, "--vision-mode", input.visionMode);
790
- pushOptionalString(args, "--vision-level", input.visionLevel);
791
- pushOptionalString(args, "--stdin-name", input.stdinName);
792
- args.push("--kdl");
793
- return args;
794
- }
795
- function buildExtractArgs(input) {
796
- validateTarget("extract", input.path, input.stdinName);
797
- if (input.selector.length === 0)
798
- throw new Error("extract requires an asset selector");
799
- if (!input.selector.startsWith("asset:") || input.selector === "asset:") {
800
- throw new Error("extract requires an asset: selector");
801
- }
802
- if (input.output.length === 0 || input.output === "-") {
803
- throw new Error("extract output must be a file path");
804
- }
805
- const args = [];
806
- if (input.readseekDir !== undefined)
807
- args.push("--readseek-dir", input.readseekDir);
808
- args.push("--output", input.output, "extract", input.path, input.selector);
809
- pushOptionalString(args, "--stdin-name", input.stdinName);
810
- return args;
811
- }
812
- function buildSearchArgs(input) {
813
- const args = ["search", input.pattern];
814
- pushOptionalString(args, "--path", input.path);
815
- pushOptionalString(args, "--language", input.language);
816
- pushOptionalNumber(args, "--limit", input.limit);
817
- appendGitScopeArgs(args, input);
818
- args.push("--kdl");
819
- return args;
820
- }
821
- function buildDefArgs(input) {
822
- const args = ["def", input.name];
823
- pushOptionalString(args, "--path", input.path);
824
- pushOptionalString(args, "--language", input.language);
825
- appendGitScopeArgs(args, input);
826
- args.push("--kdl");
827
- return args;
828
- }
829
- function buildRefsArgs(input) {
830
- const validated = validateRefsNameVsScope(input);
831
- if (!validated.ok)
832
- throw new Error(validated.error);
833
- const args = ["refs"];
834
- if (input.name !== undefined)
835
- args.push(input.name);
836
- pushOptionalString(args, "--path", input.path);
837
- if (input.scope) {
838
- args.push("--scope");
839
- pushOptionalNumber(args, "--line", input.line);
840
- pushOptionalNumber(args, "--column", input.column);
841
- }
842
- pushOptionalString(args, "--language", input.language);
843
- appendGitScopeArgs(args, input);
844
- args.push("--kdl");
845
- return args;
846
- }
847
- function buildEditArgs(input) {
848
- const args = ["edit", input.path];
849
- appendApplyAndPlanHash(args, {
850
- apply: input.apply,
851
- planHash: input.planHash,
852
- flagOrder: input.flagOrder ?? "apply-first"
853
- });
854
- pushOptionalString(args, "--language", input.language);
855
- if (input.requestFromStdin)
856
- args.push("--edits", "-");
857
- args.push("--kdl");
858
- return args;
859
- }
860
- function buildRenameArgs(input) {
861
- const args = ["rename", input.path, "--at", buildLineLocation(input.line, input.column), "--to", input.to];
862
- if (input.workspace !== undefined)
863
- args.push(`--workspace=${input.workspace}`);
864
- appendApplyAndPlanHash(args, {
865
- apply: input.apply,
866
- planHash: input.planHash,
867
- flagOrder: input.flagOrder ?? "plan-hash-first"
868
- });
869
- pushOptionalString(args, "--language", input.language);
870
- appendGitScopeArgs(args, input);
871
- args.push("--kdl");
872
- return args;
873
- }
874
1031
  // ../readseek-api/src/platform.ts
875
1032
  import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
876
1033
  import { createRequire } from "node:module";
877
1034
  import { homedir } from "node:os";
878
- import path from "node:path";
1035
+ import path2 from "node:path";
879
1036
  var READSEEK_BINARY_ENV = "READSEEK_BINARY";
880
1037
  var READSEEK_PLATFORM_PACKAGES = {
881
1038
  "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
@@ -896,7 +1053,7 @@ function resolveReadSeekBinaryOverride(environment = process.env) {
896
1053
  const binaryPath = environment[READSEEK_BINARY_ENV];
897
1054
  if (binaryPath === undefined)
898
1055
  return null;
899
- if (!path.isAbsolute(binaryPath)) {
1056
+ if (!path2.isAbsolute(binaryPath)) {
900
1057
  throw new Error(`${READSEEK_BINARY_ENV} must be an absolute path to the readseek executable`);
901
1058
  }
902
1059
  try {
@@ -924,14 +1081,14 @@ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, option
924
1081
  let packageJsonPath;
925
1082
  try {
926
1083
  const metaPackageJson = resolve(`${metaPackage}/package.json`);
927
- const metaPackageDir = path.dirname(metaPackageJson);
1084
+ const metaPackageDir = path2.dirname(metaPackageJson);
928
1085
  packageJsonPath = resolve(`${platformPackage}/package.json`, {
929
1086
  paths: [metaPackageDir]
930
1087
  });
931
1088
  } catch {
932
1089
  packageJsonPath = resolve(`${platformPackage}/package.json`);
933
1090
  }
934
- const binaryPath = path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
1091
+ const binaryPath = path2.join(path2.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
935
1092
  accessSync(binaryPath, fsConstants.X_OK);
936
1093
  return binaryPath;
937
1094
  }
@@ -942,18 +1099,18 @@ function resolveReadSeekCacheDir(subPath, options = {}) {
942
1099
  const cwd = options.cwd ?? process.cwd();
943
1100
  const localDirName = options.localDirName ?? ".readseek";
944
1101
  if (localDirName && localDirName.length > 0) {
945
- const local = path.join(cwd, localDirName);
1102
+ const local = path2.join(cwd, localDirName);
946
1103
  if (existsSync(local))
947
1104
  return local;
948
1105
  }
949
1106
  let base;
950
1107
  if (platform === "win32") {
951
- base = env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
1108
+ base = env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local");
952
1109
  } else {
953
1110
  const xdg = env.XDG_CACHE_HOME?.trim();
954
- base = xdg && path.isAbsolute(xdg) ? xdg : path.join(home, ".cache");
1111
+ base = xdg && path2.isAbsolute(xdg) ? xdg : path2.join(home, ".cache");
955
1112
  }
956
- const pathModule = platform === "win32" ? path.win32 : path;
1113
+ const pathModule = platform === "win32" ? path2.win32 : path2;
957
1114
  if (subPath === undefined) {
958
1115
  return pathModule.join(base, "readseek");
959
1116
  }
@@ -961,10 +1118,10 @@ function resolveReadSeekCacheDir(subPath, options = {}) {
961
1118
  return pathModule.join(base, ...parts);
962
1119
  }
963
1120
  // ../readseek-api/src/path.ts
964
- import path2 from "node:path";
1121
+ import path3 from "node:path";
965
1122
  function isSubPath(parent, candidate) {
966
- const relative = path2.relative(parent, candidate);
967
- return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
1123
+ const relative = path3.relative(parent, candidate);
1124
+ return relative === "" || !relative.startsWith(`..${path3.sep}`) && relative !== ".." && !path3.isAbsolute(relative);
968
1125
  }
969
1126
  // ../readseek-api/src/error.ts
970
1127
  var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
@@ -1089,21 +1246,26 @@ function sourceDocument(root) {
1089
1246
  const metadata = child(root, "metadata");
1090
1247
  const cache = child(root, "cache");
1091
1248
  const content = child(root, "content");
1092
- const map = child(root, "map");
1249
+ const structure = child(root, "structure");
1093
1250
  const diagnosticOutput = child(root, "diagnostics");
1094
- const identityOutput = child(root, "identity");
1251
+ const identityOutput = child(root, "cursor");
1095
1252
  if (metadata)
1096
1253
  result.metadata = properties(metadata);
1254
+ if (!metadata && !content && !structure && !diagnosticOutput && !identityOutput && root.properties.has("file"))
1255
+ result.file = root.properties.get("file");
1256
+ const assets = children(root, "asset");
1257
+ if (assets.length > 0)
1258
+ result.assets = assets.map(genericNode);
1097
1259
  if (cache)
1098
1260
  result.cache = properties(cache);
1099
1261
  if (content)
1100
1262
  result.content = sourceContent(content);
1101
- if (map)
1102
- result.map = sourceMap(map);
1263
+ if (structure)
1264
+ result.structure = sourceMap(structure);
1103
1265
  if (diagnosticOutput)
1104
1266
  result.diagnostics = diagnostics(diagnosticOutput);
1105
1267
  if (identityOutput)
1106
- result.identity = identity(identityOutput);
1268
+ result.cursor = identity(identityOutput);
1107
1269
  return result;
1108
1270
  }
1109
1271
  function hit(node) {
@@ -1224,7 +1386,10 @@ function genericNode(node) {
1224
1386
  result.bbox = properties(bbox);
1225
1387
  const objects = children(node, "object");
1226
1388
  if (objects.length > 0)
1227
- result.objects = objects.map(genericNode);
1389
+ result.objects = objects.map((object) => ({
1390
+ label: requireProperty(object, "label"),
1391
+ bbox: [requireProperty(object, "x0"), requireProperty(object, "y0"), requireProperty(object, "x1"), requireProperty(object, "y1")]
1392
+ }));
1228
1393
  return result;
1229
1394
  }
1230
1395
  function pdfDocument(root) {
@@ -1289,13 +1454,34 @@ function failureFromProcessResult(result, options = {}) {
1289
1454
  return new Error(`readseek killed by signal ${result.signal}`);
1290
1455
  return new Error(`readseek exited with status ${result.exitCode}`);
1291
1456
  }
1292
- function ensureSuccessfulStdout(result) {
1293
- if (result.exitCode === 0)
1457
+ function acceptsReadSeekExitCode(args, exitCode) {
1458
+ if (exitCode === 0)
1459
+ return true;
1460
+ if (exitCode !== 1)
1461
+ return false;
1462
+ let index = 0;
1463
+ const valued = new Set(["-C", "--directory", "--output", "--readseek-dir"]);
1464
+ while (index < args.length) {
1465
+ const arg = args[index];
1466
+ if (valued.has(arg)) {
1467
+ index += 2;
1468
+ continue;
1469
+ }
1470
+ if ([...valued].some((flag) => arg.startsWith(`${flag}=`)) || arg === "--kdl") {
1471
+ index++;
1472
+ continue;
1473
+ }
1474
+ return arg === "find" || arg === "check";
1475
+ }
1476
+ return false;
1477
+ }
1478
+ function ensureSuccessfulStdout(result, args = []) {
1479
+ if (!result.signal && acceptsReadSeekExitCode(args, result.exitCode))
1294
1480
  return result.stdout;
1295
1481
  throw failureFromProcessResult(result);
1296
1482
  }
1297
- function parseSuccessfulKdl(result) {
1298
- return parseReadSeekKdl(ensureSuccessfulStdout(result));
1483
+ function parseSuccessfulKdl(result, args = []) {
1484
+ return parseReadSeekKdl(ensureSuccessfulStdout(result, args));
1299
1485
  }
1300
1486
  // ../readseek-api/src/process-bun.ts
1301
1487
  function getBunSpawn() {
@@ -1385,256 +1571,13 @@ async function runReadSeekBun(binaryPath, args, options = {}) {
1385
1571
  }
1386
1572
  }
1387
1573
  async function runReadSeekBunText(binaryPath, args, options = {}) {
1388
- return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options));
1389
- }
1390
- async function runReadSeekBunKdl(binaryPath, args, options = {}) {
1391
- return parseSuccessfulKdl(await runReadSeekBun(binaryPath, args, options));
1392
- }
1393
- // ../readseek-api/src/client-output.ts
1394
- import { Type } from "@sinclair/typebox";
1395
- import { AssertError, Value } from "@sinclair/typebox/value";
1396
- var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
1397
- var Str = Type.String();
1398
- var Bool = Type.Boolean();
1399
- var OptStr = Type.Optional(Str);
1400
- var OptInt = Type.Optional(Int);
1401
- var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
1402
- var Hashlines = Type.Array(Hashline);
1403
- var ReadOutput = Type.Object({
1404
- file: Str,
1405
- language: Str,
1406
- line_count: Int,
1407
- file_hash: Str,
1408
- start_line: Int,
1409
- end_line: Int,
1410
- hashlines: Hashlines
1411
- });
1412
- var SymbolOutput = Type.Object({
1413
- kind: Str,
1414
- name: Str,
1415
- qualified_name: OptStr,
1416
- start_line: Int,
1417
- end_line: Int,
1418
- start_hash: OptStr,
1419
- end_hash: OptStr,
1420
- start_anchor: OptStr,
1421
- end_anchor: OptStr
1422
- });
1423
- function mapNodeShape(child) {
1424
- return Type.Object({
1425
- kind: Str,
1426
- name: Str,
1427
- start_line: Int,
1428
- end_line: Int,
1429
- start_anchor: Str,
1430
- end_anchor: Str,
1431
- children: Type.Array(child)
1432
- });
1574
+ return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options), args);
1433
1575
  }
1434
- function recursiveMapNode() {
1435
- return Type.Recursive((self) => mapNodeShape(self));
1576
+ async function runReadSeekBunKdl(binaryPath, args, options = {}) {
1577
+ return parseSuccessfulKdl(await runReadSeekBun(binaryPath, args, options), args);
1436
1578
  }
1437
- var MapNode = typeof Type.Recursive === "function" ? recursiveMapNode() : Type.Cyclic({ MapNode: mapNodeShape(Type.Ref("MapNode")) }, "MapNode");
1438
- var MapOutput = Type.Object({
1439
- file: Str,
1440
- language: Str,
1441
- line_count: Int,
1442
- file_hash: Str,
1443
- root: Type.Optional(MapNode),
1444
- symbols: Type.Array(SymbolOutput)
1445
- });
1446
- var SearchSpan = {
1447
- start_byte: OptInt,
1448
- end_byte: OptInt,
1449
- text: OptStr,
1450
- start_line: Int,
1451
- end_line: Int,
1452
- start_hash: Str,
1453
- end_hash: Str,
1454
- hashlines: Hashlines
1455
- };
1456
- var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
1457
- var SearchMatch = Type.Object({
1458
- pattern_index: Type.Integer({ default: 0 }),
1459
- ...SearchSpan,
1460
- captures: Type.Array(SearchCapture)
1461
- });
1462
- var SearchFileOutput = Type.Object({
1463
- file: Str,
1464
- language: Str,
1465
- file_hash: Str,
1466
- matches: Type.Array(SearchMatch)
1467
- });
1468
- var SearchEnvelope = Type.Object({
1469
- results: Type.Array(SearchFileOutput),
1470
- returned_matches: OptInt,
1471
- truncated: Type.Optional(Bool),
1472
- byte_basis: OptStr
1473
- });
1474
- var RefsEnvelope = Type.Object({
1475
- references: Type.Array(Type.Object({
1476
- file: Str,
1477
- line: Int,
1478
- column: Int,
1479
- line_hash: Str,
1480
- text: Str,
1481
- symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
1482
- }))
1483
- });
1484
- var Diagnostic = Type.Object({
1485
- kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
1486
- start_line: Int,
1487
- end_line: Int
1488
- });
1489
- var CheckEnvelope = Type.Object({
1490
- file_hash: OptStr,
1491
- error_count: Int,
1492
- missing_count: Int,
1493
- diagnostics: Type.Array(Diagnostic)
1494
- });
1495
- var DetectedObject = Type.Object({
1496
- label: Str,
1497
- bbox: Type.Tuple([Int, Int, Int, Int])
1498
- });
1499
- var PreparedImage = Type.Object({
1500
- mime: Str,
1501
- encoding: Type.Literal("base64"),
1502
- data: Str
1503
- });
1504
- var SourceDetection = Type.Object({
1505
- kind: Type.Literal("source"),
1506
- type: Str,
1507
- file: Str,
1508
- language: Str,
1509
- engine: OptStr,
1510
- supported: Bool,
1511
- mime: OptStr,
1512
- syntax: OptStr
1513
- });
1514
- var ImageDetection = Type.Object({
1515
- kind: Type.Literal("image"),
1516
- type: Str,
1517
- file: Str,
1518
- mime: OptStr,
1519
- format: Str,
1520
- width: Int,
1521
- height: Int,
1522
- animated: Bool,
1523
- encoding: Type.Optional(Type.Literal("base64")),
1524
- data: OptStr,
1525
- ocr: OptStr,
1526
- caption: OptStr,
1527
- objects: Type.Optional(Type.Array(DetectedObject))
1528
- });
1529
- var TextDetection = Type.Object({
1530
- kind: Type.Literal("text"),
1531
- type: Str,
1532
- file: Str,
1533
- mime: OptStr
1534
- });
1535
- var PdfDetection = Type.Object({
1536
- kind: Type.Literal("pdf"),
1537
- type: Type.Literal("application/pdf"),
1538
- file: Str,
1539
- mime: OptStr,
1540
- format: Type.Literal("pdf"),
1541
- pages: Int
1542
- });
1543
- var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
1544
- var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
1545
- var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
1546
- var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
1547
- var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
1548
- var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
1549
- var PdfInspectMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
1550
- var DocumentCache = Type.Object({
1551
- content_hash: Str,
1552
- state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
1553
- });
1554
- var Identifier = Type.Object({
1555
- text: Str,
1556
- start_column: Int,
1557
- end_column: Int,
1558
- start_byte: Int,
1559
- end_byte: Int
1560
- });
1561
- var IdentifyEnvelope = Type.Object({
1562
- file: Str,
1563
- language: Str,
1564
- engine: OptStr,
1565
- line_count: Int,
1566
- file_hash: Str,
1567
- line: Int,
1568
- column: Int,
1569
- line_hash: Str,
1570
- identifier: Type.Optional(Identifier),
1571
- symbol: Type.Optional(Type.Object({
1572
- name: Str,
1573
- kind: Str,
1574
- qualified_name: Str,
1575
- start_line: Int,
1576
- end_line: Int
1577
- }))
1578
- });
1579
- var EditOutputSchema = Type.Object({
1580
- file: Str,
1581
- before_hash: Str,
1582
- file_hash: Str,
1583
- plan_hash: Str,
1584
- applied: Bool,
1585
- changed: Bool,
1586
- edit_count: Int,
1587
- before_content: OptStr,
1588
- content: OptStr
1589
- });
1590
- var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
1591
- var RenameEditSchema = Type.Object({
1592
- line: Int,
1593
- start_column: Int,
1594
- end_column: Int,
1595
- start_byte: Int,
1596
- end_byte: Int,
1597
- occurrence: Str,
1598
- line_hash: Str,
1599
- text: Str
1600
- });
1601
- var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
1602
- var Edits = Type.Array(RenameEditSchema, { default: [] });
1603
- var RenameFileOutputSchema = Type.Object({
1604
- file: Str,
1605
- language: Str,
1606
- engine: OptStr,
1607
- file_hash: Str,
1608
- conflicts: Conflicts,
1609
- edits: Edits
1610
- });
1611
- var RenameOutputSchema = Type.Object({
1612
- file: Str,
1613
- language: Str,
1614
- engine: OptStr,
1615
- file_hash: Str,
1616
- old_name: Str,
1617
- new_name: Str,
1618
- plan_hash: Str,
1619
- applied: Bool,
1620
- conflicts: Conflicts,
1621
- edits: Edits,
1622
- others: Type.Array(RenameFileOutputSchema, { default: [] })
1623
- });
1624
- var DefEnvelope = Type.Object({
1625
- locations: Type.Array(Type.Object({
1626
- file: Str,
1627
- line: Int,
1628
- column: Int,
1629
- line_hash: Str,
1630
- text: Str,
1631
- kind: OptStr,
1632
- name: OptStr,
1633
- qualified_name: OptStr
1634
- }))
1635
- });
1636
1579
  // src/util.ts
1637
- import path3 from "node:path";
1580
+ import path4 from "node:path";
1638
1581
  function render(value) {
1639
1582
  return JSON.stringify(value, null, 2);
1640
1583
  }
@@ -1672,12 +1615,12 @@ function renameFiles(value, directory = process.cwd()) {
1672
1615
  const output = record(value);
1673
1616
  const files = [];
1674
1617
  if (typeof output.file === "string" && items(output.edits).length > 0) {
1675
- files.push(path3.resolve(directory, output.file));
1618
+ files.push(path4.resolve(directory, output.file));
1676
1619
  }
1677
1620
  for (const other of items(output.others)) {
1678
1621
  const item = record(other);
1679
1622
  if (typeof item.file === "string" && items(item.edits).length > 0) {
1680
- files.push(path3.resolve(directory, item.file));
1623
+ files.push(path4.resolve(directory, item.file));
1681
1624
  }
1682
1625
  }
1683
1626
  return files;
@@ -1728,21 +1671,18 @@ function resolveImagePolicy(options) {
1728
1671
  return value;
1729
1672
  throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
1730
1673
  }
1731
- function inspectArgs(filePath, select, options = {}) {
1732
- return buildInspectArgs({ path: filePath, select, ...options });
1674
+ function contentArgs(filePath, options = {}) {
1675
+ return buildShowContentArgs({ path: filePath, ...options, at: options.at ?? (options.end === undefined ? undefined : "line:1") });
1733
1676
  }
1734
- function requireInspectFacet(envelope, facet) {
1735
- const value = record(envelope)[facet];
1677
+ function requireContent(envelope) {
1678
+ const value = record(envelope).content;
1736
1679
  if (value === undefined || value === null || typeof value !== "object") {
1737
- throw new Error(`inspect returned no ${facet} facet`);
1680
+ throw new Error("show content returned no source content");
1738
1681
  }
1739
1682
  return value;
1740
1683
  }
1741
1684
  function readRangeArgs(filePath, start, end) {
1742
- return inspectArgs(filePath, "content", {
1743
- selector: start > 1 ? `line:${start}` : undefined,
1744
- end
1745
- });
1685
+ return contentArgs(filePath, { at: `line:${start}`, end });
1746
1686
  }
1747
1687
  function readSeekBinaryPath() {
1748
1688
  return resolveReadSeekBinaryOverride() ?? resolveReadSeekBinaryFromPackages(resolveFromHost);
@@ -1779,12 +1719,12 @@ async function runReadSeek(context, args, options = {}) {
1779
1719
  timeoutMs: options.timeoutMs
1780
1720
  }));
1781
1721
  }
1782
- async function runInspectContent(context, args, options = {}) {
1783
- return requireInspectFacet(await runReadSeek(context, args, options), "content");
1722
+ async function runContent(context, args, options = {}) {
1723
+ return requireContent(await runReadSeek(context, args, options));
1784
1724
  }
1785
1725
  async function postMutationPreview(context, args, filePath, changed) {
1786
1726
  try {
1787
- return await runInspectContent(context, args, {
1727
+ return await runContent(context, args, {
1788
1728
  cancelable: false,
1789
1729
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1790
1730
  });
@@ -1813,7 +1753,7 @@ async function ensureReadSeekCache(context) {
1813
1753
  if (error.code !== "ENOENT")
1814
1754
  throw error;
1815
1755
  }
1816
- readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"], { cancelable: false }).then(() => cacheDir).finally(() => {
1756
+ readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "initialize", "index"], { cancelable: false }).then(() => cacheDir).finally(() => {
1817
1757
  readSeekCacheInit = undefined;
1818
1758
  });
1819
1759
  return readSeekCacheInit;
@@ -1833,7 +1773,7 @@ function openCodeToolDescription(name) {
1833
1773
  }
1834
1774
 
1835
1775
  // src/session-anchors.ts
1836
- import path4 from "node:path";
1776
+ import path5 from "node:path";
1837
1777
 
1838
1778
  class SessionAnchors {
1839
1779
  #pathsBySession = new Map;
@@ -1847,7 +1787,7 @@ class SessionAnchors {
1847
1787
  paths.add(filePath);
1848
1788
  }
1849
1789
  forget(filePath) {
1850
- const absolutePath = path4.resolve(filePath);
1790
+ const absolutePath = path5.resolve(filePath);
1851
1791
  for (const paths of this.#pathsBySession.values())
1852
1792
  paths.delete(absolutePath);
1853
1793
  for (const [sessionID, plan] of this.#renamePlans) {
@@ -1870,14 +1810,14 @@ class SessionAnchors {
1870
1810
  const { file, old_name: oldName, new_name: newName, others } = record;
1871
1811
  if (typeof file !== "string" || typeof oldName !== "string" || typeof newName !== "string")
1872
1812
  return;
1873
- const files = new Set([path4.resolve(file)]);
1813
+ const files = new Set([path5.resolve(file)]);
1874
1814
  if (Array.isArray(others)) {
1875
1815
  for (const item of others) {
1876
1816
  if (!item || typeof item !== "object")
1877
1817
  continue;
1878
1818
  const otherFile = item.file;
1879
1819
  if (typeof otherFile === "string")
1880
- files.add(path4.resolve(otherFile));
1820
+ files.add(path5.resolve(otherFile));
1881
1821
  }
1882
1822
  }
1883
1823
  this.#renamePlans.set(sessionID, { summary: `${oldName} -> ${newName}`, files });
@@ -1903,22 +1843,21 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
1903
1843
 
1904
1844
  // src/tools.ts
1905
1845
  import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
1906
- import { tool as tool2 } from "@opencode-ai/plugin";
1846
+ import { tool as tool3 } from "@opencode-ai/plugin";
1907
1847
 
1908
1848
  // src/constants.ts
1909
1849
  var DEFAULT_VISION_TIMEOUT_MS = 30 * 60 * 1000;
1910
1850
  var DEFAULT_READ_LIMIT = 2000;
1911
1851
  var EDIT_RESULT_READ_LIMIT = 40;
1912
1852
  var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
1913
- var IMAGE_MODES = [...VISION_MODES];
1914
1853
  var VISION_LEVEL_VALUES = [...VISION_LEVELS];
1915
1854
 
1916
1855
  // src/edits.ts
1917
1856
  import { lstat, mkdir, open, readFile } from "node:fs/promises";
1918
- import path5 from "node:path";
1857
+ import path6 from "node:path";
1919
1858
  var fileMutationTails = new Map;
1920
1859
  async function withFileMutationQueue(filePath, operation) {
1921
- const key = path5.resolve(filePath);
1860
+ const key = path6.resolve(filePath);
1922
1861
  const previous = fileMutationTails.get(key);
1923
1862
  let release = () => {};
1924
1863
  const current = new Promise((resolve) => {
@@ -1973,7 +1912,7 @@ async function writeHandle(handle, content) {
1973
1912
  }
1974
1913
  }
1975
1914
  async function writeText(filePath, expected, content) {
1976
- await mkdir(path5.dirname(filePath), { recursive: true });
1915
+ await mkdir(path6.dirname(filePath), { recursive: true });
1977
1916
  if (!expected.exists) {
1978
1917
  let handle;
1979
1918
  try {
@@ -2105,20 +2044,20 @@ function simpleDiff(filePath, before, after) {
2105
2044
 
2106
2045
  // src/paths.ts
2107
2046
  import { lstat as lstat2, readlink, realpath, stat as stat2 } from "node:fs/promises";
2108
- import path6 from "node:path";
2047
+ import path7 from "node:path";
2109
2048
  function resolvePath(directory, filePath) {
2110
- return path6.resolve(directory, filePath);
2049
+ return path7.resolve(directory, filePath);
2111
2050
  }
2112
2051
  async function resolveSymlinks(filePath) {
2113
- let unresolved = path6.resolve(filePath);
2052
+ let unresolved = path7.resolve(filePath);
2114
2053
  let symlinks = 0;
2115
2054
  while (true) {
2116
- const parsed = path6.parse(unresolved);
2117
- const parts = unresolved.slice(parsed.root.length).split(path6.sep).filter(Boolean);
2055
+ const parsed = path7.parse(unresolved);
2056
+ const parts = unresolved.slice(parsed.root.length).split(path7.sep).filter(Boolean);
2118
2057
  let resolved = parsed.root;
2119
2058
  let restart = false;
2120
2059
  for (let index = 0;index < parts.length; index++) {
2121
- const candidate = path6.join(resolved, parts[index]);
2060
+ const candidate = path7.join(resolved, parts[index]);
2122
2061
  const info = await lstat2(candidate);
2123
2062
  if (!info.isSymbolicLink()) {
2124
2063
  resolved = candidate;
@@ -2131,7 +2070,7 @@ async function resolveSymlinks(filePath) {
2131
2070
  throw error;
2132
2071
  }
2133
2072
  const target = await readlink(candidate);
2134
- unresolved = path6.resolve(path6.dirname(candidate), target, ...parts.slice(index + 1));
2073
+ unresolved = path7.resolve(path7.dirname(candidate), target, ...parts.slice(index + 1));
2135
2074
  restart = true;
2136
2075
  break;
2137
2076
  }
@@ -2153,14 +2092,14 @@ async function canonicalPath(filePath) {
2153
2092
  let existing = filePath;
2154
2093
  while (true) {
2155
2094
  try {
2156
- return path6.join(await portableRealpath(existing), ...missing.reverse());
2095
+ return path7.join(await portableRealpath(existing), ...missing.reverse());
2157
2096
  } catch (error) {
2158
2097
  if (error.code !== "ENOENT")
2159
2098
  throw error;
2160
- const parent = path6.dirname(existing);
2099
+ const parent = path7.dirname(existing);
2161
2100
  if (parent === existing)
2162
2101
  throw error;
2163
- missing.push(path6.basename(existing));
2102
+ missing.push(path7.basename(existing));
2164
2103
  existing = parent;
2165
2104
  }
2166
2105
  }
@@ -2177,8 +2116,8 @@ async function authorizeExternal(context, filePath) {
2177
2116
  const info = await stat2(accessPath).catch(() => {
2178
2117
  return;
2179
2118
  });
2180
- const parentDir = info?.isDirectory() ? accessPath : path6.dirname(accessPath);
2181
- const pattern = path6.join(parentDir, "*").replaceAll("\\", "/");
2119
+ const parentDir = info?.isDirectory() ? accessPath : path7.dirname(accessPath);
2120
+ const pattern = path7.join(parentDir, "*").replaceAll("\\", "/");
2182
2121
  await context.ask({
2183
2122
  permission: "external_directory",
2184
2123
  patterns: [pattern],
@@ -2199,7 +2138,7 @@ async function authorizeRead(context, filePath) {
2199
2138
  await authorizeExternal(context, filePath);
2200
2139
  await context.ask({
2201
2140
  permission: "read",
2202
- patterns: [path6.relative(context.worktree, filePath).replaceAll("\\", "/")],
2141
+ patterns: [path7.relative(context.worktree, filePath).replaceAll("\\", "/")],
2203
2142
  always: ["*"],
2204
2143
  metadata: {}
2205
2144
  });
@@ -2218,7 +2157,7 @@ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPat
2218
2157
  for (const filePath of filePaths)
2219
2158
  await authorizeExternal(context, filePath);
2220
2159
  }
2221
- const patterns = filePaths.map((filePath) => path6.relative(context.worktree, filePath).replaceAll("\\", "/"));
2160
+ const patterns = filePaths.map((filePath) => path7.relative(context.worktree, filePath).replaceAll("\\", "/"));
2222
2161
  await context.ask({
2223
2162
  permission: "edit",
2224
2163
  patterns,
@@ -2228,54 +2167,59 @@ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPat
2228
2167
  }
2229
2168
 
2230
2169
  // src/presentation.ts
2231
- import { tool } from "@opencode-ai/plugin";
2170
+ import { pathToFileURL } from "node:url";
2171
+ import { tool as tool2 } from "@opencode-ai/plugin";
2232
2172
  function initialTitle(kind, args) {
2233
2173
  switch (kind) {
2234
- case "inspect":
2235
- return `Inspect ${args.path}`;
2236
- case "extract":
2237
- return `Extract ${args.selector} to ${args.output}`;
2238
- case "search":
2239
- return `Search ${args.pattern}`;
2240
- case "grep":
2241
- return `Grep ${args.pattern}`;
2242
- case "def":
2174
+ case "show_structure":
2175
+ case "show_metadata":
2176
+ case "check_syntax":
2177
+ case "check_rules":
2178
+ case "generate_caption":
2179
+ case "detect_objects":
2180
+ case "recognize_text":
2181
+ return `${kind.replaceAll("_", " ")} ${args.path}`;
2182
+ case "show_content":
2183
+ return `Show content ${args.path}`;
2184
+ case "show_assets":
2185
+ return `Show assets ${args.path}`;
2186
+ case "find_nodes":
2187
+ return `Find nodes ${args.pattern ?? args.context ?? args.rule}`;
2188
+ case "find_text":
2189
+ return `Find text ${args.pattern ?? args.text}`;
2190
+ case "find_definitions":
2243
2191
  return `Find definition ${args.name}`;
2244
- case "refs":
2192
+ case "find_references":
2245
2193
  return `Find references to ${args.name}`;
2246
- case "rename":
2247
- return `Rename ${args.path}:${args.line} to ${args.to}`;
2248
- case "edit":
2194
+ case "rename_symbol":
2195
+ return `Rename ${args.path}:${args.at} to ${args.to}`;
2196
+ case "edit_text":
2249
2197
  return `Edit ${args.path}`;
2250
- case "write":
2198
+ case "write_file":
2251
2199
  return `Write ${args.path}`;
2252
2200
  }
2253
2201
  }
2254
2202
  function resultPresentation(kind, args, value) {
2255
2203
  const output = record(value);
2256
2204
  switch (kind) {
2257
- case "extract":
2258
- return { title: `Extracted ${args.selector} to ${args.output}`, metadata: {} };
2259
- case "inspect": {
2260
- const map = record(output.map);
2261
- const diagnostics = record(output.diagnostics);
2262
- const identity = record(output.identity);
2205
+ case "show_assets":
2206
+ return { title: `Assets ${args.path}`, metadata: {} };
2207
+ case "show_structure": {
2208
+ const symbols = items(record(output.structure).symbols).length;
2209
+ return { title: `Structure ${args.path} (${symbols} symbols)`, metadata: { symbols } };
2210
+ }
2211
+ case "check_syntax": {
2212
+ const diagnostics = record(output.diagnostics ?? output);
2213
+ const errors = typeof diagnostics.error_count === "number" ? diagnostics.error_count : 0;
2214
+ const missing = typeof diagnostics.missing_count === "number" ? diagnostics.missing_count : 0;
2215
+ return { title: `Syntax ${args.path} (${errors} errors, ${missing} missing)`, metadata: { error_count: errors, missing_count: missing } };
2216
+ }
2217
+ case "show_content": {
2218
+ const identity = record(output.cursor);
2263
2219
  const content = record(output.content);
2264
2220
  const metadata = {};
2265
2221
  const summary = [];
2266
- if (output.map !== undefined) {
2267
- const symbols = items(map.symbols).length;
2268
- metadata.symbols = symbols;
2269
- summary.push(`${symbols} symbols`);
2270
- }
2271
- if (output.diagnostics !== undefined) {
2272
- const errors = typeof diagnostics.error_count === "number" ? diagnostics.error_count : 0;
2273
- const missing = typeof diagnostics.missing_count === "number" ? diagnostics.missing_count : 0;
2274
- metadata.error_count = errors;
2275
- metadata.missing_count = missing;
2276
- summary.push(`${errors} errors, ${missing} missing`);
2277
- }
2278
- if (output.identity !== undefined) {
2222
+ if (output.cursor !== undefined) {
2279
2223
  if (typeof identity.line === "number")
2280
2224
  metadata.line = identity.line;
2281
2225
  if (typeof identity.column === "number")
@@ -2289,29 +2233,32 @@ function resultPresentation(kind, args, value) {
2289
2233
  if (typeof content.end_line === "number")
2290
2234
  metadata.end_line = content.end_line;
2291
2235
  return {
2292
- title: summary.length > 0 ? `Inspected ${args.path} (${summary.join(", ")})` : `Inspected ${args.path}`,
2236
+ title: summary.length > 0 ? `Read ${args.path} (${summary.join(", ")})` : `Read ${args.path}`,
2293
2237
  metadata
2294
2238
  };
2295
2239
  }
2296
- case "search": {
2240
+ case "find_nodes": {
2297
2241
  const results = items(output.results);
2298
2242
  const matches = results.reduce((total, result) => total + items(record(result).matches).length, 0);
2299
2243
  return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
2300
2244
  }
2301
- case "grep": {
2245
+ case "find_text": {
2302
2246
  const results = items(output.results);
2303
- const matches = results.reduce((total, result) => total + items(record(result).matches).length, 0);
2247
+ const matches = results.reduce((total, result) => {
2248
+ const entry = record(result);
2249
+ return total + (typeof entry.count === "number" ? entry.count : items(entry.matches).length);
2250
+ }, 0);
2304
2251
  return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
2305
2252
  }
2306
- case "def": {
2253
+ case "find_definitions": {
2307
2254
  const locations = items(output.locations).length;
2308
2255
  return { title: `Found ${locations} definitions`, metadata: { locations } };
2309
2256
  }
2310
- case "refs": {
2257
+ case "find_references": {
2311
2258
  const references = items(output.references).length;
2312
2259
  return { title: `Found ${references} references`, metadata: { references } };
2313
2260
  }
2314
- case "rename": {
2261
+ case "rename_symbol": {
2315
2262
  const oldName = output.old_name;
2316
2263
  const newName = output.new_name;
2317
2264
  const otherOutputs = items(output.others).map(record);
@@ -2321,16 +2268,16 @@ function resultPresentation(kind, args, value) {
2321
2268
  const title = typeof oldName === "string" && typeof newName === "string" ? `${output.applied === true ? "Renamed" : "Plan"} ${oldName} -> ${newName}` : initialTitle(kind, args);
2322
2269
  return { title, metadata: { edits, conflicts, others } };
2323
2270
  }
2324
- case "edit":
2325
- case "write": {
2271
+ case "edit_text":
2272
+ case "write_file": {
2326
2273
  const preview = typeof output.preview_error === "string" ? " (preview unavailable)" : "";
2327
- return { title: `${kind === "edit" ? "Edited" : "Wrote"} ${args.path}${preview}`, metadata: {} };
2274
+ return { title: `${kind === "edit_text" ? "Edited" : "Wrote"} ${args.path}${preview}`, metadata: {} };
2328
2275
  }
2329
2276
  }
2330
2277
  return { title: initialTitle(kind, args), metadata: {} };
2331
2278
  }
2332
- function readseekTool(description, args, kind, execute) {
2333
- return tool({
2279
+ function readseekTool(description, args, kind, execute, imagePolicy) {
2280
+ return tool2({
2334
2281
  description,
2335
2282
  args,
2336
2283
  async execute(args, context) {
@@ -2338,9 +2285,27 @@ function readseekTool(description, args, kind, execute) {
2338
2285
  context.metadata({ title });
2339
2286
  const result = await execute(args, context);
2340
2287
  const presentation = resultPresentation(kind, args, result);
2288
+ const attachments = [];
2289
+ const imagePaths = new Map;
2290
+ const visit = (value) => {
2291
+ if (!value || typeof value !== "object")
2292
+ return;
2293
+ const item = value;
2294
+ if (typeof item.path === "string" && typeof item.mime === "string" && item.mime.startsWith("image/"))
2295
+ imagePaths.set(item.path, item.mime);
2296
+ for (const child of Object.values(item))
2297
+ visit(child);
2298
+ };
2299
+ if (imagePolicy !== undefined)
2300
+ visit(result);
2301
+ for (const [path, mime] of imagePaths) {
2302
+ if (imagePolicy !== "off")
2303
+ attachments.push({ type: "file", mime, url: pathToFileURL(path).href });
2304
+ }
2341
2305
  return {
2306
+ ...attachments.length > 0 ? { attachments } : {},
2342
2307
  title: presentation.title,
2343
- output: render(result),
2308
+ output: render(imagePolicy === "off" && imagePaths.size > 0 ? { ...record(result), warning: "Image attachments are disabled; use the returned paths to view the images." } : result),
2344
2309
  metadata: presentation.metadata
2345
2310
  };
2346
2311
  }
@@ -2350,88 +2315,30 @@ function readseekTool(description, args, kind, execute) {
2350
2315
  // src/tools.ts
2351
2316
  function createReadSeekTools(imagePolicy) {
2352
2317
  return {
2353
- readseek_inspect: readseekTool(openCodeToolDescription("inspect"), {
2354
- path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2355
- selector: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selector),
2356
- select: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selectComma),
2357
- query: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.query),
2358
- end: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.end),
2359
- limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.limit),
2360
- depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.inspectDepth),
2361
- language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageOverride),
2362
- visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeInspect),
2363
- visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
2364
- }, "inspect", async (input, context) => {
2365
- const filePath = resolvePath(context.directory, input.path);
2366
- await authorizeRead(context, filePath);
2367
- const select = (input.select ?? "content").trim() || "content";
2368
- const visionMode = input.visionMode;
2369
- const visionLevel = input.visionLevel;
2370
- const vision = validateVisionLevelVsMode({ visionMode, visionLevel });
2371
- if (!vision.ok)
2372
- throw new Error(vision.error);
2373
- const cacheDir = await ensureReadSeekCache(context);
2374
- const args = buildInspectArgs({
2375
- path: filePath,
2376
- selector: input.selector,
2377
- select,
2378
- query: input.query,
2379
- end: input.end,
2380
- limit: input.limit,
2381
- depth: input.depth ?? (select.split(",").some((facet) => facet.trim() === "map") ? DEFAULT_INSPECT_MAP_DEPTH : undefined),
2382
- language: input.language,
2383
- visionMode,
2384
- visionLevel,
2385
- readseekDir: cacheDir
2386
- });
2387
- return runReadSeek(context, args, visionMode === undefined || visionMode === "none" ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
2388
- }),
2389
- readseek_extract: readseekTool(openCodeToolDescription("extract"), {
2390
- path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2391
- selector: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.assetSelector),
2392
- output: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.output)
2393
- }, "extract", async (input, context) => {
2394
- const selector = String(input.selector ?? "").trim();
2395
- const output = String(input.output ?? "").trim();
2396
- if (!selector.startsWith("asset:") || selector === "asset:") {
2397
- throw new Error("extract requires an asset: selector");
2398
- }
2399
- if (!output || output === "-")
2400
- throw new Error("extract output must be a file path");
2401
- const filePath = resolvePath(context.directory, input.path);
2402
- const outputPath = resolvePath(context.directory, output);
2403
- await authorizeRead(context, filePath);
2404
- await rejectSymlinkMutation(outputPath);
2405
- await authorizeEdit(context, [outputPath]);
2406
- const cacheDir = await ensureReadSeekCache(context);
2407
- await runReadSeekRaw(context, buildExtractArgs({
2408
- path: filePath,
2409
- selector,
2410
- output: outputPath,
2411
- readseekDir: cacheDir
2412
- }), { cancelable: false, timeoutMs: 120000 });
2413
- return {
2414
- file: filePath,
2415
- selector,
2416
- output: outputPath,
2417
- changed: true
2418
- };
2419
- }),
2420
- readseek_grep: readseekTool(openCodeToolDescription("grep"), {
2421
- pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
2422
- path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2423
- glob: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.glob),
2424
- literal: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.literal),
2425
- ignoreCase: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignoreCase),
2426
- context: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.context),
2427
- limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit)
2428
- }, "grep", async (input, context) => {
2318
+ ...createAccessTools(imagePolicy),
2319
+ readseek_find_text: readseekTool(openCodeToolDescription("find_text"), {
2320
+ pattern: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.grepPattern),
2321
+ text: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.text),
2322
+ summary: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.summary),
2323
+ path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2324
+ glob: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.glob),
2325
+ literal: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.literal),
2326
+ ignoreCase: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignoreCase),
2327
+ context: tool3.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.context),
2328
+ limit: tool3.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit)
2329
+ }, "find_text", async (input, context) => {
2429
2330
  const target = resolvePath(context.directory, input.path ?? ".");
2430
- const pattern = input.pattern;
2331
+ if (input.pattern === undefined === (input.text === undefined)) {
2332
+ throw new Error("exactly one of pattern or text is required");
2333
+ }
2334
+ const pattern = input.pattern ?? input.text;
2431
2335
  await authorizeSearch(context, target, pattern);
2336
+ if (input.text !== undefined && !input.glob && !input.ignoreCase && !input.context && !input.summary) {
2337
+ return runReadSeek(context, buildFindTextArgs({ path: target, text: pattern, limit: input.limit }));
2338
+ }
2432
2339
  let expression;
2433
2340
  try {
2434
- const source = input.literal ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
2341
+ const source = input.literal || input.text !== undefined ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
2435
2342
  expression = new RegExp(source, input.ignoreCase ? "i" : "");
2436
2343
  } catch (error) {
2437
2344
  throw new Error(`invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
@@ -2479,6 +2386,10 @@ function createReadSeekTools(imagePolicy) {
2479
2386
  }
2480
2387
  if (ranges.length === 0)
2481
2388
  continue;
2389
+ if (input.summary) {
2390
+ results.push({ file: filePath, count: matchLines.size });
2391
+ continue;
2392
+ }
2482
2393
  const mergedRanges = [];
2483
2394
  for (const [start, end] of ranges) {
2484
2395
  const previous = mergedRanges.at(-1);
@@ -2489,7 +2400,7 @@ function createReadSeekTools(imagePolicy) {
2489
2400
  }
2490
2401
  const linesByNumber = new Map;
2491
2402
  for (const [start, end] of mergedRanges) {
2492
- const output = record(await runInspectContent(context, readRangeArgs(filePath, start, end)));
2403
+ const output = record(await runContent(context, readRangeArgs(filePath, start, end)));
2493
2404
  for (const hashline of items(output.hashlines)) {
2494
2405
  const line = record(hashline).line;
2495
2406
  if (typeof line === "number" && !linesByNumber.has(line))
@@ -2505,30 +2416,43 @@ function createReadSeekTools(imagePolicy) {
2505
2416
  }
2506
2417
  return { results, truncated };
2507
2418
  }),
2508
- readseek_search: readseekTool(openCodeToolDescription("search"), {
2509
- pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.pattern),
2510
- path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2511
- language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2512
- gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2513
- }, "search", async (input, context) => {
2419
+ readseek_find_nodes: readseekTool(openCodeToolDescription("find_nodes"), {
2420
+ pattern: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.pattern),
2421
+ context: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.patternContext),
2422
+ selector: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.selector),
2423
+ rule: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.rule),
2424
+ limit: tool3.schema.number().int().positive().optional(),
2425
+ path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2426
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2427
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2428
+ }, "find_nodes", async (input, context) => {
2514
2429
  const target = resolvePath(context.directory, input.path ?? ".");
2515
- const args = buildSearchArgs({
2430
+ const args = buildFindNodesArgs({
2516
2431
  path: target,
2517
2432
  pattern: input.pattern,
2433
+ context: input.context,
2434
+ selector: input.selector,
2435
+ rule: input.rule,
2436
+ limit: input.limit,
2518
2437
  language: input.language,
2519
2438
  gitScope: input.gitScope
2520
2439
  });
2521
- await authorizeSearch(context, target, input.pattern);
2440
+ await authorizeSearch(context, target, input.pattern ?? input.context ?? input.rule);
2441
+ if (input.rule?.startsWith("@")) {
2442
+ await authorizeRead(context, resolvePath(context.directory, input.rule.slice(1)));
2443
+ }
2444
+ if (input.rule === "-")
2445
+ throw new Error("rule stdin is unavailable in OpenCode tools; use inline KDL or @path");
2522
2446
  return runReadSeek(context, args);
2523
2447
  }),
2524
- readseek_def: readseekTool(openCodeToolDescription("def"), {
2525
- name: tool2.schema.string().describe(PARAM_DESCRIPTIONS.name),
2526
- path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2527
- language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2528
- gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2529
- }, "def", async (input, context) => {
2448
+ readseek_find_definitions: readseekTool(openCodeToolDescription("find_definitions"), {
2449
+ name: tool3.schema.string().describe(PARAM_DESCRIPTIONS.name),
2450
+ path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2451
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2452
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2453
+ }, "find_definitions", async (input, context) => {
2530
2454
  const target = resolvePath(context.directory, input.path ?? ".");
2531
- const args = buildDefArgs({
2455
+ const args = buildFindDefinitionsArgs({
2532
2456
  path: target,
2533
2457
  name: input.name,
2534
2458
  language: input.language,
@@ -2537,58 +2461,56 @@ function createReadSeekTools(imagePolicy) {
2537
2461
  await authorizeSearch(context, target, input.name);
2538
2462
  return runReadSeek(context, args);
2539
2463
  }),
2540
- readseek_refs: readseekTool(openCodeToolDescription("refs"), {
2541
- name: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
2542
- path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2543
- language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2544
- scope: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.scope),
2545
- line: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.line),
2546
- column: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
2547
- gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2548
- }, "refs", async (input, context) => {
2464
+ readseek_find_references: readseekTool(openCodeToolDescription("find_references"), {
2465
+ name: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
2466
+ path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2467
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2468
+ at: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
2469
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2470
+ }, "find_references", async (input, context) => {
2549
2471
  const target = resolvePath(context.directory, input.path ?? ".");
2550
2472
  const name = input.name;
2551
- const args = buildRefsArgs({
2473
+ const args = buildFindReferencesArgs({
2552
2474
  path: target,
2553
2475
  name,
2554
- scope: input.scope,
2555
- line: input.line,
2556
- column: input.column,
2476
+ at: input.at,
2557
2477
  language: input.language,
2558
2478
  gitScope: input.gitScope
2559
2479
  });
2560
2480
  await authorizeSearch(context, target, name ?? "*");
2561
2481
  return runReadSeek(context, args);
2562
2482
  }),
2563
- readseek_rename: readseekTool(openCodeToolDescription("rename"), {
2564
- path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2565
- line: tool2.schema.number().int().positive().describe(PARAM_DESCRIPTIONS.renameLine),
2566
- column: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
2567
- to: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
2568
- workspace: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.workspace),
2569
- apply: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue)
2570
- }, "rename", async (input, context) => {
2483
+ readseek_rename_symbol: readseekTool(openCodeToolDescription("rename_symbol"), {
2484
+ path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2485
+ at: tool3.schema.string().describe(PARAM_DESCRIPTIONS.at),
2486
+ to: tool3.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
2487
+ workspace: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.workspace),
2488
+ apply: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue),
2489
+ planHash: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.planHash),
2490
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2491
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2492
+ }, "rename_symbol", async (input, context) => {
2571
2493
  const filePath = resolvePath(context.directory, input.path);
2572
2494
  await authorizeRead(context, filePath);
2573
2495
  if (input.workspace) {
2574
- const column = input.column;
2575
- const identifyArgs = inspectArgs(filePath, "identity", {
2576
- selector: `line:${input.line}${column === undefined ? "" : `:${column}`}`
2577
- });
2578
- const envelope = await runReadSeek(context, identifyArgs);
2579
- const name = identifiedName(requireInspectFacet(envelope, "identity"));
2496
+ const envelope = await runReadSeek(context, contentArgs(filePath, { at: input.at }));
2497
+ const name = identifiedName(record(envelope).cursor);
2580
2498
  if (!name)
2581
2499
  throw new Error("readseek could not identify a binding at the rename cursor");
2582
2500
  await authorizeSearch(context, context.directory, name);
2583
2501
  }
2584
- const args = buildRenameArgs({
2502
+ const args = buildRenameSymbolArgs({
2585
2503
  path: filePath,
2586
2504
  to: input.to,
2587
- line: input.line,
2588
- column: input.column,
2589
- workspace: input.workspace ? context.directory : undefined
2505
+ at: input.at,
2506
+ workspace: input.workspace ? context.directory : undefined,
2507
+ language: input.language,
2508
+ gitScope: input.gitScope
2590
2509
  });
2591
2510
  const plan = await runReadSeek(context, args);
2511
+ if (input.planHash !== undefined && record(plan).plan_hash !== input.planHash) {
2512
+ throw new Error("readseek rename plan does not match planHash");
2513
+ }
2592
2514
  if (input.apply === false)
2593
2515
  return plan;
2594
2516
  const files = renameFiles(plan, context.directory);
@@ -2597,57 +2519,61 @@ function createReadSeekTools(imagePolicy) {
2597
2519
  throw new Error("readseek rename plan did not include a plan hash");
2598
2520
  }
2599
2521
  await authorizeEdit(context, files);
2600
- return runReadSeek(context, buildRenameArgs({
2522
+ return runReadSeek(context, buildRenameSymbolArgs({
2601
2523
  path: filePath,
2602
2524
  to: input.to,
2603
- line: input.line,
2604
- column: input.column,
2525
+ at: input.at,
2605
2526
  workspace: input.workspace ? context.directory : undefined,
2606
2527
  apply: true,
2607
- planHash
2528
+ planHash,
2529
+ language: input.language,
2530
+ gitScope: input.gitScope
2608
2531
  }), {
2609
2532
  cancelable: false,
2610
2533
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
2611
2534
  });
2612
2535
  }),
2613
- readseek_edit: readseekTool(openCodeToolDescription("edit"), {
2614
- path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2615
- edits: tool2.schema.array(tool2.schema.union([
2616
- tool2.schema.object({
2617
- set_line: tool2.schema.object({
2618
- anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.anchor),
2619
- new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextLine)
2536
+ readseek_edit_text: readseekTool(openCodeToolDescription("edit_text"), {
2537
+ path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2538
+ edits: tool3.schema.array(tool3.schema.union([
2539
+ tool3.schema.object({
2540
+ set_line: tool3.schema.object({
2541
+ anchor: tool3.schema.string().describe(PARAM_DESCRIPTIONS.anchor),
2542
+ new_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newTextLine)
2620
2543
  }).strict()
2621
2544
  }).strict(),
2622
- tool2.schema.object({
2623
- replace_lines: tool2.schema.object({
2624
- start_anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.startAnchor),
2625
- end_anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.endAnchor),
2626
- new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextRange)
2545
+ tool3.schema.object({
2546
+ replace_lines: tool3.schema.object({
2547
+ start_anchor: tool3.schema.string().describe(PARAM_DESCRIPTIONS.startAnchor),
2548
+ end_anchor: tool3.schema.string().describe(PARAM_DESCRIPTIONS.endAnchor),
2549
+ new_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newTextRange)
2627
2550
  }).strict()
2628
2551
  }).strict(),
2629
- tool2.schema.object({
2630
- insert_after: tool2.schema.object({
2631
- anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.insertAnchor),
2632
- new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextInsert)
2552
+ tool3.schema.object({
2553
+ insert_after: tool3.schema.object({
2554
+ anchor: tool3.schema.string().describe(PARAM_DESCRIPTIONS.insertAnchor),
2555
+ new_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newTextInsert)
2633
2556
  }).strict()
2634
2557
  }).strict(),
2635
- tool2.schema.object({
2636
- replace: tool2.schema.object({
2637
- old_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.oldText),
2638
- new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newText),
2639
- all: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.all)
2558
+ tool3.schema.object({
2559
+ replace: tool3.schema.object({
2560
+ old_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.oldText),
2561
+ new_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newText),
2562
+ all: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.all)
2640
2563
  }).strict()
2641
2564
  }).strict(),
2642
- tool2.schema.object({
2643
- replace_symbol: tool2.schema.object({
2644
- symbol: tool2.schema.string().describe(PARAM_DESCRIPTIONS.symbol),
2645
- new_body: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newBody)
2565
+ tool3.schema.object({
2566
+ replace_symbol: tool3.schema.object({
2567
+ symbol: tool3.schema.string().describe(PARAM_DESCRIPTIONS.symbol),
2568
+ new_body: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newBody)
2646
2569
  }).strict()
2647
2570
  }).strict()
2648
2571
  ])).min(1).describe(PARAM_DESCRIPTIONS.edits),
2649
- language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageSymbol)
2650
- }, "edit", async (input, context) => {
2572
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageSymbol),
2573
+ apply: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue),
2574
+ planHash: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.planHash),
2575
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2576
+ }, "edit_text", async (input, context) => {
2651
2577
  const filePath = resolvePath(context.directory, input.path);
2652
2578
  validateEdits(input.edits);
2653
2579
  return withFileMutationQueue(filePath, async () => {
@@ -2656,15 +2582,21 @@ function createReadSeekTools(imagePolicy) {
2656
2582
  await rejectSymlinkMutation(filePath);
2657
2583
  const edits = input.edits;
2658
2584
  const request = serializeEditPlan(edits);
2659
- const baseArgs = buildEditArgs({
2585
+ const baseArgs = buildEditTextArgs({
2660
2586
  path: filePath,
2661
2587
  language: input.language,
2588
+ gitScope: input.gitScope,
2662
2589
  requestFromStdin: true
2663
2590
  });
2664
2591
  const plan = record(await runReadSeek(context, baseArgs, { stdin: request }));
2665
2592
  if (typeof plan.before_content !== "string" || typeof plan.content !== "string" || typeof plan.plan_hash !== "string") {
2666
2593
  throw new Error("readseek edit plan is missing before_content, content, or plan_hash");
2667
2594
  }
2595
+ if (input.planHash !== undefined && plan.plan_hash !== input.planHash) {
2596
+ throw new Error("readseek edit plan does not match planHash");
2597
+ }
2598
+ if (input.apply === false)
2599
+ return plan;
2668
2600
  const before = plan.before_content;
2669
2601
  const after = plan.content;
2670
2602
  const previewStart = Math.max(1, firstChangedLine(before, after) - 3);
@@ -2674,9 +2606,10 @@ function createReadSeekTools(imagePolicy) {
2674
2606
  }
2675
2607
  await authorizeEdit(context, [filePath], simpleDiff(filePath, before, after), false);
2676
2608
  context.abort.throwIfAborted();
2677
- const applied = record(await runReadSeek(context, buildEditArgs({
2609
+ const applied = record(await runReadSeek(context, buildEditTextArgs({
2678
2610
  path: filePath,
2679
2611
  language: input.language,
2612
+ gitScope: input.gitScope,
2680
2613
  requestFromStdin: true,
2681
2614
  apply: true,
2682
2615
  planHash: plan.plan_hash
@@ -2691,10 +2624,10 @@ function createReadSeekTools(imagePolicy) {
2691
2624
  return postMutationPreview(context, previewArgs, filePath, true);
2692
2625
  });
2693
2626
  }),
2694
- readseek_write: readseekTool(openCodeToolDescription("write"), {
2695
- path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2696
- content: tool2.schema.string().describe(PARAM_DESCRIPTIONS.content)
2697
- }, "write", async (input, context) => {
2627
+ readseek_write_file: readseekTool(openCodeToolDescription("write_file"), {
2628
+ path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2629
+ content: tool3.schema.string().describe(PARAM_DESCRIPTIONS.content)
2630
+ }, "write_file", async (input, context) => {
2698
2631
  const filePath = resolvePath(context.directory, input.path);
2699
2632
  await authorizeExternal(context, filePath);
2700
2633
  return withFileMutationQueue(filePath, async () => {
@@ -2707,11 +2640,50 @@ function createReadSeekTools(imagePolicy) {
2707
2640
  await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
2708
2641
  context.abort.throwIfAborted();
2709
2642
  await writeText(filePath, before, content);
2710
- return postMutationPreview(context, inspectArgs(filePath, "content", { end: DEFAULT_READ_LIMIT }), filePath, !before.exists || before.content !== content);
2643
+ return postMutationPreview(context, contentArgs(filePath, { end: DEFAULT_READ_LIMIT }), filePath, !before.exists || before.content !== content);
2711
2644
  });
2712
2645
  })
2713
2646
  };
2714
2647
  }
2648
+ function createAccessTools(imagePolicy) {
2649
+ const pathSchema = { path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath) };
2650
+ const atSchema = { at: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.at) };
2651
+ const languageSchema = { language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language) };
2652
+ const levelSchema = { level: tool3.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.level) };
2653
+ const definitions = {
2654
+ show_content: { build: buildShowContentArgs, args: { ...pathSchema, ...atSchema, ...languageSchema, end: tool3.schema.number().int().positive().optional(), limit: tool3.schema.number().int().positive().optional(), noAnchors: tool3.schema.boolean().optional() } },
2655
+ show_structure: { build: buildShowStructureArgs, args: { ...pathSchema, ...atSchema, ...languageSchema, depth: tool3.schema.number().int().nonnegative().optional(), kind: tool3.schema.string().optional(), outline: tool3.schema.boolean().optional() } },
2656
+ show_metadata: { build: buildShowMetadataArgs, args: { ...pathSchema, ...languageSchema } },
2657
+ show_assets: { build: buildShowAssetsArgs, args: { ...pathSchema, ...atSchema } },
2658
+ check_syntax: { build: buildCheckSyntaxArgs, args: { ...pathSchema, ...languageSchema } },
2659
+ check_rules: { build: buildCheckRulesArgs, args: { path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath), rules: tool3.schema.string().min(1), gitScope: tool3.schema.enum(GIT_SCOPES).optional() } },
2660
+ generate_caption: { build: buildGenerateCaptionArgs, args: { ...pathSchema, ...atSchema, ...levelSchema, include: tool3.schema.array(tool3.schema.enum(["text", "objects"])).min(1).optional() } },
2661
+ detect_objects: { build: buildDetectObjectsArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } },
2662
+ recognize_text: { build: buildRecognizeTextArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } }
2663
+ };
2664
+ return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [
2665
+ `readseek_${name}`,
2666
+ readseekTool(openCodeToolDescription(name), definition.args, name, async (input, context) => {
2667
+ const filePath = resolvePath(context.directory, input.path ?? ".");
2668
+ const buildInput = { ...input, path: filePath, ...name === "show_structure" ? { depth: input.depth ?? DEFAULT_STRUCTURE_DEPTH } : {} };
2669
+ let args = definition.build(buildInput);
2670
+ await authorizeRead(context, filePath);
2671
+ if (name === "check_rules") {
2672
+ if (input.rules === "-")
2673
+ throw new Error("rules stdin is unavailable in OpenCode tools; use inline KDL or @path");
2674
+ await authorizeSearch(context, filePath, input.rules);
2675
+ if (input.rules.startsWith("@"))
2676
+ await authorizeRead(context, resolvePath(context.directory, input.rules.slice(1)));
2677
+ }
2678
+ const inference = ["generate_caption", "detect_objects", "recognize_text"].includes(name);
2679
+ if (name === "show_content" || name === "show_assets" || inference) {
2680
+ const readseekDir = await ensureReadSeekCache(context);
2681
+ args = definition.build({ ...buildInput, readseekDir });
2682
+ }
2683
+ return runReadSeek(context, args, { timeoutMs: inference ? DEFAULT_VISION_TIMEOUT_MS : DEFAULT_READSEEK_TIMEOUT_MS });
2684
+ }, name === "show_content" || name === "show_assets" ? imagePolicy : undefined)
2685
+ ]));
2686
+ }
2715
2687
 
2716
2688
  // v2.ts
2717
2689
  var GUIDANCE = "For structural code navigation, consult the readseek skill when ReadSeek tools are unavailable and the readseek CLI is installed.";
@@ -2724,6 +2696,29 @@ var SKILL = {
2724
2696
 
2725
2697
  When ReadSeek tools are unavailable, use the readseek CLI for structural navigation and anchored edits if it is installed and available on PATH. Run \`readseek --help\` to inspect supported commands before invoking them. Get fresh anchors before editing; re-read after external file changes.
2726
2698
 
2699
+ Commands use verb-object pairs, with the target path positional:
2700
+
2701
+ \`\`\`sh
2702
+ readseek show content file.ts --at line:10 --limit 40 --kdl
2703
+ readseek show structure file.ts --depth 1 --kdl
2704
+ readseek show metadata paper.pdf --kdl
2705
+ readseek show assets paper.pdf --at page:2 --kdl
2706
+ readseek check syntax file.ts --kdl
2707
+ readseek check rules . --rules @checks.kdl --kdl
2708
+ readseek find nodes src --pattern 'console.log($$$ARGS)' --kdl
2709
+ readseek find text src --text 'TODO' --kdl
2710
+ readseek find definitions src --name main --kdl
2711
+ readseek find references file.ts --at line:10:3 --kdl
2712
+ readseek edit text file.ts --edits @edits.kdl --kdl
2713
+ readseek rename symbol file.ts --at line:10:3 --to nextName --kdl
2714
+ \`\`\`
2715
+
2716
+ Native \`find text\` is literal, not regex. Reference selection requires exactly one of \`--name\` or \`--at\`; rename requires a line or hash cursor. Preview mutations first, then use \`--apply --plan-hash HASH\` with the returned plan hash. Edit requests are KDL: for example, \`set_line anchor="10:HASH" new_text="replacement"\`.
2717
+
2718
+ Syntax and rule checks return exit 0 when clean, 1 for findings, and 2 on failure. Exit 1 is not a transport failure. Syntax checks are parser checks, not type checking.
2719
+
2720
+ Content and assets return cached paths; assets auto-materialize. Read those paths directly. There is no extract command or asset destination option. Reading does not invoke inference. Request inference explicitly with \`generate caption\` (optionally \`--include text,objects\`), \`detect objects\`, or \`recognize text\`, using \`--level low|medium|high\` and an installed model; models are never downloaded automatically.
2721
+
2727
2722
  This V2 plugin supplies guidance only; it does not register readseek_* tools. If the CLI is unavailable, use OpenCode's built-in tools instead.`
2728
2723
  };
2729
2724
  var ReadSeekV2Plugin = {
@@ -2779,7 +2774,7 @@ var ReadSeekPlugin = async (_input, options) => {
2779
2774
  return;
2780
2775
  try {
2781
2776
  const result = JSON.parse(output.output);
2782
- if (input.tool === "readseek_rename") {
2777
+ if (input.tool === "readseek_rename_symbol") {
2783
2778
  anchors.planRename(input.sessionID, result);
2784
2779
  if (record(result).applied === true) {
2785
2780
  for (const filePath of renameFiles(result))
@@ -2789,7 +2784,7 @@ var ReadSeekPlugin = async (_input, options) => {
2789
2784
  const files = new Set;
2790
2785
  collectAnchoredFiles(result, files);
2791
2786
  for (const filePath of files)
2792
- anchors.mark(input.sessionID, path7.resolve(filePath));
2787
+ anchors.mark(input.sessionID, path8.resolve(filePath));
2793
2788
  } catch {}
2794
2789
  },
2795
2790
  "experimental.session.compacting": async (input, output) => {