opencode-readseek 0.9.16 → 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 (3) hide show
  1. package/README.md +13 -5
  2. package/dist/index.js +929 -291
  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,254 @@ 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));
996
- }
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
-
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
+ });
1009
1630
  // src/util.ts
1010
1631
  import path3 from "node:path";
1011
1632
  function render(value) {
@@ -1041,15 +1662,17 @@ function collectAnchoredFiles(value, files) {
1041
1662
  for (const item of Object.values(output))
1042
1663
  collectAnchoredFiles(item, files);
1043
1664
  }
1044
- function renameFiles(value) {
1665
+ function renameFiles(value, directory = process.cwd()) {
1045
1666
  const output = record(value);
1046
1667
  const files = [];
1047
- if (typeof output.file === "string" && items(output.edits).length > 0)
1048
- 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
+ }
1049
1671
  for (const other of items(output.others)) {
1050
1672
  const item = record(other);
1051
- if (typeof item.file === "string" && items(item.edits).length > 0)
1052
- 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
+ }
1053
1676
  }
1054
1677
  return files;
1055
1678
  }
@@ -1099,25 +1722,21 @@ function resolveImagePolicy(options) {
1099
1722
  return value;
1100
1723
  throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
1101
1724
  }
1102
- function digestArgs(filePath, select, extra = []) {
1103
- return [...buildDigestArgs({ path: filePath, select }), ...extra];
1725
+ function inspectArgs(filePath, select, options = {}) {
1726
+ return buildInspectArgs({ path: filePath, select, ...options });
1104
1727
  }
1105
- function requireDigestFacet(envelope, facet) {
1728
+ function requireInspectFacet(envelope, facet) {
1106
1729
  const value = record(envelope)[facet];
1107
1730
  if (value === undefined || value === null || typeof value !== "object") {
1108
- throw new Error(`digest returned no ${facet} facet`);
1731
+ throw new Error(`inspect returned no ${facet} facet`);
1109
1732
  }
1110
1733
  return value;
1111
1734
  }
1112
- function withAt(args, line, column) {
1113
- args.push("--at", buildLineLocation(line, column));
1114
- }
1115
1735
  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);
1736
+ return inspectArgs(filePath, "content", {
1737
+ selector: start > 1 ? `line:${start}` : undefined,
1738
+ end
1739
+ });
1121
1740
  }
