opencode-readseek 0.9.16 → 0.10.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 (3) hide show
  1. package/README.md +13 -5
  2. package/dist/index.js +934 -290
  3. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -6,21 +6,15 @@ import { createRequire as createRequire2 } from "node:module";
6
6
  import { stat } from "node:fs/promises";
7
7
 
8
8
  // ../readseek-api/src/contract.ts
9
+ var INSPECT_FACETS = [
10
+ "metadata",
11
+ "content",
12
+ "map",
13
+ "diagnostics",
14
+ "identity"
15
+ ];
9
16
  var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
10
- var VISION_ANALYSIS_MODES = ["all", "ocr", "caption", "objects"];
11
17
  var VISION_LEVELS = ["low", "medium", "high"];
12
- var DOCUMENT_NODE_KINDS = [
13
- "artifact",
14
- "footer",
15
- "header",
16
- "heading",
17
- "marginal_label",
18
- "page",
19
- "page_number",
20
- "paragraph",
21
- "section",
22
- "structural_section"
23
- ];
24
18
  var EDIT_VARIANTS = [
25
19
  "set_line",
26
20
  "replace_lines",
@@ -28,12 +22,13 @@ var EDIT_VARIANTS = [
28
22
  "replace",
29
23
  "replace_symbol"
30
24
  ];
25
+ var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
31
26
  var TOOL_NAMES = [
32
- "digest",
27
+ "inspect",
33
28
  "edit",
34
29
  "write",
35
30
  "grep",
36
- "view",
31
+ "extract",
37
32
  "search",
38
33
  "def",
39
34
  "refs",
@@ -55,28 +50,24 @@ var PARAM_DESCRIPTIONS = {
55
50
  line: "One-based cursor line; required with scope",
56
51
  renameLine: "One-based cursor line of the symbol to rename",
57
52
  column: "One-based cursor byte column for disambiguation",
58
- cached: "In a Git repository, search tracked/indexed files",
59
- others: "In a Git repository, search untracked files",
60
- ignored: "With others=true, include ignored untracked files",
53
+ gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
61
54
  select: "Facets: metadata, content, map, diagnostics, identity (default content)",
62
55
  selectComma: "Comma-separated facets: metadata, content, map, diagnostics, identity",
63
- at: "Location: line:N[:COLUMN], hash:HASH[:COLUMN], or symbol:NAME",
56
+ selector: "Positional selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID. Columns require the identity facet, which in turn rejects symbol selectors",
57
+ assetSelector: "Document asset selector in the form asset:ID",
58
+ query: "KDL selection query, @path, or '-' for standard input",
59
+ stdinName: "File name hint when the target is '-'",
64
60
  end: "Last line to include",
65
61
  limit: "Maximum number of lines to include",
66
- digestDepth: "Maximum structural depth for map output; defaults to 1 in agent tools",
62
+ inspectDepth: "Maximum structural depth for map output; defaults to 1 in agent tools",
67
63
  offset: "One-based starting line",
68
64
  readLimit: "Maximum lines to return",
69
65
  symbolRead: "Symbol name to read",
70
66
  bundle: "Include related symbols from the same file; requires symbol",
71
67
  image: "Standalone image mode: none, all, ocr, caption, objects. Required for standalone images.",
72
- visionModeDigest: "Standalone image mode: none, all, caption, objects, or ocr",
73
- visionModeView: "Analyze selected assets: all, ocr, caption, or objects",
68
+ visionModeInspect: "Standalone image mode: none, all, caption, objects, or ocr",
74
69
  visionLevel: "Vision inference level: low (default), medium, or high",
75
- node: "Node ID to use as the view root",
76
- page: "One-based source page",
77
- kind: "Node kind filter",
78
- depth: "Maximum depth below selected roots",
79
- outline: "Return outline nodes only",
70
+ output: "Destination file for extracted binary data; stdout is not supported",
80
71
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
81
72
  editVariant: "Exactly one nested edit variant",
82
73
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -113,11 +104,11 @@ var PARAM_DESCRIPTIONS = {
113
104
  scopeContext: "Context lines within each symbol"
114
105
  };
115
106
  var TOOL_DESCRIPTIONS = {
116
- digest: "Digest source content and images into the native CLI envelope. Returns metadata plus requested facets unchanged from `readseek digest`. Use it before edits to obtain fresh `LINE:HASH` anchors; use shallow maps for structural overviews, diagnostics for syntax checks, and identity for cursor context.",
107
+ inspect: "Inspect source content, images, and documents through the native CLI envelope. Use it before edits to obtain fresh `LINE:HASH` anchors; use shallow maps for structural overviews, diagnostics for syntax checks, and identity for cursor context.",
117
108
  edit: 'Edit existing text files with fresh `LINE:HASH` anchors. Read or search first when no fresh anchors are available. Each `edits[]` item must use exactly one nested object: `{"set_line":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}`, `{"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_symbol":{"symbol":"name","new_body":"..."}}`, or `{"replace":{"old_text":"...","new_text":"..."}}`.',
118
109
  write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
119
110
  grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
120
- view: "Digest a document's structure or selected content. Start with the overview, then narrow by page, node, kind, or depth instead of reading the whole document. PDF is the first supported document format.",
111
+ extract: "Extract a selected document asset into a destination file. Use an `asset:ID` selector discovered through `inspect`; extracted binary data is never returned on stdout.",
121
112
  search: "Search code by AST structure using ast-grep-style patterns. Results include line hashes usable as `LINE:HASH` anchors. Use `grep` for identifiers, strings, comments, configuration, documentation, or other plain text and regex.",
122
113
  def: "Go to a symbol definition or declaration by name. Accepts qualified or unqualified names; use this instead of text search for functions, methods, classes, types, and other declarations.",
123
114
  refs: "Find references or usages of an identifier by name or cursor binding. Results include enclosing symbols and line hashes usable as `LINE:HASH` anchors. Without cursor scope, results are identifier-name matches; with scope, they follow one binding and exclude shadows. Use text search for literals, and use this before renaming or deleting a symbol.",
@@ -129,18 +120,11 @@ function renderToolDescription(name, aliases = {}) {
129
120
  throw new Error(`Missing TOOL_DESCRIPTIONS for ${name}`);
130
121
  return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), desc);
131
122
  }
132
- var gitFlagsSchema = {
133
- cached: {
134
- type: "boolean",
135
- description: PARAM_DESCRIPTIONS.cached
136
- },
137
- others: {
138
- type: "boolean",
139
- description: PARAM_DESCRIPTIONS.others
140
- },
141
- ignored: {
142
- type: "boolean",
143
- description: PARAM_DESCRIPTIONS.ignored
123
+ var gitScopeSchema = {
124
+ gitScope: {
125
+ type: "string",
126
+ enum: GIT_SCOPES,
127
+ description: PARAM_DESCRIPTIONS.gitScope
144
128
  }
145
129
  };
146
130
  var editItemSchema = {
@@ -206,29 +190,31 @@ var editItemSchema = {
206
190
  };
207
191
  var TOOL_CONTRACTS = [
208
192
  {
209
- name: "digest",
210
- title: "Digest source",
211
- description: TOOL_DESCRIPTIONS.digest,
193
+ name: "inspect",
194
+ title: "Inspect input",
195
+ description: TOOL_DESCRIPTIONS.inspect,
212
196
  inputSchema: {
213
197
  type: "object",
214
198
  properties: {
215
199
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
200
+ selector: { type: "string", description: PARAM_DESCRIPTIONS.selector },
216
201
  select: { type: "string", description: PARAM_DESCRIPTIONS.select },
217
- at: { type: "string", description: PARAM_DESCRIPTIONS.at },
202
+ query: { type: "string", description: PARAM_DESCRIPTIONS.query },
218
203
  end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
219
204
  limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
220
- depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.digestDepth },
205
+ depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.inspectDepth },
221
206
  language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
222
207
  visionMode: {
223
208
  type: "string",
224
209
  enum: [...VISION_MODES],
225
- description: PARAM_DESCRIPTIONS.visionModeDigest
210
+ description: PARAM_DESCRIPTIONS.visionModeInspect
226
211
  },
227
212
  visionLevel: {
228
213
  type: "string",
229
214
  enum: [...VISION_LEVELS],
230
215
  description: PARAM_DESCRIPTIONS.visionLevel
231
- }
216
+ },
217
+ stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
232
218
  },
233
219
  required: ["path"],
234
220
  additionalProperties: false
@@ -302,34 +288,27 @@ var TOOL_CONTRACTS = [
302
288
  }
303
289
  },
304
290
  {
305
- name: "view",
306
- title: "View document",
307
- description: TOOL_DESCRIPTIONS.view,
291
+ name: "extract",
292
+ title: "Extract asset",
293
+ description: TOOL_DESCRIPTIONS.extract,
308
294
  inputSchema: {
309
295
  type: "object",
310
296
  properties: {
311
297
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
312
- node: { type: "string", description: PARAM_DESCRIPTIONS.node },
313
- page: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.page },
314
- kind: {
298
+ selector: {
315
299
  type: "string",
316
- enum: [...DOCUMENT_NODE_KINDS],
317
- description: PARAM_DESCRIPTIONS.kind
300
+ pattern: "^asset:.+",
301
+ description: PARAM_DESCRIPTIONS.assetSelector
318
302
  },
319
- depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.depth },
320
- outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline },
321
- visionMode: {
303
+ output: {
322
304
  type: "string",
323
- enum: [...VISION_ANALYSIS_MODES],
324
- description: PARAM_DESCRIPTIONS.visionModeView
305
+ minLength: 1,
306
+ pattern: "^(?!-$).+",
307
+ description: PARAM_DESCRIPTIONS.output
325
308
  },
326
- visionLevel: {
327
- type: "string",
328
- enum: [...VISION_LEVELS],
329
- description: PARAM_DESCRIPTIONS.visionLevel
330
- }
309
+ stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
331
310
  },
332
- required: ["path"],
311
+ required: ["path", "selector", "output"],
333
312
  additionalProperties: false
334
313
  }
335
314
  },
@@ -343,7 +322,8 @@ var TOOL_CONTRACTS = [
343
322
  pattern: { type: "string", description: PARAM_DESCRIPTIONS.pattern },
344
323
  path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
345
324
  language: { type: "string", description: PARAM_DESCRIPTIONS.language },
346
- ...gitFlagsSchema
325
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
326
+ ...gitScopeSchema
347
327
  },
348
328
  required: ["pattern"],
349
329
  additionalProperties: false
@@ -359,7 +339,7 @@ var TOOL_CONTRACTS = [
359
339
  name: { type: "string", description: PARAM_DESCRIPTIONS.name },
360
340
  path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
361
341
  language: { type: "string", description: PARAM_DESCRIPTIONS.language },
362
- ...gitFlagsSchema
342
+ ...gitScopeSchema
363
343
  },
364
344
  required: ["name"],
365
345
  additionalProperties: false
@@ -378,7 +358,7 @@ var TOOL_CONTRACTS = [
378
358
  scope: { type: "boolean", description: PARAM_DESCRIPTIONS.scope },
379
359
  line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.line },
380
360
  column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
381
- ...gitFlagsSchema
361
+ ...gitScopeSchema
382
362
  },
383
363
  additionalProperties: false
384
364
  }
@@ -396,7 +376,9 @@ var TOOL_CONTRACTS = [
396
376
  to: { type: "string", description: PARAM_DESCRIPTIONS.to },
397
377
  workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace },
398
378
  apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
399
- planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash }
379
+ planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash },
380
+ language: { type: "string", description: PARAM_DESCRIPTIONS.language },
381
+ ...gitScopeSchema
400
382
  },
