pi-readseek 0.10.7 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.ts CHANGED
@@ -6,14 +6,6 @@ import { Type as Type3 } from "@sinclair/typebox";
6
6
  import { Text as Text3 } from "@earendil-works/pi-tui";
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,244 +193,84 @@ 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
  ];
227
+ function getToolContract(name) {
228
+ const contract = TOOL_CONTRACTS.find((entry) => entry.name === name);
229
+ if (!contract)
230
+ throw new Error(`missing tool contract: ${name}`);
231
+ return contract;
232
+ }
388
233
  // ../readseek-api/src/policy.ts
389
234
  var REPLACEABLE_TOOLS = [
390
235
  {
391
- tool: "inspect",
236
+ tool: "show_content",
392
237
  builtIn: "read",
393
238
  benefit: "it returns LINE:HASH anchors for safe edits."
394
239
  },
395
240
  {
396
- tool: "edit",
241
+ tool: "edit_text",
397
242
  builtIn: "edit",
398
243
  benefit: "it verifies fresh LINE:HASH anchors."
399
244
  },
400
245
  {
401
- tool: "grep",
246
+ tool: "find_text",
402
247
  builtIn: "grep",
403
248
  benefit: "it returns LINE:HASH anchors."
404
249
  },
405
250
  {
406
- tool: "write",
251
+ tool: "write_file",
407
252
  builtIn: "write",
408
253
  benefit: "it returns LINE:HASH anchors."
409
254
  }
410
255
  ];
