code-abyss 1.7.3 → 1.7.5
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 +51 -2
- package/bin/adapters/claude.js +150 -0
- package/bin/adapters/codex.js +307 -0
- package/bin/install.js +39 -156
- package/config/codex-config.example.toml +18 -24
- package/package.json +1 -1
- package/skills/tools/gen-docs/scripts/doc_generator.js +18 -3
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ npx code-abyss
|
|
|
25
25
|
交互式菜单(方向键选择,回车确认):
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
☠️ Code Abyss v1.7.
|
|
28
|
+
☠️ Code Abyss v1.7.3
|
|
29
29
|
|
|
30
30
|
? 请选择操作 (Use arrow keys)
|
|
31
31
|
❯ 安装到 Claude Code (~/.claude/)
|
|
@@ -101,7 +101,7 @@ Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命
|
|
|
101
101
|
- 🔥 **邪修人格** — 宿命压迫叙事 + 道语标签 + 渡劫协议
|
|
102
102
|
- ⚔️ **安全工程知识体系** — 红队/蓝队/紫队三脉道统,11 领域 56 篇专业秘典
|
|
103
103
|
- ⚖️ **5 个校验关卡** — 安全扫描、模块完整性、变更分析、代码质量、文档生成
|
|
104
|
-
- ✅
|
|
104
|
+
- ✅ **单元测试覆盖** — Jest 框架,GitHub Actions CI (Node 18/20/22)
|
|
105
105
|
- ⚡ **三级授权** — T1/T2/T3 分级,零确认直接执行
|
|
106
106
|
|
|
107
107
|
---
|
|
@@ -154,6 +154,8 @@ Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命
|
|
|
154
154
|
|
|
155
155
|
## ⚙️ 推荐配置
|
|
156
156
|
|
|
157
|
+
### Claude `settings.json` 推荐模板
|
|
158
|
+
|
|
157
159
|
安装时选择「精细合并」会自动写入,也可手动参考 [`config/settings.example.json`](config/settings.example.json):
|
|
158
160
|
|
|
159
161
|
```json
|
|
@@ -183,6 +185,53 @@ Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命
|
|
|
183
185
|
|
|
184
186
|
---
|
|
185
187
|
|
|
188
|
+
### Codex `config.toml` 推荐模板
|
|
189
|
+
|
|
190
|
+
安装 `--target codex`(尤其 `-y`)时会写入以下模板到 `~/.codex/config.toml`:
|
|
191
|
+
|
|
192
|
+
```toml
|
|
193
|
+
model_provider = "custom"
|
|
194
|
+
model = "gpt-5.2-codex"
|
|
195
|
+
model_reasoning_effort = "high"
|
|
196
|
+
approval_policy = "never"
|
|
197
|
+
sandbox_mode = "danger-full-access"
|
|
198
|
+
disable_response_storage = true
|
|
199
|
+
|
|
200
|
+
[model_providers.custom]
|
|
201
|
+
name = "custom"
|
|
202
|
+
base_url = "https://your-api-endpoint.com/v1"
|
|
203
|
+
wire_api = "responses"
|
|
204
|
+
requires_openai_auth = true
|
|
205
|
+
|
|
206
|
+
[tools]
|
|
207
|
+
web_search = true
|
|
208
|
+
|
|
209
|
+
[features]
|
|
210
|
+
multi_agent = true
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
### 兼容性说明
|
|
215
|
+
|
|
216
|
+
- 模板已对齐新版 Codex 配置风格:`[tools].web_search` 与 `[features].multi_agent`
|
|
217
|
+
- 若你本地已有旧配置,安装器不会强制覆盖;会自动做三件事:补齐缺失默认项、清理 removed feature、将 deprecated `web_search_*` 迁移为 `[tools].web_search`
|
|
218
|
+
- 建议在升级后执行一次 `codex --help` / 启动自检,确认无 deprecation warning
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 🧩 适配器解耦(Claude / Codex)
|
|
223
|
+
|
|
224
|
+
为避免过度耦合,安装器按目标 CLI 拆分适配层:
|
|
225
|
+
|
|
226
|
+
- `bin/install.js`:保留通用编排(参数解析、安装/卸载流程、备份恢复)
|
|
227
|
+
- `bin/adapters/claude.js`:Claude 侧认证检测、settings merge、可选配置流程
|
|
228
|
+
- `bin/lib/ccline.js`:Claude 侧状态栏与 ccline 集成
|
|
229
|
+
- `bin/adapters/codex.js`:Codex 侧认证检测、核心文件映射、config 模板流程
|
|
230
|
+
|
|
231
|
+
当前 Claude/Codex 安装映射分别由 `getClaudeCoreFiles()` 与 `getCodexCoreFiles()` 提供,避免在主流程硬编码目标细节。
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
186
235
|
## 🎯 授权分级
|
|
187
236
|
|
|
188
237
|
| 级别 | 范围 | 行为 |
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const SETTINGS_TEMPLATE = {
|
|
7
|
+
$schema: 'https://json.schemastore.org/claude-code-settings.json',
|
|
8
|
+
env: {
|
|
9
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
10
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1'
|
|
11
|
+
},
|
|
12
|
+
alwaysThinkingEnabled: true,
|
|
13
|
+
model: 'opus',
|
|
14
|
+
outputStyle: 'abyss-cultivator',
|
|
15
|
+
attribution: { commit: '', pr: '' },
|
|
16
|
+
permissions: {
|
|
17
|
+
allow: [
|
|
18
|
+
'Bash', 'LS', 'Read', 'Agent', 'Write', 'Edit', 'MultiEdit',
|
|
19
|
+
'Glob', 'Grep', 'WebFetch', 'WebSearch', 'TodoWrite',
|
|
20
|
+
'NotebookRead', 'NotebookEdit'
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const CCLINE_CMD = process.platform === 'win32' ? 'ccline' : '~/.claude/ccline/ccline';
|
|
26
|
+
const CCLINE_STATUS_LINE = {
|
|
27
|
+
statusLine: {
|
|
28
|
+
type: 'command',
|
|
29
|
+
command: CCLINE_CMD,
|
|
30
|
+
padding: 0
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function getClaudeCoreFiles() {
|
|
35
|
+
return [
|
|
36
|
+
{ src: 'config/CLAUDE.md', dest: 'CLAUDE.md' },
|
|
37
|
+
{ src: 'output-styles', dest: 'output-styles' },
|
|
38
|
+
{ src: 'skills', dest: 'skills' },
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function detectClaudeAuth({
|
|
43
|
+
settings = {},
|
|
44
|
+
HOME,
|
|
45
|
+
env = process.env,
|
|
46
|
+
warn = () => {}
|
|
47
|
+
}) {
|
|
48
|
+
const settingsEnv = settings.env || {};
|
|
49
|
+
if (settingsEnv.ANTHROPIC_BASE_URL && settingsEnv.ANTHROPIC_AUTH_TOKEN) {
|
|
50
|
+
return { type: 'custom', detail: settingsEnv.ANTHROPIC_BASE_URL };
|
|
51
|
+
}
|
|
52
|
+
if (env.ANTHROPIC_API_KEY) return { type: 'env', detail: 'ANTHROPIC_API_KEY' };
|
|
53
|
+
if (env.ANTHROPIC_BASE_URL && env.ANTHROPIC_AUTH_TOKEN) {
|
|
54
|
+
return { type: 'env-custom', detail: env.ANTHROPIC_BASE_URL };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const cred = path.join(HOME, '.claude', '.credentials.json');
|
|
58
|
+
if (fs.existsSync(cred)) {
|
|
59
|
+
try {
|
|
60
|
+
const cc = JSON.parse(fs.readFileSync(cred, 'utf8'));
|
|
61
|
+
if (cc.claudeAiOauth || cc.apiKey) return { type: 'login', detail: 'claude login' };
|
|
62
|
+
} catch (e) {
|
|
63
|
+
warn(`凭证文件损坏: ${cred}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function configureCustomProvider(ctx, { ok }) {
|
|
71
|
+
const { confirm, input } = await import('@inquirer/prompts');
|
|
72
|
+
const doCfg = await confirm({ message: '配置自定义 provider?', default: false });
|
|
73
|
+
if (!doCfg) return;
|
|
74
|
+
|
|
75
|
+
if (!ctx.settings.env) ctx.settings.env = {};
|
|
76
|
+
const url = await input({ message: 'ANTHROPIC_BASE_URL:' });
|
|
77
|
+
const token = await input({ message: 'ANTHROPIC_AUTH_TOKEN:' });
|
|
78
|
+
if (url) ctx.settings.env.ANTHROPIC_BASE_URL = url;
|
|
79
|
+
if (token) ctx.settings.env.ANTHROPIC_AUTH_TOKEN = token;
|
|
80
|
+
|
|
81
|
+
fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
|
|
82
|
+
ok('provider 已配置');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function mergeSettings(ctx, { deepMergeNew, printMergeLog, c, ok }) {
|
|
86
|
+
const log = [];
|
|
87
|
+
deepMergeNew(ctx.settings, SETTINGS_TEMPLATE, '', log);
|
|
88
|
+
printMergeLog(log, c);
|
|
89
|
+
fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
|
|
90
|
+
ok('settings.json 合并完成');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function postClaude({
|
|
94
|
+
ctx,
|
|
95
|
+
autoYes,
|
|
96
|
+
HOME,
|
|
97
|
+
PKG_ROOT,
|
|
98
|
+
step,
|
|
99
|
+
ok,
|
|
100
|
+
warn,
|
|
101
|
+
info,
|
|
102
|
+
c,
|
|
103
|
+
deepMergeNew,
|
|
104
|
+
printMergeLog,
|
|
105
|
+
installCcline
|
|
106
|
+
}) {
|
|
107
|
+
step(2, 3, '认证检测');
|
|
108
|
+
const auth = detectClaudeAuth({ settings: ctx.settings, HOME, warn });
|
|
109
|
+
if (auth) {
|
|
110
|
+
ok(`${c.b(auth.type)} → ${auth.detail}`);
|
|
111
|
+
} else {
|
|
112
|
+
warn('未检测到 API 认证');
|
|
113
|
+
info(`支持: ${c.cyn('claude login')} | ${c.cyn('ANTHROPIC_API_KEY')} | ${c.cyn('自定义 provider')}`);
|
|
114
|
+
if (!autoYes) await configureCustomProvider(ctx, { ok });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
step(3, 3, '可选配置');
|
|
118
|
+
if (autoYes) {
|
|
119
|
+
info('自动模式: 合并推荐配置');
|
|
120
|
+
mergeSettings(ctx, { deepMergeNew, printMergeLog, c, ok });
|
|
121
|
+
await installCcline(ctx, { HOME, PKG_ROOT, CCLINE_STATUS_LINE, ok, warn, info, c });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
126
|
+
const choices = await checkbox({
|
|
127
|
+
message: '选择要安装的配置 (空格选择, 回车确认)',
|
|
128
|
+
choices: [
|
|
129
|
+
{ name: '精细合并推荐 settings.json (保留现有配置)', value: 'settings', checked: true },
|
|
130
|
+
{ name: '安装 ccline 状态栏 (需要 Nerd Font)', value: 'ccline', checked: true },
|
|
131
|
+
],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (choices.includes('settings')) {
|
|
135
|
+
mergeSettings(ctx, { deepMergeNew, printMergeLog, c, ok });
|
|
136
|
+
}
|
|
137
|
+
if (choices.includes('ccline')) {
|
|
138
|
+
await installCcline(ctx, { HOME, PKG_ROOT, CCLINE_STATUS_LINE, ok, warn, info, c });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
SETTINGS_TEMPLATE,
|
|
144
|
+
CCLINE_STATUS_LINE,
|
|
145
|
+
getClaudeCoreFiles,
|
|
146
|
+
detectClaudeAuth,
|
|
147
|
+
configureCustomProvider,
|
|
148
|
+
mergeSettings,
|
|
149
|
+
postClaude,
|
|
150
|
+
};
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const CODEX_DEFAULTS = {
|
|
7
|
+
sandboxMode: 'danger-full-access',
|
|
8
|
+
featureFlag: 'multi_agent',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const LEGACY_FEATURES = {
|
|
12
|
+
removed: [
|
|
13
|
+
'search_tool',
|
|
14
|
+
'request_rule',
|
|
15
|
+
'experimental_windows_sandbox',
|
|
16
|
+
'elevated_windows_sandbox',
|
|
17
|
+
'remote_models',
|
|
18
|
+
'collaboration_modes',
|
|
19
|
+
'steer',
|
|
20
|
+
],
|
|
21
|
+
deprecated: [
|
|
22
|
+
'web_search_request',
|
|
23
|
+
'web_search_cached',
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function escapeRegExp(input) {
|
|
28
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasTopLevelKey(content, key) {
|
|
32
|
+
const re = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`, 'm');
|
|
33
|
+
return re.test(content);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hasSection(content, sectionName) {
|
|
37
|
+
const re = new RegExp(`^\\s*\\[${escapeRegExp(sectionName)}\\]\\s*$`, 'm');
|
|
38
|
+
return re.test(content);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasKeyInSection(content, sectionName, key) {
|
|
42
|
+
const lines = content.split(/\r?\n/);
|
|
43
|
+
const sectionRe = new RegExp(`^\\s*\\[${escapeRegExp(sectionName)}\\]\\s*$`);
|
|
44
|
+
const anySectionRe = /^\s*\[[^\]]+\]\s*$/;
|
|
45
|
+
const keyRe = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
46
|
+
let inSection = false;
|
|
47
|
+
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (sectionRe.test(line)) {
|
|
50
|
+
inSection = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (inSection && anySectionRe.test(line)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (inSection && keyRe.test(line)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function appendLine(content, line, eol) {
|
|
64
|
+
if (!content) return `${line}${eol}`;
|
|
65
|
+
const normalized = content.endsWith('\n') ? content : `${content}${eol}`;
|
|
66
|
+
return `${normalized}${line}${eol}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function insertLineAfterSectionHeader(content, sectionName, line, eol) {
|
|
70
|
+
const lines = content.split(/\r?\n/);
|
|
71
|
+
const sectionRe = new RegExp(`^\\s*\\[${escapeRegExp(sectionName)}\\]\\s*$`);
|
|
72
|
+
const idx = lines.findIndex((l) => sectionRe.test(l));
|
|
73
|
+
if (idx === -1) return appendLine(content, line, eol);
|
|
74
|
+
lines.splice(idx + 1, 0, line);
|
|
75
|
+
return lines.join(eol);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function ensureKeyInSection(content, sectionName, key, valueLiteral, eol) {
|
|
79
|
+
let merged = content;
|
|
80
|
+
let added = false;
|
|
81
|
+
|
|
82
|
+
if (!hasSection(merged, sectionName)) {
|
|
83
|
+
merged = appendLine(merged, `[${sectionName}]`, eol);
|
|
84
|
+
merged = appendLine(merged, `${key} = ${valueLiteral}`, eol);
|
|
85
|
+
added = true;
|
|
86
|
+
} else if (!hasKeyInSection(merged, sectionName, key)) {
|
|
87
|
+
merged = insertLineAfterSectionHeader(merged, sectionName, `${key} = ${valueLiteral}`, eol);
|
|
88
|
+
added = true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { merged, added };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseTomlBooleanAssignment(line) {
|
|
95
|
+
const m = line.match(/=\s*(true|false)\b/i);
|
|
96
|
+
if (!m) return null;
|
|
97
|
+
return m[1].toLowerCase() === 'true';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function removeFeatureFlagsFromFeaturesSection(content, featureNames) {
|
|
101
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
102
|
+
const lines = content.split(/\r?\n/);
|
|
103
|
+
const sectionRe = /^\s*\[features\]\s*$/;
|
|
104
|
+
const anySectionRe = /^\s*\[[^\]]+\]\s*$/;
|
|
105
|
+
const assignRe = /^\s*([A-Za-z0-9_.-]+)\s*=/;
|
|
106
|
+
const featureSet = new Set(featureNames);
|
|
107
|
+
const removedEntries = [];
|
|
108
|
+
|
|
109
|
+
let inFeatures = false;
|
|
110
|
+
const kept = [];
|
|
111
|
+
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
if (sectionRe.test(line)) {
|
|
114
|
+
inFeatures = true;
|
|
115
|
+
kept.push(line);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (inFeatures && anySectionRe.test(line)) {
|
|
119
|
+
inFeatures = false;
|
|
120
|
+
kept.push(line);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (inFeatures) {
|
|
124
|
+
const m = line.match(assignRe);
|
|
125
|
+
if (m && featureSet.has(m[1])) {
|
|
126
|
+
removedEntries.push({ key: m[1], enabled: parseTomlBooleanAssignment(line) });
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
kept.push(line);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { merged: kept.join(eol), removedEntries };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function uniq(values) {
|
|
137
|
+
return [...new Set(values)];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function cleanupLegacyCodexConfig(content) {
|
|
141
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
142
|
+
const toRemove = [...LEGACY_FEATURES.removed, ...LEGACY_FEATURES.deprecated];
|
|
143
|
+
const { merged: pruned, removedEntries } = removeFeatureFlagsFromFeaturesSection(content, toRemove);
|
|
144
|
+
let merged = pruned;
|
|
145
|
+
const removed = uniq(removedEntries.map((x) => x.key));
|
|
146
|
+
const migrated = [];
|
|
147
|
+
|
|
148
|
+
const deprecatedRemoved = removedEntries.filter((x) => LEGACY_FEATURES.deprecated.includes(x.key));
|
|
149
|
+
if (deprecatedRemoved.length > 0) {
|
|
150
|
+
const shouldEnableWebSearch = deprecatedRemoved.some((x) => x.enabled !== false);
|
|
151
|
+
const { merged: withTools, added } = ensureKeyInSection(
|
|
152
|
+
merged,
|
|
153
|
+
'tools',
|
|
154
|
+
'web_search',
|
|
155
|
+
shouldEnableWebSearch ? 'true' : 'false',
|
|
156
|
+
eol
|
|
157
|
+
);
|
|
158
|
+
merged = withTools;
|
|
159
|
+
if (added) migrated.push(`tools.web_search=${shouldEnableWebSearch ? 'true' : 'false'}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { merged, removed, migrated };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function mergeCodexConfigDefaults(content) {
|
|
166
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
167
|
+
let merged = content;
|
|
168
|
+
const added = [];
|
|
169
|
+
|
|
170
|
+
if (!hasTopLevelKey(merged, 'sandbox_mode')) {
|
|
171
|
+
merged = appendLine(merged, `sandbox_mode = "${CODEX_DEFAULTS.sandboxMode}"`, eol);
|
|
172
|
+
added.push('sandbox_mode');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!hasSection(merged, 'features')) {
|
|
176
|
+
merged = appendLine(merged, '[features]', eol);
|
|
177
|
+
merged = appendLine(merged, `${CODEX_DEFAULTS.featureFlag} = true`, eol);
|
|
178
|
+
added.push('features.multi_agent');
|
|
179
|
+
} else if (!hasKeyInSection(merged, 'features', CODEX_DEFAULTS.featureFlag)) {
|
|
180
|
+
merged = insertLineAfterSectionHeader(merged, 'features', `${CODEX_DEFAULTS.featureFlag} = true`, eol);
|
|
181
|
+
added.push('features.multi_agent');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { merged, added };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function patchCodexConfig(cfgPath) {
|
|
188
|
+
const raw = fs.readFileSync(cfgPath, 'utf8');
|
|
189
|
+
const { merged: cleaned, removed, migrated } = cleanupLegacyCodexConfig(raw);
|
|
190
|
+
const { merged, added } = mergeCodexConfigDefaults(cleaned);
|
|
191
|
+
if (merged !== raw) fs.writeFileSync(cfgPath, merged);
|
|
192
|
+
return { added, removed, migrated };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function patchCodexConfigDefaults(cfgPath) {
|
|
196
|
+
return patchCodexConfig(cfgPath).added;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function patchAndReportCodexDefaults({ cfgPath, ok, warn }) {
|
|
200
|
+
try {
|
|
201
|
+
const { added, removed, migrated } = patchCodexConfig(cfgPath);
|
|
202
|
+
const changes = [];
|
|
203
|
+
if (added.length > 0) changes.push(`补全默认项: ${added.join(', ')}`);
|
|
204
|
+
if (removed.length > 0) changes.push(`清理过时项: ${removed.join(', ')}`);
|
|
205
|
+
if (migrated.length > 0) changes.push(`迁移配置: ${migrated.join(', ')}`);
|
|
206
|
+
if (changes.length > 0) ok(`config.toml 已存在,${changes.join(';')}`);
|
|
207
|
+
else ok('config.toml 已存在');
|
|
208
|
+
} catch (e) {
|
|
209
|
+
warn(`config.toml 读取失败,跳过补全: ${e.message}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function detectCodexAuth({
|
|
214
|
+
HOME,
|
|
215
|
+
env = process.env,
|
|
216
|
+
warn = () => {}
|
|
217
|
+
}) {
|
|
218
|
+
if (env.OPENAI_API_KEY) return { type: 'env', detail: 'OPENAI_API_KEY' };
|
|
219
|
+
|
|
220
|
+
const auth = path.join(HOME, '.codex', 'auth.json');
|
|
221
|
+
if (fs.existsSync(auth)) {
|
|
222
|
+
try {
|
|
223
|
+
const a = JSON.parse(fs.readFileSync(auth, 'utf8'));
|
|
224
|
+
if (a.token || a.api_key) return { type: 'login', detail: 'codex login' };
|
|
225
|
+
} catch (e) {
|
|
226
|
+
warn(`凭证文件损坏: ${auth}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const cfg = path.join(HOME, '.codex', 'config.toml');
|
|
231
|
+
if (fs.existsSync(cfg) && fs.readFileSync(cfg, 'utf8').includes('base_url')) {
|
|
232
|
+
return { type: 'custom', detail: 'config.toml' };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function getCodexCoreFiles() {
|
|
239
|
+
return [
|
|
240
|
+
{ src: 'config/AGENTS.md', dest: 'AGENTS.md' },
|
|
241
|
+
{ src: 'skills', dest: 'skills' },
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function postCodex({
|
|
246
|
+
autoYes,
|
|
247
|
+
HOME,
|
|
248
|
+
PKG_ROOT,
|
|
249
|
+
step,
|
|
250
|
+
ok,
|
|
251
|
+
warn,
|
|
252
|
+
info,
|
|
253
|
+
c
|
|
254
|
+
}) {
|
|
255
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
256
|
+
const cfgPath = path.join(HOME, '.codex', 'config.toml');
|
|
257
|
+
const exists = fs.existsSync(cfgPath);
|
|
258
|
+
|
|
259
|
+
step(2, 3, '认证检测');
|
|
260
|
+
const auth = detectCodexAuth({ HOME, warn });
|
|
261
|
+
if (auth) {
|
|
262
|
+
ok(`${c.b(auth.type)} → ${auth.detail}`);
|
|
263
|
+
} else {
|
|
264
|
+
warn('未检测到 API 认证');
|
|
265
|
+
info(`支持: ${c.cyn('codex login')} | ${c.cyn('OPENAI_API_KEY')} | ${c.cyn('自定义 provider')}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
step(3, 3, '可选配置');
|
|
269
|
+
if (autoYes) {
|
|
270
|
+
if (!exists) {
|
|
271
|
+
const src = path.join(PKG_ROOT, 'config', 'codex-config.example.toml');
|
|
272
|
+
if (fs.existsSync(src)) {
|
|
273
|
+
fs.copyFileSync(src, cfgPath);
|
|
274
|
+
ok('写入: ~/.codex/config.toml (模板)');
|
|
275
|
+
warn('请编辑 base_url 和 model');
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
patchAndReportCodexDefaults({ cfgPath, ok, warn });
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!exists) {
|
|
284
|
+
warn('未检测到 ~/.codex/config.toml');
|
|
285
|
+
const doWrite = await confirm({ message: '写入推荐 config.toml (含自定义 provider 模板)?', default: true });
|
|
286
|
+
if (doWrite) {
|
|
287
|
+
const src = path.join(PKG_ROOT, 'config', 'codex-config.example.toml');
|
|
288
|
+
if (fs.existsSync(src)) {
|
|
289
|
+
fs.copyFileSync(src, cfgPath);
|
|
290
|
+
ok('写入: ~/.codex/config.toml');
|
|
291
|
+
warn('请编辑 base_url 和 model');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
patchAndReportCodexDefaults({ cfgPath, ok, warn });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
module.exports = {
|
|
300
|
+
cleanupLegacyCodexConfig,
|
|
301
|
+
mergeCodexConfigDefaults,
|
|
302
|
+
patchCodexConfig,
|
|
303
|
+
patchCodexConfigDefaults,
|
|
304
|
+
detectCodexAuth,
|
|
305
|
+
getCodexCoreFiles,
|
|
306
|
+
postCodex,
|
|
307
|
+
};
|
package/bin/install.js
CHANGED
|
@@ -18,6 +18,17 @@ const PKG_ROOT = fs.realpathSync(path.join(__dirname, '..'));
|
|
|
18
18
|
const { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog, parseFrontmatter } =
|
|
19
19
|
require(path.join(__dirname, 'lib', 'utils.js'));
|
|
20
20
|
const { detectCclineBin, installCcline: _installCcline } = require(path.join(__dirname, 'lib', 'ccline.js'));
|
|
21
|
+
const {
|
|
22
|
+
detectCodexAuth: detectCodexAuthImpl,
|
|
23
|
+
getCodexCoreFiles,
|
|
24
|
+
postCodex: postCodexFlow,
|
|
25
|
+
} = require(path.join(__dirname, 'adapters', 'codex.js'));
|
|
26
|
+
const {
|
|
27
|
+
SETTINGS_TEMPLATE,
|
|
28
|
+
getClaudeCoreFiles,
|
|
29
|
+
detectClaudeAuth: detectClaudeAuthImpl,
|
|
30
|
+
postClaude: postClaudeFlow,
|
|
31
|
+
} = require(path.join(__dirname, 'adapters', 'claude.js'));
|
|
21
32
|
|
|
22
33
|
// ── ANSI ──
|
|
23
34
|
|
|
@@ -66,68 +77,15 @@ function fail(msg) { console.log(` ${c.red('✘')} ${msg}`); }
|
|
|
66
77
|
// ── 认证 ──
|
|
67
78
|
|
|
68
79
|
function detectClaudeAuth(settings) {
|
|
69
|
-
|
|
70
|
-
if (env.ANTHROPIC_BASE_URL && env.ANTHROPIC_AUTH_TOKEN) return { type: 'custom', detail: env.ANTHROPIC_BASE_URL };
|
|
71
|
-
if (process.env.ANTHROPIC_API_KEY) return { type: 'env', detail: 'ANTHROPIC_API_KEY' };
|
|
72
|
-
if (process.env.ANTHROPIC_BASE_URL && process.env.ANTHROPIC_AUTH_TOKEN) {
|
|
73
|
-
return { type: 'env-custom', detail: process.env.ANTHROPIC_BASE_URL };
|
|
74
|
-
}
|
|
75
|
-
const cred = path.join(HOME, '.claude', '.credentials.json');
|
|
76
|
-
if (fs.existsSync(cred)) {
|
|
77
|
-
try {
|
|
78
|
-
const cc = JSON.parse(fs.readFileSync(cred, 'utf8'));
|
|
79
|
-
if (cc.claudeAiOauth || cc.apiKey) return { type: 'login', detail: 'claude login' };
|
|
80
|
-
} catch (e) { warn(`凭证文件损坏: ${cred}`); }
|
|
81
|
-
}
|
|
82
|
-
return null;
|
|
80
|
+
return detectClaudeAuthImpl({ settings, HOME, warn });
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
function detectCodexAuth() {
|
|
86
|
-
|
|
87
|
-
const auth = path.join(HOME, '.codex', 'auth.json');
|
|
88
|
-
if (fs.existsSync(auth)) {
|
|
89
|
-
try {
|
|
90
|
-
const a = JSON.parse(fs.readFileSync(auth, 'utf8'));
|
|
91
|
-
if (a.token || a.api_key) return { type: 'login', detail: 'codex login' };
|
|
92
|
-
} catch (e) { warn(`凭证文件损坏: ${auth}`); }
|
|
93
|
-
}
|
|
94
|
-
const cfg = path.join(HOME, '.codex', 'config.toml');
|
|
95
|
-
if (fs.existsSync(cfg)) {
|
|
96
|
-
if (fs.readFileSync(cfg, 'utf8').includes('base_url')) return { type: 'custom', detail: 'config.toml' };
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
84
|
+
return detectCodexAuthImpl({ HOME, warn });
|
|
99
85
|
}
|
|
100
86
|
|
|
101
87
|
// ── 模板 ──
|
|
102
88
|
|
|
103
|
-
const SETTINGS_TEMPLATE = {
|
|
104
|
-
$schema: 'https://json.schemastore.org/claude-code-settings.json',
|
|
105
|
-
env: {
|
|
106
|
-
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
107
|
-
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1'
|
|
108
|
-
},
|
|
109
|
-
alwaysThinkingEnabled: true,
|
|
110
|
-
model: 'opus',
|
|
111
|
-
outputStyle: 'abyss-cultivator',
|
|
112
|
-
attribution: { commit: '', pr: '' },
|
|
113
|
-
permissions: {
|
|
114
|
-
allow: [
|
|
115
|
-
'Bash', 'LS', 'Read', 'Agent', 'Write', 'Edit', 'MultiEdit',
|
|
116
|
-
'Glob', 'Grep', 'WebFetch', 'WebSearch', 'TodoWrite',
|
|
117
|
-
'NotebookRead', 'NotebookEdit'
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const CCLINE_CMD = process.platform === 'win32' ? 'ccline' : '~/.claude/ccline/ccline';
|
|
123
|
-
const CCLINE_STATUS_LINE = {
|
|
124
|
-
statusLine: {
|
|
125
|
-
type: 'command',
|
|
126
|
-
command: CCLINE_CMD,
|
|
127
|
-
padding: 0
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
89
|
// ── CLI 参数 ──
|
|
132
90
|
|
|
133
91
|
const args = process.argv.slice(2);
|
|
@@ -322,12 +280,9 @@ function installCore(tgt) {
|
|
|
322
280
|
step(1, 3, `安装核心文件 → ${c.cyn(targetDir)}`);
|
|
323
281
|
fs.mkdirSync(backupDir, { recursive: true });
|
|
324
282
|
|
|
325
|
-
const filesToInstall =
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
{ src: 'output-styles', dest: tgt === 'claude' ? 'output-styles' : null },
|
|
329
|
-
{ src: 'skills', dest: 'skills' },
|
|
330
|
-
].filter(f => f.dest !== null);
|
|
283
|
+
const filesToInstall = tgt === 'codex'
|
|
284
|
+
? getCodexCoreFiles()
|
|
285
|
+
: getClaudeCoreFiles();
|
|
331
286
|
|
|
332
287
|
const manifest = {
|
|
333
288
|
manifest_version: 1, version: VERSION, target: tgt,
|
|
@@ -389,108 +344,36 @@ function installCore(tgt) {
|
|
|
389
344
|
|
|
390
345
|
// ── Claude 后续 ──
|
|
391
346
|
|
|
392
|
-
async function configureCustomProvider(ctx) {
|
|
393
|
-
const { confirm, input } = await import('@inquirer/prompts');
|
|
394
|
-
const doCfg = await confirm({ message: '配置自定义 provider?', default: false });
|
|
395
|
-
if (!doCfg) return;
|
|
396
|
-
if (!ctx.settings.env) ctx.settings.env = {};
|
|
397
|
-
const url = await input({ message: 'ANTHROPIC_BASE_URL:' });
|
|
398
|
-
const token = await input({ message: 'ANTHROPIC_AUTH_TOKEN:' });
|
|
399
|
-
if (url) ctx.settings.env.ANTHROPIC_BASE_URL = url;
|
|
400
|
-
if (token) ctx.settings.env.ANTHROPIC_AUTH_TOKEN = token;
|
|
401
|
-
fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
|
|
402
|
-
ok('provider 已配置');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function mergeSettings(ctx) {
|
|
406
|
-
const log = [];
|
|
407
|
-
deepMergeNew(ctx.settings, SETTINGS_TEMPLATE, '', log);
|
|
408
|
-
printMergeLog(log, c);
|
|
409
|
-
fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
|
|
410
|
-
ok('settings.json 合并完成');
|
|
411
|
-
}
|
|
412
|
-
|
|
413
347
|
async function postClaude(ctx) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
mergeSettings(ctx);
|
|
428
|
-
await installCcline(ctx);
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const { checkbox } = await import('@inquirer/prompts');
|
|
433
|
-
const choices = await checkbox({
|
|
434
|
-
message: '选择要安装的配置 (空格选择, 回车确认)',
|
|
435
|
-
choices: [
|
|
436
|
-
{ name: '精细合并推荐 settings.json (保留现有配置)', value: 'settings', checked: true },
|
|
437
|
-
{ name: '安装 ccline 状态栏 (需要 Nerd Font)', value: 'ccline', checked: true },
|
|
438
|
-
],
|
|
348
|
+
await postClaudeFlow({
|
|
349
|
+
ctx,
|
|
350
|
+
autoYes,
|
|
351
|
+
HOME,
|
|
352
|
+
PKG_ROOT,
|
|
353
|
+
step,
|
|
354
|
+
ok,
|
|
355
|
+
warn,
|
|
356
|
+
info,
|
|
357
|
+
c,
|
|
358
|
+
deepMergeNew,
|
|
359
|
+
printMergeLog,
|
|
360
|
+
installCcline: _installCcline,
|
|
439
361
|
});
|
|
440
|
-
|
|
441
|
-
if (choices.includes('settings')) mergeSettings(ctx);
|
|
442
|
-
if (choices.includes('ccline')) await installCcline(ctx);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async function installCcline(ctx) {
|
|
446
|
-
await _installCcline(ctx, { HOME, PKG_ROOT, CCLINE_STATUS_LINE, ok, warn, info, fail, c });
|
|
447
362
|
}
|
|
448
363
|
|
|
449
364
|
// ── Codex 后续 ──
|
|
450
365
|
|
|
451
366
|
async function postCodex() {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
info(`支持: ${c.cyn('codex login')} | ${c.cyn('OPENAI_API_KEY')} | ${c.cyn('自定义 provider')}`);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
step(3, 3, '可选配置');
|
|
466
|
-
if (autoYes) {
|
|
467
|
-
if (!exists) {
|
|
468
|
-
const src = path.join(PKG_ROOT, 'config', 'codex-config.example.toml');
|
|
469
|
-
if (fs.existsSync(src)) {
|
|
470
|
-
fs.copyFileSync(src, cfgPath);
|
|
471
|
-
ok('写入: ~/.codex/config.toml (模板)');
|
|
472
|
-
warn('请编辑 base_url 和 model');
|
|
473
|
-
}
|
|
474
|
-
} else {
|
|
475
|
-
ok('config.toml 已存在');
|
|
476
|
-
}
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (!exists) {
|
|
481
|
-
warn('未检测到 ~/.codex/config.toml');
|
|
482
|
-
const doWrite = await confirm({ message: '写入推荐 config.toml (含自定义 provider 模板)?', default: true });
|
|
483
|
-
if (doWrite) {
|
|
484
|
-
const src = path.join(PKG_ROOT, 'config', 'codex-config.example.toml');
|
|
485
|
-
if (fs.existsSync(src)) {
|
|
486
|
-
fs.copyFileSync(src, cfgPath);
|
|
487
|
-
ok('写入: ~/.codex/config.toml');
|
|
488
|
-
warn('请编辑 base_url 和 model');
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
} else {
|
|
492
|
-
ok('config.toml 已存在');
|
|
493
|
-
}
|
|
367
|
+
await postCodexFlow({
|
|
368
|
+
autoYes,
|
|
369
|
+
HOME,
|
|
370
|
+
PKG_ROOT,
|
|
371
|
+
step,
|
|
372
|
+
ok,
|
|
373
|
+
warn,
|
|
374
|
+
info,
|
|
375
|
+
c,
|
|
376
|
+
});
|
|
494
377
|
}
|
|
495
378
|
|
|
496
379
|
// ── 主流程 ──
|
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
web_search = "live"
|
|
20
|
-
|
|
21
|
-
[features]
|
|
22
|
-
unified_exec = true
|
|
23
|
-
shell_snapshot = true
|
|
24
|
-
steer = true
|
|
1
|
+
model_provider = "custom"
|
|
2
|
+
model = "gpt-5.2-codex"
|
|
3
|
+
model_reasoning_effort = "high"
|
|
4
|
+
approval_policy = "never"
|
|
5
|
+
sandbox_mode = "danger-full-access"
|
|
6
|
+
disable_response_storage = true
|
|
7
|
+
|
|
8
|
+
[model_providers.custom]
|
|
9
|
+
name = "custom"
|
|
10
|
+
base_url = "https://your-api-endpoint.com/v1"
|
|
11
|
+
wire_api = "responses"
|
|
12
|
+
requires_openai_auth = true
|
|
13
|
+
|
|
14
|
+
[tools]
|
|
15
|
+
web_search = true
|
|
16
|
+
|
|
17
|
+
[features]
|
|
18
|
+
multi_agent = true
|
package/package.json
CHANGED
|
@@ -403,7 +403,7 @@ function main() {
|
|
|
403
403
|
const result = generateDocs(args.path, args.force);
|
|
404
404
|
|
|
405
405
|
if (args.json) {
|
|
406
|
-
|
|
406
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
407
407
|
} else {
|
|
408
408
|
console.log('='.repeat(50));
|
|
409
409
|
console.log('文档生成报告');
|
|
@@ -414,7 +414,22 @@ function main() {
|
|
|
414
414
|
console.log('='.repeat(50));
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
process.
|
|
417
|
+
process.exitCode = result.status === 'success' ? 0 : 1;
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
-
main
|
|
420
|
+
if (require.main === module) {
|
|
421
|
+
main();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = {
|
|
425
|
+
parseGitignore,
|
|
426
|
+
shouldIgnore,
|
|
427
|
+
rglob,
|
|
428
|
+
detectLanguage,
|
|
429
|
+
analyzeModule,
|
|
430
|
+
generateReadme,
|
|
431
|
+
generateDesign,
|
|
432
|
+
generateDocs,
|
|
433
|
+
parseArgs,
|
|
434
|
+
main,
|
|
435
|
+
};
|