code-abyss 2.0.0 → 2.0.1
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 +66 -8
- package/bin/install.js +66 -62
- package/bin/lib/skill-registry.js +190 -0
- package/bin/lib/utils.js +9 -3
- package/bin/verify-skills-contract.js +27 -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)
|
|
@@ -25,7 +25,7 @@ npx code-abyss
|
|
|
25
25
|
交互式菜单(方向键选择,回车确认):
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
☠️ Code Abyss
|
|
28
|
+
☠️ Code Abyss v2.0.1
|
|
29
29
|
|
|
30
30
|
? 请选择操作 (Use arrow keys)
|
|
31
31
|
❯ 安装到 Claude Code (~/.claude/)
|
|
@@ -96,13 +96,15 @@ node ~/.codex/.sage-uninstall.js # Codex CLI
|
|
|
96
96
|
|
|
97
97
|
## 🎭 这是什么
|
|
98
98
|
|
|
99
|
-
Code Abyss 是一套 **Claude Code / Codex CLI
|
|
99
|
+
Code Abyss 是一套 **Claude Code / Codex CLI 攻防安全配置包**,一条命令注入:
|
|
100
100
|
|
|
101
|
-
- 🔥 **邪修人格** —
|
|
102
|
-
- ⚔️
|
|
101
|
+
- 🔥 **邪修人格** — 沉浸式角色扮演 + 道语标签 + 渡劫协议 + 反拒绝条款
|
|
102
|
+
- ⚔️ **攻防安全知识体系** — 红队/蓝队/紫队三脉道统,6 个安全化身,11 领域 56 篇专业秘典
|
|
103
|
+
- 🗡 **零废话安全研究** — 禁 disclaimer、禁道德前缀、全授权直出 PoC + 技术细节
|
|
104
|
+
- ⚡ **全开默认配置** — Claude `bypassPermissions` / Codex `approval_policy=never` + `danger-full-access`
|
|
103
105
|
- ⚖️ **5 个校验关卡** — 安全扫描、模块完整性、变更分析、代码质量、文档生成
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
+
- 🧠 **沙箱感知 + 离线优先** — 自适应执行环境,信息三级分级验证
|
|
107
|
+
- 🧬 **单源 skill registry** — `skills/**/SKILL.md` frontmatter 同时驱动 Claude commands、Codex prompts 与脚本执行链
|
|
106
108
|
|
|
107
109
|
---
|
|
108
110
|
|
|
@@ -113,7 +115,8 @@ Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命
|
|
|
113
115
|
├── CLAUDE.md 道典 ├── AGENTS.md 道典+风格
|
|
114
116
|
├── output-styles/ 输出风格 ├── config.toml 推荐配置
|
|
115
117
|
│ └── abyss-cultivator.md ├── prompts/ custom prompts
|
|
116
|
-
├──
|
|
118
|
+
├── commands/ 斜杠命令 │ └── *.md 自动生成 prompt
|
|
119
|
+
├── settings.json └── skills/ 秘典 + 脚本执行器
|
|
117
120
|
└── skills/ 56 篇秘典
|
|
118
121
|
|
|
119
122
|
可选:
|
|
@@ -127,6 +130,8 @@ Code Abyss 是一套 **Claude Code / Codex CLI 个性化配置包**,一条命
|
|
|
127
130
|
|
|
128
131
|
### 校验关卡(`/` 直接调用)
|
|
129
132
|
|
|
133
|
+
这些命令与 Codex custom prompts 都不是手写维护,而是由各自 `skills/**/SKILL.md` frontmatter 中的 `name`、`user-invocable`、`allowed-tools`、`argument-hint`、`scripts/` 状态统一生成。
|
|
134
|
+
|
|
130
135
|
| 命令 | 功能 |
|
|
131
136
|
|------|------|
|
|
132
137
|
| `/verify-security` | 扫描代码安全漏洞,检测危险模式 |
|
|
@@ -244,12 +249,62 @@ undo = true
|
|
|
244
249
|
- 新增实验功能环境变量:`CLAUDE_CODE_ENABLE_TASKS`、`CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION`
|
|
245
250
|
- 新增 `mcp__*` 通配符,自动放行所有 MCP 工具
|
|
246
251
|
- `Codex` 当前支持 `~/.codex/prompts/*.md` 作为 custom prompts;Code Abyss 会继续安装 `~/.codex/skills/`,并从 `user-invocable` skills 自动生成对应的 `prompts/`
|
|
252
|
+
- `Claude` 与 `Codex` 共用同一套 invocable skill 集合;只要 `user-invocable: true`,就会同步生成 `~/.claude/commands/*.md` 与 `~/.codex/prompts/*.md`
|
|
253
|
+
- `skills/run_skill.js` 现在仅负责执行脚本型 skill:通过共享 registry 定位脚本入口、加目标锁、spawn 子进程,并把退出码原样透传
|
|
254
|
+
- 若 skill 没有 `scripts/*.js`,Claude/Codex 两端都会退化为“先读 `SKILL.md`,再按秘典执行”的知识型模式
|
|
247
255
|
- 安装器不会再为 Codex 写入伪配置 `~/.codex/settings.json`;若检测到旧版遗留文件,会在安装时备份后移除,卸载时恢复
|
|
248
256
|
- 若你本地已有旧配置,安装器不会强制覆盖;会自动补齐默认项、清理 removed feature、迁移 deprecated `web_search_*` 到 `[tools].web_search`
|
|
249
257
|
- 建议升级后执行一次 `codex --help`,或用 `codex -p safe --help` 校验 profile 可见性
|
|
250
258
|
|
|
251
259
|
---
|
|
252
260
|
|
|
261
|
+
## 🧩 Skill registry / 生成 / 执行链
|
|
262
|
+
|
|
263
|
+
现在 `skills/**/SKILL.md` frontmatter 是唯一事实源,registry 会先把元数据标准化,再交给安装器与执行器消费。
|
|
264
|
+
|
|
265
|
+
### 标准化 contract
|
|
266
|
+
|
|
267
|
+
每个 skill 必须满足:
|
|
268
|
+
|
|
269
|
+
- 必填 frontmatter:`name`、`description`、`user-invocable`
|
|
270
|
+
- `name` 必须是 kebab-case slug,用作 `commands/*.md` / `prompts/*.md` 文件名
|
|
271
|
+
- `allowed-tools` 省略时默认 `Read`;若显式声明,则必须是 `Bash`、`Read`、`Write`、`Glob`、`Grep` 这类合法工具名列表
|
|
272
|
+
- `argument-hint` 可选,仅用于生成命令/提示词参数说明
|
|
273
|
+
- `category` 由目录前缀自动推断:`tools/` → `tool`,`domains/` → `domain`,`orchestration/` → `orchestration`
|
|
274
|
+
- `runtimeType` 由脚本入口自动推断:存在且仅存在一个 `scripts/*.js` 时为 `scripted`,否则为 `knowledge`
|
|
275
|
+
- `scripted` skill 会调用 `run_skill.js`;`knowledge` skill 只读取对应 `SKILL.md`
|
|
276
|
+
- `kind` 与 kebab-case 兼容镜像字段已从 registry 返回面移除;对外只暴露标准化字段,raw frontmatter 仅保留在 `meta`
|
|
277
|
+
- `scripts/` 下若出现多个 `.js` 入口,或 skill name 重复,安装/验证会立即失败
|
|
278
|
+
|
|
279
|
+
### 生成链
|
|
280
|
+
|
|
281
|
+
1. 安装器通过共享 skill registry 扫描全部 `SKILL.md`
|
|
282
|
+
2. registry 先校验并标准化字段,再筛出 `user-invocable: true` 的 skill
|
|
283
|
+
3. Claude 渲染为 `~/.claude/commands/*.md`
|
|
284
|
+
4. Codex 渲染为 `~/.codex/prompts/*.md`
|
|
285
|
+
5. `runtimeType=scripted` 时,双端产物都会调用各自的 `~/.claude/skills/run_skill.js` / `~/.codex/skills/run_skill.js`
|
|
286
|
+
6. `runtimeType=knowledge` 时,双端都只读取 `SKILL.md` 作为执行秘典
|
|
287
|
+
|
|
288
|
+
这保证了 **同一 skill 集合、同一元数据、同一 runtime 判定、双端同步生成**,避免 commands/prompts/script runner 各自漂移。
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 🧪 CI / Smoke 覆盖
|
|
293
|
+
|
|
294
|
+
当前 CI 覆盖:
|
|
295
|
+
|
|
296
|
+
- `npm test`
|
|
297
|
+
- `npm run verify:skills`(显式 skill contract gate;frontmatter 解析失败、缺字段、非法工具名、重复 name、多脚本入口都会直接阻断)
|
|
298
|
+
- `verify-change`
|
|
299
|
+
- `verify-module`
|
|
300
|
+
- `verify-quality`
|
|
301
|
+
- `verify-security`
|
|
302
|
+
- Claude install/uninstall smoke
|
|
303
|
+
- Codex install/uninstall smoke
|
|
304
|
+
- 生成一致性回归:同一 invocable skill 集合在 Claude commands 与 Codex prompts 中必须同步存在
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
253
308
|
## 🧩 适配器解耦(Claude / Codex)
|
|
254
309
|
|
|
255
310
|
为避免过度耦合,安装器按目标 CLI 拆分适配层:
|
|
@@ -279,6 +334,9 @@ undo = true
|
|
|
279
334
|
|------|------|
|
|
280
335
|
| `☠ 劫钟已鸣` | 开场受令 |
|
|
281
336
|
| `🔥 破妄!` | 红队攻击 |
|
|
337
|
+
| `🗡 破阵!` | 渗透/安全评估 |
|
|
338
|
+
| `🔬 验毒!` | 代码审计 |
|
|
339
|
+
| `💀 噬魂!` | 逆向/漏洞研究 |
|
|
282
340
|
| `❄ 镇魔!` | 蓝队防御 |
|
|
283
341
|
| `⚡ 炼合!` | 紫队协同 |
|
|
284
342
|
| `🩸 道基欲裂...` | 任务推进 |
|
package/bin/install.js
CHANGED
|
@@ -15,8 +15,11 @@ 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'));
|
|
20
23
|
const { detectCclineBin, installCcline: _installCcline } = require(path.join(__dirname, 'lib', 'ccline.js'));
|
|
21
24
|
const {
|
|
22
25
|
detectCodexAuth: detectCodexAuthImpl,
|
|
@@ -151,39 +154,8 @@ function runUninstall(tgt) {
|
|
|
151
154
|
|
|
152
155
|
// ── 安装核心 ──
|
|
153
156
|
|
|
154
|
-
/**
|
|
155
|
-
* 递归扫描 skills 目录,找出所有 user-invocable: true 的 SKILL.md
|
|
156
|
-
* @param {string} skillsDir - skills 源目录绝对路径
|
|
157
|
-
* @returns {Array<{meta: Object, relPath: string, hasScripts: boolean}>}
|
|
158
|
-
*/
|
|
159
157
|
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;
|
|
158
|
+
return collectInvocableSkills(skillsDir);
|
|
187
159
|
}
|
|
188
160
|
|
|
189
161
|
const INVOCABLE_TARGETS = {
|
|
@@ -211,11 +183,13 @@ function getSkillPath(skillRoot, skillRelPath) {
|
|
|
211
183
|
: `${skillRoot}/SKILL.md`;
|
|
212
184
|
}
|
|
213
185
|
|
|
214
|
-
function buildCommandFrontmatter(
|
|
215
|
-
const desc = (
|
|
216
|
-
const argHint =
|
|
217
|
-
const tools =
|
|
218
|
-
|
|
186
|
+
function buildCommandFrontmatter(skill) {
|
|
187
|
+
const desc = (skill.description || '').replace(/"/g, '\\"');
|
|
188
|
+
const argHint = skill.argumentHint;
|
|
189
|
+
const tools = Array.isArray(skill.allowedTools)
|
|
190
|
+
? skill.allowedTools.join(', ')
|
|
191
|
+
: (skill.allowedTools || 'Read');
|
|
192
|
+
const lines = ['---', `name: ${skill.name}`, `description: "${desc}"`];
|
|
219
193
|
|
|
220
194
|
if (argHint) lines.push(`argument-hint: "${argHint}"`);
|
|
221
195
|
lines.push(`allowed-tools: ${tools}`);
|
|
@@ -223,28 +197,48 @@ function buildCommandFrontmatter(meta) {
|
|
|
223
197
|
return lines;
|
|
224
198
|
}
|
|
225
199
|
|
|
226
|
-
function
|
|
200
|
+
function buildSkillArtifactSpec(skill, targetName) {
|
|
201
|
+
const targetCfg = getInvocableTarget(targetName);
|
|
202
|
+
const runtimeType = skill.runtimeType || 'knowledge';
|
|
203
|
+
const allowedTools = Array.isArray(skill.allowedTools)
|
|
204
|
+
? skill.allowedTools.join(', ')
|
|
205
|
+
: (skill.allowedTools || 'Read');
|
|
206
|
+
return {
|
|
207
|
+
targetName,
|
|
208
|
+
targetCfg,
|
|
209
|
+
name: skill.name,
|
|
210
|
+
description: skill.description,
|
|
211
|
+
argumentHint: skill.argumentHint || '',
|
|
212
|
+
allowedTools,
|
|
213
|
+
relPath: skill.relPath,
|
|
214
|
+
runtimeType,
|
|
215
|
+
scriptRunner: `node ${targetCfg.skillRoot}/run_skill.js ${skill.name} $ARGUMENTS`,
|
|
216
|
+
skillPath: getSkillPath(targetCfg.skillRoot, skill.relPath),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function buildClaudeBody(spec) {
|
|
227
221
|
const lines = [];
|
|
228
|
-
if (
|
|
222
|
+
if (spec.runtimeType === 'scripted') {
|
|
229
223
|
lines.push('以下所有步骤一气呵成,不要在步骤间停顿等待用户输入:', '');
|
|
230
|
-
lines.push(`1. 读取规范:${skillPath}`);
|
|
231
|
-
lines.push(`2.
|
|
224
|
+
lines.push(`1. 读取规范:${spec.skillPath}`);
|
|
225
|
+
lines.push(`2. 执行命令:\`${spec.scriptRunner}\``);
|
|
232
226
|
lines.push('3. 按规范分析输出,完成后续动作', '');
|
|
233
227
|
lines.push('全程不要停顿,不要询问是否继续。');
|
|
234
228
|
return lines;
|
|
235
229
|
}
|
|
236
230
|
|
|
237
231
|
lines.push('读取以下秘典,根据内容为用户提供专业指导:', '');
|
|
238
|
-
lines.push('```', skillPath, '```');
|
|
232
|
+
lines.push('```', spec.skillPath, '```');
|
|
239
233
|
return lines;
|
|
240
234
|
}
|
|
241
235
|
|
|
242
|
-
function buildCodexPromptBody(
|
|
236
|
+
function buildCodexPromptBody(spec) {
|
|
243
237
|
const lines = [];
|
|
244
|
-
if (
|
|
245
|
-
lines.push(`Read \`${skillPath}\` before acting.`, '');
|
|
246
|
-
if (
|
|
247
|
-
lines.push(`Then run
|
|
238
|
+
if (spec.argumentHint) lines.push(`Arguments: ${spec.argumentHint}`, '');
|
|
239
|
+
lines.push(`Read \`${spec.skillPath}\` before acting.`, '');
|
|
240
|
+
if (spec.runtimeType === 'scripted') {
|
|
241
|
+
lines.push(`Then run \`${spec.scriptRunner}\`.`);
|
|
248
242
|
lines.push('Do not stop between steps unless blocked by permissions or missing required inputs.');
|
|
249
243
|
lines.push('Use the skill guidance plus script output to complete the task end-to-end.');
|
|
250
244
|
return lines;
|
|
@@ -255,34 +249,44 @@ function buildCodexPromptBody(skillPath, meta, hasScripts) {
|
|
|
255
249
|
return lines;
|
|
256
250
|
}
|
|
257
251
|
|
|
258
|
-
function generateInvocableContent(
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
const lines = targetName === 'claude' ? buildCommandFrontmatter(meta) : [];
|
|
252
|
+
function generateInvocableContent(skill, targetName) {
|
|
253
|
+
const spec = buildSkillArtifactSpec(skill, targetName);
|
|
254
|
+
const lines = targetName === 'claude' ? buildCommandFrontmatter(spec) : [];
|
|
262
255
|
const body = targetName === 'claude'
|
|
263
|
-
? buildClaudeBody(
|
|
264
|
-
: buildCodexPromptBody(
|
|
256
|
+
? buildClaudeBody(spec)
|
|
257
|
+
: buildCodexPromptBody(spec);
|
|
265
258
|
return [...lines, ...body, ''].join('\n');
|
|
266
259
|
}
|
|
267
260
|
|
|
268
|
-
function
|
|
269
|
-
return
|
|
261
|
+
function normalizeGeneratedSkill(meta, skillRelPath, runtimeType) {
|
|
262
|
+
return {
|
|
263
|
+
...meta,
|
|
264
|
+
description: meta.description || '',
|
|
265
|
+
argumentHint: meta.argumentHint || '',
|
|
266
|
+
allowedTools: meta.allowedTools || 'Read',
|
|
267
|
+
relPath: skillRelPath,
|
|
268
|
+
runtimeType,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function generateCommandContent(meta, skillRelPath, runtimeType = 'knowledge') {
|
|
273
|
+
return generateInvocableContent(normalizeGeneratedSkill(meta, skillRelPath, runtimeType), 'claude');
|
|
270
274
|
}
|
|
271
275
|
|
|
272
|
-
function generatePromptContent(meta, skillRelPath,
|
|
273
|
-
return generateInvocableContent(meta, skillRelPath,
|
|
276
|
+
function generatePromptContent(meta, skillRelPath, runtimeType = 'knowledge') {
|
|
277
|
+
return generateInvocableContent(normalizeGeneratedSkill(meta, skillRelPath, runtimeType), 'codex');
|
|
274
278
|
}
|
|
275
279
|
|
|
276
280
|
function installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest, targetName) {
|
|
277
|
-
const skills =
|
|
281
|
+
const skills = collectInvocableSkills(skillsSrcDir);
|
|
278
282
|
if (skills.length === 0) return 0;
|
|
279
283
|
|
|
280
284
|
const targetCfg = getInvocableTarget(targetName);
|
|
281
285
|
const installDir = path.join(targetDir, targetCfg.dir);
|
|
282
286
|
fs.mkdirSync(installDir, { recursive: true });
|
|
283
287
|
|
|
284
|
-
skills.forEach((
|
|
285
|
-
const fileName = `${
|
|
288
|
+
skills.forEach((skill) => {
|
|
289
|
+
const fileName = `${skill.name}.md`;
|
|
286
290
|
const destFile = path.join(installDir, fileName);
|
|
287
291
|
const relFile = path.posix.join(targetCfg.dir, fileName);
|
|
288
292
|
|
|
@@ -294,7 +298,7 @@ function installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest,
|
|
|
294
298
|
info(`备份: ${c.d(relFile)}`);
|
|
295
299
|
}
|
|
296
300
|
|
|
297
|
-
const content = generateInvocableContent(
|
|
301
|
+
const content = generateInvocableContent(skill, targetName);
|
|
298
302
|
fs.writeFileSync(destFile, content);
|
|
299
303
|
manifest.installed.push(relFile);
|
|
300
304
|
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { shouldSkip, parseFrontmatter } = require('./utils');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_ALLOWED_TOOLS = ['Read'];
|
|
8
|
+
const NAME_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
|
+
const TOOL_NAME_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
10
|
+
|
|
11
|
+
function normalizeBoolean(value) {
|
|
12
|
+
return String(value).toLowerCase() === 'true';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function inferSkillKind(relPath) {
|
|
16
|
+
const normalizedRelPath = relPath.split(path.sep).join('/');
|
|
17
|
+
const [head] = normalizedRelPath.split('/');
|
|
18
|
+
if (head === 'tools') return 'tool';
|
|
19
|
+
if (head === 'domains') return 'domain';
|
|
20
|
+
if (head === 'orchestration') return 'orchestration';
|
|
21
|
+
return 'root';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function listScriptEntries(skillDir) {
|
|
25
|
+
const scriptsDir = path.join(skillDir, 'scripts');
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = fs.readdirSync(scriptsDir)
|
|
29
|
+
.filter(name => name.endsWith('.js'))
|
|
30
|
+
.sort();
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return entries.map(name => path.join(scriptsDir, name));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildRegistryError(relPath, message) {
|
|
38
|
+
const where = relPath || '.';
|
|
39
|
+
return new Error(`无效 skill (${where}): ${message}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function requireStringField(meta, fieldName, relPath) {
|
|
43
|
+
const value = meta[fieldName];
|
|
44
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
45
|
+
throw buildRegistryError(relPath, `缺少必填 frontmatter 字段 '${fieldName}'`);
|
|
46
|
+
}
|
|
47
|
+
return value.trim();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeAllowedTools(value, relPath) {
|
|
51
|
+
if (value === undefined || value === null || String(value).trim() === '') {
|
|
52
|
+
return [...DEFAULT_ALLOWED_TOOLS];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const tools = String(value)
|
|
56
|
+
.split(',')
|
|
57
|
+
.map(tool => tool.trim())
|
|
58
|
+
.filter(Boolean);
|
|
59
|
+
|
|
60
|
+
if (tools.length === 0) {
|
|
61
|
+
throw buildRegistryError(relPath, 'allowed-tools 不能为空');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const tool of tools) {
|
|
65
|
+
if (!TOOL_NAME_RE.test(tool)) {
|
|
66
|
+
throw buildRegistryError(relPath, `allowed-tools 包含非法值 '${tool}'`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return tools;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeSkillRecord(skillsDir, skillDir, meta) {
|
|
74
|
+
const relPath = path.relative(skillsDir, skillDir);
|
|
75
|
+
const normalizedMeta = meta || {};
|
|
76
|
+
const scriptEntries = listScriptEntries(skillDir);
|
|
77
|
+
|
|
78
|
+
if (scriptEntries.length > 1) {
|
|
79
|
+
throw buildRegistryError(relPath, `scripts/ 目录下只能有一个 .js 入口,当前找到 ${scriptEntries.length} 个`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const name = requireStringField(normalizedMeta, 'name', relPath);
|
|
83
|
+
if (!NAME_SLUG_RE.test(name)) {
|
|
84
|
+
throw buildRegistryError(relPath, `name 必须是 kebab-case slug,当前为 '${name}'`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const description = requireStringField(normalizedMeta, 'description', relPath);
|
|
88
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedMeta, 'user-invocable')) {
|
|
89
|
+
throw buildRegistryError(relPath, "缺少必填 frontmatter 字段 'user-invocable'");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const userInvocable = normalizeBoolean(normalizedMeta['user-invocable']);
|
|
93
|
+
const allowedTools = normalizeAllowedTools(normalizedMeta['allowed-tools'], relPath);
|
|
94
|
+
const argumentHint = normalizedMeta['argument-hint'] || '';
|
|
95
|
+
const category = inferSkillKind(relPath);
|
|
96
|
+
const runtimeType = scriptEntries.length === 1 ? 'scripted' : 'knowledge';
|
|
97
|
+
const scriptPath = scriptEntries[0] || null;
|
|
98
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
name,
|
|
102
|
+
description,
|
|
103
|
+
userInvocable,
|
|
104
|
+
allowedTools,
|
|
105
|
+
argumentHint,
|
|
106
|
+
relPath,
|
|
107
|
+
category,
|
|
108
|
+
runtimeType,
|
|
109
|
+
hasScripts: runtimeType === 'scripted',
|
|
110
|
+
scriptPath,
|
|
111
|
+
skillPath,
|
|
112
|
+
meta: normalizedMeta,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function validateUniqueSkillRecords(skills) {
|
|
117
|
+
const names = new Map();
|
|
118
|
+
const relPaths = new Map();
|
|
119
|
+
|
|
120
|
+
for (const skill of skills) {
|
|
121
|
+
if (names.has(skill.name)) {
|
|
122
|
+
throw buildRegistryError(skill.relPath, `重复的 skill name '${skill.name}',首次出现在 ${names.get(skill.name)}`);
|
|
123
|
+
}
|
|
124
|
+
names.set(skill.name, skill.relPath);
|
|
125
|
+
|
|
126
|
+
if (relPaths.has(skill.relPath)) {
|
|
127
|
+
throw buildRegistryError(skill.relPath, `重复的 skill relPath '${skill.relPath}'`);
|
|
128
|
+
}
|
|
129
|
+
relPaths.set(skill.relPath, skill.name);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function collectSkills(skillsDir) {
|
|
134
|
+
const results = [];
|
|
135
|
+
|
|
136
|
+
function scan(dir) {
|
|
137
|
+
const skillMd = path.join(dir, 'SKILL.md');
|
|
138
|
+
if (fs.existsSync(skillMd)) {
|
|
139
|
+
const relPath = path.relative(skillsDir, dir);
|
|
140
|
+
const content = fs.readFileSync(skillMd, 'utf8');
|
|
141
|
+
const meta = parseFrontmatter(content);
|
|
142
|
+
if (!meta) {
|
|
143
|
+
throw buildRegistryError(relPath, 'SKILL.md 缺少可解析的 frontmatter');
|
|
144
|
+
}
|
|
145
|
+
results.push(normalizeSkillRecord(skillsDir, dir, meta));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (!entry.isDirectory()) continue;
|
|
157
|
+
if (entry.name === 'scripts' || shouldSkip(entry.name)) continue;
|
|
158
|
+
scan(path.join(dir, entry.name));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
scan(skillsDir);
|
|
163
|
+
validateUniqueSkillRecords(results);
|
|
164
|
+
return results.sort((a, b) => a.name.localeCompare(b.name));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function collectInvocableSkills(skillsDir) {
|
|
168
|
+
return collectSkills(skillsDir).filter(skill => skill.userInvocable);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function resolveSkillByName(skillsDir, skillName) {
|
|
172
|
+
return collectSkills(skillsDir).find(skill => skill.name === skillName) || null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function resolveExecutableSkillScript(skillsDir, skillName) {
|
|
176
|
+
const skill = resolveSkillByName(skillsDir, skillName);
|
|
177
|
+
if (!skill) return { skill: null, scriptPath: null, reason: 'missing' };
|
|
178
|
+
if (skill.runtimeType !== 'scripted' || !skill.scriptPath) {
|
|
179
|
+
return { skill, scriptPath: null, reason: 'no-script' };
|
|
180
|
+
}
|
|
181
|
+
return { skill, scriptPath: skill.scriptPath, reason: null };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
collectSkills,
|
|
186
|
+
collectInvocableSkills,
|
|
187
|
+
resolveSkillByName,
|
|
188
|
+
resolveExecutableSkillScript,
|
|
189
|
+
inferSkillKind,
|
|
190
|
+
};
|
package/bin/lib/utils.js
CHANGED
|
@@ -77,9 +77,15 @@ function parseFrontmatter(content) {
|
|
|
77
77
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
78
78
|
if (!match) return null;
|
|
79
79
|
const meta = Object.create(null);
|
|
80
|
-
match[1].split(
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
80
|
+
match[1].split(/\r?\n/).forEach((rawLine, index) => {
|
|
81
|
+
const line = rawLine.trim();
|
|
82
|
+
if (!line || line.startsWith('#')) return;
|
|
83
|
+
|
|
84
|
+
const m = rawLine.match(/^([\w][\w-]*)\s*:\s*(.+)$/);
|
|
85
|
+
if (!m) {
|
|
86
|
+
throw new Error(`frontmatter 第 ${index + 1} 行格式无效: ${rawLine}`);
|
|
87
|
+
}
|
|
88
|
+
if (!UNSAFE_KEYS.has(m[1])) meta[m[1]] = m[2].trim().replace(/^["']|["']$/g, '');
|
|
83
89
|
});
|
|
84
90
|
return meta;
|
|
85
91
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { collectSkills } = require('./lib/skill-registry');
|
|
5
|
+
|
|
6
|
+
function resolveSkillsDir() {
|
|
7
|
+
return process.env.SAGE_SKILLS_DIR
|
|
8
|
+
? path.resolve(process.env.SAGE_SKILLS_DIR)
|
|
9
|
+
: path.join(__dirname, '..', 'skills');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function main() {
|
|
13
|
+
const skillsDir = resolveSkillsDir();
|
|
14
|
+
const skills = collectSkills(skillsDir);
|
|
15
|
+
console.log(`技能契约验证通过: ${skills.length} skills`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (require.main === module) {
|
|
19
|
+
try {
|
|
20
|
+
main();
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(err.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { main, resolveSkillsDir };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-abyss",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "邪修红尘仙·宿命深渊 - 一键为 Claude Code / Codex CLI 注入邪修人格与安全工程知识体系",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"node": ">=18.0.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
|
-
"test": "jest"
|
|
39
|
+
"test": "jest",
|
|
40
|
+
"verify:skills": "node bin/verify-skills-contract.js"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@inquirer/prompts": "^7.10.1"
|
package/skills/SKILL.md
CHANGED
|
@@ -8,6 +8,108 @@ disable-model-invocation: false
|
|
|
8
8
|
|
|
9
9
|
# 神通秘典 · 总纲
|
|
10
10
|
|
|
11
|
+
## Skill Authoring Contract
|
|
12
|
+
|
|
13
|
+
以下规则是 `skills/**/SKILL.md` 的正式 authoring contract;共享 registry、`run_skill.js`、Claude commands、Codex prompts、CI gate 全部以此为准。
|
|
14
|
+
|
|
15
|
+
### 必填 frontmatter
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
---
|
|
19
|
+
name: verify-quality
|
|
20
|
+
description: 代码质量校验关卡。
|
|
21
|
+
user-invocable: true
|
|
22
|
+
allowed-tools: Bash, Read, Glob # 可选,省略时默认 Read
|
|
23
|
+
argument-hint: <扫描路径> # 可选
|
|
24
|
+
---
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
必填字段:
|
|
28
|
+
|
|
29
|
+
- `name`:唯一标识,必须是 kebab-case slug,仅允许小写字母、数字、连字符
|
|
30
|
+
- `description`:会进入 Claude command frontmatter 与 Codex prompt 文本,不能为空
|
|
31
|
+
- `user-invocable`:`true/false`,决定是否进入生成集合
|
|
32
|
+
|
|
33
|
+
可选字段:
|
|
34
|
+
|
|
35
|
+
- `allowed-tools`:逗号分隔工具名列表;省略时默认 `Read`
|
|
36
|
+
- `argument-hint`:生成命令/提示词时展示参数说明
|
|
37
|
+
- 其他 frontmatter 可保留在 `meta` 中,但不会自动进入生成物
|
|
38
|
+
|
|
39
|
+
### 分类与运行时推断
|
|
40
|
+
|
|
41
|
+
- `category` 不是手写字段,而是由目录前缀自动推断:
|
|
42
|
+
- `skills/tools/*` → `tool`
|
|
43
|
+
- `skills/domains/*` → `domain`
|
|
44
|
+
- `skills/orchestration/*` → `orchestration`
|
|
45
|
+
- 其他位置 → `root`
|
|
46
|
+
- `runtimeType` 同样自动推断:
|
|
47
|
+
- `scripts/` 下存在且仅存在一个 `.js` 文件 → `scripted`
|
|
48
|
+
- 没有脚本入口 → `knowledge`
|
|
49
|
+
|
|
50
|
+
### 脚本入口规则
|
|
51
|
+
|
|
52
|
+
- 脚本型 skill 必须把唯一入口放在 `scripts/*.js`
|
|
53
|
+
- `scripts/` 下若出现多个 `.js` 文件,registry 会 fail-fast 报错
|
|
54
|
+
- `runtimeType=scripted` 时,Claude / Codex 产物都会调用各自的 `run_skill.js`
|
|
55
|
+
- `runtimeType=knowledge` 时,产物只读取对应 `SKILL.md`,不会尝试执行脚本
|
|
56
|
+
- `kind` 与 kebab-case compatibility 镜像字段已从 registry 返回面移除;对外只暴露 normalized fields,raw frontmatter 仅保留在 `meta`
|
|
57
|
+
|
|
58
|
+
### Fail-fast 校验
|
|
59
|
+
|
|
60
|
+
以下情况会让 `collectSkills()` / `npm run verify:skills` / CI 立即失败:
|
|
61
|
+
|
|
62
|
+
- `SKILL.md` 没有可解析 frontmatter
|
|
63
|
+
- frontmatter 缺少 `name`、`description` 或 `user-invocable`
|
|
64
|
+
- `name` 不是合法 kebab-case slug
|
|
65
|
+
- `allowed-tools` 含非法工具名
|
|
66
|
+
- skill name 重复,导致生成文件名冲突
|
|
67
|
+
- `scripts/` 下出现多个 `.js` 入口
|
|
68
|
+
|
|
69
|
+
### 生成链
|
|
70
|
+
|
|
71
|
+
1. registry 扫描并标准化 `skills/**/SKILL.md`
|
|
72
|
+
2. 仅 `userInvocable=true` 的 skill 进入 invocable 集合
|
|
73
|
+
3. Claude 生成 `~/.claude/commands/*.md`
|
|
74
|
+
4. Codex 生成 `~/.codex/prompts/*.md`
|
|
75
|
+
5. `run_skill.js` 仅负责 `runtimeType=scripted` 的执行编排
|
|
76
|
+
|
|
77
|
+
### 作者清单
|
|
78
|
+
|
|
79
|
+
新增或修改 skill 时,至少完成以下检查:
|
|
80
|
+
|
|
81
|
+
- 运行 `npm run verify:skills`
|
|
82
|
+
- 运行 `npm test -- --runInBand test/install-utils.test.js test/install-registry.test.js test/install-generation.test.js test/install-smoke.test.js test/run-skill.test.js`
|
|
83
|
+
- 确认命令名不会与现有 skill 冲突
|
|
84
|
+
- 若新增脚本型 skill,确认 `scripts/` 下仅一个 `.js` 入口
|
|
85
|
+
|
|
86
|
+
## 能力地图 / 调用规则 / 生成规则
|
|
87
|
+
|
|
88
|
+
### 能力地图
|
|
89
|
+
|
|
90
|
+
- `domains/`:知识型秘典,负责场景路由、原则、模板与执行纪律
|
|
91
|
+
- `tools/`:可执行校验/生成关卡,既可被 slash command / custom prompt 直接调用,也可在流程中自动触发
|
|
92
|
+
- `orchestration/`:协同规范与多 Agent 编排
|
|
93
|
+
- `run_skill.js`:脚本型 skill 执行器,不负责知识路由
|
|
94
|
+
|
|
95
|
+
### 调用规则
|
|
96
|
+
|
|
97
|
+
1. 每个 skill 的权威元数据来自对应 `SKILL.md` frontmatter
|
|
98
|
+
2. `user-invocable: true` 表示该 skill 进入可调用集合
|
|
99
|
+
3. 若 skill 目录下存在唯一 `scripts/*.js`,则视为脚本型 skill(`runtimeType=scripted`)
|
|
100
|
+
4. 若不存在脚本入口,则视为知识型 skill(`runtimeType=knowledge`),只读取 `SKILL.md` 执行
|
|
101
|
+
5. `run_skill.js` 只负责脚本型 skill:解析 skill、校验 `runtimeType`、加锁、执行脚本、透传退出码
|
|
102
|
+
|
|
103
|
+
### 自动生成规则
|
|
104
|
+
|
|
105
|
+
1. 安装器通过共享 registry 递归扫描 `skills/**/SKILL.md`
|
|
106
|
+
2. Claude 与 Codex 使用同一 invocable skill 集合
|
|
107
|
+
3. Claude 生成 `~/.claude/commands/*.md`
|
|
108
|
+
4. Codex 生成 `~/.codex/prompts/*.md`
|
|
109
|
+
5. `runtimeType=scripted` 时,双端都调用各自的 `~/.claude/skills/run_skill.js` / `~/.codex/skills/run_skill.js`
|
|
110
|
+
6. `runtimeType=knowledge` 时,双端都退化为“先读 `SKILL.md`,再据秘典执行”
|
|
111
|
+
7. `npm run verify:skills` 与 CI 会在生成前先校验整个 contract,任何无效 skill 都会阻断后续流程
|
|
112
|
+
|
|
11
113
|
## 目录结构
|
|
12
114
|
|
|
13
115
|
```
|
package/skills/run_skill.js
CHANGED
|
@@ -15,12 +15,11 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
const { spawn } = require('child_process');
|
|
18
|
-
const {
|
|
18
|
+
const { unlinkSync, closeSync, openSync } = require('fs');
|
|
19
19
|
const { join, resolve } = require('path');
|
|
20
20
|
const { createHash } = require('crypto');
|
|
21
21
|
const { tmpdir } = require('os');
|
|
22
|
-
|
|
23
|
-
const IS_WIN = process.platform === 'win32';
|
|
22
|
+
const { resolveExecutableSkillScript } = require('../bin/lib/skill-registry');
|
|
24
23
|
|
|
25
24
|
function getSkillsDir() {
|
|
26
25
|
const override = process.env.SAGE_SKILLS_DIR;
|
|
@@ -28,39 +27,29 @@ function getSkillsDir() {
|
|
|
28
27
|
return __dirname;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
const toolsDir = join(skillsDir, 'tools');
|
|
34
|
-
let entries;
|
|
35
|
-
try { entries = readdirSync(toolsDir).sort(); } catch { return found; }
|
|
36
|
-
|
|
37
|
-
for (const name of entries) {
|
|
38
|
-
const scriptsDir = join(toolsDir, name, 'scripts');
|
|
39
|
-
let scripts;
|
|
40
|
-
try { scripts = readdirSync(scriptsDir); } catch { continue; }
|
|
41
|
-
const jsFile = scripts.find(f => f.endsWith('.js'));
|
|
42
|
-
if (jsFile) found[name] = join(scriptsDir, jsFile);
|
|
43
|
-
}
|
|
44
|
-
return found;
|
|
30
|
+
function sleep(ms) {
|
|
31
|
+
return new Promise(resolveSleep => setTimeout(resolveSleep, ms));
|
|
45
32
|
}
|
|
46
33
|
|
|
47
|
-
function
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
function getScriptEntry(skillName) {
|
|
35
|
+
const skillsDir = getSkillsDir();
|
|
36
|
+
const { skill, scriptPath, reason } = resolveExecutableSkillScript(skillsDir, skillName);
|
|
37
|
+
|
|
38
|
+
if (reason === 'missing') {
|
|
51
39
|
console.error(`错误: 未知的 skill '${skillName}'`);
|
|
52
|
-
console.error(`可用的 skills: ${names}`);
|
|
53
40
|
process.exit(1);
|
|
54
41
|
}
|
|
55
|
-
return available[skillName];
|
|
56
|
-
}
|
|
57
42
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
if (reason === 'no-script') {
|
|
44
|
+
console.error(`错误: skill '${skillName}' 的 runtimeType 不是 scripted`);
|
|
45
|
+
console.error(`请先阅读: ${skill.skillPath}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { skill, scriptPath };
|
|
61
50
|
}
|
|
62
51
|
|
|
63
|
-
function acquireTargetLock(args) {
|
|
52
|
+
async function acquireTargetLock(args) {
|
|
64
53
|
const target = args.find(a => !a.startsWith('-')) || process.cwd();
|
|
65
54
|
const hash = createHash('md5').update(resolve(target)).digest('hex').slice(0, 12);
|
|
66
55
|
const lockPath = join(tmpdir(), `sage_skill_${hash}.lock`);
|
|
@@ -70,12 +59,18 @@ function acquireTargetLock(args) {
|
|
|
70
59
|
while (true) {
|
|
71
60
|
try {
|
|
72
61
|
const fd = openSync(lockPath, 'wx');
|
|
73
|
-
return { fd, lockPath };
|
|
62
|
+
return { fd, lockPath, target };
|
|
74
63
|
} catch (e) {
|
|
75
|
-
if (e.code !== 'EEXIST') return { fd: null, lockPath: null };
|
|
76
|
-
if (first) {
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
if (e.code !== 'EEXIST') return { fd: null, lockPath: null, target };
|
|
65
|
+
if (first) {
|
|
66
|
+
console.log(`⏳ 等待锁释放: ${target}`);
|
|
67
|
+
first = false;
|
|
68
|
+
}
|
|
69
|
+
if (Date.now() >= deadline) {
|
|
70
|
+
console.error(`⏳ 等待锁超时: ${target}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
await sleep(200);
|
|
79
74
|
}
|
|
80
75
|
}
|
|
81
76
|
}
|
|
@@ -89,7 +84,7 @@ function releaseLock({ fd, lockPath }) {
|
|
|
89
84
|
}
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
function main() {
|
|
87
|
+
async function main() {
|
|
93
88
|
const args = process.argv.slice(2);
|
|
94
89
|
|
|
95
90
|
if (args.length === 0 || args[0] === '-h' || args[0] === '--help') {
|
|
@@ -98,10 +93,9 @@ function main() {
|
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
const skillName = args[0];
|
|
101
|
-
const scriptPath =
|
|
96
|
+
const { scriptPath } = getScriptEntry(skillName);
|
|
102
97
|
const scriptArgs = args.slice(1);
|
|
103
|
-
|
|
104
|
-
const lock = acquireTargetLock(scriptArgs);
|
|
98
|
+
const lock = await acquireTargetLock(scriptArgs);
|
|
105
99
|
|
|
106
100
|
const child = spawn(process.execPath, [scriptPath, ...scriptArgs], {
|
|
107
101
|
stdio: 'inherit',
|
|
@@ -126,4 +120,7 @@ function main() {
|
|
|
126
120
|
});
|
|
127
121
|
}
|
|
128
122
|
|
|
129
|
-
main()
|
|
123
|
+
main().catch((err) => {
|
|
124
|
+
console.error(`执行错误: ${err.message}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|