@trim21/personal-pi-extensions 0.0.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.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # pi-extensions
2
+
3
+ [pi](https://github.com/earendil-works/pi-mono) coding-agent 自定义扩展集合。
4
+
5
+ ## 扩展概览
6
+
7
+ | 扩展 | 描述 |
8
+ | ------------------------------------------------- | ------------------------------------------------------------ |
9
+ | [bwrap](#bwrap) | 基于 bubblewrap 的 OS 级沙箱,提供文件系统和网络隔离 |
10
+ | [workspace-guard](#workspace-guard) | 限制文件写入在 workspace 内,外部写入需用户审批 |
11
+ | [opencode-edit](#opencode-edit) | 替换内置 edit 工具,使用 opencode 的 schema 和匹配引擎 |
12
+ | [bash-default-timeout](#bash-default-timeout) | 为 bash 工具设置默认超时(180 秒) |
13
+ | [agents-md-user-message](#agents-md-user-message) | 将项目级 AGENTS.md 移至 user message,避免占用 system prompt |
14
+
15
+ ---
16
+
17
+ ## bwrap
18
+
19
+ 基于 [bubblewrap](https://github.com/containers/bubblewrap) 的 OS 级沙箱,为所有 bash 命令提供文件系统和网络隔离。
20
+
21
+ **前置条件:** 安装 bubblewrap(`apt install bubblewrap` / `pacman -S bubblewrap` / `dnf install bubblewrap`)。
22
+
23
+ ### 模式
24
+
25
+ 可在运行时切换:
26
+
27
+ | 模式 | 沙箱 | 网络 | 可写文件系统 | 提权方式 |
28
+ | ----------------- | :--: | :--: | ------------------ | -------- |
29
+ | `allow-all` | 关 | 开 | 完整 | 无需 |
30
+ | `workspace-write` | 开 | 关 | workspace + `/tmp` | 用户审批 |
31
+ | `readonly` | 开 | 关 | 无 | 用户审批 |
32
+
33
+ ### 提权机制
34
+
35
+ bash 工具注册了 `request_full_access` 和 `request_full_access_reason` 参数。模型需要全权限时须说明原因(如需要网络、写入 workspace 外部路径)。
36
+
37
+ 建议模型不确定时先尝试沙箱模式,若因沙箱限制失败,再以完整权限重试。
38
+
39
+ ### 保护目录
40
+
41
+ `.git`、`.pi`、`.agent` 即使在 `workspace-write` 模式下也始终只读。
42
+
43
+ ### 运行时命令
44
+
45
+ - `/bwrap` — 显示当前模式和路径配置
46
+ - `/bwrap-allow-all` — 切换到 allow-all 模式
47
+ - `/bwrap-workspace-write` — 切换到 workspace-write 模式
48
+ - `/bwrap-readonly` — 切换到 readonly 模式
49
+
50
+ ### 配置
51
+
52
+ 配置文件(项目优先于全局):
53
+
54
+ - `~/.pi/agent/extensions/bwrap.json`(全局)
55
+ - `.pi/bwrap.json`(项目)
56
+
57
+ ```jsonc
58
+ {
59
+ // "allow-all" | "workspace-write" | "readonly"
60
+ "mode": "workspace-write",
61
+ // 自定义 bwrap 路径(可选)
62
+ "bwrapPath": "/usr/local/bin/bwrap",
63
+ // 可写路径列表,~ 展开为 $HOME,覆盖默认值
64
+ "writablePaths": [".", "/tmp", "~/my-projects"],
65
+ // 额外可写路径,与默认值合并(ro-bind)
66
+ "extraWritablePaths": ["~/.config"],
67
+ // tmpfs 挂载路径(避免写入磁盘)
68
+ "tmpfsPaths": [],
69
+ // 额外 bwrap 参数
70
+ "extraArgs": ["--die-with-parent"],
71
+ }
72
+ ```
73
+
74
+ ### 使用
75
+
76
+ ```bash
77
+ pi -e ./src/bwrap/index.ts
78
+ # 或通过配置文件注册后自动加载
79
+ ```
80
+
81
+ ---
82
+
83
+ ## workspace-guard
84
+
85
+ 阻止 `write` 和 `edit` 工具写入 workspace 外部的路径。读取工具(`read`、`ls`、`find`、`grep`)不受限制。
86
+
87
+ - workspace 内或 `/tmp` 下的路径自动放行
88
+ - 外部路径需通过确认对话框由用户审批
89
+ - 无需配置
90
+
91
+ ### 使用
92
+
93
+ ```bash
94
+ pi -e ./src/workspace-guard.ts
95
+ ```
96
+
97
+ ---
98
+
99
+ ## opencode-edit
100
+
101
+ 替换内置 `edit` 工具,使用 [opencode](https://github.com/anomalyco/opencode) 的 schema 和模糊匹配引擎。核心 replacer 和 `replace()` 函数直接复制自 opencode,行为与原版完全一致。
102
+
103
+ 支持的匹配策略:
104
+
105
+ - 精确匹配(SimpleReplacer)
106
+ - 行尾空白容差(LineTrimmedReplacer)
107
+ - 块首尾锚定(BlockAnchorReplacer)
108
+ - 空白规范化(WhitespaceNormalizedReplacer)
109
+ - 缩进灵活匹配(IndentationFlexibleReplacer)
110
+ - 转义规范化(EscapeNormalizedReplacer)
111
+ - 首尾空白修剪(TrimmedBoundaryReplacer)
112
+ - 上下文感知匹配(ContextAwareReplacer)
113
+ - 多次出现替换(MultiOccurrenceReplacer)
114
+
115
+ 所有匹配策略按顺序尝试,第一个匹配成功即返回。同时自动处理 BOM、CRLF/LF 行尾转换和文件写入队列。
116
+
117
+ ### 使用
118
+
119
+ ```bash
120
+ pi -e ./src/opencode-edit.ts
121
+ ```
122
+
123
+ ---
124
+
125
+ ## bash-default-timeout
126
+
127
+ 为所有 bash 工具调用设置 180 秒默认超时。仅在模型未显式指定 `timeout` 时生效,避免长时间运行的命令无限挂起。
128
+
129
+ ### 使用
130
+
131
+ ```bash
132
+ pi -e ./src/bash-default-timeout.ts
133
+ ```
134
+
135
+ ---
136
+
137
+ ## agents-md-user-message
138
+
139
+ 将项目级 `AGENTS.md` 从 system prompt 移至 user message,减少 system prompt 占用。
140
+
141
+ - 项目级 `AGENTS.md` → 放入 user message(仅首次消息注入一次)
142
+ - 全局 `~/.pi/agent/AGENTS.md` → 保留在 system prompt
143
+
144
+ 需要配合 `--no-context-files` 参数禁用 pi 默认的上下文文件加载。
145
+
146
+ ### 使用
147
+
148
+ ```bash
149
+ pi -e ./src/agents-md-user-message.ts --no-context-files
150
+ ```
151
+
152
+ ---
153
+
154
+ ## 安装
155
+
156
+ ### 通过 npm/git 包
157
+
158
+ ```jsonc
159
+ // ~/.pi/agent/settings.json
160
+ {
161
+ "packages": ["github:trim21/pi-extensions"],
162
+ }
163
+ ```
164
+
165
+ ### 命令行加载单个扩展
166
+
167
+ ```bash
168
+ pi -e ./src/bwrap/index.ts
169
+ pi -e ./src/workspace-guard.ts
170
+ ```
171
+
172
+ ---
173
+
174
+ ## 开发
175
+
176
+ ```bash
177
+ pnpm install # 安装依赖
178
+ pnpm run check # tsc --noEmit + prettier --check
179
+ pnpm run format # prettier --write
180
+ ```
181
+
182
+ ### 新增扩展
183
+
184
+ 1. 在 `src/` 下创建扩展文件
185
+ 2. 在 `package.json` 的 `pi.extensions` 数组中注册
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@trim21/personal-pi-extensions",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
+ "keywords": [
7
+ "pi-package"
8
+ ],
9
+ "files": [
10
+ "src",
11
+ "README.md"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "scripts": {
17
+ "check": "tsc --noEmit && prettier --check .",
18
+ "format": "prettier --write .",
19
+ "build:cli": "esbuild bin/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --external:@earendil-works/pi-coding-agent --external:typebox --external:node:*",
20
+ "test": "vitest run",
21
+ "prepare": "husky"
22
+ },
23
+ "peerDependencies": {
24
+ "@earendil-works/pi-ai": "*",
25
+ "@earendil-works/pi-agent-core": "*",
26
+ "@earendil-works/pi-coding-agent": "*",
27
+ "@earendil-works/pi-tui": "*",
28
+ "typebox": "*"
29
+ },
30
+ "devDependencies": {
31
+ "@earendil-works/pi-ai": "^0.80.6",
32
+ "@earendil-works/pi-coding-agent": "^0.80.0",
33
+ "@types/node": "^24.0.0",
34
+ "esbuild": "^0.28.1",
35
+ "husky": "^9.1.7",
36
+ "lint-staged": "^17.0.8",
37
+ "prettier": "^3.6.0",
38
+ "typebox": "1.3.1",
39
+ "typescript": "^7.0.0",
40
+ "vitest": "^4.1.10"
41
+ },
42
+ "pi": {
43
+ "extensions": [
44
+ "src/bwrap/index.ts",
45
+ "src/workspace-guard.ts",
46
+ "src/opencode-edit.ts",
47
+ "src/bash-default-timeout.ts",
48
+ "src/agents-md-user-message.ts",
49
+ "src/gh-readonly.ts",
50
+ "src/todo-pendant.ts"
51
+ ]
52
+ },
53
+ "lint-staged": {
54
+ "*.{ts,tsx,js,jsx,json,md,yaml,yml}": [
55
+ "prettier --write"
56
+ ]
57
+ },
58
+ "packageManager": "pnpm@11.18.0"
59
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * AGENTS.md User Message
3
+ *
4
+ * Moves project-level AGENTS.md from system prompt to user message.
5
+ * Global ~/.pi/agent/AGENTS.md stays in system prompt.
6
+ *
7
+ * Requires --no-context-files to disable pi's default context file loading.
8
+ *
9
+ * Usage:
10
+ * pi -e ./agents-md-user-message --no-context-files
11
+ */
12
+
13
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
+ import { getAgentDir, loadProjectContextFiles } from "@earendil-works/pi-coding-agent";
15
+
16
+ export default function (pi: ExtensionAPI) {
17
+ let messageInjected = false;
18
+
19
+ pi.on("before_agent_start", (event) => {
20
+ const contextFiles = event.systemPromptOptions.contextFiles ?? [];
21
+ if (contextFiles.length > 0) return; // pi already handled them
22
+
23
+ // --no-context-files was used, we handle everything
24
+ const files = loadProjectContextFiles({
25
+ cwd: event.systemPromptOptions.cwd,
26
+ agentDir: getAgentDir(),
27
+ });
28
+ if (files.length === 0) return;
29
+
30
+ const agentDir = getAgentDir();
31
+ const globalFiles = files.filter((f) => f.path.startsWith(agentDir));
32
+ const projectFiles = files.filter((f) => !f.path.startsWith(agentDir));
33
+
34
+ if (projectFiles.length === 0) return;
35
+
36
+ let systemPrompt = event.systemPrompt;
37
+ if (globalFiles.length > 0) {
38
+ let block = "\n\n<project_context>\n\nProject-specific instructions and guidelines:\n\n";
39
+ for (const { path, content } of globalFiles) {
40
+ block += `<project_instructions path="${path}">\n${content}\n</project_instructions>\n\n`;
41
+ }
42
+ block += "</project_context>\n";
43
+ systemPrompt += block;
44
+ }
45
+
46
+ if (!messageInjected) {
47
+ messageInjected = true;
48
+
49
+ const content = projectFiles
50
+ .map(
51
+ (f) => `<project_instructions path="${f.path}">\n${f.content}\n</project_instructions>`,
52
+ )
53
+ .join("\n\n");
54
+
55
+ return {
56
+ systemPrompt,
57
+ message: {
58
+ customType: "agents-md-user",
59
+ content,
60
+ display: true,
61
+ },
62
+ };
63
+ }
64
+
65
+ return { systemPrompt };
66
+ });
67
+
68
+ pi.on("session_shutdown", () => {
69
+ messageInjected = false;
70
+ });
71
+ }
@@ -0,0 +1,9 @@
1
+ import { isToolCallEventType, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+
3
+ export default function (pi: ExtensionAPI) {
4
+ pi.on("tool_call", (event) => {
5
+ if (isToolCallEventType("bash", event) && event.input.timeout === undefined) {
6
+ event.input.timeout = 180;
7
+ }
8
+ });
9
+ }