pi-repoprompt-cli 0.2.5 → 0.2.7

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.
package/README.md CHANGED
@@ -7,6 +7,7 @@ Provides two tools:
7
7
  - `rp_exec` — run `rp-cli -e <cmd>` against that binding (quiet defaults + output truncation)
8
8
 
9
9
  Optional:
10
+ - Diff blocks in `rp_exec` output use `delta` when installed (honoring the user's global git/delta color config), with graceful fallback otherwise
10
11
  - [Gurpartap/pi-readcache](https://github.com/Gurpartap/pi-readcache)-like caching for `rp_exec` calls that read files (`read` / `cat` / `read_file`) to save on tokens
11
12
  - returns unchanged markers and diffs on repeat reads
12
13
 
@@ -7,6 +7,8 @@ It provides two Pi tools:
7
7
  - `rp_bind` — bind to a specific RepoPrompt **window id** + **compose tab** (routing)
8
8
  - `rp_exec` — run `rp-cli -e <cmd>` against that binding
9
9
 
10
+ Diff blocks in `rp_exec` output use `delta` when installed (honoring the user's global git/delta color config), with graceful fallback otherwise
11
+
10
12
  ## Optional: readcache for `rp_exec read`
11
13
 
12
14
  If enabled, `rp_exec` will apply [Gurpartap/pi-readcache](https://github.com/Gurpartap/pi-readcache)-like token savings for single-command file reads, returning:
@@ -1,3 +1,3 @@
1
1
  {
2
- "readcacheReadFile": true
2
+ "readcacheReadFile": false
3
3
  }
@@ -1,3 +1,5 @@
1
+ import { spawnSync } from "node:child_process";
2
+
1
3
  import type { ExtensionAPI, ExtensionContext, ToolRenderResultOptions } from "@mariozechner/pi-coding-agent";
2
4
  import { highlightCode, Theme } from "@mariozechner/pi-coding-agent";
3
5
  import { Text } from "@mariozechner/pi-tui";
@@ -239,7 +241,7 @@ function hasPipeOutsideQuotes(script: string): boolean {
239
241
  * - Provide actionable error messages when blocked
240
242
  * - For best command parsing (AST-based), install `just-bash` >= 2; otherwise it falls back to a legacy splitter
241
243
  * - Syntax-highlight fenced code blocks in output (read, structure, etc.)
242
- * - Word-level diff highlighting for edit output
244
+ * - Delta-powered diff highlighting (with graceful fallback when delta is unavailable)
243
245
  */
244
246
 
245
247
  const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
@@ -954,6 +956,88 @@ function parseFencedBlocks(text: string): FencedBlock[] {
954
956
  return blocks;
955
957
  }
956
958
 
959
+ const ANSI_ESCAPE_RE = /\x1b\[[0-9;]*m/g;
960
+ const DELTA_TIMEOUT_MS = 5000;
961
+ const DELTA_MAX_BUFFER = 8 * 1024 * 1024;
962
+ const DELTA_CACHE_MAX_ENTRIES = 200;
963
+
964
+ let deltaAvailable: boolean | null = null;
965
+ const deltaDiffCache = new Map<string, string | null>();
966
+
967
+ function isDeltaInstalled(): boolean {
968
+ if (deltaAvailable !== null) {
969
+ return deltaAvailable;
970
+ }
971
+
972
+ const check = spawnSync("delta", ["--version"], {
973
+ stdio: "ignore",
974
+ timeout: 1000,
975
+ });
976
+
977
+ deltaAvailable = !check.error && check.status === 0;
978
+ return deltaAvailable;
979
+ }
980
+
981
+ function runDelta(diffText: string): string | null {
982
+ const result = spawnSync("delta", ["--color-only", "--paging=never"], {
983
+ encoding: "utf-8",
984
+ input: diffText,
985
+ timeout: DELTA_TIMEOUT_MS,
986
+ maxBuffer: DELTA_MAX_BUFFER,
987
+ });
988
+
989
+ if (result.error || result.status !== 0) {
990
+ return null;
991
+ }
992
+
993
+ return typeof result.stdout === "string" ? result.stdout : null;
994
+ }
995
+
996
+ function stripSyntheticHeader(deltaOutput: string): string {
997
+ const outputLines = deltaOutput.split("\n");
998
+ const bodyStart = outputLines.findIndex((line) => line.replace(ANSI_ESCAPE_RE, "").startsWith("@@"));
999
+
1000
+ if (bodyStart >= 0) {
1001
+ return outputLines.slice(bodyStart + 1).join("\n");
1002
+ }
1003
+
1004
+ return deltaOutput;
1005
+ }
1006
+
1007
+ function renderDiffBlockWithDelta(code: string): string | null {
1008
+ if (!isDeltaInstalled()) {
1009
+ return null;
1010
+ }
1011
+
1012
+ const cached = deltaDiffCache.get(code);
1013
+ if (cached !== undefined) {
1014
+ return cached;
1015
+ }
1016
+
1017
+ let rendered = runDelta(code);
1018
+
1019
+ if (!rendered) {
1020
+ const syntheticDiff = [
1021
+ "--- a/file",
1022
+ "+++ b/file",
1023
+ "@@ -1,1 +1,1 @@",
1024
+ code,
1025
+ ].join("\n");
1026
+
1027
+ const syntheticRendered = runDelta(syntheticDiff);
1028
+ if (syntheticRendered) {
1029
+ rendered = stripSyntheticHeader(syntheticRendered);
1030
+ }
1031
+ }
1032
+
1033
+ if (deltaDiffCache.size >= DELTA_CACHE_MAX_ENTRIES) {
1034
+ deltaDiffCache.clear();
1035
+ }
1036
+
1037
+ deltaDiffCache.set(code, rendered);
1038
+ return rendered;
1039
+ }
1040
+
957
1041
  /**
958
1042
  * Compute word-level diff with inverse highlighting on changed parts
959
1043
  */
@@ -1005,6 +1089,11 @@ function renderIntraLineDiff(
1005
1089
  * Render diff lines with syntax highlighting (red/green, word-level inverse)
1006
1090
  */
1007
1091
  function renderDiffBlock(code: string, theme: Theme): string {
1092
+ const deltaRendered = renderDiffBlockWithDelta(code);
1093
+ if (deltaRendered !== null) {
1094
+ return deltaRendered;
1095
+ }
1096
+
1008
1097
  const lines = code.split("\n");
1009
1098
  const result: string[] = [];
1010
1099
 
@@ -1094,7 +1183,7 @@ function renderDiffBlock(code: string, theme: Theme): string {
1094
1183
 
1095
1184
  /**
1096
1185
  * Render rp_exec output with syntax highlighting for fenced code blocks.
1097
- * - ```diff blocks get word-level diff highlighting
1186
+ * - ```diff blocks use delta when available, with word-level fallback
1098
1187
  * - Other fenced blocks get syntax highlighting via Pi's highlightCode
1099
1188
  * - Non-fenced content is rendered dim (no markdown parsing)
1100
1189
  */
@@ -1458,8 +1547,11 @@ export default function (pi: ExtensionAPI) {
1458
1547
  };
1459
1548
  }
1460
1549
 
1461
- const readRequest =
1462
- config.readcacheReadFile === true && rawJson !== true ? parseReadFileRequest(params.cmd) : null;
1550
+ // Parse read-like commands to:
1551
+ // - detect cacheable reads (when enabled)
1552
+ // - strip wrapper-only args like bypass_cache=true even when caching is disabled
1553
+ // (so agents can safely use bypass_cache in instructions regardless of config)
1554
+ const readRequest = parseReadFileRequest(params.cmd);
1463
1555
 
1464
1556
  const cmdToRun = readRequest ? readRequest.cmdToRun : params.cmd;
1465
1557
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-repoprompt-cli",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Integrates RepoPrompt with Pi via RepoPrompt's `rp-cli` executable",
5
5
  "keywords": ["pi-package", "pi", "pi-coding-agent", "repoprompt"],
6
6
  "license": "MIT",