opencode-readseek 0.9.5 → 0.9.6

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 (2) hide show
  1. package/dist/index.js +21 -229
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -22,11 +22,10 @@ var DOCUMENT_NODE_KINDS = [
22
22
  "structural_section"
23
23
  ];
24
24
  var TOOL_NAMES = [
25
- "read",
25
+ "digest",
26
26
  "edit",
27
27
  "write",
28
28
  "grep",
29
- "digest",
30
29
  "view",
31
30
  "search",
32
31
  "def",
@@ -103,11 +102,10 @@ var PARAM_DESCRIPTIONS = {
103
102
  scopeContext: "Context lines within each symbol"
104
103
  };
105
104
  var TOOL_DESCRIPTIONS = {
106
- read: "Read anchored text by range or symbol. Standalone images require an explicit mode. Use it before edits to obtain fresh `LINE:HASH` anchors; use `view` for documents and `digest` with the map facet for structural outlines.",
105
+ 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
106
  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":"..."}}`.',
108
107
  write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
109
108
  grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
110
- digest: "Digest source content and images into the native CLI envelope. Returns metadata plus requested facets unchanged from `readseek digest`; use for structural maps, diagnostics, and cursor identity.",
111
109
  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.",
112
110
  search: "Search code by AST structure using ast-grep-style patterns. Results include line hashes usable as `LINE:HASH` anchors. Use `grep` for identifiers, strings, comments, configuration, documentation, or other plain text and regex.",
113
111
  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.",
@@ -115,7 +113,10 @@ var TOOL_DESCRIPTIONS = {
115
113
  rename: "Rename a symbol at a source location. Resolvable same-named bindings are preserved; verified edits apply by default. Set `apply: false` to preview the plan before changing files."
116
114
  };
117
115
  function renderToolDescription(name, aliases = {}) {
118
- return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), TOOL_DESCRIPTIONS[name]);
116
+ const desc = TOOL_DESCRIPTIONS[name];
117
+ if (!desc)
118
+ throw new Error(`Missing TOOL_DESCRIPTIONS for ${name}`);
119
+ return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), desc);
119
120
  }
