@trim21/personal-pi-extensions 0.0.231 → 0.0.233
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/package.json +3 -2
- package/src/claude-code/bash.md +31 -0
- package/src/claude-code/edit.md +12 -0
- package/src/claude-code/files.ts +16 -1
- package/src/claude-code/glob.md +6 -0
- package/src/claude-code/glob.ts +7 -0
- package/src/claude-code/grep.md +10 -0
- package/src/claude-code/grep.ts +7 -0
- package/src/claude-code/read.md +13 -0
- package/src/claude-code/session-tools.ts +7 -0
- package/src/claude-code/shell.ts +7 -0
- package/src/claude-code/write.md +11 -0
- package/src/spawn-agent.ts +17 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trim21/personal-pi-extensions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.233",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
37
37
|
"@eslint/js": "10.0.1",
|
|
38
38
|
"@types/node": "^24.13.3",
|
|
39
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
39
40
|
"@vitest/coverage-v8": "^4.1.10",
|
|
40
41
|
"eslint": "^10.8.1",
|
|
41
42
|
"eslint-config-prettier": "10.1.8",
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"lint-staged": "^17.3.0",
|
|
49
50
|
"prettier": "^3.9.6",
|
|
50
51
|
"typebox": "1.3.1",
|
|
51
|
-
"typescript": "
|
|
52
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
52
53
|
"typescript-eslint": "^8.67.0",
|
|
53
54
|
"vitest": "^4.1.10"
|
|
54
55
|
},
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Bash tool
|
|
2
|
+
|
|
3
|
+
Executes a given bash command and returns its output.
|
|
4
|
+
|
|
5
|
+
The working directory persists between commands, but shell state does not. The shell environment is initialized from the user's profile (bash or zsh).
|
|
6
|
+
|
|
7
|
+
Commands run in a sandbox: no write access outside the workspace and no network access. Use the `dangerouslyDisableSandbox` parameter to request unsandboxed execution; the user must approve this request.
|
|
8
|
+
|
|
9
|
+
IMPORTANT: Avoid using this tool to run `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or `echo` commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user:
|
|
10
|
+
|
|
11
|
+
- File search: Use Glob (NOT find or ls)
|
|
12
|
+
- Content search: Use Grep (NOT grep or rg)
|
|
13
|
+
- Read files: Use Read (NOT cat/head/tail)
|
|
14
|
+
- Edit files: Use Edit (NOT sed/awk)
|
|
15
|
+
- Write files: Use Write (NOT echo >/cat <<EOF)
|
|
16
|
+
- Communication: Output text directly (NOT echo/printf)
|
|
17
|
+
While the Bash tool can do similar things, it's better to use the built-in tools as they provide a better user experience and make it easier to review tool calls and give permission.
|
|
18
|
+
|
|
19
|
+
- You may specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). By default, your command will timeout after 120000ms (2 minutes).
|
|
20
|
+
- When issuing multiple commands:
|
|
21
|
+
- If the commands are independent and can run in parallel, make multiple Bash tool calls in a single message. Example: if you need to run "git status" and "git diff", send a single message with two Bash tool calls in parallel.
|
|
22
|
+
- If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together.
|
|
23
|
+
- Use ';' only when you need to run commands sequentially but don't care if earlier commands fail.
|
|
24
|
+
- DO NOT use newlines to separate commands (newlines are ok in quoted strings).
|
|
25
|
+
- For git commands:
|
|
26
|
+
- Prefer to create a new commit rather than amending an existing commit.
|
|
27
|
+
- Before running destructive operations (e.g., git reset --hard, git push --force, git checkout --), consider whether there is a safer alternative that achieves the same goal. Only use destructive operations when they are truly the best approach.
|
|
28
|
+
- Never skip hooks (--no-verify) or bypass signing (--no-gpg-sign, -c commit.gpgsign=false) unless the user has explicitly asked for it. If a hook fails, investigate and fix the underlying issue.
|
|
29
|
+
- Avoid unnecessary `sleep` commands:
|
|
30
|
+
- Do not sleep between commands that can run immediately — just run them.
|
|
31
|
+
- Do not retry failing commands in a sleep loop — diagnose the root cause.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Edit tool
|
|
2
|
+
|
|
3
|
+
Performs exact string replacements in files.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
|
|
7
|
+
- You must use your Read tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.
|
|
8
|
+
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: line number + tab. Everything after that is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.
|
|
9
|
+
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
10
|
+
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
|
|
11
|
+
- The edit will FAIL if old_string is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use replace_all to change every instance of old_string.
|
|
12
|
+
- Use replace_all for replacing and renaming strings across the file.
|
package/src/claude-code/files.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { constants } from "node:fs";
|
|
2
|
+
import { constants, readFileSync } from "node:fs";
|
|
3
3
|
import { access, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, extname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
|
|
6
7
|
import type { ImageContent, TextContent } from "@earendil-works/pi-ai";
|
|
7
8
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -23,6 +24,14 @@ import {
|
|
|
23
24
|
|
|
24
25
|
const DEFAULT_READ_LINES = 2000;
|
|
25
26
|
const SAMPLE_BYTES = 4096;
|
|
27
|
+
|
|
28
|
+
/** Tool guidance, kept in markdown so it reads like documentation. */
|
|
29
|
+
const READ_PROMPT = readFileSync(fileURLToPath(new URL("read.md", import.meta.url)), "utf8").trim();
|
|
30
|
+
const EDIT_PROMPT = readFileSync(fileURLToPath(new URL("edit.md", import.meta.url)), "utf8").trim();
|
|
31
|
+
const WRITE_PROMPT = readFileSync(
|
|
32
|
+
fileURLToPath(new URL("write.md", import.meta.url)),
|
|
33
|
+
"utf8",
|
|
34
|
+
).trim();
|
|
26
35
|
const IMAGE_MIMES = new Map<string, string>([
|
|
27
36
|
[".gif", "image/gif"],
|
|
28
37
|
[".jpeg", "image/jpeg"],
|
|
@@ -163,6 +172,8 @@ export function registerFileTools(pi: ExtensionAPI, state: ClaudeCodeState): voi
|
|
|
163
172
|
"Results use cat -n style line numbers starting at 1. Images are returned visually.",
|
|
164
173
|
"This tool reads files, not directories.",
|
|
165
174
|
].join("\n"),
|
|
175
|
+
promptSnippet: "Read files from the local filesystem with line numbers",
|
|
176
|
+
promptGuidelines: [READ_PROMPT],
|
|
166
177
|
parameters: Type.Object(
|
|
167
178
|
{
|
|
168
179
|
file_path: Type.String({ description: "The absolute path to the file to read" }),
|
|
@@ -231,6 +242,8 @@ export function registerFileTools(pi: ExtensionAPI, state: ClaudeCodeState): voi
|
|
|
231
242
|
"old_string must match exactly and must be unique unless replace_all is true.",
|
|
232
243
|
"This tool does not use regular expressions or fuzzy matching.",
|
|
233
244
|
].join("\n"),
|
|
245
|
+
promptSnippet: "Make exact string replacements in files",
|
|
246
|
+
promptGuidelines: [EDIT_PROMPT],
|
|
234
247
|
parameters: Type.Object(
|
|
235
248
|
{
|
|
236
249
|
file_path: Type.String({ description: "The absolute path to the file to modify" }),
|
|
@@ -288,6 +301,8 @@ export function registerFileTools(pi: ExtensionAPI, state: ClaudeCodeState): voi
|
|
|
288
301
|
"This tool overwrites an existing file with the full content provided.",
|
|
289
302
|
"If the file exists, you must use Read first. Prefer Edit for partial changes.",
|
|
290
303
|
].join("\n"),
|
|
304
|
+
promptSnippet: "Create or overwrite files",
|
|
305
|
+
promptGuidelines: [WRITE_PROMPT],
|
|
291
306
|
parameters: Type.Object(
|
|
292
307
|
{
|
|
293
308
|
file_path: Type.String({
|
package/src/claude-code/glob.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* independently (declare `Glob` in the frontmatter to get only this tool).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
8
9
|
import { glob as fsGlob, stat } from "node:fs/promises";
|
|
9
10
|
import { resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
10
12
|
|
|
11
13
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
14
|
import { Type } from "typebox";
|
|
@@ -15,6 +17,9 @@ import { searchRoot, throwIfAborted } from "./common.js";
|
|
|
15
17
|
|
|
16
18
|
const GLOB_RESULT_LIMIT = 100;
|
|
17
19
|
|
|
20
|
+
/** Tool guidance, kept in markdown so it reads like documentation. */
|
|
21
|
+
const GLOB_PROMPT = readFileSync(fileURLToPath(new URL("glob.md", import.meta.url)), "utf8").trim();
|
|
22
|
+
|
|
18
23
|
export async function globFiles(
|
|
19
24
|
pattern: string,
|
|
20
25
|
cwd: string,
|
|
@@ -47,6 +52,8 @@ export function registerGlobTool(pi: ExtensionAPI): void {
|
|
|
47
52
|
'Supports glob patterns such as "**/*.js" and "src/**/*.ts".',
|
|
48
53
|
"Returns matching file paths sorted by modification time.",
|
|
49
54
|
].join("\n"),
|
|
55
|
+
promptSnippet: "Find files by name patterns",
|
|
56
|
+
promptGuidelines: [GLOB_PROMPT],
|
|
50
57
|
parameters: Type.Object(
|
|
51
58
|
{
|
|
52
59
|
pattern: Type.String({ description: "The glob pattern to match files against" }),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Grep tool
|
|
2
|
+
|
|
3
|
+
A powerful search tool built on ripgrep.
|
|
4
|
+
|
|
5
|
+
- ALWAYS use Grep for search tasks. NEVER invoke `grep` or `rg` as a Bash command. The Grep tool has been optimized for correct permissions and access.
|
|
6
|
+
- Supports full regex syntax (e.g., "log.*Error", "function\s+\w+")
|
|
7
|
+
- Filter files with glob parameter (e.g., `*.js`, `**/*.tsx`) or type parameter (e.g., "js", "py", "rust")
|
|
8
|
+
- Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
|
|
9
|
+
- Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use `interface\{\}` to find `interface{}` in Go code)
|
|
10
|
+
- Multiline matching: By default patterns match within single lines only. For cross-line patterns like `struct \{[\s\S]*?field`, use multiline: true
|
package/src/claude-code/grep.ts
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* lines capped at 500 columns, and an implicit head_limit of 250 entries.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
12
13
|
import { stat } from "node:fs/promises";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
13
15
|
|
|
14
16
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
15
17
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -21,6 +23,9 @@ const GREP_OUTPUT_MODES = ["content", "files_with_matches", "count"] as const;
|
|
|
21
23
|
|
|
22
24
|
type GrepOutputMode = (typeof GREP_OUTPUT_MODES)[number];
|
|
23
25
|
|
|
26
|
+
/** Tool guidance, kept in markdown so it reads like documentation. */
|
|
27
|
+
const GREP_PROMPT = readFileSync(fileURLToPath(new URL("grep.md", import.meta.url)), "utf8").trim();
|
|
28
|
+
|
|
24
29
|
/** Version control directories excluded from searches (noise in results). */
|
|
25
30
|
const VCS_DIRECTORIES_TO_EXCLUDE = [".git", ".svn", ".hg", ".bzr", ".jj", ".sl"] as const;
|
|
26
31
|
|
|
@@ -145,6 +150,8 @@ export function registerGrepTool(pi: ExtensionAPI): void {
|
|
|
145
150
|
"Supports regular expressions, file globs, file types, multiline matching, context lines, and paginated output.",
|
|
146
151
|
'output_mode defaults to "files_with_matches"; use "content" for matching lines or "count" for match counts.',
|
|
147
152
|
].join("\n"),
|
|
153
|
+
promptSnippet: "Search file contents with regular expressions",
|
|
154
|
+
promptGuidelines: [GREP_PROMPT],
|
|
148
155
|
parameters: Type.Object(
|
|
149
156
|
{
|
|
150
157
|
pattern: Type.String({ description: "The regular expression pattern to search for" }),
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Read tool
|
|
2
|
+
|
|
3
|
+
Reads a file from the local filesystem. You can access any file directly by using this tool.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
|
|
7
|
+
- The file_path parameter must be an absolute path, not a relative path
|
|
8
|
+
- By default, it reads up to 2000 lines starting from the beginning of the file
|
|
9
|
+
- When you already know which part of the file you need, only read that part. This can be important for larger files.
|
|
10
|
+
- Results are returned using cat -n format, with line numbers starting at 1
|
|
11
|
+
- This tool allows you to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually.
|
|
12
|
+
- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
|
|
13
|
+
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
|
|
@@ -116,6 +116,7 @@ export function registerSessionTools(pi: ExtensionAPI): void {
|
|
|
116
116
|
"Keep exactly one task in_progress while work remains and mark tasks completed immediately after finishing them.",
|
|
117
117
|
"Each task needs an imperative content form and a present-continuous activeForm.",
|
|
118
118
|
].join("\n"),
|
|
119
|
+
promptSnippet: "Plan and track work with a task list",
|
|
119
120
|
parameters: todoSchema,
|
|
120
121
|
execute(_id, params, _signal, _onUpdate, ctx) {
|
|
121
122
|
const todos = params.todos.map((todo) => ({
|
|
@@ -174,6 +175,12 @@ export function registerSessionTools(pi: ExtensionAPI): void {
|
|
|
174
175
|
"Use multiSelect for questions where multiple choices may apply.",
|
|
175
176
|
'If you recommend an option, put it first and append "(Recommended)" to its label.',
|
|
176
177
|
].join("\n"),
|
|
178
|
+
promptSnippet: "Ask the user questions during execution",
|
|
179
|
+
promptGuidelines: [
|
|
180
|
+
"Use AskUserQuestion when you need to gather preferences, clarify requirements, or choose an implementation direction.",
|
|
181
|
+
"Users can always provide their own answer through the automatically supplied Other option.",
|
|
182
|
+
'If you recommend an option, put it first and append "(Recommended)" to its label.',
|
|
183
|
+
],
|
|
177
184
|
parameters: Type.Object(
|
|
178
185
|
{
|
|
179
186
|
questions: Type.Array(questionSchema, {
|
package/src/claude-code/shell.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
1
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
5
|
import { Type } from "typebox";
|
|
3
6
|
|
|
@@ -7,10 +10,14 @@ import { resolveWorkdir } from "../lib/path.js";
|
|
|
7
10
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
8
11
|
const MAX_TIMEOUT_MS = 600_000;
|
|
9
12
|
|
|
13
|
+
/** Bash tool guidance, kept in markdown so it reads like documentation. */
|
|
14
|
+
const BASH_PROMPT = readFileSync(fileURLToPath(new URL("bash.md", import.meta.url)), "utf8").trim();
|
|
15
|
+
|
|
10
16
|
export function registerShellTools(pi: ExtensionAPI): void {
|
|
11
17
|
bwrapRuntime.setup(pi);
|
|
12
18
|
pi.registerTool({
|
|
13
19
|
name: "Bash",
|
|
20
|
+
promptGuidelines: [BASH_PROMPT],
|
|
14
21
|
label: "Bash",
|
|
15
22
|
description: [
|
|
16
23
|
"Executes a given bash command synchronously and returns its output.",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Write tool
|
|
2
|
+
|
|
3
|
+
Writes a file to the local filesystem.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
|
|
7
|
+
- This tool will overwrite the existing file if there is one at the provided path.
|
|
8
|
+
- If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.
|
|
9
|
+
- Prefer the Edit tool for modifying existing files — it only sends the diff. Only use this tool to create new files or for complete rewrites.
|
|
10
|
+
- NEVER create documentation files (*.md) or README files unless explicitly requested by the User.
|
|
11
|
+
- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.
|
package/src/spawn-agent.ts
CHANGED
|
@@ -60,9 +60,11 @@ const MAX_PROGRESS_LINES = 5;
|
|
|
60
60
|
* without bash need no bwrap setup (there are no commands to sandbox).
|
|
61
61
|
* (Workspace write protection is embedded in the opencode write/edit tools.)
|
|
62
62
|
*
|
|
63
|
-
* Claude Code style tools (capitalized names
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* Claude Code style tools (capitalized names) map to their claude-code
|
|
64
|
+
* files, so a subagent can enable exactly the tools it declares — e.g. `Grep`
|
|
65
|
+
* without `Glob`. The stateful file tools (`Read`/`Edit`/`Write`) share one
|
|
66
|
+
* implementation file (they share a read-snapshot state); the `--tools`
|
|
67
|
+
* allowlist still exposes only the declared subset.
|
|
66
68
|
*/
|
|
67
69
|
const TOOL_EXTENSION_OVERRIDES: Record<string, string> = {
|
|
68
70
|
read: "opencode/read.ts",
|
|
@@ -71,6 +73,9 @@ const TOOL_EXTENSION_OVERRIDES: Record<string, string> = {
|
|
|
71
73
|
bash: "opencode/bash.ts",
|
|
72
74
|
Grep: "claude-code/grep.ts",
|
|
73
75
|
Glob: "claude-code/glob.ts",
|
|
76
|
+
Read: "claude-code/files.ts",
|
|
77
|
+
Edit: "claude-code/files.ts",
|
|
78
|
+
Write: "claude-code/files.ts",
|
|
74
79
|
};
|
|
75
80
|
|
|
76
81
|
// ── schema ───────────────────────────────────────────────────────────────────
|
|
@@ -205,10 +210,17 @@ export function buildSubagentArgs(
|
|
|
205
210
|
const tools = agent.tools ?? DEFAULT_TOOLS;
|
|
206
211
|
// Load the opencode override for each built-in tool the agent declares
|
|
207
212
|
// (read/edit/write), so the subagent uses the enhanced implementation
|
|
208
|
-
// instead of the built-in one.
|
|
213
|
+
// instead of the built-in one. Several tool names can map to the same
|
|
214
|
+
// implementation file (e.g. cc Read/Edit/Write → claude-code/files.ts);
|
|
215
|
+
// loading a file twice would run its extension factory twice and create
|
|
216
|
+
// separate closure states, so each file is loaded at most once.
|
|
217
|
+
const loadedOverrideFiles = new Set<string>();
|
|
209
218
|
for (const tool of tools) {
|
|
210
219
|
const ext = TOOL_EXTENSION_OVERRIDES[tool];
|
|
211
|
-
if (ext
|
|
220
|
+
if (ext && !loadedOverrideFiles.has(ext)) {
|
|
221
|
+
loadedOverrideFiles.add(ext);
|
|
222
|
+
args.push("-e", extensionPath(ext));
|
|
223
|
+
}
|
|
212
224
|
}
|
|
213
225
|
args.push("--tools", tools.join(","));
|
|
214
226
|
if (systemPromptPath) args.push("--append-system-prompt", systemPromptPath);
|