1122
1741
  function readSeekBinaryPath() {
1123
1742
  return resolveReadSeekBinaryOverride() ?? resolveReadSeekBinaryFromPackages(resolveFromHost);
@@ -1146,7 +1765,7 @@ async function runReadSeekRaw(context, args, options = {}) {
1146
1765
  }
1147
1766
  async function runReadSeek(context, args, options = {}) {
1148
1767
  context.abort.throwIfAborted();
1149
- return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunJson(readSeekBinaryPath(), args, {
1768
+ return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunKdl(readSeekBinaryPath(), args, {
1150
1769
  cwd: context.directory,
1151
1770
  signal: context.abort,
1152
1771
  cancelable: options.cancelable,
@@ -1154,12 +1773,12 @@ async function runReadSeek(context, args, options = {}) {
1154
1773
  timeoutMs: options.timeoutMs
1155
1774
  }));
1156
1775
  }
1157
- async function runDigestContent(context, args, options = {}) {
1158
- return requireDigestFacet(await runReadSeek(context, args, options), "content");
1776
+ async function runInspectContent(context, args, options = {}) {
1777
+ return requireInspectFacet(await runReadSeek(context, args, options), "content");
1159
1778
  }
1160
1779
  async function postMutationPreview(context, args, filePath, changed) {
1161
1780
  try {
1162
- return await runDigestContent(context, args, {
1781
+ return await runInspectContent(context, args, {
1163
1782
  cancelable: false,
1164
1783
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1165
1784
  });
@@ -1188,21 +1807,11 @@ async function ensureReadSeekCache(context) {
1188
1807
  if (error.code !== "ENOENT")
1189
1808
  throw error;
1190
1809
  }
1191
- readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"]).then(() => cacheDir).finally(() => {
1810
+ readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"], { cancelable: false }).then(() => cacheDir).finally(() => {
1192
1811
  readSeekCacheInit = undefined;
1193
1812
  });
1194
1813
  return readSeekCacheInit;
1195
1814
  }
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
1815
 
1207
1816
  // src/policy.ts
1208
1817
  var OPENCODE_TOOL_ALIASES = Object.fromEntries(TOOL_NAMES.map((name) => [name, `readseek_${name}`]));
@@ -1247,12 +1856,12 @@ class SessionAnchors {
1247
1856
  planRename(sessionID, output) {
1248
1857
  if (!output || typeof output !== "object")
1249
1858
  return;
1250
- const record2 = output;
1251
- if (record2.applied === true) {
1859
+ const record = output;
1860
+ if (record.applied === true) {
1252
1861
  this.#renamePlans.delete(sessionID);
1253
1862
  return;
1254
1863
  }
1255
- const { file, old_name: oldName, new_name: newName, others } = record2;
1864
+ const { file, old_name: oldName, new_name: newName, others } = record;
1256
1865
  if (typeof file !== "string" || typeof oldName !== "string" || typeof newName !== "string")
1257
1866
  return;
1258
1867
  const files = new Set([path4.resolve(file)]);
@@ -1290,6 +1899,14 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
1290
1899
  import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
1291
1900
  import { tool as tool2 } from "@opencode-ai/plugin";
1292
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
+
1293
1910
  // src/edits.ts
1294
1911
  import { lstat, mkdir, open, readFile } from "node:fs/promises";
1295
1912
  import path5 from "node:path";
@@ -1352,9 +1969,9 @@ async function writeHandle(handle, content) {
1352
1969
  async function writeText(filePath, expected, content) {
1353
1970
  await mkdir(path5.dirname(filePath), { recursive: true });
1354
1971
  if (!expected.exists) {
1355
- let handle2;
1972
+ let handle;
1356
1973
  try {
1357
- handle2 = await open(filePath, "wx+");
1974
+ handle = await open(filePath, "wx+");
1358
1975
  } catch (error) {
1359
1976
  if (error.code === "EEXIST") {
1360
1977
  throw new Error(`refusing to overwrite changed file: ${filePath}`);
@@ -1362,9 +1979,9 @@ async function writeText(filePath, expected, content) {
1362
1979
  throw error;
1363
1980
  }
1364
1981
  try {
1365
- await writeHandle(handle2, content);
1982
+ await writeHandle(handle, content);
1366
1983
  } finally {
1367
- await handle2.close();
1984
+ await handle.close();
1368
1985
  }
1369
1986
  return;
1370
1987
  }
@@ -1608,10 +2225,10 @@ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPat
1608
2225
  import { tool } from "@opencode-ai/plugin";
1609
2226
  function initialTitle(kind, args) {
1610
2227
  switch (kind) {
1611
- case "view":
1612
- return `View ${args.path}`;
1613
- case "digest":
1614
- return `Digest ${args.path}`;
2228
+ case "inspect":
2229
+ return `Inspect ${args.path}`;
2230
+ case "extract":
2231
+ return `Extract ${args.selector} to ${args.output}`;
1615
2232
  case "search":
1616
2233
  return `Search ${args.pattern}`;
1617
2234
  case "grep":
@@ -1631,9 +2248,9 @@ function initialTitle(kind, args) {
1631
2248
  function resultPresentation(kind, args, value) {
1632
2249
  const output = record(value);
1633
2250
  switch (kind) {
1634
- case "view":
1635
- return { title: `Viewed ${args.path}`, metadata: {} };
1636
- case "digest": {
2251
+ case "extract":
2252
+ return { title: `Extracted ${args.selector} to ${args.output}`, metadata: {} };
2253
+ case "inspect": {
1637
2254
  const map = record(output.map);
1638
2255
  const diagnostics = record(output.diagnostics);
1639
2256
  const identity = record(output.identity);
@@ -1666,7 +2283,7 @@ function resultPresentation(kind, args, value) {
1666
2283
  if (typeof content.end_line === "number")
1667
2284
  metadata.end_line = content.end_line;
1668
2285
  return {
1669
- 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}`,
1670
2287
  metadata
1671
2288
  };
1672
2289
  }
@@ -1706,18 +2323,18 @@ function resultPresentation(kind, args, value) {
1706
2323
  }
1707
2324
  return { title: initialTitle(kind, args), metadata: {} };
1708
2325
  }
1709
- function readseekTool(description, args, kind, execute, formatResult = (result) => ({ output: render(result) })) {
2326
+ function readseekTool(description, args, kind, execute) {
1710
2327
  return tool({
1711
2328
  description,
1712
2329
  args,
1713
- async execute(args2, context) {
1714
- const title = initialTitle(kind, args2);
2330
+ async execute(args, context) {
2331
+ const title = initialTitle(kind, args);
1715
2332
  context.metadata({ title });
1716
- const result = await execute(args2, context);
1717
- const presentation = resultPresentation(kind, args2, result);
2333
+ const result = await execute(args, context);
2334
+ const presentation = resultPresentation(kind, args, result);
1718
2335
  return {
1719
2336
  title: presentation.title,
1720
- ...formatResult(result),
2337
+ output: render(result),
1721
2338
  metadata: presentation.metadata
1722
2339
  };
1723
2340
  }
@@ -1727,52 +2344,18 @@ function readseekTool(description, args, kind, execute, formatResult = (result)
1727
2344
  // src/tools.ts
1728
2345
  function createReadSeekTools(imagePolicy) {
1729
2346
  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"), {
2347
+ readseek_inspect: readseekTool(openCodeToolDescription("inspect"), {
1766
2348
  path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
2349
+ selector: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selector),
1767
2350
  select: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selectComma),
1768
- at: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
2351
+ query: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.query),
1769
2352
  end: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.end),
1770
2353
  limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.limit),
1771
- depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.digestDepth),
2354
+ depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.inspectDepth),
1772
2355
  language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageOverride),
1773
- visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeDigest),
2356
+ visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeInspect),
1774
2357
  visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1775
- }, "digest", async (input, context) => {
2358
+ }, "inspect", async (input, context) => {
1776
2359
  const filePath = resolvePath(context.directory, input.path);
1777
2360
  await authorizeRead(context, filePath);
1778
2361
  const select = (input.select ?? "content").trim() || "content";
@@ -1782,13 +2365,14 @@ function createReadSeekTools(imagePolicy) {
1782
2365
  if (!vision.ok)
1783
2366
  throw new Error(vision.error);
1784
2367
  const cacheDir = await ensureReadSeekCache(context);
1785
- const args = buildDigestArgs({
2368
+ const args = buildInspectArgs({
1786
2369
  path: filePath,
2370
+ selector: input.selector,
1787
2371
  select,
1788
- at: input.at,
2372
+ query: input.query,
1789
2373
  end: input.end,
1790
2374
  limit: input.limit,
1791
- depth: input.depth ?? (select.split(",").some((facet) => facet.trim() === "map") ? DEFAULT_DIGEST_MAP_DEPTH : undefined),
2375
+ depth: input.depth ?? (select.split(",").some((facet) => facet.trim() === "map") ? DEFAULT_INSPECT_MAP_DEPTH : undefined),
1792
2376
  language: input.language,
1793
2377
  visionMode,
1794
2378
  visionLevel,
@@ -1796,6 +2380,37 @@ function createReadSeekTools(imagePolicy) {
1796
2380
  });
1797
2381
  return runReadSeek(context, args, visionMode === undefined || visionMode === "none" ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1798
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
+ }),
1799
2414
  readseek_grep: readseekTool(openCodeToolDescription("grep"), {
1800
2415
  pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
1801
2416
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
@@ -1868,7 +2483,7 @@ function createReadSeekTools(imagePolicy) {
1868
2483
  }
1869
2484
  const linesByNumber = new Map;
1870
2485
  for (const [start, end] of mergedRanges) {
1871
- const output = record(await runDigestContent(context, readRangeArgs(filePath, start, end)));
2486
+ const output = record(await runInspectContent(context, readRangeArgs(filePath, start, end)));
1872
2487
  for (const hashline of items(output.hashlines)) {
1873
2488
  const line = record(hashline).line;
1874
2489
  if (typeof line === "number" && !linesByNumber.has(line))
@@ -1888,18 +2503,14 @@ function createReadSeekTools(imagePolicy) {
1888
2503
  pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.pattern),
1889
2504
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1890
2505
  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)
2506
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1894
2507
  }, "search", async (input, context) => {
1895
2508
  const target = resolvePath(context.directory, input.path ?? ".");
1896
2509
  const args = buildSearchArgs({
1897
2510
  path: target,
1898
2511
  pattern: input.pattern,
1899
2512
  language: input.language,
1900
- cached: input.cached,
1901
- others: input.others,
1902
- ignored: input.ignored
2513
+ gitScope: input.gitScope
1903
2514
  });
1904
2515
  await authorizeSearch(context, target, input.pattern);
1905
2516
  return runReadSeek(context, args);
@@ -1908,19 +2519,14 @@ function createReadSeekTools(imagePolicy) {
1908
2519
  name: tool2.schema.string().describe(PARAM_DESCRIPTIONS.name),
1909
2520
  path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1910
2521
  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)
2522
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1914
2523
  }, "def", async (input, context) => {
1915
2524
  const target = resolvePath(context.directory, input.path ?? ".");
1916
2525
  const args = buildDefArgs({
1917
2526
  path: target,
1918
2527
  name: input.name,
1919
2528
  language: input.language,
1920
- format: "plain",
1921
- cached: input.cached,
1922
- others: input.others,
1923
- ignored: input.ignored
2529
+ gitScope: input.gitScope
1924
2530
  });
1925
2531
  await authorizeSearch(context, target, input.name);
1926
2532
  return runReadSeek(context, args);
@@ -1932,9 +2538,7 @@ function createReadSeekTools(imagePolicy) {
1932
2538
  scope: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.scope),
1933
2539
  line: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.line),
1934
2540
  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)
2541
+ gitScope: tool2.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
1938
2542
  }, "refs", async (input, context) => {
1939
2543
  const target = resolvePath(context.directory, input.path ?? ".");
1940
2544
  const name = input.name;
@@ -1945,9 +2549,7 @@ function createReadSeekTools(imagePolicy) {
1945
2549
  line: input.line,
1946
2550
  column: input.column,
1947
2551
  language: input.language,
1948
- cached: input.cached,
1949
- others: input.others,
1950
- ignored: input.ignored
2552
+ gitScope: input.gitScope
1951
2553
  });
1952
2554
  await authorizeSearch(context, target, name ?? "*");
1953
2555
  return runReadSeek(context, args);
@@ -1963,10 +2565,12 @@ function createReadSeekTools(imagePolicy) {
1963
2565
  const filePath = resolvePath(context.directory, input.path);
1964
2566
  await authorizeRead(context, filePath);
1965
2567
  if (input.workspace) {
1966
- const identifyArgs = digestArgs(filePath, "identity");
1967
- 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
+ });
1968
2572
  const envelope = await runReadSeek(context, identifyArgs);
1969
- const name = identifiedName(requireDigestFacet(envelope, "identity"));
2573
+ const name = identifiedName(requireInspectFacet(envelope, "identity"));
1970
2574
  if (!name)
1971
2575
  throw new Error("readseek could not identify a binding at the rename cursor");
1972
2576
  await authorizeSearch(context, context.directory, name);
@@ -1981,7 +2585,7 @@ function createReadSeekTools(imagePolicy) {
1981
2585
  const plan = await runReadSeek(context, args);
1982
2586
  if (input.apply === false)
1983
2587
  return plan;
1984
- const files = renameFiles(plan);
2588
+ const files = renameFiles(plan, context.directory);
1985
2589
  const planHash = record(plan).plan_hash;
1986
2590
  if (typeof planHash !== "string" || planHash.length === 0) {
1987
2591
  throw new Error("readseek rename plan did not include a plan hash");
@@ -2045,7 +2649,7 @@ function createReadSeekTools(imagePolicy) {
2045
2649
  await authorizeRead(context, filePath);
2046
2650
  await rejectSymlinkMutation(filePath);
2047
2651
  const edits = input.edits;
2048
- const request = JSON.stringify({ edits });
2652
+ const request = serializeEditPlan(edits);
2049
2653
  const baseArgs = buildEditArgs({
2050
2654
  path: filePath,
2051
2655
  language: input.language,
@@ -2097,19 +2701,53 @@ function createReadSeekTools(imagePolicy) {
2097
2701
  await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
2098
2702
  context.abort.throwIfAborted();
2099
2703
  await writeText(filePath, before, content);
2100
- 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);
2101
2705
  });
2102
2706
  })
2103
2707
  };
2104
2708
  }
2105
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
+
2106
2744
  // index.ts
2107
2745
  var ReadSeekPlugin = async (_input, options) => {
2108
2746
  const anchors = new SessionAnchors;
2109
2747
  const imagePolicy = resolveImagePolicy(options);
2110
2748
  return {
2111
2749
  tool: createReadSeekTools(imagePolicy),
2112
- "experimental.chat.system.transform": async (_input2, output) => {
2750
+ "experimental.chat.system.transform": async (_input, output) => {
2113
2751
  output.system.push(TOOL_POLICY);
2114
2752
  },
2115
2753
  "tool.definition": async (input, output) => {
@@ -2155,8 +2793,8 @@ var ReadSeekPlugin = async (_input, options) => {
2155
2793
  }
2156
2794
  };
2157
2795
  };
2158
- var opencode_readseek_default = { id: "opencode-readseek", server: ReadSeekPlugin };
2796
+ var opencode_readseek_default = { ...ReadSeekV2Plugin, server: ReadSeekPlugin };
2159
2797
  export {
2160
- opencode_readseek_default as default,
2161
- ReadSeekPlugin
2798
+ ReadSeekPlugin,
2799
+ opencode_readseek_default as default
2162
2800
  };