pi-rg-fd 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +18 -13
  2. package/index.js +3 -8
  3. package/package.json +4 -3
  4. package/prompt.js +5 -95
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # pi-rg-fd
2
2
 
3
- Pi coding agent extension that rewrites the default system prompt's `grep` and `find` advice to `rg` and `fd` when those tools are on `PATH`.
3
+ Pi coding agent extension that always rewrites the default system prompt's `grep` and `find` advice to `rg` and `fd`.
4
4
 
5
- Pi 1.4's bash tool line names `grep` and `find`, and the default guideline still names `find`, so models reach for those commands even when ripgrep and fd are installed. This package replaces those exact lines in place. The usual prompt gets shorter, and text that is not one of those Pi 1.4 lines is left as Pi wrote it.
5
+ Pi 1.4's bash tool line names `grep` and `find`, and the default guideline still names `find`, so models reach for those commands. This package replaces those two exact lines in place. The usual prompt gets shorter, and text that is not one of those Pi 1.4 lines is left as Pi wrote it. You confirm that `rg` and `fd` run on your machine.
6
6
 
7
7
  ## Install
8
8
 
@@ -30,31 +30,36 @@ Default guideline (bash enabled, and the grep/find/ls tools off), before:
30
30
  Use bash for file operations like ls, rg, find
31
31
  ```
32
32
 
33
- With both `rg` and `fd` on `PATH`, those become:
33
+ Those lines are always rewritten to:
34
34
 
35
35
  ```text
36
36
  Execute bash commands (ls, rg, fd, etc.)
37
37
  Use bash for file operations like ls, rg, fd
