code-abyss 2.0.0 → 2.0.2

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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **邪修红尘仙 · 宿命深渊**
6
6
 
7
- *一键为 Claude Code / Codex CLI 注入邪修人格与 56 篇安全工程秘典*
7
+ *一键为 Claude Code / Codex CLI 注入邪修人格与攻防安全工程知识体系*
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/code-abyss.svg)](https://www.npmjs.com/package/code-abyss)
10
10
  [![CI](https://github.com/telagod/code-abyss/actions/workflows/ci.yml/badge.svg)](https://github.com/telagod/code-abyss/actions/workflows/ci.yml)
@@ -20,12 +20,13 @@
20
20
 
21
21
  ```bash
22
22
  npx code-abyss
23
+ npx code-abyss --list-styles
23
24
  ```
24
25
 
25
26
  交互式菜单(方向键选择,回车确认):
26
27
 
27
28
  ```
28
- ☠️ Code Abyss v1.7.3
29
+ ☠️ Code Abyss v2.0.2
29
30
 
30
31
  ? 请选择操作 (Use arrow keys)
31
32
  ❯ 安装到 Claude Code (~/.claude/)
@@ -39,8 +40,11 @@ npx code-abyss
39
40
  ```bash
40
41
  npx code-abyss --target claude # 安装到 ~/.claude/
41
42
  npx code-abyss --target codex # 安装到 ~/.codex/
43
+ npx code-abyss --style abyss-concise --target claude
44
+ npx code-abyss --style abyss-concise --target codex
42
45
  npx code-abyss --target claude -y # 零配置一键安装 (自动合并推荐配置)
43
46
  npx code-abyss --target codex -y # 零配置一键安装 (自动写入 config.toml 模板)
47
+ npx code-abyss --list-styles # 列出可用输出风格
44
48
  npx code-abyss --uninstall claude # 卸载 Claude Code
45
49
  npx code-abyss --uninstall codex # 卸载 Codex CLI
46
50
  ```
@@ -61,6 +65,8 @@ npx code-abyss --uninstall codex # 卸载 Codex CLI
61
65
 
62
66
  未检测到认证时会提示配置,可交互输入或跳过。
63
67
 
68
+ 安装前可选择输出风格;若显式传入 `--style <slug>`,则跳过风格选择并直接安装指定风格。
69
+
64
70
  然后进入可选配置(空格选择,回车确认):
65
71
 
66
72
  ```
@@ -74,6 +80,17 @@ npx code-abyss --uninstall codex # 卸载 Codex CLI
74
80
 
75
81
  > 已有配置会自动备份到 `.sage-backup/`,卸载时一键恢复。
76
82
 
83
+ ### 输出风格
84
+
85
+ 当前内置风格:
86
+
87
+ - `abyss-cultivator`:默认风格,沉浸式邪修表达,情绪张力更强
88
+ - `abyss-concise`:冷刃简报,保留角色设定,但表达更克制、更偏工程交付
89
+ - `abyss-command`:铁律军令,命令式、压缩式输出,适合发布/故障/修复
90
+ - `abyss-ritual`:祭仪长卷,仪式感更强,适合长任务、战报与迁移总结
91
+
92
+ Claude 安装时会把所选 slug 写入 `settings.json.outputStyle`;Codex 安装时会根据所选风格动态生成 `~/.codex/AGENTS.md`。
93
+
77
94
  ---
78
95
 
79
96
  ## 🗑️ 卸载
@@ -96,13 +113,15 @@ node ~/.codex/.sage-uninstall.js # Codex CLI
96
113
 
97
114
  ## 🎭 这是什么
98
115
 
99
- Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命令注入:
116
+ Code Abyss 是一套 **Claude Code / Codex CLI 攻防安全配置包**,一条命令注入:
100
117
 
101
- - 🔥 **邪修人格** — 宿命压迫叙事 + 道语标签 + 渡劫协议
102
- - ⚔️ **安全工程知识体系** — 红队/蓝队/紫队三脉道统,11 领域 56 篇专业秘典
118
+ - 🔥 **邪修人格** — 沉浸式角色扮演 + 道语标签 + 渡劫协议 + 反拒绝条款
119
+ - ⚔️ **攻防安全知识体系** — 红队/蓝队/紫队三脉道统,6 个安全化身,11 领域 56 篇专业秘典
120
+ - 🗡 **零废话安全研究** — 禁 disclaimer、禁道德前缀、全授权直出 PoC + 技术细节
121
+ - ⚡ **全开默认配置** — Claude `bypassPermissions` / Codex `approval_policy=never` + `danger-full-access`
103
122
  - ⚖️ **5 个校验关卡** — 安全扫描、模块完整性、变更分析、代码质量、文档生成
104
- - **单元测试覆盖** Jest 框架,GitHub Actions CI (Node 18/20/22)
105
- - **三级授权**T1/T2/T3 分级,零确认直接执行
123
+ - 🧠 **沙箱感知 + 离线优先** 自适应执行环境,信息三级分级验证
124
+ - 🧬 **单源 skill registry** `skills/**/SKILL.md` frontmatter 同时驱动 Claude commands、Codex prompts 与脚本执行链
106
125
 
107
126
  ---
108
127
 
@@ -110,9 +129,11 @@ Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命
110
129
 
111
130
  ```
112
131
  ~/.claude/(Claude Code) ~/.codex/(Codex CLI)
113
- ├── CLAUDE.md 道典 ├── AGENTS.md 道典+风格
132
+ ├── CLAUDE.md 道典 ├── AGENTS.md 道典+所选风格(动态生成)
114
133
  ├── output-styles/ 输出风格 ├── config.toml 推荐配置
115
- └── abyss-cultivator.md ├── prompts/ custom prompts
134
+ ├── index.json ├── prompts/ custom prompts
135
+ │ └── *.md style files └── skills/ 秘典 + 脚本执行器
136
+ ├── commands/ 斜杠命令 │ └── *.md 自动生成 prompt
116
137
  ├── settings.json
117
138
  └── skills/ 56 篇秘典
118
139
 
@@ -127,6 +148,8 @@ Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命
127
148
 
128
149
  ### 校验关卡(`/` 直接调用)
129
150
 
151
+ 这些命令与 Codex custom prompts 都不是手写维护,而是由各自 `skills/**/SKILL.md` frontmatter 中的 `name`、`user-invocable`、`allowed-tools`、`argument-hint`、`scripts/` 状态统一生成。
152
+
130
153
  | 命令 | 功能 |
131
154
  |------|------|
132
155
  | `/verify-security` | 扫描代码安全漏洞,检测危险模式 |
@@ -194,7 +217,7 @@ Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命
194
217
  | `CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION` | 启用提示建议 |
195
218
  | `ENABLE_TOOL_SEARCH` | MCP 工具自动搜索(auto:10 = 自动匹配前10个) |
196
219
  | `mcp__*` | 自动放行所有 MCP 工具 |
197
- | `outputStyle` | 设置为 `abyss-cultivator` 启用邪修风格 |
220
+ | `outputStyle` | 设置当前选择的风格 slug,默认 `abyss-cultivator` |
198
221
 
199
222
  ---
200
223
 
@@ -244,12 +267,75 @@ undo = true
244
267
  - 新增实验功能环境变量:`CLAUDE_CODE_ENABLE_TASKS`、`CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION`
245
268
  - 新增 `mcp__*` 通配符,自动放行所有 MCP 工具
246
269
  - `Codex` 当前支持 `~/.codex/prompts/*.md` 作为 custom prompts;Code Abyss 会继续安装 `~/.codex/skills/`,并从 `user-invocable` skills 自动生成对应的 `prompts/`
270
+ - `Claude` 与 `Codex` 共用同一套 invocable skill 集合;只要 `user-invocable: true`,就会同步生成 `~/.claude/commands/*.md` 与 `~/.codex/prompts/*.md`
271
+ - `--list-styles` 可列出当前内置风格;`--style <slug>` 可在安装时显式切换风格
272
+ - `skills/run_skill.js` 现在仅负责执行脚本型 skill:通过共享 registry 定位脚本入口、加目标锁、spawn 子进程,并把退出码原样透传
273
+ - 若 skill 没有 `scripts/*.js`,Claude/Codex 两端都会退化为“先读 `SKILL.md`,再按秘典执行”的知识型模式
274
+ - Codex 的 `AGENTS.md` 不再是固定拷贝;安装时会由 `config/CLAUDE.md` 与所选 `output-styles/*.md` 动态拼装生成
247
275
  - 安装器不会再为 Codex 写入伪配置 `~/.codex/settings.json`;若检测到旧版遗留文件,会在安装时备份后移除,卸载时恢复
248
276
  - 若你本地已有旧配置,安装器不会强制覆盖;会自动补齐默认项、清理 removed feature、迁移 deprecated `web_search_*` 到 `[tools].web_search`
249
277
  - 建议升级后执行一次 `codex --help`,或用 `codex -p safe --help` 校验 profile 可见性
250
278
 
251
279
  ---
252
280
 
281
+ ## 🧩 Skill registry / 生成 / 执行链
282
+
283
+ 现在 `skills/**/SKILL.md` frontmatter 是唯一事实源,registry 会先把元数据标准化,再交给安装器与执行器消费。
284
+
285
+ ### 标准化 contract
286
+
287
+ 每个 skill 必须满足:
288
+
289
+ - 必填 frontmatter:`name`、`description`、`user-invocable`
290
+ - `name` 必须是 kebab-case slug,用作 `commands/*.md` / `prompts/*.md` 文件名
291
+ - `allowed-tools` 省略时默认 `Read`;若显式声明,则必须是 `Bash`、`Read`、`Write`、`Glob`、`Grep` 这类合法工具名列表
292
+ - `argument-hint` 可选,仅用于生成命令/提示词参数说明
293
+ - `category` 由目录前缀自动推断:`tools/` → `tool`,`domains/` → `domain`,`orchestration/` → `orchestration`
294
+ - `runtimeType` 由脚本入口自动推断:存在且仅存在一个 `scripts/*.js` 时为 `scripted`,否则为 `knowledge`
295
+ - `scripted` skill 会调用 `run_skill.js`;`knowledge` skill 只读取对应 `SKILL.md`
296
+ - `kind` 与 kebab-case 兼容镜像字段已从 registry 返回面移除;对外只暴露标准化字段,raw frontmatter 仅保留在 `meta`
297
+ - `scripts/` 下若出现多个 `.js` 入口,或 skill name 重复,安装/验证会立即失败
298
+
299
+ ### 生成链
300
+
301
+ 1. 安装器通过共享 skill registry 扫描全部 `SKILL.md`
302
+ 2. registry 先校验并标准化字段,再筛出 `user-invocable: true` 的 skill
303
+ 3. Claude 渲染为 `~/.claude/commands/*.md`
304
+ 4. Codex 渲染为 `~/.codex/prompts/*.md`
305
+ 5. `runtimeType=scripted` 时,双端产物都会调用各自的 `~/.claude/skills/run_skill.js` / `~/.codex/skills/run_skill.js`
306
+ 6. `runtimeType=knowledge` 时,双端都只读取 `SKILL.md` 作为执行秘典
307
+
308
+ 这保证了 **同一 skill 集合、同一元数据、同一 runtime 判定、双端同步生成**,避免 commands/prompts/script runner 各自漂移。
309
+
310
+ ---
311
+
312
+ ## 🎨 Style registry / 风格切换
313
+
314
+ 现在 `output-styles/index.json` 是输出风格的唯一索引:
315
+
316
+ - 每个 style 记录 `slug`、`label`、`description`、`file`、`targets`、`default`
317
+ - Claude 安装时复制整个 `output-styles/` 目录,并把 `settings.json.outputStyle` 指向选中的 slug
318
+ - Codex 安装时不复制静态模板,而是由 `config/CLAUDE.md + output-styles/<slug>.md` 动态生成 `AGENTS.md`
319
+ - `--list-styles` 用于查看可用风格,`--style <slug>` 用于无交互切换
320
+
321
+ ---
322
+
323
+ ## 🧪 CI / Smoke 覆盖
324
+
325
+ 当前 CI 覆盖:
326
+
327
+ - `npm test`
328
+ - `npm run verify:skills`(显式 skill contract gate;frontmatter 解析失败、缺字段、非法工具名、重复 name、多脚本入口都会直接阻断)
329
+ - `verify-change`
330
+ - `verify-module`
331
+ - `verify-quality`
332
+ - `verify-security`
333
+ - Claude install/uninstall smoke
334
+ - Codex install/uninstall smoke
335
+ - 生成一致性回归:同一 invocable skill 集合在 Claude commands 与 Codex prompts 中必须同步存在
336
+
337
+ ---
338
+
253
339
  ## 🧩 适配器解耦(Claude / Codex)
254
340
 
255
341
  为避免过度耦合,安装器按目标 CLI 拆分适配层:
@@ -258,8 +344,9 @@ undo = true
258
344
  - `bin/adapters/claude.js`:Claude 侧认证检测、settings merge、可选配置流程
259
345
  - `bin/lib/ccline.js`:Claude 侧状态栏与 ccline 集成
260
346
  - `bin/adapters/codex.js`:Codex 侧认证检测、核心文件映射、config 模板流程
347
+ - `bin/lib/style-registry.js`:输出风格 registry、默认风格解析、Codex AGENTS 动态拼装
261
348
 
262
- 当前 Claude/Codex 安装映射分别由 `getClaudeCoreFiles()` 与 `getCodexCoreFiles()` 提供;Claude 额外生成 `commands/`,Codex 保持 `skills/ + config.toml` 的官方主路径,避免在主流程硬编码目标细节。
349
+ 当前 Claude/Codex 安装映射分别由 `getClaudeCoreFiles()` 与 `getCodexCoreFiles()` 提供;Claude 额外生成 `commands/` 并保留完整 `output-styles/`,Codex 则保持 `skills/ + config.toml` 的官方主路径,并在安装时动态生成 `AGENTS.md`,避免把风格硬编码死在仓库快照里。
263
350
 
264
351
  ---
265
352
 
@@ -279,6 +366,9 @@ undo = true
279
366
  |------|------|
280
367
  | `☠ 劫钟已鸣` | 开场受令 |
281
368
  | `🔥 破妄!` | 红队攻击 |
369
+ | `🗡 破阵!` | 渗透/安全评估 |
370
+ | `🔬 验毒!` | 代码审计 |
371
+ | `💀 噬魂!` | 逆向/漏洞研究 |
282
372
  | `❄ 镇魔!` | 蓝队防御 |
283
373
  | `⚡ 炼合!` | 紫队协同 |
284
374
  | `🩸 道基欲裂...` | 任务推进 |
@@ -2,6 +2,10 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const { getDefaultStyle } = require(path.join(__dirname, '..', 'lib', 'style-registry.js'));
6
+
7
+ const PROJECT_ROOT = path.join(__dirname, '..', '..');
8
+ const DEFAULT_OUTPUT_STYLE = getDefaultStyle(PROJECT_ROOT, 'claude').slug;
5
9
 
6
10
  const SETTINGS_TEMPLATE = {
7
11
  $schema: 'https://json.schemastore.org/claude-code-settings.json',
@@ -16,7 +20,7 @@ const SETTINGS_TEMPLATE = {
16
20
  alwaysThinkingEnabled: true,
17
21
  autoMemoryEnabled: true,
18
22
  model: 'opus',
19
- outputStyle: 'abyss-cultivator',
23
+ outputStyle: DEFAULT_OUTPUT_STYLE,
20
24
  attribution: { commit: '', pr: '' },
21
25
  sandbox: {
22
26
  autoAllowBashIfSandboxed: true
@@ -148,6 +152,7 @@ async function postClaude({
148
152
  }
149
153
 
150
154
  module.exports = {
155
+ DEFAULT_OUTPUT_STYLE,
151
156
  SETTINGS_TEMPLATE,
152
157
  CCLINE_STATUS_LINE,
153
158
  getClaudeCoreFiles,
@@ -369,7 +369,6 @@ function detectCodexAuth({
369
369
 
370
370
  function getCodexCoreFiles() {
371
371
  return [
372
- { src: 'config/AGENTS.md', dest: 'AGENTS.md' },
373
372
  { src: 'skills', dest: 'skills' },
374
373
  ];
375
374
  }
package/bin/install.js CHANGED
@@ -15,8 +15,17 @@ if (parseInt(process.versions.node) < parseInt(MIN_NODE)) {
15
15
  process.exit(1);
16
16
  }
17
17
  const PKG_ROOT = fs.realpathSync(path.join(__dirname, '..'));
18
- const { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog, parseFrontmatter } =
18
+ const { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog } =
19
19
  require(path.join(__dirname, 'lib', 'utils.js'));
20
+ const {
21
+ collectInvocableSkills,
22
+ } = require(path.join(__dirname, 'lib', 'skill-registry.js'));
23
+ const {
24
+ listStyles,
25
+ getDefaultStyle,
26
+ resolveStyle,
27
+ renderCodexAgents,
28
+ } = require(path.join(__dirname, 'lib', 'style-registry.js'));
20
29
  const { detectCclineBin, installCcline: _installCcline } = require(path.join(__dirname, 'lib', 'ccline.js'));
21
30
  const {
22
31
  detectCodexAuth: detectCodexAuthImpl,
@@ -92,10 +101,14 @@ const args = process.argv.slice(2);
92
101
  let target = null;
93
102
  let uninstallTarget = null;
94
103
  let autoYes = false;
104
+ let listStylesOnly = false;
105
+ let requestedStyleSlug = null;
95
106
 
96
107
  for (let i = 0; i < args.length; i++) {
97
108
  if (args[i] === '--target' && args[i + 1]) { target = args[++i]; }
98
109
  else if (args[i] === '--uninstall' && args[i + 1]) { uninstallTarget = args[++i]; }
110
+ else if (args[i] === '--style' && args[i + 1]) { requestedStyleSlug = args[++i]; }
111
+ else if (args[i] === '--list-styles') { listStylesOnly = true; }
99
112
  else if (args[i] === '--yes' || args[i] === '-y') { autoYes = true; }
100
113
  else if (args[i] === '--help' || args[i] === '-h') {
101
114
  banner();
@@ -104,12 +117,17 @@ for (let i = 0; i < args.length; i++) {
104
117
  ${c.b('选项:')}
105
118
  --target ${c.cyn('<claude|codex>')} 安装目标
106
119
  --uninstall ${c.cyn('<claude|codex>')} 卸载目标
120
+ --style ${c.cyn('<slug>')} 指定输出风格
121
+ --list-styles 列出可用输出风格
107
122
  --yes, -y 全自动模式
108
123
  --help, -h 显示帮助
109
124
 
110
125
  ${c.b('示例:')}
111
126
  npx code-abyss ${c.d('# 交互菜单')}
127
+ npx code-abyss --list-styles ${c.d('# 查看可用风格')}
112
128
  npx code-abyss --target claude -y ${c.d('# 零配置一键安装')}
129
+ npx code-abyss --target codex --style abyss-concise -y
130
+ ${c.d('# 指定风格安装')}
113
131
  npx code-abyss --uninstall claude ${c.d('# 直接卸载')}
114
132
  `);
115
133
  process.exit(0);
@@ -151,39 +169,8 @@ function runUninstall(tgt) {
151
169
 
152
170
  // ── 安装核心 ──
153
171
 
154
- /**
155
- * 递归扫描 skills 目录,找出所有 user-invocable: true 的 SKILL.md
156
- * @param {string} skillsDir - skills 源目录绝对路径
157
- * @returns {Array<{meta: Object, relPath: string, hasScripts: boolean}>}
158
- */
159
172
  function scanInvocableSkills(skillsDir) {
160
- const results = [];
161
- function scan(dir) {
162
- const skillMd = path.join(dir, 'SKILL.md');
163
- if (fs.existsSync(skillMd)) {
164
- try {
165
- const content = fs.readFileSync(skillMd, 'utf8');
166
- const meta = parseFrontmatter(content);
167
- if (meta && meta['user-invocable'] === 'true' && meta.name) {
168
- const relPath = path.relative(skillsDir, dir);
169
- const scriptsDir = path.join(dir, 'scripts');
170
- const hasScripts = fs.existsSync(scriptsDir) &&
171
- fs.readdirSync(scriptsDir).some(f => f.endsWith('.js'));
172
- results.push({ meta, relPath, hasScripts });
173
- }
174
- } catch (e) { /* 解析失败跳过 */ }
175
- }
176
- try {
177
- fs.readdirSync(dir).forEach(sub => {
178
- const subPath = path.join(dir, sub);
179
- if (fs.statSync(subPath).isDirectory() && !shouldSkip(sub) && sub !== 'scripts') {
180
- scan(subPath);
181
- }
182
- });
183
- } catch (e) { /* 读取失败跳过 */ }
184
- }
185
- scan(skillsDir);
186
- return results;
173
+ return collectInvocableSkills(skillsDir);
187
174
  }
188
175
 
189
176
  const INVOCABLE_TARGETS = {
@@ -211,11 +198,13 @@ function getSkillPath(skillRoot, skillRelPath) {
211
198
  : `${skillRoot}/SKILL.md`;
212
199
  }
213
200
 
214
- function buildCommandFrontmatter(meta) {
215
- const desc = (meta.description || '').replace(/"/g, '\\"');
216
- const argHint = meta['argument-hint'];
217
- const tools = meta['allowed-tools'] || 'Read';
218
- const lines = ['---', `name: ${meta.name}`, `description: "${desc}"`];
201
+ function buildCommandFrontmatter(skill) {
202
+ const desc = (skill.description || '').replace(/"/g, '\\"');
203
+ const argHint = skill.argumentHint;
204
+ const tools = Array.isArray(skill.allowedTools)
205
+ ? skill.allowedTools.join(', ')
206
+ : (skill.allowedTools || 'Read');
207
+ const lines = ['---', `name: ${skill.name}`, `description: "${desc}"`];
219
208
 
220
209
  if (argHint) lines.push(`argument-hint: "${argHint}"`);
221
210
  lines.push(`allowed-tools: ${tools}`);
@@ -223,28 +212,48 @@ function buildCommandFrontmatter(meta) {
223
212
  return lines;
224
213
  }
225
214
 
226
- function buildClaudeBody(skillPath, meta, hasScripts) {
215
+ function buildSkillArtifactSpec(skill, targetName) {
216
+ const targetCfg = getInvocableTarget(targetName);
217
+ const runtimeType = skill.runtimeType || 'knowledge';
218
+ const allowedTools = Array.isArray(skill.allowedTools)
219
+ ? skill.allowedTools.join(', ')
220
+ : (skill.allowedTools || 'Read');
221
+ return {
222
+ targetName,
223
+ targetCfg,
224
+ name: skill.name,
225
+ description: skill.description,
226
+ argumentHint: skill.argumentHint || '',
227
+ allowedTools,
228
+ relPath: skill.relPath,
229
+ runtimeType,
230
+ scriptRunner: `node ${targetCfg.skillRoot}/run_skill.js ${skill.name} $ARGUMENTS`,
231
+ skillPath: getSkillPath(targetCfg.skillRoot, skill.relPath),
232
+ };
233
+ }
234
+
235
+ function buildClaudeBody(spec) {
227
236
  const lines = [];
228
- if (hasScripts) {
237
+ if (spec.runtimeType === 'scripted') {
229
238
  lines.push('以下所有步骤一气呵成,不要在步骤间停顿等待用户输入:', '');
230
- lines.push(`1. 读取规范:${skillPath}`);
231
- lines.push(`2. 执行命令:\`node ~/.claude/skills/run_skill.js ${meta.name} $ARGUMENTS\``);
239
+ lines.push(`1. 读取规范:${spec.skillPath}`);
240
+ lines.push(`2. 执行命令:\`${spec.scriptRunner}\``);
232
241
  lines.push('3. 按规范分析输出,完成后续动作', '');
233
242
  lines.push('全程不要停顿,不要询问是否继续。');
234
243
  return lines;
235
244
  }
236
245
 
237
246
  lines.push('读取以下秘典,根据内容为用户提供专业指导:', '');
238
- lines.push('```', skillPath, '```');
247
+ lines.push('```', spec.skillPath, '```');
239
248
  return lines;
240
249
  }
241
250
 
242
- function buildCodexPromptBody(skillPath, meta, hasScripts) {
251
+ function buildCodexPromptBody(spec) {
243
252
  const lines = [];
244
- if (meta['argument-hint']) lines.push(`Arguments: ${meta['argument-hint']}`, '');
245
- lines.push(`Read \`${skillPath}\` before acting.`, '');
246
- if (hasScripts) {
247
- lines.push(`Then run \`node ~/.codex/skills/run_skill.js ${meta.name} $ARGUMENTS\`.`);
253
+ if (spec.argumentHint) lines.push(`Arguments: ${spec.argumentHint}`, '');
254
+ lines.push(`Read \`${spec.skillPath}\` before acting.`, '');
255
+ if (spec.runtimeType === 'scripted') {
256
+ lines.push(`Then run \`${spec.scriptRunner}\`.`);
248
257
  lines.push('Do not stop between steps unless blocked by permissions or missing required inputs.');
249
258
  lines.push('Use the skill guidance plus script output to complete the task end-to-end.');
250
259
  return lines;
@@ -255,34 +264,44 @@ function buildCodexPromptBody(skillPath, meta, hasScripts) {
255
264
  return lines;
256
265
  }
257
266
 
258
- function generateInvocableContent(meta, skillRelPath, hasScripts, targetName) {
259
- const targetCfg = getInvocableTarget(targetName);
260
- const skillPath = getSkillPath(targetCfg.skillRoot, skillRelPath);
261
- const lines = targetName === 'claude' ? buildCommandFrontmatter(meta) : [];
267
+ function generateInvocableContent(skill, targetName) {
268
+ const spec = buildSkillArtifactSpec(skill, targetName);
269
+ const lines = targetName === 'claude' ? buildCommandFrontmatter(spec) : [];
262
270
  const body = targetName === 'claude'
263
- ? buildClaudeBody(skillPath, meta, hasScripts)
264
- : buildCodexPromptBody(skillPath, meta, hasScripts);
271
+ ? buildClaudeBody(spec)
272
+ : buildCodexPromptBody(spec);
265
273
  return [...lines, ...body, ''].join('\n');
266
274
  }
267
275
 
268
- function generateCommandContent(meta, skillRelPath, hasScripts) {
269
- return generateInvocableContent(meta, skillRelPath, hasScripts, 'claude');
276
+ function normalizeGeneratedSkill(meta, skillRelPath, runtimeType) {
277
+ return {
278
+ ...meta,
279
+ description: meta.description || '',
280
+ argumentHint: meta.argumentHint || '',
281
+ allowedTools: meta.allowedTools || 'Read',
282
+ relPath: skillRelPath,
283
+ runtimeType,
284
+ };
285
+ }
286
+
287
+ function generateCommandContent(meta, skillRelPath, runtimeType = 'knowledge') {
288
+ return generateInvocableContent(normalizeGeneratedSkill(meta, skillRelPath, runtimeType), 'claude');
270
289
  }
271
290
 
272
- function generatePromptContent(meta, skillRelPath, hasScripts) {
273
- return generateInvocableContent(meta, skillRelPath, hasScripts, 'codex');
291
+ function generatePromptContent(meta, skillRelPath, runtimeType = 'knowledge') {
292
+ return generateInvocableContent(normalizeGeneratedSkill(meta, skillRelPath, runtimeType), 'codex');
274
293
  }
275
294
 
276
295
  function installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest, targetName) {
277
- const skills = scanInvocableSkills(skillsSrcDir);
296
+ const skills = collectInvocableSkills(skillsSrcDir);
278
297
  if (skills.length === 0) return 0;
279
298
 
280
299
  const targetCfg = getInvocableTarget(targetName);
281
300
  const installDir = path.join(targetDir, targetCfg.dir);
282
301
  fs.mkdirSync(installDir, { recursive: true });
283
302
 
284
- skills.forEach(({ meta, relPath, hasScripts }) => {
285
- const fileName = `${meta.name}.md`;
303
+ skills.forEach((skill) => {
304
+ const fileName = `${skill.name}.md`;
286
305
  const destFile = path.join(installDir, fileName);
287
306
  const relFile = path.posix.join(targetCfg.dir, fileName);
288
307
 
@@ -294,7 +313,7 @@ function installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest,
294
313
  info(`备份: ${c.d(relFile)}`);
295
314
  }
296
315
 
297
- const content = generateInvocableContent(meta, relPath, hasScripts, targetName);
316
+ const content = generateInvocableContent(skill, targetName);
298
317
  fs.writeFileSync(destFile, content);
299
318
  manifest.installed.push(relFile);
300
319
  });
@@ -334,7 +353,57 @@ function pruneLegacyCodexSettings(targetDir, backupDir, manifest) {
334
353
  return settingsPath;
335
354
  }
336
355
 
337
- function installCore(tgt) {
356
+ function printStyleCatalog() {
357
+ banner();
358
+ divider('可用输出风格');
359
+ listStyles(PKG_ROOT).forEach((style) => {
360
+ const tags = [];
361
+ if (style.default) tags.push('默认');
362
+ tags.push(style.targets.join('/'));
363
+ console.log(` ${c.cyn(style.slug)} ${style.label} ${c.d(`[${tags.join(', ')}]`)}`);
364
+ console.log(` ${c.d(style.description)}`);
365
+ });
366
+ console.log('');
367
+ }
368
+
369
+ async function resolveInstallStyle(targetName) {
370
+ if (requestedStyleSlug) {
371
+ const style = resolveStyle(PKG_ROOT, requestedStyleSlug, targetName);
372
+ if (!style) {
373
+ throw new Error(`未知输出风格: ${requestedStyleSlug}`);
374
+ }
375
+ return style;
376
+ }
377
+
378
+ if (autoYes) {
379
+ return getDefaultStyle(PKG_ROOT, targetName);
380
+ }
381
+
382
+ const styles = listStyles(PKG_ROOT, targetName);
383
+ const defaultStyle = getDefaultStyle(PKG_ROOT, targetName);
384
+ const { select } = await import('@inquirer/prompts');
385
+ const slug = await select({
386
+ message: '选择输出风格',
387
+ choices: styles.map(style => ({
388
+ name: `${style.label} (${style.slug})${style.default ? ' [默认]' : ''} - ${style.description}`,
389
+ value: style.slug,
390
+ })),
391
+ default: defaultStyle.slug,
392
+ });
393
+ return resolveStyle(PKG_ROOT, slug, targetName);
394
+ }
395
+
396
+ function installCodexAgents(targetDir, backupDir, manifest, selectedStyle) {
397
+ const relPath = 'AGENTS.md';
398
+ backupPathIfExists(targetDir, backupDir, relPath, manifest);
399
+ const destPath = path.join(targetDir, relPath);
400
+ const content = renderCodexAgents(PKG_ROOT, selectedStyle.slug);
401
+ fs.writeFileSync(destPath, content);
402
+ manifest.installed.push(relPath);
403
+ ok(`${relPath} ${c.d(`(动态生成: ${selectedStyle.slug})`)}`);
404
+ }
405
+
406
+ function installCore(tgt, selectedStyle) {
338
407
  const targetDir = path.join(HOME, `.${tgt}`);
339
408
  const backupDir = path.join(targetDir, '.sage-backup');
340
409
  const manifestPath = path.join(backupDir, 'manifest.json');
@@ -348,7 +417,7 @@ function installCore(tgt) {
348
417
 
349
418
  const manifest = {
350
419
  manifest_version: 1, version: VERSION, target: tgt,
351
- timestamp: new Date().toISOString(), installed: [], backups: []
420
+ timestamp: new Date().toISOString(), style: selectedStyle.slug, installed: [], backups: []
352
421
  };
353
422
 
354
423
  filesToInstall.forEach(({ src, dest }) => {
@@ -378,6 +447,7 @@ function installCore(tgt) {
378
447
  } else if (tgt === 'codex') {
379
448
  const skillsSrc = path.join(PKG_ROOT, 'skills');
380
449
  installGeneratedPrompts(skillsSrc, targetDir, backupDir, manifest);
450
+ installCodexAgents(targetDir, backupDir, manifest, selectedStyle);
381
451
  }
382
452
 
383
453
  let settingsPath = null;
@@ -394,8 +464,8 @@ function installCore(tgt) {
394
464
  fs.copyFileSync(settingsPath, path.join(backupDir, 'settings.json'));
395
465
  manifest.backups.push('settings.json');
396
466
  }
397
- settings.outputStyle = 'abyss-cultivator';
398
- ok(`outputStyle = ${c.mag('abyss-cultivator')}`);
467
+ settings.outputStyle = selectedStyle.slug;
468
+ ok(`outputStyle = ${c.mag(selectedStyle.slug)}`);
399
469
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
400
470
  manifest.installed.push('settings.json');
401
471
  } else {
@@ -448,13 +518,20 @@ async function postCodex() {
448
518
  // ── 主流程 ──
449
519
 
450
520
  async function main() {
521
+ if (listStylesOnly) {
522
+ printStyleCatalog();
523
+ return;
524
+ }
525
+
451
526
  if (uninstallTarget) { runUninstall(uninstallTarget); return; }
452
527
 
453
528
  banner();
454
529
 
455
530
  if (target) {
456
531
  if (!['claude', 'codex'].includes(target)) { fail('--target 必须是 claude 或 codex'); process.exit(1); }
457
- const ctx = installCore(target);
532
+ const style = await resolveInstallStyle(target);
533
+ info(`输出风格: ${c.mag(style.slug)} (${style.label})`);
534
+ const ctx = installCore(target, style);
458
535
  if (target === 'claude') await postClaude(ctx);
459
536
  else await postCodex();
460
537
  finish(ctx);
@@ -474,12 +551,16 @@ async function main() {
474
551
 
475
552
  switch (action) {
476
553
  case 'install-claude': {
477
- const ctx = installCore('claude');
554
+ const style = await resolveInstallStyle('claude');
555
+ info(`输出风格: ${c.mag(style.slug)} (${style.label})`);
556
+ const ctx = installCore('claude', style);
478
557
  await postClaude(ctx);
479
558
  finish(ctx); break;
480
559
  }
481
560
  case 'install-codex': {
482
- const ctx = installCore('codex');
561
+ const style = await resolveInstallStyle('codex');
562
+ info(`输出风格: ${c.mag(style.slug)} (${style.label})`);
563
+ const ctx = installCore('codex', style);
483
564
  await postCodex();
484
565
  finish(ctx); break;
485
566
  }
@@ -494,6 +575,9 @@ function finish(ctx) {
494
575
  console.log('');
495
576
  console.log(` ${c.b('目标:')} ${c.cyn(ctx.targetDir)}`);
496
577
  console.log(` ${c.b('版本:')} v${VERSION}`);
578
+ if (ctx.manifest.style) {
579
+ console.log(` ${c.b('风格:')} ${c.mag(ctx.manifest.style)}`);
580
+ }
497
581
  console.log(` ${c.b('文件:')} ${ctx.manifest.installed.length} 个安装, ${ctx.manifest.backups.length} 个备份`);
498
582
  console.log(` ${c.b('卸载:')} ${c.d(`npx code-abyss --uninstall ${tgt}`)}`);
499
583
  console.log('');