opencode-readseek 0.9.15 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +15 -6
  3. package/dist/index.js +938 -282
  4. 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,27 +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",
62
+ inspectDepth: "Maximum structural depth for map output; defaults to 1 in agent tools",
66
63
  offset: "One-based starting line",
67
64
  readLimit: "Maximum lines to return",
68
65
  symbolRead: "Symbol name to read",
69
66
  bundle: "Include related symbols from the same file; requires symbol",
70
67
  image: "Standalone image mode: none, all, ocr, caption, objects. Required for standalone images.",
71
- visionModeDigest: "Standalone image mode: none, all, caption, objects, or ocr",
72
- visionModeView: "Analyze selected assets: all, ocr, caption, or objects",
68
+ visionModeInspect: "Standalone image mode: none, all, caption, objects, or ocr",
73
69
  visionLevel: "Vision inference level: low (default), medium, or high",
74
- node: "Node ID to use as the view root",
75
- page: "One-based source page",
76
- kind: "Node kind filter",
77
- depth: "Maximum depth below selected roots",
78
- outline: "Return outline nodes only",
70
+ output: "Destination file for extracted binary data; stdout is not supported",
79
71
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
80
72
  editVariant: "Exactly one nested edit variant",
81
73
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -112,11 +104,11 @@ var PARAM_DESCRIPTIONS = {
112
104
  scopeContext: "Context lines within each symbol"
113
105
  };
114
106
  var TOOL_DESCRIPTIONS = {
115
- 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 for structural maps, diagnostics, and cursor identity.",
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.",
116
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":"..."}}`.',
117
109
  write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
118
110
  grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
119
- 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.",
120
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.",
121
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.",
122
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.",
@@ -128,18 +120,11 @@ function renderToolDescription(name, aliases = {}) {
128
120
  throw new Error(`Missing TOOL_DESCRIPTIONS for ${name}`);
129
121
  return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), desc);
130
122
  }
131
- var gitFlagsSchema = {
132
- cached: {
133
- type: "boolean",
134
- description: PARAM_DESCRIPTIONS.cached
135
- },
136
- others: {
137
- type: "boolean",
138
- description: PARAM_DESCRIPTIONS.others
139
- },
140
- ignored: {
141
- type: "boolean",
142
- description: PARAM_DESCRIPTIONS.ignored
123
+ var gitScopeSchema = {
124
+ gitScope: {
125
+ type: "string",
126
+ enum: GIT_SCOPES,
127
+ description: PARAM_DESCRIPTIONS.gitScope
143
128
  }
144
129
  };
145
130
  var editItemSchema = {
@@ -205,28 +190,31 @@ var editItemSchema = {
205
190
  };
206
191
  var TOOL_CONTRACTS = [
207
192
  {
208
- name: "digest",
209
- title: "Digest source",
210
- description: TOOL_DESCRIPTIONS.digest,
193
+ name: "inspect",
194
+ title: "Inspect input",
195
+ description: TOOL_DESCRIPTIONS.inspect,
211
196
  inputSchema: {
212
197
  type: "object",
213
198
  properties: {
214
199
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
200
+ selector: { type: "string", description: PARAM_DESCRIPTIONS.selector },
215
201
  select: { type: "string", description: PARAM_DESCRIPTIONS.select },
216
- at: { type: "string", description: PARAM_DESCRIPTIONS.at },
202
+ query: { type: "string", description: PARAM_DESCRIPTIONS.query },
217
203
  end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
218
204
  limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
205
+ depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.inspectDepth },
219
206
  language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
220
207
  visionMode: {
221
208
  type: "string",
222
209
  enum: [...VISION_MODES],
223
- description: PARAM_DESCRIPTIONS.visionModeDigest
210
+ description: PARAM_DESCRIPTIONS.visionModeInspect
224
211
  },
225
212
  visionLevel: {
226
213
  type: "string",
227
214
  enum: [...VISION_LEVELS],
228
215
  description: PARAM_DESCRIPTIONS.visionLevel
229
- }
216
+ },
217
+ stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
230
218
  },
231
219
  required: ["path"],
232
220
  additionalProperties: false
@@ -300,34 +288,27 @@ var TOOL_CONTRACTS = [
300
288
  }
301
289
  },
302
290
  {
303
- name: "view",
304
- title: "View document",
305
- description: TOOL_DESCRIPTIONS.view,
291
+ name: "extract",
292
+ title: "Extract asset",
293
+ description: TOOL_DESCRIPTIONS.extract,
306
294
  inputSchema: {
307
295
  type: "object",
308
296
  properties: {
309
297
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
310
- node: { type: "string", description: PARAM_DESCRIPTIONS.node },
311
- page: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.page },
312
- kind: {
298
+ selector: {
313
299
  type: "string",
314
- enum: [...DOCUMENT_NODE_KINDS],
315
- description: PARAM_DESCRIPTIONS.kind
300
+ pattern: "^asset:.+",
301
+ description: PARAM_DESCRIPTIONS.assetSelector
316
302
  },
317
- depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.depth },
318
- outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline },
319
- visionMode: {
303
+ output: {
320
304
  type: "string",
321
- enum: [...VISION_ANALYSIS_MODES],
322
- description: PARAM_DESCRIPTIONS.visionModeView
305
+ minLength: 1,
306
+ pattern: "^(?!-$).+",
307
+ description: PARAM_DESCRIPTIONS.output
323
308
  },
324
- visionLevel: {
325
- type: "string",
326
- enum: [...VISION_LEVELS],
327
- description: PARAM_DESCRIPTIONS.visionLevel
328
- }
309
+ stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
329
310
  },
330
- required: ["path"],
311
+ required: ["path", "selector", "output"],
331
312
  additionalProperties: false
332
313
  }
333
314
  },
@@ -341,7 +322,8 @@ var TOOL_CONTRACTS = [
341
322
  pattern: { type: "string", description: PARAM_DESCRIPTIONS.pattern },
342
323
  path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
343
324
  language: { type: "string", description: PARAM_DESCRIPTIONS.language },
344
- ...gitFlagsSchema
325
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
326
+ ...gitScopeSchema
345
327
  },
346
328
  required: ["pattern"],
347
329
  additionalProperties: false
@@ -357,7 +339,7 @@ var TOOL_CONTRACTS = [
357
339
  name: { type: "string", description: PARAM_DESCRIPTIONS.name },
358
340
  path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
359
341
  language: { type: "string", description: PARAM_DESCRIPTIONS.language },
360
- ...gitFlagsSchema
342
+ ...gitScopeSchema
361
343
  },
362
344
  required: ["name"],
363
345
  additionalProperties: false
@@ -376,7 +358,7 @@ var TOOL_CONTRACTS = [
376
358
  scope: { type: "boolean", description: PARAM_DESCRIPTIONS.scope },
377
359
  line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.line },
378
360
  column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
379
- ...gitFlagsSchema
361
+ ...gitScopeSchema
380
362
  },
381
363
  additionalProperties: false
382
364
  }
@@ -394,7 +376,9 @@ var TOOL_CONTRACTS = [
394
376
  to: { type: "string", description: PARAM_DESCRIPTIONS.to },
395
377
  workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace },
396
378
  apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
397
- 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
398
382
  },
399
383
  required: ["path", "line", "to"],
400
384
  additionalProperties: false
@@ -403,14 +387,14 @@ var TOOL_CONTRACTS = [
403
387
  ];
404
388
  // ../readseek-api/src/policy.ts