120
121
  var gitFlagsSchema = {
121
122
  cached: {
@@ -194,25 +195,27 @@ var editItemSchema = {
194
195
  };
195
196
  var TOOL_CONTRACTS = [
196
197
  {
197
- name: "read",
198
- title: "Read anchored text",
199
- description: TOOL_DESCRIPTIONS.read,
198
+ name: "digest",
199
+ title: "Digest source",
200
+ description: TOOL_DESCRIPTIONS.digest,
200
201
  inputSchema: {
201
202
  type: "object",
202
203
  properties: {
203
204
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
204
- offset: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.offset },
205
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.readLimit },
206
- symbol: { type: "string", description: PARAM_DESCRIPTIONS.symbolRead },
207
- bundle: {
205
+ select: { type: "string", description: PARAM_DESCRIPTIONS.select },
206
+ at: { type: "string", description: PARAM_DESCRIPTIONS.at },
207
+ end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
208
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
209
+ language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
210
+ visionMode: {
208
211
  type: "string",
209
- enum: ["local"],
210
- description: PARAM_DESCRIPTIONS.bundle
212
+ enum: [...VISION_MODES],
213
+ description: PARAM_DESCRIPTIONS.visionModeDigest
211
214
  },
212
- image: {
215
+ visionLevel: {
213
216
  type: "string",
214
- enum: [...VISION_MODES],
215
- description: PARAM_DESCRIPTIONS.image
217
+ enum: [...VISION_LEVELS],
218
+ description: PARAM_DESCRIPTIONS.visionLevel
216
219
  }
217
220
  },
218
221
  required: ["path"],
@@ -286,34 +289,6 @@ var TOOL_CONTRACTS = [
286
289
  additionalProperties: false
287
290
  }
288
291
  },
289
- {
290
- name: "digest",
291
- title: "Digest source",
292
- description: TOOL_DESCRIPTIONS.digest,
293
- inputSchema: {
294
- type: "object",
295
- properties: {
296
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
297
- select: { type: "string", description: PARAM_DESCRIPTIONS.select },
298
- at: { type: "string", description: PARAM_DESCRIPTIONS.at },
299
- end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
300
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
301
- language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
302
- visionMode: {
303
- type: "string",
304
- enum: [...VISION_MODES],
305
- description: PARAM_DESCRIPTIONS.visionModeDigest
306
- },
307
- visionLevel: {
308
- type: "string",
309
- enum: [...VISION_LEVELS],
310
- description: PARAM_DESCRIPTIONS.visionLevel
311
- }
312
- },
313
- required: ["path"],
314
- additionalProperties: false
315
- }
316
- },
317
292
  {
318
293
  name: "view",
319
294
  title: "View document",
@@ -774,7 +749,7 @@ var MAX_DOCUMENT_OUTPUT_LINES = 2000;
774
749
  var TOOL_POLICY = [
775
750
  "ReadSeek tool policy:",
776
751
  "- Prefer readseek_* tools over built-ins when they can do the job.",
777
- "- Read with readseek_read first; readseek_edit needs fresh LINE:HASH anchors.",
752
+ "- Read with readseek_digest first; readseek_edit needs fresh LINE:HASH anchors.",
778
753
  "- Use readseek_grep for text/regex, readseek_search for AST patterns, and readseek_digest/def/refs for symbol navigation.",
779
754
  "- Use readseek_edit for existing files, readseek_write for whole-file creation or replacement, and readseek_rename for symbol renames.",
780
755
  "- Do not use built-in read, edit, write, grep, or apply_patch when a ReadSeek tool can do the job.",
@@ -786,7 +761,6 @@ function openCodeToolDescription(name) {
786
761
  return renderToolDescription(name, OPENCODE_TOOL_ALIASES);
787
762
  }
788
763
  var PREFERRED_TOOL_DESCRIPTIONS = {
789
- readseek_read: "Preferred file reader; returns LINE:HASH anchors for durable references.",
790
764
  readseek_edit: "Preferred tool for editing existing text files with fresh LINE:HASH anchors.",
791
765
  readseek_write: "Preferred tool for creating or replacing a complete text file; returns LINE:HASH anchors.",
792
766
  readseek_rename: "Use to rename a symbol with binding-aware, verified edits.",
@@ -798,7 +772,6 @@ var PREFERRED_TOOL_DESCRIPTIONS = {
798
772
  readseek_view: "Use to digest document structure or selected content."
799
773
  };
800
774
  var FALLBACK_TOOL_DESCRIPTIONS = {
801
- read: "Fallback only. Prefer readseek_read so edits can use LINE:HASH anchors.",
802
775
  edit: "Fallback only. Prefer readseek_edit with fresh LINE:HASH anchors.",
803
776
  write: "Fallback only. Prefer readseek_write so the result returns LINE:HASH anchors.",
804
777
  grep: "Fallback only. Prefer readseek_grep for text/regex search with edit-ready anchors.",
@@ -816,46 +789,6 @@ function resolveImagePolicy(options) {
816
789
  return value;
817
790
  throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
818
791
  }
819
- function isVisualFile(value) {
820
- const output = record(value);
821
- const type = output.type;
822
- return typeof output.width === "number" || typeof type === "string" && type.startsWith("image/");
823
- }
824
- function isVisualBoundsError(error) {
825
- if (!(error instanceof Error))
826
- return false;
827
- const message = error.message.replace(/^error:\s*/, "");
828
- return message.startsWith("--end") && /(?:does|do) not apply to images/.test(message);
829
- }
830
- function isDocumentBoundsError(error) {
831
- if (!(error instanceof Error))
832
- return false;
833
- const message = error.message.replace(/^error:\s*/, "");
834
- return /(?:does|do) not apply to (?:documents|PDFs)/.test(message) || /document content belongs to view/.test(message) || /source facets do not apply to documents/.test(message);
835
- }
836
- function isPdfFile(value) {
837
- const output = record(value);
838
- return output.format === "pdf" || output.type === "application/pdf";
839
- }
840
- function documentReceipt(filePath, envelope) {
841
- const recordEnvelope = record(envelope);
842
- return {
843
- file: filePath,
844
- use: "readseek_view",
845
- reason: "document content belongs to readseek_view",
846
- metadata: digestMetadata(envelope),
847
- ...recordEnvelope.cache !== undefined ? { cache: recordEnvelope.cache } : {}
848
- };
849
- }
850
- async function digestDocumentReceipt(context, filePath) {
851
- const cacheDir = await ensureReadSeekCache(context);
852
- const envelope = await runReadSeek(context, [
853
- "--readseek-dir",
854
- cacheDir,
855
- ...digestArgs(filePath, "metadata")
856
- ]);
857
- return documentReceipt(filePath, envelope);
858
- }
859
792
  function digestArgs(filePath, select, extra = []) {
860
793
  return [...buildDigestArgs({ path: filePath, select }), ...extra];
861
794
  }
@@ -866,10 +799,6 @@ function requireDigestFacet(envelope, facet) {
866
799
  }
867
800
  return value;
868
801
  }
869
- function digestMetadata(envelope) {
870
- return record(record(envelope).metadata);
871
- }
872
-
873
802
  class SessionAnchors {
874
803
  #pathsBySession = new Map;
875
804
  #renamePlans = new Map;
@@ -1062,12 +991,6 @@ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPat
1062
991
  function withAt(args, line, column) {
1063
992
  args.push("--at", buildLineLocation(line, column));
1064
993
  }
1065
- function isLikelyImagePath(filePath) {
1066
- return /\.(avif|bmp|gif|heic|heif|ico|jpe?g|png|svg|tiff?|webp)$/i.test(filePath);
1067
- }
1068
- function isLikelyDocumentPath(filePath) {
1069
- return /\.pdf$/i.test(filePath);
1070
- }
1071
994
  function readRangeArgs(filePath, start, end) {
1072
995
  const extra = [];
1073
996
  if (start > 1)
@@ -1366,43 +1289,6 @@ function boundDocumentOutput(value) {
1366
1289
  return truncated ? `${output}
1367
1290
  [… document view truncated; narrow it with page, node, kind, or depth]` : output;
1368
1291
  }
1369
- function attachmentExtension(mime) {
1370
- switch (mime) {
1371
- case "image/gif":
1372
- return "gif";
1373
- case "image/jpeg":
1374
- return "jpg";
1375
- case "image/png":
1376
- return "png";
1377
- case "image/webp":
1378
- return "webp";
1379
- default:
1380
- return "bin";
1381
- }
1382
- }
1383
- function imageAttachment(image, filename) {
1384
- if (image.encoding !== "base64" || typeof image.data !== "string" || typeof image.mime !== "string") {
1385
- return;
1386
- }
1387
- return {
1388
- type: "file",
1389
- mime: image.mime,
1390
- url: `data:${image.mime};base64,${image.data}`,
1391
- filename: `${filename}.${attachmentExtension(image.mime)}`
1392
- };
1393
- }
1394
- function formatReadResult(value) {
1395
- const output = record(value);
1396
- if (output.use === "readseek_view")
1397
- return { output: render(value) };
1398
- const attachment = imageAttachment(output, "image");
1399
- if (!attachment)
1400
- return { output: render(value) };
1401
- const sanitized = { ...output };
1402
- delete sanitized.data;
1403
- delete sanitized.encoding;
1404
- return { output: render(sanitized), attachments: [attachment] };
1405
- }
1406
1292
  function previewDiffLine(prefix, line) {
1407
1293
  const value = `${prefix}${line}`;
1408
1294
  const maxBytes = 256;
@@ -1651,100 +1537,6 @@ var ReadSeekPlugin = async (_input, options) => {
1651
1537
  const imageModes = imagePolicy === "auto" ? ["none", "all", "ocr", "caption", "objects"] : ["all", "ocr", "caption", "objects"];
1652
1538
  return {
1653
1539
  tool: {
1654
- readseek_read: readseekTool(imagePolicy === "off" ? `${openCodeToolDescription("read")} Image reads are disabled.` : `${openCodeToolDescription("read")} Available image modes: ${imageModes.join(", ")}.`, {
1655
- path: tool.schema.string().describe("Path relative to the project directory"),
1656
- offset: tool.schema.number().int().positive().optional().describe("One-based starting line"),
1657
- limit: tool.schema.number().int().positive().optional().describe(`Maximum number of lines; defaults to ${DEFAULT_READ_LIMIT}`),
1658
- language: tool.schema.string().optional().describe("Language override when auto-detection is ambiguous"),
1659
- ...imagePolicy === "off" ? {} : {
1660
- image: tool.schema.enum(imageModes).optional().describe("Standalone image processing mode")
1661
- }
1662
- }, "read", async (input, context) => {
1663
- const filePath = resolvePath(context.directory, input.path);
1664
- await authorizeRead(context, filePath);
1665
- const image = input.image;
1666
- if (imagePolicy === "off" && image !== undefined)
1667
- throw new Error("image reads are disabled");
1668
- if (imagePolicy === "on" && image === "none")
1669
- throw new Error('image="none" requires imageMode="auto"');
1670
- if (isLikelyDocumentPath(filePath)) {
1671
- if (image !== undefined)
1672
- throw new Error("image does not apply to documents; use readseek_view");
1673
- if (input.offset !== undefined || input.limit !== undefined) {
1674
- throw new Error("offset and limit do not apply to documents; use readseek_view");
1675
- }
1676
- if (input.language !== undefined) {
1677
- throw new Error("language does not apply to documents; use readseek_view");
1678
- }
1679
- return digestDocumentReceipt(context, filePath);
1680
- }
1681
- let detectedText = false;
1682
- if (image === undefined && isLikelyImagePath(filePath)) {
1683
- const envelope2 = await runReadSeek(context, digestArgs(filePath, "metadata"));
1684
- const metadata2 = digestMetadata(envelope2);
1685
- if (isPdfFile(metadata2))
1686
- return documentReceipt(filePath, envelope2);
1687
- if (isVisualFile(metadata2)) {
1688
- return { file: filePath, skipped: true, reason: "image mode not selected" };
1689
- }
1690
- detectedText = true;
1691
- }
1692
- const extra = [];
1693
- const wantsImage = !detectedText && (image !== undefined || isLikelyImagePath(filePath));
1694
- if (!wantsImage) {
1695
- const start = input.offset ?? 1;
1696
- const end = start + (input.limit ?? DEFAULT_READ_LIMIT) - 1;
1697
- if (start > 1)
1698
- withAt(extra, start);
1699
- extra.push("--end", String(end));
1700
- if (input.language)
1701
- extra.push("--language", input.language);
1702
- } else {
1703
- if (input.offset !== undefined || input.limit !== undefined) {
1704
- throw new Error("offset and limit do not apply to image reads");
1705
- }
1706
- if (input.language !== undefined) {
1707
- throw new Error("language does not apply to image reads");
1708
- }
1709
- if (image !== undefined)
1710
- extra.push("--vision-mode", image);
1711
- }
1712
- let envelope;
1713
- try {
1714
- envelope = await runReadSeek(context, digestArgs(filePath, "content", extra), image !== undefined && image !== "none" ? { timeoutMs: DEFAULT_VISION_TIMEOUT_MS } : {});
1715
- } catch (error) {
1716
- if (image !== undefined)
1717
- throw error;
1718
- if (isDocumentBoundsError(error)) {
1719
- return digestDocumentReceipt(context, filePath);
1720
- }
1721
- if (isVisualBoundsError(error)) {
1722
- const metadataEnvelope = await runReadSeek(context, digestArgs(filePath, "metadata"));
1723
- if (isPdfFile(digestMetadata(metadataEnvelope))) {
1724
- return documentReceipt(filePath, metadataEnvelope);
1725
- }
1726
- if (isVisualFile(digestMetadata(metadataEnvelope))) {
1727
- return { file: filePath, skipped: true, reason: "image mode not selected" };
1728
- }
1729
- }
1730
- throw error;
1731
- }
1732
- const metadata = digestMetadata(envelope);
1733
- if (isPdfFile(metadata) || record(envelope).cache !== undefined) {
1734
- return documentReceipt(filePath, envelope);
1735
- }
1736
- const visual = isVisualFile(metadata);
1737
- if (visual && image === undefined) {
1738
- return { file: filePath, skipped: true, reason: "image mode not selected" };
1739
- }
1740
- if (visual && (input.offset !== undefined || input.limit !== undefined)) {
1741
- throw new Error("offset and limit do not apply to image reads");
1742
- }
1743
- if (visual && input.language !== undefined) {
1744
- throw new Error("language does not apply to image reads");
1745
- }
1746
- return requireDigestFacet(envelope, "content");
1747
- }, formatReadResult),
1748
1540
  readseek_view: readseekTool(openCodeToolDescription("view"), {
1749
1541
  path: tool.schema.string().describe("Document path relative to the project directory"),
1750
1542
  node: tool.schema.string().min(1).optional().describe(PARAM_DESCRIPTIONS.node),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-readseek",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "OpenCode plugin for anchored file operations and structural code navigation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "node": ">=20.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "@jarkkojs/readseek": "^0.9.5",
36
+ "@jarkkojs/readseek": "^0.9.6",
37
37
  "@opencode-ai/plugin": "^1.17.20"
38
38
  },
39
39
  "devDependencies": {