pi-readseek 0.9.10 → 0.9.11

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 -9
  2. package/dist/index.ts +122 -47
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  `pi-readseek` adds ReadSeek's anchored file operations, structural search,
4
4
  symbol navigation, and document tools to Pi. Built-ins remain available unless
5
- `overrideTools` maps `edit`, `write`, or `grep` to a ReadSeek-backed
6
- implementation.
5
+ `overrideTools` maps `read`, `edit`, `write`, or `grep` to a ReadSeek-backed
6
+ implementation. The `read` override runs ReadSeek's `digest` command; it does not
7
+ add a separate `readSeek_read` tool.
7
8
 
8
9
  ## Install
9
10
 
@@ -12,8 +13,9 @@ pi install npm:pi-readseek
12
13
  ```
13
14
 
14
15
  The package depends on `@jarkkojs/readseek`, which supplies the native binary on
15
- supported platforms. A user-managed `readseek` on `PATH` (for example a
16
- system-package or developer install) takes precedence over the bundled binary.
16
+ supported platforms. The extension uses that packaged binary exclusively; an
17
+ unresolved binary indicates a broken installation rather than falling back to an
18
+ unrelated `readseek` executable on `PATH`.
17
19
 
18
20
  ## Tools
19
21
 
@@ -58,11 +60,13 @@ Defaults:
58
60
  }
59
61
  ```
60
62
 
61
- - `overrideTools`: any of `"edit"`, `"write"`, and `"grep"`.
62
- - `imageMode`: `"auto"` uses Pi's native image input when available, `"on"`
63
- always runs local analysis, and `"off"` skips standalone images. Image modes
64
- are `all`, `ocr`, `caption`, and `objects`; in `"auto"`, `none` returns a
65
- prepared image without local analysis. Documents use `readSeek_view` with
63
+ - `overrideTools`: any of `"read"`, `"edit"`, `"write"`, and `"grep"`.
64
+ - `imageMode`: controls standalone images when `digest` overrides `read`.
65
+ `"auto"` passes the prepared image to image-capable Pi models and otherwise
66
+ runs local analysis, `"on"` always runs local analysis, and `"off"` skips the
67
+ image. Local analysis defaults to `all` and also supports `ocr`, `caption`, and
68
+ `objects` through `visionMode`. The namespaced `readSeek_digest` tool applies
69
+ `visionMode` directly, including `none`. Documents use `readSeek_view` with
66
70
  optional `visionMode` and `visionLevel`. Local analysis uses
67
71
  [Qwen3-VL-2B-Instruct](../../docs/models/qwen3-vl-2b-instruct.md).
68
72
  - `syntaxValidation`: `"warn"` (write and warn), `"block"` (abort), or `"off"`.
package/dist/index.ts CHANGED
@@ -403,6 +403,11 @@ var TOOL_CONTRACTS = [
403
403
  ];
404
404
  // ../readseek-api/src/policy.ts