411
256
  var TOOL_COMPACT_GUIDELINES = {
412
- inspect: [
413
- "Use `inspect` for shallow maps, diagnostics, and identity; use `def`, `refs`, or `search` for targeted symbol questions."
257
+ show_content: [
258
+ "Use `show_structure` for shallow overviews and `check_syntax` for diagnostics; use `find_definitions`, `find_references`, or `find_nodes` for targeted symbol questions."
414
259
  ],
415
- edit: [
260
+ edit_text: [
416
261
  "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
417
262
  ],
418
- grep: [
419
- "Use `grep` with summary first for broad searches, then narrow by path, glob, or pattern."
263
+ find_text: [
264
+ "Use `find_text` with summary first for broad searches, then narrow by path, glob, or pattern."
420
265
  ],
421
- write: [
422
- "Use anchored edits rather than `write` for small changes or appends."
266
+ write_file: [
267
+ "Use anchored edits rather than `write_file` for small changes or appends."
423
268
  ],
424
- search: [
425
- "Use `search` for AST patterns; use `grep` for plain text."
269
+ find_nodes: [
270
+ "Use `find_nodes` for AST patterns; use `find_text` for plain text."
426
271
  ],
427
- refs: [
428
- "Use `refs` before changing a symbol; add scope plus line/column to follow one binding."
272
+ find_references: [
273
+ "Use `find_references` before changing a symbol; use at with line:N:C to follow one binding."
429
274
  ]
430
275
  };
431
276
  function resolveToolName(name, aliases = {}) {
@@ -439,20 +284,20 @@ function renderCompactGuidelines(name, aliases = {}) {
439
284
  }
440
285
  function renderToolRoutingPolicy(options = {}) {
441
286
  const aliases = options.aliases ?? {};
442
- const inspect = resolveToolName("inspect", aliases);
443
- const edit = resolveToolName("edit", aliases);
444
- const write = resolveToolName("write", aliases);
445
- const grep = resolveToolName("grep", aliases);
446
- const search = resolveToolName("search", aliases);
447
- const def = resolveToolName("def", aliases);
448
- const refs = resolveToolName("refs", aliases);
449
- const rename = resolveToolName("rename", aliases);
450
- const extract = resolveToolName("extract", aliases);
287
+ const show_content = resolveToolName("show_content", aliases);
288
+ const edit_text = resolveToolName("edit_text", aliases);
289
+ const write_file = resolveToolName("write_file", aliases);
290
+ const find_text = resolveToolName("find_text", aliases);
291
+ const find_nodes = resolveToolName("find_nodes", aliases);
292
+ const find_definitions = resolveToolName("find_definitions", aliases);
293
+ const find_references = resolveToolName("find_references", aliases);
294
+ const rename_symbol = resolveToolName("rename_symbol", aliases);
295
+ const show_assets = resolveToolName("show_assets", aliases);
451
296
  if (options.style === "server") {
452
297
  return [
453
298
  "Structural code navigation, document inspection, and hash-verified edits with readseek.",
454
- "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.",
455
- `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
299
+ "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.",
300
+ `Use ${edit_text} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
456
301
  "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
457
302
  "call MCP tools for active navigation, document questions, or anchored edits."
458
303
  ].join(" ");
@@ -462,7 +307,7 @@ function renderToolRoutingPolicy(options = {}) {
462
307
  if (options.preferOverBuiltIns) {
463
308
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
464
309
  }
465
- 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.`);
310
+ 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.`);
466
311
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
467
312
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
468
313
  }
@@ -489,25 +334,9 @@ function tryParseLineAnchor(anchor) {
489
334
  }
490
335
 
491
336
  // ../readseek-api/src/validation.ts
492
- function validateRefsNameVsScope(input) {
493
- if (input.scope) {
494
- if (input.line === undefined) {
495
- return { ok: false, error: "scope requires line" };
496
- }
497
- return { ok: true };
498
- }
499
- if (input.line !== undefined || input.column !== undefined) {
500
- return { ok: false, error: "line/column require scope=true" };
501
- }
502
- if (typeof input.name !== "string" || input.name.length === 0) {
503
- return { ok: false, error: "name is required when scope is not set" };
504
- }
505
- return { ok: true };
506
- }
507
- function validateVisionLevelVsMode(input) {
508
- if (input.visionLevel !== undefined && !["all", "ocr", "caption", "objects"].includes(input.visionMode ?? "")) {
509
- return { ok: false, error: "visionLevel requires an analysis visionMode" };
510
- }
337
+ function validateReferencesSelection(input) {
338
+ if (Boolean(input.name) === Boolean(input.at))
339
+ return { ok: false, error: "exactly one of name or at is required" };
511
340
  return { ok: true };
512
341
  }
513
342
  var VARIANT_FIELDS = {
@@ -577,51 +406,8 @@ function validateExactlyOneEditVariant(edits) {
577
406
  }
578
407
  var MIN_SAFE_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
579
408
  var MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
580
- // ../readseek-api/src/edit-plan.ts
581
- import { Document, Node, format } from "@bgotink/kdl";
582
- function node(name, properties, children = []) {
583
- const result = Node.create(name);
584
- for (const [key, value] of Object.entries(properties))
585
- result.setProperty(key, value);
586
- if (children.length > 0)
587
- result.children = new Document(children);
588
- return result;
589
- }
590
- function editNode(edit) {
591
- if ("set_line" in edit) {
592
- const value = edit.set_line;
593
- return node("set-line", { anchor: value.anchor, text: value.new_text });
594
- }
595
- if ("replace_lines" in edit) {
596
- const value = edit.replace_lines;
597
- return node("replace-lines", { start: value.start_anchor, end: value.end_anchor, text: value.new_text });
598
- }
599
- if ("insert_after" in edit) {
600
- const value = edit.insert_after;
601
- return node("insert-after", { anchor: value.anchor, text: value.new_text });
602
- }
603
- if ("replace_symbol" in edit) {
604
- const value = edit.replace_symbol;
605
- return node("replace-symbol", { symbol: value.symbol, body: value.new_body });
606
- }
607
- const value = edit.replace;
608
- return node("replace", {
609
- old: value.old_text,
610
- new: value.new_text,
611
- ...value.all === undefined ? {} : { all: value.all }
612
- });
613
- }
614
- function serializeEditPlan(edits) {
615
- const validation = validateExactlyOneEditVariant(edits);
616
- if (!validation.ok)
617
- throw new Error(validation.error);
618
- return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
619
- }
620
409
  // ../readseek-api/src/argv.ts
621
- var DEFAULT_INSPECT_MAP_DEPTH = 1;
622
- function buildLineLocation(line, column) {
623
- return column === undefined ? `line:${line}` : `line:${line}:${column}`;
624
- }
410
+ var DEFAULT_STRUCTURE_DEPTH = 1;
625
411
  function appendGitScopeArgs(args, input) {
626
412
  if (input.gitScope !== undefined)
627
413
  args.push("--git-scope", input.gitScope);
@@ -648,30 +434,6 @@ function appendApplyAndPlanHash(args, input) {
648
434
  pushOptionalString(args, "--plan-hash", input.planHash);
649
435
  return args;
650
436
  }
651
- function selectArg(select) {
652
- if (select === undefined)
653
- return;
654
- return Array.isArray(select) ? select.join(",") : select;
655
- }
656
- function selectedInspectFacets(select) {
657
- const facets = (selectArg(select) ?? "content").split(",").map((facet) => facet.trim());
658
- for (const facet of facets) {
659
- if (!INSPECT_FACETS.includes(facet)) {
660
- throw new Error(`unknown inspect facet: ${facet}`);
661
- }
662
- }
663
- return facets;
664
- }
665
- function hasInspectFacet(facets, facet) {
666
- return facets.includes(facet);
667
- }
668
- function validateTarget(command, target, stdinName) {
669
- if (target.length === 0)
670
- throw new Error(`${command} requires a target file path or '-'`);
671
- if (stdinName !== undefined && stdinName.length > 0 && target !== "-") {
672
- throw new Error("stdinName requires '-' as the target");
673
- }
674
- }
675
437
  function validatePositiveInteger(value, name) {
676
438
  if (!Number.isSafeInteger(value) || value < 1) {
677
439
  throw new Error(`${name} must be a positive integer`);
@@ -717,1226 +479,1300 @@ function parseSelector(text) {
717
479
  }
718
480
  return { kind, value };
719
481
  }
720
- function buildInspectArgs(input) {
721
- validateTarget("inspect", input.path, input.stdinName);
722
- if (input.selector !== undefined && input.selector.length === 0) {
723
- throw new Error("selector must not be empty");
482
+ function start(verb, object, input) {
483
+ if (!input.path)
484
+ throw new Error(`${verb} ${object} requires a path`);
485
+ if (input.stdinName !== undefined && input.path !== "-")
486
+ throw new Error("stdinName requires '-' as the target");
487
+ const args = [];
488
+ pushOptionalString(args, "--readseek-dir", input.readseekDir);
489
+ args.push(verb, object, input.path);
490
+ pushOptionalString(args, "--stdin-name", input.stdinName);
491
+ return args;
492
+ }
493
+ function at2(args, value) {
494
+ if (value !== undefined) {
495
+ parseSelector(value);
496
+ args.push("--at", value);
724
497
  }
725
- const vision = validateVisionLevelVsMode({
726
- visionMode: input.visionMode,
727
- visionLevel: input.visionLevel
728
- });
729
- if (!vision.ok)
730
- throw new Error(vision.error);
731
- const facets = selectedInspectFacets(input.select);
732
- const hasContent = hasInspectFacet(facets, "content");
733
- const hasIdentity = hasInspectFacet(facets, "identity");
734
- const hasMap = hasInspectFacet(facets, "map");
735
- const hasSourceFacet = hasMap || hasInspectFacet(facets, "diagnostics") || hasIdentity;
498
+ }
499
+ function finish(args) {
500
+ args.push("--kdl");
501
+ return args;
502
+ }
503
+ function language(args, input) {
504
+ pushOptionalString(args, "--language", input.language);
505
+ }
506
+ function limit2(args, value) {
507
+ if (value !== undefined)
508
+ validatePositiveInteger(value, "limit");
509
+ pushOptionalNumber(args, "--limit", value);
510
+ }
511
+ function anchors(args, input) {
512
+ if (input.noAnchors)
513
+ args.push("--no-anchors");
514
+ }
515
+ function buildShowContentArgs(input) {
736
516
  if (input.end !== undefined)
737
517
  validatePositiveInteger(input.end, "end");
738
- if (input.limit !== undefined)
739
- validatePositiveInteger(input.limit, "limit");
740
- if (input.end !== undefined && input.limit !== undefined) {
518
+ if (input.end !== undefined && input.limit !== undefined)
741
519
  throw new Error("cannot combine end with limit");
742
- }
743
- if (input.depth !== undefined) {
744
- if (!Number.isSafeInteger(input.depth) || input.depth < 0) {
745
- throw new Error("depth must be a non-negative integer");
746
- }
747
- if (!hasMap)
748
- throw new Error("depth requires the map facet");
749
- }
750
- if (input.selector !== undefined && input.query !== undefined) {
751
- throw new Error("cannot combine selector with query");
752
- }
753
- if (!hasContent && (input.end !== undefined || input.limit !== undefined || input.query !== undefined)) {
754
- throw new Error("ranges and queries require the content facet");
755
- }
756
- if (hasIdentity && input.selector === undefined) {
757
- throw new Error("the identity facet requires a selector");
758
- }
759
- if (input.selector !== undefined && !hasContent && !hasIdentity) {
760
- throw new Error("selectors require the content or identity facet");
761
- }
762
- if (input.selector !== undefined) {
763
- const selector = parseSelector(input.selector);
764
- if (selector.kind === "symbol" && hasIdentity) {
765
- throw new Error("the identity facet does not accept symbol selectors");
766
- }
767
- if (selector.column !== undefined && !hasIdentity) {
768
- throw new Error("selector columns require the identity facet");
769
- }
770
- }
771
- const hasVision = input.visionMode !== undefined || input.visionLevel !== undefined;
772
- if (hasVision && !hasContent) {
773
- throw new Error("vision options require the content facet");
774
- }
775
- if (hasVision && hasSourceFacet) {
776
- throw new Error("vision options cannot be combined with source facets");
777
- }
778
- const args = [];
779
- if (input.readseekDir !== undefined)
780
- args.push("--readseek-dir", input.readseekDir);
781
- args.push("inspect", input.path);
782
- if (input.selector !== undefined)
783
- args.push(input.selector);
784
- const select = selectArg(input.select);
785
- if (select !== undefined)
786
- args.push("--select", select);
787
- pushOptionalString(args, "--query", input.query);
520
+ const args = start("show", "content", input);
521
+ at2(args, input.at);
788
522
  pushOptionalNumber(args, "--end", input.end);
789
- pushOptionalNumber(args, "--limit", input.limit);
523
+ limit2(args, input.limit);
524
+ language(args, input);
525
+ anchors(args, input);
526
+ return finish(args);
527
+ }
528
+ function buildShowStructureArgs(input) {
529
+ if (input.depth !== undefined && (!Number.isSafeInteger(input.depth) || input.depth < 0))
530
+ throw new Error("depth must be a non-negative integer");
531
+ const args = start("show", "structure", input);
532
+ at2(args, input.at);
790
533
  pushOptionalNumber(args, "--depth", input.depth);
791
- pushOptionalString(args, "--language", input.language);
792
- pushOptionalString(args, "--vision-mode", input.visionMode);
793
- pushOptionalString(args, "--vision-level", input.visionLevel);
794
- pushOptionalString(args, "--stdin-name", input.stdinName);
795
- args.push("--kdl");
534
+ pushOptionalString(args, "--kind", input.kind);
535
+ language(args, input);
536
+ if (input.outline)
537
+ args.push("--outline");
538
+ return finish(args);
539
+ }
540
+ function buildShowMetadataArgs(input) {
541
+ const args = start("show", "metadata", input);
542
+ language(args, input);
543
+ return finish(args);
544
+ }
545
+ function buildShowAssetsArgs(input) {
546
+ if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
547
+ throw new Error("assets requires a page, node, or asset selector");
548
+ const args = start("show", "assets", input);
549
+ at2(args, input.at);
550
+ return finish(args);
551
+ }
552
+ function buildCheckSyntaxArgs(input) {
553
+ const args = start("check", "syntax", input);
554
+ language(args, input);
555
+ return finish(args);
556
+ }
557
+ function visionArgs(verb, object, input) {
558
+ if (input.level !== undefined && !VISION_LEVELS.includes(input.level))
559
+ throw new Error("invalid vision level");
560
+ if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
561
+ throw new Error("vision requires a document selector");
562
+ const args = start(verb, object, input);
563
+ at2(args, input.at);
564
+ pushOptionalString(args, "--level", input.level);
796
565
  return args;
797
566
  }
798
- function buildExtractArgs(input) {
799
- validateTarget("extract", input.path, input.stdinName);
800
- if (input.selector.length === 0)
801
- throw new Error("extract requires an asset selector");
802
- if (!input.selector.startsWith("asset:") || input.selector === "asset:") {
803
- throw new Error("extract requires an asset: selector");
804
- }
805
- if (input.output.length === 0 || input.output === "-") {
806
- throw new Error("extract output must be a file path");
807
- }
808
- const args = [];
809
- if (input.readseekDir !== undefined)
810
- args.push("--readseek-dir", input.readseekDir);
811
- args.push("--output", input.output, "extract", input.path, input.selector);
812
- pushOptionalString(args, "--stdin-name", input.stdinName);
813
- return args;
567
+ function buildGenerateCaptionArgs(input) {
568
+ const args = visionArgs("generate", "caption", input);
569
+ if (input.include !== undefined) {
570
+ if (input.include.length === 0 || input.include.some((task) => task !== "text" && task !== "objects"))
571
+ throw new Error("include requires text or objects");
572
+ args.push("--include", input.include.join(","));
573
+ }
574
+ return finish(args);
575
+ }
576
+ function buildDetectObjectsArgs(input) {
577
+ return finish(visionArgs("detect", "objects", input));
578
+ }
579
+ function buildRecognizeTextArgs(input) {
580
+ return finish(visionArgs("recognize", "text", input));
581
+ }
582
+ function buildFindNodesArgs(input) {
583
+ const hasPattern = Boolean(input.pattern);
584
+ const hasRule = Boolean(input.rule);
585
+ const hasContext = Boolean(input.context);
586
+ if (hasRule) {
587
+ if (hasPattern || hasContext || input.selector)
588
+ throw new Error("rule cannot be combined with pattern, context, or selector");
589
+ } else if (!hasPattern && !hasContext) {
590
+ throw new Error("find nodes requires pattern, context, or rule");
591
+ }
592
+ if (hasContext && !input.selector) {
593
+ throw new Error("context requires selector");
594
+ }
595
+ if (hasPattern && hasContext) {
596
+ throw new Error("pattern and context cannot be combined");
597
+ }
598
+ const args = start("find", "nodes", input);
599
+ pushOptionalString(args, "--pattern", input.pattern);
600
+ pushOptionalString(args, "--context", input.context);
601
+ pushOptionalString(args, "--selector", input.selector);
602
+ pushOptionalString(args, "--rule", input.rule);
603
+ language(args, input);
604
+ limit2(args, input.limit);
605
+ appendGitScopeArgs(args, input);
606
+ anchors(args, input);
607
+ return finish(args);
814
608
  }
815
- function buildSearchArgs(input) {
816
- const args = ["search", input.pattern];
817
- pushOptionalString(args, "--path", input.path);
818
- pushOptionalString(args, "--language", input.language);
819
- pushOptionalNumber(args, "--limit", input.limit);
609
+ function buildFindTextArgs(input) {
610
+ const args = start("find", "text", input);
611
+ args.push("--text", input.text);
612
+ limit2(args, input.limit);
820
613
  appendGitScopeArgs(args, input);
821
- args.push("--kdl");
822
- return args;
614
+ return finish(args);
823
615
  }
824
- function buildDefArgs(input) {
825
- const args = ["def", input.name];
826
- pushOptionalString(args, "--path", input.path);
827
- pushOptionalString(args, "--language", input.language);
616
+ function buildFindDefinitionsArgs(input) {
617
+ const args = start("find", "definitions", input);
618
+ args.push("--name", input.name);
619
+ language(args, input);
828
620
  appendGitScopeArgs(args, input);
829
- args.push("--kdl");
830
- return args;
621
+ anchors(args, input);
622
+ return finish(args);
831
623
  }
832
- function buildRefsArgs(input) {
833
- const validated = validateRefsNameVsScope(input);
624
+ function buildFindReferencesArgs(input) {
625
+ const validated = validateReferencesSelection(input);
834
626
  if (!validated.ok)
835
627
  throw new Error(validated.error);
836
- const args = ["refs"];
837
- if (input.name !== undefined)
838
- args.push(input.name);
839
- pushOptionalString(args, "--path", input.path);
840
- if (input.scope) {
841
- args.push("--scope");
842
- pushOptionalNumber(args, "--line", input.line);
843
- pushOptionalNumber(args, "--column", input.column);
844
- }
845
- pushOptionalString(args, "--language", input.language);
628
+ if (input.at !== undefined && !["line", "hash"].includes(parseSelector(input.at).kind))
629
+ throw new Error("references requires a line or hash selector");
630
+ const args = start("find", "references", input);
631
+ pushOptionalString(args, "--name", input.name);
632
+ at2(args, input.at);
633
+ language(args, input);
846
634
  appendGitScopeArgs(args, input);
847
- args.push("--kdl");
848
- return args;
635
+ anchors(args, input);
636
+ return finish(args);
849
637
  }
850
- function buildEditArgs(input) {
851
- const args = ["edit", input.path];
852
- appendApplyAndPlanHash(args, {
853
- apply: input.apply,
854
- planHash: input.planHash,
855
- flagOrder: input.flagOrder ?? "apply-first"
856
- });
857
- pushOptionalString(args, "--language", input.language);
638
+ function buildCheckRulesArgs(input) {
639
+ const args = start("check", "rules", input);
640
+ args.push("--rules", input.rules);
641
+ appendGitScopeArgs(args, input);
642
+ return finish(args);
643
+ }
644
+ function buildEditTextArgs(input) {
645
+ const args = start("edit", "text", input);
646
+ appendApplyAndPlanHash(args, input);
647
+ language(args, input);
648
+ appendGitScopeArgs(args, input);
858
649
  if (input.requestFromStdin)
859
650
  args.push("--edits", "-");
860
- args.push("--kdl");
861
- return args;
862
- }
863
- function buildRenameArgs(input) {
864
- const args = ["rename", input.path, "--at", buildLineLocation(input.line, input.column), "--to", input.to];
651
+ return finish(args);
652
+ }
653
+ function buildRenameSymbolArgs(input) {
654
+ if (!["line", "hash"].includes(parseSelector(input.at).kind))
655
+ throw new Error("rename requires a line or hash selector");
656
+ const args = start("rename", "symbol", input);
657
+ at2(args, input.at);
658
+ args.push("--to", input.to);
865
659
  if (input.workspace !== undefined)
866
660
  args.push(`--workspace=${input.workspace}`);
867
- appendApplyAndPlanHash(args, {
868
- apply: input.apply,
869
- planHash: input.planHash,
870
- flagOrder: input.flagOrder ?? "plan-hash-first"
871
- });
872
- pushOptionalString(args, "--language", input.language);
661
+ appendApplyAndPlanHash(args, { ...input, flagOrder: input.flagOrder ?? "plan-hash-first" });
662
+ language(args, input);
873
663
  appendGitScopeArgs(args, input);
874
- args.push("--kdl");
875
- return args;
876
- }
877
- // ../readseek-api/src/platform.ts
878
- import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
879
- import { createRequire } from "node:module";
880
- import path from "node:path";
881
- var READSEEK_BINARY_ENV = "READSEEK_BINARY";
882
- var READSEEK_PLATFORM_PACKAGES = {
883
- "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
884
- "linux-arm64": "@jarkkojs/readseek-linux-arm64",
885
- "linux-x64": "@jarkkojs/readseek-linux-x64",
886
- "win32-x64": "@jarkkojs/readseek-win32-x64"
887
- };
888
- var resolveFromPackage = createRequire(import.meta.url).resolve;
889
- function readSeekBinaryName(platform = process.platform) {
890
- if (platform === "linux")
891
- return "readseek.AppImage";
892
- return platform === "win32" ? "readseek.exe" : "readseek";
664
+ return finish(args);
893
665
  }
894
- function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
895
- return `${platform}-${arch}`;
896
- }
897
- function resolveReadSeekBinaryOverride(environment = process.env) {
898
- const binaryPath = environment[READSEEK_BINARY_ENV];
899
- if (binaryPath === undefined)
900
- return null;
901
- if (!path.isAbsolute(binaryPath)) {
902
- throw new Error(`${READSEEK_BINARY_ENV} must be an absolute path to the readseek executable`);
903
- }
666
+ // ../readseek-api/src/client-output.ts
667
+ import { Type } from "@sinclair/typebox";
668
+ import { AssertError, Value } from "@sinclair/typebox/value";
669
+ var OPERATIONS = ["Clone", "Clean", "Default", "Assert"];
670
+ function parse(schema, value, what) {
904
671
  try {
905
- if (!statSync(binaryPath).isFile())
906
- throw new Error("not a regular file");
907
- accessSync(binaryPath, fsConstants.X_OK);
672
+ return Value.Parse(OPERATIONS, schema, value);
908
673
  } catch (error) {
909
- throw new Error(`${READSEEK_BINARY_ENV} does not point to an executable file: ${binaryPath}`, {
910
- cause: error
911
- });
912
- }
913
- return binaryPath;
914
- }
915
- function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, options = {}) {
916
- const resolve = typeof resolver === "function" ? resolver : resolver.resolve.bind(resolver);
917
- const platform = options.platform ?? process.platform;
918
- const arch = options.arch ?? process.arch;
919
- const key = readSeekPlatformKey(platform, arch);
920
- const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
921
- if (!platformPackage) {
922
- const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
923
- throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
924
- }
925
- const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
926
- let packageJsonPath;
927
- try {
928
- const metaPackageJson = resolve(`${metaPackage}/package.json`);
929
- const metaPackageDir = path.dirname(metaPackageJson);
930
- packageJsonPath = resolve(`${platformPackage}/package.json`, {
931
- paths: [metaPackageDir]
932
- });
933
- } catch {
934
- packageJsonPath = resolve(`${platformPackage}/package.json`);
674
+ if (!(error instanceof AssertError))
675
+ throw error;
676
+ const detail = error.error;
677
+ throw new Error(`invalid readseek ${what}${detail?.path ?? ""}: ${detail?.message ?? "invalid value"}`);
935
678
  }
936
- const binaryPath = path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
937
- accessSync(binaryPath, fsConstants.X_OK);
938
- return binaryPath;
939
679
  }
940
- // ../readseek-api/src/error.ts
941
- var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
942
- function cleanReadSeekErrorText(stderr) {
943
- return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
944
- }
945
- // ../readseek-api/src/kdl.ts
946
- import { parse } from "@bgotink/kdl";
947
- function convertNode(node) {
948
- if (node.getTag() !== null)
949
- throw new Error("KDL node annotations are not supported");
950
- const properties = new Map;
951
- for (const entry of node.entries) {
952
- const name = entry.getName();
953
- if (name === null)
954
- throw new Error("KDL positional arguments are not supported");
955
- if (properties.has(name))
956
- throw new Error(`KDL duplicate property '${name}'`);
957
- if (entry.getTag() !== null)
958
- throw new Error("KDL value annotations are not supported");
959
- const value = entry.getValue();
960
- if (value === null)
961
- throw new Error("KDL null values are not supported");
962
- if (typeof value === "number" && !Number.isFinite(value)) {
963
- throw new Error("KDL number out of range");
964
- }
965
- properties.set(name, value);
966
- }
967
- return {
968
- name: node.getName(),
969
- properties,
970
- children: node.children?.nodes.map(convertNode) ?? []
971
- };
972
- }
973
- function parseKdlDocument(text) {
974
- const document = parse(text);
975
- if (document.nodes.length !== 1)
976
- throw new Error("KDL document must contain exactly one root node");
977
- return convertNode(document.nodes[0]);
680
+ var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
681
+ var Str = Type.String();
682
+ var Bool = Type.Boolean();
683
+ var OptStr = Type.Optional(Str);
684
+ var OptInt = Type.Optional(Int);
685
+ var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
686
+ var Hashlines = Type.Array(Hashline);
687
+ var ReadOutput = Type.Object({
688
+ file: Str,
689
+ language: Str,
690
+ line_count: Int,
691
+ file_hash: Str,
692
+ start_line: Int,
693
+ end_line: Int,
694
+ hashlines: Hashlines,
695
+ truncated: Type.Optional(Bool)
696
+ });
697
+ var SymbolOutput = Type.Object({
698
+ kind: Str,
699
+ name: Str,
700
+ qualified_name: OptStr,
701
+ start_line: Int,
702
+ end_line: Int,
703
+ start_hash: OptStr,
704
+ end_hash: OptStr,
705
+ start_anchor: OptStr,
706
+ end_anchor: OptStr
707
+ });
708
+ function mapNodeShape(child) {
709
+ return Type.Object({
710
+ kind: Str,
711
+ name: Str,
712
+ start_line: Int,
713
+ end_line: Int,
714
+ start_anchor: Str,
715
+ end_anchor: Str,
716
+ children: Type.Array(child)
717
+ });
978
718
  }
979
-
980
- // ../readseek-api/src/output-kdl.ts
981
- function properties(node) {
982
- return Object.fromEntries(node.properties);
719
+ function recursiveStructureNode() {
720
+ return Type.Recursive((self) => mapNodeShape(self));
983
721
  }
984
- function children(node, name) {
985
- return node.children.filter((child) => child.name === name);
722
+ var StructureNode = typeof Type.Recursive === "function" ? recursiveStructureNode() : Type.Cyclic({ StructureNode: mapNodeShape(Type.Ref("StructureNode")) }, "StructureNode");
723
+ var StructureOutput = Type.Object({
724
+ file: Str,
725
+ language: Str,
726
+ line_count: Int,
727
+ file_hash: Str,
728
+ root: Type.Optional(StructureNode),
729
+ symbols: Type.Array(SymbolOutput)
730
+ });
731
+ var SearchSpan = {
732
+ start_byte: OptInt,
733
+ end_byte: OptInt,
734
+ text: OptStr,
735
+ start_line: Int,
736
+ end_line: Int,
737
+ start_hash: Str,
738
+ end_hash: Str,
739
+ hashlines: Hashlines
740
+ };
741
+ var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
742
+ var SearchMatch = Type.Object({
743
+ pattern_index: Type.Integer({ default: 0 }),
744
+ ...SearchSpan,
745
+ captures: Type.Array(SearchCapture)
746
+ });
747
+ var SearchFileOutput = Type.Object({
748
+ file: Str,
749
+ language: Str,
750
+ file_hash: Str,
751
+ matches: Type.Array(SearchMatch)
752
+ });
753
+ var SearchEnvelope = Type.Object({
754
+ results: Type.Array(SearchFileOutput),
755
+ returned_matches: OptInt,
756
+ truncated: Type.Optional(Bool),
757
+ byte_basis: OptStr
758
+ });
759
+ var RefsEnvelope = Type.Object({
760
+ references: Type.Array(Type.Object({
761
+ file: Str,
762
+ line: Int,
763
+ column: Int,
764
+ line_hash: Str,
765
+ text: Str,
766
+ symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
767
+ }))
768
+ });
769
+ var Diagnostic = Type.Object({
770
+ kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
771
+ start_line: Int,
772
+ end_line: Int
773
+ });
774
+ var CheckEnvelope = Type.Object({
775
+ file_hash: OptStr,
776
+ error_count: Int,
777
+ missing_count: Int,
778
+ diagnostics: Type.Array(Diagnostic)
779
+ });
780
+ var DetectedObject = Type.Object({
781
+ label: Str,
782
+ bbox: Type.Tuple([Int, Int, Int, Int])
783
+ });
784
+ var SourceDetection = Type.Object({
785
+ kind: Type.Literal("source"),
786
+ type: Str,
787
+ file: Str,
788
+ language: Str,
789
+ engine: OptStr,
790
+ supported: Bool,
791
+ mime: OptStr,
792
+ syntax: OptStr
793
+ });
794
+ var ImageDetection = Type.Object({
795
+ kind: Type.Literal("image"),
796
+ type: Str,
797
+ file: Str,
798
+ mime: OptStr,
799
+ format: Str,
800
+ width: Int,
801
+ height: Int,
802
+ animated: Bool,
803
+ ocr: OptStr,
804
+ caption: OptStr,
805
+ objects: Type.Optional(Type.Array(DetectedObject))
806
+ });
807
+ var TextDetection = Type.Object({
808
+ kind: Type.Literal("text"),
809
+ type: Str,
810
+ file: Str,
811
+ mime: OptStr
812
+ });
813
+ var PdfDetection = Type.Object({
814
+ kind: Type.Literal("pdf"),
815
+ type: Type.Literal("application/pdf"),
816
+ file: Str,
817
+ mime: OptStr,
818
+ format: Type.Literal("pdf"),
819
+ pages: Int
820
+ });
821
+ var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
822
+ var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
823
+ var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
824
+ var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
825
+ var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
826
+ var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
827
+ var PdfMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
828
+ var DocumentCache = Type.Object({
829
+ content_hash: Str,
830
+ state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
831
+ });
832
+ var Identifier = Type.Object({
833
+ text: Str,
834
+ start_column: Int,
835
+ end_column: Int,
836
+ start_byte: Int,
837
+ end_byte: Int
838
+ });
839
+ var CursorEnvelope = Type.Object({
840
+ file: Str,
841
+ language: Str,
842
+ engine: OptStr,
843
+ line_count: Int,
844
+ file_hash: Str,
845
+ line: Int,
846
+ column: Int,
847
+ line_hash: Str,
848
+ identifier: Type.Optional(Identifier),
849
+ symbol: Type.Optional(Type.Object({
850
+ name: Str,
851
+ kind: Str,
852
+ qualified_name: Str,
853
+ start_line: Int,
854
+ end_line: Int
855
+ }))
856
+ });
857
+ var EditOutputSchema = Type.Object({
858
+ file: Str,
859
+ before_hash: Str,
860
+ file_hash: Str,
861
+ plan_hash: Str,
862
+ applied: Bool,
863
+ changed: Bool,
864
+ edit_count: Int,
865
+ before_content: OptStr,
866
+ content: OptStr
867
+ });
868
+ var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
869
+ var RenameEditSchema = Type.Object({
870
+ line: Int,
871
+ start_column: Int,
872
+ end_column: Int,
873
+ start_byte: Int,
874
+ end_byte: Int,
875
+ occurrence: Str,
876
+ line_hash: Str,
877
+ text: Str
878
+ });
879
+ var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
880
+ var Edits = Type.Array(RenameEditSchema, { default: [] });
881
+ var RenameFileOutputSchema = Type.Object({
882
+ file: Str,
883
+ language: Str,
884
+ engine: OptStr,
885
+ file_hash: Str,
886
+ conflicts: Conflicts,
887
+ edits: Edits
888
+ });
889
+ var RenameOutputSchema = Type.Object({
890
+ file: Str,
891
+ language: Str,
892
+ engine: OptStr,
893
+ file_hash: Str,
894
+ old_name: Str,
895
+ new_name: Str,
896
+ plan_hash: Str,
897
+ applied: Bool,
898
+ conflicts: Conflicts,
899
+ edits: Edits,
900
+ others: Type.Array(RenameFileOutputSchema, { default: [] })
901
+ });
902
+ var DefEnvelope = Type.Object({
903
+ locations: Type.Array(Type.Object({
904
+ file: Str,
905
+ line: Int,
906
+ column: Int,
907
+ line_hash: Str,
908
+ text: Str,
909
+ kind: OptStr,
910
+ name: OptStr,
911
+ qualified_name: OptStr
912
+ }))
913
+ });
914
+ function parseReadOutput(value) {
915
+ return parse(ReadOutput, value, "read output");
986
916
  }
987
- function child(node, name) {
988
- return node.children.find((candidate) => candidate.name === name);
917
+ function parseStructureOutput(value) {
918
+ return parse(StructureOutput, value, "structure output");
989
919
  }
990
- function requireProperty(node, name) {
991
- const value = node.properties.get(name);
992
- if (value === undefined)
993
- throw new Error(`invalid readseek KDL: ${node.name} is missing '${name}'`);
994
- return value;
920
+ function parseSearchOutput(value) {
921
+ const envelope = parse(SearchEnvelope, value, "search output");
922
+ const results = envelope.results;
923
+ if (envelope.returned_matches !== undefined)
924
+ results.returned_matches = envelope.returned_matches;
925
+ if (envelope.truncated !== undefined)
926
+ results.truncated = envelope.truncated;
927
+ if (envelope.byte_basis !== undefined)
928
+ results.byte_basis = envelope.byte_basis;
929
+ return results;
995
930
  }
996
- function line(node) {
997
- const anchor = requireProperty(node, "anchor");
998
- const content = requireProperty(node, "content");
999
- if (typeof anchor !== "string" || typeof content !== "string") {
1000
- throw new Error("invalid readseek KDL line");
1001
- }
1002
- const match = /^(\d+):([0-9a-f]+)$/.exec(anchor);
1003
- if (!match)
1004
- throw new Error(`invalid readseek KDL line anchor: ${anchor}`);
1005
- return {
1006
- line: Number(match[1]),
1007
- hash: match[2],
1008
- text: content
1009
- };
931
+ function parseRefsOutput(value) {
932
+ return parse(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
933
+ ...reference,
934
+ enclosingSymbol: symbol?.qualified_name
935
+ }));
1010
936
  }
1011
- function mapNode(node) {
937
+ function parseCheckOutput(value) {
938
+ const output = parse(CheckEnvelope, value, "check output");
1012
939
  return {
1013
- ...properties(node),
1014
- children: children(node, "node").map(mapNode)
940
+ file_hash: output.file_hash,
941
+ errorCount: output.error_count,
942
+ missingCount: output.missing_count,
943
+ diagnostics: output.diagnostics
1015
944
  };
1016
945
  }
1017
- function sourceContent(node) {
1018
- const result = properties(node);
1019
- result.hashlines = children(node, "line").map(line);
1020
- const objects = children(node, "object");
1021
- if (objects.length > 0) {
1022
- result.objects = objects.map((object) => ({
1023
- label: requireProperty(object, "label"),
1024
- bbox: [
1025
- requireProperty(object, "xmin"),
1026
- requireProperty(object, "ymin"),
1027
- requireProperty(object, "xmax"),
1028
- requireProperty(object, "ymax")
1029
- ]
1030
- }));
946
+ function parseDetectOutput(value) {
947
+ const output = value ?? {};
948
+ if (output.type === "application/pdf") {
949
+ return { kind: "pdf", ...parse(PdfDetectionInput, value, "detect output") };
1031
950
  }
1032
- return result;
951
+ if (output.width !== undefined || output.height !== undefined) {
952
+ return { kind: "image", ...parse(ImageDetectionInput, value, "detect output") };
953
+ }
954
+ if (output.language !== undefined) {
955
+ return { kind: "source", ...parse(SourceDetectionInput, value, "detect output") };
956
+ }
957
+ return { kind: "text", ...parse(TextDetectionInput, value, "detect output") };
1033
958
  }
1034
- function sourceMap(node) {
1035
- const root = child(node, "node");
959
+ function parseTextContent(value) {
960
+ const output = value ?? {};
961
+ if (output.hashlines === undefined || output.start_line !== undefined)
962
+ return parseReadOutput(value);
963
+ const spanned = parse(UnspannedContent, value, "content");
1036
964
  return {
1037
- ...properties(node),
1038
- ...root ? { root: mapNode(root) } : {},
1039
- symbols: children(node, "symbol").map(properties)
965
+ ...spanned,
966
+ start_line: spanned.hashlines[0]?.line ?? 1,
967
+ end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
1040
968
  };
1041
969
  }
1042
- function diagnostics(node) {
970
+ function parseCursorOutput(value) {
971
+ return parse(CursorEnvelope, value, "cursor output");
972
+ }
973
+ function parsePdfMetadata(value) {
974
+ if (!value || typeof value !== "object")
975
+ return;
976
+ if (value.format !== "pdf")
977
+ return;
978
+ const document = parse(PdfMetadata, value, "inspect metadata");
979
+ return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
980
+ }
981
+ function parseEditOutput(value) {
982
+ return parse(EditOutputSchema, value, "edit output");
983
+ }
984
+ function parseRenameOutput(value) {
985
+ return parse(RenameOutputSchema, value, "rename output");
986
+ }
987
+ function parseDefOutput(value) {
988
+ return parse(DefEnvelope, value, "def output").locations;
989
+ }
990
+ var Bbox = Type.Object({ x: Type.Number(), y: Type.Number(), width: Type.Number(), height: Type.Number() });
991
+ var Position = { page: OptInt, destination: OptStr, bbox: Type.Optional(Bbox) };
992
+ var Provenance = { inferred: Type.Optional(Bool), model: OptStr, level: OptStr, task: OptStr };
993
+ var ImageDescriptor = Type.Object({
994
+ id: OptStr,
995
+ kind: Type.Optional(Type.Literal("image")),
996
+ content_identity: OptStr,
997
+ path: Str,
998
+ mime: Str,
999
+ width: Int,
1000
+ height: Int,
1001
+ caption: OptStr,
1002
+ ocr: OptStr,
1003
+ objects: Type.Optional(Type.Array(DetectedObject)),
1004
+ ...Provenance
1005
+ });
1006
+ var DocumentNode = Type.Object({
1007
+ id: Str,
1008
+ kind: Str,
1009
+ parent_id: OptStr,
1010
+ title: OptStr,
1011
+ level: OptInt,
1012
+ column: OptInt,
1013
+ text: OptStr,
1014
+ ...Position
1015
+ });
1016
+ var DocumentAsset = Type.Object({
1017
+ id: Str,
1018
+ kind: Str,
1019
+ mime: Str,
1020
+ path: OptStr,
1021
+ content_identity: OptStr,
1022
+ bytes: OptInt,
1023
+ ...Position
1024
+ });
1025
+ var DocumentVision = Type.Object({
1026
+ asset_id: Str,
1027
+ mode: OptStr,
1028
+ caption: OptStr,
1029
+ ocr: OptStr,
1030
+ objects: Type.Optional(Type.Array(DetectedObject)),
1031
+ caption_requested: Type.Optional(Bool),
1032
+ objects_requested: Type.Optional(Bool),
1033
+ ocr_requested: Type.Optional(Bool),
1034
+ ...Provenance
1035
+ });
1036
+ var DocumentOutput = Type.Object({
1037
+ id: OptStr,
1038
+ format: Str,
1039
+ source: Str,
1040
+ title: OptStr,
1041
+ pages: Int,
1042
+ nodes: Type.Array(DocumentNode, { default: [] }),
1043
+ assets: Type.Array(DocumentAsset, { default: [] }),
1044
+ vision: Type.Array(DocumentVision, { default: [] })
1045
+ });
1046
+ var TextHit = Type.Object({
1047
+ line: Int,
1048
+ column: Int,
1049
+ text: Str,
1050
+ start_line: Int,
1051
+ end_line: Int,
1052
+ start_byte: Int,
1053
+ end_byte: Int,
1054
+ hashlines: Hashlines
1055
+ });
1056
+ var DocumentHit = Type.Object({ node_id: Str, kind: Str, snippet: Str, page: OptInt });
1057
+ var TextSearchOutput = Type.Object({
1058
+ text: Str,
1059
+ mode: Type.Literal("literal"),
1060
+ total_matches: Int,
1061
+ returned_matches: Int,
1062
+ truncated: Bool,
1063
+ results: Type.Array(Type.Object({ file: Str, file_hash: Str, matches: Type.Array(TextHit) })),
1064
+ documents: Type.Array(Type.Object({ file: Str, content_hash: Str, hits: Type.Array(DocumentHit) }))
1065
+ });
1066
+ var RuleFinding = Type.Object({
1067
+ name: Str,
1068
+ message: Str,
1069
+ path: Str,
1070
+ language: Str,
1071
+ start_byte: Int,
1072
+ end_byte: Int,
1073
+ line: Int,
1074
+ column: Int,
1075
+ end_line: Int,
1076
+ end_column: Int
1077
+ });
1078
+ var RulesOutput = Type.Object({
1079
+ complete: Bool,
1080
+ check_count: Int,
1081
+ file_count: Int,
1082
+ violation_count: Int,
1083
+ diagnostics: Type.Array(RuleFinding)
1084
+ });
1085
+ function parseDocumentOutput(value) {
1086
+ return parse(DocumentOutput, value, "document output");
1087
+ }
1088
+ function parseContentOutput(value) {
1089
+ const output = value;
1090
+ if (output?.format !== undefined)
1091
+ return parseDocumentOutput(value);
1092
+ if (!output || output.metadata === undefined || output.content === undefined)
1093
+ throw new Error("invalid readseek content: missing metadata or content");
1094
+ const metadata = parseDetectOutput(output.metadata);
1043
1095
  return {
1044
- ...properties(node),
1045
- diagnostics: children(node, "diagnostic").map(properties)
1096
+ metadata,
1097
+ content: metadata.kind === "image" ? parse(ImageDescriptor, output.content, "image descriptor") : parseTextContent(output.content),
1098
+ ...output.cursor !== undefined ? { cursor: parseCursorOutput(output.cursor) } : {}
1046
1099
  };
1047
1100
  }
1048
- function identity(node) {
1049
- const result = properties(node);
1050
- const identifier = child(node, "identifier");
1051
- const symbol = child(node, "symbol");
1052
- if (identifier)
1053
- result.identifier = properties(identifier);
1054
- if (symbol)
1055
- result.symbol = properties(symbol);
1056
- return result;
1101
+ function parseStructureEnvelope(value) {
1102
+ const output = value;
1103
+ if (output?.format !== undefined)
1104
+ return parseDocumentOutput(value);
1105
+ if (!output || output.structure === undefined)
1106
+ throw new Error("invalid readseek structure: missing structure");
1107
+ return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
1108
+ }
1109
+ function parseTextSearchOutput(value) {
1110
+ return parse(TextSearchOutput, value, "text search output");
1111
+ }
1112
+ function parseRulesOutput(value) {
1113
+ return parse(RulesOutput, value, "rules output");
1114
+ }
1115
+ var SourceAssetsOutput = Type.Object({ file: OptStr, metadata: Type.Optional(Detection), assets: Type.Array(DocumentAsset, { default: [] }) });
1116
+ function parseAssetsOutput(value) {
1117
+ const raw = value;
1118
+ if (raw?.format !== undefined)
1119
+ return parseDocumentOutput(value);
1120
+ if (!raw || raw.file === undefined && raw.metadata === undefined)
1121
+ throw new Error("invalid readseek assets output: missing file or metadata");
1122
+ return parse(SourceAssetsOutput, { ...raw, ...raw.metadata !== undefined ? { metadata: parseDetectOutput(raw.metadata) } : {} }, "assets output");
1057
1123
  }
1058
- function sourceDocument(root) {
1059
- const result = {};
1060
- const metadata = child(root, "metadata");
1061
- const cache = child(root, "cache");
1062
- const content = child(root, "content");
1063
- const map = child(root, "map");
1064
- const diagnosticOutput = child(root, "diagnostics");
1065
- const identityOutput = child(root, "identity");
1066
- if (metadata)
1067
- result.metadata = properties(metadata);
1068
- if (cache)
1069
- result.cache = properties(cache);
1070
- if (content)
1071
- result.content = sourceContent(content);
1072
- if (map)
1073
- result.map = sourceMap(map);
1074
- if (diagnosticOutput)
1075
- result.diagnostics = diagnostics(diagnosticOutput);
1076
- if (identityOutput)
1077
- result.identity = identity(identityOutput);
1124
+
1125
+ // ../readseek-api/src/edit-plan.ts
1126
+ import { Document, Node, format } from "@bgotink/kdl";
1127
+ function node(name, properties, children = []) {
1128
+ const result = Node.create(name);
1129
+ for (const [key, value] of Object.entries(properties))
1130
+ result.setProperty(key, value);
1131
+ if (children.length > 0)
1132
+ result.children = new Document(children);
1078
1133
  return result;
1079
1134
  }
1080
- function hit(node) {
1081
- return {
1082
- ...properties(node),
1083
- hashlines: children(node, "line").map(line),
1084
- captures: children(node, "capture").map((capture) => ({
1085
- ...properties(capture),
1086
- hashlines: children(capture, "line").map(line)
1087
- }))
1088
- };
1135
+ function editNode(edit) {
1136
+ if ("set_line" in edit) {
1137
+ const value = edit.set_line;
1138
+ return node("set-line", { anchor: value.anchor, text: value.new_text });
1139
+ }
1140
+ if ("replace_lines" in edit) {
1141
+ const value = edit.replace_lines;
1142
+ return node("replace-lines", { start: value.start_anchor, end: value.end_anchor, text: value.new_text });
1143
+ }
1144
+ if ("insert_after" in edit) {
1145
+ const value = edit.insert_after;
1146
+ return node("insert-after", { anchor: value.anchor, text: value.new_text });
1147
+ }
1148
+ if ("replace_symbol" in edit) {
1149
+ const value = edit.replace_symbol;
1150
+ return node("replace-symbol", { symbol: value.symbol, body: value.new_body });
1151
+ }
1152
+ const value = edit.replace;
1153
+ return node("replace", {
1154
+ old: value.old_text,
1155
+ new: value.new_text,
1156
+ ...value.all === undefined ? {} : { all: value.all }
1157
+ });
1089
1158
  }
1090
- function search(root) {
1159
+ function serializeEditPlan(edits) {
1160
+ const validation = validateExactlyOneEditVariant(edits);
1161
+ if (!validation.ok)
1162
+ throw new Error(validation.error);
1163
+ return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
1164
+ }
1165
+
1166
+ // ../readseek-api/src/client.ts
1167
+ function classifyReadSeekFailure(err) {
1168
+ const failure = err;
1169
+ const message = String(failure?.message ?? err);
1170
+ const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
1171
+ if (missing) {
1172
+ return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1173
+ }
1174
+ return { code: "readseek-execution-error", message };
1175
+ }
1176
+ function runOptions(options, vision = false) {
1091
1177
  return {
1092
- ...properties(root),
1093
- results: children(root, "match").map((match) => ({
1094
- ...properties(match),
1095
- matches: children(match, "hit").map(hit)
1096
- })),
1097
- documents: children(root, "document").map((documentNode) => ({
1098
- ...properties(documentNode),
1099
- hits: children(documentNode, "hit").map(properties)
1100
- }))
1178
+ signal: options.signal,
1179
+ stdin: options.stdin,
1180
+ timeoutMs: options.timeoutMs,
1181
+ cancelable: options.cancelable,
1182
+ ...vision ? { vision: true } : {}
1101
1183
  };
1102
1184
  }
1103
- function references(root) {
1104
- const parsed = children(root, "reference").map((reference) => {
1105
- const sourceLine = child(reference, "line");
1106
- if (!sourceLine)
1107
- throw new Error("invalid readseek KDL reference: missing line");
1108
- const parsedLine = line(sourceLine);
1109
- const symbol = child(reference, "symbol");
1110
- return {
1111
- ...properties(reference),
1112
- line_hash: parsedLine.hash,
1113
- text: parsedLine.text,
1114
- ...symbol ? { symbol: properties(symbol) } : {}
1115
- };
1116
- });
1185
+ function createReadSeekClient(runner) {
1117
1186
  return {
1118
- references: parsed,
1119
- locations: parsed.map((reference) => {
1120
- const { symbol, ...location } = reference;
1121
- return symbol && typeof symbol === "object" ? { ...location, ...symbol } : location;
1122
- })
1187
+ async showContent(path, options = {}) {
1188
+ const value = await runner.kdl(buildShowContentArgs({ ...options, path }), runOptions(options));
1189
+ return parseContentOutput(value);
1190
+ },
1191
+ async showStructure(path, options = {}) {
1192
+ const value = await runner.kdl(buildShowStructureArgs({ ...options, path }), runOptions(options));
1193
+ return parseStructureEnvelope(value);
1194
+ },
1195
+ async showMetadata(path, options = {}) {
1196
+ const value = await runner.kdl(buildShowMetadataArgs({ ...options, path }), runOptions(options));
1197
+ return parsePdfMetadata(value) ?? parseDetectOutput(value?.metadata);
1198
+ },
1199
+ async showAssets(path, options = {}) {
1200
+ const value = await runner.kdl(buildShowAssetsArgs({ ...options, path }), runOptions(options));
1201
+ return parseAssetsOutput(value);
1202
+ },
1203
+ async checkSyntax(path, options = {}) {
1204
+ const value = await runner.kdl(buildCheckSyntaxArgs({ ...options, path }), runOptions(options));
1205
+ return parseCheckOutput(value?.diagnostics ?? value);
1206
+ },
1207
+ async checkRules(path, options) {
1208
+ const value = await runner.kdl(buildCheckRulesArgs({ ...options, path }), runOptions(options));
1209
+ return parseRulesOutput(value);
1210
+ },
1211
+ async generateCaption(path, options = {}) {
1212
+ const value = await runner.kdl(buildGenerateCaptionArgs({ ...options, path }), runOptions(options, true));
1213
+ return parseContentOutput(value);
1214
+ },
1215
+ async detectObjects(path, options = {}) {
1216
+ const value = await runner.kdl(buildDetectObjectsArgs({ ...options, path }), runOptions(options, true));
1217
+ return parseContentOutput(value);
1218
+ },
1219
+ async recognizeText(path, options = {}) {
1220
+ const value = await runner.kdl(buildRecognizeTextArgs({ ...options, path }), runOptions(options, true));
1221
+ return parseContentOutput(value);
1222
+ },
1223
+ async findNodes(path, options) {
1224
+ const value = await runner.kdl(buildFindNodesArgs({ ...options, path }), runOptions(options));
1225
+ return parseSearchOutput(value);
1226
+ },
1227
+ async findText(path, options) {
1228
+ const value = await runner.kdl(buildFindTextArgs({ ...options, path }), runOptions(options));
1229
+ return parseTextSearchOutput(value);
1230
+ },
1231
+ async findDefinitions(path, options) {
1232
+ const value = await runner.kdl(buildFindDefinitionsArgs({ ...options, path }), runOptions(options));
1233
+ return parseDefOutput(value);
1234
+ },
1235
+ async findReferences(path, options) {
1236
+ const value = await runner.kdl(buildFindReferencesArgs({ ...options, path }), runOptions(options));
1237
+ return parseRefsOutput(value);
1238
+ },
1239
+ async editText(path, edits, options = {}) {
1240
+ const stdin = serializeEditPlan(edits);
1241
+ return parseEditOutput(await runner.kdl(buildEditTextArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
1242
+ },
1243
+ async renameSymbol(path, options) {
1244
+ const value = await runner.kdl(buildRenameSymbolArgs({ ...options, path }), runOptions(options));
1245
+ return parseRenameOutput(value);
1246
+ }
1123
1247
  };
1124
1248
  }
1125
- function definitions(root) {
1126
- return {
1127
- locations: children(root, "definition").map((definition) => {
1128
- const symbol = child(definition, "symbol");
1129
- const sourceLine = child(definition, "line");
1130
- if (!symbol || !sourceLine) {
1131
- throw new Error("invalid readseek KDL definition: missing symbol or line");
1132
- }
1133
- const symbolProperties = properties(symbol);
1134
- const parsedLine = line(sourceLine);
1135
- const definitionColumn = definition.properties.get("column");
1136
- const nameByte = symbol.properties.get("name_byte");
1137
- const startByte = symbol.properties.get("start_byte");
1138
- let column = 1;
1139
- if (typeof definitionColumn === "number") {
1140
- column = definitionColumn;
1141
- } else if (typeof nameByte === "number" && typeof startByte === "number") {
1142
- column = nameByte - startByte + 1;
1143
- }
1144
- return {
1145
- file: requireProperty(definition, "file"),
1146
- line: parsedLine.line,
1147
- column,
1148
- line_hash: parsedLine.hash,
1149
- text: parsedLine.text,
1150
- kind: symbolProperties.kind,
1151
- name: symbolProperties.name,
1152
- qualified_name: symbolProperties.qualified_name
1153
- };
1154
- })
1155
- };
1249
+ // ../readseek-api/src/platform.ts
1250
+ import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
1251
+ import { createRequire } from "node:module";
1252
+ import path2 from "node:path";
1253
+ var READSEEK_BINARY_ENV = "READSEEK_BINARY";
1254
+ var READSEEK_PLATFORM_PACKAGES = {
1255
+ "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
1256
+ "linux-arm64": "@jarkkojs/readseek-linux-arm64",
1257
+ "linux-x64": "@jarkkojs/readseek-linux-x64",
1258
+ "win32-x64": "@jarkkojs/readseek-win32-x64"
1259
+ };
1260
+ var resolveFromPackage = createRequire(import.meta.url).resolve;
1261
+ function readSeekBinaryName(platform = process.platform) {
1262
+ if (platform === "linux")
1263
+ return "readseek.AppImage";
1264
+ return platform === "win32" ? "readseek.exe" : "readseek";
1156
1265
  }
1157
- function edit(root) {
1158
- const result = properties(root);
1159
- for (const name of ["before_content", "content"]) {
1160
- const contentNode = child(root, name);
1161
- if (contentNode)
1162
- result[name] = requireProperty(contentNode, "content");
1266
+ function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
1267
+ return `${platform}-${arch}`;
1268
+ }
1269
+ function resolveReadSeekBinaryOverride(environment = process.env) {
1270
+ const binaryPath = environment[READSEEK_BINARY_ENV];
1271
+ if (binaryPath === undefined)
1272
+ return null;
1273
+ if (!path2.isAbsolute(binaryPath)) {
1274
+ throw new Error(`${READSEEK_BINARY_ENV} must be an absolute path to the readseek executable`);
1163
1275
  }
1164
- return result;
1276
+ try {
1277
+ if (!statSync(binaryPath).isFile())
1278
+ throw new Error("not a regular file");
1279
+ accessSync(binaryPath, fsConstants.X_OK);
1280
+ } catch (error) {
1281
+ throw new Error(`${READSEEK_BINARY_ENV} does not point to an executable file: ${binaryPath}`, {
1282
+ cause: error
1283
+ });
1284
+ }
1285
+ return binaryPath;
1165
1286
  }
1166
- function renameEdit(node) {
1167
- const sourceLine = child(node, "line");
1168
- if (!sourceLine)
1169
- throw new Error("invalid readseek KDL rename edit: missing line");
1287
+ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, options = {}) {
1288
+ const resolve = typeof resolver === "function" ? resolver : resolver.resolve.bind(resolver);
1289
+ const platform = options.platform ?? process.platform;
1290
+ const arch = options.arch ?? process.arch;
1291
+ const key = readSeekPlatformKey(platform, arch);
1292
+ const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
1293
+ if (!platformPackage) {
1294
+ const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
1295
+ throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
1296
+ }
1297
+ const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
1298
+ let packageJsonPath;
1299
+ try {
1300
+ const metaPackageJson = resolve(`${metaPackage}/package.json`);
1301
+ const metaPackageDir = path2.dirname(metaPackageJson);
1302
+ packageJsonPath = resolve(`${platformPackage}/package.json`, {
1303
+ paths: [metaPackageDir]
1304
+ });
1305
+ } catch {
1306
+ packageJsonPath = resolve(`${platformPackage}/package.json`);
1307
+ }
1308
+ const binaryPath = path2.join(path2.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
1309
+ accessSync(binaryPath, fsConstants.X_OK);
1310
+ return binaryPath;
1311
+ }
1312
+ // ../readseek-api/src/error.ts
1313
+ var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
1314
+ function cleanReadSeekErrorText(stderr) {
1315
+ return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
1316
+ }
1317
+ // ../readseek-api/src/kdl.ts
1318
+ import { parse as parse2 } from "@bgotink/kdl";
1319
+ function convertNode(node) {
1320
+ if (node.getTag() !== null)
1321
+ throw new Error("KDL node annotations are not supported");
1322
+ const properties = new Map;
1323
+ for (const entry of node.entries) {
1324
+ const name = entry.getName();
1325
+ if (name === null)
1326
+ throw new Error("KDL positional arguments are not supported");
1327
+ if (properties.has(name))
1328
+ throw new Error(`KDL duplicate property '${name}'`);
1329
+ if (entry.getTag() !== null)
1330
+ throw new Error("KDL value annotations are not supported");
1331
+ const value = entry.getValue();
1332
+ if (value === null)
1333
+ throw new Error("KDL null values are not supported");
1334
+ if (typeof value === "number" && !Number.isFinite(value)) {
1335
+ throw new Error("KDL number out of range");
1336
+ }
1337
+ properties.set(name, value);
1338
+ }
1170
1339
  return {
1171
- ...properties(node),
1172
- text: requireProperty(sourceLine, "content")
1340
+ name: node.getName(),
1341
+ properties,
1342
+ children: node.children?.nodes.map(convertNode) ?? []
1173
1343
  };
1174
1344
  }
1175
- function renameFile(node) {
1345
+ function parseKdlDocument(text) {
1346
+ const document = parse2(text);
1347
+ if (document.nodes.length !== 1)
1348
+ throw new Error("KDL document must contain exactly one root node");
1349
+ return convertNode(document.nodes[0]);
1350
+ }
1351
+
1352
+ // ../readseek-api/src/output-kdl.ts
1353
+ function properties(node) {
1354
+ return Object.fromEntries(node.properties);
1355
+ }
1356
+ function children(node, name) {
1357
+ return node.children.filter((child) => child.name === name);
1358
+ }
1359
+ function child(node, name) {
1360
+ return node.children.find((candidate) => candidate.name === name);
1361
+ }
1362
+ function requireProperty(node, name) {
1363
+ const value = node.properties.get(name);
1364
+ if (value === undefined)
1365
+ throw new Error(`invalid readseek KDL: ${node.name} is missing '${name}'`);
1366
+ return value;
1367
+ }
1368
+ function line(node) {
1369
+ const anchor = requireProperty(node, "anchor");
1370
+ const content = requireProperty(node, "content");
1371
+ if (typeof anchor !== "string" || typeof content !== "string") {
1372
+ throw new Error("invalid readseek KDL line");
1373
+ }
1374
+ const match = /^(\d+):([0-9a-f]+)$/.exec(anchor);
1375
+ if (!match)
1376
+ throw new Error(`invalid readseek KDL line anchor: ${anchor}`);
1176
1377
  return {
1177
- ...properties(node),
1178
- edits: children(node, "edit").map(renameEdit),
1179
- conflicts: children(node, "conflict").map(properties)
1378
+ line: Number(match[1]),
1379
+ hash: match[2],
1380
+ text: content
1180
1381
  };
1181
1382
  }
1182
- function rename(root) {
1383
+ function mapNode(node) {
1183
1384
  return {
1184
- ...renameFile(root),
1185
- others: children(root, "other").map(renameFile)
1385
+ ...properties(node),
1386
+ children: children(node, "node").map(mapNode)
1186
1387
  };
1187
1388
  }
1188
- function genericNode(node) {
1389
+ function sourceContent(node) {
1189
1390
  const result = properties(node);
1190
- const textNode = child(node, "text");
1191
- const bbox = child(node, "bbox");
1192
- if (textNode)
1193
- result.text = requireProperty(textNode, "content");
1194
- if (bbox)
1195
- result.bbox = properties(bbox);
1391
+ result.hashlines = children(node, "line").map(line);
1196
1392
  const objects = children(node, "object");
1197
- if (objects.length > 0)
1198
- result.objects = objects.map(genericNode);
1393
+ if (objects.length > 0) {
1394
+ result.objects = objects.map((object) => ({
1395
+ label: requireProperty(object, "label"),
1396
+ bbox: [
1397
+ requireProperty(object, "xmin"),
1398
+ requireProperty(object, "ymin"),
1399
+ requireProperty(object, "xmax"),
1400
+ requireProperty(object, "ymax")
1401
+ ]
1402
+ }));
1403
+ }
1199
1404
  return result;
1200
1405
  }
1201
- function pdfDocument(root) {
1406
+ function sourceMap(node) {
1407
+ const root = child(node, "node");
1202
1408
  return {
1203
- ...properties(root),
1204
- nodes: children(root, "node").map(genericNode),
1205
- assets: children(root, "asset").map(genericNode),
1206
- vision: children(root, "vision").map(genericNode)
1409
+ ...properties(node),
1410
+ ...root ? { root: mapNode(root) } : {},
1411
+ symbols: children(node, "symbol").map(properties)
1207
1412
  };
1208
1413
  }
1209
- function parseReadSeekKdl(text) {
1210
- const root = parseKdlDocument(text);
1211
- switch (root.name) {
1212
- case "document":
1213
- return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1214
- case "search":
1215
- return search(root);
1216
- case "check":
1217
- return diagnostics(root);
1218
- case "refs":
1219
- return references(root);
1220
- case "def":
1221
- return definitions(root);
1222
- case "edit":
1223
- return edit(root);
1224
- case "edit-batch":
1225
- return { ...properties(root), files: children(root, "file").map(edit) };
1226
- case "rename":
1227
- return rename(root);
1228
- default:
1229
- throw new Error(`unsupported readseek KDL root: ${root.name}`);
1230
- }
1231
- }
1232
- // ../readseek-api/src/process.ts
1233
- var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
1234
- var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
1235
- function readSeekProcessEnvironment() {
1236
- return process.platform === "linux" ? { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } : process.env;
1237
- }
1238
- function resolveProcessLimits(options = {}) {
1414
+ function diagnostics(node) {
1239
1415
  return {
1240
- timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
1241
- maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
1242
- cancelable: options.cancelable !== false
1416
+ ...properties(node),
1417
+ diagnostics: children(node, "diagnostic").map(properties)
1243
1418
  };
1244
1419
  }
1245
- function readSeekCancellationError(signal) {
1246
- const reason = signal?.reason;
1247
- return reason instanceof Error && reason.name !== "AbortError" ? reason : new Error("readseek cancelled");
1420
+ function identity(node) {
1421
+ const result = properties(node);
1422
+ const identifier = child(node, "identifier");
1423
+ const symbol = child(node, "symbol");
1424
+ if (identifier)
1425
+ result.identifier = properties(identifier);
1426
+ if (symbol)
1427
+ result.symbol = properties(symbol);
1428
+ return result;
1248
1429
  }
1249
- function failureFromProcessResult(result, options = {}) {
1250
- if (options.overflow) {
1251
- return new Error(`readseek ${options.overflow} exceeded the ${options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES}-byte output limit`);
1252
- }
1253
- if (options.timedOut) {
1254
- return new Error(`readseek timed out after ${options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS} ms`);
1255
- }
1256
- const cleaned = cleanReadSeekErrorText(result.stderr);
1257
- if (cleaned)
1258
- return new Error(cleaned);
1259
- if (result.signal)
1260
- return new Error(`readseek killed by signal ${result.signal}`);
1261
- return new Error(`readseek exited with status ${result.exitCode}`);
1262
- }
1263
- function ensureSuccessfulStdout(result) {
1264
- if (result.exitCode === 0)
1265
- return result.stdout;
1266
- throw failureFromProcessResult(result);
1267
- }
1268
- // ../readseek-api/src/process-node.ts
1269
- import { spawn } from "node:child_process";
1270
- import { StringDecoder } from "node:string_decoder";
1271
- function attachBoundedStream(stream, maxOutputBytes, onOverflow) {
1272
- const decoder = new StringDecoder("utf8");
1273
- let text = "";
1274
- let bytes = 0;
1275
- let overflowed = false;
1276
- stream.on("data", (chunk) => {
1277
- if (overflowed)
1278
- return;
1279
- const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
1280
- bytes += buffer.length;
1281
- if (bytes > maxOutputBytes) {
1282
- overflowed = true;
1283
- onOverflow();
1284
- return;
1285
- }
1286
- text += decoder.write(buffer);
1287
- });
1288
- return {
1289
- text: () => text + decoder.end()
1290
- };
1291
- }
1292
- async function runReadSeekNode(binaryPath, args, options = {}) {
1293
- const limits = resolveProcessLimits(options);
1294
- if (limits.cancelable && options.signal?.aborted) {
1295
- throw readSeekCancellationError(options.signal);
1296
- }
1297
- return await new Promise((resolve, reject) => {
1298
- let settled = false;
1299
- let child;
1300
- let timer;
1301
- const finish = (result) => {
1302
- if (settled)
1303
- return;
1304
- settled = true;
1305
- if (timer !== undefined)
1306
- clearTimeout(timer);
1307
- options.signal?.removeEventListener("abort", onAbort);
1308
- resolve(result);
1309
- };
1310
- const fail = (error) => {
1311
- if (settled)
1312
- return;
1313
- settled = true;
1314
- if (timer !== undefined)
1315
- clearTimeout(timer);
1316
- options.signal?.removeEventListener("abort", onAbort);
1317
- reject(error instanceof Error ? error : new Error(String(error)));
1318
- };
1319
- const terminate = (error) => {
1320
- if (settled)
1321
- return;
1322
- fail(error);
1323
- try {
1324
- child.kill("SIGKILL");
1325
- } catch {}
1326
- };
1327
- const onAbort = () => {
1328
- terminate(readSeekCancellationError(options.signal));
1329
- };
1330
- try {
1331
- child = spawn(binaryPath, [...args], {
1332
- cwd: options.cwd,
1333
- env: readSeekProcessEnvironment(),
1334
- shell: false,
1335
- stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
1336
- windowsHide: true
1337
- });
1338
- } catch (error) {
1339
- fail(error);
1340
- return;
1341
- }
1342
- const stdout = attachBoundedStream(child.stdout, limits.maxOutputBytes, () => {
1343
- terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stdout", maxOutputBytes: limits.maxOutputBytes }));
1344
- });
1345
- const stderr = attachBoundedStream(child.stderr, limits.maxOutputBytes, () => {
1346
- terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stderr", maxOutputBytes: limits.maxOutputBytes }));
1347
- });
1348
- timer = setTimeout(() => {
1349
- terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
1350
- }, limits.timeoutMs);
1351
- timer.unref?.();
1352
- if (limits.cancelable && options.signal) {
1353
- options.signal.addEventListener("abort", onAbort, { once: true });
1354
- }
1355
- if (options.stdin !== undefined) {
1356
- child.stdin.on("error", (error) => {
1357
- if (error.code !== "EPIPE")
1358
- fail(error);
1359
- });
1360
- child.stdin.end(options.stdin, "utf8");
1361
- }
1362
- child.on("error", fail);
1363
- child.on("close", (code, signal) => {
1364
- finish({
1365
- stdout: stdout.text(),
1366
- stderr: stderr.text(),
1367
- exitCode: code,
1368
- signal
1369
- });
1370
- });
1371
- });
1372
- }
1373
- async function runReadSeekNodeText(binaryPath, args, options = {}) {
1374
- return ensureSuccessfulStdout(await runReadSeekNode(binaryPath, args, options));
1375
- }
1376
- // ../readseek-api/src/client-output.ts
1377
- import { Type } from "@sinclair/typebox";
1378
- import { AssertError, Value } from "@sinclair/typebox/value";
1379
- var OPERATIONS = ["Clone", "Clean", "Default", "Assert"];
1380
- function parse2(schema, value, what) {
1381
- try {
1382
- return Value.Parse(OPERATIONS, schema, value);
1383
- } catch (error) {
1384
- if (!(error instanceof AssertError))
1385
- throw error;
1386
- const detail = error.error;
1387
- throw new Error(`invalid readseek ${what}${detail?.path ?? ""}: ${detail?.message ?? "invalid value"}`);
1388
- }
1389
- }
1390
- var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
1391
- var Str = Type.String();
1392
- var Bool = Type.Boolean();
1393
- var OptStr = Type.Optional(Str);
1394
- var OptInt = Type.Optional(Int);
1395
- var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
1396
- var Hashlines = Type.Array(Hashline);
1397
- var ReadOutput = Type.Object({
1398
- file: Str,
1399
- language: Str,
1400
- line_count: Int,
1401
- file_hash: Str,
1402
- start_line: Int,
1403
- end_line: Int,
1404
- hashlines: Hashlines
1405
- });
1406
- var SymbolOutput = Type.Object({
1407
- kind: Str,
1408
- name: Str,
1409
- qualified_name: OptStr,
1410
- start_line: Int,
1411
- end_line: Int,
1412
- start_hash: OptStr,
1413
- end_hash: OptStr,
1414
- start_anchor: OptStr,
1415
- end_anchor: OptStr
1416
- });
1417
- function mapNodeShape(child) {
1418
- return Type.Object({
1419
- kind: Str,
1420
- name: Str,
1421
- start_line: Int,
1422
- end_line: Int,
1423
- start_anchor: Str,
1424
- end_anchor: Str,
1425
- children: Type.Array(child)
1426
- });
1427
- }
1428
- function recursiveMapNode() {
1429
- return Type.Recursive((self) => mapNodeShape(self));
1430
- }
1431
- var MapNode = typeof Type.Recursive === "function" ? recursiveMapNode() : Type.Cyclic({ MapNode: mapNodeShape(Type.Ref("MapNode")) }, "MapNode");
1432
- var MapOutput = Type.Object({
1433
- file: Str,
1434
- language: Str,
1435
- line_count: Int,
1436
- file_hash: Str,
1437
- root: Type.Optional(MapNode),
1438
- symbols: Type.Array(SymbolOutput)
1439
- });
1440
- var SearchSpan = {
1441
- start_byte: OptInt,
1442
- end_byte: OptInt,
1443
- text: OptStr,
1444
- start_line: Int,
1445
- end_line: Int,
1446
- start_hash: Str,
1447
- end_hash: Str,
1448
- hashlines: Hashlines
1449
- };
1450
- var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
1451
- var SearchMatch = Type.Object({
1452
- pattern_index: Type.Integer({ default: 0 }),
1453
- ...SearchSpan,
1454
- captures: Type.Array(SearchCapture)
1455
- });
1456
- var SearchFileOutput = Type.Object({
1457
- file: Str,
1458
- language: Str,
1459
- file_hash: Str,
1460
- matches: Type.Array(SearchMatch)
1461
- });
1462
- var SearchEnvelope = Type.Object({
1463
- results: Type.Array(SearchFileOutput),
1464
- returned_matches: OptInt,
1465
- truncated: Type.Optional(Bool),
1466
- byte_basis: OptStr
1467
- });
1468
- var RefsEnvelope = Type.Object({
1469
- references: Type.Array(Type.Object({
1470
- file: Str,
1471
- line: Int,
1472
- column: Int,
1473
- line_hash: Str,
1474
- text: Str,
1475
- symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
1476
- }))
1477
- });
1478
- var Diagnostic = Type.Object({
1479
- kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
1480
- start_line: Int,
1481
- end_line: Int
1482
- });
1483
- var CheckEnvelope = Type.Object({
1484
- file_hash: OptStr,
1485
- error_count: Int,
1486
- missing_count: Int,
1487
- diagnostics: Type.Array(Diagnostic)
1488
- });
1489
- var DetectedObject = Type.Object({
1490
- label: Str,
1491
- bbox: Type.Tuple([Int, Int, Int, Int])
1492
- });
1493
- var PreparedImage = Type.Object({
1494
- mime: Str,
1495
- encoding: Type.Literal("base64"),
1496
- data: Str
1497
- });
1498
- var SourceDetection = Type.Object({
1499
- kind: Type.Literal("source"),
1500
- type: Str,
1501
- file: Str,
1502
- language: Str,
1503
- engine: OptStr,
1504
- supported: Bool,
1505
- mime: OptStr,
1506
- syntax: OptStr
1507
- });
1508
- var ImageDetection = Type.Object({
1509
- kind: Type.Literal("image"),
1510
- type: Str,
1511
- file: Str,
1512
- mime: OptStr,
1513
- format: Str,
1514
- width: Int,
1515
- height: Int,
1516
- animated: Bool,
1517
- encoding: Type.Optional(Type.Literal("base64")),
1518
- data: OptStr,
1519
- ocr: OptStr,
1520
- caption: OptStr,
1521
- objects: Type.Optional(Type.Array(DetectedObject))
1522
- });
1523
- var TextDetection = Type.Object({
1524
- kind: Type.Literal("text"),
1525
- type: Str,
1526
- file: Str,
1527
- mime: OptStr
1528
- });
1529
- var PdfDetection = Type.Object({
1530
- kind: Type.Literal("pdf"),
1531
- type: Type.Literal("application/pdf"),
1532
- file: Str,
1533
- mime: OptStr,
1534
- format: Type.Literal("pdf"),
1535
- pages: Int
1536
- });
1537
- var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
1538
- var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
1539
- var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
1540
- var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
1541
- var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
1542
- var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
1543
- var PdfInspectMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
1544
- var DocumentCache = Type.Object({
1545
- content_hash: Str,
1546
- state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
1547
- });
1548
- var Identifier = Type.Object({
1549
- text: Str,
1550
- start_column: Int,
1551
- end_column: Int,
1552
- start_byte: Int,
1553
- end_byte: Int
1554
- });
1555
- var IdentifyEnvelope = Type.Object({
1556
- file: Str,
1557
- language: Str,
1558
- engine: OptStr,
1559
- line_count: Int,
1560
- file_hash: Str,
1561
- line: Int,
1562
- column: Int,
1563
- line_hash: Str,
1564
- identifier: Type.Optional(Identifier),
1565
- symbol: Type.Optional(Type.Object({
1566
- name: Str,
1567
- kind: Str,
1568
- qualified_name: Str,
1569
- start_line: Int,
1570
- end_line: Int
1571
- }))
1572
- });
1573
- var EditOutputSchema = Type.Object({
1574
- file: Str,
1575
- before_hash: Str,
1576
- file_hash: Str,
1577
- plan_hash: Str,
1578
- applied: Bool,
1579
- changed: Bool,
1580
- edit_count: Int,
1581
- before_content: OptStr,
1582
- content: OptStr
1583
- });
1584
- var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
1585
- var RenameEditSchema = Type.Object({
1586
- line: Int,
1587
- start_column: Int,
1588
- end_column: Int,
1589
- start_byte: Int,
1590
- end_byte: Int,
1591
- occurrence: Str,
1592
- line_hash: Str,
1593
- text: Str
1594
- });
1595
- var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
1596
- var Edits = Type.Array(RenameEditSchema, { default: [] });
1597
- var RenameFileOutputSchema = Type.Object({
1598
- file: Str,
1599
- language: Str,
1600
- engine: OptStr,
1601
- file_hash: Str,
1602
- conflicts: Conflicts,
1603
- edits: Edits
1604
- });
1605
- var RenameOutputSchema = Type.Object({
1606
- file: Str,
1607
- language: Str,
1608
- engine: OptStr,
1609
- file_hash: Str,
1610
- old_name: Str,
1611
- new_name: Str,
1612
- plan_hash: Str,
1613
- applied: Bool,
1614
- conflicts: Conflicts,
1615
- edits: Edits,
1616
- others: Type.Array(RenameFileOutputSchema, { default: [] })
1617
- });
1618
- var DefEnvelope = Type.Object({
1619
- locations: Type.Array(Type.Object({
1620
- file: Str,
1621
- line: Int,
1622
- column: Int,
1623
- line_hash: Str,
1624
- text: Str,
1625
- kind: OptStr,
1626
- name: OptStr,
1627
- qualified_name: OptStr
1628
- }))
1629
- });
1630
- function parseReadOutput(value) {
1631
- return parse2(ReadOutput, value, "read output");
1430
+ function sourceDocument(root) {
1431
+ const result = {};
1432
+ const metadata = child(root, "metadata");
1433
+ const cache = child(root, "cache");
1434
+ const content = child(root, "content");
1435
+ const structure = child(root, "structure");
1436
+ const diagnosticOutput = child(root, "diagnostics");
1437
+ const identityOutput = child(root, "cursor");
1438
+ if (metadata)
1439
+ result.metadata = properties(metadata);
1440
+ if (!metadata && !content && !structure && !diagnosticOutput && !identityOutput && root.properties.has("file"))
1441
+ result.file = root.properties.get("file");
1442
+ const assets = children(root, "asset");
1443
+ if (assets.length > 0)
1444
+ result.assets = assets.map(genericNode);
1445
+ if (cache)
1446
+ result.cache = properties(cache);
1447
+ if (content)
1448
+ result.content = sourceContent(content);
1449
+ if (structure)
1450
+ result.structure = sourceMap(structure);
1451
+ if (diagnosticOutput)
1452
+ result.diagnostics = diagnostics(diagnosticOutput);
1453
+ if (identityOutput)
1454
+ result.cursor = identity(identityOutput);
1455
+ return result;
1632
1456
  }
1633
- function parseMapOutput(value) {
1634
- return parse2(MapOutput, value, "map output");
1457
+ function hit(node) {
1458
+ return {
1459
+ ...properties(node),
1460
+ hashlines: children(node, "line").map(line),
1461
+ captures: children(node, "capture").map((capture) => ({
1462
+ ...properties(capture),
1463
+ hashlines: children(capture, "line").map(line)
1464
+ }))
1465
+ };
1635
1466
  }
1636
- function parseSearchOutput(value) {
1637
- const envelope = parse2(SearchEnvelope, value, "search output");
1638
- const results = envelope.results;
1639
- if (envelope.returned_matches !== undefined)
1640
- results.returned_matches = envelope.returned_matches;
1641
- if (envelope.truncated !== undefined)
1642
- results.truncated = envelope.truncated;
1643
- if (envelope.byte_basis !== undefined)
1644
- results.byte_basis = envelope.byte_basis;
1645
- return results;
1467
+ function search(root) {
1468
+ return {
1469
+ ...properties(root),
1470
+ results: children(root, "match").map((match) => ({
1471
+ ...properties(match),
1472
+ matches: children(match, "hit").map(hit)
1473
+ })),
1474
+ documents: children(root, "document").map((documentNode) => ({
1475
+ ...properties(documentNode),
1476
+ hits: children(documentNode, "hit").map(properties)
1477
+ }))
1478
+ };
1646
1479
  }
1647
- function parseRefsOutput(value) {
1648
- return parse2(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
1649
- ...reference,
1650
- enclosingSymbol: symbol?.qualified_name
1651
- }));
1480
+ function references(root) {
1481
+ const parsed = children(root, "reference").map((reference) => {
1482
+ const sourceLine = child(reference, "line");
1483
+ if (!sourceLine)
1484
+ throw new Error("invalid readseek KDL reference: missing line");
1485
+ const parsedLine = line(sourceLine);
1486
+ const symbol = child(reference, "symbol");
1487
+ return {
1488
+ ...properties(reference),
1489
+ line_hash: parsedLine.hash,
1490
+ text: parsedLine.text,
1491
+ ...symbol ? { symbol: properties(symbol) } : {}
1492
+ };
1493
+ });
1494
+ return {
1495
+ references: parsed,
1496
+ locations: parsed.map((reference) => {
1497
+ const { symbol, ...location } = reference;
1498
+ return symbol && typeof symbol === "object" ? { ...location, ...symbol } : location;
1499
+ })
1500
+ };
1652
1501
  }
1653
- function parseCheckOutput(value) {
1654
- const output = parse2(CheckEnvelope, value, "check output");
1502
+ function definitions(root) {
1655
1503
  return {
1656
- file_hash: output.file_hash,
1657
- errorCount: output.error_count,
1658
- missingCount: output.missing_count,
1659
- diagnostics: output.diagnostics
1504
+ locations: children(root, "definition").map((definition) => {
1505
+ const symbol = child(definition, "symbol");
1506
+ const sourceLine = child(definition, "line");
1507
+ if (!symbol || !sourceLine) {
1508
+ throw new Error("invalid readseek KDL definition: missing symbol or line");
1509
+ }
1510
+ const symbolProperties = properties(symbol);
1511
+ const parsedLine = line(sourceLine);
1512
+ const definitionColumn = definition.properties.get("column");
1513
+ const nameByte = symbol.properties.get("name_byte");
1514
+ const startByte = symbol.properties.get("start_byte");
1515
+ let column = 1;
1516
+ if (typeof definitionColumn === "number") {
1517
+ column = definitionColumn;
1518
+ } else if (typeof nameByte === "number" && typeof startByte === "number") {
1519
+ column = nameByte - startByte + 1;
1520
+ }
1521
+ return {
1522
+ file: requireProperty(definition, "file"),
1523
+ line: parsedLine.line,
1524
+ column,
1525
+ line_hash: parsedLine.hash,
1526
+ text: parsedLine.text,
1527
+ kind: symbolProperties.kind,
1528
+ name: symbolProperties.name,
1529
+ qualified_name: symbolProperties.qualified_name
1530
+ };
1531
+ })
1660
1532
  };
1661
1533
  }
1662
- function parseDetectOutput(value) {
1663
- const output = value ?? {};
1664
- if (output.type === "application/pdf") {
1665
- return { kind: "pdf", ...parse2(PdfDetectionInput, value, "detect output") };
1666
- }
1667
- if (output.width !== undefined || output.height !== undefined) {
1668
- return { kind: "image", ...parse2(ImageDetectionInput, value, "detect output") };
1669
- }
1670
- if (output.language !== undefined) {
1671
- return { kind: "source", ...parse2(SourceDetectionInput, value, "detect output") };
1534
+ function edit(root) {
1535
+ const result = properties(root);
1536
+ for (const name of ["before_content", "content"]) {
1537
+ const contentNode = child(root, name);
1538
+ if (contentNode)
1539
+ result[name] = requireProperty(contentNode, "content");
1672
1540
  }
1673
- return { kind: "text", ...parse2(TextDetectionInput, value, "detect output") };
1541
+ return result;
1542
+ }
1543
+ function renameEdit(node) {
1544
+ const sourceLine = child(node, "line");
1545
+ if (!sourceLine)
1546
+ throw new Error("invalid readseek KDL rename edit: missing line");
1547
+ return {
1548
+ ...properties(node),
1549
+ text: requireProperty(sourceLine, "content")
1550
+ };
1674
1551
  }
1675
- function parseImageContent(value) {
1676
- const detection = parseDetectOutput(value);
1677
- if (detection.kind !== "image")
1678
- throw new Error("invalid readseek image content: expected an image");
1679
- return detection;
1552
+ function renameFile(node) {
1553
+ return {
1554
+ ...properties(node),
1555
+ edits: children(node, "edit").map(renameEdit),
1556
+ conflicts: children(node, "conflict").map(properties)
1557
+ };
1680
1558
  }
1681
- function parseTextContent(value) {
1682
- const output = value ?? {};
1683
- if (output.hashlines === undefined || output.start_line !== undefined)
1684
- return parseReadOutput(value);
1685
- const spanned = parse2(UnspannedContent, value, "content");
1559
+ function rename(root) {
1686
1560
  return {
1687
- ...spanned,
1688
- start_line: spanned.hashlines[0]?.line ?? 1,
1689
- end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
1561
+ ...renameFile(root),
1562
+ others: children(root, "other").map(renameFile)
1690
1563
  };
1691
1564
  }
1692
- function parseDocumentCache(value) {
1693
- return parse2(DocumentCache, value, "document cache");
1565
+ function genericNode(node) {
1566
+ const result = properties(node);
1567
+ const textNode = child(node, "text");
1568
+ const bbox = child(node, "bbox");
1569
+ if (textNode)
1570
+ result.text = requireProperty(textNode, "content");
1571
+ if (bbox)
1572
+ result.bbox = properties(bbox);
1573
+ const objects = children(node, "object");
1574
+ if (objects.length > 0)
1575
+ result.objects = objects.map((object) => ({
1576
+ label: requireProperty(object, "label"),
1577
+ bbox: [requireProperty(object, "x0"), requireProperty(object, "y0"), requireProperty(object, "x1"), requireProperty(object, "y1")]
1578
+ }));
1579
+ return result;
1694
1580
  }
1695
- function parseIdentifyOutput(value) {
1696
- return parse2(IdentifyEnvelope, value, "identify output");
1581
+ function pdfDocument(root) {
1582
+ return {
1583
+ ...properties(root),
1584
+ nodes: children(root, "node").map(genericNode),
1585
+ assets: children(root, "asset").map(genericNode),
1586
+ vision: children(root, "vision").map(genericNode)
1587
+ };
1697
1588
  }
1698
- function parsePdfInspectMetadata(value) {
1699
- if (!value || typeof value !== "object")
1700
- return;
1701
- if (value.format !== "pdf")
1702
- return;
1703
- const document = parse2(PdfInspectMetadata, value, "inspect metadata");
1704
- return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
1589
+ function parseReadSeekKdl(text) {
1590
+ const root = parseKdlDocument(text);
1591
+ switch (root.name) {
1592
+ case "document":
1593
+ return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1594
+ case "search":
1595
+ return search(root);
1596
+ case "check":
1597
+ return diagnostics(root);
1598
+ case "refs":
1599
+ return references(root);
1600
+ case "def":
1601
+ return definitions(root);
1602
+ case "edit":
1603
+ return edit(root);
1604
+ case "edit-batch":
1605
+ return { ...properties(root), files: children(root, "file").map(edit) };
1606
+ case "rename":
1607
+ return rename(root);
1608
+ default:
1609
+ throw new Error(`unsupported readseek KDL root: ${root.name}`);
1610
+ }
1705
1611
  }
1706
- function parseEditOutput(value) {
1707
- return parse2(EditOutputSchema, value, "edit output");
1612
+ // ../readseek-api/src/process.ts
1613
+ var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
1614
+ var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
1615
+ function readSeekProcessEnvironment() {
1616
+ return process.platform === "linux" ? { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } : process.env;
1708
1617
  }
1709
- function parseRenameOutput(value) {
1710
- return parse2(RenameOutputSchema, value, "rename output");
1618
+ function resolveProcessLimits(options = {}) {
1619
+ return {
1620
+ timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
1621
+ maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
1622
+ cancelable: options.cancelable !== false
1623
+ };
1711
1624
  }
1712
- function parseDefOutput(value) {
1713
- return parse2(DefEnvelope, value, "def output").locations;
1625
+ function readSeekCancellationError(signal) {
1626
+ const reason = signal?.reason;
1627
+ return reason instanceof Error && reason.name !== "AbortError" ? reason : new Error("readseek cancelled");
1714
1628
  }
1715
-
1716
- // ../readseek-api/src/client.ts
1717
- function classifyReadSeekFailure(err) {
1718
- const failure = err;
1719
- const message = String(failure?.message ?? err);
1720
- const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
1721
- if (missing) {
1722
- return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1629
+ function failureFromProcessResult(result, options = {}) {
1630
+ if (options.overflow) {
1631
+ return new Error(`readseek ${options.overflow} exceeded the ${options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES}-byte output limit`);
1723
1632
  }
1724
- return { code: "readseek-execution-error", message };
1633
+ if (options.timedOut) {
1634
+ return new Error(`readseek timed out after ${options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS} ms`);
1635
+ }
1636
+ const cleaned = cleanReadSeekErrorText(result.stderr);
1637
+ if (cleaned)
1638
+ return new Error(cleaned);
1639
+ if (result.signal)
1640
+ return new Error(`readseek killed by signal ${result.signal}`);
1641
+ return new Error(`readseek exited with status ${result.exitCode}`);
1725
1642
  }
1726
- function parseSourceInspectEnvelope(value) {
1727
- if (!value || typeof value !== "object")
1728
- throw new Error("invalid readseek inspect output");
1729
- const output = value;
1730
- const result = {};
1731
- if (output.metadata !== undefined)
1732
- result.metadata = parseDetectOutput(output.metadata);
1733
- if (output.cache !== undefined)
1734
- result.cache = parseDocumentCache(output.cache);
1735
- if (output.content !== undefined)
1736
- result.content = output.content;
1737
- if (output.map !== undefined)
1738
- result.map = parseMapOutput(output.map);
1739
- if (output.diagnostics !== undefined)
1740
- result.diagnostics = parseCheckOutput(output.diagnostics);
1741
- if (output.identity !== undefined)
1742
- result.identity = parseIdentifyOutput(output.identity);
1743
- return result;
1643
+ function acceptsReadSeekExitCode(args, exitCode) {
1644
+ if (exitCode === 0)
1645
+ return true;
1646
+ if (exitCode !== 1)
1647
+ return false;
1648
+ let index = 0;
1649
+ const valued = new Set(["-C", "--directory", "--output", "--readseek-dir"]);
1650
+ while (index < args.length) {
1651
+ const arg = args[index];
1652
+ if (valued.has(arg)) {
1653
+ index += 2;
1654
+ continue;
1655
+ }
1656
+ if ([...valued].some((flag) => arg.startsWith(`${flag}=`)) || arg === "--kdl") {
1657
+ index++;
1658
+ continue;
1659
+ }
1660
+ return arg === "find" || arg === "check";
1661
+ }
1662
+ return false;
1744
1663
  }
1745
- function createReadSeekClient(runner) {
1746
- function inspectArgs(target, options) {
1747
- return buildInspectArgs({
1748
- path: target,
1749
- selector: options.selector,
1750
- select: options.select ?? "content",
1751
- query: options.query,
1752
- end: options.end,
1753
- limit: options.limit,
1754
- depth: options.depth,
1755
- language: options.language,
1756
- visionMode: options.visionMode,
1757
- visionLevel: options.visionLevel,
1758
- stdinName: options.stdinName
1759
- });
1664
+ function ensureSuccessfulStdout(result, args = []) {
1665
+ if (!result.signal && acceptsReadSeekExitCode(args, result.exitCode))
1666
+ return result.stdout;
1667
+ throw failureFromProcessResult(result);
1668
+ }
1669
+ // ../readseek-api/src/process-node.ts
1670
+ import { spawn } from "node:child_process";
1671
+ import { StringDecoder } from "node:string_decoder";
1672
+ function attachBoundedStream(stream, maxOutputBytes, onOverflow) {
1673
+ const decoder = new StringDecoder("utf8");
1674
+ let text = "";
1675
+ let bytes = 0;
1676
+ let overflowed = false;
1677
+ stream.on("data", (chunk) => {
1678
+ if (overflowed)
1679
+ return;
1680
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
1681
+ bytes += buffer.length;
1682
+ if (bytes > maxOutputBytes) {
1683
+ overflowed = true;
1684
+ onOverflow();
1685
+ return;
1686
+ }
1687
+ text += decoder.write(buffer);
1688
+ });
1689
+ return {
1690
+ text: () => text + decoder.end()
1691
+ };
1692
+ }
1693
+ async function runReadSeekNode(binaryPath, args, options = {}) {
1694
+ const limits = resolveProcessLimits(options);
1695
+ if (limits.cancelable && options.signal?.aborted) {
1696
+ throw readSeekCancellationError(options.signal);
1760
1697
  }
1761
- function inspectRunOptions(options) {
1762
- return {
1763
- signal: options.signal,
1764
- stdin: options.stdin,
1765
- timeoutMs: options.timeoutMs,
1766
- vision: options.vision
1698
+ return await new Promise((resolve, reject) => {
1699
+ let settled = false;
1700
+ let child;
1701
+ let timer;
1702
+ const finish = (result) => {
1703
+ if (settled)
1704
+ return;
1705
+ settled = true;
1706
+ if (timer !== undefined)
1707
+ clearTimeout(timer);
1708
+ options.signal?.removeEventListener("abort", onAbort);
1709
+ resolve(result);
1767
1710
  };
1768
- }
1769
- async function inspect(target, options = {}) {
1770
- return runner.kdl(inspectArgs(target, options), inspectRunOptions(options));
1771
- }
1772
- async function inspectText(target, options = {}) {
1773
- return runner.text(inspectArgs(target, options), inspectRunOptions(options));
1774
- }
1775
- async function inspectSource(target, options = {}) {
1776
- return parseSourceInspectEnvelope(await inspect(target, options));
1777
- }
1778
- function requireInspectMetadata(envelope, field) {
1779
- if (envelope.metadata === undefined)
1780
- throw new Error(`invalid readseek ${field}: missing metadata`);
1781
- return envelope.metadata;
1782
- }
1783
- function requireInspectContent(envelope, field) {
1784
- if (envelope.content === undefined)
1785
- throw new Error(`invalid readseek ${field}: missing content`);
1786
- return envelope.content;
1787
- }
1788
- async function inspectContent(filePath, options = {}) {
1789
- const inspected = await inspect(filePath, {
1790
- select: ["metadata", "content"],
1791
- selector: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1792
- end: options.endLine,
1793
- visionMode: options.visionMode,
1794
- signal: options.signal,
1795
- vision: options.vision
1711
+ const fail = (error) => {
1712
+ if (settled)
1713
+ return;
1714
+ settled = true;
1715
+ if (timer !== undefined)
1716
+ clearTimeout(timer);
1717
+ options.signal?.removeEventListener("abort", onAbort);
1718
+ reject(error instanceof Error ? error : new Error(String(error)));
1719
+ };
1720
+ const terminate = (error) => {
1721
+ if (settled)
1722
+ return;
1723
+ fail(error);
1724
+ try {
1725
+ child.kill("SIGKILL");
1726
+ } catch {}
1727
+ };
1728
+ const onAbort = () => {
1729
+ terminate(readSeekCancellationError(options.signal));
1730
+ };
1731
+ try {
1732
+ child = spawn(binaryPath, [...args], {
1733
+ cwd: options.cwd,
1734
+ env: readSeekProcessEnvironment(),
1735
+ shell: false,
1736
+ stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
1737
+ windowsHide: true
1738
+ });
1739
+ } catch (error) {
1740
+ fail(error);
1741
+ return;
1742
+ }
1743
+ const stdout = attachBoundedStream(child.stdout, limits.maxOutputBytes, () => {
1744
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stdout", maxOutputBytes: limits.maxOutputBytes }));
1745
+ });
1746
+ const stderr = attachBoundedStream(child.stderr, limits.maxOutputBytes, () => {
1747
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stderr", maxOutputBytes: limits.maxOutputBytes }));
1796
1748
  });
1797
- const pdf = parsePdfInspectMetadata(inspected);
1798
- if (pdf)
1799
- return { metadata: pdf };
1800
- const envelope = parseSourceInspectEnvelope(inspected);
1801
- const metadata = requireInspectMetadata(envelope, "content");
1802
- const content = requireInspectContent(envelope, "content");
1803
- if (metadata.kind === "image") {
1804
- return { metadata, image: parseImageContent(content) };
1749
+ timer = setTimeout(() => {
1750
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
1751
+ }, limits.timeoutMs);
1752
+ timer.unref?.();
1753
+ if (limits.cancelable && options.signal) {
1754
+ options.signal.addEventListener("abort", onAbort, { once: true });
1805
1755
  }
1806
- return { metadata, text: parseTextContent(content) };
1807
- }
1808
- return {
1809
- inspect,
1810
- inspectText,
1811
- inspectContent,
1812
- async extract(target, selector, output, options = {}) {
1813
- await runner.text(buildExtractArgs({
1814
- path: target,
1815
- selector,
1816
- output,
1817
- stdinName: options.stdinName
1818
- }), {
1819
- signal: options.signal,
1820
- stdin: options.stdin,
1821
- timeoutMs: options.timeoutMs,
1822
- cancelable: options.cancelable
1823
- });
1824
- },
1825
- async image(filePath, modes, options = {}) {
1826
- if (modes.length === 0)
1827
- throw new Error(`readseek returned no image analysis for ${filePath}`);
1828
- const inspected = await inspectContent(filePath, {
1829
- visionMode: modes.length > 1 ? "all" : modes[0],
1830
- signal: options.signal,
1831
- vision: true
1832
- });
1833
- if (!inspected.image)
1834
- throw new Error(`readseek returned no image analysis for ${filePath}`);
1835
- return inspected.image;
1836
- },
1837
- async preparedImage(filePath, options = {}) {
1838
- const inspected = await inspectContent(filePath, {
1839
- visionMode: "none",
1840
- signal: options.signal
1841
- });
1842
- const output = inspected.image;
1843
- if (!output || output.encoding !== "base64" || output.data === undefined || output.mime === undefined) {
1844
- throw new Error(`readseek returned no prepared image for ${filePath}`);
1845
- }
1846
- return { mime: output.mime, encoding: output.encoding, data: output.data };
1847
- },
1848
- async search(target, pattern, options = {}) {
1849
- const args = buildSearchArgs({
1850
- path: target,
1851
- pattern,
1852
- language: options.language,
1853
- limit: options.limit,
1854
- gitScope: options.gitScope
1855
- });
1856
- return parseSearchOutput(await runner.kdl(args, { signal: options.signal }));
1857
- },
1858
- async refs(target, options = {}) {
1859
- const args = buildRefsArgs({
1860
- path: target,
1861
- name: options.name,
1862
- scope: options.scope,
1863
- line: options.line,
1864
- column: options.column,
1865
- language: options.language,
1866
- gitScope: options.gitScope
1867
- });
1868
- return parseRefsOutput(await runner.kdl(args, { signal: options.signal }));
1869
- },
1870
- async def(target, options) {
1871
- const args = buildDefArgs({
1872
- path: target,
1873
- name: options.name,
1874
- language: options.language,
1875
- gitScope: options.gitScope
1876
- });
1877
- return parseDefOutput(await runner.kdl(args, { signal: options.signal }));
1878
- },
1879
- async check(filePath, content, options = {}) {
1880
- const envelope = await inspectSource("-", {
1881
- select: "diagnostics",
1882
- stdin: content,
1883
- stdinName: filePath,
1884
- signal: options.signal
1885
- });
1886
- if (!envelope.diagnostics)
1887
- throw new Error("invalid readseek diagnostics: missing diagnostics facet");
1888
- return envelope.diagnostics;
1889
- },
1890
- async identify(filePath, content, options) {
1891
- const envelope = await inspectSource("-", {
1892
- select: "identity",
1893
- stdin: content,
1894
- stdinName: filePath,
1895
- selector: buildLineLocation(options.line, options.column),
1896
- language: options.language,
1897
- signal: options.signal
1898
- });
1899
- if (!envelope.identity)
1900
- throw new Error("invalid readseek identity: missing identity facet");
1901
- return envelope.identity;
1902
- },
1903
- async detect(filePath, options = {}) {
1904
- const inspected = await inspect(filePath, { select: "metadata", signal: options.signal });
1905
- const pdf = parsePdfInspectMetadata(inspected);
1906
- if (pdf)
1907
- return pdf;
1908
- return requireInspectMetadata(parseSourceInspectEnvelope(inspected), "metadata");
1909
- },
1910
- async edit(filePath, edits, options = {}) {
1911
- const args = buildEditArgs({
1912
- path: filePath,
1913
- apply: options.apply,
1914
- planHash: options.planHash,
1915
- language: options.language,
1916
- requestFromStdin: true
1917
- });
1918
- return parseEditOutput(await runner.kdl(args, {
1919
- signal: options.signal,
1920
- stdin: serializeEditPlan(edits),
1921
- timeoutMs: options.timeoutMs,
1922
- cancelable: options.cancelable
1923
- }));
1924
- },
1925
- async rename(filePath, options) {
1926
- const args = buildRenameArgs({
1927
- path: filePath,
1928
- to: options.to,
1929
- line: options.line,
1930
- column: options.column,
1931
- workspace: options.workspace,
1932
- apply: options.apply,
1933
- planHash: options.planHash,
1934
- language: options.language,
1935
- gitScope: options.gitScope
1756
+ if (options.stdin !== undefined) {
1757
+ child.stdin.on("error", (error) => {
1758
+ if (error.code !== "EPIPE")
1759
+ fail(error);
1936
1760
  });
1937
- return parseRenameOutput(await runner.kdl(args, { signal: options.signal }));
1761
+ child.stdin.end(options.stdin, "utf8");
1938
1762
  }
1939
- };
1763
+ child.on("error", fail);
1764
+ child.on("close", (code, signal) => {
1765
+ finish({
1766
+ stdout: stdout.text(),
1767
+ stderr: stderr.text(),
1768
+ exitCode: code,
1769
+ signal
1770
+ });
1771
+ });
1772
+ });
1773
+ }
1774
+ async function runReadSeekNodeText(binaryPath, args, options = {}) {
1775
+ return ensureSuccessfulStdout(await runReadSeekNode(binaryPath, args, options), args);
1940
1776
  }
1941
1777
  // src/tool-prompt-metadata.ts
1942
1778
  var REPLACEABLE_BY_TOOL = new Map(REPLACEABLE_TOOLS.map((entry) => [entry.tool, entry]));
@@ -2415,7 +2251,7 @@ function formatReplaceHint(edits, noopEdits) {
2415
2251
  return;
2416
2252
  if (counts.replace !== counts.total)
2417
2253
  return;
2418
- return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_inspect/readSeek_grep/readSeek_search.]";
2254
+ return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_show_content/readSeek_find_text/readSeek_find_nodes.]";
2419
2255
  }
2420
2256
  function buildEditOutput(input) {
2421
2257
  const summary = `Updated ${input.displayPath}`;
@@ -2508,17 +2344,17 @@ function classifyEdit(oldContent, newContent) {
2508
2344
  import { existsSync as existsSync2, statSync as statSync3 } from "node:fs";
2509
2345
  import { createRequire as createRequire2 } from "node:module";
2510
2346
  import { homedir as homedir2 } from "node:os";
2511
- import path2 from "node:path";
2347
+ import path3 from "node:path";
2512
2348
 
2513
2349
  // src/readseek-settings.ts
2514
2350
  import { readFileSync, statSync as statSync2 } from "node:fs";
2515
2351
  import { homedir } from "node:os";
2516
2352
  import { join } from "node:path";
2517
2353
  var READSEEK_TOOL_REPLACEMENTS = {
2518
- read: "readSeek_inspect",
2519
- edit: "readSeek_edit",
2520
- grep: "readSeek_grep",
2521
- write: "readSeek_write"
2354
+ read: "readSeek_show_content",
2355
+ edit: "readSeek_edit_text",
2356
+ grep: "readSeek_find_text",
2357
+ write: "readSeek_write_file"
2522
2358
  };
2523
2359
  var READSEEK_KEYS = ["overrideTools", "imageMode", "syntaxValidation", "timeoutMs", "grep", "display"];
2524
2360
  var READSEEK_GREP_KEYS = ["maxLines", "maxBytes"];
@@ -2735,7 +2571,7 @@ function readSeekBinaryAvailability() {
2735
2571
  var defaultReadSeekDirInit;
2736
2572
  function defaultReadSeekDir() {
2737
2573
  const home = homedir2();
2738
- return home ? path2.join(home, ".pi", "readseek") : null;
2574
+ return home ? path3.join(home, ".pi", "readseek") : null;
2739
2575
  }
2740
2576
  function directoryExists(dir) {
2741
2577
  try {
@@ -2754,24 +2590,39 @@ async function ensureDefaultReadSeekDir() {
2754
2590
  return null;
2755
2591
  if (directoryExists(dir))
2756
2592
  return dir;
2757
- defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "init"], { timeoutMs: readSeekTimeoutMs() }).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2593
+ defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "initialize", "index"], { timeoutMs: readSeekTimeoutMs() }).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2758
2594
  defaultReadSeekDirInit = undefined;
2759
2595
  });
2760
2596
  return defaultReadSeekDirInit;
2761
2597
  }
2762
2598
  function hasProjectReadSeekDir(cwd = process.cwd()) {
2763
- let dir = path2.resolve(cwd);
2599
+ let dir = path3.resolve(cwd);
2764
2600
  while (true) {
2765
- if (existsSync2(path2.join(dir, ".readseek")))
2601
+ if (existsSync2(path3.join(dir, ".readseek")))
2766
2602
  return true;
2767
- const parent = path2.dirname(dir);
2603
+ const parent = path3.dirname(dir);
2768
2604
  if (parent === dir)
2769
2605
  return false;
2770
2606
  dir = parent;
2771
2607
  }
2772
2608
  }
2609
+ function supportsReadSeekDir(args) {
2610
+ const first = args[0];
2611
+ const second = args[1];
2612
+ if (first === "initialize" && second === "index")
2613
+ return true;
2614
+ if (first === "show" && (second === "content" || second === "assets"))
2615
+ return true;
2616
+ if (first === "generate" && second === "caption")
2617
+ return true;
2618
+ if (first === "detect" && second === "objects")
2619
+ return true;
2620
+ if (first === "recognize" && second === "text")
2621
+ return true;
2622
+ return false;
2623
+ }
2773
2624
  async function readSeekInvocationArgs(args) {
2774
- if (hasProjectReadSeekDir())
2625
+ if (!supportsReadSeekDir(args) || hasProjectReadSeekDir())
2775
2626
  return [...args];
2776
2627
  const readSeekDir = await ensureDefaultReadSeekDir();
2777
2628
  return readSeekDir ? ["--readseek-dir", readSeekDir, ...args] : [...args];
@@ -2825,22 +2676,24 @@ var runner = {
2825
2676
  }),
2826
2677
  kdl: async (args, options = {}) => parseReadSeekKdl(await runner.text(args, options))
2827
2678
  };
2828
- var client = createReadSeekClient(runner);
2829
- var readSeekInspect = client.inspect;
2830
- var readSeekExtract = client.extract;
2831
- var readSeekSearch = client.search;
2832
- var readSeekRefs = client.refs;
2833
- var readSeekDef = client.def;
2834
- var readSeekCheck = client.check;
2835
- var readSeekEdit = client.edit;
2836
- var readSeekRename = client.rename;
2679
+ var client2 = createReadSeekClient(runner);
2680
+ var readSeekShowContent = client2.showContent;
2681
+ var readSeekShowStructure = client2.showStructure;
2682
+ var readSeekShowMetadata = client2.showMetadata;
2683
+ var readSeekShowAssets = client2.showAssets;
2684
+ var readSeekGenerateCaption = client2.generateCaption;
2685
+ var readSeekDetectObjects = client2.detectObjects;
2686
+ var readSeekRecognizeText = client2.recognizeText;
2687
+ var readSeekFindNodes = client2.findNodes;
2688
+ var readSeekFindReferences = client2.findReferences;
2689
+ var readSeekFindDefinitions = client2.findDefinitions;
2690
+ var readSeekCheckSyntax = client2.checkSyntax;
2691
+ var readSeekCheckRules = client2.checkRules;
2692
+ var readSeekEditText = client2.editText;
2693
+ var readSeekRenameSymbol = client2.renameSymbol;
2837
2694
  async function readSeekMap(filePath, options = {}) {
2838
- const envelope = await client.inspect(filePath, {
2839
- select: "map",
2840
- depth: DEFAULT_INSPECT_MAP_DEPTH,
2841
- signal: options.signal
2842
- });
2843
- const map = envelope.map;
2695
+ const envelope = await client2.showStructure(filePath, { depth: DEFAULT_STRUCTURE_DEPTH, signal: options.signal });
2696
+ const map = "structure" in envelope ? envelope.structure : undefined;
2844
2697
  if (!map || map.language === "unknown" && map.symbols.length === 0)
2845
2698
  return null;
2846
2699
  return map;
@@ -2865,8 +2718,8 @@ async function validateSyntaxRegression(input, options = {}) {
2865
2718
  let before;
2866
2719
  let after;
2867
2720
  try {
2868
- before = input.before === undefined ? EMPTY : await readSeekCheck(input.filePath, input.before, { signal: options.signal });
2869
- after = await readSeekCheck(input.filePath, input.after, { signal: options.signal });
2721
+ before = input.before === undefined ? EMPTY : await readSeekCheckSyntax("-", { stdinName: input.filePath, stdin: input.before, signal: options.signal });
2722
+ after = await readSeekCheckSyntax("-", { stdinName: input.filePath, stdin: input.after, signal: options.signal });
2870
2723
  } catch {
2871
2724
  return null;
2872
2725
  }
@@ -3552,7 +3405,7 @@ async function executeEdit(opts) {
3552
3405
  return await withFileMutationQueue(absolutePath, async () => {
3553
3406
  throwIfAborted(signal);
3554
3407
  if (wasReadInSession && !wasReadInSession(absolutePath)) {
3555
- const hint = `Call readSeek_inspect(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`;
3408
+ const hint = `Call readSeek_show_content(${JSON.stringify(rawPath)}) first, or use readSeek_find_text, readSeek_find_nodes, or readSeek_write_file to produce fresh anchors for this file.`;
3556
3409
  return buildEditError(absolutePath, "file-not-read", `You must get fresh anchors for ${absolutePath} before editing it. ${hint}`, hint);
3557
3410
  }
3558
3411
  const parsedEdits = validateExactlyOneEditVariant(parsed.edits ?? []);
@@ -3562,7 +3415,7 @@ async function executeEdit(opts) {
3562
3415
  const edits = parsedEdits.edits;
3563
3416
  let plan;
3564
3417
  try {
3565
- plan = await readSeekEdit(absolutePath, edits, { signal, language: parsed.language });
3418
+ plan = await readSeekEditText(absolutePath, edits, { signal, language: parsed.language });
3566
3419
  } catch (err) {
3567
3420
  throwIfAborted(signal);
3568
3421
  const failure = classifyReadSeekFailure(err);
@@ -3598,7 +3451,7 @@ async function executeEdit(opts) {
3598
3451
  throwIfAborted(signal);
3599
3452
  let applied;
3600
3453
  try {
3601
- applied = await readSeekEdit(absolutePath, edits, {
3454
+ applied = await readSeekEditText(absolutePath, edits, {
3602
3455
  apply: true,
3603
3456
  planHash: plan.plan_hash,
3604
3457
  language: parsed.language,
@@ -3666,9 +3519,9 @@ async function executeEdit(opts) {
3666
3519
  }
3667
3520
  }
3668
3521
  function registerEditTool(pi, options = {}) {
3669
- const name = options.name ?? "readSeek_edit";
3522
+ const name = options.name ?? "readSeek_edit_text";
3670
3523
  const promptMetadata = defineToolPromptMetadata({
3671
- promptUrl: new URL("../prompts/edit.md", import.meta.url),
3524
+ promptUrl: new URL("../prompts/edit_text.md", import.meta.url),
3672
3525
  promptSnippet: "Edit existing files with fresh LINE:HASH anchors",
3673
3526
  registeredName: name,
3674
3527
  toolAliases: options.toolAliases
@@ -3765,7 +3618,7 @@ ${theme.fg("error", info.errorText)}`;
3765
3618
 
3766
3619
  // src/grep.ts
3767
3620
  import { readFile as fsReadFile2, stat as fsStat } from "node:fs/promises";
3768
- import path3 from "node:path";
3621
+ import path4 from "node:path";
3769
3622
  import { createGrepTool } from "@earendil-works/pi-coding-agent";
3770
3623
  import { Type as Type5 } from "@sinclair/typebox";
3771
3624
  import { Text as Text4 } from "@earendil-works/pi-tui";
@@ -4135,7 +3988,7 @@ function formatGrepResultText(input) {
4135
3988
 
4136
3989
  // src/coerce-obvious-int.ts
4137
3990
  import {
4138
- coerceObviousBase10Int as coerceObviousBase10Int2
3991
+ coerceObviousBase10Int
4139
3992
  } from "@jarkkojs/readseek-api";
4140
3993
 
4141
3994
  // src/tui-source-render.ts
@@ -4255,21 +4108,21 @@ async function executeGrep(opts) {
4255
4108
  const { toolCallId, params, signal, onUpdate, cwd, onFileAnchored } = opts;
4256
4109
  await ensureHashInit();
4257
4110
  const rawParams = params;
4258
- const context = coerceObviousBase10Int2(rawParams.context, "context");
4111
+ const context = coerceObviousBase10Int(rawParams.context, "context");
4259
4112
  if (!context.ok) {
4260
4113
  return buildToolErrorResult("grep", "invalid-params-combo", context.message);
4261
4114
  }
4262
4115
  if (context.value !== undefined && context.value < 0) {
4263
4116
  return buildToolErrorResult("grep", "invalid-params-combo", `Invalid context: expected a non-negative integer, received ${context.value}.`);
4264
4117
  }
4265
- const limit = coerceObviousBase10Int2(rawParams.limit, "limit");
4118
+ const limit = coerceObviousBase10Int(rawParams.limit, "limit");
4266
4119
  if (!limit.ok) {
4267
4120
  return buildToolErrorResult("grep", "invalid-limit", limit.message);
4268
4121
  }
4269
4122
  if (limit.value !== undefined && limit.value < 1) {
4270
4123
  return buildToolErrorResult("grep", "invalid-limit", `Invalid limit: expected a positive integer, received ${limit.value}.`);
4271
4124
  }
4272
- const scopeContext = coerceObviousBase10Int2(rawParams.scopeContext, "scopeContext");
4125
+ const scopeContext = coerceObviousBase10Int(rawParams.scopeContext, "scopeContext");
4273
4126
  if (!scopeContext.ok) {
4274
4127
  return buildToolErrorResult("grep", "invalid-params-combo", scopeContext.message);
4275
4128
  }
@@ -4351,7 +4204,7 @@ async function executeGrep(opts) {
4351
4204
  };
4352
4205
  const toAbsolutePath = (displayPath) => {
4353
4206
  if (searchPathIsDirectory)
4354
- return path3.resolve(searchPath, displayPath);
4207
+ return path4.resolve(searchPath, displayPath);
4355
4208
  return searchPath;
4356
4209
  };
4357
4210
  const groupsByPath = new Map;
@@ -4539,9 +4392,9 @@ ${warning}` } : item),
4539
4392
  };
4540
4393
  }
4541
4394
  function registerGrepTool(pi, options = {}) {
4542
- const name = options.name ?? "readSeek_grep";
4395
+ const name = options.name ?? "readSeek_find_text";
4543
4396
  const promptMetadata = defineToolPromptMetadata({
4544
- promptUrl: new URL("../prompts/grep.md", import.meta.url),
4397
+ promptUrl: new URL("../prompts/find_text.md", import.meta.url),
4545
4398
  promptSnippet: "Search text or regex with edit-ready LINE:HASH anchors",
4546
4399
  registeredName: name
4547
4400
  });
@@ -4628,7 +4481,7 @@ function registerGrepTool(pi, options = {}) {
4628
4481
  }
4629
4482
 
4630
4483
  // src/search.ts
4631
- import path4 from "node:path";
4484
+ import path5 from "node:path";
4632
4485
  import { Type as Type6 } from "@sinclair/typebox";
4633
4486
 
4634
4487
  // src/stat-search-path.ts
@@ -4687,7 +4540,7 @@ function mergeRanges(ranges) {
4687
4540
  return merged;
4688
4541
  }
4689
4542
  var SEARCH_PROMPT_METADATA = defineToolPromptMetadata({
4690
- promptUrl: new URL("../prompts/search.md", import.meta.url),
4543
+ promptUrl: new URL("../prompts/find_nodes.md", import.meta.url),
4691
4544
  promptSnippet: "Search code by AST structure using ast-grep-style patterns"
4692
4545
  });
4693
4546
  function linesFromSearchResult(result, ranges) {
@@ -4713,7 +4566,7 @@ function linesFromSearchResult(result, ranges) {
4713
4566
  return lines;
4714
4567
  }
4715
4568
  function readSeekLanguageForPath(language, searchPath, isFile) {
4716
- if (language === "typescript" && isFile && path4.extname(searchPath).toLowerCase() === ".tsx")
4569
+ if (language === "typescript" && isFile && path5.extname(searchPath).toLowerCase() === ".tsx")
4717
4570
  return "tsx";
4718
4571
  return language;
4719
4572
  }
@@ -4727,13 +4580,18 @@ async function executeSearch(opts) {
4727
4580
  const searchPathIsFile = statResult.stats.isFile();
4728
4581
  try {
4729
4582
  const effectiveLang = readSeekLanguageForPath(p.language, searchPath, searchPathIsFile);
4730
- const results = await readSeekSearch(searchPath, p.pattern, {
4583
+ const results = await readSeekFindNodes(searchPath, {
4584
+ pattern: p.pattern,
4585
+ context: p.context,
4586
+ selector: p.selector,
4587
+ rule: p.rule,
4588
+ limit: p.limit,
4731
4589
  language: effectiveLang,
4732
4590
  gitScope: p.gitScope,
4733
4591
  signal
4734
4592
  });
4735
4593
  if (results.length === 0) {
4736
- const emptyOutput = buildSearchOutput({ pattern: p.pattern, files: [] });
4594
+ const emptyOutput = buildSearchOutput({ pattern: p.pattern ?? p.context ?? p.rule ?? "", files: [] });
4737
4595
  return {
4738
4596
  content: [{ type: "text", text: emptyOutput.text }],
4739
4597
  details: {
@@ -4743,8 +4601,8 @@ async function executeSearch(opts) {
4743
4601
  }
4744
4602
  const readSeekFiles = [];
4745
4603
  for (const result of results) {
4746
- const abs = path4.isAbsolute(result.file) ? result.file : path4.resolve(cwd, result.file);
4747
- const display = path4.relative(cwd, abs) || abs;
4604
+ const abs = path5.isAbsolute(result.file) ? result.file : path5.resolve(cwd, result.file);
4605
+ const display = path5.relative(cwd, abs) || abs;
4748
4606
  const ranges = result.matches.map((match) => ({ startLine: match.start_line, endLine: match.end_line }));
4749
4607
  const mergedRanges = mergeRanges(ranges);
4750
4608
  const lines = linesFromSearchResult(result, mergedRanges);
@@ -4758,7 +4616,7 @@ async function executeSearch(opts) {
4758
4616
  });
4759
4617
  }
4760
4618
  if (readSeekFiles.length === 0) {
4761
- const emptyOutput = buildSearchOutput({ pattern: p.pattern, files: [] });
4619
+ const emptyOutput = buildSearchOutput({ pattern: p.pattern ?? p.rule ?? "", files: [] });
4762
4620
  return {
4763
4621
  content: [{ type: "text", text: emptyOutput.text }],
4764
4622
  details: {
@@ -4767,7 +4625,7 @@ async function executeSearch(opts) {
4767
4625
  };
4768
4626
  }
4769
4627
  const builtOutput = buildSearchOutput({
4770
- pattern: p.pattern,
4628
+ pattern: p.pattern ?? p.rule ?? "",
4771
4629
  files: readSeekFiles
4772
4630
  });
4773
4631
  for (const readSeekFile of readSeekFiles) {
@@ -4786,14 +4644,18 @@ async function executeSearch(opts) {
4786
4644
  }
4787
4645
  function registerSearchTool(pi, options = {}) {
4788
4646
  const tool = registerReadSeekTool(pi, {
4789
- name: "readSeek_search",
4647
+ name: "readSeek_find_nodes",
4790
4648
  label: "Structural Search",
4791
4649
  description: SEARCH_PROMPT_METADATA.description,
4792
4650
  promptSnippet: SEARCH_PROMPT_METADATA.promptSnippet,
4793
4651
  promptGuidelines: SEARCH_PROMPT_METADATA.promptGuidelines,
4794
4652
  parameters: Type6.Object({
4795
- pattern: Type6.String({ description: PARAM_DESCRIPTIONS.pattern }),
4653
+ pattern: Type6.Optional(Type6.String({ description: PARAM_DESCRIPTIONS.pattern })),
4654
+ context: Type6.Optional(Type6.String({ description: PARAM_DESCRIPTIONS.patternContext })),
4655
+ selector: Type6.Optional(Type6.String({ description: PARAM_DESCRIPTIONS.selector })),
4796
4656
  language: languageParam(),
4657
+ rule: Type6.Optional(Type6.String({ description: PARAM_DESCRIPTIONS.rule })),
4658
+ limit: Type6.Optional(Type6.Integer({ minimum: 1 })),
4797
4659
  path: searchPathParam(),
4798
4660
  ...readSeekGitSearchParams()
4799
4661
  }),
@@ -4803,7 +4665,7 @@ function registerSearchTool(pi, options = {}) {
4803
4665
  renderCall(args, theme, ...rest) {
4804
4666
  return renderReadSeekSearchCall(args, theme, rest, {
4805
4667
  label: "search",
4806
- accent: `/${args.pattern}/`,
4668
+ accent: `/${args.pattern ?? args.context ?? args.rule}/`,
4807
4669
  flags: [args.gitScope && `git-scope=${args.gitScope}`]
4808
4670
  });
4809
4671
  },
@@ -4821,15 +4683,14 @@ function registerSearchTool(pi, options = {}) {
4821
4683
 
4822
4684
  // src/refs.ts
4823
4685
  import { Type as Type7 } from "@sinclair/typebox";
4824
- import path5 from "node:path";
4686
+ import path6 from "node:path";
4825
4687
 
4826
4688
  // src/refs-output.ts
4827
4689
  function refsQueryLabel(input) {
4828
4690
  if (input.name)
4829
4691
  return input.name;
4830
- if (input.scope && input.line !== undefined) {
4831
- return input.column === undefined ? `line:${input.line}` : `line:${input.line}:${input.column}`;
4832
- }
4692
+ if (input.at)
4693
+ return input.at;
4833
4694
  return "?";
4834
4695
  }
4835
4696
  function buildRefsOutput(input) {
@@ -4853,7 +4714,7 @@ function buildRefsOutput(input) {
4853
4714
 
4854
4715
  // src/refs.ts
4855
4716
  var REFS_PROMPT_METADATA = defineToolPromptMetadata({
4856
- promptUrl: new URL("../prompts/refs.md", import.meta.url),
4717
+ promptUrl: new URL("../prompts/find_references.md", import.meta.url),
4857
4718
  promptSnippet: "Find references by identifier name or cursor binding"
4858
4719
  });
4859
4720
  function refsLine(reference) {
@@ -4866,14 +4727,14 @@ function groupReferences(references, cwd) {
4866
4727
  const files = new Map;
4867
4728
  const seen = new Set;
4868
4729
  for (const reference of references) {
4869
- const abs = path5.isAbsolute(reference.file) ? reference.file : path5.resolve(cwd, reference.file);
4730
+ const abs = path6.isAbsolute(reference.file) ? reference.file : path6.resolve(cwd, reference.file);
4870
4731
  const dedupeKey = `${abs}:${reference.line}:${reference.column}`;
4871
4732
  if (seen.has(dedupeKey))
4872
4733
  continue;
4873
4734
  seen.add(dedupeKey);
4874
4735
  let file = files.get(abs);
4875
4736
  if (!file) {
4876
- file = { displayPath: path5.relative(cwd, abs) || abs, path: abs, lines: [] };
4737
+ file = { displayPath: path6.relative(cwd, abs) || abs, path: abs, lines: [] };
4877
4738
  files.set(abs, file);
4878
4739
  }
4879
4740
  file.lines.push(refsLine(reference));
@@ -4886,7 +4747,7 @@ function isReadSeekCursorValidationFailure(message) {
4886
4747
  async function executeRefs(opts) {
4887
4748
  const { params, signal, cwd, onFileAnchored } = opts;
4888
4749
  const p = params;
4889
- const scopeError = validateRefsNameVsScope(p);
4750
+ const scopeError = validateReferencesSelection(p);
4890
4751
  if (!scopeError.ok) {
4891
4752
  return buildToolErrorResult("refs", "invalid-parameter", `refs parameter: ${scopeError.error}`);
4892
4753
  }
@@ -4895,11 +4756,9 @@ async function executeRefs(opts) {
4895
4756
  if (!statResult.ok)
4896
4757
  return statResult.error;
4897
4758
  try {
4898
- const references = await readSeekRefs(searchPath, {
4759
+ const references = await readSeekFindReferences(searchPath, {
4899
4760
  name: p.name,
4900
- scope: p.scope,
4901
- line: p.line,
4902
- column: p.column,
4761
+ at: p.at,
4903
4762
  language: p.language,
4904
4763
  gitScope: p.gitScope,
4905
4764
  signal
@@ -4907,9 +4766,7 @@ async function executeRefs(opts) {
4907
4766
  const files = groupReferences(references, cwd);
4908
4767
  const builtOutput = buildRefsOutput({
4909
4768
  name: p.name,
4910
- scope: p.scope,
4911
- line: p.line,
4912
- column: p.column,
4769
+ at: p.at,
4913
4770
  files
4914
4771
  });
4915
4772
  for (const file of files) {
@@ -4921,7 +4778,7 @@ async function executeRefs(opts) {
4921
4778
  };
4922
4779
  } catch (err) {
4923
4780
  const failure = classifyReadSeekFailure(err);
4924
- if (p.scope && isReadSeekCursorValidationFailure(failure.message)) {
4781
+ if (p.at && isReadSeekCursorValidationFailure(failure.message)) {
4925
4782
  return buildToolErrorResult("refs", "invalid-parameter", failure.message);
4926
4783
  }
4927
4784
  return buildToolErrorResult("refs", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
@@ -4929,7 +4786,7 @@ async function executeRefs(opts) {
4929
4786
  }
4930
4787
  function registerRefsTool(pi, options = {}) {
4931
4788
  const tool = registerReadSeekTool(pi, {
4932
- name: "readSeek_refs",
4789
+ name: "readSeek_find_references",
4933
4790
  label: "References",
4934
4791
  description: REFS_PROMPT_METADATA.description,
4935
4792
  promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
@@ -4938,20 +4795,18 @@ function registerRefsTool(pi, options = {}) {
4938
4795
  name: Type7.Optional(Type7.String({ description: PARAM_DESCRIPTIONS.refsName })),
4939
4796
  path: searchPathParam(),
4940
4797
  language: languageParam(),
4941
- scope: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.scope })),
4942
- line: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.line })),
4943
- column: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.column })),
4798
+ at: Type7.Optional(Type7.String({ description: PARAM_DESCRIPTIONS.at })),
4944
4799
  ...readSeekGitSearchParams()
4945
4800
  }),
4946
4801
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
4947
4802
  return executeRefs({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
4948
4803
  },
4949
4804
  renderCall(args, theme, ...rest) {
4950
- const accent = args.name ?? (args.scope ? args.column === undefined ? `line:${args.line}` : `line:${args.line}:${args.column}` : "?");
4805
+ const accent = args.name ?? args.at ?? "?";
4951
4806
  return renderReadSeekSearchCall(args, theme, rest, {
4952
4807
  label: "refs",
4953
4808
  accent,
4954
- flags: [args.scope && "scope", args.gitScope && `git-scope=${args.gitScope}`]
4809
+ flags: [args.at && "binding", args.gitScope && `git-scope=${args.gitScope}`]
4955
4810
  });
4956
4811
  },
4957
4812
  renderResult(result, options, theme, ...rest) {
@@ -4968,16 +4823,15 @@ function registerRefsTool(pi, options = {}) {
4968
4823
 
4969
4824
  // src/rename.ts
4970
4825
  import { Type as Type8 } from "@sinclair/typebox";
4971
- import path6 from "node:path";
4826
+ import path7 from "node:path";
4972
4827
  import { Text as Text5 } from "@earendil-works/pi-tui";
4973
4828
  var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
4974
- promptUrl: new URL("../prompts/rename.md", import.meta.url),
4829
+ promptUrl: new URL("../prompts/rename_symbol.md", import.meta.url),
4975
4830
  promptSnippet: "Rename a symbol at a source location with verified edits"
4976
4831
  });
4977
4832
  var renameSchema = Type8.Object({
4978
4833
  path: filePathParam(),
4979
- line: Type8.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.renameLine }),
4980
- column: Type8.Optional(Type8.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.column })),
4834
+ at: Type8.String({ description: PARAM_DESCRIPTIONS.at }),
4981
4835
  to: Type8.String({ description: PARAM_DESCRIPTIONS.to }),
4982
4836
  workspace: Type8.Optional(Type8.Boolean({ description: PARAM_DESCRIPTIONS.workspace })),
4983
4837
  apply: Type8.Optional(Type8.Boolean({ description: PARAM_DESCRIPTIONS.applyDefaultTrue }))
@@ -4988,18 +4842,14 @@ async function executeRename(opts) {
4988
4842
  if (!p.to.trim()) {
4989
4843
  return buildToolErrorResult("rename", "invalid-parameter", "rename parameter 'to' must not be empty");
4990
4844
  }
4991
- if (!Number.isSafeInteger(p.line) || p.line < 1) {
4992
- return buildToolErrorResult("rename", "invalid-parameter", "rename parameter 'line' must be a positive integer");
4993
- }
4994
- if (p.column !== undefined && (!Number.isSafeInteger(p.column) || p.column < 1)) {
4995
- return buildToolErrorResult("rename", "invalid-parameter", "rename parameter 'column' must be a positive integer");
4845
+ if (typeof p.at !== "string" || !/^(line:[1-9][0-9]*|hash:[0-9a-fA-F]+)(:[1-9][0-9]*)?$/.test(p.at)) {
4846
+ return buildToolErrorResult("rename_symbol", "invalid-parameter", "at must be a line or hash location with an optional positive column");
4996
4847
  }
4997
4848
  const filePath = resolveToCwd2(p.path, cwd);
4998
4849
  try {
4999
- const output = await readSeekRename(filePath, {
4850
+ const output = await readSeekRenameSymbol(filePath, {
5000
4851
  to: p.to,
5001
- line: p.line,
5002
- column: p.column,
4852
+ at: p.at,
5003
4853
  workspace: p.workspace ? cwd : undefined,
5004
4854
  apply: p.apply ?? true,
5005
4855
  signal
@@ -5008,14 +4858,14 @@ async function executeRename(opts) {
5008
4858
  for (const file of [output, ...output.others]) {
5009
4859
  if (file.edits.length === 0)
5010
4860
  continue;
5011
- const absoluteFile = path6.isAbsolute(file.file) ? file.file : path6.resolve(cwd, file.file);
4861
+ const absoluteFile = path7.isAbsolute(file.file) ? file.file : path7.resolve(cwd, file.file);
5012
4862
  onFileMutated?.(absoluteFile);
5013
4863
  }
5014
4864
  }
5015
- const primaryAbs = path6.isAbsolute(output.file) ? output.file : path6.resolve(cwd, output.file);
4865
+ const primaryAbs = path7.isAbsolute(output.file) ? output.file : path7.resolve(cwd, output.file);
5016
4866
  const files = [primaryAbs];
5017
4867
  for (const other of output.others) {
5018
- const abs = path6.isAbsolute(other.file) ? other.file : path6.resolve(cwd, other.file);
4868
+ const abs = path7.isAbsolute(other.file) ? other.file : path7.resolve(cwd, other.file);
5019
4869
  if (!files.includes(abs))
5020
4870
  files.push(abs);
5021
4871
  }
@@ -5054,7 +4904,7 @@ First conflict at ${firstConflict.line}:${firstConflict.column}: ${firstConflict
5054
4904
  }
5055
4905
  function registerRenameTool(pi, options = {}) {
5056
4906
  registerReadSeekTool(pi, {
5057
- name: "readSeek_rename",
4907
+ name: "readSeek_rename_symbol",
5058
4908
  label: "Rename",
5059
4909
  description: RENAME_PROMPT_METADATA.description,
5060
4910
  promptSnippet: RENAME_PROMPT_METADATA.promptSnippet,
@@ -5097,7 +4947,7 @@ function registerRenameTool(pi, options = {}) {
5097
4947
  });
5098
4948
  if (expanded && output) {
5099
4949
  for (const file of outputs) {
5100
- const display = path6.relative(cwd, file.file) || file.file;
4950
+ const display = path7.relative(cwd, file.file) || file.file;
5101
4951
  text += `
5102
4952
  ${linkedPathLine(theme, file.file, display, cwd, ` (${file.edits.length} edits)`, width)}`;
5103
4953
  }
@@ -5320,7 +5170,7 @@ async function executeWrite(opts) {
5320
5170
  const warnings = [];
5321
5171
  const readSeekWarnings = [];
5322
5172
  if (hasBareCarriageReturn(content)) {
5323
- const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_inspect/readSeek_edit would normalize differently.";
5173
+ const message = "File content contains bare CR (\\r) line endings; readSeek_write_file refuses to emit anchors that readSeek_show_content/readSeek_edit_text would normalize differently.";
5324
5174
  warnings.push(message);
5325
5175
  readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
5326
5176
  return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}
@@ -5399,9 +5249,9 @@ async function executeWrite(opts) {
5399
5249
  };
5400
5250
  }
5401
5251
  function registerWriteTool(pi, options = {}) {
5402
- const name = options.name ?? "readSeek_write";
5252
+ const name = options.name ?? "readSeek_write_file";
5403
5253
  const promptMetadata = defineToolPromptMetadata({
5404
- promptUrl: new URL("../prompts/write.md", import.meta.url),
5254
+ promptUrl: new URL("../prompts/write_file.md", import.meta.url),
5405
5255
  promptSnippet: "Create or replace a complete file with LINE:HASH anchors",
5406
5256
  registeredName: name
5407
5257
  });
@@ -5513,23 +5363,23 @@ function registerWriteTool(pi, options = {}) {
5513
5363
 
5514
5364
  // src/def.ts
5515
5365
  import { Type as Type10 } from "@sinclair/typebox";
5516
- import path7 from "node:path";
5366
+ import path8 from "node:path";
5517
5367
  var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5518
- promptUrl: new URL("../prompts/def.md", import.meta.url),
5368
+ promptUrl: new URL("../prompts/find_definitions.md", import.meta.url),
5519
5369
  promptSnippet: "Go to a symbol definition or declaration by name"
5520
5370
  });
5521
5371
  async function executeDef(opts) {
5522
5372
  const { params, signal, cwd, onFileAnchored } = opts;
5523
5373
  const p = params;
5524
5374
  if (!p.name || !p.name.trim()) {
5525
- return buildToolErrorResult("def", "invalid-parameter", "readSeek_def requires 'name'");
5375
+ return buildToolErrorResult("def", "invalid-parameter", "readSeek_find_definitions requires 'name'");
5526
5376
  }
5527
5377
  const searchPath = resolveToCwd2(p.path ?? ".", cwd);
5528
5378
  const statResult = await statSearchPathOrError("def", p.path, searchPath);
5529
5379
  if (!statResult.ok)
5530
5380
  return statResult.error;
5531
5381
  try {
5532
- const definitions = await readSeekDef(searchPath, {
5382
+ const definitions = await readSeekFindDefinitions(searchPath, {
5533
5383
  name: p.name,
5534
5384
  language: p.language,
5535
5385
  gitScope: p.gitScope,
@@ -5545,10 +5395,10 @@ async function executeDef(opts) {
5545
5395
  }
5546
5396
  const files = new Map;
5547
5397
  for (const def of definitions) {
5548
- const abs = path7.isAbsolute(def.file) ? def.file : path7.resolve(cwd, def.file);
5398
+ const abs = path8.isAbsolute(def.file) ? def.file : path8.resolve(cwd, def.file);
5549
5399
  let file = files.get(abs);
5550
5400
  if (!file) {
5551
- file = { displayPath: path7.relative(cwd, abs) || abs, path: abs, lines: [] };
5401
+ file = { displayPath: path8.relative(cwd, abs) || abs, path: abs, lines: [] };
5552
5402
  files.set(abs, file);
5553
5403
  }
5554
5404
  file.lines.push(buildReadSeekLineWithHash(def.line, def.line_hash, def.text));
@@ -5578,7 +5428,7 @@ async function executeDef(opts) {
5578
5428
  }
5579
5429
  function registerDefTool(pi, options = {}) {
5580
5430
  registerReadSeekTool(pi, {
5581
- name: "readSeek_def",
5431
+ name: "readSeek_find_definitions",
5582
5432
  label: "Definition",
5583
5433
  description: DEF_PROMPT_METADATA.description,
5584
5434
  promptSnippet: DEF_PROMPT_METADATA.promptSnippet,
@@ -5610,379 +5460,120 @@ function registerDefTool(pi, options = {}) {
5610
5460
  });
5611
5461
  }
5612
5462
 
5613
- // src/inspect.ts
5463
+ // src/access.ts
5614
5464
  import { Text as Text7 } from "@earendil-works/pi-tui";
5615
5465
  import { Type as Type11 } from "@sinclair/typebox";
5616
- var facetSchema = Type11.Union(INSPECT_FACETS.map((facet) => Type11.Literal(facet)));
5617
- var inspectSchema = Type11.Object({
5618
- path: filePathParam(),
5619
- selector: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.selector })),
5620
- select: Type11.Optional(Type11.Union([
5621
- facetSchema,
5622
- Type11.Array(facetSchema, { minItems: 1 }),
5623
- Type11.String({ description: PARAM_DESCRIPTIONS.selectComma })
5624
- ], { description: PARAM_DESCRIPTIONS.select })),
5625
- query: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.query })),
5626
- end: optionalIntOrString(PARAM_DESCRIPTIONS.end),
5627
- limit: optionalIntOrString(PARAM_DESCRIPTIONS.limit),
5628
- depth: optionalIntOrString(PARAM_DESCRIPTIONS.inspectDepth),
5629
- language: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.languageOverride })),
5630
- visionMode: Type11.Optional(Type11.Union(VISION_MODES.map((mode) => Type11.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeInspect })),
5631
- visionLevel: Type11.Optional(Type11.Union(VISION_LEVELS.map((level) => Type11.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel }))
5632
- });
5633
- function hasInspectAnchors(value) {
5466
+ import { readFile as readFile2 } from "node:fs/promises";
5467
+ var operations = {
5468
+ show_content: readSeekShowContent,
5469
+ show_structure: readSeekShowStructure,
5470
+ show_metadata: readSeekShowMetadata,
5471
+ show_assets: readSeekShowAssets,
5472
+ check_syntax: readSeekCheckSyntax,
5473
+ check_rules: readSeekCheckRules,
5474
+ generate_caption: readSeekGenerateCaption,
5475
+ detect_objects: readSeekDetectObjects,
5476
+ recognize_text: readSeekRecognizeText
5477
+ };
5478
+ function hasAnchors(value) {
5634
5479
  if (!value || typeof value !== "object")
5635
5480
  return false;
5636
5481
  const record = value;
5637
5482
  if (Array.isArray(record.hashlines) && record.hashlines.length > 0)
5638
5483
  return true;
5639
- return Object.values(record).some(hasInspectAnchors);
5640
- }
5641
- function parseSelect(select) {
5642
- if (select === undefined)
5643
- return;
5644
- if (Array.isArray(select)) {
5645
- const facets = select;
5646
- if (facets.length === 0)
5647
- throw new Error("select must include at least one facet");
5648
- for (const facet of facets) {
5649
- if (!INSPECT_FACETS.includes(facet)) {
5650
- throw new Error(`unknown inspect facet \`${facet}\`; expected ${INSPECT_FACETS.join(", ")}`);
5651
- }
5652
- }
5653
- return facets;
5654
- }
5655
- if (typeof select !== "string") {
5656
- throw new Error("select must be a facet, facet list, or comma-separated facets");
5657
- }
5658
- if (INSPECT_FACETS.includes(select)) {
5659
- return select;
5660
- }
5661
- const parts = select.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
5662
- if (parts.length === 0)
5663
- throw new Error("select must include at least one facet");
5664
- for (const part of parts) {
5665
- if (!INSPECT_FACETS.includes(part)) {
5666
- throw new Error(`unknown inspect facet \`${part}\`; expected ${INSPECT_FACETS.join(", ")}`);
5667
- }
5668
- }
5669
- return parts;
5670
- }
5671
- function selectedFacets(select) {
5672
- if (select === undefined)
5673
- return ["content"];
5674
- return Array.isArray(select) ? select : [select];
5675
- }
5676
- function summarizeEnvelope(envelope, facets) {
5677
- if (!envelope || typeof envelope !== "object")
5678
- return `inspected ${facets.join(",")}`;
5679
- const record = envelope;
5680
- const parts = [];
5681
- for (const facet of facets) {
5682
- if (facet === "metadata" || record[facet] !== undefined)
5683
- parts.push(facet);
5684
- }
5685
- if (record.metadata !== undefined && !parts.includes("metadata"))
5686
- parts.unshift("metadata");
5687
- return `inspected ${parts.join(",") || "metadata"}`;
5688
- }
5689
- function parseImageEnvelope(envelope) {
5690
- if (!envelope || typeof envelope !== "object")
5691
- return;
5692
- const record = envelope;
5693
- if (!record.metadata || typeof record.metadata !== "object")
5694
- return;
5695
- const metadata = record.metadata;
5696
- if (typeof metadata.type !== "string" || !metadata.type.startsWith("image/"))
5697
- return;
5698
- if (!record.content || typeof record.content !== "object")
5699
- return { metadata };
5700
- const content = record.content;
5701
- const mime = content.mime ?? content.type ?? metadata.mime ?? metadata.type;
5702
- const prepared = content.encoding === "base64" && typeof content.data === "string" && typeof mime === "string" ? { data: content.data, mimeType: mime } : undefined;
5703
- return { metadata, prepared };
5704
- }
5705
- function buildInspectSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5706
- return {
5707
- content,
5708
- details: {
5709
- readSeekValue: {
5710
- tool: "inspect",
5711
- ok: true,
5712
- path: filePath,
5713
- select: facets,
5714
- envelope
5715
- }
5716
- }
5717
- };
5484
+ return Object.values(record).some(hasAnchors);
5718
5485
  }
5719
- async function executeInspect(opts) {
5720
- const { params, signal, cwd, onFileAnchored } = opts;
5721
- const p = params;
5722
- let select;
5723
- try {
5724
- select = parseSelect(p.select);
5725
- } catch (err) {
5726
- return buildToolErrorResult("inspect", "invalid-parameter", err?.message ?? String(err), {
5727
- path: p.path
5728
- });
5729
- }
5730
- const facets = selectedFacets(select);
5731
- const appliesImagePolicy = opts.imageMode !== undefined && facets.includes("content");
5732
- const end = coerceObviousBase10Int2(p.end, "end");
5733
- if (!end.ok || end.value !== undefined && end.value < 1) {
5734
- const message = end.ok ? `Invalid end: expected a positive integer, received ${end.value}.` : end.message;
5735
- return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5736
- }
5737
- const limit = coerceObviousBase10Int2(p.limit, "limit");
5738
- if (!limit.ok || limit.value !== undefined && limit.value < 1) {
5739
- const message = limit.ok ? `Invalid limit: expected a positive integer, received ${limit.value}.` : limit.message;
5740
- return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5741
- }
5742
- if (end.value !== undefined && limit.value !== undefined) {
5743
- return buildToolErrorResult("inspect", "invalid-params-combo", "end and limit cannot be combined.", { path: p.path });
5744
- }
5745
- const depth = coerceObviousBase10Int2(p.depth, "depth");
5746
- if (!depth.ok || depth.value !== undefined && depth.value < 0) {
5747
- const message = depth.ok ? `Invalid depth: expected a non-negative integer, received ${depth.value}.` : depth.message;
5748
- return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5749
- }
5750
- if (depth.value !== undefined && !facets.includes("map")) {
5751
- return buildToolErrorResult("inspect", "invalid-parameter", "depth requires the map facet.", { path: p.path });
5752
- }
5753
- const selector = typeof p.selector === "string" ? p.selector.trim() : undefined;
5754
- if (p.selector !== undefined && !selector) {
5755
- return buildToolErrorResult("inspect", "invalid-parameter", "Invalid selector: expected a non-empty selector.", { path: p.path });
5756
- }
5757
- const query = typeof p.query === "string" ? p.query.trim() : undefined;
5758
- if (p.query !== undefined && !query) {
5759
- return buildToolErrorResult("inspect", "invalid-parameter", "Invalid query: expected a non-empty query.", { path: p.path });
5760
- }
5761
- if (selector !== undefined && query !== undefined) {
5762
- return buildToolErrorResult("inspect", "invalid-params-combo", "selector and query cannot be combined.", { path: p.path });
5763
- }
5764
- if (facets.includes("identity") && selector === undefined) {
5765
- return buildToolErrorResult("inspect", "invalid-params-combo", "the identity facet requires a selector.", { path: p.path });
5766
- }
5767
- if (p.visionLevel !== undefined && (p.visionMode === undefined || p.visionMode === "none") && !appliesImagePolicy) {
5768
- return buildToolErrorResult("inspect", "invalid-params-combo", "visionLevel requires an analysis visionMode.", { path: p.path });
5769
- }
5770
- const filePath = resolveToCwd2(p.path, cwd);
5771
- const commonOptions = {
5772
- select,
5773
- signal,
5774
- depth: depth.value ?? (facets.includes("map") ? DEFAULT_INSPECT_MAP_DEPTH : undefined)
5775
- };
5776
- const selectionOptions = {
5777
- ...commonOptions,
5778
- selector,
5779
- query,
5780
- end: end.value,
5781
- limit: limit.value,
5782
- language: p.language
5783
- };
5784
- const probeOptions = {
5785
- ...commonOptions,
5786
- select: [...new Set(["metadata", ...facets])].filter((facet) => facet !== "identity")
5787
- };
5788
- const hasSelectionOptions = selector !== undefined || query !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5789
- const hasVisionOptions = p.visionMode !== undefined || p.visionLevel !== undefined;
5790
- const initialVisionMode = appliesImagePolicy ? undefined : p.visionMode;
5791
- const initialVisionLevel = appliesImagePolicy ? undefined : p.visionLevel;
5486
+ function imagePaths(value, paths = new Map) {
5487
+ if (!value || typeof value !== "object")
5488
+ return paths;
5489
+ const record = value;
5490
+ if (typeof record.path === "string" && typeof record.mime === "string" && record.mime.startsWith("image/")) {
5491
+ paths.set(record.path, record.mime);
5492
+ }
5493
+ for (const child of Object.values(record))
5494
+ imagePaths(child, paths);
5495
+ return paths;
5496
+ }
5497
+ async function executeAccess(opts) {
5498
+ if (!opts.params || typeof opts.params !== "object" || Array.isArray(opts.params)) {
5499
+ return buildToolErrorResult(opts.tool, "invalid-parameter", "Expected an object");
5500
+ }
5501
+ const p = opts.params;
5502
+ const contract = getToolContract(opts.tool);
5503
+ for (const key of Object.keys(p)) {
5504
+ if (key === "stdinName" || !Object.hasOwn(contract.inputSchema.properties, key))
5505
+ return buildToolErrorResult(opts.tool, "invalid-parameter", `Unknown parameter: ${key}`);
5506
+ }
5507
+ const path = p.path === undefined && opts.tool === "check_rules" ? "." : p.path;
5508
+ if (typeof path !== "string" || !path.trim())
5509
+ return buildToolErrorResult(opts.tool, "invalid-parameter", "path is required");
5510
+ if (path === "-")
5511
+ return buildToolErrorResult(opts.tool, "invalid-parameter", "Agent tools require a file path; stdin is available through the CLI or API");
5512
+ const filePath = resolveToCwd2(path, opts.cwd);
5792
5513
  try {
5793
- const envelope = await readSeekInspect(filePath, {
5794
- ...appliesImagePolicy ? probeOptions : selectionOptions,
5795
- visionMode: initialVisionMode,
5796
- visionLevel: initialVisionLevel,
5797
- vision: initialVisionMode !== undefined && initialVisionMode !== "none"
5798
- });
5799
- const image = appliesImagePolicy ? parseImageEnvelope(envelope) : undefined;
5800
- if (image) {
5801
- const imageDetails = { metadata: image.metadata };
5802
- if (opts.imageMode === "off") {
5803
- return buildInspectSuccess(filePath, facets, imageDetails, [
5804
- { type: "text", text: `[Skipped standalone image: ${p.path}; readseek.imageMode is off]` }
5805
- ]);
5806
- }
5807
- if (opts.imageMode === "auto" && opts.modelSupportsImages && image.prepared) {
5808
- return buildInspectSuccess(filePath, facets, imageDetails, [
5809
- { type: "image", data: image.prepared.data, mimeType: image.prepared.mimeType }
5810
- ]);
5514
+ const { path: _path, ...options } = p;
5515
+ const envelope = await operations[opts.tool](filePath, { ...options, signal: opts.signal });
5516
+ if (hasAnchors(envelope))
5517
+ opts.onFileAnchored?.(filePath);
5518
+ const content = [
5519
+ { type: "text", text: JSON.stringify(envelope) }
5520
+ ];
5521
+ if (opts.tool === "show_content" || opts.tool === "show_assets") {
5522
+ for (const [imagePath, mimeType] of imagePaths(envelope)) {
5523
+ if (opts.imageMode === "off")
5524
+ content.push({ type: "text", text: `Image available at ${imagePath}; image attachments are disabled by readseek.imageMode.` });
5525
+ else if (opts.modelSupportsImages)
5526
+ content.push({ type: "image", data: (await readFile2(imagePath)).toString("base64"), mimeType });
5527
+ else
5528
+ content.push({ type: "text", text: `Image available at ${imagePath}; this host model does not support image attachments.` });
5811
5529
  }
5812
- const visionMode = p.visionMode && p.visionMode !== "none" ? p.visionMode : "all";
5813
- const analyzedEnvelope = await readSeekInspect(filePath, {
5814
- ...commonOptions,
5815
- visionMode,
5816
- visionLevel: p.visionLevel,
5817
- vision: true
5818
- });
5819
- return buildInspectSuccess(filePath, facets, analyzedEnvelope);
5820
- }
5821
- if (p.visionLevel !== undefined && (p.visionMode === undefined || p.visionMode === "none")) {
5822
- return buildToolErrorResult("inspect", "invalid-params-combo", "visionLevel requires an analysis visionMode.", {
5823
- path: p.path
5824
- });
5825
5530
  }
5826
- const selectedEnvelope = appliesImagePolicy && (hasSelectionOptions || hasVisionOptions) ? await readSeekInspect(filePath, {
5827
- ...selectionOptions,
5828
- visionMode: p.visionMode,
5829
- visionLevel: p.visionLevel,
5830
- vision: p.visionMode !== undefined && p.visionMode !== "none"
5831
- }) : envelope;
5832
- if (hasInspectAnchors(selectedEnvelope))
5833
- onFileAnchored?.(filePath);
5834
- return buildInspectSuccess(filePath, facets, selectedEnvelope);
5531
+ return { content, details: { readSeekValue: { tool: opts.tool, ok: true, path: filePath, envelope } } };
5835
5532
  } catch (err) {
5836
5533
  const failure = classifyReadSeekFailure(err);
5837
- return buildToolErrorResult("inspect", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5838
- }
5839
- }
5840
- function registerInspectTool(pi, options = {}) {
5841
- const promptMetadata = defineToolPromptMetadata({
5842
- promptUrl: new URL("../prompts/inspect.md", import.meta.url),
5843
- promptSnippet: "Inspect source, images, and documents",
5844
- registeredName: options.name ?? "readSeek_inspect",
5845
- toolAliases: options.toolAliases
5846
- });
5847
- return registerReadSeekTool(pi, {
5848
- name: options.name ?? "readSeek_inspect",
5849
- label: "Inspect",
5850
- description: promptMetadata.description,
5851
- promptSnippet: promptMetadata.promptSnippet,
5852
- toolAliases: options.toolAliases,
5853
- promptGuidelines: promptMetadata.promptGuidelines,
5854
- parameters: inspectSchema,
5855
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5856
- return executeInspect({
5857
- params,
5858
- signal,
5859
- cwd: ctx.cwd,
5860
- onFileAnchored: options.onFileAnchored,
5861
- imageMode: options.imageMode,
5862
- modelSupportsImages: ctx.model?.input.includes("image") ?? false
5863
- });
5864
- },
5865
- renderCall(args, theme, ...rest) {
5866
- const context = rest[0] ?? {};
5867
- const cwd = context.cwd ?? process.cwd();
5868
- const displayPath = typeof args?.path === "string" ? args.path : "?";
5869
- let text = theme.fg("toolTitle", theme.bold("inspect"));
5870
- text += ` ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5871
- if (args?.select !== undefined) {
5872
- const select = Array.isArray(args.select) ? args.select.join(",") : String(args.select);
5873
- text += theme.fg("dim", ` [${select}]`);
5874
- }
5875
- if (typeof args?.selector === "string" && args.selector)
5876
- text += theme.fg("dim", ` @ ${args.selector}`);
5877
- return new Text7(clampLineToWidth(text, context.width), 0, 0);
5878
- },
5879
- renderResult(result, options, theme, ...rest) {
5880
- const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
5881
- if (isPartial)
5882
- return renderPendingResult("pending inspect", width, theme);
5883
- const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
5884
- if (isError || result.isError) {
5885
- return renderErrorResult(textContent, { expanded, width, fallback: "inspect failed", theme });
5886
- }
5887
- const value = result.details?.readSeekValue;
5888
- const facets = Array.isArray(value?.select) ? value.select : ["content"];
5889
- const label = summarizeEnvelope(value?.envelope, facets);
5890
- let text = summaryLine(label, { hidden: !!textContent && !expanded, theme, style: "success" });
5891
- if (expanded && textContent)
5892
- text += `
5893
- ${textContent}`;
5894
- return new Text7(clampLinesToWidth(text.split(`
5534
+ return buildToolErrorResult(opts.tool, failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5535
+ }
5536
+ }
5537
+ function registerAccessTools(pi, options = {}) {
5538
+ for (const name of Object.keys(operations)) {
5539
+ const registeredName = name === "show_content" ? options.name ?? `readSeek_${name}` : `readSeek_${name}`;
5540
+ const contract = getToolContract(name);
5541
+ const metadata = defineToolPromptMetadata({
5542
+ promptUrl: new URL(`../prompts/${name}.md`, import.meta.url),
5543
+ promptSnippet: contract.title,
5544
+ registeredName,
5545
+ toolAliases: options.toolAliases
5546
+ });
5547
+ registerReadSeekTool(pi, {
5548
+ name: registeredName,
5549
+ label: contract.title,
5550
+ ...metadata,
5551
+ parameters: Type11.Unsafe({
5552
+ ...contract.inputSchema,
5553
+ properties: Object.fromEntries(Object.entries(contract.inputSchema.properties).filter(([key]) => key !== "stdinName"))
5554
+ }),
5555
+ async execute(_id, params, signal, _update, ctx) {
5556
+ return executeAccess({ tool: name, params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored, imageMode: options.imageMode, modelSupportsImages: ctx.model?.input.includes("image") ?? false });
5557
+ },
5558
+ renderCall(args) {
5559
+ return new Text7(`${name} ${args.path ?? ""}${args.at ? ` @ ${args.at}` : ""}`, 0, 0);
5560
+ },
5561
+ renderResult(result, renderOptions, theme, ...rest) {
5562
+ const { isPartial, isError, expanded, width } = resolveRenderResultContext(renderOptions, rest);
5563
+ if (isPartial)
5564
+ return renderPendingResult(`pending ${name}`, width, theme);
5565
+ const text = result.content?.find((item) => item.type === "text")?.text ?? "";
5566
+ if (isError || result.isError)
5567
+ return renderErrorResult(text, { expanded, width, fallback: `${name} failed`, theme });
5568
+ const summary = summaryLine(contract.title, { hidden: !!text && !expanded, theme, style: "success" });
5569
+ return new Text7(clampLinesToWidth((expanded ? `${summary}
5570
+ ${text}` : summary).split(`
5895
5571
  `), width).join(`
5896
5572
  `), 0, 0);
5897
- }
5898
- });
5899
- }
5900
-
5901
- // src/extract.ts
5902
- import { Text as Text8 } from "@earendil-works/pi-tui";
5903
- import { Type as Type12 } from "@sinclair/typebox";
5904
- var EXTRACT_PROMPT_METADATA = defineToolPromptMetadata({
5905
- promptUrl: new URL("../prompts/extract.md", import.meta.url),
5906
- promptSnippet: "Extract a selected document asset to a file"
5907
- });
5908
- async function executeExtract(opts) {
5909
- const { signal, cwd, onFileMutated } = opts;
5910
- const input = opts.params;
5911
- const selector = typeof input.selector === "string" ? input.selector.trim() : "";
5912
- if (!selector.startsWith("asset:") || selector === "asset:") {
5913
- return buildToolErrorResult("extract", "invalid-selector", "selector must identify an asset as asset:ID.", {
5914
- path: input.path
5915
- });
5916
- }
5917
- const output = typeof input.output === "string" ? input.output.trim() : "";
5918
- if (!output || output === "-") {
5919
- return buildToolErrorResult("extract", "invalid-output", "output must be a file path, not stdout.", {
5920
- path: input.path
5921
- });
5922
- }
5923
- const filePath = resolveToCwd2(input.path, cwd);
5924
- const outputPath = resolveToCwd2(output, cwd);
5925
- try {
5926
- await readSeekExtract(filePath, selector, outputPath, { signal, cancelable: false });
5927
- onFileMutated?.(outputPath);
5928
- return {
5929
- content: [{ type: "text", text: `Extracted ${selector} to ${outputPath}` }],
5930
- details: {
5931
- readSeekValue: {
5932
- tool: "extract",
5933
- ok: true,
5934
- path: filePath,
5935
- selector,
5936
- output: outputPath
5937
- }
5938
5573
  }
5939
- };
5940
- } catch (error) {
5941
- const failure = classifyReadSeekFailure(error);
5942
- return buildToolErrorResult("extract", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5574
+ });
5943
5575
  }
5944
5576
  }
5945
- function registerExtractTool(pi, options = {}) {
5946
- return registerReadSeekTool(pi, {
5947
- name: "readSeek_extract",
5948
- label: "Extract",
5949
- ...EXTRACT_PROMPT_METADATA,
5950
- parameters: Type12.Object({
5951
- path: filePathParam(),
5952
- selector: Type12.String({ pattern: "^asset:.+", description: PARAM_DESCRIPTIONS.assetSelector }),
5953
- output: Type12.String({ minLength: 1, pattern: "^(?!-$).+", description: PARAM_DESCRIPTIONS.output })
5954
- }),
5955
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5956
- return executeExtract({
5957
- params,
5958
- signal,
5959
- cwd: ctx.cwd,
5960
- onFileMutated: options.onFileMutated
5961
- });
5962
- },
5963
- renderCall(args, theme, ...rest) {
5964
- const context = rest[0] ?? {};
5965
- const cwd = context.cwd ?? process.cwd();
5966
- const displayPath = typeof args?.path === "string" ? args.path : "?";
5967
- const output = typeof args?.output === "string" ? args.output : "?";
5968
- let text = `${theme.fg("toolTitle", theme.bold("extract"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5969
- if (typeof args?.selector === "string")
5970
- text += theme.fg("dim", ` ${args.selector}`);
5971
- text += theme.fg("dim", ` → ${output}`);
5972
- return new Text8(clampLineToWidth(text, context.width), 0, 0);
5973
- },
5974
- renderResult(result, options, theme, ...rest) {
5975
- const { isPartial, isError, width } = resolveRenderResultContext(options, rest);
5976
- if (isPartial)
5977
- return renderPendingResult("pending extract", width, theme);
5978
- const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
5979
- if (isError || result.isError) {
5980
- return renderErrorResult(textContent, { expanded: false, width, fallback: "extract failed", theme });
5981
- }
5982
- return new Text8(clampLineToWidth(summaryLine(textContent || "asset extracted", { theme, style: "success" }), width), 0, 0);
5983
- }
5984
- });
5985
- }
5986
5577
 
5987
5578
  // src/session-anchors.ts
5988
5579
  class SessionAnchors {
@@ -6003,30 +5594,37 @@ class SessionAnchors {
6003
5594
 
6004
5595
  // index.ts
6005
5596
  var READSEEK_TOOL_ENTRIES = [
6006
- { builtIn: "edit", readSeekName: "readSeek_edit" },
6007
- { builtIn: "grep", readSeekName: "readSeek_grep" },
6008
- { builtIn: null, readSeekName: "readSeek_search" },
6009
- { builtIn: null, readSeekName: "readSeek_refs" },
6010
- { builtIn: null, readSeekName: "readSeek_rename" },
6011
- { builtIn: "write", readSeekName: "readSeek_write" },
6012
- { builtIn: null, readSeekName: "readSeek_def" },
6013
- { builtIn: "read", readSeekName: "readSeek_inspect" },
6014
- { builtIn: null, readSeekName: "readSeek_extract" }
5597
+ { builtIn: "edit", readSeekName: "readSeek_edit_text" },
5598
+ { builtIn: "grep", readSeekName: "readSeek_find_text" },
5599
+ { builtIn: null, readSeekName: "readSeek_find_nodes" },
5600
+ { builtIn: null, readSeekName: "readSeek_find_references" },
5601
+ { builtIn: null, readSeekName: "readSeek_rename_symbol" },
5602
+ { builtIn: "write", readSeekName: "readSeek_write_file" },
5603
+ { builtIn: null, readSeekName: "readSeek_find_definitions" },
5604
+ { builtIn: "read", readSeekName: "readSeek_show_content" },
5605
+ { builtIn: null, readSeekName: "readSeek_show_structure" },
5606
+ { builtIn: null, readSeekName: "readSeek_show_metadata" },
5607
+ { builtIn: null, readSeekName: "readSeek_show_assets" },
5608
+ { builtIn: null, readSeekName: "readSeek_check_syntax" },
5609
+ { builtIn: null, readSeekName: "readSeek_check_rules" },
5610
+ { builtIn: null, readSeekName: "readSeek_generate_caption" },
5611
+ { builtIn: null, readSeekName: "readSeek_detect_objects" },
5612
+ { builtIn: null, readSeekName: "readSeek_recognize_text" }
6015
5613
  ];
6016
5614
  function piReadSeekExtension(pi) {
6017
5615
  const sessionAnchors = new SessionAnchors;
6018
5616
  const { settings } = resolveReadSeekJsonSettings();
6019
5617
  const replacedBuiltIns = new Set(settings.overrideTools ?? []);
6020
5618
  const swap = (builtIn, readSeekName) => replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
6021
- const readName = swap("read", "readSeek_inspect");
6022
- const editName = swap("edit", "readSeek_edit");
6023
- const writeName = swap("write", "readSeek_write");
6024
- const grepName = swap("grep", "readSeek_grep");
5619
+ const readName = swap("read", "readSeek_show_content");
5620
+ const editName = swap("edit", "readSeek_edit_text");
5621
+ const writeName = swap("write", "readSeek_write_file");
5622
+ const grepName = swap("grep", "readSeek_find_text");
6025
5623
  const toolAliases = {
6026
- readSeek_inspect: readName,
6027
- readSeek_edit: editName,
6028
- readSeek_grep: grepName,
6029
- readSeek_write: writeName
5624
+ readSeek_show_content: readName,
5625
+ readSeek_edit_text: editName,
5626
+ readSeek_find_text: grepName,
5627
+ readSeek_write_file: writeName
6030
5628
  };
6031
5629
  const policyAliases = Object.fromEntries(TOOL_NAMES.map((name) => {
6032
5630
  const readSeekName = `readSeek_${name}`;
@@ -6046,15 +5644,12 @@ function piReadSeekExtension(pi) {
6046
5644
  registerRefsTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6047
5645
  registerRenameTool(pi, { onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath) });
6048
5646
  registerDefTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6049
- registerInspectTool(pi, {
5647
+ registerAccessTools(pi, {
6050
5648
  onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
6051
5649
  name: readName,
6052
5650
  toolAliases,
6053
5651
  imageMode: replacedBuiltIns.has("read") ? resolveReadSeekImageMode() : undefined
6054
5652
  });
6055
- registerExtractTool(pi, {
6056
- onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath)
6057
- });
6058
5653
  registerWriteTool(pi, {
6059
5654
  onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
6060
5655
  name: writeName