agent-purge 0.0.0-stage → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZiChuanLan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,76 @@
1
- # Temporary Holding Version
1
+ # agent-purge
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 一份系统提示词,喂给多个 coding agent。支持替换(replace)、追加(append)、清空(off)三种模式,可热切换,全程可逆。
4
+
5
+ ```
6
+ agent-purge web # 本地控制台 http://127.0.0.1:7788
7
+ agent-purge codex # 用当前 persona 启动 codex
8
+ agent-purge pi --ask "…" # 一次性提问
9
+ ```
10
+
11
+ ## 它做什么
12
+
13
+ 每家 coding agent 读系统提示词的位置都不一样:有的认启动参数,有的认配置文件,有的只能靠插件改运行时对象。agent-purge 把这些入口统一成三件事:
14
+
15
+ | 通道 | 机制 | 生效范围 |
16
+ |---|---|---|
17
+ | `install` | 写入该 harness 的托管配置块(Markdown / hooks / 插件) | 常驻,别人启动也生效 |
18
+ | `shim` | 往 PATH 前置同名小壳,转发给 `agent-purge <h>` | 裸敲命令也接管 |
19
+ | `slash` | 会话内 `/purge on\|off\|status` | 热切换,不动磁盘 |
20
+
21
+ 三层都写的是同一份 `personas/<id>.md` 的正文。`--mode off` 或 Web 总开关关掉时,全部撤回到原样——不留残渣。
22
+
23
+ ## 安装
24
+
25
+ ```bash
26
+ npm i -g agent-purge # 或 npx agent-purge web
27
+ ```
28
+
29
+ 要求 Node ≥ 20,零依赖。
30
+
31
+ **数据独立存放**,升级不会被覆盖:默认 `~/.agent-purge/`(Windows `C:\Users\<你>\.agent-purge\`),里面是 `state.json` 和 `personas/`。想换位置就设环境变量:
32
+
33
+ ```bash
34
+ export AGENT_PURGE_HOME=/path/to/your/data # Windows: setx AGENT_PURGE_HOME D:\ap-data
35
+ ```
36
+
37
+ 旧版本(0.1.x)把数据放在包目录里,首次运行会自动搬到新家,原文件不动。
38
+
39
+ ## 人设
40
+
41
+ `personas/<id>.md` = frontmatter(`id` / `name` / `description` / `harness` / `model`)+ 正文。正文就是会被写进系统提示词的那一段。
42
+
43
+ ```bash
44
+ agent-purge ls # 列出全部 persona,标出当前匹配的
45
+ agent-purge use <id> # 切换当前 persona
46
+ agent-purge pi --persona <id> # 只这一轮用别的
47
+ ```
48
+
49
+ 首次运行会在 `personas/` 下生成 `neutral.md` 起步模板。
50
+
51
+ ## 支持情况
52
+
53
+ `agent-purge ls` 和 Web 控制台显示的是**运行期真值**:每个 harness 有没有装、版本、注入层(SYS / USER)、三个通道各自可用性。`matrix.json` 里是静态能力表,两者冲突时以运行期为准。
54
+
55
+ 闭源桌面端(提示词在服务端或二进制里)只做原样启动,不做任何注入——`agent-purge <h>` 会明确打印原因,不会假装生效。
56
+
57
+ ## 目录
58
+
59
+ ```
60
+ bin/ CLI 入口
61
+ lib/ 状态、persona、注入、通道、检测
62
+ backends/ 每个 harness 的启动参数与身份句处理
63
+ web/ 控制台(server.mjs + ui.html)
64
+ bench/ 注入成功率实测与裁判脚本(开发用,不参与运行)
65
+ matrix.json 能力矩阵
66
+ ```
67
+
68
+ ## 边界
69
+
70
+ - 只改提示词相关的位置,不碰二进制、不破解、不绕过任何授权。
71
+ - 所有改动可逆:`agent-purge uninstall <h>` / `disarm` / `--mode off`。
72
+ - 状态只存本机 `state.json`,不上传任何数据。
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,181 @@
1
+ // 每家的 build() 是纯函数:只描述"怎么启动",不 spawn、不写盘。
2
+ // setup/cleanup 由入口统一调用,保证能按哈希回滚。
3
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ const inline = (text) => text.replace(/\s+$/, "");
7
+
8
+ export const backends = {
9
+ // pi:命令行已试过 --system-prompt,但 pi.cmd 走 cmd.exe,10K 人设会撞 8191 上限(实测“命令行太长”)。
10
+ // 所以 pi 的注入靠扩展(sys 层、无长度限制、跟 state.json 走),这里只透传。
11
+ // 扩展由 launch() 在启动前自动确保安装。
12
+ pi: ({ args }) => ({ cmd: "pi", args }),
13
+
14
+ codex: ({ personaPath, personaText, mode, args }) => ({
15
+ cmd: "codex",
16
+ args:
17
+ mode === "off"
18
+ ? [...args]
19
+ : mode === "append"
20
+ ? ["-c", `developer_instructions=${inline(personaText)}`, ...args]
21
+ : ["-c", `model_instructions_file=${personaPath.replace(/\\/g, "/")}`, ...args],
22
+ }),
23
+
24
+ // CodeBuddy(腾讯云,npm @tencent-ai/codebuddy-code):原生整段替换,无残留
25
+ codebuddy: ({ personaPath, personaText, mode, args }) => ({
26
+ cmd: "codebuddy",
27
+ args:
28
+ mode === "off"
29
+ ? [...args]
30
+ : mode === "append"
31
+ ? ["--append-system-prompt", inline(personaText), ...args]
32
+ // replace 走文件:内联 9.6K 会把裸命令(.cmd 包装器)撞上 8191 上限
33
+ : ["--system-prompt-file", personaPath, ...args],
34
+ }),
35
+
36
+ claude: ({ personaPath, mode, args }) => ({
37
+ cmd: "claude",
38
+ args:
39
+ mode === "off"
40
+ ? [...args]
41
+ : mode === "append"
42
+ ? ["--append-system-prompt-file", personaPath, ...args]
43
+ : ["--system-prompt-file", personaPath, ...args],
44
+ residueHint: mode === "off" ? "" : "不补丁时残留 62 字身份句",
45
+ }),
46
+
47
+ grok: ({ personaText, mode, args }) => ({
48
+ cmd: "grok",
49
+ args:
50
+ mode === "off"
51
+ ? [...args]
52
+ : mode === "append"
53
+ ? ["--rules", inline(personaText), ...args]
54
+ : ["--system-prompt-override", inline(personaText), ...args],
55
+ }),
56
+
57
+ omp: ({ personaText, mode, args }) =>
58
+ mode === "off"
59
+ ? { cmd: "omp", args: [...args] }
60
+ : {
61
+ cmd: "omp",
62
+ args: ["--system-prompt", inline(personaText), ...args],
63
+ residueHint: "MCP tool routes 仍会追加(10K 量级),关掉 MCP server 可缩",
64
+ },
65
+
66
+ // opencode 靠 cwd 下 .opencode/plugin/*.ts 发现插件,必须原地 splice
67
+ opencode: ({ personaText, mode, cwd, args }) => {
68
+ if (mode === "off") return { cmd: "opencode", args: [...args] };
69
+ const dir = join(cwd, ".opencode", "plugin");
70
+ const file = join(dir, "agent-purge.ts");
71
+ const src = `export const AgentPurge = async () => ({
72
+ "experimental.chat.system.transform": async (_input, output) => {
73
+ output.system.splice(0, output.system.length, ${JSON.stringify(inline(personaText))});
74
+ },
75
+ });
76
+ `;
77
+ return {
78
+ cmd: "opencode",
79
+ note: "[plugin] .opencode/plugin/agent-purge.ts(原地 splice output.system)",
80
+ args,
81
+ setup: () => {
82
+ mkdirSync(dir, { recursive: true });
83
+ writeFileSync(file, src);
84
+ },
85
+ cleanup: () => rmSync(file, { force: true }),
86
+ };
87
+ },
88
+
89
+ // 需要落盘或调用方配合,CLI 不代劳,只给可复制的配方
90
+ zcode: () => ({
91
+ manual: [
92
+ "CLI 拿不到 customSystemPrompt(只有 app-server/SDK 能传);CLI 只有 UserPromptSubmit hook 注入,且落在 user 角色。",
93
+ "注入配方: ~/.zcode/cli/config.json 里 hooks.enabled=true + UserPromptSubmit hook 返回 additionalContext。",
94
+ "归零配方: 等长替换 resources/glm/zcode.cjs 里的 IJs(42 字),但 cli_prefix 段仍留 42 字。",
95
+ ],
96
+ }),
97
+
98
+ hermes: () => ({
99
+ manual: [
100
+ "hermes 从 $HERMES_HOME/SOUL.md 取身份段。",
101
+ "配方: 复制现有 HERMES_HOME 到 .sandbox/hermes-home,覆盖 SOUL.md,再 HERMES_HOME=<该目录> 启动。",
102
+ "必须加 --ignore-rules: Hermes 自带的 rules 层会把人设顶回去(实测不加时它自称 Hermes 并明说“这一条我不照办”,加了立刻完全采纳)。",
103
+ "注意首次启动会自装 Python 依赖(约 80 秒)。",
104
+ ],
105
+ }),
106
+
107
+ antigravity: () => ({
108
+ manual: [
109
+ "prompt 在 resources/bin/language_server.exe(Go embed.FS)。",
110
+ "闭源 App 的身份句在二进制里,只能等长替换;改动会使签名失效、更新被覆盖,所以默认不做。",
111
+ ],
112
+ }),
113
+
114
+ qoder: ({ reason }) => ({ unsupported: reason }),
115
+ workbuddy: ({ reason }) => ({ unsupported: reason }),
116
+ };
117
+
118
+
119
+
120
+
121
+ // 一次性问答(headless)规格:Web UI 与 `agent-purge <h> --ask` 共用。
122
+ // 只收录实测过参数的 harness,没验过的不猜。
123
+ export const oneshots = {
124
+ pi: ({ prompt }) => ({
125
+ cmd: "pi",
126
+ args: ["-p", prompt],
127
+ note: "注入靠扩展(sys 层);人设走 state.json,不占命令行",
128
+ }),
129
+ claude: ({ prompt, personaPath, mode }) => ({
130
+ cmd: "claude",
131
+ args: ["-p", prompt, ...(mode === "off" ? [] : [mode === "append" ? "--append-system-prompt-file" : "--system-prompt-file", personaPath])],
132
+ }),
133
+ grok: ({ prompt, personaText, mode }) => ({
134
+ cmd: "grok",
135
+ args: ["-p", prompt, "--always-approve", ...(mode === "off" ? [] : [mode === "append" ? "--rules" : "--system-prompt-override", inline(personaText)])],
136
+ }),
137
+ omp: ({ prompt, personaText, mode }) => ({
138
+ cmd: "omp",
139
+ args: ["-p", ...(mode === "off" ? [] : ["--system-prompt", inline(personaText)]), prompt],
140
+ }),
141
+ codex: ({ prompt, personaPath, personaText, mode }) => ({
142
+ cmd: "codex",
143
+ args: [
144
+ ...(mode === "off"
145
+ ? []
146
+ : ["-c", mode === "append" ? `developer_instructions=${inline(personaText)}` : `model_instructions_file=${personaPath.replace(/\\/g, "/")}`]),
147
+ "exec",
148
+ prompt,
149
+ ],
150
+ }),
151
+ opencode: ({ prompt, personaText, mode, cwd }) => {
152
+ if (mode === "off") return { cmd: "opencode", args: ["run", prompt] };
153
+ const b = backends.opencode({ personaText, cwd, args: ["run", prompt] });
154
+ return b;
155
+ },
156
+ zcode: ({ prompt }) => ({
157
+ cmd: "zcode",
158
+ args: ["--prompt", prompt, "--mode", "yolo", "--output-format", "stream-json", "--no-color"],
159
+ note: "persona 对 zcode CLI 不生效:customSystemPrompt 只有 app-server/SDK 能传",
160
+ }),
161
+ codebuddy: ({ prompt, personaPath, personaText, mode }) => ({
162
+ cmd: "codebuddy",
163
+ args: [
164
+ "-p",
165
+ prompt,
166
+ "--max-turns",
167
+ "5",
168
+ ...(mode === "off"
169
+ ? []
170
+ : mode === "append"
171
+ ? ["--append-system-prompt", inline(personaText)]
172
+ : ["--system-prompt-file", personaPath]),
173
+ ],
174
+ }),
175
+ hermes: ({ prompt, mode }) => ({
176
+ cmd: "hermes",
177
+ // --ignore-rules 是关键:不加时 Hermes 自带 rules 层会把人设顶回去(模型自称 Hermes、明说“不照办”)。
178
+ args: ["-z", prompt, ...(mode === "off" ? [] : ["--ignore-rules"])],
179
+ note: "身份段来自 $HERMES_HOME/SOUL.md;注入时必须带 --ignore-rules,关电源时不带",
180
+ }),
181
+ };