opencode-readseek 0.11.4 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +22 -17
  2. package/dist/index.js +733 -360
  3. package/dist/v2.js +15 -15
  4. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -16,22 +16,22 @@ var EDIT_VARIANTS = [
16
16
  ];
17
17
  var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
18
18
  var TOOL_NAMES = [
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"
19
+ "show",
20
+ "tree",
21
+ "meta",
22
+ "assets",
23
+ "syntax",
24
+ "check",
25
+ "caption",
26
+ "objects",
27
+ "ocr",
28
+ "search",
29
+ "grep",
30
+ "def",
31
+ "refs",
32
+ "edit",
33
+ "rename",
34
+ "write"
35
35
  ];
36
36
  var PARAM_DESCRIPTIONS = {
37
37
  path: "Absolute or relative file path",
@@ -43,12 +43,12 @@ var PARAM_DESCRIPTIONS = {
43
43
  pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
44
44
  patternContext: "Surrounding code context for sub-statement patterns",
45
45
  selector: "Target AST node kind to extract from pattern context",
46
- grepPattern: "Regex pattern; set literal for exact text",
46
+ grepPattern: "Native regex pattern; set literal for exact text. Empty regex matches every line; empty literal is invalid.",
47
47
  name: "Qualified or unqualified symbol name",
48
48
  refsName: "Identifier to find references for",
49
49
  gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
50
- at: "Location selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
51
- assetAt: "Document selector: page:N, node:ID, or asset:ID",
50
+ at: "Location selector: N[:COLUMN], START-END, H[:COLUMN], NAME, line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
51
+ assetAt: "Document selector: N, page:N, node:ID, or asset:ID",
52
52
  rule: "KDL node selection rule, @path, or '-' for standard input",
53
53
  rules: "KDL structural checks, @path, or '-' for standard input",
54
54
  stdinName: "File name hint when the target is '-'",
@@ -60,7 +60,6 @@ var PARAM_DESCRIPTIONS = {
60
60
  kind: "Restrict structure to this node kind",
61
61
  outline: "Show outline of document hierarchy or source symbols",
62
62
  noAnchors: "Omit source line anchors",
63
- text: "Literal native text query; adapter find_text also supports regex via pattern",
64
63
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
65
64
  editVariant: "Exactly one nested edit variant",
66
65
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -92,27 +91,28 @@ var PARAM_DESCRIPTIONS = {
92
91
  literal: "Treat pattern literally",
93
92
  context: "Surrounding lines for each match",
94
93
  matchLimit: "Maximum matches to return",
94
+ grepLimit: "Maximum matching lines to return; positive integer, defaults to 100",
95
95
  summary: "Return per-file counts",
96
96
  grepScope: "Group matches by enclosing symbol",
97
- scopeContext: "Context lines within each symbol"
97
+ scopeContext: "Non-negative context lines within each symbol; requires scope: symbol"
98
98
  };
99
99
  var TOOL_DESCRIPTIONS = {
100
- 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.",
101
- show_structure: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
102
- show_metadata: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
103
- show_assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
104
- check_syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
105
- check_rules: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
106
- generate_caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
107
- detect_objects: "Explicitly infer object labels and bounding boxes using an installed model.",
108
- recognize_text: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
109
- find_nodes: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `find_text` for text and regex.",
110
- 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.",
111
- find_definitions: "Find a symbol definition or declaration by qualified or unqualified name.",
112
- find_references: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
113
- 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.",
114
- rename_symbol: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
115
- write_file: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit_text` for small changes."
100
+ show: "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.",
101
+ tree: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
102
+ meta: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
103
+ assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
104
+ syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
105
+ check: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
106
+ caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
107
+ objects: "Explicitly infer object labels and bounding boxes using an installed model.",
108
+ ocr: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
109
+ search: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `grep` for text and regex.",
110
+ grep: "Search with native regex or literal matching. Returns matching-line counts, source LINE:HASH anchors, and document node-local locations. Native glob, context, summary, limits, Git scope, and symbol grouping apply; default limit is 100 matching lines.",
111
+ def: "Find a symbol definition or declaration by qualified or unqualified name.",
112
+ refs: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
113
+ edit: "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.",
114
+ rename: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
115
+ write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes."
116
116
  };
117
117
  function rewriteBacktickedToolNames(text, aliases = {}) {
118
118
  return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), text);
@@ -205,48 +205,48 @@ function tool(name, title, properties, required) {
205
205
  return { name, title, description: TOOL_DESCRIPTIONS[name], inputSchema: { type: "object", properties, required, additionalProperties: false } };
206
206
  }
207
207
  var TOOL_CONTRACTS = [
208
- tool("show_content", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
209
- 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"]),
210
- tool("show_metadata", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
211
- tool("show_assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
212
- tool("check_syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
213
- tool("check_rules", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
214
- 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"]),
215
- tool("detect_objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
216
- tool("recognize_text", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
217
- 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) }, []),
218
- 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) }, []),
219
- tool("find_definitions", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
220
- tool("find_references", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
221
- 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"]),
222
- 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"]),
223
- tool("write_file", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
208
+ tool("show", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
209
+ tool("tree", "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"]),
210
+ tool("meta", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
211
+ tool("assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
212
+ tool("syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
213
+ tool("check", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
214
+ tool("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"]),
215
+ tool("objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
216
+ tool("ocr", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
217
+ tool("search", "Search AST 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) }, []),
218
+ tool("grep", "Search text", { path: string(PARAM_DESCRIPTIONS.searchPath), limit: { ...integer(PARAM_DESCRIPTIONS.grepLimit), default: 100 }, ...gitScopeSchema, 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) }, ["pattern"]),
219
+ tool("def", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
220
+ tool("refs", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
221
+ tool("edit", "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"]),
222
+ tool("rename", "Rename symbol", { ...path, ...at, ...lang, ...gitScopeSchema, ...apply, to: string(PARAM_DESCRIPTIONS.to), workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace } }, ["path", "at", "to"]),
223
+ tool("write", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
224
224
  ];
225
225
  // ../readseek-api/src/policy.ts
226
226
  var BUILTIN_FALLBACKS = {
227
- read: "show_content",
228
- edit: "edit_text",
229
- write: "write_file",
230
- grep: "find_text",
231
- apply_patch: "edit_text"
227
+ read: "show",
228
+ edit: "edit",
229
+ write: "write",
230
+ grep: "grep",
231
+ apply_patch: "edit"
232
232
  };
233
233
  var PREFERENCE_LEADS = {
234
- show_content: "Preferred for reading source with anchors.",
235
- edit_text: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
236
- write_file: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
237
- find_text: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
238
- find_nodes: "Preferred for AST or structural code search with ast-grep-style patterns.",
239
- find_definitions: "Preferred for go-to-definition or declaration lookup by name.",
240
- find_references: "Preferred for finding identifier-name matches or binding-specific references before rename_symbol or delete.",
241
- rename_symbol: "Preferred for renaming a symbol with binding-aware, verified edits.",
242
- show_assets: "Preferred for materializing selected document assets with usable cached paths."
234
+ show: "Preferred for reading source with anchors.",
235
+ edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
236
+ write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
237
+ grep: "Preferred native text/regex search; returns edit-ready source LINE:HASH anchors and document node-local locations.",
238
+ search: "Preferred for AST or structural code search with ast-grep-style patterns.",
239
+ def: "Preferred for go-to-definition or declaration lookup by name.",
240
+ refs: "Preferred for finding identifier-name matches or binding-specific references before `rename` or delete.",
241
+ rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
242
+ assets: "Preferred for materializing selected document assets with usable cached paths."
243
243
  };
244
244
  var FALLBACK_TEMPLATES = {
245
- read: "Prefer `show_content` with fresh LINE:HASH anchors.",
246
- edit: "Prefer `edit_text` with fresh LINE:HASH anchors.",
247
- write: "Prefer `write_file` so the result returns LINE:HASH anchors.",
248
- grep: "Prefer `find_text` for text/regex search with edit-ready anchors.",
249
- apply_patch: "Prefer `edit_text` for existing text files."
245
+ read: "Prefer `show` with fresh LINE:HASH anchors.",
246
+ edit: "Prefer `edit` with fresh LINE:HASH anchors.",
247
+ write: "Prefer `write` so the result returns LINE:HASH anchors.",
248
+ grep: "Prefer `grep` for native text/regex search with source anchors and document locations.",
249
+ apply_patch: "Prefer `edit` for existing text files."
250
250
  };
251
251
  function resolveToolName(name, aliases = {}) {
252
252
  return aliases[name] ?? name;
@@ -262,21 +262,21 @@ function renderFallbackToolDescription(builtIn, aliases = {}) {
262
262
  }
263
263
  function renderToolRoutingPolicy(options = {}) {
264
264
  const aliases = options.aliases ?? {};
265
- const show_content = resolveToolName("show_content", aliases);
266
- const edit_text = resolveToolName("edit_text", aliases);
267
- const write_file = resolveToolName("write_file", aliases);
268
- const find_text = resolveToolName("find_text", aliases);
269
- const find_nodes = resolveToolName("find_nodes", aliases);
270
- const find_definitions = resolveToolName("find_definitions", aliases);
271
- const find_references = resolveToolName("find_references", aliases);
272
- const rename_symbol = resolveToolName("rename_symbol", aliases);
273
- const show_assets = resolveToolName("show_assets", aliases);
265
+ const show = resolveToolName("show", aliases);
266
+ const edit = resolveToolName("edit", aliases);
267
+ const write = resolveToolName("write", aliases);
268
+ const grep = resolveToolName("grep", aliases);
269
+ const search = resolveToolName("search", aliases);
270
+ const def = resolveToolName("def", aliases);
271
+ const refs = resolveToolName("refs", aliases);
272
+ const rename = resolveToolName("rename", aliases);
273
+ const assets = resolveToolName("assets", aliases);
274
274
  const title = options.title ?? "ReadSeek tool policy:";
275
275
  const bullets = [];
276
276
  if (options.preferOverBuiltIns) {
277
277
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
278
278
  }
279
- 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.`);
279
+ bullets.push(`- Read with ${show} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${resolveToolName("tree", aliases)} structures only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${resolveToolName("tree", aliases)} for document structure; run ${resolveToolName("syntax", aliases)} after source edits for a quick syntax check.`, `- Use ${assets} to materialize selected document assets and read their returned paths.`);
280
280
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
281
281
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
282
282
  }
@@ -316,6 +316,59 @@ function validateReferencesSelection(input) {
316
316
  return { ok: false, error: "exactly one of name or at is required" };
317
317
  return { ok: true };
318
318
  }
319
+ function validateGrepParams(input) {
320
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
321
+ return { ok: false, error: "grep params must be an object" };
322
+ }
323
+ const params = input;
324
+ const allowed = [
325
+ "pattern",
326
+ "path",
327
+ "glob",
328
+ "ignoreCase",
329
+ "literal",
330
+ "context",
331
+ "limit",
332
+ "summary",
333
+ "scope",
334
+ "scopeContext",
335
+ "gitScope",
336
+ "signal",
337
+ "stdin",
338
+ "timeoutMs",
339
+ "cancelable"
340
+ ];
341
+ if (Object.keys(params).some((key) => !allowed.includes(key)))
342
+ return { ok: false, error: "unknown grep parameter" };
343
+ if (typeof params.pattern !== "string")
344
+ return { ok: false, error: "grep requires a pattern string" };
345
+ if (params.literal === true && params.pattern.length === 0)
346
+ return { ok: false, error: "literal pattern must not be empty" };
347
+ for (const key of ["path", "glob"]) {
348
+ if (params[key] !== undefined && (typeof params[key] !== "string" || params[key].length === 0)) {
349
+ return { ok: false, error: `${key} must be a non-empty string` };
350
+ }
351
+ }
352
+ for (const key of ["ignoreCase", "literal", "summary"]) {
353
+ if (params[key] !== undefined && typeof params[key] !== "boolean")
354
+ return { ok: false, error: `${key} must be a boolean` };
355
+ }
356
+ for (const key of ["context", "limit", "scopeContext"]) {
357
+ const value = params[key];
358
+ const minimum = key === "limit" ? 1 : 0;
359
+ if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum)) {
360
+ return { ok: false, error: `${key} must be a ${minimum === 1 ? "positive" : "non-negative"} integer` };
361
+ }
362
+ }
363
+ if (params.scope !== undefined && params.scope !== "symbol")
364
+ return { ok: false, error: "scope must be symbol" };
365
+ if (params.scopeContext !== undefined && params.scope !== "symbol")
366
+ return { ok: false, error: "scopeContext requires scope" };
367
+ if (params.gitScope !== undefined && !GIT_SCOPES.includes(params.gitScope)) {
368
+ return { ok: false, error: "invalid gitScope" };
369
+ }
370
+ return { ok: true };
371
+ }
319
372
  var VARIANT_FIELDS = {
320
373
  set_line: ["anchor", "new_text"],
321
374
  replace_lines: ["start_anchor", "end_anchor", "new_text"],
@@ -420,50 +473,111 @@ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
420
473
  function isSelectorKind(value) {
421
474
  return SELECTOR_KINDS.includes(value);
422
475
  }
476
+ function parsePositiveSelectorInt(raw, name) {
477
+ if (!/^-?\d+$/.test(raw))
478
+ throw new Error(`invalid ${name} \`${raw}\``);
479
+ const parsed = Number(raw);
480
+ if (!Number.isSafeInteger(parsed))
481
+ throw new Error(`invalid ${name} \`${raw}\``);
482
+ if (parsed < 1)
483
+ throw new Error(`${name} must be greater than zero`);
484
+ return parsed;
485
+ }
486
+ function parseLineSelector(value, explicitKind) {
487
+ const parts = value.split(":");
488
+ if (parts.length > 2)
489
+ throw new Error("selector has too many components");
490
+ const head = parts[0];
491
+ if (parts.length === 2) {
492
+ if (head.slice(1).includes("-"))
493
+ throw new Error("line range cannot include a column");
494
+ const line = parsePositiveSelectorInt(head, "line");
495
+ const column = parsePositiveSelectorInt(parts[1], "column");
496
+ return { kind: "line", value: String(line), column, explicitKind };
497
+ }
498
+ const dash = head.indexOf("-", 1);
499
+ if (dash > 0) {
500
+ const start = parsePositiveSelectorInt(head.slice(0, dash), "line");
501
+ const endLine = parsePositiveSelectorInt(head.slice(dash + 1), "end line");
502
+ if (endLine < start)
503
+ throw new Error(`end line ${endLine} precedes start line ${start}`);
504
+ return { kind: "line", value: String(start), endLine, explicitKind };
505
+ }
506
+ const line = parsePositiveSelectorInt(head, "line");
507
+ return { kind: "line", value: String(line), explicitKind };
508
+ }
509
+ function parseHashSelector(value, explicitKind) {
510
+ const parts = value.split(":");
511
+ if (parts.length > 2)
512
+ throw new Error("selector has too many components");
513
+ const head = parts[0];
514
+ if (!/^[0-9a-fA-F]{6}$/.test(head))
515
+ throw new Error(`invalid line hash \`${head}\``);
516
+ if (parts.length === 2) {
517
+ const column = parsePositiveSelectorInt(parts[1], "column");
518
+ return { kind: "hash", value: head, column, explicitKind };
519
+ }
520
+ return { kind: "hash", value: head, explicitKind };
521
+ }
522
+ function isDocumentSelector(parsed) {
523
+ return ["page", "node", "asset"].includes(parsed.kind) || parsed.kind === "line" && !parsed.explicitKind && parsed.column === undefined && parsed.endLine === undefined;
524
+ }
525
+ function isBindingSelector(parsed) {
526
+ if (parsed.endLine !== undefined)
527
+ return false;
528
+ if (parsed.kind === "line" || parsed.kind === "hash")
529
+ return true;
530
+ return parsed.kind === "symbol" && !parsed.explicitKind && /^[0-9a-fA-F]{6}$/.test(parsed.value);
531
+ }
423
532
  function parseSelector(text) {
533
+ if (text.length === 0)
534
+ throw new Error("selector must not be empty");
424
535
  const separator = text.indexOf(":");
425
- if (separator < 0) {
426
- throw new Error("selector must start with line:, hash:, symbol:, page:, node:, or asset:");
427
- }
428
- const kind = text.slice(0, separator);
429
- const value = text.slice(separator + 1);
430
- if (value.length === 0)
431
- throw new Error(`${kind} selector must not be empty`);
432
- if (!isSelectorKind(kind)) {
536
+ const hasSingleColon = separator >= 0 && text[separator + 1] !== ":";
537
+ if (hasSingleColon) {
538
+ const kind = text.slice(0, separator);
539
+ const value = text.slice(separator + 1);
540
+ if (isSelectorKind(kind)) {
541
+ if (value.length === 0)
542
+ throw new Error(`${kind} selector must not be empty`);
543
+ if (kind === "line")
544
+ return parseLineSelector(value, true);
545
+ if (kind === "hash")
546
+ return parseHashSelector(value, true);
547
+ if (kind === "page") {
548
+ if (value.includes(":"))
549
+ throw new Error("page selector has too many components");
550
+ parsePositiveSelectorInt(value, "page");
551
+ }
552
+ return { kind, value, explicitKind: true };
553
+ }
554
+ if (/^\d+$/.test(kind))
555
+ return parseLineSelector(text, false);
556
+ if (/^[0-9a-fA-F]{6}$/.test(kind) && /^\d+$/.test(value))
557
+ return parseHashSelector(text, false);
433
558
  throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
434
559
  }
435
- if (kind === "line" || kind === "hash") {
436
- const parts = value.split(":");
437
- if (parts.length > 2)
438
- throw new Error("selector has too many components");
439
- if (parts.length === 2) {
440
- const column = Number(parts[1]);
441
- if (!Number.isSafeInteger(column))
442
- throw new Error(`invalid column \`${parts[1]}\``);
443
- if (column < 1)
444
- throw new Error("column must be greater than zero");
445
- return { kind, value: parts[0], column };
560
+ if (/^[0-9-]/.test(text)) {
561
+ if (/^[0-9a-fA-F]{6}$/.test(text) && /[a-fA-F]/.test(text)) {
562
+ return parseHashSelector(text, false);
446
563
  }
564
+ return parseLineSelector(text, false);
447
565
  }
448
- if (kind === "page") {
449
- if (value.includes(":"))
450
- throw new Error("page selector has too many components");
451
- const page = Number(value);
452
- if (!Number.isSafeInteger(page))
453
- throw new Error(`invalid page \`${value}\``);
454
- if (page < 1)
455
- throw new Error("page must be greater than zero");
566
+ if (/^[0-9a-fA-F]{6}$/.test(text) && /\d/.test(text)) {
567
+ return parseHashSelector(text, false);
456
568
  }
457
- return { kind, value };
569
+ if (/\s/.test(text))
570
+ throw new Error(`invalid selector \`${text}\``);
571
+ return { kind: "symbol", value: text, explicitKind: false };
458
572
  }
459
- function start(verb, object, input) {
573
+ function start(command, input) {
460
574
  if (!input.path)
461
- throw new Error(`${verb} ${object} requires a path`);
575
+ throw new Error(`${command} requires a path`);
462
576
  if (input.stdinName !== undefined && input.path !== "-")
463
577
  throw new Error("stdinName requires '-' as the target");
464
578
  const args = [];
465
579
  pushOptionalString(args, "--readseek-dir", input.readseekDir);
466
- args.push(verb, object, input.path);
580
+ args.push(command, input.path);
467
581
  pushOptionalString(args, "--stdin-name", input.stdinName);
468
582
  return args;
469
583
  }
@@ -489,12 +603,12 @@ function anchors(args, input) {
489
603
  if (input.noAnchors)
490
604
  args.push("--no-anchors");
491
605
  }
492
- function buildShowContentArgs(input) {
606
+ function buildShowArgs(input) {
493
607
  if (input.end !== undefined)
494
608
  validatePositiveInteger(input.end, "end");
495
609
  if (input.end !== undefined && input.limit !== undefined)
496
610
  throw new Error("cannot combine end with limit");
497
- const args = start("show", "content", input);
611
+ const args = start("show", input);
498
612
  at2(args, input.at);
499
613
  pushOptionalNumber(args, "--end", input.end);
500
614
  limit2(args, input.limit);
@@ -502,10 +616,10 @@ function buildShowContentArgs(input) {
502
616
  anchors(args, input);
503
617
  return finish(args);
504
618
  }
505
- function buildShowStructureArgs(input) {
619
+ function buildTreeArgs(input) {
506
620
  if (input.depth !== undefined && (!Number.isSafeInteger(input.depth) || input.depth < 0))
507
621
  throw new Error("depth must be a non-negative integer");
508
- const args = start("show", "structure", input);
622
+ const args = start("tree", input);
509
623
  at2(args, input.at);
510
624
  pushOptionalNumber(args, "--depth", input.depth);
511
625
  pushOptionalString(args, "--kind", input.kind);
@@ -514,35 +628,35 @@ function buildShowStructureArgs(input) {
514
628
  args.push("--outline");
515
629
  return finish(args);
516
630
  }
517
- function buildShowMetadataArgs(input) {
518
- const args = start("show", "metadata", input);
631
+ function buildMetaArgs(input) {
632
+ const args = start("meta", input);
519
633
  language(args, input);
520
634
  return finish(args);
521
635
  }
522
- function buildShowAssetsArgs(input) {
523
- if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
636
+ function buildAssetsArgs(input) {
637
+ if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
524
638
  throw new Error("assets requires a page, node, or asset selector");
525
- const args = start("show", "assets", input);
639
+ const args = start("assets", input);
526
640
  at2(args, input.at);
527
641
  return finish(args);
528
642
  }
529
- function buildCheckSyntaxArgs(input) {
530
- const args = start("check", "syntax", input);
643
+ function buildSyntaxArgs(input) {
644
+ const args = start("syntax", input);
531
645
  language(args, input);
532
646
  return finish(args);
533
647
  }
534
- function visionArgs(verb, object, input) {
648
+ function visionArgs(command, input) {
535
649
  if (input.level !== undefined && !VISION_LEVELS.includes(input.level))
536
650
  throw new Error("invalid vision level");
537
- if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
651
+ if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
538
652
  throw new Error("vision requires a document selector");
539
- const args = start(verb, object, input);
653
+ const args = start(command, input);
540
654
  at2(args, input.at);
541
655
  pushOptionalString(args, "--level", input.level);
542
656
  return args;
543
657
  }
544
- function buildGenerateCaptionArgs(input) {
545
- const args = visionArgs("generate", "caption", input);
658
+ function buildCaptionArgs(input) {
659
+ const args = visionArgs("caption", input);
546
660
  if (input.include !== undefined) {
547
661
  if (input.include.length === 0 || input.include.some((task) => task !== "text" && task !== "objects"))
548
662
  throw new Error("include requires text or objects");
@@ -550,13 +664,13 @@ function buildGenerateCaptionArgs(input) {
550
664
  }
551
665
  return finish(args);
552
666
  }
553
- function buildDetectObjectsArgs(input) {
554
- return finish(visionArgs("detect", "objects", input));
667
+ function buildObjectsArgs(input) {
668
+ return finish(visionArgs("objects", input));
555
669
  }
556
- function buildRecognizeTextArgs(input) {
557
- return finish(visionArgs("recognize", "text", input));
670
+ function buildOcrArgs(input) {
671
+ return finish(visionArgs("ocr", input));
558
672
  }
559
- function buildFindNodesArgs(input) {
673
+ function buildSearchArgs(input) {
560
674
  const hasPattern = Boolean(input.pattern);
561
675
  const hasRule = Boolean(input.rule);
562
676
  const hasContext = Boolean(input.context);
@@ -564,7 +678,7 @@ function buildFindNodesArgs(input) {
564
678
  if (hasPattern || hasContext || input.selector)
565
679
  throw new Error("rule cannot be combined with pattern, context, or selector");
566
680
  } else if (!hasPattern && !hasContext) {
567
- throw new Error("find nodes requires pattern, context, or rule");
681
+ throw new Error("search requires pattern, context, or rule");
568
682
  }
569
683
  if (hasContext && !input.selector) {
570
684
  throw new Error("context requires selector");
@@ -572,7 +686,7 @@ function buildFindNodesArgs(input) {
572
686
  if (hasPattern && hasContext) {
573
687
  throw new Error("pattern and context cannot be combined");
574
688
  }
575
- const args = start("find", "nodes", input);
689
+ const args = start("search", input);
576
690
  pushOptionalString(args, "--pattern", input.pattern);
577
691
  pushOptionalString(args, "--context", input.context);
578
692
  pushOptionalString(args, "--selector", input.selector);
@@ -583,28 +697,46 @@ function buildFindNodesArgs(input) {
583
697
  anchors(args, input);
584
698
  return finish(args);
585
699
  }
586
- function buildFindTextArgs(input) {
587
- const args = start("find", "text", input);
588
- args.push("--text", input.text);
589
- limit2(args, input.limit);
700
+ function buildGrepArgs(input) {
701
+ const validated = validateGrepParams(input);
702
+ if (!validated.ok)
703
+ throw new Error(validated.error);
704
+ const args = ["grep", "--kdl", `--pattern=${input.pattern}`];
705
+ const needsPathTerminator = input.path !== undefined && input.path.startsWith("-") && input.path !== "-";
706
+ if (input.path !== undefined && !needsPathTerminator)
707
+ args.push(input.path);
708
+ if (input.literal)
709
+ args.push("--literal");
710
+ if (input.ignoreCase)
711
+ args.push("--ignore-case");
712
+ if (input.glob !== undefined)
713
+ args.push(`--glob=${input.glob}`);
714
+ pushOptionalNumber(args, "--context", input.context);
715
+ limit2(args, input.limit ?? 100);
716
+ if (input.summary)
717
+ args.push("--summary");
718
+ pushOptionalString(args, "--scope", input.scope);
719
+ pushOptionalNumber(args, "--scope-context", input.scopeContext);
590
720
  appendGitScopeArgs(args, input);
591
- return finish(args);
721
+ if (input.path !== undefined && needsPathTerminator)
722
+ args.push("--", input.path);
723
+ return args;
592
724
  }
593
- function buildFindDefinitionsArgs(input) {
594
- const args = start("find", "definitions", input);
725
+ function buildDefArgs(input) {
726
+ const args = start("def", input);
595
727
  args.push("--name", input.name);
596
728
  language(args, input);
597
729
  appendGitScopeArgs(args, input);
598
730
  anchors(args, input);
599
731
  return finish(args);
600
732
  }
601
- function buildFindReferencesArgs(input) {
733
+ function buildRefsArgs(input) {
602
734
  const validated = validateReferencesSelection(input);
603
735
  if (!validated.ok)
604
736
  throw new Error(validated.error);
605
- if (input.at !== undefined && !["line", "hash"].includes(parseSelector(input.at).kind))
737
+ if (input.at !== undefined && !isBindingSelector(parseSelector(input.at)))
606
738
  throw new Error("references requires a line or hash selector");
607
- const args = start("find", "references", input);
739
+ const args = start("refs", input);
608
740
  pushOptionalString(args, "--name", input.name);
609
741
  at2(args, input.at);
610
742
  language(args, input);
@@ -612,14 +744,14 @@ function buildFindReferencesArgs(input) {
612
744
  anchors(args, input);
613
745
  return finish(args);
614
746
  }
615
- function buildCheckRulesArgs(input) {
616
- const args = start("check", "rules", input);
747
+ function buildCheckArgs(input) {
748
+ const args = start("check", input);
617
749
  args.push("--rules", input.rules);
618
750
  appendGitScopeArgs(args, input);
619
751
  return finish(args);
620
752
  }
621
- function buildEditTextArgs(input) {
622
- const args = start("edit", "text", input);
753
+ function buildEditArgs(input) {
754
+ const args = start("edit", input);
623
755
  appendApplyAndPlanHash(args, input);
624
756
  language(args, input);
625
757
  appendGitScopeArgs(args, input);
@@ -627,10 +759,10 @@ function buildEditTextArgs(input) {
627
759
  args.push("--edits", "-");
628
760
  return finish(args);
629
761
  }
630
- function buildRenameSymbolArgs(input) {
631
- if (!["line", "hash"].includes(parseSelector(input.at).kind))
762
+ function buildRenameArgs(input) {
763
+ if (!isBindingSelector(parseSelector(input.at)))
632
764
  throw new Error("rename requires a line or hash selector");
633
- const args = start("rename", "symbol", input);
765
+ const args = start("rename", input);
634
766
  at2(args, input.at);
635
767
  args.push("--to", input.to);
636
768
  if (input.workspace !== undefined)
@@ -643,6 +775,17 @@ function buildRenameSymbolArgs(input) {
643
775
  // ../readseek-api/src/client-output.ts
644
776
  import { Type } from "@sinclair/typebox";
645
777
  import { AssertError, Value } from "@sinclair/typebox/value";
778
+ var OPERATIONS = ["Clone", "Clean", "Default", "Assert"];
779
+ function parse(schema, value, what) {
780
+ try {
781
+ return Value.Parse(OPERATIONS, schema, value);
782
+ } catch (error) {
783
+ if (!(error instanceof AssertError))
784
+ throw error;
785
+ const detail = error.error;
786
+ throw new Error(`invalid readseek ${what}${detail?.path ?? ""}: ${detail?.message ?? "invalid value"}`);
787
+ }
788
+ }
646
789
  var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
647
790
  var Str = Type.String();
648
791
  var Bool = Type.Boolean();
@@ -877,6 +1020,82 @@ var DefEnvelope = Type.Object({
877
1020
  qualified_name: OptStr
878
1021
  }))
879
1022
  });
1023
+ function parseReadOutput(value) {
1024
+ return parse(ReadOutput, value, "read output");
1025
+ }
1026
+ function parseStructureOutput(value) {
1027
+ return parse(StructureOutput, value, "structure output");
1028
+ }
1029
+ function parseSearchOutput(value) {
1030
+ const envelope = parse(SearchEnvelope, value, "search output");
1031
+ const results = envelope.results;
1032
+ if (envelope.returned_matches !== undefined)
1033
+ results.returned_matches = envelope.returned_matches;
1034
+ if (envelope.truncated !== undefined)
1035
+ results.truncated = envelope.truncated;
1036
+ if (envelope.byte_basis !== undefined)
1037
+ results.byte_basis = envelope.byte_basis;
1038
+ return results;
1039
+ }
1040
+ function parseRefsOutput(value) {
1041
+ return parse(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
1042
+ ...reference,
1043
+ enclosingSymbol: symbol?.qualified_name
1044
+ }));
1045
+ }
1046
+ function parseCheckOutput(value) {
1047
+ const output = parse(CheckEnvelope, value, "check output");
1048
+ return {
1049
+ file_hash: output.file_hash,
1050
+ errorCount: output.error_count,
1051
+ missingCount: output.missing_count,
1052
+ diagnostics: output.diagnostics
1053
+ };
1054
+ }
1055
+ function parseDetectOutput(value) {
1056
+ const output = value ?? {};
1057
+ if (output.type === "application/pdf") {
1058
+ return { kind: "pdf", ...parse(PdfDetectionInput, value, "detect output") };
1059
+ }
1060
+ if (output.width !== undefined || output.height !== undefined) {
1061
+ return { kind: "image", ...parse(ImageDetectionInput, value, "detect output") };
1062
+ }
1063
+ if (output.language !== undefined) {
1064
+ return { kind: "source", ...parse(SourceDetectionInput, value, "detect output") };
1065
+ }
1066
+ return { kind: "text", ...parse(TextDetectionInput, value, "detect output") };
1067
+ }
1068
+ function parseTextContent(value) {
1069
+ const output = value ?? {};
1070
+ if (output.hashlines === undefined || output.start_line !== undefined)
1071
+ return parseReadOutput(value);
1072
+ const spanned = parse(UnspannedContent, value, "content");
1073
+ return {
1074
+ ...spanned,
1075
+ start_line: spanned.hashlines[0]?.line ?? 1,
1076
+ end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
1077
+ };
1078
+ }
1079
+ function parseCursorOutput(value) {
1080
+ return parse(CursorEnvelope, value, "cursor output");
1081
+ }
1082
+ function parsePdfMetadata(value) {
1083
+ if (!value || typeof value !== "object")
1084
+ return;
1085
+ if (value.format !== "pdf")
1086
+ return;
1087
+ const document = parse(PdfMetadata, value, "inspect metadata");
1088
+ return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
1089
+ }
1090
+ function parseEditOutput(value) {
1091
+ return parse(EditOutputSchema, value, "edit output");
1092
+ }
1093
+ function parseRenameOutput(value) {
1094
+ return parse(RenameOutputSchema, value, "rename output");
1095
+ }
1096
+ function parseDefOutput(value) {
1097
+ return parse(DefEnvelope, value, "def output").locations;
1098
+ }
880
1099
  var Bbox = Type.Object({ x: Type.Number(), y: Type.Number(), width: Type.Number(), height: Type.Number() });
881
1100
  var Position = { page: OptInt, destination: OptStr, bbox: Type.Optional(Bbox) };
882
1101
  var Provenance = { inferred: Type.Optional(Bool), model: OptStr, level: OptStr, task: OptStr };
@@ -933,25 +1152,41 @@ var DocumentOutput = Type.Object({
933
1152
  assets: Type.Array(DocumentAsset, { default: [] }),
934
1153
  vision: Type.Array(DocumentVision, { default: [] })
935
1154
  });
936
- var TextHit = Type.Object({
937
- line: Int,
938
- column: Int,
1155
+ var GrepCount = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
1156
+ var GrepPosition = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
1157
+ var GrepAnchor = Type.String({ pattern: "^[1-9][0-9]*:[0-9a-f]{6}$" });
1158
+ var GrepLineSchema = Type.Object({
1159
+ line: GrepPosition,
1160
+ hash: Type.String({ pattern: "^[0-9a-f]{6}$" }),
939
1161
  text: Str,
940
- start_line: Int,
941
- end_line: Int,
942
- start_byte: Int,
943
- end_byte: Int,
944
- hashlines: Hashlines
1162
+ kind: Type.Union([Type.Literal("match"), Type.Literal("context")])
945
1163
  });
946
- var DocumentHit = Type.Object({ node_id: Str, kind: Str, snippet: Str, page: OptInt });
947
- var TextSearchOutput = Type.Object({
948
- text: Str,
949
- mode: Type.Literal("literal"),
950
- total_matches: Int,
951
- returned_matches: Int,
1164
+ var GrepHitSchema = Type.Object({ line: GrepPosition, column: GrepPosition, start_byte: GrepCount, end_byte: GrepCount, text: Str, anchor: GrepAnchor });
1165
+ var GrepSymbolSchema = Type.Object({ name: Str, kind: Str, start_line: GrepPosition, end_line: GrepPosition, lines: Type.Array(GrepLineSchema) });
1166
+ var GrepSourceResultSchema = Type.Object({
1167
+ file: Str,
1168
+ file_hash: Str,
1169
+ language: Str,
1170
+ total_matches: GrepCount,
1171
+ returned_matches: GrepCount,
1172
+ hits: Type.Array(GrepHitSchema),
1173
+ lines: Type.Array(GrepLineSchema),
1174
+ symbols: Type.Array(GrepSymbolSchema)
1175
+ });
1176
+ var GrepDocumentHitSchema = Type.Object({ node_id: Str, kind: Str, snippet: Str, line: GrepPosition, page: Type.Optional(GrepPosition) });
1177
+ var GrepDocumentResultSchema = Type.Object({ file: Str, content_hash: Str, total_matches: GrepCount, returned_matches: GrepCount, hits: Type.Array(GrepDocumentHitSchema) });
1178
+ var GrepOutputSchema = Type.Object({
1179
+ pattern: Str,
1180
+ mode: Type.Union([Type.Literal("regex"), Type.Literal("literal")]),
1181
+ ignore_case: Bool,
1182
+ summary: Bool,
1183
+ total_matches: GrepCount,
1184
+ returned_matches: GrepCount,
952
1185
  truncated: Bool,
953
- results: Type.Array(Type.Object({ file: Str, file_hash: Str, matches: Type.Array(TextHit) })),
954
- documents: Type.Array(Type.Object({ file: Str, content_hash: Str, hits: Type.Array(DocumentHit) }))
1186
+ count_unit: Type.Literal("lines"),
1187
+ scope: Type.Optional(Type.Literal("symbol")),
1188
+ results: Type.Array(GrepSourceResultSchema),
1189
+ documents: Type.Array(GrepDocumentResultSchema)
955
1190
  });
956
1191
  var RuleFinding = Type.Object({
957
1192
  name: Str,
@@ -972,7 +1207,72 @@ var RulesOutput = Type.Object({
972
1207
  violation_count: Int,
973
1208
  diagnostics: Type.Array(RuleFinding)
974
1209
  });
1210
+ function parseDocumentOutput(value) {
1211
+ return parse(DocumentOutput, value, "document output");
1212
+ }
1213
+ function parseContentOutput(value) {
1214
+ const output = value;
1215
+ if (output?.format !== undefined)
1216
+ return parseDocumentOutput(value);
1217
+ if (!output || output.metadata === undefined || output.content === undefined)
1218
+ throw new Error("invalid readseek content: missing metadata or content");
1219
+ const metadata = parseDetectOutput(output.metadata);
1220
+ return {
1221
+ metadata,
1222
+ content: metadata.kind === "image" ? parse(ImageDescriptor, output.content, "image descriptor") : parseTextContent(output.content),
1223
+ ...output.cursor !== undefined ? { cursor: parseCursorOutput(output.cursor) } : {}
1224
+ };
1225
+ }
1226
+ function parseStructureEnvelope(value) {
1227
+ const output = value;
1228
+ if (output?.format !== undefined)
1229
+ return parseDocumentOutput(value);
1230
+ if (!output || output.structure === undefined)
1231
+ throw new Error("invalid readseek structure: missing structure");
1232
+ return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
1233
+ }
1234
+ function parseGrepOutput(value) {
1235
+ const output = parse(GrepOutputSchema, value, "grep output");
1236
+ const invalid = (message) => {
1237
+ throw new Error(`invalid readseek grep output: ${message}`);
1238
+ };
1239
+ const groups = [...output.results, ...output.documents];
1240
+ if (output.returned_matches > output.total_matches || groups.reduce((sum, group) => sum + group.total_matches, 0) !== output.total_matches || groups.reduce((sum, group) => sum + group.returned_matches, 0) !== output.returned_matches)
1241
+ invalid("inconsistent counts");
1242
+ for (const group of groups) {
1243
+ if (group.total_matches === 0 || group.returned_matches > group.total_matches)
1244
+ invalid("inconsistent file counts");
1245
+ if (output.summary ? group.hits.length !== 0 : group.hits.length !== group.returned_matches)
1246
+ invalid("inconsistent hit count");
1247
+ }
1248
+ for (const result of output.results) {
1249
+ if (output.summary && (result.lines.length > 0 || result.symbols.length > 0))
1250
+ invalid("summary contains display entries");
1251
+ if (output.scope !== "symbol" && result.symbols.length > 0)
1252
+ invalid("symbols require symbol scope");
1253
+ for (const hit of result.hits) {
1254
+ if (hit.end_byte < hit.start_byte || Number(hit.anchor.split(":")[0]) !== hit.line)
1255
+ invalid("invalid hit span or anchor");
1256
+ }
1257
+ for (const symbol of result.symbols) {
1258
+ if (symbol.end_line < symbol.start_line || symbol.lines.some((line) => line.line < symbol.start_line || line.line > symbol.end_line))
1259
+ invalid("invalid symbol range");
1260
+ }
1261
+ }
1262
+ return output;
1263
+ }
1264
+ function parseRulesOutput(value) {
1265
+ return parse(RulesOutput, value, "rules output");
1266
+ }
975
1267
  var SourceAssetsOutput = Type.Object({ file: OptStr, metadata: Type.Optional(Detection), assets: Type.Array(DocumentAsset, { default: [] }) });
1268
+ function parseAssetsOutput(value) {
1269
+ const raw = value;
1270
+ if (raw?.format !== undefined)
1271
+ return parseDocumentOutput(value);
1272
+ if (!raw || raw.file === undefined && raw.metadata === undefined)
1273
+ throw new Error("invalid readseek assets output: missing file or metadata");
1274
+ return parse(SourceAssetsOutput, { ...raw, ...raw.metadata !== undefined ? { metadata: parseDetectOutput(raw.metadata) } : {} }, "assets output");
1275
+ }
976
1276
 
977
1277
  // ../readseek-api/src/edit-plan.ts
978
1278
  import { Document, Node, format } from "@bgotink/kdl";
@@ -1014,9 +1314,84 @@ function serializeEditPlan(edits) {
1014
1314
  throw new Error(validation.error);
1015
1315
  return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
1016
1316
  }
1317
+
1318
+ // ../readseek-api/src/client.ts
1319
+ function runOptions(options, vision = false) {
1320
+ return {
1321
+ signal: options.signal,
1322
+ stdin: options.stdin,
1323
+ timeoutMs: options.timeoutMs,
1324
+ cancelable: options.cancelable,
1325
+ ...vision ? { vision: true } : {}
1326
+ };
1327
+ }
1328
+ function createReadSeekClient(runner) {
1329
+ return {
1330
+ async show(path, options = {}) {
1331
+ const value = await runner.kdl(buildShowArgs({ ...options, path }), runOptions(options));
1332
+ return parseContentOutput(value);
1333
+ },
1334
+ async tree(path, options = {}) {
1335
+ const value = await runner.kdl(buildTreeArgs({ ...options, path }), runOptions(options));
1336
+ return parseStructureEnvelope(value);
1337
+ },
1338
+ async meta(path, options = {}) {
1339
+ const value = await runner.kdl(buildMetaArgs({ ...options, path }), runOptions(options));
1340
+ return parsePdfMetadata(value) ?? parseDetectOutput(value?.metadata);
1341
+ },
1342
+ async assets(path, options = {}) {
1343
+ const value = await runner.kdl(buildAssetsArgs({ ...options, path }), runOptions(options));
1344
+ return parseAssetsOutput(value);
1345
+ },
1346
+ async syntax(path, options = {}) {
1347
+ const value = await runner.kdl(buildSyntaxArgs({ ...options, path }), runOptions(options));
1348
+ return parseCheckOutput(value?.diagnostics ?? value);
1349
+ },
1350
+ async check(path, options) {
1351
+ const value = await runner.kdl(buildCheckArgs({ ...options, path }), runOptions(options));
1352
+ return parseRulesOutput(value);
1353
+ },
1354
+ async caption(path, options = {}) {
1355
+ const value = await runner.kdl(buildCaptionArgs({ ...options, path }), runOptions(options, true));
1356
+ return parseContentOutput(value);
1357
+ },
1358
+ async objects(path, options = {}) {
1359
+ const value = await runner.kdl(buildObjectsArgs({ ...options, path }), runOptions(options, true));
1360
+ return parseContentOutput(value);
1361
+ },
1362
+ async ocr(path, options = {}) {
1363
+ const value = await runner.kdl(buildOcrArgs({ ...options, path }), runOptions(options, true));
1364
+ return parseContentOutput(value);
1365
+ },
1366
+ async search(path, options) {
1367
+ const value = await runner.kdl(buildSearchArgs({ ...options, path }), runOptions(options));
1368
+ return parseSearchOutput(value);
1369
+ },
1370
+ async grep(params) {
1371
+ const value = await runner.kdl(buildGrepArgs(params), runOptions(params));
1372
+ return parseGrepOutput(value);
1373
+ },
1374
+ async def(path, options) {
1375
+ const value = await runner.kdl(buildDefArgs({ ...options, path }), runOptions(options));
1376
+ return parseDefOutput(value);
1377
+ },
1378
+ async refs(path, options) {
1379
+ const value = await runner.kdl(buildRefsArgs({ ...options, path }), runOptions(options));
1380
+ return parseRefsOutput(value);
1381
+ },
1382
+ async edit(path, edits, options = {}) {
1383
+ const stdin = serializeEditPlan(edits);
1384
+ return parseEditOutput(await runner.kdl(buildEditArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
1385
+ },
1386
+ async rename(path, options) {
1387
+ const value = await runner.kdl(buildRenameArgs({ ...options, path }), runOptions(options));
1388
+ return parseRenameOutput(value);
1389
+ }
1390
+ };
1391
+ }
1017
1392
  // ../readseek-api/src/show-presentation.ts
1018
1393
  function isShowTool(name) {
1019
- return name === "show_content" || name === "show_structure" || name === "show_metadata" || name === "show_assets";
1394
+ return name === "show" || name === "tree" || name === "meta" || name === "assets";
1020
1395
  }
1021
1396
  function record(value) {
1022
1397
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
@@ -1131,7 +1506,7 @@ function presentShowResult(tool, value, options = {}) {
1131
1506
  const path = inline(options.path || output.source || output.file || content.file || structure.file || metadata.file || cursor.file) || "(unknown path)";
1132
1507
  const summary = [];
1133
1508
  const result = { summary: "", body: "", notices: [], metadata: {} };
1134
- if (tool === "show_metadata") {
1509
+ if (tool === "meta") {
1135
1510
  const data = Object.keys(metadata).length > 0 ? metadata : output;
1136
1511
  if (text(data.format || data.language || data.type))
1137
1512
  summary.push(inline(data.format || data.language || data.type));
@@ -1140,7 +1515,7 @@ function presentShowResult(tool, value, options = {}) {
1140
1515
  summary.push(plural(result.metadata.pages, "page"));
1141
1516
  }
1142
1517
  result.body = fields(data) || "No metadata available.";
1143
- } else if (tool === "show_assets") {
1518
+ } else if (tool === "assets") {
1144
1519
  const assets = records(output.assets);
1145
1520
  result.metadata.assets = assets.length;
1146
1521
  summary.push(plural(assets.length, "asset"));
@@ -1154,7 +1529,7 @@ function presentShowResult(tool, value, options = {}) {
1154
1529
  summary.push(plural(nodes.length, "node"));
1155
1530
  if (result.metadata.pages !== undefined)
1156
1531
  summary.push(plural(result.metadata.pages, "page"));
1157
- const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show_content")];
1532
+ const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show")];
1158
1533
  if (assets.length > 0) {
1159
1534
  result.metadata.assets = assets.length;
1160
1535
  sections.push(`Assets
@@ -1163,7 +1538,7 @@ ${assetList(assets)}`);
1163
1538
  result.body = sections.filter(Boolean).join(`
1164
1539
 
1165
1540
  `) || "No content in this selection.";
1166
- } else if (tool === "show_structure") {
1541
+ } else if (tool === "tree") {
1167
1542
  const symbols = records(structure.symbols);
1168
1543
  result.metadata.symbols = symbols.length;
1169
1544
  summary.push(plural(symbols.length, "symbol"));
@@ -1217,7 +1592,7 @@ ${assetList(assets)}`);
1217
1592
  const description = Object.keys(content).length > 0 ? content : output;
1218
1593
  result.body = fields(description) || "No content in this selection.";
1219
1594
  }
1220
- if (tool === "show_structure")
1595
+ if (tool === "tree")
1221
1596
  result.notices.push("Structure is limited to the requested depth, kind, and outline. Use a deeper depth or focus with at to inspect further.");
1222
1597
  if (text(output.warning))
1223
1598
  result.notices.push(safe(text(output.warning)));
@@ -1229,6 +1604,31 @@ function formatShowResult(presentation) {
1229
1604
 
1230
1605
  `);
1231
1606
  }
1607
+ // ../readseek-api/src/grep-presentation.ts
1608
+ function renderGrepOutput(output) {
1609
+ const rendered = [`${output.returned_matches} of ${output.total_matches} matching lines${output.truncated ? " (truncated)" : ""}`];
1610
+ const displayLine = (line) => `${line.line}:${line.hash}|${line.kind === "match" ? ">>" : " "}${line.text}`;
1611
+ for (const result of output.results) {
1612
+ rendered.push(`${result.file}: ${result.total_matches} matching lines (${result.returned_matches} returned)`);
1613
+ if (output.summary)
1614
+ continue;
1615
+ rendered.push(...result.lines.map(displayLine));
1616
+ for (const symbol of result.symbols) {
1617
+ rendered.push(` ${symbol.kind} ${symbol.name} (${symbol.start_line}-${symbol.end_line})`);
1618
+ rendered.push(...symbol.lines.map(displayLine));
1619
+ }
1620
+ }
1621
+ for (const document of output.documents) {
1622
+ rendered.push(`${document.file}: ${document.total_matches} matching lines (${document.returned_matches} returned)`);
1623
+ if (output.summary)
1624
+ continue;
1625
+ for (const hit of document.hits) {
1626
+ rendered.push(` node:${hit.node_id}${hit.page === undefined ? "" : ` page:${hit.page}`} line:${hit.line} (${hit.kind}): ${hit.snippet}`);
1627
+ }
1628
+ }
1629
+ return rendered.join(`
1630
+ `);
1631
+ }
1232
1632
  // ../readseek-api/src/platform.ts
1233
1633
  import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
1234
1634
  import { createRequire } from "node:module";
@@ -1330,7 +1730,7 @@ function cleanReadSeekErrorText(stderr) {
1330
1730
  return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
1331
1731
  }
1332
1732
  // ../readseek-api/src/kdl.ts
1333
- import { parse } from "@bgotink/kdl";
1733
+ import { parse as parse2 } from "@bgotink/kdl";
1334
1734
  function convertNode(node) {
1335
1735
  if (node.getTag() !== null)
1336
1736
  throw new Error("KDL node annotations are not supported");
@@ -1358,7 +1758,7 @@ function convertNode(node) {
1358
1758
  };
1359
1759
  }
1360
1760
  function parseKdlDocument(text) {
1361
- const document = parse(text);
1761
+ const document = parse2(text);
1362
1762
  if (document.nodes.length !== 1)
1363
1763
  throw new Error("KDL document must contain exactly one root node");
1364
1764
  return convertNode(document.nodes[0]);
@@ -1492,6 +1892,46 @@ function search(root) {
1492
1892
  }))
1493
1893
  };
1494
1894
  }
1895
+ function grepChildren(node, allowed) {
1896
+ if (node.children.some((child) => !allowed.includes(child.name))) {
1897
+ throw new Error(`invalid readseek KDL grep: unexpected child of ${node.name}`);
1898
+ }
1899
+ }
1900
+ function grepLine(node) {
1901
+ grepChildren(node, []);
1902
+ const parsed = line(node);
1903
+ if (requireProperty(node, "number") !== parsed.line)
1904
+ throw new Error("invalid readseek KDL grep line number");
1905
+ return { ...parsed, kind: requireProperty(node, "kind") };
1906
+ }
1907
+ function grep(root) {
1908
+ grepChildren(root, ["match", "document"]);
1909
+ return parseGrepOutput({
1910
+ ...properties(root),
1911
+ results: children(root, "match").map((match) => {
1912
+ grepChildren(match, ["hit", "line", "symbol"]);
1913
+ return {
1914
+ ...properties(match),
1915
+ hits: children(match, "hit").map((hit) => {
1916
+ grepChildren(hit, []);
1917
+ return properties(hit);
1918
+ }),
1919
+ lines: children(match, "line").map(grepLine),
1920
+ symbols: children(match, "symbol").map((symbol) => {
1921
+ grepChildren(symbol, ["line"]);
1922
+ return { ...properties(symbol), lines: children(symbol, "line").map(grepLine) };
1923
+ })
1924
+ };
1925
+ }),
1926
+ documents: children(root, "document").map((document) => {
1927
+ grepChildren(document, ["hit"]);
1928
+ return { ...properties(document), hits: children(document, "hit").map((hit) => {
1929
+ grepChildren(hit, []);
1930
+ return properties(hit);
1931
+ }) };
1932
+ })
1933
+ });
1934
+ }
1495
1935
  function references(root) {
1496
1936
  const parsed = children(root, "reference").map((reference) => {
1497
1937
  const sourceLine = child(reference, "line");
@@ -1607,7 +2047,7 @@ function parseReadSeekKdl(text) {
1607
2047
  case "document":
1608
2048
  return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1609
2049
  case "search":
1610
- return search(root);
2050
+ return ["mode", "ignore_case", "summary", "count_unit"].some((key) => root.properties.has(key)) ? grep(root) : search(root);
1611
2051
  case "check":
1612
2052
  return diagnostics(root);
1613
2053
  case "refs":
@@ -1672,7 +2112,7 @@ function acceptsReadSeekExitCode(args, exitCode) {
1672
2112
  index++;
1673
2113
  continue;
1674
2114
  }
1675
- return arg === "find" || arg === "check";
2115
+ return arg === "grep" || arg === "search" || arg === "def" || arg === "refs" || arg === "syntax" || arg === "check";
1676
2116
  }
1677
2117
  return false;
1678
2118
  }
@@ -1806,8 +2246,8 @@ function collectAnchoredFiles(value, files) {
1806
2246
  if (!value || typeof value !== "object")
1807
2247
  return;
1808
2248
  const output = value;
1809
- const hashlines = output.hashlines;
1810
- if (typeof output.file === "string" && Array.isArray(hashlines) && hashlines.some((line) => typeof record2(line).line === "number" && typeof record2(line).hash === "string"))
2249
+ const lineGroups = [output.hashlines, output.lines, ...items(output.symbols).map((symbol) => record2(symbol).lines)];
2250
+ if (typeof output.file === "string" && lineGroups.some((lines) => items(lines).some((line) => typeof record2(line).line === "number" && typeof record2(line).hash === "string")))
1811
2251
  files.add(output.file);
1812
2252
  for (const item of Object.values(output))
1813
2253
  collectAnchoredFiles(item, files);
@@ -1873,12 +2313,12 @@ function resolveImagePolicy(options) {
1873
2313
  throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
1874
2314
  }
1875
2315
  function contentArgs(filePath, options = {}) {
1876
- return buildShowContentArgs({ path: filePath, ...options, at: options.at ?? (options.end === undefined ? undefined : "line:1") });
2316
+ return buildShowArgs({ path: filePath, ...options, at: options.at ?? (options.end === undefined ? undefined : "line:1") });
1877
2317
  }
1878
2318
  function requireContent(envelope) {
1879
2319
  const value = record2(envelope).content;
1880
2320
  if (value === undefined || value === null || typeof value !== "object") {
1881
- throw new Error("show content returned no source content");
2321
+ throw new Error("show returned no source content");
1882
2322
  }
1883
2323
  return value;
1884
2324
  }
@@ -1920,6 +2360,12 @@ async function runReadSeek(context, args, options = {}) {
1920
2360
  timeoutMs: options.timeoutMs
1921
2361
  }));
1922
2362
  }
2363
+ function readSeekClient(context) {
2364
+ return createReadSeekClient({
2365
+ text: (args, options) => runReadSeekRaw(context, [...args], options),
2366
+ kdl: (args, options) => runReadSeek(context, [...args], options)
2367
+ });
2368
+ }
1923
2369
  async function runContent(context, args, options = {}) {
1924
2370
  return requireContent(await runReadSeek(context, args, options));
1925
2371
  }
@@ -1954,7 +2400,7 @@ async function ensureReadSeekCache(context) {
1954
2400
  if (error.code !== "ENOENT")
1955
2401
  throw error;
1956
2402
  }
1957
- readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "initialize", "index"], { cancelable: false }).then(() => cacheDir).finally(() => {
2403
+ readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"], { cancelable: false }).then(() => cacheDir).finally(() => {
1958
2404
  readSeekCacheInit = undefined;
1959
2405
  });
1960
2406
  return readSeekCacheInit;
@@ -2043,7 +2489,6 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
2043
2489
  }
2044
2490
 
2045
2491
  // src/tools.ts
2046
- import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
2047
2492
  import { tool as tool3 } from "@opencode-ai/plugin";
2048
2493
 
2049
2494
  // src/constants.ts
@@ -2372,31 +2817,31 @@ import { pathToFileURL } from "node:url";
2372
2817
  import { tool as tool2 } from "@opencode-ai/plugin";
2373
2818
  function initialTitle(kind, args) {
2374
2819
  switch (kind) {
2375
- case "show_structure":
2376
- case "show_metadata":
2377
- case "check_syntax":
2378
- case "check_rules":
2379
- case "generate_caption":
2380
- case "detect_objects":
2381
- case "recognize_text":
2382
- return `${kind.replaceAll("_", " ")} ${args.path}`;
2383
- case "show_content":
2820
+ case "tree":
2821
+ case "meta":
2822
+ case "syntax":
2823
+ case "check":
2824
+ case "caption":
2825
+ case "objects":
2826
+ case "ocr":
2827
+ return `${kind} ${args.path}`;
2828
+ case "show":
2384
2829
  return `Show content ${args.path}`;
2385
- case "show_assets":
2830
+ case "assets":
2386
2831
  return `Show assets ${args.path}`;
2387
- case "find_nodes":
2832
+ case "search":
2388
2833
  return `Find nodes ${args.pattern ?? args.context ?? args.rule}`;
2389
- case "find_text":
2390
- return `Find text ${args.pattern ?? args.text}`;
2391
- case "find_definitions":
2834
+ case "grep":
2835
+ return `Find text ${args.pattern}`;
2836
+ case "def":
2392
2837
  return `Find definition ${args.name}`;
2393
- case "find_references":
2838
+ case "refs":
2394
2839
  return `Find references to ${args.name ?? args.at}`;
2395
- case "rename_symbol":
2840
+ case "rename":
2396
2841
  return `Rename ${args.path}:${args.at} to ${args.to}`;
2397
- case "edit_text":
2842
+ case "edit":
2398
2843
  return `Edit ${args.path}`;
2399
- case "write_file":
2844
+ case "write":
2400
2845
  return `Write ${args.path}`;
2401
2846
  }
2402
2847
  }
@@ -2407,34 +2852,31 @@ function resultPresentation(kind, args, value) {
2407
2852
  return { title: presentation.summary, metadata: presentation.metadata };
2408
2853
  }
2409
2854
  switch (kind) {
2410
- case "check_syntax": {
2855
+ case "syntax": {
2411
2856
  const diagnostics = record2(output.diagnostics ?? output);
2412
2857
  const errors = typeof diagnostics.error_count === "number" ? diagnostics.error_count : 0;
2413
2858
  const missing = typeof diagnostics.missing_count === "number" ? diagnostics.missing_count : 0;
2414
2859
  return { title: `Syntax ${args.path} (${errors} errors, ${missing} missing)`, metadata: { error_count: errors, missing_count: missing } };
2415
2860
  }
2416
- case "find_nodes": {
2861
+ case "search": {
2417
2862
  const results = items(output.results);
2418
2863
  const matches = results.reduce((total, result) => total + items(record2(result).matches).length, 0);
2419
2864
  return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
2420
2865
  }
2421
- case "find_text": {
2422
- const results = items(output.results);
2423
- const matches = results.reduce((total, result) => {
2424
- const entry = record2(result);
2425
- return total + (typeof entry.count === "number" ? entry.count : items(entry.matches).length);
2426
- }, 0);
2427
- return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
2866
+ case "grep": {
2867
+ const results = items(output.results).length + items(output.documents).length;
2868
+ const matches = output.total_matches;
2869
+ return { title: `Found ${matches} matches`, metadata: { results, matches } };
2428
2870
  }
2429
- case "find_definitions": {
2871
+ case "def": {
2430
2872
  const locations = items(output.locations).length;
2431
2873
  return { title: `Found ${locations} definitions`, metadata: { locations } };
2432
2874
  }
2433
- case "find_references": {
2875
+ case "refs": {
2434
2876
  const references = items(output.references).length;
2435
2877
  return { title: `Found ${references} references`, metadata: { references } };
2436
2878
  }
2437
- case "rename_symbol": {
2879
+ case "rename": {
2438
2880
  const oldName = output.old_name;
2439
2881
  const newName = output.new_name;
2440
2882
  const otherOutputs = items(output.others).map(record2);
@@ -2444,10 +2886,10 @@ function resultPresentation(kind, args, value) {
2444
2886
  const title = typeof oldName === "string" && typeof newName === "string" ? `${output.applied === true ? "Renamed" : "Plan"} ${oldName} -> ${newName}` : initialTitle(kind, args);
2445
2887
  return { title, metadata: { edits, conflicts, others } };
2446
2888
  }
2447
- case "edit_text":
2448
- case "write_file": {
2889
+ case "edit":
2890
+ case "write": {
2449
2891
  const preview = typeof output.preview_error === "string" ? " (preview unavailable)" : "";
2450
- return { title: `${kind === "edit_text" ? "Edited" : "Wrote"} ${args.path}${preview}`, metadata: {} };
2892
+ return { title: `${kind === "edit" ? "Edited" : "Wrote"} ${args.path}${preview}`, metadata: {} };
2451
2893
  }
2452
2894
  }
2453
2895
  return { title: initialTitle(kind, args), metadata: {} };
@@ -2482,8 +2924,8 @@ function readseekTool(description, args, kind, execute, imagePolicy) {
2482
2924
  return {
2483
2925
  ...attachments.length > 0 ? { attachments } : {},
2484
2926
  title: presentation.title,
2485
- output: isShowTool(kind) ? formatShowResult(presentShowResult(kind, displayResult, args)) : render(displayResult),
2486
- metadata: { ...presentation.metadata, ...isShowTool(kind) ? { readseek: result } : {} }
2927
+ output: isShowTool(kind) ? formatShowResult(presentShowResult(kind, displayResult, args)) : kind === "grep" ? renderGrepOutput(result) : render(displayResult),
2928
+ metadata: { ...presentation.metadata, ...isShowTool(kind) || kind === "grep" ? { readseek: result } : {} }
2487
2929
  };
2488
2930
  }
2489
2931
  });
@@ -2493,107 +2935,38 @@ function readseekTool(description, args, kind, execute, imagePolicy) {
2493
2935
  function createReadSeekTools(imagePolicy) {
2494
2936
  return {
2495
2937
  ...createAccessTools(imagePolicy),
2496
- readseek_find_text: readseekTool(openCodeToolDescription("find_text"), {
2497
- pattern: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.grepPattern),
2498
- text: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.text),
2938
+ readseek_grep: readseekTool(openCodeToolDescription("grep"), {
2939
+ pattern: tool3.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
2499
2940
  summary: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.summary),
2500
2941
  path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2501
2942
  glob: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.glob),
2502
2943
  literal: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.literal),
2503
2944
  ignoreCase: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignoreCase),
2504
2945
  context: tool3.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.context),
2505
- limit: tool3.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit)
2506
- }, "find_text", async (input, context) => {
2946
+ limit: tool3.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit),
2947
+ scope: tool3.schema.literal("symbol").optional().describe(PARAM_DESCRIPTIONS.grepScope),
2948
+ scopeContext: tool3.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.scopeContext),
2949
+ gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2950
+ }, "grep", async (input, context) => {
2507
2951
  const target = resolvePath(context.directory, input.path ?? ".");
2508
- if (input.pattern === undefined === (input.text === undefined)) {
2509
- throw new Error("exactly one of pattern or text is required");
2510
- }
2511
- const pattern = input.pattern ?? input.text;
2952
+ const pattern = input.pattern;
2512
2953
  await authorizeSearch(context, target, pattern);
2513
- if (input.text !== undefined && !input.glob && !input.ignoreCase && !input.context && !input.summary) {
2514
- return runReadSeek(context, buildFindTextArgs({ path: target, text: pattern, limit: input.limit }));
2515
- }
2516
- let expression;
2517
- try {
2518
- const source = input.literal || input.text !== undefined ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
2519
- expression = new RegExp(source, input.ignoreCase ? "i" : "");
2520
- } catch (error) {
2521
- throw new Error(`invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
2522
- }
2523
- const info = await stat3(target);
2524
- const files = [];
2525
- if (info.isFile())
2526
- files.push(target);
2527
- else if (info.isDirectory()) {
2528
- const glob = new Bun.Glob(input.glob ?? "**/*");
2529
- for await (const filePath of glob.scan({ cwd: target, absolute: true, onlyFiles: true, followSymlinks: false })) {
2530
- files.push(filePath);
2531
- }
2532
- } else
2533
- throw new Error(`grep target is not a file or directory: ${target}`);
2534
- const maxMatches = input.limit ?? 100;
2535
- const contextLines = input.context ?? 0;
2536
- const results = [];
2537
- let totalMatches = 0;
2538
- let truncated = false;
2539
- for (const filePath of files.sort()) {
2540
- context.abort.throwIfAborted();
2541
- if (truncated)
2542
- break;
2543
- const buffer = await readFile2(filePath);
2544
- if (buffer.includes(0))
2545
- continue;
2546
- const lines = buffer.toString("utf8").replace(/\r\n/g, `
2547
- `).split(`
2548
- `);
2549
- const matchLines = new Set;
2550
- const ranges = [];
2551
- for (let index = 0;index < lines.length; index++) {
2552
- expression.lastIndex = 0;
2553
- if (!expression.test(lines[index] ?? ""))
2554
- continue;
2555
- if (totalMatches === maxMatches) {
2556
- truncated = true;
2557
- break;
2558
- }
2559
- const line = index + 1;
2560
- matchLines.add(line);
2561
- ranges.push([Math.max(1, line - contextLines), Math.min(lines.length, line + contextLines)]);
2562
- totalMatches++;
2563
- }
2564
- if (ranges.length === 0)
2565
- continue;
2566
- if (input.summary) {
2567
- results.push({ file: filePath, count: matchLines.size });
2568
- continue;
2569
- }
2570
- const mergedRanges = [];
2571
- for (const [start, end] of ranges) {
2572
- const previous = mergedRanges.at(-1);
2573
- if (previous && start <= previous[1] + 1)
2574
- previous[1] = Math.max(previous[1], end);
2575
- else
2576
- mergedRanges.push([start, end]);
2577
- }
2578
- const linesByNumber = new Map;
2579
- for (const [start, end] of mergedRanges) {
2580
- const output = record2(await runContent(context, readRangeArgs(filePath, start, end)));
2581
- for (const hashline of items(output.hashlines)) {
2582
- const line = record2(hashline).line;
2583
- if (typeof line === "number" && !linesByNumber.has(line))
2584
- linesByNumber.set(line, hashline);
2585
- }
2586
- }
2587
- const hashlines = [...linesByNumber.values()];
2588
- const matches = hashlines.filter((hashline) => {
2589
- const line = record2(hashline).line;
2590
- return typeof line === "number" && matchLines.has(line);
2591
- });
2592
- results.push({ file: filePath, matches, hashlines });
2593
- }
2594
- return { results, truncated };
2954
+ return readSeekClient(context).grep({
2955
+ pattern,
2956
+ path: target,
2957
+ glob: input.glob,
2958
+ ignoreCase: input.ignoreCase,
2959
+ literal: input.literal,
2960
+ context: input.context,
2961
+ limit: input.limit,
2962
+ summary: input.summary,
2963
+ scope: input.scope,
2964
+ scopeContext: input.scopeContext,
2965
+ gitScope: input.gitScope,
2966
+ signal: context.abort
2967
+ });
2595
2968
  }),
2596
- readseek_find_nodes: readseekTool(openCodeToolDescription("find_nodes"), {
2969
+ readseek_search: readseekTool(openCodeToolDescription("search"), {
2597
2970
  pattern: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.pattern),
2598
2971
  context: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.patternContext),
2599
2972
  selector: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.selector),
@@ -2602,9 +2975,9 @@ function createReadSeekTools(imagePolicy) {
2602
2975
  path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2603
2976
  language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2604
2977
  gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2605
- }, "find_nodes", async (input, context) => {
2978
+ }, "search", async (input, context) => {
2606
2979
  const target = resolvePath(context.directory, input.path ?? ".");
2607
- const args = buildFindNodesArgs({
2980
+ const args = buildSearchArgs({
2608
2981
  path: target,
2609
2982
  pattern: input.pattern,
2610
2983
  context: input.context,
@@ -2622,14 +2995,14 @@ function createReadSeekTools(imagePolicy) {
2622
2995
  throw new Error("rule stdin is unavailable in OpenCode tools; use inline KDL or @path");
2623
2996
  return runReadSeek(context, args);
2624
2997
  }),
2625
- readseek_find_definitions: readseekTool(openCodeToolDescription("find_definitions"), {
2998
+ readseek_def: readseekTool(openCodeToolDescription("def"), {
2626
2999
  name: tool3.schema.string().describe(PARAM_DESCRIPTIONS.name),
2627
3000
  path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2628
3001
  language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2629
3002
  gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2630
- }, "find_definitions", async (input, context) => {
3003
+ }, "def", async (input, context) => {
2631
3004
  const target = resolvePath(context.directory, input.path ?? ".");
2632
- const args = buildFindDefinitionsArgs({
3005
+ const args = buildDefArgs({
2633
3006
  path: target,
2634
3007
  name: input.name,
2635
3008
  language: input.language,
@@ -2638,16 +3011,16 @@ function createReadSeekTools(imagePolicy) {
2638
3011
  await authorizeSearch(context, target, input.name);
2639
3012
  return runReadSeek(context, args);
2640
3013
  }),
2641
- readseek_find_references: readseekTool(openCodeToolDescription("find_references"), {
3014
+ readseek_refs: readseekTool(openCodeToolDescription("refs"), {
2642
3015
  name: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
2643
3016
  path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
2644
3017
  language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2645
3018
  at: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
2646
3019
  gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2647
- }, "find_references", async (input, context) => {
3020
+ }, "refs", async (input, context) => {
2648
3021
  const target = resolvePath(context.directory, input.path ?? ".");
2649
3022
  const name = input.name;
2650
- const args = buildFindReferencesArgs({
3023
+ const args = buildRefsArgs({
2651
3024
  path: target,
2652
3025
  name,
2653
3026
  at: input.at,
@@ -2657,7 +3030,7 @@ function createReadSeekTools(imagePolicy) {
2657
3030
  await authorizeSearch(context, target, name ?? "*");
2658
3031
  return runReadSeek(context, args);
2659
3032
  }),
2660
- readseek_rename_symbol: readseekTool(openCodeToolDescription("rename_symbol"), {
3033
+ readseek_rename: readseekTool(openCodeToolDescription("rename"), {
2661
3034
  path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2662
3035
  at: tool3.schema.string().describe(PARAM_DESCRIPTIONS.at),
2663
3036
  to: tool3.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
@@ -2666,7 +3039,7 @@ function createReadSeekTools(imagePolicy) {
2666
3039
  planHash: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.planHash),
2667
3040
  language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
2668
3041
  gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2669
- }, "rename_symbol", async (input, context) => {
3042
+ }, "rename", async (input, context) => {
2670
3043
  const filePath = resolvePath(context.directory, input.path);
2671
3044
  await authorizeRead(context, filePath);
2672
3045
  if (input.workspace) {
@@ -2676,7 +3049,7 @@ function createReadSeekTools(imagePolicy) {
2676
3049
  throw new Error("readseek could not identify a binding at the rename cursor");
2677
3050
  await authorizeSearch(context, context.directory, name);
2678
3051
  }
2679
- const args = buildRenameSymbolArgs({
3052
+ const args = buildRenameArgs({
2680
3053
  path: filePath,
2681
3054
  to: input.to,
2682
3055
  at: input.at,
@@ -2696,7 +3069,7 @@ function createReadSeekTools(imagePolicy) {
2696
3069
  throw new Error("readseek rename plan did not include a plan hash");
2697
3070
  }
2698
3071
  await authorizeEdit(context, files);
2699
- return runReadSeek(context, buildRenameSymbolArgs({
3072
+ return runReadSeek(context, buildRenameArgs({
2700
3073
  path: filePath,
2701
3074
  to: input.to,
2702
3075
  at: input.at,
@@ -2710,7 +3083,7 @@ function createReadSeekTools(imagePolicy) {
2710
3083
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
2711
3084
  });
2712
3085
  }),
2713
- readseek_edit_text: readseekTool(openCodeToolDescription("edit_text"), {
3086
+ readseek_edit: readseekTool(openCodeToolDescription("edit"), {
2714
3087
  path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2715
3088
  edits: tool3.schema.array(tool3.schema.union([
2716
3089
  tool3.schema.object({
@@ -2750,7 +3123,7 @@ function createReadSeekTools(imagePolicy) {
2750
3123
  apply: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue),
2751
3124
  planHash: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.planHash),
2752
3125
  gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
2753
- }, "edit_text", async (input, context) => {
3126
+ }, "edit", async (input, context) => {
2754
3127
  const filePath = resolvePath(context.directory, input.path);
2755
3128
  validateEdits(input.edits);
2756
3129
  return withFileMutationQueue(filePath, async () => {
@@ -2759,7 +3132,7 @@ function createReadSeekTools(imagePolicy) {
2759
3132
  await rejectSymlinkMutation(filePath);
2760
3133
  const edits = input.edits;
2761
3134
  const request = serializeEditPlan(edits);
2762
- const baseArgs = buildEditTextArgs({
3135
+ const baseArgs = buildEditArgs({
2763
3136
  path: filePath,
2764
3137
  language: input.language,
2765
3138
  gitScope: input.gitScope,
@@ -2783,7 +3156,7 @@ function createReadSeekTools(imagePolicy) {
2783
3156
  }
2784
3157
  await authorizeEdit(context, [filePath], simpleDiff(filePath, before, after), false);
2785
3158
  context.abort.throwIfAborted();
2786
- const applied = record2(await runReadSeek(context, buildEditTextArgs({
3159
+ const applied = record2(await runReadSeek(context, buildEditArgs({
2787
3160
  path: filePath,
2788
3161
  language: input.language,
2789
3162
  gitScope: input.gitScope,
@@ -2801,10 +3174,10 @@ function createReadSeekTools(imagePolicy) {
2801
3174
  return postMutationPreview(context, previewArgs, filePath, true);
2802
3175
  });
2803
3176
  }),
2804
- readseek_write_file: readseekTool(openCodeToolDescription("write_file"), {
3177
+ readseek_write: readseekTool(openCodeToolDescription("write"), {
2805
3178
  path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2806
3179
  content: tool3.schema.string().describe(PARAM_DESCRIPTIONS.content)
2807
- }, "write_file", async (input, context) => {
3180
+ }, "write", async (input, context) => {
2808
3181
  const filePath = resolvePath(context.directory, input.path);
2809
3182
  await authorizeExternal(context, filePath);
2810
3183
  return withFileMutationQueue(filePath, async () => {
@@ -2828,37 +3201,37 @@ function createAccessTools(imagePolicy) {
2828
3201
  const languageSchema = { language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language) };
2829
3202
  const levelSchema = { level: tool3.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.level) };
2830
3203
  const definitions = {
2831
- show_content: { build: buildShowContentArgs, args: { ...pathSchema, ...atSchema, ...languageSchema, end: tool3.schema.number().int().positive().optional(), limit: tool3.schema.number().int().positive().optional(), noAnchors: tool3.schema.boolean().optional() } },
2832
- show_structure: { build: buildShowStructureArgs, args: { ...pathSchema, ...atSchema, ...languageSchema, depth: tool3.schema.number().int().nonnegative().optional(), kind: tool3.schema.string().optional(), outline: tool3.schema.boolean().optional() } },
2833
- show_metadata: { build: buildShowMetadataArgs, args: { ...pathSchema, ...languageSchema } },
2834
- show_assets: { build: buildShowAssetsArgs, args: { ...pathSchema, ...atSchema } },
2835
- check_syntax: { build: buildCheckSyntaxArgs, args: { ...pathSchema, ...languageSchema } },
2836
- check_rules: { build: buildCheckRulesArgs, args: { path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath), rules: tool3.schema.string().min(1), gitScope: tool3.schema.enum(GIT_SCOPES).optional() } },
2837
- generate_caption: { build: buildGenerateCaptionArgs, args: { ...pathSchema, ...atSchema, ...levelSchema, include: tool3.schema.array(tool3.schema.enum(["text", "objects"])).min(1).optional() } },
2838
- detect_objects: { build: buildDetectObjectsArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } },
2839
- recognize_text: { build: buildRecognizeTextArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } }
3204
+ show: { build: buildShowArgs, args: { ...pathSchema, ...atSchema, ...languageSchema, end: tool3.schema.number().int().positive().optional(), limit: tool3.schema.number().int().positive().optional(), noAnchors: tool3.schema.boolean().optional() } },
3205
+ tree: { build: buildTreeArgs, args: { ...pathSchema, ...atSchema, ...languageSchema, depth: tool3.schema.number().int().nonnegative().optional(), kind: tool3.schema.string().optional(), outline: tool3.schema.boolean().optional() } },
3206
+ meta: { build: buildMetaArgs, args: { ...pathSchema, ...languageSchema } },
3207
+ assets: { build: buildAssetsArgs, args: { ...pathSchema, ...atSchema } },
3208
+ syntax: { build: buildSyntaxArgs, args: { ...pathSchema, ...languageSchema } },
3209
+ check: { build: buildCheckArgs, args: { path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath), rules: tool3.schema.string().min(1), gitScope: tool3.schema.enum(GIT_SCOPES).optional() } },
3210
+ caption: { build: buildCaptionArgs, args: { ...pathSchema, ...atSchema, ...levelSchema, include: tool3.schema.array(tool3.schema.enum(["text", "objects"])).min(1).optional() } },
3211
+ objects: { build: buildObjectsArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } },
3212
+ ocr: { build: buildOcrArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } }
2840
3213
  };
2841
3214
  return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [
2842
3215
  `readseek_${name}`,
2843
3216
  readseekTool(openCodeToolDescription(name), definition.args, name, async (input, context) => {
2844
3217
  const filePath = resolvePath(context.directory, input.path ?? ".");
2845
- const buildInput = { ...input, path: filePath, ...name === "show_structure" ? { depth: input.depth ?? DEFAULT_STRUCTURE_DEPTH } : {} };
3218
+ const buildInput = { ...input, path: filePath, ...name === "tree" ? { depth: input.depth ?? DEFAULT_STRUCTURE_DEPTH } : {} };
2846
3219
  let args = definition.build(buildInput);
2847
3220
  await authorizeRead(context, filePath);
2848
- if (name === "check_rules") {
3221
+ if (name === "check") {
2849
3222
  if (input.rules === "-")
2850
3223
  throw new Error("rules stdin is unavailable in OpenCode tools; use inline KDL or @path");
2851
3224
  await authorizeSearch(context, filePath, input.rules);
2852
3225
  if (input.rules.startsWith("@"))
2853
3226
  await authorizeRead(context, resolvePath(context.directory, input.rules.slice(1)));
2854
3227
  }
2855
- const inference = ["generate_caption", "detect_objects", "recognize_text"].includes(name);
2856
- if (name === "show_content" || name === "show_assets" || inference) {
3228
+ const inference = ["caption", "objects", "ocr"].includes(name);
3229
+ if (name === "show" || name === "assets" || inference) {
2857
3230
  const readseekDir = await ensureReadSeekCache(context);
2858
3231
  args = definition.build({ ...buildInput, readseekDir });
2859
3232
  }
2860
3233
  return runReadSeek(context, args, { timeoutMs: inference ? DEFAULT_VISION_TIMEOUT_MS : DEFAULT_READSEEK_TIMEOUT_MS });
2861
- }, name === "show_content" || name === "show_assets" ? imagePolicy : undefined)
3234
+ }, name === "show" || name === "assets" ? imagePolicy : undefined)
2862
3235
  ]));
2863
3236
  }
2864
3237
 
@@ -2873,28 +3246,28 @@ var SKILL = {
2873
3246
 
2874
3247
  When ReadSeek tools are unavailable, use the readseek CLI for structural navigation and anchored edits if it is installed and available on PATH. Run \`readseek --help\` to inspect supported commands before invoking them. Get fresh anchors before editing; re-read after external file changes.
2875
3248
 
2876
- Commands use verb-object pairs, with the target path positional:
3249
+ Commands use flat single-word verbs, with target and primary query arguments positional:
2877
3250
 
2878
3251
  \`\`\`sh
2879
- readseek show content file.ts --at line:10 --limit 40 --kdl
2880
- readseek show structure file.ts --depth 1 --kdl
2881
- readseek show metadata paper.pdf --kdl
2882
- readseek show assets paper.pdf --at page:2 --kdl
2883
- readseek check syntax file.ts --kdl
2884
- readseek check rules . --rules @checks.kdl --kdl
2885
- readseek find nodes src --pattern 'console.log($$$ARGS)' --kdl
2886
- readseek find text src --text 'TODO' --kdl
2887
- readseek find definitions src --name main --kdl
2888
- readseek find references file.ts --at line:10:3 --kdl
2889
- readseek edit text file.ts --edits @edits.kdl --kdl
2890
- readseek rename symbol file.ts --at line:10:3 --to nextName --kdl
3252
+ readseek show file.ts --at line:10 --limit 40 --kdl
3253
+ readseek tree file.ts --depth 1 --kdl
3254
+ readseek meta paper.pdf --kdl
3255
+ readseek assets paper.pdf --at page:2 --kdl
3256
+ readseek syntax file.ts --kdl
3257
+ readseek check . @checks.kdl --kdl
3258
+ readseek search 'console.log($$$ARGS)' src --kdl
3259
+ readseek grep 'TODO' src --kdl
3260
+ readseek def main src --kdl
3261
+ readseek refs file.ts --at line:10:3 --kdl
3262
+ readseek edit file.ts @edits.kdl --kdl
3263
+ readseek rename file.ts --at line:10:3 nextName --kdl
2891
3264
  \`\`\`
2892
3265
 
2893
- Native \`find text\` is literal, not regex. Reference selection requires exactly one of \`--name\` or \`--at\`; rename requires a line or hash cursor. Preview mutations first, then use \`--apply --plan-hash HASH\` with the returned plan hash. Edit requests are KDL: for example, \`set_line anchor="10:HASH" new_text="replacement"\`.
3266
+ Native \`grep\` uses UTF-8 RE2 regex by default; add \`--literal\` for exact text. Its positive \`--limit\` defaults to 100 matching lines, not occurrences. Reference selection requires either a symbol \`name\` or \`--at\`; rename requires a line or hash cursor. Preview mutations first, then use \`--apply --plan-hash HASH\` with the returned plan hash. Edit requests are KDL: for example, \`set_line anchor="10:HASH" new_text="replacement"\`.
2894
3267
 
2895
3268
  Syntax and rule checks return exit 0 when clean, 1 for findings, and 2 on failure. Exit 1 is not a transport failure. Syntax checks are parser checks, not type checking.
2896
3269
 
2897
- Content and assets return cached paths; assets auto-materialize. Read those paths directly. There is no extract command or asset destination option. Reading does not invoke inference. Request inference explicitly with \`generate caption\` (optionally \`--include text,objects\`), \`detect objects\`, or \`recognize text\`, using \`--level low|medium|high\` and an installed model; models are never downloaded automatically.
3270
+ Content and assets return cached paths; assets auto-materialize. Read those paths directly. There is no extract command or asset destination option. Reading does not invoke inference. Request inference explicitly with \`caption\` (optionally \`--include text,objects\`), \`objects\`, or \`ocr\`, using \`--level low|medium|high\` and an installed model; models are never downloaded automatically.
2898
3271
 
2899
3272
  This V2 plugin supplies guidance only; it does not register readseek_* tools. If the CLI is unavailable, use OpenCode's built-in tools instead.`
2900
3273
  };
@@ -2951,7 +3324,7 @@ var ReadSeekPlugin = async (_input, options) => {
2951
3324
  return;
2952
3325
  try {
2953
3326
  const result = output.metadata.readseek ?? JSON.parse(output.output);
2954
- if (input.tool === "readseek_rename_symbol") {
3327
+ if (input.tool === "readseek_rename") {
2955
3328
  anchors.planRename(input.sessionID, result);
2956
3329
  if (record2(result).applied === true) {
2957
3330
  for (const filePath of renameFiles(result))