405
389
  var BUILTIN_FALLBACKS = {
406
- read: "digest",
390
+ read: "inspect",
407
391
  edit: "edit",
408
392
  write: "write",
409
393
  grep: "grep",
410
394
  apply_patch: "edit"
411
395
  };
412
396
  var PREFERENCE_LEADS = {
413
- digest: "Preferred for reading source with anchors and facets.",
397
+ inspect: "Preferred for reading source with anchors and facets.",
414
398
  edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
415
399
  write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
416
400
  grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
@@ -418,10 +402,10 @@ var PREFERENCE_LEADS = {
418
402
  def: "Preferred for go-to-definition or declaration lookup by name.",
419
403
  refs: "Preferred for finding identifier-name matches or binding-specific references before rename or delete.",
420
404
  rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
421
- view: "Preferred for digesting document structure or selected content."
405
+ extract: "Preferred for saving a selected document asset to a file."
422
406
  };
423
407
  var FALLBACK_TEMPLATES = {
424
- read: "Prefer `digest` with fresh LINE:HASH anchors.",
408
+ read: "Prefer `inspect` with fresh LINE:HASH anchors.",
425
409
  edit: "Prefer `edit` with fresh LINE:HASH anchors.",
426
410
  write: "Prefer `write` so the result returns LINE:HASH anchors.",
427
411
  grep: "Prefer `grep` for text/regex search with edit-ready anchors.",
@@ -444,7 +428,7 @@ function renderFallbackToolDescription(builtIn, aliases = {}) {
444
428
  }
445
429
  function renderToolRoutingPolicy(options = {}) {
446
430
  const aliases = options.aliases ?? {};
447
- const digest = resolveToolName("digest", aliases);
431
+ const inspect = resolveToolName("inspect", aliases);
448
432
  const edit = resolveToolName("edit", aliases);
449
433
  const write = resolveToolName("write", aliases);
450
434
  const grep = resolveToolName("grep", aliases);
@@ -452,11 +436,11 @@ function renderToolRoutingPolicy(options = {}) {
452
436
  const def = resolveToolName("def", aliases);
453
437
  const refs = resolveToolName("refs", aliases);
454
438
  const rename = resolveToolName("rename", aliases);
455
- const view = resolveToolName("view", aliases);
439
+ const extract = resolveToolName("extract", aliases);
456
440
  if (options.style === "server") {
457
441
  return [
458
- "Structural code navigation, document viewing, and hash-verified edits with readseek.",
459
- "For structural questions, prefer these tools to text Grep/Bash: definitions, references, AST search, multi-facet digest (content/map/diagnostics/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.",
460
444
  `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
461
445
  "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
462
446
  "call MCP tools for active navigation, document questions, or anchored edits."
@@ -467,7 +451,7 @@ function renderToolRoutingPolicy(options = {}) {
467
451
  if (options.preferOverBuiltIns) {
468
452
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
469
453
  }
470
- bullets.push(`- Read with ${digest} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, and ${digest}/${def}/${refs} for symbol navigation.`, `- 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.`);
471
455
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
472
456
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
473
457
  }
@@ -502,12 +486,6 @@ function tryParseLineAnchor(anchor) {
502
486
  }
503
487
 
504
488
  // ../readseek-api/src/validation.ts
505
- function validateGitSelection(input) {
506
- if (input.ignored && !input.others) {
507
- return { ok: false, error: "ignored requires others=true" };
508
- }
509
- return { ok: true };
510
- }
511
489
  function validateRefsNameVsScope(input) {
512
490
  if (input.scope) {
513
491
  if (input.line === undefined) {
@@ -524,11 +502,8 @@ function validateRefsNameVsScope(input) {
524
502
  return { ok: true };
525
503
  }
526
504
  function validateVisionLevelVsMode(input) {
527
- if (input.outline && (input.visionMode !== undefined || input.visionLevel !== undefined)) {
528
- return { ok: false, error: "Cannot combine outline with visionMode or visionLevel" };
529
- }
530
- if (input.visionLevel !== undefined && input.visionMode === undefined) {
531
- 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" };
532
507
  }
533
508
  return { ok: true };
534
509
  }
@@ -599,21 +574,54 @@ function validateExactlyOneEditVariant(edits) {
599
574
  }
600
575
  var MIN_SAFE_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
601
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
+ }
602
617
  // ../readseek-api/src/argv.ts
618
+ var DEFAULT_INSPECT_MAP_DEPTH = 1;
603
619
  function buildLineLocation(line, column) {
604
620
  return column === undefined ? `line:${line}` : `line:${line}:${column}`;
605
621
  }
606
- function appendGitSelectionArgs(args, input) {
607
- const validated = validateGitSelection(input);
608
- if (!validated.ok) {
609
- throw new Error(validated.error);
610
- }
611
- if (input.cached)
612
- args.push("--cached");
613
- if (input.others)
614
- args.push("--others");
615
- if (input.ignored)
616
- args.push("--ignored");
622
+ function appendGitScopeArgs(args, input) {
623
+ if (input.gitScope !== undefined)
624
+ args.push("--git-scope", input.gitScope);
617
625
  return args;
618
626
  }
619
627
  function pushOptionalString(args, flag, value) {
@@ -642,51 +650,163 @@ function selectArg(select) {
642
650
  return;
643
651
  return Array.isArray(select) ? select.join(",") : select;
644
652
  }
645
- function buildDigestArgs(input) {
653
+ function selectedInspectFacets(select) {
654
+ const facets = (selectArg(select) ?? "content").split(",").map((facet) => facet.trim());
655
+ for (const facet of facets) {
656
+ if (!INSPECT_FACETS.includes(facet)) {
657
+ throw new Error(`unknown inspect facet: ${facet}`);
658
+ }
659
+ }
660
+ return facets;
661
+ }
662
+ function hasInspectFacet(facets, facet) {
663
+ return facets.includes(facet);
664
+ }
665
+ function validateTarget(command, target, stdinName) {
666
+ if (target.length === 0)
667
+ throw new Error(`${command} requires a target file path or '-'`);
668
+ if (stdinName !== undefined && stdinName.length > 0 && target !== "-") {
669
+ throw new Error("stdinName requires '-' as the target");
670
+ }
671
+ }
672
+ function validatePositiveInteger(value, name) {
673
+ if (!Number.isSafeInteger(value) || value < 1) {
674
+ throw new Error(`${name} must be a positive integer`);
675
+ }
676
+ }
677
+ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
678
+ function isSelectorKind(value) {
679
+ return SELECTOR_KINDS.includes(value);
680
+ }
681
+ function parseSelector(text) {
682
+ const separator = text.indexOf(":");
683
+ if (separator < 0) {
684
+ throw new Error("selector must start with line:, hash:, symbol:, page:, node:, or asset:");
685
+ }
686
+ const kind = text.slice(0, separator);
687
+ const value = text.slice(separator + 1);
688
+ if (value.length === 0)
689
+ throw new Error(`${kind} selector must not be empty`);
690
+ if (!isSelectorKind(kind)) {
691
+ throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
692
+ }
693
+ if (kind === "line" || kind === "hash") {
694
+ const parts = value.split(":");
695
+ if (parts.length > 2)
696
+ throw new Error("selector has too many components");
697
+ if (parts.length === 2) {
698
+ const column = Number(parts[1]);
699
+ if (!Number.isSafeInteger(column))
700
+ throw new Error(`invalid column \`${parts[1]}\``);
701
+ if (column < 1)
702
+ throw new Error("column must be greater than zero");
703
+ return { kind, value: parts[0], column };
704
+ }
705
+ }
706
+ if (kind === "page") {
707
+ if (value.includes(":"))
708
+ throw new Error("page selector has too many components");
709
+ const page = Number(value);
710
+ if (!Number.isSafeInteger(page))
711
+ throw new Error(`invalid page \`${value}\``);
712
+ if (page < 1)
713
+ throw new Error("page must be greater than zero");
714
+ }
715
+ return { kind, value };
716
+ }
717
+ function buildInspectArgs(input) {
718
+ validateTarget("inspect", input.path, input.stdinName);
719
+ if (input.selector !== undefined && input.selector.length === 0) {
720
+ throw new Error("selector must not be empty");
721
+ }
646
722
  const vision = validateVisionLevelVsMode({
647
723
  visionMode: input.visionMode,
648
724
  visionLevel: input.visionLevel
649
725
  });
650
726
  if (!vision.ok)
651
727
  throw new Error(vision.error);
728
+ const facets = selectedInspectFacets(input.select);
729
+ const hasContent = hasInspectFacet(facets, "content");
730
+ const hasIdentity = hasInspectFacet(facets, "identity");
731
+ const hasMap = hasInspectFacet(facets, "map");
732
+ const hasSourceFacet = hasMap || hasInspectFacet(facets, "diagnostics") || hasIdentity;
733
+ if (input.end !== undefined)
734
+ validatePositiveInteger(input.end, "end");
735
+ if (input.limit !== undefined)
736
+ validatePositiveInteger(input.limit, "limit");
737
+ if (input.end !== undefined && input.limit !== undefined) {
738
+ throw new Error("cannot combine end with limit");
739
+ }
740
+ if (input.depth !== undefined) {
741
+ if (!Number.isSafeInteger(input.depth) || input.depth < 0) {
742
+ throw new Error("depth must be a non-negative integer");
743
+ }
744
+ if (!hasMap)
745
+ throw new Error("depth requires the map facet");
746
+ }
747
+ if (input.selector !== undefined && input.query !== undefined) {
748
+ throw new Error("cannot combine selector with query");
749
+ }
750
+ if (!hasContent && (input.end !== undefined || input.limit !== undefined || input.query !== undefined)) {
751
+ throw new Error("ranges and queries require the content facet");
752
+ }
753
+ if (hasIdentity && input.selector === undefined) {
754
+ throw new Error("the identity facet requires a selector");
755
+ }
756
+ if (input.selector !== undefined && !hasContent && !hasIdentity) {
757
+ throw new Error("selectors require the content or identity facet");
758
+ }
759
+ if (input.selector !== undefined) {
760
+ const selector = parseSelector(input.selector);
761
+ if (selector.kind === "symbol" && hasIdentity) {
762
+ throw new Error("the identity facet does not accept symbol selectors");
763
+ }
764
+ if (selector.column !== undefined && !hasIdentity) {
765
+ throw new Error("selector columns require the identity facet");
766
+ }
767
+ }
768
+ const hasVision = input.visionMode !== undefined || input.visionLevel !== undefined;
769
+ if (hasVision && !hasContent) {
770
+ throw new Error("vision options require the content facet");
771
+ }
772
+ if (hasVision && hasSourceFacet) {
773
+ throw new Error("vision options cannot be combined with source facets");
774
+ }
652
775
  const args = [];
653
776
  if (input.readseekDir !== undefined)
654
777
  args.push("--readseek-dir", input.readseekDir);
655
- args.push("digest", input.path);
778
+ args.push("inspect", input.path);
779
+ if (input.selector !== undefined)
780
+ args.push(input.selector);
656
781
  const select = selectArg(input.select);
657
782
  if (select !== undefined)
658
783
  args.push("--select", select);
659
- pushOptionalString(args, "--at", input.at);
784
+ pushOptionalString(args, "--query", input.query);
660
785
  pushOptionalNumber(args, "--end", input.end);
661
786
  pushOptionalNumber(args, "--limit", input.limit);
787
+ pushOptionalNumber(args, "--depth", input.depth);
662
788
  pushOptionalString(args, "--language", input.language);
663
789
  pushOptionalString(args, "--vision-mode", input.visionMode);
664
790
  pushOptionalString(args, "--vision-level", input.visionLevel);
665
791
  pushOptionalString(args, "--stdin-name", input.stdinName);
792
+ args.push("--kdl");
666
793
  return args;
667
794
  }
668
- function buildViewArgs(input) {
669
- const vision = validateVisionLevelVsMode({
670
- visionMode: input.visionMode,
671
- visionLevel: input.visionLevel,
672
- outline: input.outline
673
- });
674
- if (!vision.ok)
675
- 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
+ }
676
805
  const args = [];
677
806
  if (input.readseekDir !== undefined)
678
807
  args.push("--readseek-dir", input.readseekDir);
679
- args.push("view", input.path);
680
- if (input.format !== undefined)
681
- args.push("--format", input.format);
682
- pushOptionalString(args, "--node", input.node);
683
- pushOptionalNumber(args, "--page", input.page);
684
- pushOptionalString(args, "--kind", input.kind);
685
- pushOptionalNumber(args, "--depth", input.depth);
686
- if (input.outline)
687
- args.push("--outline");
688
- pushOptionalString(args, "--vision-mode", input.visionMode);
689
- pushOptionalString(args, "--vision-level", input.visionLevel);
808
+ args.push("--output", input.output, "extract", input.path, input.selector);
809
+ pushOptionalString(args, "--stdin-name", input.stdinName);
690
810
  return args;
691
811
  }
692
812
  function buildSearchArgs(input) {
@@ -694,15 +814,17 @@ function buildSearchArgs(input) {
694
814
  pushOptionalString(args, "--path", input.path);
695
815
  pushOptionalString(args, "--language", input.language);
696
816
  pushOptionalNumber(args, "--limit", input.limit);
697
- return appendGitSelectionArgs(args, input);
817
+ appendGitScopeArgs(args, input);
818
+ args.push("--kdl");
819
+ return args;
698
820
  }
699
821
  function buildDefArgs(input) {
700
822
  const args = ["def", input.name];
701
823
  pushOptionalString(args, "--path", input.path);
702
- if (input.format !== undefined)
703
- args.push("--format", input.format);
704
824
  pushOptionalString(args, "--language", input.language);
705
- return appendGitSelectionArgs(args, input);
825
+ appendGitScopeArgs(args, input);
826
+ args.push("--kdl");
827
+ return args;
706
828
  }
707
829
  function buildRefsArgs(input) {
708
830
  const validated = validateRefsNameVsScope(input);
@@ -717,10 +839,10 @@ function buildRefsArgs(input) {
717
839
  pushOptionalNumber(args, "--line", input.line);
718
840
  pushOptionalNumber(args, "--column", input.column);
719
841
  }
720
- if (input.format !== undefined)
721
- args.push("--format", input.format);
722
842
  pushOptionalString(args, "--language", input.language);
723
- return appendGitSelectionArgs(args, input);
843
+ appendGitScopeArgs(args, input);
844
+ args.push("--kdl");
845
+ return args;
724
846
  }
725
847
  function buildEditArgs(input) {
726
848
  const args = ["edit", input.path];
@@ -732,6 +854,7 @@ function buildEditArgs(input) {
732
854
  pushOptionalString(args, "--language", input.language);
733
855
  if (input.requestFromStdin)
734
856
  args.push("--edits", "-");
857
+ args.push("--kdl");
735
858
  return args;
736
859
  }
737
860
  function buildRenameArgs(input) {
@@ -744,7 +867,9 @@ function buildRenameArgs(input) {
744
867
  flagOrder: input.flagOrder ?? "plan-hash-first"
745
868
  });
746
869
  pushOptionalString(args, "--language", input.language);
747
- return appendGitSelectionArgs(args, input);
870
+ appendGitScopeArgs(args, input);
871
+ args.push("--kdl");
872
+ return args;
748
873
  }
749
874
  // ../readseek-api/src/platform.ts
750
875
  import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
@@ -760,6 +885,8 @@ var READSEEK_PLATFORM_PACKAGES = {
760
885
  };
761
886
  var resolveFromPackage = createRequire(import.meta.url).resolve;
762
887
  function readSeekBinaryName(platform = process.platform) {
888
+ if (platform === "linux")
889
+ return "readseek.AppImage";
763
890
  return platform === "win32" ? "readseek.exe" : "readseek";
764
891
  }
765
892
  function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
@@ -791,9 +918,9 @@ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, option
791
918
  const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
792
919
  if (!platformPackage) {
793
920
  const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
794
- throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
921
+ throw new Error(`@jarkkojs/readseek-api ships no binary for ${key}; it supports ${supported}`);
795
922
  }
796
- const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
923
+ const metaPackage = options.metaPackage ?? "@jarkkojs/readseek-api";
797
924
  const metaPackageJson = resolve(`${metaPackage}/package.json`);
798
925
  const metaPackageDir = path.dirname(metaPackageJson);
799
926
  let packageJsonPath;
@@ -839,22 +966,304 @@ function isSubPath(parent, candidate) {
839
966
  const relative = path2.relative(parent, candidate);
840
967
  return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
841
968
  }
842
- // ../readseek-api/src/json.ts
843
- 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*$/;
844
971
  function cleanReadSeekErrorText(stderr) {
845
972
  return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
846
973
  }
847
- function parseReadSeekJson(stdout) {
848
- try {
849
- return JSON.parse(stdout);
850
- } catch {
851
- const preview = stdout.trim().slice(0, 200);
852
- 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}`);
853
1259
  }
854
1260
  }
855
1261
  // ../readseek-api/src/process.ts
856
1262
  var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
857
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
+ }
858
1267
  function resolveProcessLimits(options = {}) {
859
1268
  return {
860
1269
  timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
@@ -885,8 +1294,8 @@ function ensureSuccessfulStdout(result) {
885
1294
  return result.stdout;
886
1295
  throw failureFromProcessResult(result);
887
1296
  }
888
- function parseSuccessfulJson(result) {
889
- return parseReadSeekJson(ensureSuccessfulStdout(result));
1297
+ function parseSuccessfulKdl(result) {
1298
+ return parseReadSeekKdl(ensureSuccessfulStdout(result));
890
1299
  }
891
1300
  // ../readseek-api/src/process-bun.ts
892
1301
  function getBunSpawn() {
@@ -936,7 +1345,7 @@ async function runReadSeekBun(binaryPath, args, options = {}) {
936
1345
  const stdin = options.stdin === undefined ? undefined : new TextEncoder().encode(options.stdin);
937
1346
  const spawnOptions = {
938
1347
  cwd: options.cwd,
939
- env: process.env,
1348
+ env: readSeekProcessEnvironment(),
940
1349
  stderr: "pipe",
941
1350
  stdin,
942
1351
  stdout: "pipe"
@@ -970,26 +1379,254 @@ async function runReadSeekBun(binaryPath, args, options = {}) {
970
1379
  } finally {
971
1380
  if (timeout !== undefined)
972
1381
  clearTimeout(timeout);
1382
+ pending.catch(() => {
1383
+ return;
1384
+ });
973
1385
  }
974
1386
  }
975
1387
  async function runReadSeekBunText(binaryPath, args, options = {}) {
976
1388
  return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options));
977
1389
  }
978
- async function runReadSeekBunJson(binaryPath, args, options = {}) {
979
- return parseSuccessfulJson(await runReadSeekBun(binaryPath, args, options));
980
- }
981
- // src/constants.ts
982
- var DEFAULT_VISION_TIMEOUT_MS = 30 * 60 * 1000;
983
- var DEFAULT_READ_LIMIT = 2000;
984
- var EDIT_RESULT_READ_LIMIT = 40;
985
- var MAX_DOCUMENT_OUTPUT_BYTES = 256 * 1024;
986
- var MAX_DOCUMENT_OUTPUT_LINES = 2000;
987
- var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
988
- var IMAGE_MODES = [...VISION_MODES];
989
- var VISION_ANALYSIS_MODE_VALUES = [...VISION_ANALYSIS_MODES];
990
- var VISION_LEVEL_VALUES = [...VISION_LEVELS];
991
- var DOCUMENT_NODE_KIND_VALUES = [...DOCUMENT_NODE_KINDS];
992
-
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
+ var MapNode = Type.Recursive((Self) => Type.Object({
1424
+ kind: Str,
1425
+ name: Str,
1426
+ start_line: Int,
1427
+ end_line: Int,
1428
+ start_anchor: Str,
1429
+ end_anchor: Str,
1430
+ children: Type.Array(Self)
1431
+ }));
1432
+ var MapOutput = Type.Object({
1433
+ file: Str,
1434
+ language: Str,
1435
+ line_count: Int,
1436
+ file_hash: Str,
1437
+ root: Type.Optional(MapNode),
1438
+ symbols: Type.Array(SymbolOutput)
1439
+ });
1440
+ var SearchSpan = {
1441
+ start_byte: OptInt,
1442
+ end_byte: OptInt,
1443
+ text: OptStr,
1444
+ start_line: Int,
1445
+ end_line: Int,
1446
+ start_hash: Str,
1447
+ end_hash: Str,
1448
+ hashlines: Hashlines
1449
+ };
1450
+ var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
1451
+ var SearchMatch = Type.Object({
1452
+ pattern_index: Type.Integer({ default: 0 }),
1453
+ ...SearchSpan,
1454
+ captures: Type.Array(SearchCapture)
1455
+ });
1456
+ var SearchFileOutput = Type.Object({
1457
+ file: Str,
1458
+ language: Str,
1459
+ file_hash: Str,
1460
+ matches: Type.Array(SearchMatch)
1461
+ });
1462
+ var SearchEnvelope = Type.Object({
1463
+ results: Type.Array(SearchFileOutput),
1464
+ returned_matches: OptInt,
1465
+ truncated: Type.Optional(Bool),
1466
+ byte_basis: OptStr
1467
+ });
1468
+ var RefsEnvelope = Type.Object({
1469
+ references: Type.Array(Type.Object({
1470
+ file: Str,
1471
+ line: Int,
1472
+ column: Int,
1473
+ line_hash: Str,
1474
+ text: Str,
1475
+ symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
1476
+ }))
1477
+ });
1478
+ var Diagnostic = Type.Object({
1479
+ kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
1480
+ start_line: Int,
1481
+ end_line: Int
1482
+ });
1483
+ var CheckEnvelope = Type.Object({
1484
+ file_hash: OptStr,
1485
+ error_count: Int,
1486
+ missing_count: Int,
1487
+ diagnostics: Type.Array(Diagnostic)
1488
+ });
1489
+ var DetectedObject = Type.Object({
1490
+ label: Str,
1491
+ bbox: Type.Tuple([Int, Int, Int, Int])
1492
+ });
1493
+ var PreparedImage = Type.Object({
1494
+ mime: Str,
1495
+ encoding: Type.Literal("base64"),
1496
+ data: Str
1497
+ });
1498
+ var SourceDetection = Type.Object({
1499
+ kind: Type.Literal("source"),
1500
+ type: Str,
1501
+ file: Str,
1502
+ language: Str,
1503
+ engine: OptStr,
1504
+ supported: Bool,
1505
+ mime: OptStr,
1506
+ syntax: OptStr
1507
+ });
1508
+ var ImageDetection = Type.Object({
1509
+ kind: Type.Literal("image"),
1510
+ type: Str,
1511
+ file: Str,
1512
+ mime: OptStr,
1513
+ format: Str,
1514
+ width: Int,
1515
+ height: Int,
1516
+ animated: Bool,
1517
+ encoding: Type.Optional(Type.Literal("base64")),
1518
+ data: OptStr,
1519
+ ocr: OptStr,
1520
+ caption: OptStr,
1521
+ objects: Type.Optional(Type.Array(DetectedObject))
1522
+ });
1523
+ var TextDetection = Type.Object({
1524
+ kind: Type.Literal("text"),
1525
+ type: Str,
1526
+ file: Str,
1527
+ mime: OptStr
1528
+ });
1529
+ var PdfDetection = Type.Object({
1530
+ kind: Type.Literal("pdf"),
1531
+ type: Type.Literal("application/pdf"),
1532
+ file: Str,
1533
+ mime: OptStr,
1534
+ format: Type.Literal("pdf"),
1535
+ pages: Int
1536
+ });
1537
+ var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
1538
+ var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
1539
+ var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
1540
+ var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
1541
+ var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
1542
+ var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
1543
+ var PdfInspectMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
1544
+ var DocumentCache = Type.Object({
1545
+ content_hash: Str,
1546
+ state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
1547
+ });
1548
+ var Identifier = Type.Object({
1549
+ text: Str,
1550
+ start_column: Int,
1551
+ end_column: Int,
1552
+ start_byte: Int,
1553
+ end_byte: Int
1554
+ });
1555
+ var IdentifyEnvelope = Type.Object({
1556
+ file: Str,
1557
+ language: Str,
1558
+ engine: OptStr,
1559
+ line_count: Int,
1560
+ file_hash: Str,
1561
+ line: Int,
1562
+ column: Int,
1563
+ line_hash: Str,
1564
+ identifier: Type.Optional(Identifier),
1565
+ symbol: Type.Optional(Type.Object({
1566
+ name: Str,
1567
+ kind: Str,
1568
+ qualified_name: Str,
1569
+ start_line: Int,
1570
+ end_line: Int
1571
+ }))
1572
+ });
1573
+ var EditOutputSchema = Type.Object({
1574
+ file: Str,
1575
+ before_hash: Str,
1576
+ file_hash: Str,
1577
+ plan_hash: Str,
1578
+ applied: Bool,
1579
+ changed: Bool,
1580
+ edit_count: Int,
1581
+ before_content: OptStr,
1582
+ content: OptStr
1583
+ });
1584
+ var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
1585
+ var RenameEditSchema = Type.Object({
1586
+ line: Int,
1587
+ start_column: Int,
1588
+ end_column: Int,
1589
+ start_byte: Int,
1590
+ end_byte: Int,
1591
+ occurrence: Str,
1592
+ line_hash: Str,
1593
+ text: Str
1594
+ });
1595
+ var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
1596
+ var Edits = Type.Array(RenameEditSchema, { default: [] });
1597
+ var RenameFileOutputSchema = Type.Object({
1598
+ file: Str,
1599
+ language: Str,
1600
+ engine: OptStr,
1601
+ file_hash: Str,
1602
+ conflicts: Conflicts,
1603
+ edits: Edits
1604
+ });
1605
+ var RenameOutputSchema = Type.Object({
1606
+ file: Str,
1607
+ language: Str,
1608
+ engine: OptStr,
1609
+ file_hash: Str,
1610
+ old_name: Str,
1611
+ new_name: Str,
1612
+ plan_hash: Str,
1613
+ applied: Bool,
1614
+ conflicts: Conflicts,
1615
+ edits: Edits,
1616
+ others: Type.Array(RenameFileOutputSchema, { default: [] })
1617
+ });
1618
+ var DefEnvelope = Type.Object({
1619
+ locations: Type.Array(Type.Object({
1620
+ file: Str,
1621
+ line: Int,
1622
+ column: Int,
1623
+ line_hash: Str,
1624
+ text: Str,
1625
+ kind: OptStr,
1626
+ name: OptStr,
1627
+ qualified_name: OptStr
1628
+ }))
1629
+ });
993
1630
  // src/util.ts
994
1631
  import path3 from "node:path";
995
1632
  function render(value) {
@@ -1025,15 +1662,17 @@ function collectAnchoredFiles(value, files) {
1025
1662
  for (const item of Object.values(output))
1026
1663
  collectAnchoredFiles(item, files);
1027
1664
  }
1028
- function renameFiles(value) {
1665
+ function renameFiles(value, directory = process.cwd()) {
1029
1666
  const output = record(value);
1030
1667
  const files = [];
1031
- if (typeof output.file === "string" && items(output.edits).length > 0)
1032
- files.push(path3.resolve(output.file));
1668
+ if (typeof output.file === "string" && items(output.edits).length > 0) {
1669
+ files.push(path3.resolve(directory, output.file));
1670
+ }
1033
1671
  for (const other of items(output.others)) {
1034
1672
  const item = record(other);
1035
- if (typeof item.file === "string" && items(item.edits).length > 0)
1036
- files.push(path3.resolve(item.file));
1673
+ if (typeof item.file === "string" && items(item.edits).length > 0) {
1674
+ files.push(path3.resolve(directory, item.file));
1675
+ }
1037
1676
  }
1038
1677
  return files;
1039
1678
  }
@@ -1083,25 +1722,21 @@ function resolveImagePolicy(options) {
1083
1722
  return value;
1084
1723
  throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
1085
1724
  }
1086
- function digestArgs(filePath, select, extra = []) {
1087
- return [...buildDigestArgs({ path: filePath, select }), ...extra];
1725
+ function inspectArgs(filePath, select, options = {}) {
1726
+ return buildInspectArgs({ path: filePath, select, ...options });
1088
1727
  }
1089
- function requireDigestFacet(envelope, facet) {
1728
+ function requireInspectFacet(envelope, facet) {
1090
1729
  const value = record(envelope)[facet];
1091
1730
  if (value === undefined || value === null || typeof value !== "object") {
1092
- throw new Error(`digest returned no ${facet} facet`);
1731
+ throw new Error(`inspect returned no ${facet} facet`);
1093
1732
  }
1094
1733
  return value;
1095
1734
  }
1096
- function withAt(args, line, column) {
1097
- args.push("--at", buildLineLocation(line, column));
1098
- }
1099
1735
  function readRangeArgs(filePath, start, end) {
1100
- const extra = [];
1101
- if (start > 1)
1102
- withAt(extra, start);
1103
- extra.push("--end", String(end));
1104
- return digestArgs(filePath, "content", extra);
1736
+ return inspectArgs(filePath, "content", {
1737
+ selector: start > 1 ? `line:${start}` : undefined,
1738
+ end
1739
+ });
1105
1740
  }
1106
1741
  function readSeekBinaryPath() {
1107
1742
  return resolveReadSeekBinaryOverride() ?? resolveReadSeekBinaryFromPackages(resolveFromHost);
@@ -1130,7 +1765,7 @@ async function runReadSeekRaw(context, args, options = {}) {
1130
1765
  }
1131
1766
  async function runReadSeek(context, args, options = {}) {
1132
1767
  context.abort.throwIfAborted();
1133
- return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunJson(readSeekBinaryPath(), args, {
1768
+ return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunKdl(readSeekBinaryPath(), args, {
1134
1769
  cwd: context.directory,
1135
1770
  signal: context.abort,
1136
1771
  cancelable: options.cancelable,
@@ -1138,12 +1773,12 @@ async function runReadSeek(context, args, options = {}) {
1138
1773
  timeoutMs: options.timeoutMs
1139
1774
  }));
1140
1775
  }
1141
- async function runDigestContent(context, args, options = {}) {
1142
- return requireDigestFacet(await runReadSeek(context, args, options), "content");
1776
+ async function runInspectContent(context, args, options = {}) {
1777
+ return requireInspectFacet(await runReadSeek(context, args, options), "content");
1143
1778
  }
1144
1779
  async function postMutationPreview(context, args, filePath, changed) {
1145
1780
  try {
1146
- return await runDigestContent(context, args, {
1781
+ return await runInspectContent(context, args, {
1147
1782
  cancelable: false,
1148
1783
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1149
1784
  });
@@ -1172,21 +1807,11 @@ async function ensureReadSeekCache(context) {
1172
1807
  if (error.code !== "ENOENT")
1173
1808
  throw error;
1174
1809
  }
1175
- readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"]).then(() => cacheDir).finally(() => {
1810
+ readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"], { cancelable: false }).then(() => cacheDir).finally(() => {
1176
1811
  readSeekCacheInit = undefined;
1177
1812
  });
1178
1813
  return readSeekCacheInit;
1179
1814
  }
1180
- function boundDocumentOutput(value) {
1181
- const lines = value.split(`
1182
- `);
1183
- let output = lines.length > MAX_DOCUMENT_OUTPUT_LINES ? lines.slice(0, MAX_DOCUMENT_OUTPUT_LINES).join(`
1184
- `) : value;
1185
- const truncated = output !== value || Buffer.byteLength(output, "utf8") > MAX_DOCUMENT_OUTPUT_BYTES;
1186
- output = utf8Prefix(output, MAX_DOCUMENT_OUTPUT_BYTES);
1187
- return truncated ? `${output}
1188
- [… document view truncated; narrow it with page, node, kind, or depth]` : output;
1189
- }
1190
1815
 
1191
1816
  // src/policy.ts
1192
1817
  var OPENCODE_TOOL_ALIASES = Object.fromEntries(TOOL_NAMES.map((name) => [name, `readseek_${name}`]));
@@ -1231,12 +1856,12 @@ class SessionAnchors {
1231
1856
  planRename(sessionID, output) {
1232
1857
  if (!output || typeof output !== "object")
1233
1858
  return;
1234
- const record2 = output;
1235
- if (record2.applied === true) {
1859
+ const record = output;
1860
+ if (record.applied === true) {
1236
1861
  this.#renamePlans.delete(sessionID);
1237
1862
  return;
1238
1863
  }
1239
- const { file, old_name: oldName, new_name: newName, others } = record2;
1864
+ const { file, old_name: oldName, new_name: newName, others } = record;
1240
1865
  if (typeof file !== "string" || typeof oldName !== "string" || typeof newName !== "string")
1241
1866
  return;
1242
1867
  const files = new Set([path4.resolve(file)]);
@@ -1274,6 +1899,14 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
1274
1899
  import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
1275
1900
  import { tool as tool2 } from "@opencode-ai/plugin";
1276
1901
 
1902
+ // src/constants.ts
1903
+ var DEFAULT_VISION_TIMEOUT_MS = 30 * 60 * 1000;
1904
+ var DEFAULT_READ_LIMIT = 2000;
1905
+ var EDIT_RESULT_READ_LIMIT = 40;
1906
+ var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
1907
+ var IMAGE_MODES = [...VISION_MODES];
1908
+ var VISION_LEVEL_VALUES = [...VISION_LEVELS];
1909
+
1277
1910
  // src/edits.ts
1278
1911
  import { lstat, mkdir, open, readFile } from "node:fs/promises";
1279
1912
  import path5 from "node:path";
@@ -1336,9 +1969,9 @@ async function writeHandle(handle, content) {
1336
1969
  async function writeText(filePath, expected, content) {
1337
1970
  await mkdir(path5.dirname(filePath), { recursive: true });
1338
1971
  if (!expected.exists) {
1339
- let handle2;
1972
+ let handle;
1340
1973
  try {
1341
- handle2 = await open(filePath, "wx+");
1974
+ handle = await open(filePath, "wx+");
1342
1975
  } catch (error) {
1343
1976
  if (error.code === "EEXIST") {
1344
1977
  throw new Error(`refusing to overwrite changed file: ${filePath}`);
@@ -1346,9 +1979,9 @@ async function writeText(filePath, expected, content) {
1346
1979
  throw error;
1347
1980
  }
1348
1981
  try {
1349
- await writeHandle(handle2, content);
1982
+ await writeHandle(handle, content);
1350
1983
  } finally {
1351
- await handle2.close();
1984
+ await handle.close();
1352
1985
  }
1353
1986
  return;
1354
1987
  }
@@ -1592,10 +2225,10 @@ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPat
1592
2225
  import { tool } from "@opencode-ai/plugin";
1593
2226
  function initialTitle(kind, args) {
1594
2227
  switch (kind) {
1595
- case "view":
1596
- return `View ${args.path}`;
1597
- case "digest":
1598
- return `Digest ${args.path}`;
2228
+ case "inspect":
2229
+ return `Inspect ${args.path}`;
2230
+ case "extract":
2231
+ return `Extract ${args.selector} to ${args.output}`;
1599
2232
  case "search":
1600
2233
  return `Search ${args.pattern}`;
1601
2234
  case "grep":
@@ -1615,9 +2248,9 @@ function initialTitle(kind, args) {
1615
2248
  function resultPresentation(kind, args, value) {
1616
2249
  const output = record(value);
1617
2250
  switch (kind) {
1618
- case "view":
1619
- return { title: `Viewed ${args.path}`, metadata: {} };
1620
- case "digest": {
2251
+ case "extract":
2252
+ return { title: `Extracted ${args.selector} to ${args.output}`, metadata: {} };
2253
+ case "inspect": {
1621
2254
  const map = record(output.map);
1622
2255
  const diagnostics = record(output.diagnostics);
1623
2256
  const identity = record(output.identity);
@@ -1650,7 +2283,7 @@ function resultPresentation(kind, args, value) {
1650
2283
  if (typeof content.end_line === "number")
1651
2284
  metadata.end_line = content.end_line;
1652
2285
  return {
1653
- title: summary.length > 0 ? `Digested ${args.path} (${summary.join(", ")})` : `Digested ${args.path}`,
2286
+ title: summary.length > 0 ? `Inspected ${args.path} (${summary.join(", ")})` : `Inspected ${args.path}`,
1654
2287
  metadata
1655
2288
  };
1656
2289
  }
@@ -1690,18 +2323,18 @@ function resultPresentation(kind, args, value) {
1690
2323
  }
1691
2324
  return { title: initialTitle(kind, args), metadata: {} };
1692
2325
  }
1693
- function readseekTool(description, args, kind, execute, formatResult = (result) => ({ output: render(result) })) {
2326
+ function readseekTool(description, args, kind, execute) {
1694
2327
  return tool({
1695
2328
  description,
1696
2329
  args,
1697
- async execute(args2, context) {
1698
- const title = initialTitle(kind, args2);
2330
+ async execute(args, context) {
2331
+ const title = initialTitle(kind, args);
1699
2332
  context.metadata({ title });
1700
- const result = await execute(args2, context);
1701
- const presentation = resultPresentation(kind, args2, result);
2333
+ const result = await execute(args, context);
2334
+ const presentation = resultPresentation(kind, args, result);
1702
2335
  return {
1703
2336
  title: presentation.title,
1704
- ...formatResult(result),
2337
+ output: render(result),
1705
2338
  metadata: presentation.metadata
1706
2339
  };
1707
2340
  }
@@ -1711,51 +2344,18 @@ function readseekTool(description, args, kind, execute, formatResult = (result)
1711
2344
  // src/tools.ts
1712
2345
  function createReadSeekTools(imagePolicy) {
1713
2346
  return {
1714
- readseek_view: readseekTool(openCodeToolDescription("view"), {
1715
- path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1716
- node: tool2.schema.string().min(1).optional().describe(PARAM_DESCRIPTIONS.node),
1717
- page: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.page),
1718
- kind: tool2.schema.enum(DOCUMENT_NODE_KIND_VALUES).optional().describe(PARAM_DESCRIPTIONS.kind),
1719
- depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.depth),
1720
- outline: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.outline),
1721
- visionMode: tool2.schema.enum(VISION_ANALYSIS_MODE_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionModeView),
1722
- visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1723
- }, "view", async (input, context) => {
1724
- const filePath = resolvePath(context.directory, input.path);
1725
- await authorizeRead(context, filePath);
1726
- const visionMode = input.visionMode;
1727
- const visionLevel = input.visionLevel;
1728
- const vision = validateVisionLevelVsMode({
1729
- visionMode,
1730
- visionLevel,
1731
- outline: input.outline
1732
- });
1733
- if (!vision.ok)
1734
- throw new Error(vision.error);
1735
- const cacheDir = await ensureReadSeekCache(context);
1736
- const args = buildViewArgs({
1737
- path: filePath,
1738
- node: input.node,
1739
- page: input.page,
1740
- kind: input.kind,
1741
- depth: input.depth,
1742
- outline: input.outline,
1743
- visionMode,
1744
- visionLevel,
1745
- readseekDir: cacheDir
1746
- });
1747
- return runReadSeekRaw(context, args, visionMode === undefined ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1748
- }, (result) => ({ output: boundDocumentOutput(String(result)) })),
1749
- readseek_digest: readseekTool(openCodeToolDescription("digest"), {
2347
+ readseek_inspect: readseekTool(openCodeToolDescription("inspect"), {
1750
2348
  path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2349
+ selector: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selector),
1751
2350
  select: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selectComma),
1752
- at: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
2351
+ query: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.query),
1753
2352
  end: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.end),
1754
2353
  limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.limit),
2354
+ depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.inspectDepth),
1755
2355
  language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageOverride),
1756
- visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeDigest),
2356
+ visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeInspect),
1757
2357
  visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1758
- }, "digest", async (input, context) => {
2358
+ }, "inspect", async (input, context) => {
1759
2359
  const filePath = resolvePath(context.directory, input.path);
1760
2360
  await authorizeRead(context, filePath);
1761
2361
  const select = (input.select ?? "content").trim() || "content";
@@ -1765,12 +2365,14 @@ function createReadSeekTools(imagePolicy) {
1765
2365
  if (!vision.ok)
1766
2366
  throw new Error(vision.error);
1767
2367
  const cacheDir = await ensureReadSeekCache(context);
1768
- const args = buildDigestArgs({
2368
+ const args = buildInspectArgs({
1769
2369
  path: filePath,
2370
+ selector: input.selector,
1770
2371
  select,
1771
- at: input.at,
2372
+ query: input.query,
1772
2373
  end: input.end,
1773
2374
  limit: input.limit,
2375
+ depth: input.depth ?? (select.split(",").some((facet) => facet.trim() === "map") ? DEFAULT_INSPECT_MAP_DEPTH : undefined),
1774
2376
  language: input.language,
1775
2377
  visionMode,
1776
2378
  visionLevel,
@@ -1778,6 +2380,37 @@ function createReadSeekTools(imagePolicy) {
1778
2380
  });
1779
2381
  return runReadSeek(context, args, visionMode === undefined || visionMode === "none" ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1780
2382
  }),
2383
+ readseek_extract: readseekTool(openCodeToolDescription("extract"), {
2384
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2385
+ selector: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.assetSelector),
2386
+ output: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.output)
2387
+ }, "extract", async (input, context) => {
2388
+ const selector = String(input.selector ?? "").trim();
2389
+ const output = String(input.output ?? "").trim();
2390
+ if (!selector.startsWith("asset:") || selector === "asset:") {
2391
+ throw new Error("extract requires an asset: selector");
2392
+ }
2393
+ if (!output || output === "-")
2394
+ throw new Error("extract output must be a file path");
2395
+ const filePath = resolvePath(context.directory, input.path);
2396
+ const outputPath = resolvePath(context.directory, output);
2397
+ await authorizeRead(context, filePath);
2398
+ await rejectSymlinkMutation(outputPath);
2399
+ await authorizeEdit(context, [outputPath]);
2400
+ const cacheDir = await ensureReadSeekCache(context);
2401
+ await runReadSeekRaw(context, buildExtractArgs({
2402
+ path: filePath,
2403
+ selector,
2404
+ output: outputPath,
2405
+ readseekDir: cacheDir
2406
+ }), { cancelable: false, timeoutMs: 120000 });
2407
+ return {
2408
+ file: filePath,
2409
+ selector,
2410
+ output: outputPath,
2411
+ changed: true
2412
+ };
2413
+ }),
1781
2414
  readseek_grep: readseekTool(openCodeToolDescription("grep"), {
1782
2415
  pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
1783
2416
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
@@ -1850,7 +2483,7 @@ function createReadSeekTools(imagePolicy) {
1850
2483
  }
1851
2484
  const linesByNumber = new Map;
1852
2485
  for (const [start, end] of mergedRanges) {
1853
- const output = record(await runDigestContent(context, readRangeArgs(filePath, start, end)));
2486
+ const output = record(await runInspectContent(context, readRangeArgs(filePath, start, end)));
1854
2487
  for (const hashline of items(output.hashlines)) {
1855
2488
  const line = record(hashline).line;
1856
2489
  if (typeof line === "number" && !linesByNumber.has(line))
@@ -1870,18 +2503,14 @@ function createReadSeekTools(imagePolicy) {
1870
2503
  pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.pattern),
1871
2504
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1872
2505
  language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1873
- cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1874
- others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1875
- ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
2506
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1876
2507
  }, "search", async (input, context) => {
1877
2508
  const target = resolvePath(context.directory, input.path ?? ".");
1878
2509
  const args = buildSearchArgs({
1879
2510
  path: target,
1880
2511
  pattern: input.pattern,
1881
2512
  language: input.language,
1882
- cached: input.cached,
1883
- others: input.others,
1884
- ignored: input.ignored
2513
+ gitScope: input.gitScope
1885
2514
  });
1886
2515
  await authorizeSearch(context, target, input.pattern);
1887
2516
  return runReadSeek(context, args);
@@ -1890,19 +2519,14 @@ function createReadSeekTools(imagePolicy) {
1890
2519
  name: tool2.schema.string().describe(PARAM_DESCRIPTIONS.name),
1891
2520
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1892
2521
  language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1893
- cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1894
- others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1895
- ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
2522
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1896
2523
  }, "def", async (input, context) => {
1897
2524
  const target = resolvePath(context.directory, input.path ?? ".");
1898
2525
  const args = buildDefArgs({
1899
2526
  path: target,
1900
2527
  name: input.name,
1901
2528
  language: input.language,
1902
- format: "plain",
1903
- cached: input.cached,
1904
- others: input.others,
1905
- ignored: input.ignored
2529
+ gitScope: input.gitScope
1906
2530
  });
1907
2531
  await authorizeSearch(context, target, input.name);
1908
2532
  return runReadSeek(context, args);
@@ -1914,9 +2538,7 @@ function createReadSeekTools(imagePolicy) {
1914
2538
  scope: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.scope),
1915
2539
  line: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.line),
1916
2540
  column: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1917
- cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1918
- others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1919
- ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
2541
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1920
2542
  }, "refs", async (input, context) => {
1921
2543
  const target = resolvePath(context.directory, input.path ?? ".");
1922
2544
  const name = input.name;
@@ -1927,9 +2549,7 @@ function createReadSeekTools(imagePolicy) {
1927
2549
  line: input.line,
1928
2550
  column: input.column,
1929
2551
  language: input.language,
1930
- cached: input.cached,
1931
- others: input.others,
1932
- ignored: input.ignored
2552
+ gitScope: input.gitScope
1933
2553
  });
1934
2554
  await authorizeSearch(context, target, name ?? "*");
1935
2555
  return runReadSeek(context, args);
@@ -1945,10 +2565,12 @@ function createReadSeekTools(imagePolicy) {
1945
2565
  const filePath = resolvePath(context.directory, input.path);
1946
2566
  await authorizeRead(context, filePath);
1947
2567
  if (input.workspace) {
1948
- const identifyArgs = digestArgs(filePath, "identity");
1949
- withAt(identifyArgs, input.line, input.column);
2568
+ const column = input.column;
2569
+ const identifyArgs = inspectArgs(filePath, "identity", {
2570
+ selector: `line:${input.line}${column === undefined ? "" : `:${column}`}`
2571
+ });
1950
2572
  const envelope = await runReadSeek(context, identifyArgs);
1951
- const name = identifiedName(requireDigestFacet(envelope, "identity"));
2573
+ const name = identifiedName(requireInspectFacet(envelope, "identity"));
1952
2574
  if (!name)
1953
2575
  throw new Error("readseek could not identify a binding at the rename cursor");
1954
2576
  await authorizeSearch(context, context.directory, name);
@@ -1963,7 +2585,7 @@ function createReadSeekTools(imagePolicy) {
1963
2585
  const plan = await runReadSeek(context, args);
1964
2586
  if (input.apply === false)
1965
2587
  return plan;
1966
- const files = renameFiles(plan);
2588
+ const files = renameFiles(plan, context.directory);
1967
2589
  const planHash = record(plan).plan_hash;
1968
2590
  if (typeof planHash !== "string" || planHash.length === 0) {
1969
2591
  throw new Error("readseek rename plan did not include a plan hash");
@@ -2027,7 +2649,7 @@ function createReadSeekTools(imagePolicy) {
2027
2649
  await authorizeRead(context, filePath);
2028
2650
  await rejectSymlinkMutation(filePath);
2029
2651
  const edits = input.edits;
2030
- const request = JSON.stringify({ edits });
2652
+ const request = serializeEditPlan(edits);
2031
2653
  const baseArgs = buildEditArgs({
2032
2654
  path: filePath,
2033
2655
  language: input.language,
@@ -2079,19 +2701,53 @@ function createReadSeekTools(imagePolicy) {
2079
2701
  await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
2080
2702
  context.abort.throwIfAborted();
2081
2703
  await writeText(filePath, before, content);
2082
- return postMutationPreview(context, digestArgs(filePath, "content", ["--end", String(DEFAULT_READ_LIMIT)]), filePath, !before.exists || before.content !== content);
2704
+ return postMutationPreview(context, inspectArgs(filePath, "content", { end: DEFAULT_READ_LIMIT }), filePath, !before.exists || before.content !== content);
2083
2705
  });
2084
2706
  })
2085
2707
  };
2086
2708
  }
2087
2709
 
2710
+ // v2.ts
2711
+ import { define } from "@opencode-ai/plugin/v2/promise";
2712
+ var GUIDANCE = "For structural code navigation, consult the readseek skill when ReadSeek tools are unavailable and the readseek CLI is installed.";
2713
+ var ReadSeekV2Plugin = define({
2714
+ id: "opencode-readseek",
2715
+ setup: async (ctx) => {
2716
+ await ctx.skill.transform((draft) => {
2717
+ draft.source({
2718
+ type: "embedded",
2719
+ skill: {
2720
+ name: "readseek",
2721
+ description: "Use for structural code navigation and anchored edits when the readseek CLI is installed.",
2722
+ location: "/plugin/opencode-readseek/SKILL.md",
2723
+ content: `# ReadSeek CLI
2724
+
2725
+ When ReadSeek tools are unavailable, use the readseek CLI for structural navigation and anchored edits if it is installed and available on PATH. Run \`readseek --help\` to inspect supported commands before invoking them. Get fresh anchors before editing; re-read after external file changes.
2726
+
2727
+ This V2 plugin supplies guidance only; it does not register readseek_* tools. If the CLI is unavailable, use OpenCode's built-in tools instead.`
2728
+ }
2729
+ });
2730
+ });
2731
+ await ctx.agent.transform((draft) => {
2732
+ for (const agent of draft.list()) {
2733
+ draft.update(agent.id, (item) => {
2734
+ if (!item.system?.includes(GUIDANCE))
2735
+ item.system = [item.system, GUIDANCE].filter(Boolean).join(`
2736
+
2737
+ `);
2738
+ });
2739
+ }
2740
+ });
2741
+ }
2742
+ });
2743
+
2088
2744
  // index.ts
2089
2745
  var ReadSeekPlugin = async (_input, options) => {
2090
2746
  const anchors = new SessionAnchors;
2091
2747
  const imagePolicy = resolveImagePolicy(options);
2092
2748
  return {
2093
2749
  tool: createReadSeekTools(imagePolicy),
2094
- "experimental.chat.system.transform": async (_input2, output) => {
2750
+ "experimental.chat.system.transform": async (_input, output) => {
2095
2751
  output.system.push(TOOL_POLICY);
2096
2752
  },
2097
2753
  "tool.definition": async (input, output) => {
@@ -2137,8 +2793,8 @@ var ReadSeekPlugin = async (_input, options) => {
2137
2793
  }
2138
2794
  };
2139
2795
  };
2140
- var opencode_readseek_default = { id: "opencode-readseek", server: ReadSeekPlugin };
2796
+ var opencode_readseek_default = { ...ReadSeekV2Plugin, server: ReadSeekPlugin };
2141
2797
  export {
2142
- opencode_readseek_default as default,
2143
- ReadSeekPlugin
2798
+ ReadSeekPlugin,
2799
+ opencode_readseek_default as default
2144
2800
  };