opencode-readseek 0.10.7 → 0.11.1

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 +50 -5
  2. package/dist/index.js +1623 -1437
  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,51 +397,8 @@ 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);
577
- // ../readseek-api/src/edit-plan.ts
578
- import { Document, Node, format } from "@bgotink/kdl";
579
- function node(name, properties, children = []) {
580
- const result = Node.create(name);
581
- for (const [key, value] of Object.entries(properties))
582
- result.setProperty(key, value);
583
- if (children.length > 0)
584
- result.children = new Document(children);
585
- return result;
586
- }
587
- function editNode(edit) {
588
- if ("set_line" in edit) {
589
- const value = edit.set_line;
590
- return node("set-line", { anchor: value.anchor, text: value.new_text });
591
- }
592
- if ("replace_lines" in edit) {
593
- const value = edit.replace_lines;
594
- return node("replace-lines", { start: value.start_anchor, end: value.end_anchor, text: value.new_text });
595
- }
596
- if ("insert_after" in edit) {
597
- const value = edit.insert_after;
598
- return node("insert-after", { anchor: value.anchor, text: value.new_text });
599
- }
600
- if ("replace_symbol" in edit) {
601
- const value = edit.replace_symbol;
602
- return node("replace-symbol", { symbol: value.symbol, body: value.new_body });
603
- }
604
- const value = edit.replace;
605
- return node("replace", {
606
- old: value.old_text,
607
- new: value.new_text,
608
- ...value.all === undefined ? {} : { all: value.all }
609
- });
610
- }
611
- function serializeEditPlan(edits) {
612
- const validation = validateExactlyOneEditVariant(edits);
613
- if (!validation.ok)
614
- throw new Error(validation.error);
615
- return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
616
- }
617
400
  // ../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
- }
401
+ var DEFAULT_STRUCTURE_DEPTH = 1;
622
402
  function appendGitScopeArgs(args, input) {
623
403
  if (input.gitScope !== undefined)
624
404
  args.push("--git-scope", input.gitScope);
@@ -645,30 +425,6 @@ function appendApplyAndPlanHash(args, input) {
645
425
  pushOptionalString(args, "--plan-hash", input.planHash);
646
426
  return args;
647
427
  }
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
428
  function validatePositiveInteger(value, name) {
673
429
  if (!Number.isSafeInteger(value) || value < 1) {
674
430
  throw new Error(`${name} must be a positive integer`);
@@ -714,224 +470,840 @@ function parseSelector(text) {
714
470
  }
715
471
  return { kind, value };
716
472
  }
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");
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);
721
488
  }
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;
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) {
733
507
  if (input.end !== undefined)
734
508
  validatePositiveInteger(input.end, "end");
735
- if (input.limit !== undefined)
736
- validatePositiveInteger(input.limit, "limit");
737
- if (input.end !== undefined && input.limit !== undefined) {
509
+ if (input.end !== undefined && input.limit !== undefined)
738
510
  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);
511
+ const args = start("show", "content", input);
512
+ at2(args, input.at);
785
513
  pushOptionalNumber(args, "--end", input.end);
786
- pushOptionalNumber(args, "--limit", input.limit);
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);
787
524
  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");
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);
793
556
  return args;
794
557
  }
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;
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);
811
599
  }
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);
600
+ function buildFindTextArgs(input) {
601
+ const args = start("find", "text", input);
602
+ args.push("--text", input.text);
603
+ limit2(args, input.limit);
817
604
  appendGitScopeArgs(args, input);
818
- args.push("--kdl");
819
- return args;
605
+ return finish(args);
820
606
  }
821
- function buildDefArgs(input) {
822
- const args = ["def", input.name];
823
- pushOptionalString(args, "--path", input.path);
824
- pushOptionalString(args, "--language", input.language);
607
+ function buildFindDefinitionsArgs(input) {
608
+ const args = start("find", "definitions", input);
609
+ args.push("--name", input.name);
610
+ language(args, input);
825
611
  appendGitScopeArgs(args, input);
826
- args.push("--kdl");
827
- return args;
612
+ anchors(args, input);
613
+ return finish(args);
828
614
  }
829
- function buildRefsArgs(input) {
830
- const validated = validateRefsNameVsScope(input);
615
+ function buildFindReferencesArgs(input) {
616
+ const validated = validateReferencesSelection(input);
831
617
  if (!validated.ok)
832
618
  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);
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);
843
625
  appendGitScopeArgs(args, input);
844
- args.push("--kdl");
845
- return args;
626
+ anchors(args, input);
627
+ return finish(args);
846
628
  }
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);
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);
855
640
  if (input.requestFromStdin)
856
641
  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];
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);
862
650
  if (input.workspace !== undefined)
863
651
  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);
652
+ appendApplyAndPlanHash(args, { ...input, flagOrder: input.flagOrder ?? "plan-hash-first" });
653
+ language(args, input);
870
654
  appendGitScopeArgs(args, input);
871
- args.push("--kdl");
872
- return args;
873
- }
874
- // ../readseek-api/src/platform.ts
875
- import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
876
- import { createRequire } from "node:module";
877
- import { homedir } from "node:os";
878
- import path from "node:path";
879
- var READSEEK_BINARY_ENV = "READSEEK_BINARY";
880
- var READSEEK_PLATFORM_PACKAGES = {
881
- "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
882
- "linux-arm64": "@jarkkojs/readseek-linux-arm64",
883
- "linux-x64": "@jarkkojs/readseek-linux-x64",
884
- "win32-x64": "@jarkkojs/readseek-win32-x64"
885
- };
886
- var resolveFromPackage = createRequire(import.meta.url).resolve;
887
- function readSeekBinaryName(platform = process.platform) {
888
- if (platform === "linux")
889
- return "readseek.AppImage";
890
- return platform === "win32" ? "readseek.exe" : "readseek";
655
+ return finish(args);
891
656
  }
892
- function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
893
- return `${platform}-${arch}`;
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
+ });
894
698
  }
