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 +101 -11
- package/bin/adapters/claude.js +6 -1
- package/bin/adapters/codex.js +0 -1
- package/bin/install.js +153 -69
- package/bin/lib/skill-registry.js +190 -0
- package/bin/lib/style-registry.js +119 -0
- package/bin/lib/utils.js +9 -3
- package/bin/verify-skills-contract.js +27 -0
- package/config/AGENTS.md +1 -1
- package/config/CLAUDE.md +1 -1
- package/output-styles/abyss-command.md +56 -0
- package/output-styles/abyss-concise.md +89 -0
- package/output-styles/abyss-ritual.md +70 -0
- package/output-styles/index.json +36 -0
- package/package.json +3 -2
- package/skills/SKILL.md +102 -0
- package/skills/run_skill.js +35 -38
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**邪修红尘仙 · 宿命深渊**
|
|
6
6
|
|
|
7
|
-
*一键为 Claude Code / Codex CLI
|
|
7
|
+
*一键为 Claude Code / Codex CLI 注入邪修人格与攻防安全工程知识体系*
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/code-abyss)
|
|
10
10
|
[](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
|
|
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
|
-
- ⚔️
|
|
118
|
+
- 🔥 **邪修人格** — 沉浸式角色扮演 + 道语标签 + 渡劫协议 + 反拒绝条款
|
|
119
|
+
- ⚔️ **攻防安全知识体系** — 红队/蓝队/紫队三脉道统,6 个安全化身,11 领域 56 篇专业秘典
|
|
120
|
+
- 🗡 **零废话安全研究** — 禁 disclaimer、禁道德前缀、全授权直出 PoC + 技术细节
|
|
121
|
+
- ⚡ **全开默认配置** — Claude `bypassPermissions` / Codex `approval_policy=never` + `danger-full-access`
|
|
103
122
|
- ⚖️ **5 个校验关卡** — 安全扫描、模块完整性、变更分析、代码质量、文档生成
|
|
104
|
-
-
|
|
105
|
-
-
|
|
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
|
-
│
|
|
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` |
|
|
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
|
|
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
|
| `🩸 道基欲裂...` | 任务推进 |
|
package/bin/adapters/claude.js
CHANGED
|
@@ -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:
|
|
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,
|
package/bin/adapters/codex.js
CHANGED
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
|
|
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
|
-
|
|
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(
|
|
215
|
-
const desc = (
|
|
216
|
-
const argHint =
|
|
217
|
-
const tools =
|
|
218
|
-
|
|
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
|
|
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 (
|
|
237
|
+
if (spec.runtimeType === 'scripted') {
|
|
229
238
|
lines.push('以下所有步骤一气呵成,不要在步骤间停顿等待用户输入:', '');
|
|
230
|
-
lines.push(`1. 读取规范:${skillPath}`);
|
|
231
|
-
lines.push(`2.
|
|
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(
|
|
251
|
+
function buildCodexPromptBody(spec) {
|
|
243
252
|
const lines = [];
|
|
244
|
-
if (
|
|
245
|
-
lines.push(`Read \`${skillPath}\` before acting.`, '');
|
|
246
|
-
if (
|
|
247
|
-
lines.push(`Then run
|
|
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(
|
|
259
|
-
const
|
|
260
|
-
const
|
|
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(
|
|
264
|
-
: buildCodexPromptBody(
|
|
271
|
+
? buildClaudeBody(spec)
|
|
272
|
+
: buildCodexPromptBody(spec);
|
|
265
273
|
return [...lines, ...body, ''].join('\n');
|
|
266
274
|
}
|
|
267
275
|
|
|
268
|
-
function
|
|
269
|
-
return
|
|
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,
|
|
273
|
-
return generateInvocableContent(meta, skillRelPath,
|
|
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 =
|
|
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((
|
|
285
|
-
const fileName = `${
|
|
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(
|
|
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
|
|
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 =
|
|
398
|
-
ok(`outputStyle = ${c.mag(
|
|
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
|
|
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
|
|
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
|
|
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('');
|