401
383
  required: ["path", "line", "to"],
402
384
  additionalProperties: false
@@ -405,14 +387,14 @@ var TOOL_CONTRACTS = [
405
387
  ];
406
388
  // ../readseek-api/src/policy.ts
407
389
  var BUILTIN_FALLBACKS = {
408
- read: "digest",
390
+ read: "inspect",
409
391
  edit: "edit",
410
392
  write: "write",
411
393
  grep: "grep",
412
394
  apply_patch: "edit"
413
395
  };
414
396
  var PREFERENCE_LEADS = {
415
- digest: "Preferred for reading source with anchors and facets.",
397
+ inspect: "Preferred for reading source with anchors and facets.",
416
398
  edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
417
399
  write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
418
400
  grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
@@ -420,10 +402,10 @@ var PREFERENCE_LEADS = {
420
402
  def: "Preferred for go-to-definition or declaration lookup by name.",
421
403
  refs: "Preferred for finding identifier-name matches or binding-specific references before rename or delete.",
422
404
  rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
423
- view: "Preferred for digesting document structure or selected content."
405
+ extract: "Preferred for saving a selected document asset to a file."
424
406
  };
425
407
  var FALLBACK_TEMPLATES = {
426
- read: "Prefer `digest` with fresh LINE:HASH anchors.",
408
+ read: "Prefer `inspect` with fresh LINE:HASH anchors.",
427
409
  edit: "Prefer `edit` with fresh LINE:HASH anchors.",
428
410
  write: "Prefer `write` so the result returns LINE:HASH anchors.",
429
411
  grep: "Prefer `grep` for text/regex search with edit-ready anchors.",
@@ -446,7 +428,7 @@ function renderFallbackToolDescription(builtIn, aliases = {}) {
446
428
  }
447
429
  function renderToolRoutingPolicy(options = {}) {
448
430
  const aliases = options.aliases ?? {};
449
- const digest = resolveToolName("digest", aliases);
431
+ const inspect = resolveToolName("inspect", aliases);
450
432
  const edit = resolveToolName("edit", aliases);
451
433
  const write = resolveToolName("write", aliases);
452
434
  const grep = resolveToolName("grep", aliases);
@@ -454,11 +436,11 @@ function renderToolRoutingPolicy(options = {}) {
454
436
  const def = resolveToolName("def", aliases);
455
437
  const refs = resolveToolName("refs", aliases);
456
438
  const rename = resolveToolName("rename", aliases);
457
- const view = resolveToolName("view", aliases);
439
+ const extract = resolveToolName("extract", aliases);
458
440
  if (options.style === "server") {
459
441
  return [
460
- "Structural code navigation, document viewing, and hash-verified edits with readseek.",
461
- "For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST search; shallow digest maps; diagnostics and identity; and document views.",
442
+ "Structural code navigation, document inspection, and hash-verified edits with readseek.",
443
+ "For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST search; shallow inspect maps; diagnostics and identity; and document inspection.",
462
444
  `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
463
445
  "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
464
446
  "call MCP tools for active navigation, document questions, or anchored edits."
@@ -469,7 +451,7 @@ function renderToolRoutingPolicy(options = {}) {
469
451
  if (options.preferOverBuiltIns) {
470
452
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
471
453
  }
472
- bullets.push(`- Read with ${digest} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${digest} maps only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${view} for document structure; run ${digest} with select diagnostics after source edits for a quick syntax check.`);
454
+ bullets.push(`- Read with ${inspect} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${inspect} maps only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${inspect} for document structure; run ${inspect} with select diagnostics after source edits for a quick syntax check.`, `- Use ${extract} to save a selected document asset.`);
473
455
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
474
456
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
475
457
  }
@@ -504,12 +486,6 @@ function tryParseLineAnchor(anchor) {
504
486
  }
505
487
 
506
488
  // ../readseek-api/src/validation.ts
507
- function validateGitSelection(input) {
508
- if (input.ignored && !input.others) {
509
- return { ok: false, error: "ignored requires others=true" };
510
- }
511
- return { ok: true };
512
- }
513
489
  function validateRefsNameVsScope(input) {
514
490
  if (input.scope) {
515
491
  if (input.line === undefined) {
@@ -526,11 +502,8 @@ function validateRefsNameVsScope(input) {
526
502
  return { ok: true };
527
503
  }
528
504
  function validateVisionLevelVsMode(input) {
529
- if (input.outline && (input.visionMode !== undefined || input.visionLevel !== undefined)) {
530
- return { ok: false, error: "Cannot combine outline with visionMode or visionLevel" };
531
- }
532
- if (input.visionLevel !== undefined && input.visionMode === undefined) {
533
- return { ok: false, error: "visionLevel requires visionMode" };
505
+ if (input.visionLevel !== undefined && !["all", "ocr", "caption", "objects"].includes(input.visionMode ?? "")) {
506
+ return { ok: false, error: "visionLevel requires an analysis visionMode" };
534
507
  }
535
508
  return { ok: true };
536
509
  }
@@ -601,22 +574,54 @@ function validateExactlyOneEditVariant(edits) {
601
574
  }
602
575
  var MIN_SAFE_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
603
576
  var MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
577
+ // ../readseek-api/src/edit-plan.ts
578
+ import { Document, Node, format } from "@bgotink/kdl";
579
+ function node(name, properties, children = []) {
580
+ const result = Node.create(name);
581
+ for (const [key, value] of Object.entries(properties))
582
+ result.setProperty(key, value);
583
+ if (children.length > 0)
584
+ result.children = new Document(children);
585
+ return result;
586
+ }
587
+ function editNode(edit) {
588
+ if ("set_line" in edit) {
589
+ const value = edit.set_line;
590
+ return node("set-line", { anchor: value.anchor, text: value.new_text });
591
+ }
592
+ if ("replace_lines" in edit) {
593
+ const value = edit.replace_lines;
594
+ return node("replace-lines", { start: value.start_anchor, end: value.end_anchor, text: value.new_text });
595
+ }
596
+ if ("insert_after" in edit) {
597
+ const value = edit.insert_after;
598
+ return node("insert-after", { anchor: value.anchor, text: value.new_text });
599
+ }
600
+ if ("replace_symbol" in edit) {
601
+ const value = edit.replace_symbol;
602
+ return node("replace-symbol", { symbol: value.symbol, body: value.new_body });
603
+ }
604
+ const value = edit.replace;
605
+ return node("replace", {
606
+ old: value.old_text,
607
+ new: value.new_text,
608
+ ...value.all === undefined ? {} : { all: value.all }
609
+ });
610
+ }
611
+ function serializeEditPlan(edits) {
612
+ const validation = validateExactlyOneEditVariant(edits);
613
+ if (!validation.ok)
614
+ throw new Error(validation.error);
615
+ return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
616
+ }
604
617
  // ../readseek-api/src/argv.ts
605
- var DEFAULT_DIGEST_MAP_DEPTH = 1;
618
+ var DEFAULT_INSPECT_MAP_DEPTH = 1;
606
619
  function buildLineLocation(line, column) {
607
620
  return column === undefined ? `line:${line}` : `line:${line}:${column}`;
608
621
  }
609
- function appendGitSelectionArgs(args, input) {
610
- const validated = validateGitSelection(input);
611
- if (!validated.ok) {
612
- throw new Error(validated.error);
613
- }
614
- if (input.cached)
615
- args.push("--cached");
616
- if (input.others)
617
- args.push("--others");
618
- if (input.ignored)
619
- args.push("--ignored");
622
+ function appendGitScopeArgs(args, input) {
623
+ if (input.gitScope !== undefined)
624
+ args.push("--git-scope", input.gitScope);
620
625
  return args;
621
626
  }
622
627
  function pushOptionalString(args, flag, value) {
@@ -645,33 +650,138 @@ function selectArg(select) {
645
650
  return;
646
651
  return Array.isArray(select) ? select.join(",") : select;
647
652
  }
648
- function selectsDigestMap(select) {
649
- const value = selectArg(select);
650
- return value?.split(",").some((facet) => facet.trim() === "map") ?? false;
653
+ function selectedInspectFacets(select) {
654
+ const facets = (selectArg(select) ?? "content").split(",").map((facet) => facet.trim());
655
+ for (const facet of facets) {
656
+ if (!INSPECT_FACETS.includes(facet)) {
657
+ throw new Error(`unknown inspect facet: ${facet}`);
658
+ }
659
+ }
660
+ return facets;
661
+ }
662
+ function hasInspectFacet(facets, facet) {
663
+ return facets.includes(facet);
664
+ }
665
+ function validateTarget(command, target, stdinName) {
666
+ if (target.length === 0)
667
+ throw new Error(`${command} requires a target file path or '-'`);
668
+ if (stdinName !== undefined && stdinName.length > 0 && target !== "-") {
669
+ throw new Error("stdinName requires '-' as the target");
670
+ }
671
+ }
672
+ function validatePositiveInteger(value, name) {
673
+ if (!Number.isSafeInteger(value) || value < 1) {
674
+ throw new Error(`${name} must be a positive integer`);
675
+ }
676
+ }
677
+ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
678
+ function isSelectorKind(value) {
679
+ return SELECTOR_KINDS.includes(value);
680
+ }
681
+ function parseSelector(text) {
682
+ const separator = text.indexOf(":");
683
+ if (separator < 0) {
684
+ throw new Error("selector must start with line:, hash:, symbol:, page:, node:, or asset:");
685
+ }
686
+ const kind = text.slice(0, separator);
687
+ const value = text.slice(separator + 1);
688
+ if (value.length === 0)
689
+ throw new Error(`${kind} selector must not be empty`);
690
+ if (!isSelectorKind(kind)) {
691
+ throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
692
+ }
693
+ if (kind === "line" || kind === "hash") {
694
+ const parts = value.split(":");
695
+ if (parts.length > 2)
696
+ throw new Error("selector has too many components");
697
+ if (parts.length === 2) {
698
+ const column = Number(parts[1]);
699
+ if (!Number.isSafeInteger(column))
700
+ throw new Error(`invalid column \`${parts[1]}\``);
701
+ if (column < 1)
702
+ throw new Error("column must be greater than zero");
703
+ return { kind, value: parts[0], column };
704
+ }
705
+ }
706
+ if (kind === "page") {
707
+ if (value.includes(":"))
708
+ throw new Error("page selector has too many components");
709
+ const page = Number(value);
710
+ if (!Number.isSafeInteger(page))
711
+ throw new Error(`invalid page \`${value}\``);
712
+ if (page < 1)
713
+ throw new Error("page must be greater than zero");
714
+ }
715
+ return { kind, value };
651
716
  }
652
- function buildDigestArgs(input) {
717
+ function buildInspectArgs(input) {
718
+ validateTarget("inspect", input.path, input.stdinName);
719
+ if (input.selector !== undefined && input.selector.length === 0) {
720
+ throw new Error("selector must not be empty");
721
+ }
653
722
  const vision = validateVisionLevelVsMode({
654
723
  visionMode: input.visionMode,
655
724
  visionLevel: input.visionLevel
656
725
  });
657
726
  if (!vision.ok)
658
727
  throw new Error(vision.error);
659
- const selectsMap = selectsDigestMap(input.select);
728
+ const facets = selectedInspectFacets(input.select);
729
+ const hasContent = hasInspectFacet(facets, "content");
730
+ const hasIdentity = hasInspectFacet(facets, "identity");
731
+ const hasMap = hasInspectFacet(facets, "map");
732
+ const hasSourceFacet = hasMap || hasInspectFacet(facets, "diagnostics") || hasIdentity;
733
+ if (input.end !== undefined)
734
+ validatePositiveInteger(input.end, "end");
735
+ if (input.limit !== undefined)
736
+ validatePositiveInteger(input.limit, "limit");
737
+ if (input.end !== undefined && input.limit !== undefined) {
738
+ throw new Error("cannot combine end with limit");
739
+ }
660
740
  if (input.depth !== undefined) {
661
- if (!Number.isInteger(input.depth) || input.depth < 0) {
741
+ if (!Number.isSafeInteger(input.depth) || input.depth < 0) {
662
742
  throw new Error("depth must be a non-negative integer");
663
743
  }
664
- if (!selectsMap)
744
+ if (!hasMap)
665
745
  throw new Error("depth requires the map facet");
666
746
  }
747
+ if (input.selector !== undefined && input.query !== undefined) {
748
+ throw new Error("cannot combine selector with query");
749
+ }
750
+ if (!hasContent && (input.end !== undefined || input.limit !== undefined || input.query !== undefined)) {
751
+ throw new Error("ranges and queries require the content facet");
752
+ }
753
+ if (hasIdentity && input.selector === undefined) {
754
+ throw new Error("the identity facet requires a selector");
755
+ }
756
+ if (input.selector !== undefined && !hasContent && !hasIdentity) {
757
+ throw new Error("selectors require the content or identity facet");
758
+ }
759
+ if (input.selector !== undefined) {
760
+ const selector = parseSelector(input.selector);
761
+ if (selector.kind === "symbol" && hasIdentity) {
762
+ throw new Error("the identity facet does not accept symbol selectors");
763
+ }
764
+ if (selector.column !== undefined && !hasIdentity) {
765
+ throw new Error("selector columns require the identity facet");
766
+ }
767
+ }
768
+ const hasVision = input.visionMode !== undefined || input.visionLevel !== undefined;
769
+ if (hasVision && !hasContent) {
770
+ throw new Error("vision options require the content facet");
771
+ }
772
+ if (hasVision && hasSourceFacet) {
773
+ throw new Error("vision options cannot be combined with source facets");
774
+ }
667
775
  const args = [];
668
776
  if (input.readseekDir !== undefined)
669
777
  args.push("--readseek-dir", input.readseekDir);
670
- args.push("digest", input.path);
778
+ args.push("inspect", input.path);
779
+ if (input.selector !== undefined)
780
+ args.push(input.selector);
671
781
  const select = selectArg(input.select);
672
782
  if (select !== undefined)
673
783
  args.push("--select", select);
674
- pushOptionalString(args, "--at", input.at);
784
+ pushOptionalString(args, "--query", input.query);
675
785
  pushOptionalNumber(args, "--end", input.end);
676
786
  pushOptionalNumber(args, "--limit", input.limit);
677
787
  pushOptionalNumber(args, "--depth", input.depth);
@@ -679,30 +789,24 @@ function buildDigestArgs(input) {
679
789
  pushOptionalString(args, "--vision-mode", input.visionMode);
680
790
  pushOptionalString(args, "--vision-level", input.visionLevel);
681
791
  pushOptionalString(args, "--stdin-name", input.stdinName);
792
+ args.push("--kdl");
682
793
  return args;
683
794
  }
684
- function buildViewArgs(input) {
685
- const vision = validateVisionLevelVsMode({
686
- visionMode: input.visionMode,
687
- visionLevel: input.visionLevel,
688
- outline: input.outline
689
- });
690
- if (!vision.ok)
691
- throw new Error(vision.error);
795
+ function buildExtractArgs(input) {
796
+ validateTarget("extract", input.path, input.stdinName);
797
+ if (input.selector.length === 0)
798
+ throw new Error("extract requires an asset selector");
799
+ if (!input.selector.startsWith("asset:") || input.selector === "asset:") {
800
+ throw new Error("extract requires an asset: selector");
801
+ }
802
+ if (input.output.length === 0 || input.output === "-") {
803
+ throw new Error("extract output must be a file path");
804
+ }
692
805
  const args = [];
693
806
  if (input.readseekDir !== undefined)
694
807
  args.push("--readseek-dir", input.readseekDir);
695
- args.push("view", input.path);
696
- if (input.format !== undefined)
697
- args.push("--format", input.format);
698
- pushOptionalString(args, "--node", input.node);
699
- pushOptionalNumber(args, "--page", input.page);
700
- pushOptionalString(args, "--kind", input.kind);
701
- pushOptionalNumber(args, "--depth", input.depth);
702
- if (input.outline)
703
- args.push("--outline");
704
- pushOptionalString(args, "--vision-mode", input.visionMode);
705
- pushOptionalString(args, "--vision-level", input.visionLevel);
808
+ args.push("--output", input.output, "extract", input.path, input.selector);
809
+ pushOptionalString(args, "--stdin-name", input.stdinName);
706
810
  return args;
707
811
  }
708
812
  function buildSearchArgs(input) {
@@ -710,15 +814,17 @@ function buildSearchArgs(input) {
710
814
  pushOptionalString(args, "--path", input.path);
711
815
  pushOptionalString(args, "--language", input.language);
712
816
  pushOptionalNumber(args, "--limit", input.limit);
713
- return appendGitSelectionArgs(args, input);
817
+ appendGitScopeArgs(args, input);
818
+ args.push("--kdl");
819
+ return args;
714
820
  }
715
821
  function buildDefArgs(input) {
716
822
  const args = ["def", input.name];
717
823
  pushOptionalString(args, "--path", input.path);
718
- if (input.format !== undefined)
719
- args.push("--format", input.format);
720
824
  pushOptionalString(args, "--language", input.language);
721
- return appendGitSelectionArgs(args, input);
825
+ appendGitScopeArgs(args, input);
826
+ args.push("--kdl");
827
+ return args;
722
828
  }
723
829
  function buildRefsArgs(input) {
724
830
  const validated = validateRefsNameVsScope(input);
@@ -733,10 +839,10 @@ function buildRefsArgs(input) {
733
839
  pushOptionalNumber(args, "--line", input.line);
734
840
  pushOptionalNumber(args, "--column", input.column);
735
841
  }
736
- if (input.format !== undefined)
737
- args.push("--format", input.format);
738
842
  pushOptionalString(args, "--language", input.language);
739
- return appendGitSelectionArgs(args, input);
843
+ appendGitScopeArgs(args, input);
844
+ args.push("--kdl");
845
+ return args;
740
846
  }
741
847
  function buildEditArgs(input) {
742
848
  const args = ["edit", input.path];
@@ -748,6 +854,7 @@ function buildEditArgs(input) {
748
854
  pushOptionalString(args, "--language", input.language);
749
855
  if (input.requestFromStdin)
750
856
  args.push("--edits", "-");
857
+ args.push("--kdl");
751
858
  return args;
752
859
  }
753
860
  function buildRenameArgs(input) {
@@ -760,7 +867,9 @@ function buildRenameArgs(input) {
760
867
  flagOrder: input.flagOrder ?? "plan-hash-first"
761
868
  });
762
869
  pushOptionalString(args, "--language", input.language);
763
- return appendGitSelectionArgs(args, input);
870
+ appendGitScopeArgs(args, input);
871
+ args.push("--kdl");
872
+ return args;
764
873
  }
765
874
  // ../readseek-api/src/platform.ts
766
875
  import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
@@ -776,6 +885,8 @@ var READSEEK_PLATFORM_PACKAGES = {
776
885
  };
777
886
  var resolveFromPackage = createRequire(import.meta.url).resolve;
778
887
  function readSeekBinaryName(platform = process.platform) {
888
+ if (platform === "linux")
889
+ return "readseek.AppImage";
779
890
  return platform === "win32" ? "readseek.exe" : "readseek";
780
891
  }
781
892
  function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
@@ -855,22 +966,304 @@ function isSubPath(parent, candidate) {
855
966
  const relative = path2.relative(parent, candidate);
856
967
  return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
857
968
  }
858
- // ../readseek-api/src/json.ts
859
- var READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
969
+ // ../readseek-api/src/error.ts
970
+ var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
860
971
  function cleanReadSeekErrorText(stderr) {
861
972
  return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
862
973
  }
863
- function parseReadSeekJson(stdout) {
864
- try {
865
- return JSON.parse(stdout);
866
- } catch {
867
- const preview = stdout.trim().slice(0, 200);
868
- throw new Error(`readseek returned invalid JSON: ${preview}`);
974
+ // ../readseek-api/src/kdl.ts
975
+ import { parse } from "@bgotink/kdl";
976
+ function convertNode(node) {
977
+ if (node.getTag() !== null)
978
+ throw new Error("KDL node annotations are not supported");
979
+ const properties = new Map;
980
+ for (const entry of node.entries) {
981
+ const name = entry.getName();
982
+ if (name === null)
983
+ throw new Error("KDL positional arguments are not supported");
984
+ if (properties.has(name))
985
+ throw new Error(`KDL duplicate property '${name}'`);
986
+ if (entry.getTag() !== null)
987
+ throw new Error("KDL value annotations are not supported");
988
+ const value = entry.getValue();
989
+ if (value === null)
990
+ throw new Error("KDL null values are not supported");
991
+ if (typeof value === "number" && !Number.isFinite(value)) {
992
+ throw new Error("KDL number out of range");
993
+ }
994
+ properties.set(name, value);
995
+ }
996
+ return {
997
+ name: node.getName(),
998
+ properties,
999
+ children: node.children?.nodes.map(convertNode) ?? []
1000
+ };
1001
+ }
1002
+ function parseKdlDocument(text) {
1003
+ const document = parse(text);
1004
+ if (document.nodes.length !== 1)
1005
+ throw new Error("KDL document must contain exactly one root node");
1006
+ return convertNode(document.nodes[0]);
1007
+ }
1008
+
1009
+ // ../readseek-api/src/output-kdl.ts
1010
+ function properties(node) {
1011
+ return Object.fromEntries(node.properties);
1012
+ }
1013
+ function children(node, name) {
1014
+ return node.children.filter((child) => child.name === name);
1015
+ }
1016
+ function child(node, name) {
1017
+ return node.children.find((candidate) => candidate.name === name);
1018
+ }
1019
+ function requireProperty(node, name) {
1020
+ const value = node.properties.get(name);
1021
+ if (value === undefined)
1022
+ throw new Error(`invalid readseek KDL: ${node.name} is missing '${name}'`);
1023
+ return value;
1024
+ }
1025
+ function line(node) {
1026
+ const anchor = requireProperty(node, "anchor");
1027
+ const content = requireProperty(node, "content");
1028
+ if (typeof anchor !== "string" || typeof content !== "string") {
1029
+ throw new Error("invalid readseek KDL line");
1030
+ }
1031
+ const match = /^(\d+):([0-9a-f]+)$/.exec(anchor);
1032
+ if (!match)
1033
+ throw new Error(`invalid readseek KDL line anchor: ${anchor}`);
1034
+ return {
1035
+ line: Number(match[1]),
1036
+ hash: match[2],
1037
+ text: content
1038
+ };
1039
+ }
1040
+ function mapNode(node) {
1041
+ return {
1042
+ ...properties(node),
1043
+ children: children(node, "node").map(mapNode)
1044
+ };
1045
+ }
1046
+ function sourceContent(node) {
1047
+ const result = properties(node);
1048
+ result.hashlines = children(node, "line").map(line);
1049
+ const objects = children(node, "object");
1050
+ if (objects.length > 0) {
1051
+ result.objects = objects.map((object) => ({
1052
+ label: requireProperty(object, "label"),
1053
+ bbox: [
1054
+ requireProperty(object, "xmin"),
1055
+ requireProperty(object, "ymin"),
1056
+ requireProperty(object, "xmax"),
1057
+ requireProperty(object, "ymax")
1058
+ ]
1059
+ }));
1060
+ }
1061
+ return result;
1062
+ }
1063
+ function sourceMap(node) {
1064
+ const root = child(node, "node");
1065
+ return {
1066
+ ...properties(node),
1067
+ ...root ? { root: mapNode(root) } : {},
1068
+ symbols: children(node, "symbol").map(properties)
1069
+ };
1070
+ }
1071
+ function diagnostics(node) {
1072
+ return {
1073
+ ...properties(node),
1074
+ diagnostics: children(node, "diagnostic").map(properties)
1075
+ };
1076
+ }
1077
+ function identity(node) {
1078
+ const result = properties(node);
1079
+ const identifier = child(node, "identifier");
1080
+ const symbol = child(node, "symbol");
1081
+ if (identifier)
1082
+ result.identifier = properties(identifier);
1083
+ if (symbol)
1084
+ result.symbol = properties(symbol);
1085
+ return result;
1086
+ }
1087
+ function sourceDocument(root) {
1088
+ const result = {};
1089
+ const metadata = child(root, "metadata");
1090
+ const cache = child(root, "cache");
1091
+ const content = child(root, "content");
1092
+ const map = child(root, "map");
1093
+ const diagnosticOutput = child(root, "diagnostics");
1094
+ const identityOutput = child(root, "identity");
1095
+ if (metadata)
1096
+ result.metadata = properties(metadata);
1097
+ if (cache)
1098
+ result.cache = properties(cache);
1099
+ if (content)
1100
+ result.content = sourceContent(content);
1101
+ if (map)
1102
+ result.map = sourceMap(map);
1103
+ if (diagnosticOutput)
1104
+ result.diagnostics = diagnostics(diagnosticOutput);
1105
+ if (identityOutput)
1106
+ result.identity = identity(identityOutput);
1107
+ return result;
1108
+ }
1109
+ function hit(node) {
1110
+ return {
1111
+ ...properties(node),
1112
+ hashlines: children(node, "line").map(line),
1113
+ captures: children(node, "capture").map((capture) => ({
1114
+ ...properties(capture),
1115
+ hashlines: children(capture, "line").map(line)
1116
+ }))
1117
+ };
1118
+ }
1119
+ function search(root) {
1120
+ return {
1121
+ ...properties(root),
1122
+ results: children(root, "match").map((match) => ({
1123
+ ...properties(match),
1124
+ matches: children(match, "hit").map(hit)
1125
+ })),
1126
+ documents: children(root, "document").map((documentNode) => ({
1127
+ ...properties(documentNode),
1128
+ hits: children(documentNode, "hit").map(properties)
1129
+ }))
1130
+ };
1131
+ }
1132
+ function references(root) {
1133
+ const parsed = children(root, "reference").map((reference) => {
1134
+ const sourceLine = child(reference, "line");
1135
+ if (!sourceLine)
1136
+ throw new Error("invalid readseek KDL reference: missing line");
1137
+ const parsedLine = line(sourceLine);
1138
+ const symbol = child(reference, "symbol");
1139
+ return {
1140
+ ...properties(reference),
1141
+ line_hash: parsedLine.hash,
1142
+ text: parsedLine.text,
1143
+ ...symbol ? { symbol: properties(symbol) } : {}
1144
+ };
1145
+ });
1146
+ return {
1147
+ references: parsed,
1148
+ locations: parsed.map((reference) => {
1149
+ const { symbol, ...location } = reference;
1150
+ return symbol && typeof symbol === "object" ? { ...location, ...symbol } : location;
1151
+ })
1152
+ };
1153
+ }
1154
+ function definitions(root) {
1155
+ return {
1156
+ locations: children(root, "definition").map((definition) => {
1157
+ const symbol = child(definition, "symbol");
1158
+ const sourceLine = child(definition, "line");
1159
+ if (!symbol || !sourceLine) {
1160
+ throw new Error("invalid readseek KDL definition: missing symbol or line");
1161
+ }
1162
+ const symbolProperties = properties(symbol);
1163
+ const parsedLine = line(sourceLine);
1164
+ const definitionColumn = definition.properties.get("column");
1165
+ const nameByte = symbol.properties.get("name_byte");
1166
+ const startByte = symbol.properties.get("start_byte");
1167
+ let column = 1;
1168
+ if (typeof definitionColumn === "number") {
1169
+ column = definitionColumn;
1170
+ } else if (typeof nameByte === "number" && typeof startByte === "number") {
1171
+ column = nameByte - startByte + 1;
1172
+ }
1173
+ return {
1174
+ file: requireProperty(definition, "file"),
1175
+ line: parsedLine.line,
1176
+ column,
1177
+ line_hash: parsedLine.hash,
1178
+ text: parsedLine.text,
1179
+ kind: symbolProperties.kind,
1180
+ name: symbolProperties.name,
1181
+ qualified_name: symbolProperties.qualified_name
1182
+ };
1183
+ })
1184
+ };
1185
+ }
1186
+ function edit(root) {
1187
+ const result = properties(root);
1188
+ for (const name of ["before_content", "content"]) {
1189
+ const contentNode = child(root, name);
1190
+ if (contentNode)
1191
+ result[name] = requireProperty(contentNode, "content");
1192
+ }
1193
+ return result;
1194
+ }
1195
+ function renameEdit(node) {
1196
+ const sourceLine = child(node, "line");
1197
+ if (!sourceLine)
1198
+ throw new Error("invalid readseek KDL rename edit: missing line");
1199
+ return {
1200
+ ...properties(node),
1201
+ text: requireProperty(sourceLine, "content")
1202
+ };
1203
+ }
1204
+ function renameFile(node) {
1205
+ return {
1206
+ ...properties(node),
1207
+ edits: children(node, "edit").map(renameEdit),
1208
+ conflicts: children(node, "conflict").map(properties)
1209
+ };
1210
+ }
1211
+ function rename(root) {
1212
+ return {
1213
+ ...renameFile(root),
1214
+ others: children(root, "other").map(renameFile)
1215
+ };
1216
+ }
1217
+ function genericNode(node) {
1218
+ const result = properties(node);
1219
+ const textNode = child(node, "text");
1220
+ const bbox = child(node, "bbox");
1221
+ if (textNode)
1222
+ result.text = requireProperty(textNode, "content");
1223
+ if (bbox)
1224
+ result.bbox = properties(bbox);
1225
+ const objects = children(node, "object");
1226
+ if (objects.length > 0)
1227
+ result.objects = objects.map(genericNode);
1228
+ return result;
1229
+ }
1230
+ function pdfDocument(root) {
1231
+ return {
1232
+ ...properties(root),
1233
+ nodes: children(root, "node").map(genericNode),
1234
+ assets: children(root, "asset").map(genericNode),
1235
+ vision: children(root, "vision").map(genericNode)
1236
+ };
1237
+ }
1238
+ function parseReadSeekKdl(text) {
1239
+ const root = parseKdlDocument(text);
1240
+ switch (root.name) {
1241
+ case "document":
1242
+ return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1243
+ case "search":
1244
+ return search(root);
1245
+ case "check":
1246
+ return diagnostics(root);
1247
+ case "refs":
1248
+ return references(root);
1249
+ case "def":
1250
+ return definitions(root);
1251
+ case "edit":
1252
+ return edit(root);
1253
+ case "edit-batch":
1254
+ return { ...properties(root), files: children(root, "file").map(edit) };
1255
+ case "rename":
1256
+ return rename(root);
1257
+ default:
1258
+ throw new Error(`unsupported readseek KDL root: ${root.name}`);
869
1259
  }
870
1260
  }
871
1261
  // ../readseek-api/src/process.ts
872
1262
  var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
873
1263
  var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
1264
+ function readSeekProcessEnvironment() {
1265
+ return process.platform === "linux" ? { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } : process.env;
1266
+ }
874
1267
  function resolveProcessLimits(options = {}) {
875
1268
  return {
876
1269
  timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
@@ -901,8 +1294,8 @@ function ensureSuccessfulStdout(result) {
901
1294
  return result.stdout;
902
1295
  throw failureFromProcessResult(result);
903
1296
  }
904
- function parseSuccessfulJson(result) {
905
- return parseReadSeekJson(ensureSuccessfulStdout(result));
1297
+ function parseSuccessfulKdl(result) {
1298
+ return parseReadSeekKdl(ensureSuccessfulStdout(result));
906
1299
  }
907
1300
  // ../readseek-api/src/process-bun.ts
908
1301
  function getBunSpawn() {
@@ -952,7 +1345,7 @@ async function runReadSeekBun(binaryPath, args, options = {}) {
952
1345
  const stdin = options.stdin === undefined ? undefined : new TextEncoder().encode(options.stdin);
953
1346
  const spawnOptions = {
954
1347
  cwd: options.cwd,
955
- env: process.env,
1348
+ env: readSeekProcessEnvironment(),
956
1349
  stderr: "pipe",
957
1350
  stdin,
958
1351
  stdout: "pipe"
@@ -986,26 +1379,260 @@ async function runReadSeekBun(binaryPath, args, options = {}) {
986
1379
  } finally {
987
1380
  if (timeout !== undefined)
988
1381
  clearTimeout(timeout);
1382
+ pending.catch(() => {
1383
+ return;
1384
+ });
989
1385
  }
990
1386
  }
991
1387
  async function runReadSeekBunText(binaryPath, args, options = {}) {
992
1388
  return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options));
993
1389
  }
994
- async function runReadSeekBunJson(binaryPath, args, options = {}) {
995
- return parseSuccessfulJson(await runReadSeekBun(binaryPath, args, options));
1390
+ async function runReadSeekBunKdl(binaryPath, args, options = {}) {
1391
+ return parseSuccessfulKdl(await runReadSeekBun(binaryPath, args, options));
1392
+ }
1393
+ // ../readseek-api/src/client-output.ts
1394
+ import { Type } from "@sinclair/typebox";
1395
+ import { AssertError, Value } from "@sinclair/typebox/value";
1396
+ var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
1397
+ var Str = Type.String();
1398
+ var Bool = Type.Boolean();
1399
+ var OptStr = Type.Optional(Str);
1400
+ var OptInt = Type.Optional(Int);
1401
+ var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
1402
+ var Hashlines = Type.Array(Hashline);
1403
+ var ReadOutput = Type.Object({
1404
+ file: Str,
1405
+ language: Str,
1406
+ line_count: Int,
1407
+ file_hash: Str,
1408
+ start_line: Int,
1409
+ end_line: Int,
1410
+ hashlines: Hashlines
1411
+ });
1412
+ var SymbolOutput = Type.Object({
1413
+ kind: Str,
1414
+ name: Str,
1415
+ qualified_name: OptStr,
1416
+ start_line: Int,
1417
+ end_line: Int,
1418
+ start_hash: OptStr,
1419
+ end_hash: OptStr,
1420
+ start_anchor: OptStr,
1421
+ end_anchor: OptStr
1422
+ });
1423
+ function mapNodeShape(child) {
1424
+ return Type.Object({
1425
+ kind: Str,
1426
+ name: Str,
1427
+ start_line: Int,
1428
+ end_line: Int,
1429
+ start_anchor: Str,
1430
+ end_anchor: Str,
1431
+ children: Type.Array(child)
1432
+ });
996
1433
  }
997
- // src/constants.ts
998
- var DEFAULT_VISION_TIMEOUT_MS = 30 * 60 * 1000;
999
- var DEFAULT_READ_LIMIT = 2000;
1000
- var EDIT_RESULT_READ_LIMIT = 40;
1001
- var MAX_DOCUMENT_OUTPUT_BYTES = 256 * 1024;
1002
- var MAX_DOCUMENT_OUTPUT_LINES = 2000;
1003
- var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
1004
- var IMAGE_MODES = [...VISION_MODES];
1005
- var VISION_ANALYSIS_MODE_VALUES = [...VISION_ANALYSIS_MODES];
1006
- var VISION_LEVEL_VALUES = [...VISION_LEVELS];
1007
- var DOCUMENT_NODE_KIND_VALUES = [...DOCUMENT_NODE_KINDS];
1008
-
1434
+ function recursiveMapNode() {
1435
+ return Type.Recursive((self) => mapNodeShape(self));
1436
+ }
1437
+ var MapNode = typeof Type.Recursive === "function" ? recursiveMapNode() : Type.Cyclic({ MapNode: mapNodeShape(Type.Ref("MapNode")) }, "MapNode");
1438
+ var MapOutput = Type.Object({
1439
+ file: Str,
1440
+ language: Str,
1441
+ line_count: Int,
1442
+ file_hash: Str,
1443
+ root: Type.Optional(MapNode),
1444
+ symbols: Type.Array(SymbolOutput)
1445
+ });
1446
+ var SearchSpan = {
1447
+ start_byte: OptInt,
1448
+ end_byte: OptInt,
1449
+ text: OptStr,
1450
+ start_line: Int,
1451
+ end_line: Int,
1452
+ start_hash: Str,
1453
+ end_hash: Str,
1454
+ hashlines: Hashlines
1455
+ };
1456
+ var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
1457
+ var SearchMatch = Type.Object({
1458
+ pattern_index: Type.Integer({ default: 0 }),
1459
+ ...SearchSpan,
1460
+ captures: Type.Array(SearchCapture)
1461
+ });
1462
+ var SearchFileOutput = Type.Object({
1463
+ file: Str,
1464
+ language: Str,
1465
+ file_hash: Str,
1466
+ matches: Type.Array(SearchMatch)
1467
+ });
1468
+ var SearchEnvelope = Type.Object({
1469
+ results: Type.Array(SearchFileOutput),
1470
+ returned_matches: OptInt,
1471
+ truncated: Type.Optional(Bool),
1472
+ byte_basis: OptStr
1473
+ });
1474
+ var RefsEnvelope = Type.Object({
1475
+ references: Type.Array(Type.Object({
1476
+ file: Str,
1477
+ line: Int,
1478
+ column: Int,
1479
+ line_hash: Str,
1480
+ text: Str,
1481
+ symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
1482
+ }))
1483
+ });
1484
+ var Diagnostic = Type.Object({
1485
+ kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
1486
+ start_line: Int,
1487
+ end_line: Int
1488
+ });
1489
+ var CheckEnvelope = Type.Object({
1490
+ file_hash: OptStr,
1491
+ error_count: Int,
1492
+ missing_count: Int,
1493
+ diagnostics: Type.Array(Diagnostic)
1494
+ });
1495
+ var DetectedObject = Type.Object({
1496
+ label: Str,
1497
+ bbox: Type.Tuple([Int, Int, Int, Int])
1498
+ });
1499
+ var PreparedImage = Type.Object({
1500
+ mime: Str,
1501
+ encoding: Type.Literal("base64"),
1502
+ data: Str
1503
+ });
1504
+ var SourceDetection = Type.Object({
1505
+ kind: Type.Literal("source"),
1506
+ type: Str,
1507
+ file: Str,
1508
+ language: Str,
1509
+ engine: OptStr,
1510
+ supported: Bool,
1511
+ mime: OptStr,
1512
+ syntax: OptStr
1513
+ });
1514
+ var ImageDetection = Type.Object({
1515
+ kind: Type.Literal("image"),
1516
+ type: Str,
1517
+ file: Str,
1518
+ mime: OptStr,
1519
+ format: Str,
1520
+ width: Int,
1521
+ height: Int,
1522
+ animated: Bool,
1523
+ encoding: Type.Optional(Type.Literal("base64")),
1524
+ data: OptStr,
1525
+ ocr: OptStr,
1526
+ caption: OptStr,
1527
+ objects: Type.Optional(Type.Array(DetectedObject))
1528
+ });
1529
+ var TextDetection = Type.Object({
1530
+ kind: Type.Literal("text"),
1531
+ type: Str,
1532
+ file: Str,
1533
+ mime: OptStr
1534
+ });
1535
+ var PdfDetection = Type.Object({
1536
+ kind: Type.Literal("pdf"),
1537
+ type: Type.Literal("application/pdf"),
1538
+ file: Str,
1539
+ mime: OptStr,
1540
+ format: Type.Literal("pdf"),
1541
+ pages: Int
1542
+ });
1543
+ var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
1544
+ var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
1545
+ var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
1546
+ var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
1547
+ var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
1548
+ var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
1549
+ var PdfInspectMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
1550
+ var DocumentCache = Type.Object({
1551
+ content_hash: Str,
1552
+ state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
1553
+ });
1554
+ var Identifier = Type.Object({
1555
+ text: Str,
1556
+ start_column: Int,
1557
+ end_column: Int,
1558
+ start_byte: Int,
1559
+ end_byte: Int
1560
+ });
1561
+ var IdentifyEnvelope = Type.Object({
1562
+ file: Str,
1563
+ language: Str,
1564
+ engine: OptStr,
1565
+ line_count: Int,
1566
+ file_hash: Str,
1567
+ line: Int,
1568
+ column: Int,
1569
+ line_hash: Str,
1570
+ identifier: Type.Optional(Identifier),
1571
+ symbol: Type.Optional(Type.Object({
1572
+ name: Str,
1573
+ kind: Str,
1574
+ qualified_name: Str,
1575
+ start_line: Int,
1576
+ end_line: Int
1577
+ }))
1578
+ });
1579
+ var EditOutputSchema = Type.Object({
1580
+ file: Str,
1581
+ before_hash: Str,
1582
+ file_hash: Str,
1583
+ plan_hash: Str,
1584
+ applied: Bool,
1585
+ changed: Bool,
1586
+ edit_count: Int,
1587
+ before_content: OptStr,
1588
+ content: OptStr
1589
+ });
1590
+ var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
1591
+ var RenameEditSchema = Type.Object({
1592
+ line: Int,
1593
+ start_column: Int,
1594
+ end_column: Int,
1595
+ start_byte: Int,
1596
+ end_byte: Int,
1597
+ occurrence: Str,
1598
+ line_hash: Str,
1599
+ text: Str
1600
+ });
1601
+ var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
1602
+ var Edits = Type.Array(RenameEditSchema, { default: [] });
1603
+ var RenameFileOutputSchema = Type.Object({
1604
+ file: Str,
1605
+ language: Str,
1606
+ engine: OptStr,
1607
+ file_hash: Str,
1608
+ conflicts: Conflicts,
1609
+ edits: Edits
1610
+ });
1611
+ var RenameOutputSchema = Type.Object({
1612
+ file: Str,
1613
+ language: Str,
1614
+ engine: OptStr,
1615
+ file_hash: Str,
1616
+ old_name: Str,
1617
+ new_name: Str,
1618
+ plan_hash: Str,
1619
+ applied: Bool,
1620
+ conflicts: Conflicts,
1621
+ edits: Edits,
1622
+ others: Type.Array(RenameFileOutputSchema, { default: [] })
1623
+ });
1624
+ var DefEnvelope = Type.Object({
1625
+ locations: Type.Array(Type.Object({
1626
+ file: Str,
1627
+ line: Int,
1628
+ column: Int,
1629
+ line_hash: Str,
1630
+ text: Str,
1631
+ kind: OptStr,
1632
+ name: OptStr,
1633
+ qualified_name: OptStr
1634
+ }))
1635
+ });
1009
1636
  // src/util.ts
1010
1637
  import path3 from "node:path";
1011
1638
  function render(value) {
@@ -1041,15 +1668,17 @@ function collectAnchoredFiles(value, files) {
1041
1668
  for (const item of Object.values(output))
1042
1669
  collectAnchoredFiles(item, files);
1043
1670
  }
1044
- function renameFiles(value) {
1671
+ function renameFiles(value, directory = process.cwd()) {
1045
1672
  const output = record(value);
1046
1673
  const files = [];
1047
- if (typeof output.file === "string" && items(output.edits).length > 0)
1048
- files.push(path3.resolve(output.file));
1674
+ if (typeof output.file === "string" && items(output.edits).length > 0) {
1675
+ files.push(path3.resolve(directory, output.file));
1676
+ }
1049
1677
  for (const other of items(output.others)) {
1050
1678
  const item = record(other);
1051
- if (typeof item.file === "string" && items(item.edits).length > 0)
1052
- files.push(path3.resolve(item.file));
1679
+ if (typeof item.file === "string" && items(item.edits).length > 0) {
1680
+ files.push(path3.resolve(directory, item.file));
1681
+ }
1053
1682
  }
1054
1683
  return files;
1055
1684
  }
@@ -1099,25 +1728,21 @@ function resolveImagePolicy(options) {
1099
1728
  return value;
1100
1729
  throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
1101
1730
  }
1102
- function digestArgs(filePath, select, extra = []) {
1103
- return [...buildDigestArgs({ path: filePath, select }), ...extra];
1731
+ function inspectArgs(filePath, select, options = {}) {
1732
+ return buildInspectArgs({ path: filePath, select, ...options });
1104
1733
  }
1105
- function requireDigestFacet(envelope, facet) {
1734
+ function requireInspectFacet(envelope, facet) {
1106
1735
  const value = record(envelope)[facet];
1107
1736
  if (value === undefined || value === null || typeof value !== "object") {
1108
- throw new Error(`digest returned no ${facet} facet`);
1737
+ throw new Error(`inspect returned no ${facet} facet`);
1109
1738
  }
1110
1739
  return value;
1111
1740
  }
1112
- function withAt(args, line, column) {
1113
- args.push("--at", buildLineLocation(line, column));
1114
- }
1115
1741
  function readRangeArgs(filePath, start, end) {
1116
- const extra = [];
1117
- if (start > 1)
1118
- withAt(extra, start);
1119
- extra.push("--end", String(end));
1120
- return digestArgs(filePath, "content", extra);
1742
+ return inspectArgs(filePath, "content", {
1743
+ selector: start > 1 ? `line:${start}` : undefined,
1744
+ end
1745
+ });
1121
1746
  }
1122
1747
  function readSeekBinaryPath() {
1123
1748
  return resolveReadSeekBinaryOverride() ?? resolveReadSeekBinaryFromPackages(resolveFromHost);
@@ -1146,7 +1771,7 @@ async function runReadSeekRaw(context, args, options = {}) {
1146
1771
  }
1147
1772
  async function runReadSeek(context, args, options = {}) {
1148
1773
  context.abort.throwIfAborted();
1149
- return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunJson(readSeekBinaryPath(), args, {
1774
+ return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunKdl(readSeekBinaryPath(), args, {
1150
1775
  cwd: context.directory,
1151
1776
  signal: context.abort,
1152
1777
  cancelable: options.cancelable,
@@ -1154,12 +1779,12 @@ async function runReadSeek(context, args, options = {}) {
1154
1779
  timeoutMs: options.timeoutMs
1155
1780
  }));
1156
1781
  }
1157
- async function runDigestContent(context, args, options = {}) {
1158
- return requireDigestFacet(await runReadSeek(context, args, options), "content");
1782
+ async function runInspectContent(context, args, options = {}) {
1783
+ return requireInspectFacet(await runReadSeek(context, args, options), "content");
1159
1784
  }
1160
1785
  async function postMutationPreview(context, args, filePath, changed) {
1161
1786
  try {
1162
- return await runDigestContent(context, args, {
1787
+ return await runInspectContent(context, args, {
1163
1788
  cancelable: false,
1164
1789
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1165
1790
  });
@@ -1188,21 +1813,11 @@ async function ensureReadSeekCache(context) {
1188
1813
  if (error.code !== "ENOENT")
1189
1814
  throw error;
1190
1815
  }
1191
- readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"]).then(() => cacheDir).finally(() => {
1816
+ readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"], { cancelable: false }).then(() => cacheDir).finally(() => {
1192
1817
  readSeekCacheInit = undefined;
1193
1818
  });
1194
1819
  return readSeekCacheInit;
1195
1820
  }
1196
- function boundDocumentOutput(value) {
1197
- const lines = value.split(`
1198
- `);
1199
- let output = lines.length > MAX_DOCUMENT_OUTPUT_LINES ? lines.slice(0, MAX_DOCUMENT_OUTPUT_LINES).join(`
1200
- `) : value;
1201
- const truncated = output !== value || Buffer.byteLength(output, "utf8") > MAX_DOCUMENT_OUTPUT_BYTES;
1202
- output = utf8Prefix(output, MAX_DOCUMENT_OUTPUT_BYTES);
1203
- return truncated ? `${output}
1204
- [… document view truncated; narrow it with page, node, kind, or depth]` : output;
1205
- }
1206
1821
 
1207
1822
  // src/policy.ts
1208
1823
  var OPENCODE_TOOL_ALIASES = Object.fromEntries(TOOL_NAMES.map((name) => [name, `readseek_${name}`]));
@@ -1247,12 +1862,12 @@ class SessionAnchors {
1247
1862
  planRename(sessionID, output) {
1248
1863
  if (!output || typeof output !== "object")
1249
1864
  return;
1250
- const record2 = output;
1251
- if (record2.applied === true) {
1865
+ const record = output;
1866
+ if (record.applied === true) {
1252
1867
  this.#renamePlans.delete(sessionID);
1253
1868
  return;
1254
1869
  }
1255
- const { file, old_name: oldName, new_name: newName, others } = record2;
1870
+ const { file, old_name: oldName, new_name: newName, others } = record;
1256
1871
  if (typeof file !== "string" || typeof oldName !== "string" || typeof newName !== "string")
1257
1872
  return;
1258
1873
  const files = new Set([path4.resolve(file)]);
@@ -1290,6 +1905,14 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
1290
1905
  import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
1291
1906
  import { tool as tool2 } from "@opencode-ai/plugin";
1292
1907
 
1908
+ // src/constants.ts
1909
+ var DEFAULT_VISION_TIMEOUT_MS = 30 * 60 * 1000;
1910
+ var DEFAULT_READ_LIMIT = 2000;
1911
+ var EDIT_RESULT_READ_LIMIT = 40;
1912
+ var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
1913
+ var IMAGE_MODES = [...VISION_MODES];
1914
+ var VISION_LEVEL_VALUES = [...VISION_LEVELS];
1915
+
1293
1916
  // src/edits.ts
1294
1917
  import { lstat, mkdir, open, readFile } from "node:fs/promises";
1295
1918
  import path5 from "node:path";
@@ -1352,9 +1975,9 @@ async function writeHandle(handle, content) {
1352
1975
  async function writeText(filePath, expected, content) {
1353
1976
  await mkdir(path5.dirname(filePath), { recursive: true });
1354
1977
  if (!expected.exists) {
1355
- let handle2;
1978
+ let handle;
1356
1979
  try {
1357
- handle2 = await open(filePath, "wx+");
1980
+ handle = await open(filePath, "wx+");
1358
1981
  } catch (error) {
1359
1982
  if (error.code === "EEXIST") {
1360
1983
  throw new Error(`refusing to overwrite changed file: ${filePath}`);
@@ -1362,9 +1985,9 @@ async function writeText(filePath, expected, content) {
1362
1985
  throw error;
1363
1986
  }
1364
1987
  try {
1365
- await writeHandle(handle2, content);
1988
+ await writeHandle(handle, content);
1366
1989
  } finally {
1367
- await handle2.close();
1990
+ await handle.close();
1368
1991
  }
1369
1992
  return;
1370
1993
  }
@@ -1608,10 +2231,10 @@ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPat
1608
2231
  import { tool } from "@opencode-ai/plugin";
1609
2232
  function initialTitle(kind, args) {
1610
2233
  switch (kind) {
1611
- case "view":
1612
- return `View ${args.path}`;
1613
- case "digest":
1614
- return `Digest ${args.path}`;
2234
+ case "inspect":
2235
+ return `Inspect ${args.path}`;
2236
+ case "extract":
2237
+ return `Extract ${args.selector} to ${args.output}`;
1615
2238
  case "search":
1616
2239
  return `Search ${args.pattern}`;
1617
2240
  case "grep":
@@ -1631,9 +2254,9 @@ function initialTitle(kind, args) {
1631
2254
  function resultPresentation(kind, args, value) {
1632
2255
  const output = record(value);
1633
2256
  switch (kind) {
1634
- case "view":
1635
- return { title: `Viewed ${args.path}`, metadata: {} };
1636
- case "digest": {
2257
+ case "extract":
2258
+ return { title: `Extracted ${args.selector} to ${args.output}`, metadata: {} };
2259
+ case "inspect": {
1637
2260
  const map = record(output.map);
1638
2261
  const diagnostics = record(output.diagnostics);
1639
2262
  const identity = record(output.identity);
@@ -1666,7 +2289,7 @@ function resultPresentation(kind, args, value) {
1666
2289
  if (typeof content.end_line === "number")
1667
2290
  metadata.end_line = content.end_line;
1668
2291
  return {
1669
- title: summary.length > 0 ? `Digested ${args.path} (${summary.join(", ")})` : `Digested ${args.path}`,
2292
+ title: summary.length > 0 ? `Inspected ${args.path} (${summary.join(", ")})` : `Inspected ${args.path}`,
1670
2293
  metadata
1671
2294
  };
1672
2295
  }
@@ -1706,18 +2329,18 @@ function resultPresentation(kind, args, value) {
1706
2329
  }
1707
2330
  return { title: initialTitle(kind, args), metadata: {} };
1708
2331
  }
1709
- function readseekTool(description, args, kind, execute, formatResult = (result) => ({ output: render(result) })) {
2332
+ function readseekTool(description, args, kind, execute) {
1710
2333
  return tool({
1711
2334
  description,
1712
2335
  args,
1713
- async execute(args2, context) {
1714
- const title = initialTitle(kind, args2);
2336
+ async execute(args, context) {
2337
+ const title = initialTitle(kind, args);
1715
2338
  context.metadata({ title });
1716
- const result = await execute(args2, context);
1717
- const presentation = resultPresentation(kind, args2, result);
2339
+ const result = await execute(args, context);
2340
+ const presentation = resultPresentation(kind, args, result);
1718
2341
  return {
1719
2342
  title: presentation.title,
1720
- ...formatResult(result),
2343
+ output: render(result),
1721
2344
  metadata: presentation.metadata
1722
2345
  };
1723
2346
  }
@@ -1727,52 +2350,18 @@ function readseekTool(description, args, kind, execute, formatResult = (result)
1727
2350
  // src/tools.ts
1728
2351
  function createReadSeekTools(imagePolicy) {
1729
2352
  return {
1730
- readseek_view: readseekTool(openCodeToolDescription("view"), {
1731
- path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1732
- node: tool2.schema.string().min(1).optional().describe(PARAM_DESCRIPTIONS.node),
1733
- page: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.page),
1734
- kind: tool2.schema.enum(DOCUMENT_NODE_KIND_VALUES).optional().describe(PARAM_DESCRIPTIONS.kind),
1735
- depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.depth),
1736
- outline: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.outline),
1737
- visionMode: tool2.schema.enum(VISION_ANALYSIS_MODE_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionModeView),
1738
- visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1739
- }, "view", async (input, context) => {
1740
- const filePath = resolvePath(context.directory, input.path);
1741
- await authorizeRead(context, filePath);
1742
- const visionMode = input.visionMode;
1743
- const visionLevel = input.visionLevel;
1744
- const vision = validateVisionLevelVsMode({
1745
- visionMode,
1746
- visionLevel,
1747
- outline: input.outline
1748
- });
1749
- if (!vision.ok)
1750
- throw new Error(vision.error);
1751
- const cacheDir = await ensureReadSeekCache(context);
1752
- const args = buildViewArgs({
1753
- path: filePath,
1754
- node: input.node,
1755
- page: input.page,
1756
- kind: input.kind,
1757
- depth: input.depth,
1758
- outline: input.outline,
1759
- visionMode,
1760
- visionLevel,
1761
- readseekDir: cacheDir
1762
- });
1763
- return runReadSeekRaw(context, args, visionMode === undefined ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1764
- }, (result) => ({ output: boundDocumentOutput(String(result)) })),
1765
- readseek_digest: readseekTool(openCodeToolDescription("digest"), {
2353
+ readseek_inspect: readseekTool(openCodeToolDescription("inspect"), {
1766
2354
  path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2355
+ selector: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selector),
1767
2356
  select: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selectComma),
1768
- at: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
2357
+ query: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.query),
1769
2358
  end: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.end),
1770
2359
  limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.limit),
1771
- depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.digestDepth),
2360
+ depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.inspectDepth),
1772
2361
  language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageOverride),
1773
- visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeDigest),
2362
+ visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeInspect),
1774
2363
  visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1775
- }, "digest", async (input, context) => {
2364
+ }, "inspect", async (input, context) => {
1776
2365
  const filePath = resolvePath(context.directory, input.path);
1777
2366
  await authorizeRead(context, filePath);
1778
2367
  const select = (input.select ?? "content").trim() || "content";
@@ -1782,13 +2371,14 @@ function createReadSeekTools(imagePolicy) {
1782
2371
  if (!vision.ok)
1783
2372
  throw new Error(vision.error);
1784
2373
  const cacheDir = await ensureReadSeekCache(context);
1785
- const args = buildDigestArgs({
2374
+ const args = buildInspectArgs({
1786
2375
  path: filePath,
2376
+ selector: input.selector,
1787
2377
  select,
1788
- at: input.at,
2378
+ query: input.query,
1789
2379
  end: input.end,
1790
2380
  limit: input.limit,
1791
- depth: input.depth ?? (select.split(",").some((facet) => facet.trim() === "map") ? DEFAULT_DIGEST_MAP_DEPTH : undefined),
2381
+ depth: input.depth ?? (select.split(",").some((facet) => facet.trim() === "map") ? DEFAULT_INSPECT_MAP_DEPTH : undefined),
1792
2382
  language: input.language,
1793
2383
  visionMode,
1794
2384
  visionLevel,
@@ -1796,6 +2386,37 @@ function createReadSeekTools(imagePolicy) {
1796
2386
  });
1797
2387
  return runReadSeek(context, args, visionMode === undefined || visionMode === "none" ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1798
2388
  }),
2389
+ readseek_extract: readseekTool(openCodeToolDescription("extract"), {
2390
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2391
+ selector: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.assetSelector),
2392
+ output: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.output)
2393
+ }, "extract", async (input, context) => {
2394
+ const selector = String(input.selector ?? "").trim();
2395
+ const output = String(input.output ?? "").trim();
2396
+ if (!selector.startsWith("asset:") || selector === "asset:") {
2397
+ throw new Error("extract requires an asset: selector");
2398
+ }
2399
+ if (!output || output === "-")
2400
+ throw new Error("extract output must be a file path");
2401
+ const filePath = resolvePath(context.directory, input.path);
2402
+ const outputPath = resolvePath(context.directory, output);
2403
+ await authorizeRead(context, filePath);
2404
+ await rejectSymlinkMutation(outputPath);
2405
+ await authorizeEdit(context, [outputPath]);
2406
+ const cacheDir = await ensureReadSeekCache(context);
2407
+ await runReadSeekRaw(context, buildExtractArgs({
2408
+ path: filePath,
2409
+ selector,
2410
+ output: outputPath,
2411
+ readseekDir: cacheDir
2412
+ }), { cancelable: false, timeoutMs: 120000 });
2413
+ return {
2414
+ file: filePath,
2415
+ selector,
2416
+ output: outputPath,
2417
+ changed: true
2418
+ };
2419
+ }),
1799
2420
  readseek_grep: readseekTool(openCodeToolDescription("grep"), {
1800
2421
  pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
1801
2422
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
@@ -1868,7 +2489,7 @@ function createReadSeekTools(imagePolicy) {
1868
2489
  }
1869
2490
  const linesByNumber = new Map;
1870
2491
  for (const [start, end] of mergedRanges) {
1871
- const output = record(await runDigestContent(context, readRangeArgs(filePath, start, end)));
2492
+ const output = record(await runInspectContent(context, readRangeArgs(filePath, start, end)));
1872
2493
  for (const hashline of items(output.hashlines)) {
1873
2494
  const line = record(hashline).line;
1874
2495
  if (typeof line === "number" && !linesByNumber.has(line))
@@ -1888,18 +2509,14 @@ function createReadSeekTools(imagePolicy) {
1888
2509
  pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.pattern),
1889
2510
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1890
2511
  language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1891
- cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1892
- others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1893
- ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
2512
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1894
2513
  }, "search", async (input, context) => {
1895
2514
  const target = resolvePath(context.directory, input.path ?? ".");
1896
2515
  const args = buildSearchArgs({
1897
2516
  path: target,
1898
2517
  pattern: input.pattern,
1899
2518
  language: input.language,
1900
- cached: input.cached,
1901
- others: input.others,
1902
- ignored: input.ignored
2519
+ gitScope: input.gitScope
1903
2520
  });
1904
2521
  await authorizeSearch(context, target, input.pattern);
1905
2522
  return runReadSeek(context, args);
@@ -1908,19 +2525,14 @@ function createReadSeekTools(imagePolicy) {
1908
2525
  name: tool2.schema.string().describe(PARAM_DESCRIPTIONS.name),
1909
2526
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1910
2527
  language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1911
- cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1912
- others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1913
- ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
2528
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1914
2529
  }, "def", async (input, context) => {
1915
2530
  const target = resolvePath(context.directory, input.path ?? ".");
1916
2531
  const args = buildDefArgs({
1917
2532
  path: target,
1918
2533
  name: input.name,
1919
2534
  language: input.language,
1920
- format: "plain",
1921
- cached: input.cached,
1922
- others: input.others,
1923
- ignored: input.ignored
2535
+ gitScope: input.gitScope
1924
2536
  });
1925
2537
  await authorizeSearch(context, target, input.name);
1926
2538
  return runReadSeek(context, args);
@@ -1932,9 +2544,7 @@ function createReadSeekTools(imagePolicy) {
1932
2544
  scope: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.scope),
1933
2545
  line: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.line),
1934
2546
  column: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1935
- cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1936
- others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1937
- ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
2547
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1938
2548
  }, "refs", async (input, context) => {
1939
2549
  const target = resolvePath(context.directory, input.path ?? ".");
1940
2550
  const name = input.name;
@@ -1945,9 +2555,7 @@ function createReadSeekTools(imagePolicy) {
1945
2555
  line: input.line,
1946
2556
  column: input.column,
1947
2557
  language: input.language,
1948
- cached: input.cached,
1949
- others: input.others,
1950
- ignored: input.ignored
2558
+ gitScope: input.gitScope
1951
2559
  });
1952
2560
  await authorizeSearch(context, target, name ?? "*");
1953
2561
  return runReadSeek(context, args);
@@ -1963,10 +2571,12 @@ function createReadSeekTools(imagePolicy) {
1963
2571
  const filePath = resolvePath(context.directory, input.path);
1964
2572
  await authorizeRead(context, filePath);
1965
2573
  if (input.workspace) {
1966
- const identifyArgs = digestArgs(filePath, "identity");
1967
- withAt(identifyArgs, input.line, input.column);
2574
+ const column = input.column;
2575
+ const identifyArgs = inspectArgs(filePath, "identity", {
2576
+ selector: `line:${input.line}${column === undefined ? "" : `:${column}`}`
2577
+ });
1968
2578
  const envelope = await runReadSeek(context, identifyArgs);
1969
- const name = identifiedName(requireDigestFacet(envelope, "identity"));
2579
+ const name = identifiedName(requireInspectFacet(envelope, "identity"));
1970
2580
  if (!name)
1971
2581
  throw new Error("readseek could not identify a binding at the rename cursor");
1972
2582
  await authorizeSearch(context, context.directory, name);
@@ -1981,7 +2591,7 @@ function createReadSeekTools(imagePolicy) {
1981
2591
  const plan = await runReadSeek(context, args);
1982
2592
  if (input.apply === false)
1983
2593
  return plan;
1984
- const files = renameFiles(plan);
2594
+ const files = renameFiles(plan, context.directory);
1985
2595
  const planHash = record(plan).plan_hash;
1986
2596
  if (typeof planHash !== "string" || planHash.length === 0) {
1987
2597
  throw new Error("readseek rename plan did not include a plan hash");
@@ -2045,7 +2655,7 @@ function createReadSeekTools(imagePolicy) {
2045
2655
  await authorizeRead(context, filePath);
2046
2656
  await rejectSymlinkMutation(filePath);
2047
2657
  const edits = input.edits;
2048
- const request = JSON.stringify({ edits });
2658
+ const request = serializeEditPlan(edits);
2049
2659
  const baseArgs = buildEditArgs({
2050
2660
  path: filePath,
2051
2661
  language: input.language,
@@ -2097,19 +2707,53 @@ function createReadSeekTools(imagePolicy) {
2097
2707
  await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
2098
2708
  context.abort.throwIfAborted();
2099
2709
  await writeText(filePath, before, content);
2100
- return postMutationPreview(context, digestArgs(filePath, "content", ["--end", String(DEFAULT_READ_LIMIT)]), filePath, !before.exists || before.content !== content);
2710
+ return postMutationPreview(context, inspectArgs(filePath, "content", { end: DEFAULT_READ_LIMIT }), filePath, !before.exists || before.content !== content);
2101
2711
  });
2102
2712
  })
2103
2713
  };
2104
2714
  }
2105
2715
 
2716
+ // v2.ts
2717
+ import { define } from "@opencode-ai/plugin/v2/promise";
2718
+ var GUIDANCE = "For structural code navigation, consult the readseek skill when ReadSeek tools are unavailable and the readseek CLI is installed.";
2719
+ var ReadSeekV2Plugin = define({
2720
+ id: "opencode-readseek",
2721
+ setup: async (ctx) => {
2722
+ await ctx.skill.transform((draft) => {
2723
+ draft.source({
2724
+ type: "embedded",
2725
+ skill: {
2726
+ name: "readseek",
2727
+ description: "Use for structural code navigation and anchored edits when the readseek CLI is installed.",
2728
+ location: "/plugin/opencode-readseek/SKILL.md",
2729
+ content: `# ReadSeek CLI
2730
+
2731
+ 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.
2732
+
2733
+ This V2 plugin supplies guidance only; it does not register readseek_* tools. If the CLI is unavailable, use OpenCode's built-in tools instead.`
2734
+ }
2735
+ });
2736
+ });
2737
+ await ctx.agent.transform((draft) => {
2738
+ for (const agent of draft.list()) {
2739
+ draft.update(agent.id, (item) => {
2740
+ if (!item.system?.includes(GUIDANCE))
2741
+ item.system = [item.system, GUIDANCE].filter(Boolean).join(`
2742
+
2743
+ `);
2744
+ });
2745
+ }
2746
+ });
2747
+ }
2748
+ });
2749
+
2106
2750
  // index.ts
2107
2751
  var ReadSeekPlugin = async (_input, options) => {
2108
2752
  const anchors = new SessionAnchors;
2109
2753
  const imagePolicy = resolveImagePolicy(options);
2110
2754
  return {
2111
2755
  tool: createReadSeekTools(imagePolicy),
2112
- "experimental.chat.system.transform": async (_input2, output) => {
2756
+ "experimental.chat.system.transform": async (_input, output) => {
2113
2757
  output.system.push(TOOL_POLICY);
2114
2758
  },
2115
2759
  "tool.definition": async (input, output) => {
@@ -2155,8 +2799,8 @@ var ReadSeekPlugin = async (_input, options) => {
2155
2799
  }
2156
2800
  };
2157
2801
  };
2158
- var opencode_readseek_default = { id: "opencode-readseek", server: ReadSeekPlugin };
2802
+ var opencode_readseek_default = { ...ReadSeekV2Plugin, server: ReadSeekPlugin };
2159
2803
  export {
2160
- opencode_readseek_default as default,
2161
- ReadSeekPlugin
2804
+ ReadSeekPlugin,
2805
+ opencode_readseek_default as default
2162
2806
  };