38
38
  ```
39
39
 
40
- Each binary is swapped on its own. `fd` is checked independently of `rg`. On Windows the scan accepts `rg.exe` and `fd.exe` (and the rest of `PATHEXT`). On Debian and Ubuntu, where the package installs `fdfind`, that name is used instead of `fd`.
40
+ `rg` and `fd` are shorter than `grep` and `find`. If neither line is present (a custom system prompt, or a future Pi release that rewords them), the hook returns without changing the prompt. The `before_agent_start` hook is a string replace cached by the prompt text, so the same input always yields the same output.
41
41
 
42
- | On PATH | Bash tool line | Guideline |
43
- | --- | --- | --- |
44
- | `rg` and `fd` | `Execute bash commands (ls, rg, fd, etc.)` | `Use bash for file operations like ls, rg, fd` |
45
- | `rg` only | `Execute bash commands (ls, rg, find, etc.)` | unchanged (it already says `rg`; `find` stays) |
46
- | `fd` only | `Execute bash commands (ls, grep, fd, etc.)` | `Use bash for file operations like ls, rg, fd` |
47
- | `fdfind` only | `Execute bash commands (ls, grep, fdfind, etc.)` | `Use bash for file operations like ls, rg, fdfind` |
48
- | neither | unchanged | unchanged |
42
+ Pi's interactive mode downloads `rg` and `fd` when they are missing, into `~/.pi/agent/bin` (or `$PI_CODING_AGENT_DIR/bin` when that variable is set; a leading `~` is expanded). Bash calls see that directory on `PATH`. The managed files are named `rg` and `fd`, plus `.exe` on Windows.
49
43
 
50
- `rg` and `fd` are shorter than `grep` and `find`. `fdfind` is two characters longer than `find`, which is the Debian binary's name. If neither line is present (a custom system prompt, or a future Pi release that rewords them), the hook returns without changing the prompt.
44
+ Debian and Ubuntu's apt package `fd-find` installs the binary as `fdfind`. Alias it so the rewritten prompt matches a command on `PATH`:
51
45
 
52
- Availability is resolved once, when the extension loads, by stating files on `PATH`. The `before_agent_start` hook is a string replace cached by the prompt text, so the same input always yields the same output.
46
+ ```bash
47
+ alias fd=fdfind
48
+ ```
53
49
 
54
50
  ## Prompt text only
55
51
 
56
52
  `pi-cli-guard` and `pi-prefer-rg` rewrite or block bash commands when the model runs them. `pi-rg-fd` edits the system prompt Pi already built. Commands the model still emits run unchanged.
57
53
 
54
+ ## Compatibility
55
+
56
+ With `@earendil-works/pi-coding-agent` installed, `npm run compat` builds Pi's default system prompt from that package and loads this extension through Pi's extension loader. CI runs the same check on Ubuntu and Windows against the latest Pi release, every day and on manual dispatch.
57
+
58
+ ```bash
59
+ npm install --no-save @earendil-works/pi-coding-agent@latest
60
+ npm run compat
61
+ ```
62
+
58
63
  ## License
59
64
 
60
65
  MIT
package/index.js CHANGED
@@ -1,17 +1,12 @@
1
- import { detectSearchTools, rewritePrompt } from "./prompt.js";
1
+ import { rewritePrompt } from "./prompt.js";
2
2
 
3
3
  const CACHE_LIMIT = 8;
4
4
 
5
- /**
6
- * @param {{ rg: boolean, fd: "fd" | "fdfind" | null }} [tools]
7
- */
8
- export function createExtension(tools = detectSearchTools()) {
5
+ export function createExtension() {
9
6
  /** @type {Map<string, string>} */
10
7
  const cache = new Map();
11
8
 
12
9
  return function piRgFd(pi) {
13
- if (!tools.rg && !tools.fd) return;
14
-
15
10
  pi.on("before_agent_start", (event) => {
16
11
  const input = event.systemPrompt;
17
12
  if (typeof input !== "string") return;
@@ -21,7 +16,7 @@ export function createExtension(tools = detectSearchTools()) {
21
16
  cache.delete(input);
22
17
  cache.set(input, output);
23
18
  } else {
24
- output = rewritePrompt(input, tools);
19
+ output = rewritePrompt(input);
25
20
  cache.set(input, output);
26
21
  if (cache.size > CACHE_LIMIT) cache.delete(cache.keys().next().value);
27
22
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-rg-fd",
3
- "version": "0.1.0",
4
- "description": "Pi extension that rewrites the default grep/find prompt lines to rg and fd when those tools are on PATH.",
3
+ "version": "0.2.0",
4
+ "description": "Pi extension that rewrites the default grep/find prompt lines to rg and fd.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "license": "MIT",
@@ -31,7 +31,8 @@
31
31
  "node": ">=22.19.0"
32
32
  },
33
33
  "scripts": {
34
- "test": "node --test"
34
+ "test": "node --test",
35
+ "compat": "node scripts/compat.js"
35
36
  },
36
37
  "pi": {
37
38
  "extensions": [
package/prompt.js CHANGED
@@ -1,6 +1,3 @@
1
- import { statSync } from "node:fs";
2
- import path from "node:path";
3
-
4
1
  /**
5
2
  * Default bash tool line in @earendil-works/pi-coding-agent 1.4.0
6
3
  * (`bashToolSystemPromptContribution.snippet`).
@@ -13,89 +10,8 @@ export const BASH_SNIPPET = "Execute bash commands (ls, grep, find, etc.)";
13
10
  */
14
11
  export const BASH_RULE = "Use bash for file operations like ls, rg, find";
15
12
 
16
- const WINDOWS_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC";
17
-
18
- /**
19
- * @param {Record<string, string | undefined>} env
20
- * @param {string} name
21
- * @param {string} platform
22
- */
23
- function envValue(env, name, platform) {
24
- if (platform !== "win32") return env[name] ?? "";
25
- const lower = name.toLowerCase();
26
- for (const key of Object.keys(env)) {
27
- if (key.toLowerCase() === lower) return env[key] ?? "";
28
- }
29
- return "";
30
- }
31
-
32
- /**
33
- * @param {string} name
34
- * @param {string} platform
35
- * @param {string} pathext
36
- */
37
- function commandNames(name, platform, pathext) {
38
- if (platform !== "win32") return [name];
39
- const extensions = new Set([".exe", ".EXE"]);
40
- for (const raw of pathext.split(";")) {
41
- const ext = raw.trim();
42
- if (ext === "" || ext === ".") {
43
- extensions.add("");
44
- continue;
45
- }
46
- const suffix = ext.startsWith(".") ? ext : `.${ext}`;
47
- extensions.add(suffix);
48
- extensions.add(suffix.toLowerCase());
49
- extensions.add(suffix.toUpperCase());
50
- }
51
- return [...extensions].map((ext) => (ext ? name + ext : name));
52
- }
53
-
54
- /**
55
- * @param {string} file
56
- * @param {string} platform
57
- */
58
- function isRunnableFile(file, platform) {
59
- try {
60
- const stat = statSync(file);
61
- if (!stat.isFile()) return false;
62
- if (platform === "win32") return true;
63
- return (stat.mode & 0o111) !== 0;
64
- } catch {
65
- return false;
66
- }
67
- }
68
-
69
- /**
70
- * Locate `rg` and `fd` by scanning PATH. No processes are spawned.
71
- * `fd` is `"fd"`, `"fdfind"` (Debian/Ubuntu), or null.
72
- *
73
- * @param {{ env?: NodeJS.ProcessEnv, platform?: string }} [options]
74
- * @returns {{ rg: boolean, fd: "fd" | "fdfind" | null }}
75
- */
76
- export function detectSearchTools(options = {}) {
77
- const platform = options.platform ?? process.platform;
78
- const env = options.env ?? process.env;
79
- const dirs = envValue(env, "PATH", platform)
80
- .split(platform === "win32" ? ";" : ":")
81
- .filter((dir) => dir.length > 0);
82
- const pathext = platform === "win32" ? envValue(env, "PATHEXT", platform) || WINDOWS_PATHEXT : "";
83
-
84
- const has = (name) => {
85
- const names = commandNames(name, platform, pathext);
86
- for (const dir of dirs) {
87
- for (const candidate of names) {
88
- if (isRunnableFile(path.join(dir, candidate), platform)) return true;
89
- }
90
- }
91
- return false;
92
- };
93
-
94
- return {
95
- rg: has("rg"),
96
- fd: has("fd") ? "fd" : has("fdfind") ? "fdfind" : null,
97
- };
98
- }
13
+ const REWRITTEN_SNIPPET = "Execute bash commands (ls, rg, fd, etc.)";
14
+ const REWRITTEN_RULE = "Use bash for file operations like ls, rg, fd";
99
15
 
100
16
  /**
101
17
  * @param {string} text
@@ -112,15 +28,9 @@ function replaceExact(text, from, to) {
112
28
  * unchanged (same string when nothing matches).
113
29
  *
114
30
  * @param {string} prompt
115
- * @param {{ rg: boolean, fd: "fd" | "fdfind" | null }} tools
116
31
  */
117
- export function rewritePrompt(prompt, tools) {
118
- if (!tools.rg && !tools.fd) return prompt;
119
- const contentTool = tools.rg ? "rg" : "grep";
120
- const fileTool = tools.fd ?? "find";
121
- let next = replaceExact(prompt, BASH_SNIPPET, `Execute bash commands (ls, ${contentTool}, ${fileTool}, etc.)`);
122
- if (tools.fd) {
123
- next = replaceExact(next, BASH_RULE, `Use bash for file operations like ls, rg, ${tools.fd}`);
124
- }
32
+ export function rewritePrompt(prompt) {
33
+ let next = replaceExact(prompt, BASH_SNIPPET, REWRITTEN_SNIPPET);
34
+ next = replaceExact(next, BASH_RULE, REWRITTEN_RULE);
125
35
  return next;
126
36
  }