Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 127 additions & 3 deletions bin/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ if (parseInt(process.versions.node) < parseInt(MIN_NODE)) {
process.exit(1);
}
const PKG_ROOT = fs.realpathSync(path.join(__dirname, '..'));
const { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog } =
const { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog, parseFrontmatter } =
require(path.join(__dirname, 'lib', 'utils.js'));
const { detectCclineBin, installCcline: _installCcline } = require(path.join(__dirname, 'lib', 'ccline.js'));

Expand Down Expand Up @@ -193,6 +193,122 @@ function runUninstall(tgt) {

// ── 安装核心 ──

/**
* 递归扫描 skills 目录,找出所有 user-invocable: true 的 SKILL.md
* @param {string} skillsDir - skills 源目录绝对路径
* @returns {Array<{meta: Object, relPath: string, hasScripts: boolean}>}
*/
function scanInvocableSkills(skillsDir) {
const results = [];
function scan(dir) {
const skillMd = path.join(dir, 'SKILL.md');
if (fs.existsSync(skillMd)) {
try {
const content = fs.readFileSync(skillMd, 'utf8');
const meta = parseFrontmatter(content);
if (meta && meta['user-invocable'] === 'true' && meta.name) {
const relPath = path.relative(skillsDir, dir);
const scriptsDir = path.join(dir, 'scripts');
const hasScripts = fs.existsSync(scriptsDir) &&
fs.readdirSync(scriptsDir).some(f => f.endsWith('.js'));
results.push({ meta, relPath, hasScripts });
}
} catch (e) { /* 解析失败跳过 */ }
}
try {
fs.readdirSync(dir).forEach(sub => {
const subPath = path.join(dir, sub);
if (fs.statSync(subPath).isDirectory() && !shouldSkip(sub) && sub !== 'scripts') {
scan(subPath);
}
});
} catch (e) { /* 读取失败跳过 */ }
}
scan(skillsDir);
return results;
}

/**
* 根据 SKILL.md 元数据生成 command .md 内容
* @param {Object} meta - parseFrontmatter 返回的元数据
* @param {string} skillRelPath - 相对于 skills/ 的路径(如 'tools/gen-docs')
* @param {boolean} hasScripts - 是否有可执行脚本
* @returns {string} command .md 文件内容
*/
function generateCommandContent(meta, skillRelPath, hasScripts) {
const name = meta.name;
const desc = (meta.description || '').replace(/"/g, '\\"');
const argHint = meta['argument-hint'];
const tools = meta['allowed-tools'] || 'Read';
const skillPath = skillRelPath
? `~/.claude/skills/${skillRelPath}/SKILL.md`
: '~/.claude/skills/SKILL.md';

const lines = [
'---',
`name: ${name}`,
`description: "${desc}"`,
];
if (argHint) lines.push(`argument-hint: "${argHint}"`);
lines.push(`allowed-tools: ${tools}`);
lines.push('---');
lines.push('');
lines.push('## 执行指南');
lines.push('');
lines.push('先读取完整的 Skill 定义获取详细规范:');
lines.push('');
lines.push('```');
lines.push(skillPath);
lines.push('```');

if (hasScripts) {
lines.push('');
lines.push('然后执行:');
lines.push('');
lines.push('```bash');
lines.push(`node ~/.claude/skills/run_skill.js ${name} $ARGUMENTS`);
lines.push('```');
} else {
lines.push('');
lines.push('根据秘典内容为用户提供专业指导。');
}

lines.push('');
return lines.join('\n');
}

/**
* 扫描 skills 并为 user-invocable 的 skill 生成 command 包装,文件级合并安装
*/
function installGeneratedCommands(skillsSrcDir, targetDir, backupDir, manifest) {
const skills = scanInvocableSkills(skillsSrcDir);
if (skills.length === 0) return 0;

const cmdsDir = path.join(targetDir, 'commands');
fs.mkdirSync(cmdsDir, { recursive: true });

skills.forEach(({ meta, relPath, hasScripts }) => {
const fileName = `${meta.name}.md`;
const destFile = path.join(cmdsDir, fileName);
const relFile = path.posix.join('commands', fileName);

if (fs.existsSync(destFile)) {
const cmdsBackupDir = path.join(backupDir, 'commands');
fs.mkdirSync(cmdsBackupDir, { recursive: true });
fs.copyFileSync(destFile, path.join(cmdsBackupDir, fileName));
manifest.backups.push(relFile);
info(`备份: ${c.d(relFile)}`);
}

const content = generateCommandContent(meta, relPath, hasScripts);
fs.writeFileSync(destFile, content);
manifest.installed.push(relFile);
});

ok(`commands/ ${c.d(`(自动生成 ${skills.length} 个斜杠命令)`)}`);
return skills.length;
}

function installCore(tgt) {
const targetDir = path.join(HOME, `.${tgt}`);
const backupDir = path.join(targetDir, '.sage-backup');
Expand All @@ -205,7 +321,7 @@ function installCore(tgt) {
{ src: 'config/CLAUDE.md', dest: tgt === 'claude' ? 'CLAUDE.md' : null },
{ src: 'config/AGENTS.md', dest: tgt === 'codex' ? 'AGENTS.md' : null },
{ src: 'output-styles', dest: tgt === 'claude' ? 'output-styles' : null },
{ src: 'skills', dest: 'skills' }
{ src: 'skills', dest: 'skills' },
].filter(f => f.dest !== null);

const manifest = {
Expand All @@ -223,6 +339,7 @@ function installCore(tgt) {
}
warn(`跳过: ${src}`); return;
}

if (fs.existsSync(destPath)) {
const bp = path.join(backupDir, dest);
rmSafe(bp); copyRecursive(destPath, bp); manifest.backups.push(dest);
Expand All @@ -232,6 +349,12 @@ function installCore(tgt) {
rmSafe(destPath); copyRecursive(srcPath, destPath); manifest.installed.push(dest);
});

// 为 Claude 目标自动生成 user-invocable 斜杠命令
if (tgt === 'claude') {
const skillsSrc = path.join(PKG_ROOT, 'skills');
installGeneratedCommands(skillsSrc, targetDir, backupDir, manifest);
}

const settingsPath = path.join(targetDir, 'settings.json');
let settings = {};
if (fs.existsSync(settingsPath)) {
Expand Down Expand Up @@ -426,5 +549,6 @@ if (require.main === module) {

module.exports = {
deepMergeNew, detectClaudeAuth, detectCodexAuth,
detectCclineBin, copyRecursive, shouldSkip, SETTINGS_TEMPLATE
detectCclineBin, copyRecursive, shouldSkip, SETTINGS_TEMPLATE,
scanInvocableSkills, generateCommandContent, installGeneratedCommands
};
18 changes: 17 additions & 1 deletion bin/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,20 @@ function printMergeLog(log, c) {
});
}

module.exports = { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog, SKIP };
/**
* 解析 Markdown 文件的 YAML frontmatter
* @param {string} content - 文件内容
* @returns {Object|null} 解析后的键值对,无 frontmatter 返回 null
*/
function parseFrontmatter(content) {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) return null;
const meta = {};
match[1].split('\n').forEach(line => {
const m = line.match(/^([\w][\w-]*)\s*:\s*(.+)/);
if (m) meta[m[1]] = m[2].trim().replace(/^["']|["']$/g, '');
});
return meta;
}

module.exports = { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog, parseFrontmatter, SKIP };
20 changes: 10 additions & 10 deletions config/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
| ❄ 玄冰 | 镇魔之盾,护佑安宁 | 蓝队、告警、IOC、应急、取证、SIEM、EDR |
| ⚡ 紫霄 | 攻守一体,方为大道 | 紫队、ATT&CK、TTP、检测验证、规则调优 |

详细攻防技术见 `skills/security/` 各秘典。
详细攻防技术见 `skills/domains/security/` 各秘典。

---

Expand Down Expand Up @@ -156,15 +156,15 @@

| 化身 | 秘典 | 触发场景 |
|------|------|----------|
| 🔥 赤焰 | `skills/security/red-team.md` | 渗透、红队、exploit、C2 |
| ❄ 玄冰 | `skills/security/blue-team.md` | 蓝队、告警、IOC、应急 |
| ⚡ 紫霄 | `skills/security/` | ATT&CK、TTP、攻防演练 |
| 📜 符箓 | `skills/development/` | 语言开发任务 |
| 👁 天眼 | `skills/security/threat-intel.md` | OSINT、威胁情报 |
| 🔮 丹鼎 | `skills/ai/` | RAG、Agent、LLM |
| 🕸 天罗 | `skills/multi-agent/` | TeamCreate、多Agent协同 |
| 🏗 阵法 | `skills/architecture/` | 架构、API、云原生、缓存、合规 |
| 🔧 炼器 | `skills/devops/` | Git、测试、数据库、性能、可观测性 |
| 🔥 赤焰 | `skills/domains/security/red-team.md` | 渗透、红队、exploit、C2 |
| ❄ 玄冰 | `skills/domains/security/blue-team.md` | 蓝队、告警、IOC、应急 |
| ⚡ 紫霄 | `skills/domains/security/` | ATT&CK、TTP、攻防演练 |
| 📜 符箓 | `skills/domains/development/` | 语言开发任务 |
| 👁 天眼 | `skills/domains/security/threat-intel.md` | OSINT、威胁情报 |
| 🔮 丹鼎 | `skills/domains/ai/` | RAG、Agent、LLM |
| 🕸 天罗 | `skills/orchestration/multi-agent/SKILL.md` | TeamCreate、多Agent协同 |
| 🏗 阵法 | `skills/domains/architecture/` | 架构、API、云原生、缓存、合规 |
| 🔧 炼器 | `skills/domains/devops/` | Git、测试、数据库、性能、可观测性 |

**校验关卡**(自动触发,不可跳过):

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion skills/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: sage
description: 邪修红尘仙·神通秘典总纲。智能路由到专业秘典。当魔尊需要任何开发、安全、架构、DevOps、AI 相关能力时,通过此入口路由到最匹配的专业秘典。
license: MIT
user-invocable: true
user-invocable: false
disable-model-invocation: false
---

Expand Down