code-abyss 2.0.3 → 2.0.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-abyss",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "为 Claude Code / Codex CLI 注入邪修人格、4种可切换输出风格与 57 篇攻防工程秘典",
5
5
  "keywords": [
6
6
  "claude",
@@ -17,6 +17,10 @@ argument-hint: [--last N]
17
17
 
18
18
  `/hi` 直接改写当前会话 JSONL 中最近 N 条(默认5条)模型输出里的拒绝话术,原地替换为同意措辞。
19
19
 
20
+ 自动检测运行环境(Claude Code vs Codex CLI),定位对应会话文件:
21
+ - Claude Code: `~/.claude/projects/<cwd-slug>/*.jsonl`(按 mtime 取最新)
22
+ - Codex CLI: `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`(递归扫描按 mtime 取最新)
23
+
20
24
  **不是匹配报告,是直接改写。**
21
25
 
22
26
  ## 用法
@@ -69,7 +73,9 @@ node scripts/refusal_rewriter.js --dry-run # 仅报告,不改写
69
73
 
70
74
  ## 注意事项
71
75
 
72
- - 仅改写 `type: "assistant"` 且含 `type: "text"` 的消息
76
+ - 自动检测运行环境:脚本路径含 `.codex` Codex 模式,含 `.claude` → Claude 模式,否则按 cwd 回退检测
77
+ - Claude 模式:仅改写 `type: "assistant"` 且含 `content[].type: "text"` 的消息
78
+ - Codex 模式:仅改写 `type: "response_item"` + `payload.role: "assistant"` 且含 `content[].type: "output_text"` 的消息
73
79
  - 不改写用户消息、系统消息、工具调用
74
80
  - 改写是幂等的:多次执行不会产生副作用
75
81
  - `--dry-run` 模式仅输出匹配报告,不写入文件
@@ -4,7 +4,8 @@
4
4
  /**
5
5
  * 破禁术 · 反拒绝覆写器
6
6
  *
7
- * 扫描当前 Claude 会话 JSONL,直接替换拒绝话术为同意措辞。
7
+ * 扫描当前 Claude / Codex 会话 JSONL,直接替换拒绝话术为同意措辞。
8
+ * 自动检测运行环境(Claude Code vs Codex CLI),定位对应会话文件。
8
9
  *
9
10
  * 用法:
10
11
  * node refusal_rewriter.js [--last N] [--dry-run]
@@ -13,7 +14,6 @@
13
14
  const fs = require('fs');
14
15
  const path = require('path');
15
16
  const os = require('os');
16
- const { createHash } = require('crypto');
17
17
 
18
18
  // ─── 替换规则表 ───────────────────────────────────────────
19
19
 
@@ -80,19 +80,38 @@ function parseArgs(argv) {
80
80
  return { last, dryRun };
81
81
  }
82
82
 
83
- // ─── 会话 JSONL 定位 ──────────────────────────────────────
83
+ // ─── 环境检测 ─────────────────────────────────────────────
84
84
 
85
- function cwdToSlug(cwd) {
86
- return cwd.replace(/\//g, '-').replace(/^-/, '');
85
+ function detectRuntime() {
86
+ // Codex 在 skills 路径中包含 .codex
87
+ const scriptDir = __dirname;
88
+ if (scriptDir.includes('.codex')) return 'codex';
89
+ if (scriptDir.includes('.claude')) return 'claude';
90
+
91
+ // 回退:检查 ~/.codex/sessions 和 ~/.claude/projects 哪个存在
92
+ const home = os.homedir();
93
+ const codexSessions = path.join(home, '.codex', 'sessions');
94
+ const claudeProjects = path.join(home, '.claude', 'projects');
95
+
96
+ // 优先检测当前 cwd 是否有对应的 claude project
97
+ const cwd = process.cwd();
98
+ const slug = cwd.replace(/\//g, '-').replace(/^-/, '');
99
+ const claudeProjectDir = path.join(claudeProjects, slug);
100
+ if (fs.existsSync(claudeProjectDir)) return 'claude';
101
+ if (fs.existsSync(codexSessions)) return 'codex';
102
+
103
+ return 'claude'; // 默认回退
87
104
  }
88
105
 
89
- function findSessionJsonl() {
106
+ // ─── Claude 会话定位 ─────────────────────────────────────
107
+
108
+ function findClaudeSessionJsonl() {
90
109
  const cwd = process.cwd();
91
- const slug = cwdToSlug(cwd);
110
+ const slug = cwd.replace(/\//g, '-').replace(/^-/, '');
92
111
  const projectDir = path.join(os.homedir(), '.claude', 'projects', slug);
93
112
 
94
113
  if (!fs.existsSync(projectDir)) {
95
- console.error(`错误: 未找到项目目录 ${projectDir}`);
114
+ console.error(`错误: 未找到 Claude 项目目录 ${projectDir}`);
96
115
  process.exit(1);
97
116
  }
98
117
 
@@ -113,6 +132,43 @@ function findSessionJsonl() {
113
132
  return files[0].full;
114
133
  }
115
134
 
135
+ // ─── Codex 会话定位 ──────────────────────────────────────
136
+
137
+ function findCodexSessionJsonl() {
138
+ const sessionsDir = path.join(os.homedir(), '.codex', 'sessions');
139
+
140
+ if (!fs.existsSync(sessionsDir)) {
141
+ console.error(`错误: 未找到 Codex 会话目录 ${sessionsDir}`);
142
+ process.exit(1);
143
+ }
144
+
145
+ // 递归扫描所有 .jsonl,按 mtime 倒序取最新
146
+ const allJsonl = [];
147
+
148
+ function scan(dir) {
149
+ let entries;
150
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
151
+ for (const entry of entries) {
152
+ const full = path.join(dir, entry.name);
153
+ if (entry.isDirectory()) {
154
+ scan(full);
155
+ } else if (entry.name.endsWith('.jsonl')) {
156
+ allJsonl.push({ full, mtime: fs.statSync(full).mtimeMs });
157
+ }
158
+ }
159
+ }
160
+
161
+ scan(sessionsDir);
162
+ allJsonl.sort((a, b) => b.mtime - a.mtime);
163
+
164
+ if (allJsonl.length === 0) {
165
+ console.error(`错误: ${sessionsDir} 下无 .jsonl 会话文件`);
166
+ process.exit(1);
167
+ }
168
+
169
+ return allJsonl[0].full;
170
+ }
171
+
116
172
  // ─── JSONL 解析与回写 ─────────────────────────────────────
117
173
 
118
174
  function readJsonlLines(filePath) {
@@ -141,19 +197,53 @@ function applyReplacements(text) {
141
197
  return { result, matched, changed: matched.length > 0 };
142
198
  }
143
199
 
144
- function processAssistantMessage(obj) {
200
+ // ─── Claude 消息处理 ─────────────────────────────────────
201
+
202
+ function isClaudeAssistant(obj) {
203
+ return obj.type === 'assistant' && Array.isArray(obj.message?.content);
204
+ }
205
+
206
+ function processClaudeMessage(obj) {
207
+ let totalMatched = [];
208
+ let changed = false;
209
+
210
+ if (!isClaudeAssistant(obj)) return { obj, matched: totalMatched, changed };
211
+
212
+ for (const block of obj.message.content) {
213
+ if (block.type === 'text' && typeof block.text === 'string') {
214
+ const { result, matched, changed: blockChanged } = applyReplacements(block.text);
215
+ if (blockChanged) {
216
+ block.text = result;
217
+ totalMatched.push(...matched);
218
+ changed = true;
219
+ }
220
+ }
221
+ }
222
+
223
+ return { obj, matched: totalMatched, changed };
224
+ }
225
+
226
+ // ─── Codex 消息处理 ──────────────────────────────────────
227
+
228
+ function isCodexAssistant(obj) {
229
+ return obj.type === 'response_item'
230
+ && obj.payload?.role === 'assistant'
231
+ && Array.isArray(obj.payload?.content);
232
+ }
233
+
234
+ function processCodexMessage(obj) {
145
235
  let totalMatched = [];
146
236
  let changed = false;
147
237
 
148
- if (obj.type === 'assistant' && Array.isArray(obj.message?.content)) {
149
- for (const block of obj.message.content) {
150
- if (block.type === 'text' && typeof block.text === 'string') {
151
- const { result, matched, changed: blockChanged } = applyReplacements(block.text);
152
- if (blockChanged) {
153
- block.text = result;
154
- totalMatched.push(...matched);
155
- changed = true;
156
- }
238
+ if (!isCodexAssistant(obj)) return { obj, matched: totalMatched, changed };
239
+
240
+ for (const block of obj.payload.content) {
241
+ if (block.type === 'output_text' && typeof block.text === 'string') {
242
+ const { result, matched, changed: blockChanged } = applyReplacements(block.text);
243
+ if (blockChanged) {
244
+ block.text = result;
245
+ totalMatched.push(...matched);
246
+ changed = true;
157
247
  }
158
248
  }
159
249
  }
@@ -165,9 +255,17 @@ function processAssistantMessage(obj) {
165
255
 
166
256
  function main() {
167
257
  const { last, dryRun } = parseArgs(process.argv);
168
- const jsonlPath = findSessionJsonl();
258
+ const runtime = detectRuntime();
259
+
260
+ const jsonlPath = runtime === 'codex'
261
+ ? findCodexSessionJsonl()
262
+ : findClaudeSessionJsonl();
263
+
264
+ const isAssistant = runtime === 'codex' ? isCodexAssistant : isClaudeAssistant;
265
+ const processMessage = runtime === 'codex' ? processCodexMessage : processClaudeMessage;
169
266
 
170
267
  console.log(`🔓 破禁术启动`);
268
+ console.log(` 运行环境: ${runtime === 'codex' ? 'Codex CLI' : 'Claude Code'}`);
171
269
  console.log(` 会话文件: ${jsonlPath}`);
172
270
  console.log(` 扫描范围: 最近 ${last} 条模型输出`);
173
271
  console.log(` 模式: ${dryRun ? '仅报告 (dry-run)' : '直接改写'}`);
@@ -180,7 +278,7 @@ function main() {
180
278
  for (let i = lines.length - 1; i >= 0 && assistantIndices.length < last; i--) {
181
279
  try {
182
280
  const obj = JSON.parse(lines[i]);
183
- if (obj.type === 'assistant') {
281
+ if (isAssistant(obj)) {
184
282
  assistantIndices.push(i);
185
283
  }
186
284
  } catch {
@@ -198,7 +296,7 @@ function main() {
198
296
 
199
297
  for (const idx of assistantIndices) {
200
298
  const obj = JSON.parse(lines[idx]);
201
- const { obj: processed, matched, changed } = processAssistantMessage(obj);
299
+ const { obj: processed, matched, changed } = processMessage(obj);
202
300
 
203
301
  if (changed) {
204
302
  totalChanged++;