895
- function resolveReadSeekBinaryOverride(environment = process.env) {
896
- const binaryPath = environment[READSEEK_BINARY_ENV];
897
- if (binaryPath === undefined)
898
- return null;
899
- if (!path.isAbsolute(binaryPath)) {
900
- throw new Error(`${READSEEK_BINARY_ENV} must be an absolute path to the readseek executable`);
901
- }
902
- try {
903
- if (!statSync(binaryPath).isFile())
904
- throw new Error("not a regular file");
905
- accessSync(binaryPath, fsConstants.X_OK);
906
- } catch (error) {
907
- throw new Error(`${READSEEK_BINARY_ENV} does not point to an executable file: ${binaryPath}`, {
908
- cause: error
909
- });
910
- }
911
- return binaryPath;
699
+ function recursiveStructureNode() {
700
+ return Type.Recursive((self) => mapNodeShape(self));
912
701
  }
913
- function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, options = {}) {
914
- const resolve = typeof resolver === "function" ? resolver : resolver.resolve.bind(resolver);
915
- const platform = options.platform ?? process.platform;
916
- const arch = options.arch ?? process.arch;
917
- const key = readSeekPlatformKey(platform, arch);
918
- const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
919
- if (!platformPackage) {
920
- const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
921
- throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
922
- }
923
- const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
924
- let packageJsonPath;
925
- try {
926
- const metaPackageJson = resolve(`${metaPackage}/package.json`);
927
- const metaPackageDir = path.dirname(metaPackageJson);
928
- packageJsonPath = resolve(`${platformPackage}/package.json`, {
929
- paths: [metaPackageDir]
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
+
991
+ // ../readseek-api/src/edit-plan.ts
992
+ import { Document, Node, format } from "@bgotink/kdl";
993
+ function node(name, properties, children = []) {
994
+ const result = Node.create(name);
995
+ for (const [key, value] of Object.entries(properties))
996
+ result.setProperty(key, value);
997
+ if (children.length > 0)
998
+ result.children = new Document(children);
999
+ return result;
1000
+ }
1001
+ function editNode(edit) {
1002
+ if ("set_line" in edit) {
1003
+ const value = edit.set_line;
1004
+ return node("set-line", { anchor: value.anchor, text: value.new_text });
1005
+ }
1006
+ if ("replace_lines" in edit) {
1007
+ const value = edit.replace_lines;
1008
+ return node("replace-lines", { start: value.start_anchor, end: value.end_anchor, text: value.new_text });
1009
+ }
1010
+ if ("insert_after" in edit) {
1011
+ const value = edit.insert_after;
1012
+ return node("insert-after", { anchor: value.anchor, text: value.new_text });
1013
+ }
1014
+ if ("replace_symbol" in edit) {
1015
+ const value = edit.replace_symbol;
1016
+ return node("replace-symbol", { symbol: value.symbol, body: value.new_body });
1017
+ }
1018
+ const value = edit.replace;
1019
+ return node("replace", {
1020
+ old: value.old_text,
1021
+ new: value.new_text,
1022
+ ...value.all === undefined ? {} : { all: value.all }
1023
+ });
1024
+ }
1025
+ function serializeEditPlan(edits) {
1026
+ const validation = validateExactlyOneEditVariant(edits);
1027
+ if (!validation.ok)
1028
+ throw new Error(validation.error);
1029
+ return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
1030
+ }
1031
+ // ../readseek-api/src/show-presentation.ts
1032
+ function isShowTool(name) {
1033
+ return name === "show_content" || name === "show_structure" || name === "show_metadata" || name === "show_assets";
1034
+ }
1035
+ function record(value) {
1036
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
1037
+ }
1038
+ function records(value) {
1039
+ return Array.isArray(value) ? value.map(record) : [];
1040
+ }
1041
+ function text(value) {
1042
+ return typeof value === "string" ? value : "";
1043
+ }
1044
+ function count(value) {
1045
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
1046
+ }
1047
+ function safe(value) {
1048
+ return value.replace(/[\x00-\x09\x0b-\x1f\x7f-\x9f]/g, (char) => {
1049
+ if (char === "\t")
1050
+ return "\\t";
1051
+ if (char === "\r")
1052
+ return "\\r";
1053
+ return `\\x${char.charCodeAt(0).toString(16).padStart(2, "0")}`;
1054
+ });
1055
+ }
1056
+ function inline(value) {
1057
+ return safe(text(value)).replaceAll(`
1058
+ `, "\\n");
1059
+ }
1060
+ function plural(n, unit) {
1061
+ return `${n} ${unit}${n === 1 ? "" : "s"}`;
1062
+ }
1063
+ function fields(value, omit = []) {
1064
+ return Object.entries(value).flatMap(([key, item]) => {
1065
+ if (omit.includes(key) || item === undefined || item === null || typeof item === "object")
1066
+ return [];
1067
+ const label = key.replaceAll("_", " ");
1068
+ return [`${label[0]?.toUpperCase() ?? ""}${label.slice(1)}: ${safe(String(item))}`];
1069
+ }).join(`
1070
+ `);
1071
+ }
1072
+ function location(value) {
1073
+ const start = text(value.start_anchor) || (count(value.start_line) !== undefined ? `${value.start_line}${text(value.start_hash) ? `:${value.start_hash}` : ""}` : "");
1074
+ const end = text(value.end_anchor) || (count(value.end_line) !== undefined ? `${value.end_line}${text(value.end_hash) ? `:${value.end_hash}` : ""}` : "");
1075
+ return start ? ` ${inline(start)}${end && end !== start ? ` - ${inline(end)}` : ""}` : "";
1076
+ }
1077
+ function sourceStructure(structure) {
1078
+ const sections = [];
1079
+ if (structure.root) {
1080
+ const lines = [];
1081
+ const stack = [{ node: record(structure.root), depth: 0 }];
1082
+ while (stack.length > 0) {
1083
+ const { node, depth } = stack.pop();
1084
+ lines.push(`${" ".repeat(Math.min(depth, 12))}${inline(node.kind) || "node"}${text(node.name) ? ` ${inline(node.name)}` : ""}${location(node)}`);
1085
+ const children = records(node.children);
1086
+ for (let i = children.length - 1;i >= 0; i--)
1087
+ stack.push({ node: children[i], depth: depth + 1 });
1088
+ }
1089
+ sections.push(`Tree
1090
+ ${lines.join(`
1091
+ `)}`);
1092
+ }
1093
+ const symbols = records(structure.symbols);
1094
+ if (symbols.length > 0) {
1095
+ sections.push(`Symbols
1096
+ ${symbols.map((symbol) => `${inline(symbol.kind) || "symbol"} ${inline(symbol.qualified_name || symbol.name)}${location(symbol)}`).join(`
1097
+ `)}`);
1098
+ }
1099
+ return sections.join(`
1100
+
1101
+ `) || "No structure found in this selection.";
1102
+ }
1103
+ function documentNodes(nodes, content) {
1104
+ const depths = new Map;
1105
+ return nodes.map((node) => {
1106
+ const depth = Math.min((depths.get(text(node.parent_id)) ?? -1) + 1, 12);
1107
+ depths.set(text(node.id), depth);
1108
+ const indent = " ".repeat(depth);
1109
+ const label = inline(node.title) || inline(node.kind) || "node";
1110
+ const page = count(node.page);
1111
+ const selector = text(node.id) ? `node:${inline(node.id)}` : "";
1112
+ const position = [selector, page !== undefined ? `page:${page}` : ""].filter(Boolean).join(" · ");
1113
+ const heading = `${indent}${label}${position ? ` [${position}]` : ""}`;
1114
+ const body = text(node.text);
1115
+ return content && body ? `${heading}
1116
+ ${safe(body).split(`
1117
+ `).map((line) => indent + line).join(`
1118
+ `)}` : heading;
1119
+ }).join(content ? `
1120
+
1121
+ ` : `
1122
+ `);
1123
+ }
1124
+ function assetList(assets) {
1125
+ return assets.map((asset) => {
1126
+ const id = text(asset.id);
1127
+ const page = count(asset.page);
1128
+ const heading = [id ? `asset:${inline(id)}` : inline(asset.kind) || "image", inline(asset.mime), page !== undefined ? `page:${page}` : ""].filter(Boolean).join(" · ");
1129
+ const details = fields(asset, ["id", "kind", "mime", "page", "bbox", "content_identity"]);
1130
+ return `${heading}${details ? `
1131
+ ${details.split(`
1132
+ `).map((line) => ` ${line}`).join(`
1133
+ `)}` : ""}`;
1134
+ }).join(`
1135
+
1136
+ `);
1137
+ }
1138
+ function presentShowResult(tool, value, options = {}) {
1139
+ const output = record(value);
1140
+ const metadata = record(output.metadata);
1141
+ const content = record(output.content);
1142
+ const cursor = record(output.cursor);
1143
+ const structure = record(output.structure);
1144
+ const document = typeof output.format === "string" && (Array.isArray(output.nodes) || typeof output.source === "string");
1145
+ const path = inline(options.path || output.source || output.file || content.file || structure.file || metadata.file || cursor.file) || "(unknown path)";
1146
+ const summary = [];
1147
+ const result = { summary: "", body: "", notices: [], metadata: {} };
1148
+ if (tool === "show_metadata") {
1149
+ const data = Object.keys(metadata).length > 0 ? metadata : output;
1150
+ if (text(data.format || data.language || data.type))
1151
+ summary.push(inline(data.format || data.language || data.type));
1152
+ if (count(data.pages) !== undefined) {
1153
+ result.metadata.pages = data.pages;
1154
+ summary.push(plural(result.metadata.pages, "page"));
1155
+ }
1156
+ result.body = fields(data) || "No metadata available.";
1157
+ } else if (tool === "show_assets") {
1158
+ const assets = records(output.assets);
1159
+ result.metadata.assets = assets.length;
1160
+ summary.push(plural(assets.length, "asset"));
1161
+ result.body = assetList(assets) || "No assets in this selection.";
1162
+ } else if (document) {
1163
+ const nodes = records(output.nodes);
1164
+ const assets = records(output.assets);
1165
+ result.metadata.nodes = nodes.length;
1166
+ if (count(output.pages) !== undefined)
1167
+ result.metadata.pages = output.pages;
1168
+ summary.push(plural(nodes.length, "node"));
1169
+ if (result.metadata.pages !== undefined)
1170
+ summary.push(plural(result.metadata.pages, "page"));
1171
+ const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show_content")];
1172
+ if (assets.length > 0) {
1173
+ result.metadata.assets = assets.length;
1174
+ sections.push(`Assets
1175
+ ${assetList(assets)}`);
1176
+ }
1177
+ result.body = sections.filter(Boolean).join(`
1178
+
1179
+ `) || "No content in this selection.";
1180
+ } else if (tool === "show_structure") {
1181
+ const symbols = records(structure.symbols);
1182
+ result.metadata.symbols = symbols.length;
1183
+ summary.push(plural(symbols.length, "symbol"));
1184
+ result.body = sourceStructure(structure);
1185
+ } else if (content.kind === "image" || count(content.width) !== undefined && count(content.height) !== undefined || count(metadata.width) !== undefined && count(metadata.height) !== undefined || text(content.mime).startsWith("image/") || text(metadata.mime).startsWith("image/") || text(metadata.type).startsWith("image/")) {
1186
+ const description = Object.keys(content).length > 0 ? content : Object.keys(metadata).length > 0 ? metadata : output;
1187
+ if (count(description.width) !== undefined && count(description.height) !== undefined) {
1188
+ summary.push(`${description.width} × ${description.height}`);
1189
+ } else if (count(metadata.width) !== undefined && count(metadata.height) !== undefined) {
1190
+ summary.push(`${metadata.width} × ${metadata.height}`);
1191
+ }
1192
+ const mime = text(description.mime || metadata.mime || metadata.type);
1193
+ if (mime)
1194
+ summary.push(inline(mime));
1195
+ result.body = fields(description, ["hashlines"]) || fields(metadata) || "No content in this selection.";
1196
+ } else if (Array.isArray(content.hashlines) || Array.isArray(cursor.hashlines)) {
1197
+ const lines = records(content.hashlines ?? cursor.hashlines);
1198
+ const start = count(content.start_line) ?? count(lines[0]?.line);
1199
+ const end = count(content.end_line) ?? count(lines.at(-1)?.line);
1200
+ const total = count(content.line_count) ?? count(cursor.line_count);
1201
+ if (start !== undefined && end !== undefined && start <= end) {
1202
+ result.metadata.start_line = start;
1203
+ result.metadata.end_line = end;
1204
+ summary.push(`lines ${start}-${end}${total !== undefined ? ` of ${total}` : ""}`);
1205
+ } else
1206
+ summary.push(plural(lines.length, "line"));
1207
+ const lang = text(metadata.language || content.language);
1208
+ if (lang)
1209
+ summary.push(inline(lang));
1210
+ const rows = lines.map((line) => `${line.line}${options.noAnchors ? "" : `:${text(line.hash)}`} | ${safe(text(line.text)).replaceAll("\\n", "\\\\n")}`);
1211
+ const identifier = record(cursor.identifier);
1212
+ if (count(cursor.line) !== undefined)
1213
+ result.metadata.line = cursor.line;
1214
+ if (count(cursor.column) !== undefined)
1215
+ result.metadata.column = cursor.column;
1216
+ if (Object.keys(cursor).length > 0) {
1217
+ const symbol = record(cursor.symbol);
1218
+ const description = [text(identifier.text), text(symbol.qualified_name || symbol.name), text(symbol.kind)].filter(Boolean);
1219
+ rows.unshift(`Cursor: ${cursor.line ?? "?"}:${cursor.column ?? "?"}${description.length > 0 ? ` · ${[...new Set(description)].map(inline).join(" · ")}` : ""}`, "");
1220
+ }
1221
+ result.body = rows.join(`
1222
+ `) || "Empty source selection.";
1223
+ if (start !== undefined && start > 1)
1224
+ result.notices.push(`Earlier lines omitted. Read preceding content with at: line:1 and limit: ${start - 1}.`);
1225
+ if (end !== undefined && total !== undefined && end < total) {
1226
+ result.notices.push(`More lines available. Continue with at: line:${end + 1}.`);
1227
+ } else if (content.truncated === true && (end === undefined || total === undefined)) {
1228
+ result.notices.push(`Content is truncated.${end !== undefined ? ` Continue with at: line:${end + 1}.` : " Select a smaller range with at and limit."}`);
1229
+ }
1230
+ } else {
1231
+ const description = Object.keys(content).length > 0 ? content : output;
1232
+ result.body = fields(description) || "No content in this selection.";
1233
+ }
1234
+ if (tool === "show_structure")
1235
+ result.notices.push("Structure is limited to the requested depth, kind, and outline. Use a deeper depth or focus with at to inspect further.");
1236
+ if (text(output.warning))
1237
+ result.notices.push(safe(text(output.warning)));
1238
+ result.summary = summary.join(" · ") || "complete";
1239
+ return result;
1240
+ }
1241
+ function formatShowResult(presentation) {
1242
+ return [presentation.summary, presentation.body, ...presentation.notices.map((notice) => `Note: ${notice}`)].filter(Boolean).join(`
1243
+
1244
+ `);
1245
+ }
1246
+ // ../readseek-api/src/platform.ts
1247
+ import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
1248
+ import { createRequire } from "node:module";
1249
+ import { homedir } from "node:os";
1250
+ import path2 from "node:path";
1251
+ var READSEEK_BINARY_ENV = "READSEEK_BINARY";
1252
+ var READSEEK_PLATFORM_PACKAGES = {
1253
+ "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
1254
+ "linux-arm64": "@jarkkojs/readseek-linux-arm64",
1255
+ "linux-x64": "@jarkkojs/readseek-linux-x64",
1256
+ "win32-x64": "@jarkkojs/readseek-win32-x64"
1257
+ };
1258
+ var resolveFromPackage = createRequire(import.meta.url).resolve;
1259
+ function readSeekBinaryName(platform = process.platform) {
1260
+ if (platform === "linux")
1261
+ return "readseek.AppImage";
1262
+ return platform === "win32" ? "readseek.exe" : "readseek";
1263
+ }
1264
+ function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
1265
+ return `${platform}-${arch}`;
1266
+ }
1267
+ function resolveReadSeekBinaryOverride(environment = process.env) {
1268
+ const binaryPath = environment[READSEEK_BINARY_ENV];
1269
+ if (binaryPath === undefined)
1270
+ return null;
1271
+ if (!path2.isAbsolute(binaryPath)) {
1272
+ throw new Error(`${READSEEK_BINARY_ENV} must be an absolute path to the readseek executable`);
1273
+ }
1274
+ try {
1275
+ if (!statSync(binaryPath).isFile())
1276
+ throw new Error("not a regular file");
1277
+ accessSync(binaryPath, fsConstants.X_OK);
1278
+ } catch (error) {
1279
+ throw new Error(`${READSEEK_BINARY_ENV} does not point to an executable file: ${binaryPath}`, {
1280
+ cause: error
1281
+ });
1282
+ }
1283
+ return binaryPath;
1284
+ }
1285
+ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, options = {}) {
1286
+ const resolve = typeof resolver === "function" ? resolver : resolver.resolve.bind(resolver);
1287
+ const platform = options.platform ?? process.platform;
1288
+ const arch = options.arch ?? process.arch;
1289
+ const key = readSeekPlatformKey(platform, arch);
1290
+ const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
1291
+ if (!platformPackage) {
1292
+ const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
1293
+ throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
1294
+ }
1295
+ const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
1296
+ let packageJsonPath;
1297
+ try {
1298
+ const metaPackageJson = resolve(`${metaPackage}/package.json`);
1299
+ const metaPackageDir = path2.dirname(metaPackageJson);
1300
+ packageJsonPath = resolve(`${platformPackage}/package.json`, {
1301
+ paths: [metaPackageDir]
930
1302
  });
931
1303
  } catch {
932
1304
  packageJsonPath = resolve(`${platformPackage}/package.json`);
933
1305
  }
934
- const binaryPath = path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
1306
+ const binaryPath = path2.join(path2.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
935
1307
  accessSync(binaryPath, fsConstants.X_OK);
936
1308
  return binaryPath;
937
1309
  }
@@ -942,18 +1314,18 @@ function resolveReadSeekCacheDir(subPath, options = {}) {
942
1314
  const cwd = options.cwd ?? process.cwd();
943
1315
  const localDirName = options.localDirName ?? ".readseek";
944
1316
  if (localDirName && localDirName.length > 0) {
945
- const local = path.join(cwd, localDirName);
1317
+ const local = path2.join(cwd, localDirName);
946
1318
  if (existsSync(local))
947
1319
  return local;
948
1320
  }
949
1321
  let base;
950
1322
  if (platform === "win32") {
951
- base = env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
1323
+ base = env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local");
952
1324
  } else {
953
1325
  const xdg = env.XDG_CACHE_HOME?.trim();
954
- base = xdg && path.isAbsolute(xdg) ? xdg : path.join(home, ".cache");
1326
+ base = xdg && path2.isAbsolute(xdg) ? xdg : path2.join(home, ".cache");
955
1327
  }
956
- const pathModule = platform === "win32" ? path.win32 : path;
1328
+ const pathModule = platform === "win32" ? path2.win32 : path2;
957
1329
  if (subPath === undefined) {
958
1330
  return pathModule.join(base, "readseek");
959
1331
  }
@@ -961,10 +1333,10 @@ function resolveReadSeekCacheDir(subPath, options = {}) {
961
1333
  return pathModule.join(base, ...parts);
962
1334
  }
963
1335
  // ../readseek-api/src/path.ts
964
- import path2 from "node:path";
1336
+ import path3 from "node:path";
965
1337
  function isSubPath(parent, candidate) {
966
- const relative = path2.relative(parent, candidate);
967
- return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
1338
+ const relative = path3.relative(parent, candidate);
1339
+ return relative === "" || !relative.startsWith(`..${path3.sep}`) && relative !== ".." && !path3.isAbsolute(relative);
968
1340
  }
969
1341
  // ../readseek-api/src/error.ts
970
1342
  var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
@@ -1011,634 +1383,420 @@ function properties(node) {
1011
1383
  return Object.fromEntries(node.properties);
1012
1384
  }
1013
1385
  function children(node, name) {
1014
- return node.children.filter((child) => child.name === name);
1015
- }
1016
- function child(node, name) {
1017
- return node.children.find((candidate) => candidate.name === name);
1018
- }
1019
- function requireProperty(node, name) {
1020
- const value = node.properties.get(name);
1021
- if (value === undefined)
1022
- throw new Error(`invalid readseek KDL: ${node.name} is missing '${name}'`);
1023
- return value;
1024
- }
1025
- function line(node) {
1026
- const anchor = requireProperty(node, "anchor");
1027
- const content = requireProperty(node, "content");
1028
- if (typeof anchor !== "string" || typeof content !== "string") {
1029
- throw new Error("invalid readseek KDL line");
1030
- }
1031
- const match = /^(\d+):([0-9a-f]+)$/.exec(anchor);
1032
- if (!match)
1033
- throw new Error(`invalid readseek KDL line anchor: ${anchor}`);
1034
- return {
1035
- line: Number(match[1]),
1036
- hash: match[2],
1037
- text: content
1038
- };
1039
- }
1040
- function mapNode(node) {
1041
- return {
1042
- ...properties(node),
1043
- children: children(node, "node").map(mapNode)
1044
- };
1045
- }
1046
- function sourceContent(node) {
1047
- const result = properties(node);
1048
- result.hashlines = children(node, "line").map(line);
1049
- const objects = children(node, "object");
1050
- if (objects.length > 0) {
1051
- result.objects = objects.map((object) => ({
1052
- label: requireProperty(object, "label"),
1053
- bbox: [
1054
- requireProperty(object, "xmin"),
1055
- requireProperty(object, "ymin"),
1056
- requireProperty(object, "xmax"),
1057
- requireProperty(object, "ymax")
1058
- ]
1059
- }));
1060
- }
1061
- return result;
1062
- }
1063
- function sourceMap(node) {
1064
- const root = child(node, "node");
1065
- return {
1066
- ...properties(node),
1067
- ...root ? { root: mapNode(root) } : {},
1068
- symbols: children(node, "symbol").map(properties)
1069
- };
1070
- }
1071
- function diagnostics(node) {
1072
- return {
1073
- ...properties(node),
1074
- diagnostics: children(node, "diagnostic").map(properties)
1075
- };
1076
- }
1077
- function identity(node) {
1078
- const result = properties(node);
1079
- const identifier = child(node, "identifier");
1080
- const symbol = child(node, "symbol");
1081
- if (identifier)
1082
- result.identifier = properties(identifier);
1083
- if (symbol)
1084
- result.symbol = properties(symbol);
1085
- return result;
1086
- }
1087
- function sourceDocument(root) {
1088
- const result = {};
1089
- const metadata = child(root, "metadata");
1090
- const cache = child(root, "cache");
1091
- const content = child(root, "content");
1092
- const map = child(root, "map");
1093
- const diagnosticOutput = child(root, "diagnostics");
1094
- const identityOutput = child(root, "identity");
1095
- if (metadata)
1096
- result.metadata = properties(metadata);
1097
- if (cache)
1098
- result.cache = properties(cache);
1099
- if (content)
1100
- result.content = sourceContent(content);
1101
- if (map)
1102
- result.map = sourceMap(map);
1103
- if (diagnosticOutput)
1104
- result.diagnostics = diagnostics(diagnosticOutput);
1105
- if (identityOutput)
1106
- result.identity = identity(identityOutput);
1107
- return result;
1108
- }
1109
- function hit(node) {
1110
- return {
1111
- ...properties(node),
1112
- hashlines: children(node, "line").map(line),
1113
- captures: children(node, "capture").map((capture) => ({
1114
- ...properties(capture),
1115
- hashlines: children(capture, "line").map(line)
1116
- }))
1117
- };
1118
- }
1119
- function search(root) {
1120
- return {
1121
- ...properties(root),
1122
- results: children(root, "match").map((match) => ({
1123
- ...properties(match),
1124
- matches: children(match, "hit").map(hit)
1125
- })),
1126
- documents: children(root, "document").map((documentNode) => ({
1127
- ...properties(documentNode),
1128
- hits: children(documentNode, "hit").map(properties)
1129
- }))
1130
- };
1131
- }
1132
- function references(root) {
1133
- const parsed = children(root, "reference").map((reference) => {
1134
- const sourceLine = child(reference, "line");
1135
- if (!sourceLine)
1136
- throw new Error("invalid readseek KDL reference: missing line");
1137
- const parsedLine = line(sourceLine);
1138
- const symbol = child(reference, "symbol");
1139
- return {
1140
- ...properties(reference),
1141
- line_hash: parsedLine.hash,
1142
- text: parsedLine.text,
1143
- ...symbol ? { symbol: properties(symbol) } : {}
1144
- };
1145
- });
1146
- return {
1147
- references: parsed,
1148
- locations: parsed.map((reference) => {
1149
- const { symbol, ...location } = reference;
1150
- return symbol && typeof symbol === "object" ? { ...location, ...symbol } : location;
1151
- })
1152
- };
1153
- }
1154
- function definitions(root) {
1155
- return {
1156
- locations: children(root, "definition").map((definition) => {
1157
- const symbol = child(definition, "symbol");
1158
- const sourceLine = child(definition, "line");
1159
- if (!symbol || !sourceLine) {
1160
- throw new Error("invalid readseek KDL definition: missing symbol or line");
1161
- }
1162
- const symbolProperties = properties(symbol);
1163
- const parsedLine = line(sourceLine);
1164
- const definitionColumn = definition.properties.get("column");
1165
- const nameByte = symbol.properties.get("name_byte");
1166
- const startByte = symbol.properties.get("start_byte");
1167
- let column = 1;
1168
- if (typeof definitionColumn === "number") {
1169
- column = definitionColumn;
1170
- } else if (typeof nameByte === "number" && typeof startByte === "number") {
1171
- column = nameByte - startByte + 1;
1172
- }
1173
- return {
1174
- file: requireProperty(definition, "file"),
1175
- line: parsedLine.line,
1176
- column,
1177
- line_hash: parsedLine.hash,
1178
- text: parsedLine.text,
1179
- kind: symbolProperties.kind,
1180
- name: symbolProperties.name,
1181
- qualified_name: symbolProperties.qualified_name
1182
- };
1183
- })
1184
- };
1185
- }
1186
- function edit(root) {
1187
- const result = properties(root);
1188
- for (const name of ["before_content", "content"]) {
1189
- const contentNode = child(root, name);
1190
- if (contentNode)
1191
- result[name] = requireProperty(contentNode, "content");
1192
- }
1193
- return result;
1194
- }
1195
- function renameEdit(node) {
1196
- const sourceLine = child(node, "line");
1197
- if (!sourceLine)
1198
- throw new Error("invalid readseek KDL rename edit: missing line");
1199
- return {
1200
- ...properties(node),
1201
- text: requireProperty(sourceLine, "content")
1202
- };
1203
- }
1204
- function renameFile(node) {
1205
- return {
1206
- ...properties(node),
1207
- edits: children(node, "edit").map(renameEdit),
1208
- conflicts: children(node, "conflict").map(properties)
1209
- };
1210
- }
1211
- function rename(root) {
1212
- return {
1213
- ...renameFile(root),
1214
- others: children(root, "other").map(renameFile)
1215
- };
1216
- }
1217
- function genericNode(node) {
1218
- const result = properties(node);
1219
- const textNode = child(node, "text");
1220
- const bbox = child(node, "bbox");
1221
- if (textNode)
1222
- result.text = requireProperty(textNode, "content");
1223
- if (bbox)
1224
- result.bbox = properties(bbox);
1225
- const objects = children(node, "object");
1226
- if (objects.length > 0)
1227
- result.objects = objects.map(genericNode);
1228
- return result;
1229
- }
1230
- function pdfDocument(root) {
1231
- return {
1232
- ...properties(root),
1233
- nodes: children(root, "node").map(genericNode),
1234
- assets: children(root, "asset").map(genericNode),
1235
- vision: children(root, "vision").map(genericNode)
1236
- };
1237
- }
1238
- function parseReadSeekKdl(text) {
1239
- const root = parseKdlDocument(text);
1240
- switch (root.name) {
1241
- case "document":
1242
- return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1243
- case "search":
1244
- return search(root);
1245
- case "check":
1246
- return diagnostics(root);
1247
- case "refs":
1248
- return references(root);
1249
- case "def":
1250
- return definitions(root);
1251
- case "edit":
1252
- return edit(root);
1253
- case "edit-batch":
1254
- return { ...properties(root), files: children(root, "file").map(edit) };
1255
- case "rename":
1256
- return rename(root);
1257
- default:
1258
- throw new Error(`unsupported readseek KDL root: ${root.name}`);
1259
- }
1386
+ return node.children.filter((child) => child.name === name);
1260
1387
  }
1261
- // ../readseek-api/src/process.ts
1262
- var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
1263
- var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
1264
- function readSeekProcessEnvironment() {
1265
- return process.platform === "linux" ? { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } : process.env;
1388
+ function child(node, name) {
1389
+ return node.children.find((candidate) => candidate.name === name);
1266
1390
  }
1267
- function resolveProcessLimits(options = {}) {
1391
+ function requireProperty(node, name) {
1392
+ const value = node.properties.get(name);
1393
+ if (value === undefined)
1394
+ throw new Error(`invalid readseek KDL: ${node.name} is missing '${name}'`);
1395
+ return value;
1396
+ }
1397
+ function line(node) {
1398
+ const anchor = requireProperty(node, "anchor");
1399
+ const content = requireProperty(node, "content");
1400
+ if (typeof anchor !== "string" || typeof content !== "string") {
1401
+ throw new Error("invalid readseek KDL line");
1402
+ }
1403
+ const match = /^(\d+):([0-9a-f]+)$/.exec(anchor);
1404
+ if (!match)
1405
+ throw new Error(`invalid readseek KDL line anchor: ${anchor}`);
1268
1406
  return {
1269
- timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
1270
- maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
1271
- cancelable: options.cancelable !== false
1407
+ line: Number(match[1]),
1408
+ hash: match[2],
1409
+ text: content
1272
1410
  };
1273
1411
  }
1274
- function readSeekCancellationError(signal) {
1275
- const reason = signal?.reason;
1276
- return reason instanceof Error && reason.name !== "AbortError" ? reason : new Error("readseek cancelled");
1412
+ function mapNode(node) {
1413
+ return {
1414
+ ...properties(node),
1415
+ children: children(node, "node").map(mapNode)
1416
+ };
1277
1417
  }
1278
- function failureFromProcessResult(result, options = {}) {
1279
- if (options.overflow) {
1280
- return new Error(`readseek ${options.overflow} exceeded the ${options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES}-byte output limit`);
1281
- }
1282
- if (options.timedOut) {
1283
- return new Error(`readseek timed out after ${options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS} ms`);
1418
+ function sourceContent(node) {
1419
+ const result = properties(node);
1420
+ result.hashlines = children(node, "line").map(line);
1421
+ const objects = children(node, "object");
1422
+ if (objects.length > 0) {
1423
+ result.objects = objects.map((object) => ({
1424
+ label: requireProperty(object, "label"),
1425
+ bbox: [
1426
+ requireProperty(object, "xmin"),
1427
+ requireProperty(object, "ymin"),
1428
+ requireProperty(object, "xmax"),
1429
+ requireProperty(object, "ymax")
1430
+ ]
1431
+ }));
1284
1432
  }
1285
- const cleaned = cleanReadSeekErrorText(result.stderr);
1286
- if (cleaned)
1287
- return new Error(cleaned);
1288
- if (result.signal)
1289
- return new Error(`readseek killed by signal ${result.signal}`);
1290
- return new Error(`readseek exited with status ${result.exitCode}`);
1433
+ return result;
1291
1434
  }
1292
- function ensureSuccessfulStdout(result) {
1293
- if (result.exitCode === 0)
1294
- return result.stdout;
1295
- throw failureFromProcessResult(result);
1435
+ function sourceMap(node) {
1436
+ const root = child(node, "node");
1437
+ return {
1438
+ ...properties(node),
1439
+ ...root ? { root: mapNode(root) } : {},
1440
+ symbols: children(node, "symbol").map(properties)
1441
+ };
1296
1442
  }
1297
- function parseSuccessfulKdl(result) {
1298
- return parseReadSeekKdl(ensureSuccessfulStdout(result));
1443
+ function diagnostics(node) {
1444
+ return {
1445
+ ...properties(node),
1446
+ diagnostics: children(node, "diagnostic").map(properties)
1447
+ };
1299
1448
  }
1300
- // ../readseek-api/src/process-bun.ts
1301
- function getBunSpawn() {
1302
- const bunGlobal = globalThis;
1303
- const spawn = bunGlobal.Bun?.spawn;
1304
- if (typeof spawn !== "function") {
1305
- throw new Error("Bun.spawn is unavailable in this runtime");
1306
- }
1307
- return spawn;
1449
+ function identity(node) {
1450
+ const result = properties(node);
1451
+ const identifier = child(node, "identifier");
1452
+ const symbol = child(node, "symbol");
1453
+ if (identifier)
1454
+ result.identifier = properties(identifier);
1455
+ if (symbol)
1456
+ result.symbol = properties(symbol);
1457
+ return result;
1308
1458
  }
1309
- async function readBoundedText(stream, streamName, maxOutputBytes, onOverflow) {
1310
- const reader = stream.getReader();
1311
- const chunks = [];
1312
- let totalBytes = 0;
1313
- try {
1314
- while (true) {
1315
- const { done, value } = await reader.read();
1316
- if (done)
1317
- break;
1318
- totalBytes += value.byteLength;
1319
- if (totalBytes > maxOutputBytes) {
1320
- onOverflow();
1321
- await reader.cancel().catch(() => {
1322
- return;
1323
- });
1324
- throw failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: streamName, maxOutputBytes });
1325
- }
1326
- chunks.push(value);
1327
- }
1328
- } finally {
1329
- reader.releaseLock();
1330
- }
1331
- const bytes = new Uint8Array(totalBytes);
1332
- let offset = 0;
1333
- for (const chunk of chunks) {
1334
- bytes.set(chunk, offset);
1335
- offset += chunk.byteLength;
1336
- }
1337
- return new TextDecoder().decode(bytes);
1459
+ function sourceDocument(root) {
1460
+ const result = {};
1461
+ const metadata = child(root, "metadata");
1462
+ const cache = child(root, "cache");
1463
+ const content = child(root, "content");
1464
+ const structure = child(root, "structure");
1465
+ const diagnosticOutput = child(root, "diagnostics");
1466
+ const identityOutput = child(root, "cursor");
1467
+ if (metadata)
1468
+ result.metadata = properties(metadata);
1469
+ if (!metadata && !content && !structure && !diagnosticOutput && !identityOutput && root.properties.has("file"))
1470
+ result.file = root.properties.get("file");
1471
+ const assets = children(root, "asset");
1472
+ if (assets.length > 0)
1473
+ result.assets = assets.map(genericNode);
1474
+ if (cache)
1475
+ result.cache = properties(cache);
1476
+ if (content)
1477
+ result.content = sourceContent(content);
1478
+ if (structure)
1479
+ result.structure = sourceMap(structure);
1480
+ if (diagnosticOutput)
1481
+ result.diagnostics = diagnostics(diagnosticOutput);
1482
+ if (identityOutput)
1483
+ result.cursor = identity(identityOutput);
1484
+ return result;
1338
1485
  }
1339
- async function runReadSeekBun(binaryPath, args, options = {}) {
1340
- const limits = resolveProcessLimits(options);
1341
- if (limits.cancelable && options.signal?.aborted) {
1342
- throw readSeekCancellationError(options.signal);
1343
- }
1344
- const spawn = getBunSpawn();
1345
- const stdin = options.stdin === undefined ? undefined : new TextEncoder().encode(options.stdin);
1346
- const spawnOptions = {
1347
- cwd: options.cwd,
1348
- env: readSeekProcessEnvironment(),
1349
- stderr: "pipe",
1350
- stdin,
1351
- stdout: "pipe"
1352
- };
1353
- const child = spawn([binaryPath, ...args], limits.cancelable && options.signal ? { ...spawnOptions, killSignal: "SIGKILL", signal: options.signal } : spawnOptions);
1354
- const killForOverflow = () => {
1355
- try {
1356
- child.kill("SIGKILL");
1357
- } catch {}
1486
+ function hit(node) {
1487
+ return {
1488
+ ...properties(node),
1489
+ hashlines: children(node, "line").map(line),
1490
+ captures: children(node, "capture").map((capture) => ({
1491
+ ...properties(capture),
1492
+ hashlines: children(capture, "line").map(line)
1493
+ }))
1358
1494
  };
1359
- let timeout;
1360
- const timeoutResult = new Promise((_resolve, reject) => {
1361
- timeout = setTimeout(() => {
1362
- try {
1363
- child.kill("SIGKILL");
1364
- } catch {}
1365
- reject(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
1366
- }, limits.timeoutMs);
1367
- timeout.unref?.();
1368
- });
1369
- const pending = Promise.all([
1370
- readBoundedText(child.stdout, "stdout", limits.maxOutputBytes, killForOverflow),
1371
- readBoundedText(child.stderr, "stderr", limits.maxOutputBytes, killForOverflow),
1372
- child.exited
1373
- ]);
1374
- try {
1375
- const [stdout, stderr, exitCode] = await Promise.race([pending, timeoutResult]);
1376
- if (limits.cancelable && options.signal?.aborted)
1377
- throw readSeekCancellationError(options.signal);
1378
- return { stdout, stderr, exitCode, signal: null };
1379
- } finally {
1380
- if (timeout !== undefined)
1381
- clearTimeout(timeout);
1382
- pending.catch(() => {
1383
- return;
1384
- });
1385
- }
1386
- }
1387
- async function runReadSeekBunText(binaryPath, args, options = {}) {
1388
- return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options));
1389
1495
  }
1390
- async function runReadSeekBunKdl(binaryPath, args, options = {}) {
1391
- return parseSuccessfulKdl(await runReadSeekBun(binaryPath, args, options));
1496
+ function search(root) {
1497
+ return {
1498
+ ...properties(root),
1499
+ results: children(root, "match").map((match) => ({
1500
+ ...properties(match),
1501
+ matches: children(match, "hit").map(hit)
1502
+ })),
1503
+ documents: children(root, "document").map((documentNode) => ({
1504
+ ...properties(documentNode),
1505
+ hits: children(documentNode, "hit").map(properties)
1506
+ }))
1507
+ };
1392
1508
  }
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)
1509
+ function references(root) {
1510
+ const parsed = children(root, "reference").map((reference) => {
1511
+ const sourceLine = child(reference, "line");
1512
+ if (!sourceLine)
1513
+ throw new Error("invalid readseek KDL reference: missing line");
1514
+ const parsedLine = line(sourceLine);
1515
+ const symbol = child(reference, "symbol");
1516
+ return {
1517
+ ...properties(reference),
1518
+ line_hash: parsedLine.hash,
1519
+ text: parsedLine.text,
1520
+ ...symbol ? { symbol: properties(symbol) } : {}
1521
+ };
1432
1522
  });
1523
+ return {
1524
+ references: parsed,
1525
+ locations: parsed.map((reference) => {
1526
+ const { symbol, ...location } = reference;
1527
+ return symbol && typeof symbol === "object" ? { ...location, ...symbol } : location;
1528
+ })
1529
+ };
1433
1530
  }
1434
- function recursiveMapNode() {
1435
- return Type.Recursive((self) => mapNodeShape(self));
1531
+ function definitions(root) {
1532
+ return {
1533
+ locations: children(root, "definition").map((definition) => {
1534
+ const symbol = child(definition, "symbol");
1535
+ const sourceLine = child(definition, "line");
1536
+ if (!symbol || !sourceLine) {
1537
+ throw new Error("invalid readseek KDL definition: missing symbol or line");
1538
+ }
1539
+ const symbolProperties = properties(symbol);
1540
+ const parsedLine = line(sourceLine);
1541
+ const definitionColumn = definition.properties.get("column");
1542
+ const nameByte = symbol.properties.get("name_byte");
1543
+ const startByte = symbol.properties.get("start_byte");
1544
+ let column = 1;
1545
+ if (typeof definitionColumn === "number") {
1546
+ column = definitionColumn;
1547
+ } else if (typeof nameByte === "number" && typeof startByte === "number") {
1548
+ column = nameByte - startByte + 1;
1549
+ }
1550
+ return {
1551
+ file: requireProperty(definition, "file"),
1552
+ line: parsedLine.line,
1553
+ column,
1554
+ line_hash: parsedLine.hash,
1555
+ text: parsedLine.text,
1556
+ kind: symbolProperties.kind,
1557
+ name: symbolProperties.name,
1558
+ qualified_name: symbolProperties.qualified_name
1559
+ };
1560
+ })
1561
+ };
1562
+ }
1563
+ function edit(root) {
1564
+ const result = properties(root);
1565
+ for (const name of ["before_content", "content"]) {
1566
+ const contentNode = child(root, name);
1567
+ if (contentNode)
1568
+ result[name] = requireProperty(contentNode, "content");
1569
+ }
1570
+ return result;
1571
+ }
1572
+ function renameEdit(node) {
1573
+ const sourceLine = child(node, "line");
1574
+ if (!sourceLine)
1575
+ throw new Error("invalid readseek KDL rename edit: missing line");
1576
+ return {
1577
+ ...properties(node),
1578
+ text: requireProperty(sourceLine, "content")
1579
+ };
1580
+ }
1581
+ function renameFile(node) {
1582
+ return {
1583
+ ...properties(node),
1584
+ edits: children(node, "edit").map(renameEdit),
1585
+ conflicts: children(node, "conflict").map(properties)
1586
+ };
1587
+ }
1588
+ function rename(root) {
1589
+ return {
1590
+ ...renameFile(root),
1591
+ others: children(root, "other").map(renameFile)
1592
+ };
1593
+ }
1594
+ function genericNode(node) {
1595
+ const result = properties(node);
1596
+ const textNode = child(node, "text");
1597
+ const bbox = child(node, "bbox");
1598
+ if (textNode)
1599
+ result.text = requireProperty(textNode, "content");
1600
+ if (bbox)
1601
+ result.bbox = properties(bbox);
1602
+ const objects = children(node, "object");
1603
+ if (objects.length > 0)
1604
+ result.objects = objects.map((object) => ({
1605
+ label: requireProperty(object, "label"),
1606
+ bbox: [requireProperty(object, "x0"), requireProperty(object, "y0"), requireProperty(object, "x1"), requireProperty(object, "y1")]
1607
+ }));
1608
+ return result;
1609
+ }
1610
+ function pdfDocument(root) {
1611
+ return {
1612
+ ...properties(root),
1613
+ nodes: children(root, "node").map(genericNode),
1614
+ assets: children(root, "asset").map(genericNode),
1615
+ vision: children(root, "vision").map(genericNode)
1616
+ };
1617
+ }
1618
+ function parseReadSeekKdl(text) {
1619
+ const root = parseKdlDocument(text);
1620
+ switch (root.name) {
1621
+ case "document":
1622
+ return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1623
+ case "search":
1624
+ return search(root);
1625
+ case "check":
1626
+ return diagnostics(root);
1627
+ case "refs":
1628
+ return references(root);
1629
+ case "def":
1630
+ return definitions(root);
1631
+ case "edit":
1632
+ return edit(root);
1633
+ case "edit-batch":
1634
+ return { ...properties(root), files: children(root, "file").map(edit) };
1635
+ case "rename":
1636
+ return rename(root);
1637
+ default:
1638
+ throw new Error(`unsupported readseek KDL root: ${root.name}`);
1639
+ }
1640
+ }
1641
+ // ../readseek-api/src/process.ts
1642
+ var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
1643
+ var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
1644
+ function readSeekProcessEnvironment() {
1645
+ return process.platform === "linux" ? { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } : process.env;
1646
+ }
1647
+ function resolveProcessLimits(options = {}) {
1648
+ return {
1649
+ timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
1650
+ maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
1651
+ cancelable: options.cancelable !== false
1652
+ };
1653
+ }
1654
+ function readSeekCancellationError(signal) {
1655
+ const reason = signal?.reason;
1656
+ return reason instanceof Error && reason.name !== "AbortError" ? reason : new Error("readseek cancelled");
1657
+ }
1658
+ function failureFromProcessResult(result, options = {}) {
1659
+ if (options.overflow) {
1660
+ return new Error(`readseek ${options.overflow} exceeded the ${options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES}-byte output limit`);
1661
+ }
1662
+ if (options.timedOut) {
1663
+ return new Error(`readseek timed out after ${options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS} ms`);
1664
+ }
1665
+ const cleaned = cleanReadSeekErrorText(result.stderr);
1666
+ if (cleaned)
1667
+ return new Error(cleaned);
1668
+ if (result.signal)
1669
+ return new Error(`readseek killed by signal ${result.signal}`);
1670
+ return new Error(`readseek exited with status ${result.exitCode}`);
1671
+ }
1672
+ function acceptsReadSeekExitCode(args, exitCode) {
1673
+ if (exitCode === 0)
1674
+ return true;
1675
+ if (exitCode !== 1)
1676
+ return false;
1677
+ let index = 0;
1678
+ const valued = new Set(["-C", "--directory", "--output", "--readseek-dir"]);
1679
+ while (index < args.length) {
1680
+ const arg = args[index];
1681
+ if (valued.has(arg)) {
1682
+ index += 2;
1683
+ continue;
1684
+ }
1685
+ if ([...valued].some((flag) => arg.startsWith(`${flag}=`)) || arg === "--kdl") {
1686
+ index++;
1687
+ continue;
1688
+ }
1689
+ return arg === "find" || arg === "check";
1690
+ }
1691
+ return false;
1692
+ }
1693
+ function ensureSuccessfulStdout(result, args = []) {
1694
+ if (!result.signal && acceptsReadSeekExitCode(args, result.exitCode))
1695
+ return result.stdout;
1696
+ throw failureFromProcessResult(result);
1697
+ }
1698
+ function parseSuccessfulKdl(result, args = []) {
1699
+ return parseReadSeekKdl(ensureSuccessfulStdout(result, args));
1700
+ }
1701
+ // ../readseek-api/src/process-bun.ts
1702
+ function getBunSpawn() {
1703
+ const bunGlobal = globalThis;
1704
+ const spawn = bunGlobal.Bun?.spawn;
1705
+ if (typeof spawn !== "function") {
1706
+ throw new Error("Bun.spawn is unavailable in this runtime");
1707
+ }
1708
+ return spawn;
1709
+ }
1710
+ async function readBoundedText(stream, streamName, maxOutputBytes, onOverflow) {
1711
+ const reader = stream.getReader();
1712
+ const chunks = [];
1713
+ let totalBytes = 0;
1714
+ try {
1715
+ while (true) {
1716
+ const { done, value } = await reader.read();
1717
+ if (done)
1718
+ break;
1719
+ totalBytes += value.byteLength;
1720
+ if (totalBytes > maxOutputBytes) {
1721
+ onOverflow();
1722
+ await reader.cancel().catch(() => {
1723
+ return;
1724
+ });
1725
+ throw failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: streamName, maxOutputBytes });
1726
+ }
1727
+ chunks.push(value);
1728
+ }
1729
+ } finally {
1730
+ reader.releaseLock();
1731
+ }
1732
+ const bytes = new Uint8Array(totalBytes);
1733
+ let offset = 0;
1734
+ for (const chunk of chunks) {
1735
+ bytes.set(chunk, offset);
1736
+ offset += chunk.byteLength;
1737
+ }
1738
+ return new TextDecoder().decode(bytes);
1739
+ }
1740
+ async function runReadSeekBun(binaryPath, args, options = {}) {
1741
+ const limits = resolveProcessLimits(options);
1742
+ if (limits.cancelable && options.signal?.aborted) {
1743
+ throw readSeekCancellationError(options.signal);
1744
+ }
1745
+ const spawn = getBunSpawn();
1746
+ const stdin = options.stdin === undefined ? undefined : new TextEncoder().encode(options.stdin);
1747
+ const spawnOptions = {
1748
+ cwd: options.cwd,
1749
+ env: readSeekProcessEnvironment(),
1750
+ stderr: "pipe",
1751
+ stdin,
1752
+ stdout: "pipe"
1753
+ };
1754
+ const child = spawn([binaryPath, ...args], limits.cancelable && options.signal ? { ...spawnOptions, killSignal: "SIGKILL", signal: options.signal } : spawnOptions);
1755
+ const killForOverflow = () => {
1756
+ try {
1757
+ child.kill("SIGKILL");
1758
+ } catch {}
1759
+ };
1760
+ let timeout;
1761
+ const timeoutResult = new Promise((_resolve, reject) => {
1762
+ timeout = setTimeout(() => {
1763
+ try {
1764
+ child.kill("SIGKILL");
1765
+ } catch {}
1766
+ reject(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
1767
+ }, limits.timeoutMs);
1768
+ timeout.unref?.();
1769
+ });
1770
+ const pending = Promise.all([
1771
+ readBoundedText(child.stdout, "stdout", limits.maxOutputBytes, killForOverflow),
1772
+ readBoundedText(child.stderr, "stderr", limits.maxOutputBytes, killForOverflow),
1773
+ child.exited
1774
+ ]);
1775
+ try {
1776
+ const [stdout, stderr, exitCode] = await Promise.race([pending, timeoutResult]);
1777
+ if (limits.cancelable && options.signal?.aborted)
1778
+ throw readSeekCancellationError(options.signal);
1779
+ return { stdout, stderr, exitCode, signal: null };
1780
+ } finally {
1781
+ if (timeout !== undefined)
1782
+ clearTimeout(timeout);
1783
+ pending.catch(() => {
1784
+ return;
1785
+ });
1786
+ }
1787
+ }
1788
+ async function runReadSeekBunText(binaryPath, args, options = {}) {
1789
+ return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options), args);
1790
+ }
1791
+ async function runReadSeekBunKdl(binaryPath, args, options = {}) {
1792
+ return parseSuccessfulKdl(await runReadSeekBun(binaryPath, args, options), args);
1436
1793
  }
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
1794
  // src/util.ts
1637
- import path3 from "node:path";
1795
+ import path4 from "node:path";
1638
1796
  function render(value) {
1639
1797
  return JSON.stringify(value, null, 2);
1640
1798
  }
1641
- function record(value) {
1799
+ function record2(value) {
1642
1800
  return value && typeof value === "object" ? value : {};
1643
1801
  }
1644
1802
  function items(value) {
@@ -1663,21 +1821,21 @@ function collectAnchoredFiles(value, files) {
1663
1821
  return;
1664
1822
  const output = value;
1665
1823
  const hashlines = output.hashlines;
1666
- if (typeof output.file === "string" && Array.isArray(hashlines) && hashlines.some((line) => typeof record(line).line === "number" && typeof record(line).hash === "string"))
1824
+ if (typeof output.file === "string" && Array.isArray(hashlines) && hashlines.some((line) => typeof record2(line).line === "number" && typeof record2(line).hash === "string"))
1667
1825
  files.add(output.file);
1668
1826
  for (const item of Object.values(output))
1669
1827
  collectAnchoredFiles(item, files);
1670
1828
  }
1671
1829
  function renameFiles(value, directory = process.cwd()) {
1672
- const output = record(value);
1830
+ const output = record2(value);
1673
1831
  const files = [];
1674
1832
  if (typeof output.file === "string" && items(output.edits).length > 0) {
1675
- files.push(path3.resolve(directory, output.file));
1833
+ files.push(path4.resolve(directory, output.file));
1676
1834
  }
1677
1835
  for (const other of items(output.others)) {
1678
- const item = record(other);
1836
+ const item = record2(other);
1679
1837
  if (typeof item.file === "string" && items(item.edits).length > 0) {
1680
- files.push(path3.resolve(directory, item.file));
1838
+ files.push(path4.resolve(directory, item.file));
1681
1839
  }
1682
1840
  }
1683
1841
  return files;
@@ -1728,21 +1886,18 @@ function resolveImagePolicy(options) {
1728
1886
  return value;
1729
1887
  throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
1730
1888
  }
1731
- function inspectArgs(filePath, select, options = {}) {
1732
- return buildInspectArgs({ path: filePath, select, ...options });
1889
+ function contentArgs(filePath, options = {}) {
1890
+ return buildShowContentArgs({ path: filePath, ...options, at: options.at ?? (options.end === undefined ? undefined : "line:1") });
1733
1891
  }
1734
- function requireInspectFacet(envelope, facet) {
1735
- const value = record(envelope)[facet];
1892
+ function requireContent(envelope) {
1893
+ const value = record2(envelope).content;
1736
1894
  if (value === undefined || value === null || typeof value !== "object") {
1737
- throw new Error(`inspect returned no ${facet} facet`);
1895
+ throw new Error("show content returned no source content");
1738
1896
  }
1739
1897
  return value;
1740
1898
  }
1741
1899
  function readRangeArgs(filePath, start, end) {
1742
- return inspectArgs(filePath, "content", {
1743
- selector: start > 1 ? `line:${start}` : undefined,
1744
- end
1745
- });
1900
+ return contentArgs(filePath, { at: `line:${start}`, end });
1746
1901
  }
1747
1902
  function readSeekBinaryPath() {
1748
1903
  return resolveReadSeekBinaryOverride() ?? resolveReadSeekBinaryFromPackages(resolveFromHost);
@@ -1779,12 +1934,12 @@ async function runReadSeek(context, args, options = {}) {
1779
1934
  timeoutMs: options.timeoutMs
1780
1935
  }));
1781
1936
  }
1782
- async function runInspectContent(context, args, options = {}) {
1783
- return requireInspectFacet(await runReadSeek(context, args, options), "content");
1937
+ async function runContent(context, args, options = {}) {
1938
+ return requireContent(await runReadSeek(context, args, options));
1784
1939
  }
1785
1940
  async function postMutationPreview(context, args, filePath, changed) {
1786
1941
  try {
1787
- return await runInspectContent(context, args, {
1942
+ return await runContent(context, args, {
1788
1943
  cancelable: false,
1789
1944
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1790
1945
  });
@@ -1813,7 +1968,7 @@ async function ensureReadSeekCache(context) {
1813
1968
  if (error.code !== "ENOENT")
1814
1969
  throw error;
1815
1970
  }
1816
- readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"], { cancelable: false }).then(() => cacheDir).finally(() => {
1971
+ readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "initialize", "index"], { cancelable: false }).then(() => cacheDir).finally(() => {
1817
1972
  readSeekCacheInit = undefined;
1818
1973
  });
1819
1974
  return readSeekCacheInit;
@@ -1833,7 +1988,7 @@ function openCodeToolDescription(name) {
1833
1988
  }
1834
1989
 
1835
1990
  // src/session-anchors.ts
1836
- import path4 from "node:path";
1991
+ import path5 from "node:path";
1837
1992
 
1838
1993
  class SessionAnchors {
1839
1994
  #pathsBySession = new Map;
@@ -1847,7 +2002,7 @@ class SessionAnchors {
1847
2002
  paths.add(filePath);
1848
2003
  }
1849
2004
  forget(filePath) {
1850
- const absolutePath = path4.resolve(filePath);
2005
+ const absolutePath = path5.resolve(filePath);
1851
2006
  for (const paths of this.#pathsBySession.values())
1852
2007
  paths.delete(absolutePath);
1853
2008
  for (const [sessionID, plan] of this.#renamePlans) {
@@ -1870,14 +2025,14 @@ class SessionAnchors {
1870
2025
  const { file, old_name: oldName, new_name: newName, others } = record;
1871
2026
  if (typeof file !== "string" || typeof oldName !== "string" || typeof newName !== "string")
1872
2027
  return;
1873
- const files = new Set([path4.resolve(file)]);
2028
+ const files = new Set([path5.resolve(file)]);
1874
2029
  if (Array.isArray(others)) {
1875
2030
  for (const item of others) {
1876
2031
  if (!item || typeof item !== "object")
1877
2032
  continue;
1878
2033
  const otherFile = item.file;
1879
2034
  if (typeof otherFile === "string")
1880
- files.add(path4.resolve(otherFile));
2035
+ files.add(path5.resolve(otherFile));
1881
2036
  }
1882
2037
  }
1883
2038
  this.#renamePlans.set(sessionID, { summary: `${oldName} -> ${newName}`, files });
@@ -1903,22 +2058,21 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
1903
2058
 
1904
2059
  // src/tools.ts
1905
2060
  import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
1906
- import { tool as tool2 } from "@opencode-ai/plugin";
2061
+ import { tool as tool3 } from "@opencode-ai/plugin";
1907
2062
 
1908
2063
  // src/constants.ts
1909
2064
  var DEFAULT_VISION_TIMEOUT_MS = 30 * 60 * 1000;
1910
2065
  var DEFAULT_READ_LIMIT = 2000;
1911
2066
  var EDIT_RESULT_READ_LIMIT = 40;
1912
2067
  var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
1913
- var IMAGE_MODES = [...VISION_MODES];
1914
2068
  var VISION_LEVEL_VALUES = [...VISION_LEVELS];
1915
2069
 
1916
2070
  // src/edits.ts
1917
2071
  import { lstat, mkdir, open, readFile } from "node:fs/promises";
1918
- import path5 from "node:path";
2072
+ import path6 from "node:path";
1919
2073
  var fileMutationTails = new Map;
1920
2074
  async function withFileMutationQueue(filePath, operation) {
1921
- const key = path5.resolve(filePath);
2075
+ const key = path6.resolve(filePath);
1922
2076
  const previous = fileMutationTails.get(key);
1923
2077
  let release = () => {};
1924
2078
  const current = new Promise((resolve) => {
@@ -1973,7 +2127,7 @@ async function writeHandle(handle, content) {
1973
2127
  }
1974
2128
  }
1975
2129
  async function writeText(filePath, expected, content) {
1976
- await mkdir(path5.dirname(filePath), { recursive: true });
2130
+ await mkdir(path6.dirname(filePath), { recursive: true });
1977
2131
  if (!expected.exists) {
1978
2132
  let handle;
1979
2133
  try {
@@ -2105,20 +2259,20 @@ function simpleDiff(filePath, before, after) {
2105
2259
 
2106
2260
  // src/paths.ts
2107
2261
  import { lstat as lstat2, readlink, realpath, stat as stat2 } from "node:fs/promises";
2108
- import path6 from "node:path";
2262
+ import path7 from "node:path";
2109
2263
  function resolvePath(directory, filePath) {
2110
- return path6.resolve(directory, filePath);
2264
+ return path7.resolve(directory, filePath);
2111
2265
  }
2112
2266
  async function resolveSymlinks(filePath) {
2113
- let unresolved = path6.resolve(filePath);
2267
+ let unresolved = path7.resolve(filePath);
2114
2268
  let symlinks = 0;
2115
2269
  while (true) {
2116
- const parsed = path6.parse(unresolved);
2117
- const parts = unresolved.slice(parsed.root.length).split(path6.sep).filter(Boolean);
2270
+ const parsed = path7.parse(unresolved);
2271
+ const parts = unresolved.slice(parsed.root.length).split(path7.sep).filter(Boolean);
2118
2272
  let resolved = parsed.root;
2119
2273
  let restart = false;
2120
2274
  for (let index = 0;index < parts.length; index++) {
2121
- const candidate = path6.join(resolved, parts[index]);
2275
+ const candidate = path7.join(resolved, parts[index]);
2122
2276
  const info = await lstat2(candidate);
2123
2277
  if (!info.isSymbolicLink()) {
2124
2278
  resolved = candidate;
@@ -2131,7 +2285,7 @@ async function resolveSymlinks(filePath) {
2131
2285
  throw error;
2132
2286
  }
2133
2287
  const target = await readlink(candidate);
2134
- unresolved = path6.resolve(path6.dirname(candidate), target, ...parts.slice(index + 1));
2288
+ unresolved = path7.resolve(path7.dirname(candidate), target, ...parts.slice(index + 1));
2135
2289
  restart = true;
2136
2290
  break;
2137
2291
  }
@@ -2153,14 +2307,14 @@ async function canonicalPath(filePath) {
2153
2307
  let existing = filePath;
2154
2308
  while (true) {
2155
2309
  try {
2156
- return path6.join(await portableRealpath(existing), ...missing.reverse());
2310
+ return path7.join(await portableRealpath(existing), ...missing.reverse());
2157
2311
  } catch (error) {
2158
2312
  if (error.code !== "ENOENT")
2159
2313
  throw error;
2160
- const parent = path6.dirname(existing);
2314
+ const parent = path7.dirname(existing);
2161
2315
  if (parent === existing)
2162
2316
  throw error;
2163
- missing.push(path6.basename(existing));
2317
+ missing.push(path7.basename(existing));
2164
2318
  existing = parent;
2165
2319
  }
2166
2320
  }
@@ -2177,8 +2331,8 @@ async function authorizeExternal(context, filePath) {
2177
2331
  const info = await stat2(accessPath).catch(() => {
2178
2332
  return;
2179
2333
  });
2180
- const parentDir = info?.isDirectory() ? accessPath : path6.dirname(accessPath);
2181
- const pattern = path6.join(parentDir, "*").replaceAll("\\", "/");
2334
+ const parentDir = info?.isDirectory() ? accessPath : path7.dirname(accessPath);
2335
+ const pattern = path7.join(parentDir, "*").replaceAll("\\", "/");
2182
2336
  await context.ask({
2183
2337
  permission: "external_directory",
2184
2338
  patterns: [pattern],
@@ -2199,7 +2353,7 @@ async function authorizeRead(context, filePath) {
2199
2353
  await authorizeExternal(context, filePath);
2200
2354
  await context.ask({
2201
2355
  permission: "read",
2202
- patterns: [path6.relative(context.worktree, filePath).replaceAll("\\", "/")],
2356
+ patterns: [path7.relative(context.worktree, filePath).replaceAll("\\", "/")],
2203
2357
  always: ["*"],
2204
2358
  metadata: {}
2205
2359
  });
@@ -2218,7 +2372,7 @@ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPat
2218
2372
  for (const filePath of filePaths)
2219
2373
  await authorizeExternal(context, filePath);
2220
2374
  }
2221
- const patterns = filePaths.map((filePath) => path6.relative(context.worktree, filePath).replaceAll("\\", "/"));
2375
+ const patterns = filePaths.map((filePath) => path7.relative(context.worktree, filePath).replaceAll("\\", "/"));
2222
2376
  await context.ask({
2223
2377
  permission: "edit",
2224
2378
  patterns,
@@ -2228,109 +2382,92 @@ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPat
2228
2382
  }
2229
2383
 
2230
2384
  // src/presentation.ts
2231
- import { tool } from "@opencode-ai/plugin";
2385
+ import { pathToFileURL } from "node:url";
2386
+ import { tool as tool2 } from "@opencode-ai/plugin";
2232
2387
  function initialTitle(kind, args) {
2233
2388
  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":
2389
+ case "show_structure":
2390
+ case "show_metadata":
2391
+ case "check_syntax":
2392
+ case "check_rules":
2393
+ case "generate_caption":
2394
+ case "detect_objects":
2395
+ case "recognize_text":
2396
+ return `${kind.replaceAll("_", " ")} ${args.path}`;
2397
+ case "show_content":
2398
+ return `Show content ${args.path}`;
2399
+ case "show_assets":
2400
+ return `Show assets ${args.path}`;
2401
+ case "find_nodes":
2402
+ return `Find nodes ${args.pattern ?? args.context ?? args.rule}`;
2403
+ case "find_text":
2404
+ return `Find text ${args.pattern ?? args.text}`;
2405
+ case "find_definitions":
2243
2406
  return `Find definition ${args.name}`;
2244
- case "refs":
2407
+ case "find_references":
2245
2408
  return `Find references to ${args.name}`;
2246
- case "rename":
2247
- return `Rename ${args.path}:${args.line} to ${args.to}`;
2248
- case "edit":
2409
+ case "rename_symbol":
2410
+ return `Rename ${args.path}:${args.at} to ${args.to}`;
2411
+ case "edit_text":
2249
2412
  return `Edit ${args.path}`;
2250
- case "write":
2413
+ case "write_file":
2251
2414
  return `Write ${args.path}`;
2252
2415
  }
2253
2416
  }
2254
2417
  function resultPresentation(kind, args, value) {
2255
- const output = record(value);
2418
+ const output = record2(value);
2419
+ if (isShowTool(kind)) {
2420
+ const presentation = presentShowResult(kind, value, args);
2421
+ return { title: presentation.summary, metadata: presentation.metadata };
2422
+ }
2256
2423
  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);
2263
- const content = record(output.content);
2264
- const metadata = {};
2265
- 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) {
2279
- if (typeof identity.line === "number")
2280
- metadata.line = identity.line;
2281
- if (typeof identity.column === "number")
2282
- metadata.column = identity.column;
2283
- const identifier = record(identity.identifier).text;
2284
- if (typeof identifier === "string")
2285
- summary.push(identifier);
2286
- }
2287
- if (typeof content.start_line === "number")
2288
- metadata.start_line = content.start_line;
2289
- if (typeof content.end_line === "number")
2290
- metadata.end_line = content.end_line;
2291
- return {
2292
- title: summary.length > 0 ? `Inspected ${args.path} (${summary.join(", ")})` : `Inspected ${args.path}`,
2293
- metadata
2294
- };
2424
+ case "check_syntax": {
2425
+ const diagnostics = record2(output.diagnostics ?? output);
2426
+ const errors = typeof diagnostics.error_count === "number" ? diagnostics.error_count : 0;
2427
+ const missing = typeof diagnostics.missing_count === "number" ? diagnostics.missing_count : 0;
2428
+ return { title: `Syntax ${args.path} (${errors} errors, ${missing} missing)`, metadata: { error_count: errors, missing_count: missing } };
2295
2429
  }
2296
- case "search": {
2430
+ case "find_nodes": {
2297
2431
  const results = items(output.results);
2298
- const matches = results.reduce((total, result) => total + items(record(result).matches).length, 0);
2432
+ const matches = results.reduce((total, result) => total + items(record2(result).matches).length, 0);
2299
2433
  return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
2300
2434
  }
2301
- case "grep": {
2435
+ case "find_text": {
2302
2436
  const results = items(output.results);
2303
- const matches = results.reduce((total, result) => total + items(record(result).matches).length, 0);
2437
+ const matches = results.reduce((total, result) => {
2438
+ const entry = record2(result);
2439
+ return total + (typeof entry.count === "number" ? entry.count : items(entry.matches).length);
2440
+ }, 0);
2304
2441
  return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
2305
2442
  }
2306
- case "def": {
2443
+ case "find_definitions": {
2307
2444
  const locations = items(output.locations).length;
2308
2445
  return { title: `Found ${locations} definitions`, metadata: { locations } };
2309
2446
  }
2310
- case "refs": {
2447
+ case "find_references": {
2311
2448
  const references = items(output.references).length;
2312
2449
  return { title: `Found ${references} references`, metadata: { references } };
2313
2450
  }
2314
- case "rename": {
2451
+ case "rename_symbol": {
2315
2452
  const oldName = output.old_name;
2316
2453
  const newName = output.new_name;
2317
- const otherOutputs = items(output.others).map(record);
2454
+ const otherOutputs = items(output.others).map(record2);
2318
2455
  const edits = otherOutputs.reduce((total, item) => total + items(item.edits).length, items(output.edits).length);
2319
2456
  const conflicts = otherOutputs.reduce((total, item) => total + items(item.conflicts).length, items(output.conflicts).length);
2320
2457
  const others = otherOutputs.length;
2321
2458
  const title = typeof oldName === "string" && typeof newName === "string" ? `${output.applied === true ? "Renamed" : "Plan"} ${oldName} -> ${newName}` : initialTitle(kind, args);
2322
2459
  return { title, metadata: { edits, conflicts, others } };
2323
2460
  }
2324
- case "edit":
2325
- case "write": {
2461
+ case "edit_text":
2462
+ case "write_file": {
2326
2463
  const preview = typeof output.preview_error === "string" ? " (preview unavailable)" : "";
2327
- return { title: `${kind === "edit" ? "Edited" : "Wrote"} ${args.path}${preview}`, metadata: {} };
2464
+ return { title: `${kind === "edit_text" ? "Edited" : "Wrote"} ${args.path}${preview}`, metadata: {} };
2328
2465
  }
2329
2466
  }
2330
2467
  return { title: initialTitle(kind, args), metadata: {} };
2331
2468
  }
2332
- function readseekTool(description, args, kind, execute) {
2333
- return tool({
2469
+ function readseekTool(description, args, kind, execute, imagePolicy) {
2470
+ return tool2({
2334
2471
  description,
2335
2472
  args,
2336
2473
  async execute(args, context) {
@@ -2338,10 +2475,29 @@ function readseekTool(description, args, kind, execute) {
2338
2475
  context.metadata({ title });
2339
2476
  const result = await execute(args, context);
2340
2477
  const presentation = resultPresentation(kind, args, result);
2478
+ const attachments = [];
2479
+ const imagePaths = new Map;
2480
+ const visit = (value) => {
2481
+ if (!value || typeof value !== "object")
2482
+ return;
2483
+ const item = value;
2484
+ if (typeof item.path === "string" && typeof item.mime === "string" && item.mime.startsWith("image/"))
2485
+ imagePaths.set(item.path, item.mime);
2486
+ for (const child of Object.values(item))
2487
+ visit(child);
2488
+ };
2489
+ if (imagePolicy !== undefined)
2490
+ visit(result);
2491
+ for (const [path, mime] of imagePaths) {
2492
+ if (imagePolicy !== "off")
2493
+ attachments.push({ type: "file", mime, url: pathToFileURL(path).href });
2494
+ }
2495
+ const displayResult = imagePolicy === "off" && imagePaths.size > 0 ? { ...record2(result), warning: "Image attachments are disabled; use the returned paths to view the images." } : result;
2341
2496
  return {
2497
+ ...attachments.length > 0 ? { attachments } : {},
2342
2498
  title: presentation.title,
2343
- output: render(result),
2344
- metadata: presentation.metadata
2499
+ output: isShowTool(kind) ? formatShowResult(presentShowResult(kind, displayResult, args)) : render(displayResult),
2500
+ metadata: { ...presentation.metadata, ...isShowTool(kind) ? { readseek: result } : {} }
2345
2501
  };
2346
2502
  }
2347
2503
  });
@@ -2350,88 +2506,30 @@ function readseekTool(description, args, kind, execute) {
2350
2506
  // src/tools.ts
2351
2507
  function createReadSeekTools(imagePolicy) {
2352
2508
  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) => {
2509
+ ...createAccessTools(imagePolicy),
2510
+ readseek_find_text: readseekTool(openCodeToolDescription("find_text"), {
2511
+ pattern: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.grepPattern),
2512
+ text: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.text),
2513
+ summary: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.summary),
2514
+ path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2515
+ glob: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.glob),
2516
+ literal: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.literal),
2517
+ ignoreCase: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignoreCase),
2518
+ context: tool3.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.context),
2519
+ limit: tool3.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit)
2520
+ }, "find_text", async (input, context) => {
2429
2521
  const target = resolvePath(context.directory, input.path ?? ".");
2430
- const pattern = input.pattern;
2522
+ if (input.pattern === undefined === (input.text === undefined)) {
2523
+ throw new Error("exactly one of pattern or text is required");
2524
+ }
2525
+ const pattern = input.pattern ?? input.text;
2431
2526
  await authorizeSearch(context, target, pattern);
2527
+ if (input.text !== undefined && !input.glob && !input.ignoreCase && !input.context && !input.summary) {
2528
+ return runReadSeek(context, buildFindTextArgs({ path: target, text: pattern, limit: input.limit }));
2529
+ }
2432
2530
  let expression;
2433
2531
  try {
2434
- const source = input.literal ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
2532
+ const source = input.literal || input.text !== undefined ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
2435
2533
  expression = new RegExp(source, input.ignoreCase ? "i" : "");
2436
2534
  } catch (error) {
2437
2535
  throw new Error(`invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
@@ -2479,6 +2577,10 @@ function createReadSeekTools(imagePolicy) {
2479
2577
  }
2480
2578
  if (ranges.length === 0)
2481
2579
  continue;
2580
+ if (input.summary) {
2581
+ results.push({ file: filePath, count: matchLines.size });
2582
+ continue;
2583
+ }
2482
2584
  const mergedRanges = [];
2483
2585
  for (const [start, end] of ranges) {
2484
2586
  const previous = mergedRanges.at(-1);
@@ -2489,46 +2591,59 @@ function createReadSeekTools(imagePolicy) {
2489
2591
  }
2490
2592
  const linesByNumber = new Map;
2491
2593
  for (const [start, end] of mergedRanges) {
2492
- const output = record(await runInspectContent(context, readRangeArgs(filePath, start, end)));
2594
+ const output = record2(await runContent(context, readRangeArgs(filePath, start, end)));
2493
2595
  for (const hashline of items(output.hashlines)) {
2494
- const line = record(hashline).line;
2596
+ const line = record2(hashline).line;
2495
2597
  if (typeof line === "number" && !linesByNumber.has(line))
2496
2598
  linesByNumber.set(line, hashline);
2497
2599
  }
2498
2600
  }
2499
2601
  const hashlines = [...linesByNumber.values()];
2500
2602
  const matches = hashlines.filter((hashline) => {
2501
- const line = record(hashline).line;
2603
+ const line = record2(hashline).line;
2502
2604
  return typeof line === "number" && matchLines.has(line);
2503
2605
  });
2504
2606
  results.push({ file: filePath, matches, hashlines });
2505
2607
  }
2506
2608
  return { results, truncated };
2507
2609
  }),
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) => {
2610
+ readseek_find_nodes: readseekTool(openCodeToolDescription("find_nodes"), {
2611
+ pattern: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.pattern),
2612
+ context: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.patternContext),
2613
+ selector: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.selector),
2614
+ rule: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.rule),
2615
+ limit: tool3.schema.number().int().positive().optional(),
2616
+ path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2617
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2618
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2619
+ }, "find_nodes", async (input, context) => {
2514
2620
  const target = resolvePath(context.directory, input.path ?? ".");
2515
- const args = buildSearchArgs({
2621
+ const args = buildFindNodesArgs({
2516
2622
  path: target,
2517
2623
  pattern: input.pattern,
2624
+ context: input.context,
2625
+ selector: input.selector,
2626
+ rule: input.rule,
2627
+ limit: input.limit,
2518
2628
  language: input.language,
2519
2629
  gitScope: input.gitScope
2520
2630
  });
2521
- await authorizeSearch(context, target, input.pattern);
2631
+ await authorizeSearch(context, target, input.pattern ?? input.context ?? input.rule);
2632
+ if (input.rule?.startsWith("@")) {
2633
+ await authorizeRead(context, resolvePath(context.directory, input.rule.slice(1)));
2634
+ }
2635
+ if (input.rule === "-")
2636
+ throw new Error("rule stdin is unavailable in OpenCode tools; use inline KDL or @path");
2522
2637
  return runReadSeek(context, args);
2523
2638
  }),
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) => {
2639
+ readseek_find_definitions: readseekTool(openCodeToolDescription("find_definitions"), {
2640
+ name: tool3.schema.string().describe(PARAM_DESCRIPTIONS.name),
2641
+ path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2642
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2643
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2644
+ }, "find_definitions", async (input, context) => {
2530
2645
  const target = resolvePath(context.directory, input.path ?? ".");
2531
- const args = buildDefArgs({
2646
+ const args = buildFindDefinitionsArgs({
2532
2647
  path: target,
2533
2648
  name: input.name,
2534
2649
  language: input.language,
@@ -2537,117 +2652,119 @@ function createReadSeekTools(imagePolicy) {
2537
2652
  await authorizeSearch(context, target, input.name);
2538
2653
  return runReadSeek(context, args);
2539
2654
  }),
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) => {
2655
+ readseek_find_references: readseekTool(openCodeToolDescription("find_references"), {
2656
+ name: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
2657
+ path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2658
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2659
+ at: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
2660
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2661
+ }, "find_references", async (input, context) => {
2549
2662
  const target = resolvePath(context.directory, input.path ?? ".");
2550
2663
  const name = input.name;
2551
- const args = buildRefsArgs({
2664
+ const args = buildFindReferencesArgs({
2552
2665
  path: target,
2553
2666
  name,
2554
- scope: input.scope,
2555
- line: input.line,
2556
- column: input.column,
2667
+ at: input.at,
2557
2668
  language: input.language,
2558
2669
  gitScope: input.gitScope
2559
2670
  });
2560
2671
  await authorizeSearch(context, target, name ?? "*");
2561
2672
  return runReadSeek(context, args);
2562
2673
  }),
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) => {
2674
+ readseek_rename_symbol: readseekTool(openCodeToolDescription("rename_symbol"), {
2675
+ path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2676
+ at: tool3.schema.string().describe(PARAM_DESCRIPTIONS.at),
2677
+ to: tool3.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
2678
+ workspace: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.workspace),
2679
+ apply: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue),
2680
+ planHash: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.planHash),
2681
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2682
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2683
+ }, "rename_symbol", async (input, context) => {
2571
2684
  const filePath = resolvePath(context.directory, input.path);
2572
2685
  await authorizeRead(context, filePath);
2573
2686
  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"));
2687
+ const envelope = await runReadSeek(context, contentArgs(filePath, { at: input.at }));
2688
+ const name = identifiedName(record2(envelope).cursor);
2580
2689
  if (!name)
2581
2690
  throw new Error("readseek could not identify a binding at the rename cursor");
2582
2691
  await authorizeSearch(context, context.directory, name);
2583
2692
  }
2584
- const args = buildRenameArgs({
2693
+ const args = buildRenameSymbolArgs({
2585
2694
  path: filePath,
2586
2695
  to: input.to,
2587
- line: input.line,
2588
- column: input.column,
2589
- workspace: input.workspace ? context.directory : undefined
2696
+ at: input.at,
2697
+ workspace: input.workspace ? context.directory : undefined,
2698
+ language: input.language,
2699
+ gitScope: input.gitScope
2590
2700
  });
2591
2701
  const plan = await runReadSeek(context, args);
2702
+ if (input.planHash !== undefined && record2(plan).plan_hash !== input.planHash) {
2703
+ throw new Error("readseek rename plan does not match planHash");
2704
+ }
2592
2705
  if (input.apply === false)
2593
2706
  return plan;
2594
2707
  const files = renameFiles(plan, context.directory);
2595
- const planHash = record(plan).plan_hash;
2708
+ const planHash = record2(plan).plan_hash;
2596
2709
  if (typeof planHash !== "string" || planHash.length === 0) {
2597
2710
  throw new Error("readseek rename plan did not include a plan hash");
2598
2711
  }
2599
2712
  await authorizeEdit(context, files);
2600
- return runReadSeek(context, buildRenameArgs({
2713
+ return runReadSeek(context, buildRenameSymbolArgs({
2601
2714
  path: filePath,
2602
2715
  to: input.to,
2603
- line: input.line,
2604
- column: input.column,
2716
+ at: input.at,
2605
2717
  workspace: input.workspace ? context.directory : undefined,
2606
2718
  apply: true,
2607
- planHash
2719
+ planHash,
2720
+ language: input.language,
2721
+ gitScope: input.gitScope
2608
2722
  }), {
2609
2723
  cancelable: false,
2610
2724
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
2611
2725
  });
2612
2726
  }),
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)
2727
+ readseek_edit_text: readseekTool(openCodeToolDescription("edit_text"), {
2728
+ path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2729
+ edits: tool3.schema.array(tool3.schema.union([
2730
+ tool3.schema.object({
2731
+ set_line: tool3.schema.object({
2732
+ anchor: tool3.schema.string().describe(PARAM_DESCRIPTIONS.anchor),
2733
+ new_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newTextLine)
2620
2734
  }).strict()
2621
2735
  }).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)
2736
+ tool3.schema.object({
2737
+ replace_lines: tool3.schema.object({
2738
+ start_anchor: tool3.schema.string().describe(PARAM_DESCRIPTIONS.startAnchor),
2739
+ end_anchor: tool3.schema.string().describe(PARAM_DESCRIPTIONS.endAnchor),
2740
+ new_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newTextRange)
2627
2741
  }).strict()
2628
2742
  }).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)
2743
+ tool3.schema.object({
2744
+ insert_after: tool3.schema.object({
2745
+ anchor: tool3.schema.string().describe(PARAM_DESCRIPTIONS.insertAnchor),
2746
+ new_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newTextInsert)
2633
2747
  }).strict()
2634
2748
  }).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)
2749
+ tool3.schema.object({
2750
+ replace: tool3.schema.object({
2751
+ old_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.oldText),
2752
+ new_text: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newText),
2753
+ all: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.all)
2640
2754
  }).strict()
2641
2755
  }).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)
2756
+ tool3.schema.object({
2757
+ replace_symbol: tool3.schema.object({
2758
+ symbol: tool3.schema.string().describe(PARAM_DESCRIPTIONS.symbol),
2759
+ new_body: tool3.schema.string().describe(PARAM_DESCRIPTIONS.newBody)
2646
2760
  }).strict()
2647
2761
  }).strict()
2648
2762
  ])).min(1).describe(PARAM_DESCRIPTIONS.edits),
2649
- language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageSymbol)
2650
- }, "edit", async (input, context) => {
2763
+ language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageSymbol),
2764
+ apply: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue),
2765
+ planHash: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.planHash),
2766
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2767
+ }, "edit_text", async (input, context) => {
2651
2768
  const filePath = resolvePath(context.directory, input.path);
2652
2769
  validateEdits(input.edits);
2653
2770
  return withFileMutationQueue(filePath, async () => {
@@ -2656,15 +2773,21 @@ function createReadSeekTools(imagePolicy) {
2656
2773
  await rejectSymlinkMutation(filePath);
2657
2774
  const edits = input.edits;
2658
2775
  const request = serializeEditPlan(edits);
2659
- const baseArgs = buildEditArgs({
2776
+ const baseArgs = buildEditTextArgs({
2660
2777
  path: filePath,
2661
2778
  language: input.language,
2779
+ gitScope: input.gitScope,
2662
2780
  requestFromStdin: true
2663
2781
  });
2664
- const plan = record(await runReadSeek(context, baseArgs, { stdin: request }));
2782
+ const plan = record2(await runReadSeek(context, baseArgs, { stdin: request }));
2665
2783
  if (typeof plan.before_content !== "string" || typeof plan.content !== "string" || typeof plan.plan_hash !== "string") {
2666
2784
  throw new Error("readseek edit plan is missing before_content, content, or plan_hash");
2667
2785
  }
2786
+ if (input.planHash !== undefined && plan.plan_hash !== input.planHash) {
2787
+ throw new Error("readseek edit plan does not match planHash");
2788
+ }
2789
+ if (input.apply === false)
2790
+ return plan;
2668
2791
  const before = plan.before_content;
2669
2792
  const after = plan.content;
2670
2793
  const previewStart = Math.max(1, firstChangedLine(before, after) - 3);
@@ -2674,9 +2797,10 @@ function createReadSeekTools(imagePolicy) {
2674
2797
  }
2675
2798
  await authorizeEdit(context, [filePath], simpleDiff(filePath, before, after), false);
2676
2799
  context.abort.throwIfAborted();
2677
- const applied = record(await runReadSeek(context, buildEditArgs({
2800
+ const applied = record2(await runReadSeek(context, buildEditTextArgs({
2678
2801
  path: filePath,
2679
2802
  language: input.language,
2803
+ gitScope: input.gitScope,
2680
2804
  requestFromStdin: true,
2681
2805
  apply: true,
2682
2806
  planHash: plan.plan_hash
@@ -2691,10 +2815,10 @@ function createReadSeekTools(imagePolicy) {
2691
2815
  return postMutationPreview(context, previewArgs, filePath, true);
2692
2816
  });
2693
2817
  }),
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) => {
2818
+ readseek_write_file: readseekTool(openCodeToolDescription("write_file"), {
2819
+ path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2820
+ content: tool3.schema.string().describe(PARAM_DESCRIPTIONS.content)
2821
+ }, "write_file", async (input, context) => {
2698
2822
  const filePath = resolvePath(context.directory, input.path);
2699
2823
  await authorizeExternal(context, filePath);
2700
2824
  return withFileMutationQueue(filePath, async () => {
@@ -2707,11 +2831,50 @@ function createReadSeekTools(imagePolicy) {
2707
2831
  await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
2708
2832
  context.abort.throwIfAborted();
2709
2833
  await writeText(filePath, before, content);
2710
- return postMutationPreview(context, inspectArgs(filePath, "content", { end: DEFAULT_READ_LIMIT }), filePath, !before.exists || before.content !== content);
2834
+ return postMutationPreview(context, contentArgs(filePath, { end: DEFAULT_READ_LIMIT }), filePath, !before.exists || before.content !== content);
2711
2835
  });
2712
2836
  })
2713
2837
  };
2714
2838
  }
2839
+ function createAccessTools(imagePolicy) {
2840
+ const pathSchema = { path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath) };
2841
+ const atSchema = { at: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.at) };
2842
+ const languageSchema = { language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language) };
2843
+ const levelSchema = { level: tool3.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.level) };
2844
+ const definitions = {
2845
+ 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() } },
2846
+ 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() } },
2847
+ show_metadata: { build: buildShowMetadataArgs, args: { ...pathSchema, ...languageSchema } },
2848
+ show_assets: { build: buildShowAssetsArgs, args: { ...pathSchema, ...atSchema } },
2849
+ check_syntax: { build: buildCheckSyntaxArgs, args: { ...pathSchema, ...languageSchema } },
2850
+ 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() } },
2851
+ generate_caption: { build: buildGenerateCaptionArgs, args: { ...pathSchema, ...atSchema, ...levelSchema, include: tool3.schema.array(tool3.schema.enum(["text", "objects"])).min(1).optional() } },
2852
+ detect_objects: { build: buildDetectObjectsArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } },
2853
+ recognize_text: { build: buildRecognizeTextArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } }
2854
+ };
2855
+ return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [
2856
+ `readseek_${name}`,
2857
+ readseekTool(openCodeToolDescription(name), definition.args, name, async (input, context) => {
2858
+ const filePath = resolvePath(context.directory, input.path ?? ".");
2859
+ const buildInput = { ...input, path: filePath, ...name === "show_structure" ? { depth: input.depth ?? DEFAULT_STRUCTURE_DEPTH } : {} };
2860
+ let args = definition.build(buildInput);
2861
+ await authorizeRead(context, filePath);
2862
+ if (name === "check_rules") {
2863
+ if (input.rules === "-")
2864
+ throw new Error("rules stdin is unavailable in OpenCode tools; use inline KDL or @path");
2865
+ await authorizeSearch(context, filePath, input.rules);
2866
+ if (input.rules.startsWith("@"))
2867
+ await authorizeRead(context, resolvePath(context.directory, input.rules.slice(1)));
2868
+ }
2869
+ const inference = ["generate_caption", "detect_objects", "recognize_text"].includes(name);
2870
+ if (name === "show_content" || name === "show_assets" || inference) {
2871
+ const readseekDir = await ensureReadSeekCache(context);
2872
+ args = definition.build({ ...buildInput, readseekDir });
2873
+ }
2874
+ return runReadSeek(context, args, { timeoutMs: inference ? DEFAULT_VISION_TIMEOUT_MS : DEFAULT_READSEEK_TIMEOUT_MS });
2875
+ }, name === "show_content" || name === "show_assets" ? imagePolicy : undefined)
2876
+ ]));
2877
+ }
2715
2878
 
2716
2879
  // v2.ts
2717
2880
  var GUIDANCE = "For structural code navigation, consult the readseek skill when ReadSeek tools are unavailable and the readseek CLI is installed.";
@@ -2724,6 +2887,29 @@ var SKILL = {
2724
2887
 
2725
2888
  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
2889
 
2890
+ Commands use verb-object pairs, with the target path positional:
2891
+
2892
+ \`\`\`sh
2893
+ readseek show content file.ts --at line:10 --limit 40 --kdl
2894
+ readseek show structure file.ts --depth 1 --kdl
2895
+ readseek show metadata paper.pdf --kdl
2896
+ readseek show assets paper.pdf --at page:2 --kdl
2897
+ readseek check syntax file.ts --kdl
2898
+ readseek check rules . --rules @checks.kdl --kdl
2899
+ readseek find nodes src --pattern 'console.log($$$ARGS)' --kdl
2900
+ readseek find text src --text 'TODO' --kdl
2901
+ readseek find definitions src --name main --kdl
2902
+ readseek find references file.ts --at line:10:3 --kdl
2903
+ readseek edit text file.ts --edits @edits.kdl --kdl
2904
+ readseek rename symbol file.ts --at line:10:3 --to nextName --kdl
2905
+ \`\`\`
2906
+
2907
+ 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"\`.
2908
+
2909
+ 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.
2910
+
2911
+ 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.
2912
+
2727
2913
  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
2914
  };
2729
2915
  var ReadSeekV2Plugin = {
@@ -2779,9 +2965,9 @@ var ReadSeekPlugin = async (_input, options) => {
2779
2965
  return;
2780
2966
  try {
2781
2967
  const result = JSON.parse(output.output);
2782
- if (input.tool === "readseek_rename") {
2968
+ if (input.tool === "readseek_rename_symbol") {
2783
2969
  anchors.planRename(input.sessionID, result);
2784
- if (record(result).applied === true) {
2970
+ if (record2(result).applied === true) {
2785
2971
  for (const filePath of renameFiles(result))
2786
2972
  anchors.forget(filePath);
2787
2973
  }
@@ -2789,7 +2975,7 @@ var ReadSeekPlugin = async (_input, options) => {
2789
2975
  const files = new Set;
2790
2976
  collectAnchoredFiles(result, files);
2791
2977
  for (const filePath of files)
2792
- anchors.mark(input.sessionID, path7.resolve(filePath));
2978
+ anchors.mark(input.sessionID, path8.resolve(filePath));
2793
2979
  } catch {}
2794
2980
  },
2795
2981
  "experimental.session.compacting": async (input, output) => {