405
405
  var REPLACEABLE_TOOLS = [
406
+ {
407
+ tool: "digest",
408
+ builtIn: "read",
409
+ benefit: "it returns LINE:HASH anchors for safe edits."
410
+ },
406
411
  {
407
412
  tool: "edit",
408
413
  builtIn: "edit",
@@ -685,23 +690,17 @@ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, option
685
690
  const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
686
691
  const metaPackageJson = resolve(`${metaPackage}/package.json`);
687
692
  const metaPackageDir = path.dirname(metaPackageJson);
688
- const packageJsonPath = resolve(`${platformPackage}/package.json`, {
689
- paths: [metaPackageDir]
690
- });
691
- return path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
692
- }
693
- function resolveReadSeekBinaryFromPath(pathEnv = process.env.PATH) {
694
- const name = readSeekBinaryName();
695
- for (const dir of (pathEnv ?? "").split(path.delimiter)) {
696
- if (!dir)
697
- continue;
698
- const candidate = path.join(dir, name);
699
- try {
700
- accessSync(candidate, fsConstants.X_OK);
701
- return candidate;
702
- } catch {}
693
+ let packageJsonPath;
694
+ try {
695
+ packageJsonPath = resolve(`${platformPackage}/package.json`);
696
+ } catch {
697
+ packageJsonPath = resolve(`${platformPackage}/package.json`, {
698
+ paths: [metaPackageDir]
699
+ });
703
700
  }
704
- return null;
701
+ const binaryPath = path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
702
+ accessSync(binaryPath, fsConstants.X_OK);
703
+ return binaryPath;
705
704
  }
706
705
  // ../readseek-api/src/json.ts
707
706
  var READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
@@ -2150,6 +2149,7 @@ import { readFileSync, statSync } from "node:fs";
2150
2149
  import { homedir as homedir2 } from "node:os";
2151
2150
  import { join } from "node:path";
2152
2151
  var READSEEK_TOOL_REPLACEMENTS = {
2152
+ read: "readSeek_digest",
2153
2153
  edit: "readSeek_edit",
2154
2154
  grep: "readSeek_grep",
2155
2155
  write: "readSeek_write"
@@ -2346,6 +2346,9 @@ function resolveReadSeekJsonSettings() {
2346
2346
  warnings: [...globalResult.warnings, ...projectResult.warnings]
2347
2347
  };
2348
2348
  }
2349
+ function resolveReadSeekImageMode() {
2350
+ return resolveReadSeekJsonSettings().settings.imageMode ?? "auto";
2351
+ }
2349
2352
  function resolveReadSeekSyntaxValidation() {
2350
2353
  return resolveReadSeekJsonSettings().settings.syntaxValidation;
2351
2354
  }
@@ -2359,7 +2362,7 @@ function resolveReadSeekToolDisplayMode(tool) {
2359
2362
  // src/readseek-client.ts
2360
2363
  var resolveFromHost = createRequire2(import.meta.url).resolve;
2361
2364
  function readSeekBinaryPath() {
2362
- return resolveReadSeekBinaryFromPath() ?? resolveReadSeekBinaryFromPackages(resolveFromHost);
2365
+ return resolveReadSeekBinaryFromPackages(resolveFromHost);
2363
2366
  }
2364
2367
  function readSeekBinaryAvailability() {
2365
2368
  try {
@@ -5460,6 +5463,36 @@ function summarizeEnvelope(envelope, facets) {
5460
5463
  parts.unshift("metadata");
5461
5464
  return `digested ${parts.join(",") || "metadata"}`;
5462
5465
  }
5466
+ function parseImageEnvelope(envelope) {
5467
+ if (!envelope || typeof envelope !== "object")
5468
+ return;
5469
+ const record = envelope;
5470
+ if (!record.metadata || typeof record.metadata !== "object")
5471
+ return;
5472
+ const metadata = record.metadata;
5473
+ if (typeof metadata.type !== "string" || !metadata.type.startsWith("image/"))
5474
+ return;
5475
+ if (!record.content || typeof record.content !== "object")
5476
+ return { metadata };
5477
+ const content = record.content;
5478
+ const mime = content.mime ?? content.type ?? metadata.mime ?? metadata.type;
5479
+ const prepared = content.encoding === "base64" && typeof content.data === "string" && typeof mime === "string" ? { data: content.data, mimeType: mime } : undefined;
5480
+ return { metadata, prepared };
5481
+ }
5482
+ function buildDigestSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5483
+ return {
5484
+ content,
5485
+ details: {
5486
+ readSeekValue: {
5487
+ tool: "digest",
5488
+ ok: true,
5489
+ path: filePath,
5490
+ select: facets,
5491
+ envelope
5492
+ }
5493
+ }
5494
+ };
5495
+ }
5463
5496
  async function executeDigest(opts) {
5464
5497
  const { params, signal, cwd, onFileAnchored } = opts;
5465
5498
  const p = params;
@@ -5471,6 +5504,8 @@ async function executeDigest(opts) {
5471
5504
  path: p.path
5472
5505
  });
5473
5506
  }
5507
+ const facets = selectedFacets(select);
5508
+ const appliesImagePolicy = opts.imageMode !== undefined && facets.includes("content");
5474
5509
  const end = coerceObviousBase10Int(p.end, "end");
5475
5510
  if (!end.ok || end.value !== undefined && end.value < 1) {
5476
5511
  const message = end.ok ? `Invalid end: expected a positive integer, received ${end.value}.` : end.message;
@@ -5487,41 +5522,67 @@ async function executeDigest(opts) {
5487
5522
  path: p.path
5488
5523
  });
5489
5524
  }
5490
- if (p.visionLevel !== undefined && p.visionMode === undefined) {
5525
+ if (p.visionLevel !== undefined && p.visionMode === undefined && !appliesImagePolicy) {
5491
5526
  return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5492
5527
  path: p.path
5493
5528
  });
5494
5529
  }
5495
5530
  const filePath = resolveToCwd(p.path, cwd);
5496
- const facets = selectedFacets(select);
5497
- const vision = p.visionMode !== undefined && p.visionMode !== "none";
5531
+ const commonOptions = { select, signal };
5532
+ const textOptions = {
5533
+ ...commonOptions,
5534
+ at,
5535
+ end: end.value,
5536
+ limit: limit.value,
5537
+ language: p.language
5538
+ };
5539
+ const hasTextOptions = at !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5540
+ const hasVisionOptions = p.visionMode !== undefined || p.visionLevel !== undefined;
5541
+ const initialVisionMode = appliesImagePolicy ? undefined : p.visionMode;
5542
+ const initialVisionLevel = appliesImagePolicy ? undefined : p.visionLevel;
5498
5543
  try {
5499
5544
  const envelope = await readSeekDigestNative(filePath, {
5500
- select,
5501
- at,
5502
- end: end.value,
5503
- limit: limit.value,
5504
- language: p.language,
5545
+ ...appliesImagePolicy ? commonOptions : textOptions,
5546
+ visionMode: initialVisionMode,
5547
+ visionLevel: initialVisionLevel,
5548
+ vision: initialVisionMode !== undefined && initialVisionMode !== "none"
5549
+ });
5550
+ const image = appliesImagePolicy ? parseImageEnvelope(envelope) : undefined;
5551
+ if (image) {
5552
+ const imageDetails = { metadata: image.metadata };
5553
+ if (opts.imageMode === "off") {
5554
+ return buildDigestSuccess(filePath, facets, imageDetails, [
5555
+ { type: "text", text: `[Skipped standalone image: ${p.path}; readseek.imageMode is off]` }
5556
+ ]);
5557
+ }
5558
+ if (opts.imageMode === "auto" && opts.modelSupportsImages && image.prepared) {
5559
+ return buildDigestSuccess(filePath, facets, imageDetails, [
5560
+ { type: "image", data: image.prepared.data, mimeType: image.prepared.mimeType }
5561
+ ]);
5562
+ }
5563
+ const visionMode = p.visionMode && p.visionMode !== "none" ? p.visionMode : "all";
5564
+ const analyzedEnvelope = await readSeekDigestNative(filePath, {
5565
+ ...commonOptions,
5566
+ visionMode,
5567
+ visionLevel: p.visionLevel,
5568
+ vision: true
5569
+ });
5570
+ return buildDigestSuccess(filePath, facets, analyzedEnvelope);
5571
+ }
5572
+ if (p.visionLevel !== undefined && p.visionMode === undefined) {
5573
+ return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5574
+ path: p.path
5575
+ });
5576
+ }
5577
+ const textEnvelope = appliesImagePolicy && (hasTextOptions || hasVisionOptions) ? await readSeekDigestNative(filePath, {
5578
+ ...textOptions,
5505
5579
  visionMode: p.visionMode,
5506
5580
  visionLevel: p.visionLevel,
5507
- signal,
5508
- vision
5509
- });
5510
- if (hasDigestAnchors(envelope))
5581
+ vision: p.visionMode !== undefined && p.visionMode !== "none"
5582
+ }) : envelope;
5583
+ if (hasDigestAnchors(textEnvelope))
5511
5584
  onFileAnchored?.(filePath);
