eskill 1.0.6
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 +147 -0
- package/cli.js +206 -0
- package/lib/agent-config.js +55 -0
- package/lib/completion.js +78 -0
- package/lib/config.js +75 -0
- package/lib/git-url-parser.js +50 -0
- package/lib/installer.js +151 -0
- package/lib/npm-installer.js +31 -0
- package/lib/search.js +116 -0
- package/package.json +29 -0
- package/scripts/pre-publish-check.js +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# @empjs/skill
|
|
2
|
+
|
|
3
|
+
Unified AI Agent Skills Management - Install skills from Git URLs
|
|
4
|
+
|
|
5
|
+
从 GitHub/GitLab URL 直接安装 Claude、Cursor、Windsurf 等 AI 编程助手的技能。
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
- ✅ 从 Git URL 安装技能
|
|
10
|
+
- ✅ 搜索 99,749+ 技能(通过 SkillsMP API)
|
|
11
|
+
- ✅ 列出已安装的技能
|
|
12
|
+
- ✅ 删除技能
|
|
13
|
+
- ✅ **技能存储在 eskill 包目录,卸载时自动删除**
|
|
14
|
+
- ✅ Tab 键自动补全
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @empjs/skill
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 技能存储位置
|
|
23
|
+
|
|
24
|
+
⚠️ **重要变化**:技能现在存储在 **eskill 包目录** 下
|
|
25
|
+
|
|
26
|
+
- **存储位置**:`node_modules/@empjs/skill/skills-storage/`
|
|
27
|
+
- **优点**:`npm uninstall -g @empjs/skill` 时会自动删除所有技能
|
|
28
|
+
- **说明**:无需手动清理,卸载即删除
|
|
29
|
+
|
|
30
|
+
## 使用
|
|
31
|
+
|
|
32
|
+
### 1. 搜索技能(首次使用需配置 API Key)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# 首次搜索会提示配置 API Key
|
|
36
|
+
eskill search "git"
|
|
37
|
+
|
|
38
|
+
# 配置 API Key
|
|
39
|
+
eskill config set-api-key
|
|
40
|
+
# 访问 https://skillsmp.com/docs/api 获取免费 API Key
|
|
41
|
+
|
|
42
|
+
# 查看配置状态
|
|
43
|
+
eskill config status
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. 安装技能
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 从 GitHub URL 安装
|
|
50
|
+
eskill install https://github.com/anthropics/skills
|
|
51
|
+
|
|
52
|
+
# 强制覆盖已存在的技能
|
|
53
|
+
eskill install <url> --force
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. 管理技能
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 列出已安装的技能
|
|
60
|
+
eskill list
|
|
61
|
+
eskill ls
|
|
62
|
+
|
|
63
|
+
# 删除技能
|
|
64
|
+
eskill remove <skill-name>
|
|
65
|
+
eskill rm <skill-name>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 4. 卸载 eskill(自动删除所有技能)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm uninstall -g @empjs/skill
|
|
72
|
+
# 所有技能会自动删除,无需手动清理
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 5. 自动补全
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 安装自动补全
|
|
79
|
+
eskill completion >> ~/.bashrc
|
|
80
|
+
source ~/.bashrc
|
|
81
|
+
|
|
82
|
+
# 使用 Tab 补全
|
|
83
|
+
eskill remove <Tab> # 显示已安装的技能
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 命令参考
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
eskill install <url> # 安装技能
|
|
90
|
+
eskill add <url> # 同上(别名)
|
|
91
|
+
eskill search <query> # 搜索技能
|
|
92
|
+
eskill list # 列出已安装技能
|
|
93
|
+
eskill ls # 同上(别名)
|
|
94
|
+
eskill remove <name> # 删除技能
|
|
95
|
+
eskill rm <name> # 同上(别名)
|
|
96
|
+
eskill config <action> # 配置管理
|
|
97
|
+
eskill agents # 列出支持的 agents
|
|
98
|
+
eskill completion # 生成自动补全脚本
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 项目结构
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
skill-manager/
|
|
105
|
+
├── cli.js # CLI 入口
|
|
106
|
+
├── lib/ # 核心代码
|
|
107
|
+
│ ├── agent-config.js
|
|
108
|
+
│ ├── completion.js
|
|
109
|
+
│ ├── config.js
|
|
110
|
+
│ ├── git-url-parser.js
|
|
111
|
+
│ ├── installer.js
|
|
112
|
+
│ ├── npm-installer.js
|
|
113
|
+
│ └── search.js
|
|
114
|
+
├── skills-storage/ # 技能存储目录(卸载时自动删除)
|
|
115
|
+
│ └── .gitkeep
|
|
116
|
+
└── package.json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 安全注意事项
|
|
120
|
+
|
|
121
|
+
⚠️ **重要**:
|
|
122
|
+
|
|
123
|
+
1. **API Key 存储位置**:`~/.eskill/config.json`
|
|
124
|
+
2. **已配置 `.gitignore`**:`.eskill/` 目录不会被上传到 Git
|
|
125
|
+
3. **已配置 `.npmignore`**:敏感文件不会被发布到 npm
|
|
126
|
+
4. **请勿提交**:包含 API Key 的配置文件
|
|
127
|
+
|
|
128
|
+
## 开发
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# 克隆项目
|
|
132
|
+
git clone <repo>
|
|
133
|
+
cd skill-manager
|
|
134
|
+
|
|
135
|
+
# 安装依赖
|
|
136
|
+
npm install
|
|
137
|
+
|
|
138
|
+
# 全局链接(用于测试)
|
|
139
|
+
npm link
|
|
140
|
+
|
|
141
|
+
# 运行安全检查
|
|
142
|
+
npm run security-check
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 许可证
|
|
146
|
+
|
|
147
|
+
MIT
|
package/cli.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { installFromGitUrl, listSkills, removeSkill } from './lib/installer.js';
|
|
5
|
+
import { AGENTS, getDefaultAgent } from './lib/agent-config.js';
|
|
6
|
+
import { bashCompletionScript, zshCompletionScript, listSkillsForCompletion } from './lib/completion.js';
|
|
7
|
+
import { searchSkills, formatSkillList } from './lib/search.js';
|
|
8
|
+
import { setApiKey, showConfigStatus } from './lib/config.js';
|
|
9
|
+
import readline from 'readline';
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name('eskill')
|
|
15
|
+
.description('Unified AI Agent Skills Management - Install skills from Git URLs')
|
|
16
|
+
.version('1.0.0');
|
|
17
|
+
|
|
18
|
+
// 安装命令
|
|
19
|
+
program
|
|
20
|
+
.command('install')
|
|
21
|
+
.alias('add')
|
|
22
|
+
.description('从 Git URL 安装技能')
|
|
23
|
+
.argument('<url>', 'GitHub/GitLab URL')
|
|
24
|
+
.option('-a, --agent <name>', '目标 agent', getDefaultAgent())
|
|
25
|
+
.option('-l, --link', '使用符号链接而非复制', false)
|
|
26
|
+
.option('-f, --force', '强制覆盖已存在的技能', false)
|
|
27
|
+
.action(async (url, options) => {
|
|
28
|
+
const spinner = ora('正在安装技能...').start();
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await installFromGitUrl(url, options);
|
|
32
|
+
spinner.succeed('技能安装成功');
|
|
33
|
+
} catch (error) {
|
|
34
|
+
spinner.fail(`安装失败: ${error.message}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 列出命令
|
|
40
|
+
program
|
|
41
|
+
.command('list')
|
|
42
|
+
.alias('ls')
|
|
43
|
+
.description('列出已安装的技能')
|
|
44
|
+
.option('-a, --agent <name>', '目标 agent', getDefaultAgent())
|
|
45
|
+
.action(async (options) => {
|
|
46
|
+
try {
|
|
47
|
+
const skills = await listSkills(options.agent);
|
|
48
|
+
|
|
49
|
+
if (skills.length === 0) {
|
|
50
|
+
console.log('未安装任何技能');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`\nskill仓库目录:\n`);
|
|
55
|
+
skills.forEach(skill => {
|
|
56
|
+
console.log(` • ${skill.name}`);
|
|
57
|
+
});
|
|
58
|
+
console.log('');
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`错误: ${error.message}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 删除命令
|
|
66
|
+
program
|
|
67
|
+
.command('remove')
|
|
68
|
+
.alias('rm')
|
|
69
|
+
.alias('uninstall')
|
|
70
|
+
.description('删除已安装的技能')
|
|
71
|
+
.argument('<name>', '技能名称')
|
|
72
|
+
.option('-a, --agent <name>', '目标 agent', getDefaultAgent())
|
|
73
|
+
.action(async (name, options) => {
|
|
74
|
+
try {
|
|
75
|
+
removeSkill(name, options.agent);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(`错误: ${error.message}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 搜索命令
|
|
83
|
+
program
|
|
84
|
+
.command('search')
|
|
85
|
+
.description('搜索技能 (SkillsMP)')
|
|
86
|
+
.argument('<query>', '搜索关键词')
|
|
87
|
+
.option('-p, --page <number>', '页码', '1')
|
|
88
|
+
.option('-l, --limit <number>', '每页数量 (max: 100)', '20')
|
|
89
|
+
.option('-s, --sort <by>', '排序方式 (stars/recent)', 'stars')
|
|
90
|
+
.option('--ai', '使用 AI 语义搜索', false)
|
|
91
|
+
.action(async (query, options) => {
|
|
92
|
+
const spinner = ora('正在搜索...').start();
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await searchSkills(query, {
|
|
96
|
+
page: parseInt(options.page),
|
|
97
|
+
limit: parseInt(options.limit),
|
|
98
|
+
sortBy: options.sort,
|
|
99
|
+
ai: options.ai
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
spinner.stop();
|
|
103
|
+
|
|
104
|
+
if (result.success === false) {
|
|
105
|
+
if (result.needsApiKey) {
|
|
106
|
+
console.log('\n❌ 首次使用需要配置 SkillsMP API Key\n');
|
|
107
|
+
console.log('📝 获取步骤:');
|
|
108
|
+
console.log(' 1. 访问: https://skillsmp.com/docs/api');
|
|
109
|
+
console.log(' 2. 注册并获取免费的 API Key');
|
|
110
|
+
console.log(' 3. 运行命令配置: eskill config set-api-key\n');
|
|
111
|
+
console.log('💡 配置后即可搜索 99,749+ 个技能\n');
|
|
112
|
+
} else {
|
|
113
|
+
console.error(`搜索失败: ${result.error?.message || '未知错误'}`);
|
|
114
|
+
}
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const skills = result.data || result.skills || result.results || [];
|
|
119
|
+
console.log(formatSkillList(skills));
|
|
120
|
+
|
|
121
|
+
// 显示分页信息
|
|
122
|
+
if (result.totalPages || result.total) {
|
|
123
|
+
console.log(`总计: ${result.total || skills.length} 个技能\n`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 显示安装提示
|
|
127
|
+
if (skills.length > 0) {
|
|
128
|
+
console.log('💡 使用以下命令安装技能:');
|
|
129
|
+
console.log(` eskill install <GitHub URL>\n`);
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
spinner.fail(`搜索失败: ${error.message}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// 配置命令
|
|
138
|
+
program
|
|
139
|
+
.command('config')
|
|
140
|
+
.description('配置管理')
|
|
141
|
+
.argument('<action>', '操作: set-api-key, status')
|
|
142
|
+
.action(async (action) => {
|
|
143
|
+
if (action === 'set-api-key') {
|
|
144
|
+
const rl = readline.createInterface({
|
|
145
|
+
input: process.stdin,
|
|
146
|
+
output: process.stdout
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
rl.question('请输入 SkillsMP API Key: ', (apiKey) => {
|
|
150
|
+
if (apiKey.trim()) {
|
|
151
|
+
setApiKey(apiKey.trim());
|
|
152
|
+
console.log('\n✓ API Key 已配置成功');
|
|
153
|
+
console.log('现在可以使用 eskill search 搜索技能了\n');
|
|
154
|
+
} else {
|
|
155
|
+
console.log('\n✗ API Key 不能为空\n');
|
|
156
|
+
}
|
|
157
|
+
rl.close();
|
|
158
|
+
});
|
|
159
|
+
} else if (action === 'status') {
|
|
160
|
+
showConfigStatus();
|
|
161
|
+
} else {
|
|
162
|
+
console.log('\n未知操作。支持的操作:');
|
|
163
|
+
console.log(' set-api-key - 设置 API Key');
|
|
164
|
+
console.log(' status - 查看配置状态\n');
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 列出支持的 agents
|
|
169
|
+
program
|
|
170
|
+
.command('agents')
|
|
171
|
+
.alias('list-agents')
|
|
172
|
+
.description('列出支持的 agents')
|
|
173
|
+
.action(() => {
|
|
174
|
+
console.log('\n支持的 Agents:\n');
|
|
175
|
+
Object.entries(AGENTS).forEach(([key, config]) => {
|
|
176
|
+
console.log(` • ${key.padEnd(12)} - ${config.name}`);
|
|
177
|
+
console.log(` ${config.skillDir}\n`);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// 自动补全命令
|
|
182
|
+
program
|
|
183
|
+
.command('completion')
|
|
184
|
+
.description('生成自动补全脚本')
|
|
185
|
+
.option('-s, --shell <type>', 'Shell 类型 (bash/zsh)', 'bash')
|
|
186
|
+
.action((options) => {
|
|
187
|
+
const shell = options.shell.toLowerCase();
|
|
188
|
+
if (shell === 'bash') {
|
|
189
|
+
console.log(bashCompletionScript());
|
|
190
|
+
} else if (shell === 'zsh') {
|
|
191
|
+
console.log(zshCompletionScript());
|
|
192
|
+
} else {
|
|
193
|
+
console.error(`不支持的 shell 类型: ${shell}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// 内部命令:列出技能(用于自动补全)
|
|
199
|
+
program
|
|
200
|
+
.command('_list_skills')
|
|
201
|
+
.description('(内部命令) 列出技能名称')
|
|
202
|
+
.action(async () => {
|
|
203
|
+
await listSkillsForCompletion();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
program.parse();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取不同 Agent 的技能目录
|
|
3
|
+
*/
|
|
4
|
+
export const AGENTS = {
|
|
5
|
+
claude: {
|
|
6
|
+
name: 'Claude Code',
|
|
7
|
+
skillDir: '~/Desktop/skill-manager/skills-Repository'
|
|
8
|
+
},
|
|
9
|
+
cursor: {
|
|
10
|
+
name: 'Cursor',
|
|
11
|
+
skillDir: '~/.cursor/skills'
|
|
12
|
+
},
|
|
13
|
+
windsurf: {
|
|
14
|
+
name: 'Windsurf',
|
|
15
|
+
skillDir: '~/.windsurf/skills'
|
|
16
|
+
},
|
|
17
|
+
gemini: {
|
|
18
|
+
name: 'Gemini Code',
|
|
19
|
+
skillDir: '~/.gemini/skills'
|
|
20
|
+
},
|
|
21
|
+
copilot: {
|
|
22
|
+
name: 'GitHub Copilot',
|
|
23
|
+
skillDir: '~/.copilot/skills'
|
|
24
|
+
},
|
|
25
|
+
opencode: {
|
|
26
|
+
name: 'OpenCode',
|
|
27
|
+
skillDir: '~/.opencode/skills'
|
|
28
|
+
},
|
|
29
|
+
codex: {
|
|
30
|
+
name: 'Codex CLI',
|
|
31
|
+
skillDir: '~/.codex/skills'
|
|
32
|
+
},
|
|
33
|
+
continue: {
|
|
34
|
+
name: 'Continue',
|
|
35
|
+
skillDir: '~/.continue/skills'
|
|
36
|
+
},
|
|
37
|
+
centralized: {
|
|
38
|
+
name: 'Centralized',
|
|
39
|
+
skillDir: '~/.emp-agent/skills'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 获取默认 agent(优先使用 Claude Code)
|
|
45
|
+
*/
|
|
46
|
+
export function getDefaultAgent() {
|
|
47
|
+
return 'claude';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 展开路径中的 ~
|
|
52
|
+
*/
|
|
53
|
+
export function expandHomePath(path) {
|
|
54
|
+
return path.replace(/^~\//, `${process.env.HOME}/`);
|
|
55
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* eskill 命令自动补全脚本
|
|
4
|
+
* 安装方法:
|
|
5
|
+
* eskill completion >> ~/.bashrc
|
|
6
|
+
* eskill completion >> ~/.zshrc
|
|
7
|
+
*/
|
|
8
|
+
import { listSkills } from './installer.js';
|
|
9
|
+
import { getDefaultAgent } from './agent-config.js';
|
|
10
|
+
|
|
11
|
+
// 获取已安装的技能列表
|
|
12
|
+
async function getSkillList() {
|
|
13
|
+
try {
|
|
14
|
+
const agent = getDefaultAgent();
|
|
15
|
+
const skills = await listSkills(agent);
|
|
16
|
+
return skills.map(s => s.name);
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Bash 补全脚本
|
|
23
|
+
export function bashCompletionScript() {
|
|
24
|
+
return '\n_eskill_completion() {\n' +
|
|
25
|
+
' local cur prev words cword\n' +
|
|
26
|
+
' _init_completion || return\n\n' +
|
|
27
|
+
' case ${prev} in\n' +
|
|
28
|
+
' remove|rm|uninstall)\n' +
|
|
29
|
+
' local skills=$(eskill _list_skills 2>/dev/null)\n' +
|
|
30
|
+
' COMPREPLY=($(compgen -W "${skills}" -- "${cur}"))\n' +
|
|
31
|
+
' ;;\n' +
|
|
32
|
+
' *)\n' +
|
|
33
|
+
' case ${cur} in\n' +
|
|
34
|
+
' -*)\n' +
|
|
35
|
+
' COMPREPLY=($(compgen -W "-a --agent -l --link -f --force -h --help -V --version" -- "${cur}"))\n' +
|
|
36
|
+
' ;;\n' +
|
|
37
|
+
' *)\n' +
|
|
38
|
+
' COMPREPLY=($(compgen -W "install add list ls remove rm uninstall agents list-agents help" -- "${cur}"))\n' +
|
|
39
|
+
' ;;\n' +
|
|
40
|
+
' esac\n' +
|
|
41
|
+
' ;;\n' +
|
|
42
|
+
' esac\n' +
|
|
43
|
+
'}\n\n' +
|
|
44
|
+
'complete -F _eskill_completion eskill\n';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Zsh 补全脚本
|
|
48
|
+
export function zshCompletionScript() {
|
|
49
|
+
return '\n#compdef eskill\n\n' +
|
|
50
|
+
'_eskill() {\n' +
|
|
51
|
+
' local -a commands\n' +
|
|
52
|
+
' commands=(\n' +
|
|
53
|
+
' \'install:从 Git URL 安装技能\'\n' +
|
|
54
|
+
' \'add:从 Git URL 安装技能(别名)\'\n' +
|
|
55
|
+
' \'list:列出已安装的技能\'\n' +
|
|
56
|
+
' \'ls:列出已安装的技能(别名)\'\n' +
|
|
57
|
+
' \'remove:删除已安装的技能\'\n' +
|
|
58
|
+
' \'rm:删除已安装的技能(别名)\'\n' +
|
|
59
|
+
' \'uninstall:删除已安装的技能(别名)\'\n' +
|
|
60
|
+
' \'agents:列出支持的 agents\'\n' +
|
|
61
|
+
' \'list-agents:列出支持的 agents(别名)\'\n' +
|
|
62
|
+
' \'help:显示帮助信息\'\n' +
|
|
63
|
+
' )\n\n' +
|
|
64
|
+
' if [[ ${words[(I)(remove|rm|uninstall)]} -gt 0 ]]; then\n' +
|
|
65
|
+
' local skills=($(eskill _list_skills 2>/dev/null))\n' +
|
|
66
|
+
' _describe \'skills\' skills\n' +
|
|
67
|
+
' else\n' +
|
|
68
|
+
' _describe \'command\' commands\n' +
|
|
69
|
+
' fi\n' +
|
|
70
|
+
'}\n\n' +
|
|
71
|
+
'_eskill "$@"\n';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 列出技能的内部命令
|
|
75
|
+
export async function listSkillsForCompletion() {
|
|
76
|
+
const skills = await getSkillList();
|
|
77
|
+
console.log(skills.join(' '));
|
|
78
|
+
}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置管理
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
|
|
8
|
+
const CONFIG_DIR = join(homedir(), '.eskill');
|
|
9
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 确保配置目录存在
|
|
13
|
+
*/
|
|
14
|
+
function ensureConfigDir() {
|
|
15
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
16
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 读取配置
|
|
22
|
+
*/
|
|
23
|
+
export function readConfig() {
|
|
24
|
+
if (existsSync(CONFIG_FILE)) {
|
|
25
|
+
try {
|
|
26
|
+
const data = readFileSync(CONFIG_FILE, 'utf-8');
|
|
27
|
+
return JSON.parse(data);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 写入配置
|
|
37
|
+
*/
|
|
38
|
+
export function writeConfig(config) {
|
|
39
|
+
ensureConfigDir();
|
|
40
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 设置 API key
|
|
45
|
+
*/
|
|
46
|
+
export function setApiKey(key) {
|
|
47
|
+
const config = readConfig();
|
|
48
|
+
config.skillsmpApiKey = key;
|
|
49
|
+
writeConfig(config);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 获取 API key
|
|
54
|
+
*/
|
|
55
|
+
export function getApiKey() {
|
|
56
|
+
const config = readConfig();
|
|
57
|
+
return config.skillsmpApiKey || process.env.SKILLSMP_API_KEY;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 检查是否已配置 API key
|
|
62
|
+
*/
|
|
63
|
+
export function hasApiKey() {
|
|
64
|
+
return !!getApiKey();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 显示配置状态
|
|
69
|
+
*/
|
|
70
|
+
export function showConfigStatus() {
|
|
71
|
+
const config = readConfig();
|
|
72
|
+
console.log('\n配置状态:\n');
|
|
73
|
+
console.log(` API Key: ${config.skillsmpApiKey ? '✓ 已配置' : '✗ 未配置'}`);
|
|
74
|
+
console.log(` 配置文件: ${CONFIG_FILE}\n`);
|
|
75
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 解析 Git URL(支持 GitHub/GitLab)
|
|
3
|
+
* 支持格式:
|
|
4
|
+
* - https://github.com/user/repo/tree/main/skills/skill-name
|
|
5
|
+
* - https://gitlab.com/user/repo/-/tree/main/skills/skill-name
|
|
6
|
+
* - https://github.com/user/repo
|
|
7
|
+
*/
|
|
8
|
+
export function parseGitUrl(url) {
|
|
9
|
+
const patterns = [
|
|
10
|
+
// GitHub with tree and path: https://github.com/user/repo/tree/branch/path/to/skill
|
|
11
|
+
{
|
|
12
|
+
regex: /^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)(?:\/(.*))?$/,
|
|
13
|
+
platform: 'github'
|
|
14
|
+
},
|
|
15
|
+
// GitLab with tree and path: https://gitlab.com/user/repo/-/tree/branch/path/to/skill
|
|
16
|
+
{
|
|
17
|
+
regex: /^https?:\/\/gitlab\.com\/([^\/]+)\/([^\/]+)\/-\/tree\/([^\/]+)(?:\/(.*))?$/,
|
|
18
|
+
platform: 'gitlab'
|
|
19
|
+
},
|
|
20
|
+
// Simple GitHub URL: https://github.com/user/repo
|
|
21
|
+
{
|
|
22
|
+
regex: /^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)$/,
|
|
23
|
+
platform: 'github',
|
|
24
|
+
defaultBranch: true
|
|
25
|
+
},
|
|
26
|
+
// Simple GitLab URL: https://gitlab.com/user/repo
|
|
27
|
+
{
|
|
28
|
+
regex: /^https?:\/\/gitlab\.com\/([^\/]+)\/([^\/]+)$/,
|
|
29
|
+
platform: 'gitlab',
|
|
30
|
+
defaultBranch: true
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const pattern of patterns) {
|
|
35
|
+
const match = url.match(pattern.regex);
|
|
36
|
+
if (match) {
|
|
37
|
+
const [, owner, repo, branch, path] = match;
|
|
38
|
+
return {
|
|
39
|
+
platform: pattern.platform,
|
|
40
|
+
owner,
|
|
41
|
+
repo: pattern.defaultBranch ? repo : repo.replace(/\.git$/, ''),
|
|
42
|
+
branch: pattern.defaultBranch ? 'main' : branch,
|
|
43
|
+
path: path || '',
|
|
44
|
+
cloneUrl: `https://${pattern.platform === 'github' ? 'github.com' : 'gitlab.com'}/${owner}/${repo}.git`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error(`不支持的 Git URL 格式: ${url}`);
|
|
50
|
+
}
|
package/lib/installer.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, mkdirSync, symlinkSync, rmSync, readdirSync } from 'fs';
|
|
3
|
+
import { join, basename, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { parseGitUrl } from './git-url-parser.js';
|
|
6
|
+
import { AGENTS, expandHomePath } from './agent-config.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 获取 eskill 包的安装目录
|
|
10
|
+
* 技能安装在这里,卸载时会自动删除
|
|
11
|
+
*/
|
|
12
|
+
function getEskillPackageDir() {
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
return dirname(__dirname); // 返回包根目录
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 获取技能存储目录
|
|
20
|
+
*/
|
|
21
|
+
function getSkillsStorageDir() {
|
|
22
|
+
const pkgDir = getEskillPackageDir();
|
|
23
|
+
return join(pkgDir, 'skills-storage');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 从 Git URL 安装技能
|
|
28
|
+
*/
|
|
29
|
+
export async function installFromGitUrl(gitUrl, options = {}) {
|
|
30
|
+
const { agent = 'claude', link = false, force = false } = options;
|
|
31
|
+
|
|
32
|
+
// 解析 Git URL
|
|
33
|
+
const parsed = parseGitUrl(gitUrl);
|
|
34
|
+
console.log(`平台: ${parsed.platform}`);
|
|
35
|
+
console.log(`仓库: ${parsed.owner}/${parsed.repo}`);
|
|
36
|
+
console.log(`分支: ${parsed.branch}`);
|
|
37
|
+
if (parsed.path) {
|
|
38
|
+
console.log(`路径: ${parsed.path}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 获取技能存储目录
|
|
42
|
+
const skillDir = getSkillsStorageDir();
|
|
43
|
+
|
|
44
|
+
// 确保技能目录存在
|
|
45
|
+
if (!existsSync(skillDir)) {
|
|
46
|
+
mkdirSync(skillDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 确定技能名称
|
|
50
|
+
const skillName = parsed.path ? basename(parsed.path) : parsed.repo;
|
|
51
|
+
const targetPath = join(skillDir, skillName);
|
|
52
|
+
|
|
53
|
+
// 检查是否已存在
|
|
54
|
+
if (existsSync(targetPath)) {
|
|
55
|
+
if (!force) {
|
|
56
|
+
throw new Error(`技能已存在: ${skillName}\n使用 --force 选项强制覆盖`);
|
|
57
|
+
}
|
|
58
|
+
console.log(`删除已存在的技能: ${targetPath}`);
|
|
59
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 创建临时目录
|
|
63
|
+
const tempDir = `/tmp/eskill-${Date.now()}`;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
// 克隆仓库
|
|
67
|
+
console.log(`\n克隆仓库...`);
|
|
68
|
+
const cloneUrl = parsed.cloneUrl;
|
|
69
|
+
execSync(
|
|
70
|
+
`git clone --depth 1 --branch ${parsed.branch} --single-branch ${cloneUrl} ${tempDir}`,
|
|
71
|
+
{ stdio: 'inherit' }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// 确定源路径
|
|
75
|
+
const sourcePath = parsed.path ? join(tempDir, parsed.path) : tempDir;
|
|
76
|
+
|
|
77
|
+
// 验证源路径存在
|
|
78
|
+
if (!existsSync(sourcePath)) {
|
|
79
|
+
throw new Error(`路径不存在: ${sourcePath}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 安装技能
|
|
83
|
+
if (link) {
|
|
84
|
+
// 使用符号链接
|
|
85
|
+
console.log(`\n创建符号链接: ${sourcePath} -> ${targetPath}`);
|
|
86
|
+
symlinkSync(sourcePath, targetPath, 'dir');
|
|
87
|
+
} else {
|
|
88
|
+
// 复制文件
|
|
89
|
+
console.log(`\n复制文件到: ${targetPath}`);
|
|
90
|
+
execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(`\n✓ 技能已安装到: ${targetPath}`);
|
|
94
|
+
console.log(` 存储位置: eskill 包目录`);
|
|
95
|
+
console.log(` 说明: 卸载 eskill 时会自动删除所有技能`);
|
|
96
|
+
|
|
97
|
+
return { success: true, path: targetPath };
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// 清理临时目录
|
|
100
|
+
if (existsSync(tempDir)) {
|
|
101
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
} finally {
|
|
105
|
+
// 清理临时目录(如果是符号链接则保留源)
|
|
106
|
+
if (!link && existsSync(tempDir)) {
|
|
107
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 列出已安装的技能
|
|
114
|
+
*/
|
|
115
|
+
export function listSkills(agent = 'claude') {
|
|
116
|
+
const skillDir = getSkillsStorageDir();
|
|
117
|
+
|
|
118
|
+
if (!existsSync(skillDir)) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 读取目录中的技能
|
|
123
|
+
return readdirSync(skillDir, { withFileTypes: true })
|
|
124
|
+
.filter(dirent => dirent.isDirectory())
|
|
125
|
+
.map(dirent => ({
|
|
126
|
+
name: dirent.name,
|
|
127
|
+
path: join(skillDir, dirent.name)
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 删除技能
|
|
133
|
+
*/
|
|
134
|
+
export function removeSkill(skillName, agent = 'claude') {
|
|
135
|
+
const skillDir = getSkillsStorageDir();
|
|
136
|
+
const targetPath = join(skillDir, skillName);
|
|
137
|
+
|
|
138
|
+
if (!existsSync(targetPath)) {
|
|
139
|
+
throw new Error(`技能不存在: ${skillName}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
143
|
+
console.log(`✓ 已删除技能: ${skillName}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 获取技能存储目录路径
|
|
148
|
+
*/
|
|
149
|
+
export function getSkillsDir() {
|
|
150
|
+
return getSkillsStorageDir();
|
|
151
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm 包名解析器
|
|
3
|
+
* 支持格式:
|
|
4
|
+
* - @scope/package-name
|
|
5
|
+
* - package-name
|
|
6
|
+
* - package-name@version
|
|
7
|
+
* - @scope/package-name@version
|
|
8
|
+
*/
|
|
9
|
+
export function parseNpmPackageName(input) {
|
|
10
|
+
// 匹配 npm 包名(支持 scope 和版本)
|
|
11
|
+
const match = input.match(/^(@[^\/]+\/[^@]+|[^@]+)(?:@(.+))?$/);
|
|
12
|
+
|
|
13
|
+
if (!match) {
|
|
14
|
+
throw new Error(`无效的 npm 包名: ${input}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [, packageName, version = 'latest'] = match;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
packageName,
|
|
21
|
+
version,
|
|
22
|
+
fullName: packageName
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 检查是否为 npm 包
|
|
28
|
+
*/
|
|
29
|
+
export function isNpmPackage(input) {
|
|
30
|
+
return input.startsWith('@') || !input.includes('/');
|
|
31
|
+
}
|
package/lib/search.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 技能搜索功能
|
|
3
|
+
* 支持 SkillsMP API 搜索
|
|
4
|
+
*/
|
|
5
|
+
import https from 'https';
|
|
6
|
+
import { getApiKey } from './config.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 发起 HTTPS 请求
|
|
10
|
+
*/
|
|
11
|
+
function httpsGet(url, headers = {}) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
https.get(url, {
|
|
14
|
+
headers: {
|
|
15
|
+
'User-Agent': 'eskill-cli/1.0.0',
|
|
16
|
+
...headers
|
|
17
|
+
}
|
|
18
|
+
}, (res) => {
|
|
19
|
+
let data = '';
|
|
20
|
+
res.on('data', chunk => data += chunk);
|
|
21
|
+
res.on('end', () => {
|
|
22
|
+
try {
|
|
23
|
+
resolve(JSON.parse(data));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
resolve({ raw: data });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}).on('error', reject);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* SkillsMP 搜索
|
|
34
|
+
*/
|
|
35
|
+
async function searchSkillsMP(query, options = {}) {
|
|
36
|
+
const { page = 1, limit = 20, sortBy = 'stars', ai = false } = options;
|
|
37
|
+
const apiKey = getApiKey();
|
|
38
|
+
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
needsApiKey: true,
|
|
43
|
+
error: { message: '需要 API key' }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const endpoint = ai ? '/api/v1/skills/ai-search' : '/api/v1/skills/search';
|
|
48
|
+
let url = `https://skillsmp.com${endpoint}?q=${encodeURIComponent(query)}`;
|
|
49
|
+
|
|
50
|
+
if (!ai) {
|
|
51
|
+
url += `&page=${page}&limit=${limit}&sortBy=${sortBy}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const headers = { 'Authorization': `Bearer ${apiKey}` };
|
|
56
|
+
const result = await httpsGet(url, headers);
|
|
57
|
+
return result;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(`SkillsMP 搜索失败: ${error.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 统一搜索接口
|
|
65
|
+
*/
|
|
66
|
+
export async function searchSkills(query, options = {}) {
|
|
67
|
+
return await searchSkillsMP(query, options);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 格式化技能列表显示
|
|
72
|
+
*/
|
|
73
|
+
export function formatSkillList(skills, showIndex = true) {
|
|
74
|
+
if (!skills || skills.length === 0) {
|
|
75
|
+
return '未找到相关技能';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let output = '\n';
|
|
79
|
+
skills.forEach((skill, index) => {
|
|
80
|
+
if (showIndex) {
|
|
81
|
+
output += ` ${index + 1}. ${skill.name || skill.title}\n`;
|
|
82
|
+
} else {
|
|
83
|
+
output += ` • ${skill.name || skill.title}\n`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (skill.description) {
|
|
87
|
+
output += ` ${skill.description.substring(0, 80)}${skill.description.length > 80 ? '...' : ''}\n`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (skill.author || skill.owner) {
|
|
91
|
+
output += ` 作者: ${skill.author || skill.owner}\n`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (skill.stars !== undefined) {
|
|
95
|
+
output += ` Stars: ⭐ ${skill.stars}\n`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (skill.url || skill.github_url) {
|
|
99
|
+
output += ` ${skill.url || skill.github_url}\n`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
output += '\n';
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return output;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 从搜索结果中提取 GitHub URL 用于安装
|
|
110
|
+
*/
|
|
111
|
+
export function extractInstallUrl(skill) {
|
|
112
|
+
if (skill.github_url) return skill.github_url;
|
|
113
|
+
if (skill.url && skill.url.includes('github.com')) return skill.url;
|
|
114
|
+
if (skill.repository) return skill.repository;
|
|
115
|
+
return null;
|
|
116
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eskill",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "Unified AI Agent Skills Management - Install skills from Git URLs",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"eskill": "./cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"prepublishOnly": "node scripts/pre-publish-check.js",
|
|
12
|
+
"test": "node scripts/pre-publish-check.js",
|
|
13
|
+
"security-check": "node scripts/pre-publish-check.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude",
|
|
17
|
+
"skills",
|
|
18
|
+
"agent",
|
|
19
|
+
"cursor",
|
|
20
|
+
"windsurf",
|
|
21
|
+
"git"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"commander": "^12.0.0",
|
|
27
|
+
"ora": "^8.0.1"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 发布前安全检查
|
|
4
|
+
* 确保没有敏感信息被包含在发布包中
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
console.log('🔒 安全检查中...\n');
|
|
10
|
+
|
|
11
|
+
const issues = [];
|
|
12
|
+
|
|
13
|
+
// 1. 检查 .eskill 目录
|
|
14
|
+
const configDir = join(process.env.HOME, '.eskill');
|
|
15
|
+
if (existsSync(configDir)) {
|
|
16
|
+
console.log('⚠️ 警告: 检测到本地配置目录');
|
|
17
|
+
console.log(` 位置: ${configDir}`);
|
|
18
|
+
console.log(' ✅ 已被 .gitignore 和 .npmignore 忽略\n');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 2. 检查 node_modules
|
|
22
|
+
if (existsSync(join(process.cwd(), 'node_modules'))) {
|
|
23
|
+
console.log('✅ node_modules 存在(已忽略)');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 3. 检查 .gitignore
|
|
27
|
+
if (existsSync(join(process.cwd(), '.gitignore'))) {
|
|
28
|
+
const gitignore = readFileSync(join(process.cwd(), '.gitignore'), 'utf-8');
|
|
29
|
+
if (gitignore.includes('.eskill/')) {
|
|
30
|
+
console.log('✅ .gitignore 包含 .eskill/');
|
|
31
|
+
} else {
|
|
32
|
+
issues.push('.gitignore 未包含 .eskill/');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 4. 检查 .npmignore
|
|
37
|
+
if (existsSync(join(process.cwd(), '.npmignore'))) {
|
|
38
|
+
const npmignore = readFileSync(join(process.cwd(), '.npmignore'), 'utf-8');
|
|
39
|
+
if (npmignore.includes('.eskill/')) {
|
|
40
|
+
console.log('✅ .npmignore 包含 .eskill/');
|
|
41
|
+
} else {
|
|
42
|
+
issues.push('.npmignore 未包含 .eskill/');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log('\n📋 检查结果:');
|
|
47
|
+
|
|
48
|
+
if (issues.length > 0) {
|
|
49
|
+
console.log('\n❌ 发现问题:');
|
|
50
|
+
issues.forEach(issue => console.log(` - ${issue}`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
} else {
|
|
53
|
+
console.log('\n✅ 所有检查通过!可以安全发布。');
|
|
54
|
+
console.log('\n📝 发布步骤:');
|
|
55
|
+
console.log(' npm publish');
|
|
56
|
+
console.log(' 或');
|
|
57
|
+
console.log(' git push origin main\n');
|
|
58
|
+
}
|