5512
- const text = JSON.stringify(envelope);
5513
- return {
5514
- content: [{ type: "text", text }],
5515
- details: {
5516
- readSeekValue: {
5517
- tool: "digest",
5518
- ok: true,
5519
- path: filePath,
5520
- select: facets,
5521
- envelope
5522
- }
5523
- }
5524
- };
5585
+ return buildDigestSuccess(filePath, facets, textEnvelope);
5525
5586
  } catch (err) {
5526
5587
  const failure = classifyReadSeekFailure(err);
5527
5588
  return buildToolErrorResult("digest", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
@@ -5543,7 +5604,14 @@ function registerDigestTool(pi, options = {}) {
5543
5604
  promptGuidelines: promptMetadata.promptGuidelines,
5544
5605
  parameters: digestSchema,
5545
5606
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5546
- return executeDigest({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
5607
+ return executeDigest({
5608
+ params,
5609
+ signal,
5610
+ cwd: ctx.cwd,
5611
+ onFileAnchored: options.onFileAnchored,
5612
+ imageMode: options.imageMode,
5613
+ modelSupportsImages: ctx.model?.input.includes("image") ?? false
5614
+ });
5547
5615
  },
5548
5616
  renderCall(args, theme, ...rest) {
5549
5617
  const context = rest[0] ?? {};
@@ -5736,7 +5804,7 @@ var READSEEK_TOOL_ENTRIES = [
5736
5804
  { builtIn: null, readSeekName: "readSeek_rename" },
5737
5805
  { builtIn: "write", readSeekName: "readSeek_write" },
5738
5806
  { builtIn: null, readSeekName: "readSeek_def" },
5739
- { builtIn: null, readSeekName: "readSeek_digest" },
5807
+ { builtIn: "read", readSeekName: "readSeek_digest" },
5740
5808
  { builtIn: null, readSeekName: "readSeek_view" }
5741
5809
  ];
5742
5810
  function formatSettingsWarning(warning) {
@@ -5756,11 +5824,13 @@ function piReadSeekExtension(pi) {
5756
5824
  const { settings } = resolveReadSeekJsonSettings();
5757
5825
  const replacedBuiltIns = new Set(settings.overrideTools ?? []);
5758
5826
  const binaryAvailable = readSeekBinaryAvailability().available;
5759
- const swap = (builtIn, readSeekName) => binaryAvailable && replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
5827
+ const swap = (builtIn, readSeekName) => replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
5828
+ const readName = swap("read", "readSeek_digest");
5760
5829
  const editName = swap("edit", "readSeek_edit");
5761
5830
  const writeName = swap("write", "readSeek_write");
5762
5831
  const grepName = swap("grep", "readSeek_grep");
5763
5832
  const toolAliases = {
5833
+ readSeek_digest: readName,
5764
5834
  readSeek_edit: editName,
5765
5835
  readSeek_grep: grepName,
5766
5836
  readSeek_write: writeName
@@ -5775,7 +5845,12 @@ function piReadSeekExtension(pi) {
5775
5845
  registerRefsTool(pi, { onFileAnchored: markAnchored });
5776
5846
  registerRenameTool(pi, { onFileMutated: forgetAnchors });
5777
5847
  registerDefTool(pi, { onFileAnchored: markAnchored });
5778
- registerDigestTool(pi, { onFileAnchored: markAnchored, name: "readSeek_digest", toolAliases });
5848
+ registerDigestTool(pi, {
5849
+ onFileAnchored: markAnchored,
5850
+ name: readName,
5851
+ toolAliases,
5852
+ imageMode: replacedBuiltIns.has("read") ? resolveReadSeekImageMode() : undefined
5853
+ });
5779
5854
  registerViewTool(pi);
5780
5855
  registerWriteTool(pi, { onFileAnchored: markAnchored, name: writeName });
5781
5856
  pi.on("before_agent_start", (event) => {
@@ -5791,7 +5866,7 @@ ${toolRoutingPolicy(policyAliases)}` };
5791
5866
  const problems = warnings.map(formatSettingsWarning);
5792
5867
  const availability = readSeekBinaryAvailability();
5793
5868
  if (!availability.available) {
5794
- problems.push(`readseek tools are inactive: ${availability.reason}`);
5869
+ problems.push(`readseek installation is broken: ${availability.reason}`);
5795
5870
  }
5796
5871
  for (const problem of problems) {
5797
5872
  if (ctx.hasUI)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-readseek",
3
- "version": "0.9.10",
3
+ "version": "0.9.11",
4
4
  "description": "Pi extension for LINE:HASH-anchored file operations and structural code navigation",
5
5
  "type": "module",
6
6
  "exports": {
@@ -40,7 +40,7 @@
40
40
  "node": ">=20.0.0"
41
41
  },
42
42
  "dependencies": {
43
- "@jarkkojs/readseek": "^0.9.10",
43
+ "@jarkkojs/readseek": "^0.9.11",
44
44
  "diff": "^9.0.0",
45
45
  "xxhash-wasm": "^1.1.0"
46
46
  },