skill-base-cli 1.0.0

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 ADDED
@@ -0,0 +1,86 @@
1
+ # Skill Base CLI
2
+
3
+ 命令行工具,用于搜索、安装、更新和发布 AI Agent Skills。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g skill-base-cli
9
+ ```
10
+
11
+ 或使用 npx 直接运行:
12
+
13
+ ```bash
14
+ npx skill-base-cli <command>
15
+ ```
16
+
17
+ ## 环境配置
18
+
19
+ CLI 默认连接 `http://localhost:8000`,可通过环境变量修改:
20
+
21
+ ```bash
22
+ export SKB_BASE_URL=https://your-skill-base-server.com
23
+ ```
24
+
25
+ ## 命令
26
+
27
+ ### 认证
28
+
29
+ ```bash
30
+ # 登录
31
+ skb login
32
+
33
+ # 登出
34
+ skb logout
35
+ ```
36
+
37
+ ### Skill 管理
38
+
39
+ ```bash
40
+ # 搜索 Skill
41
+ skb search <keyword>
42
+
43
+ # 安装 Skill
44
+ skb install <skill_id>
45
+ skb install <skill_id>@<version>
46
+ skb install <skill_id> -d ./target-dir
47
+
48
+ # 更新 Skill
49
+ skb update <skill_id>
50
+ skb update <skill_id> -d ./target-dir
51
+
52
+ # 发布 Skill
53
+ skb publish <directory>
54
+ skb publish <directory> --name "Skill Name" --description "Description"
55
+ skb publish <directory> --changelog "版本说明"
56
+ ```
57
+
58
+ ## 快速开始
59
+
60
+ ```bash
61
+ # 1. 登录
62
+ skb login
63
+
64
+ # 2. 搜索
65
+ skb search vue
66
+
67
+ # 3. 安装
68
+ skb install vue-best-practices
69
+
70
+ # 4. 发布自己的 Skill
71
+ skb publish ./my-skill --changelog "初始版本"
72
+ ```
73
+
74
+ ## 发布要求
75
+
76
+ - 目录必须包含 `SKILL.md` 文件
77
+ - 文件夹名称将作为 `skill_id`
78
+ - `name` 和 `description` 可从 SKILL.md 自动提取
79
+
80
+ ## 系统要求
81
+
82
+ - Node.js >= 18.0.0
83
+
84
+ ## License
85
+
86
+ MIT
package/bin/skb.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import login from '../lib/commands/login.js';
5
+ import logout from '../lib/commands/logout.js';
6
+ import search from '../lib/commands/search.js';
7
+ import install from '../lib/commands/install.js';
8
+ import update from '../lib/commands/update.js';
9
+ import publish from '../lib/commands/publish.js';
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name('skb')
15
+ .description('Skill Base CLI - 命令行管理工具')
16
+ .version('1.0.0');
17
+
18
+ program
19
+ .command('login')
20
+ .description('登录获取访问令牌')
21
+ .action(login);
22
+
23
+ program
24
+ .command('logout')
25
+ .description('登出并清除本地凭证')
26
+ .action(logout);
27
+
28
+ program
29
+ .command('search <keyword>')
30
+ .description('搜索 Skill')
31
+ .action(search);
32
+
33
+ program
34
+ .command('install <target>')
35
+ .description('安装 Skill(支持 name@version 格式)')
36
+ .option('-d, --dir <directory>', '指定解压目标目录', process.cwd())
37
+ .action(install);
38
+
39
+ program
40
+ .command('update <skill_id>')
41
+ .description('更新 Skill 到最新版本')
42
+ .option('-d, --dir <directory>', '指定解压目标目录', process.cwd())
43
+ .action(update);
44
+
45
+ program
46
+ .command('publish <directory>')
47
+ .description('发布新版本(指定 Skill 文件夹)')
48
+ .option('--name <name>', 'Skill 名称(默认从 SKILL.md 提取)')
49
+ .option('--description <desc>', 'Skill 描述(默认从 SKILL.md 提取)')
50
+ .option('--changelog <log>', '版本变更日志', '更新版本')
51
+ .action(publish);
52
+
53
+ program.parse();
package/lib/api.js ADDED
@@ -0,0 +1,89 @@
1
+ import { getConfig } from './config.js';
2
+ import { loadCredentials } from './auth.js';
3
+
4
+ async function handleResponse(response) {
5
+ if (!response.ok) {
6
+ let message = `HTTP ${response.status}`;
7
+ try {
8
+ const data = await response.json();
9
+ if (data.detail) {
10
+ message = data.detail;
11
+ }
12
+ } catch (e) {
13
+ // 无法解析 JSON,使用默认消息
14
+ }
15
+ throw new Error(message);
16
+ }
17
+ return response.json();
18
+ }
19
+
20
+ function getAuthHeaders() {
21
+ const credentials = loadCredentials();
22
+ const headers = {};
23
+ if (credentials?.token) {
24
+ headers['Authorization'] = `Bearer ${credentials.token}`;
25
+ }
26
+ return headers;
27
+ }
28
+
29
+ export function createClient() {
30
+ const { apiUrl } = getConfig();
31
+
32
+ return {
33
+ async get(path) {
34
+ const response = await fetch(`${apiUrl}${path}`, {
35
+ method: 'GET',
36
+ headers: {
37
+ ...getAuthHeaders()
38
+ }
39
+ });
40
+ return handleResponse(response);
41
+ },
42
+
43
+ async post(path, body) {
44
+ const response = await fetch(`${apiUrl}${path}`, {
45
+ method: 'POST',
46
+ headers: {
47
+ 'Content-Type': 'application/json',
48
+ ...getAuthHeaders()
49
+ },
50
+ body: JSON.stringify(body)
51
+ });
52
+ return handleResponse(response);
53
+ },
54
+
55
+ async postForm(path, formData) {
56
+ // 使用原生 FormData,让 fetch 自动设置 Content-Type(含 boundary)
57
+ const response = await fetch(`${apiUrl}${path}`, {
58
+ method: 'POST',
59
+ headers: {
60
+ ...getAuthHeaders()
61
+ },
62
+ body: formData
63
+ });
64
+ return handleResponse(response);
65
+ },
66
+
67
+ async download(path) {
68
+ const response = await fetch(`${apiUrl}${path}`, {
69
+ method: 'GET',
70
+ headers: {
71
+ ...getAuthHeaders()
72
+ }
73
+ });
74
+ if (!response.ok) {
75
+ let message = `HTTP ${response.status}`;
76
+ try {
77
+ const data = await response.json();
78
+ if (data.detail) {
79
+ message = data.detail;
80
+ }
81
+ } catch (e) {
82
+ // 无法解析 JSON,使用默认消息
83
+ }
84
+ throw new Error(message);
85
+ }
86
+ return response;
87
+ }
88
+ };
89
+ }
package/lib/auth.js ADDED
@@ -0,0 +1,35 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getConfig } from './config.js';
4
+
5
+ export function loadCredentials() {
6
+ const { credentialsPath } = getConfig();
7
+
8
+ try {
9
+ const content = fs.readFileSync(credentialsPath, 'utf-8');
10
+ return JSON.parse(content);
11
+ } catch (err) {
12
+ // 文件不存在时返回 null
13
+ return null;
14
+ }
15
+ }
16
+
17
+ export function saveCredentials({ token, username }) {
18
+ const { credentialsDir, credentialsPath } = getConfig();
19
+
20
+ // 创建目录(recursive)
21
+ fs.mkdirSync(credentialsDir, { recursive: true });
22
+
23
+ // 写入 JSON
24
+ fs.writeFileSync(credentialsPath, JSON.stringify({ token, username }, null, 2));
25
+ }
26
+
27
+ export function removeCredentials() {
28
+ const { credentialsPath } = getConfig();
29
+
30
+ try {
31
+ fs.unlinkSync(credentialsPath);
32
+ } catch (err) {
33
+ // 不存在时静默忽略
34
+ }
35
+ }
@@ -0,0 +1,82 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import extract from 'extract-zip';
7
+ import { createClient } from '../api.js';
8
+
9
+ /**
10
+ * 下载并解压 Skill 到指定目录
11
+ * @param {string} skillId - Skill ID
12
+ * @param {string} version - 版本号(或 'latest')
13
+ * @param {string} targetDir - 目标目录
14
+ * @returns {Promise<{skillId: string, version: string, targetDir: string}>}
15
+ */
16
+ export async function downloadAndExtract(skillId, version, targetDir) {
17
+ const client = createClient();
18
+
19
+ // 先获取 Skill 信息确认存在
20
+ const skillInfo = await client.get(`/skills/${encodeURIComponent(skillId)}`);
21
+
22
+ // 如果是 latest,使用实际版本号
23
+ const actualVersion = version === 'latest' ? skillInfo.latest_version : version;
24
+
25
+ if (!actualVersion) {
26
+ throw new Error(`Skill ${skillId} 没有可用版本`);
27
+ }
28
+
29
+ // 下载 zip
30
+ const response = await client.download(
31
+ `/skills/${encodeURIComponent(skillId)}/versions/${encodeURIComponent(actualVersion)}/download`
32
+ );
33
+
34
+ // 获取 ArrayBuffer 并写入临时文件
35
+ const arrayBuffer = await response.arrayBuffer();
36
+ const buffer = Buffer.from(arrayBuffer);
37
+
38
+ const tmpZip = path.join(os.tmpdir(), `skb-${skillId}-${actualVersion}-${Date.now()}.zip`);
39
+ fs.writeFileSync(tmpZip, buffer);
40
+
41
+ // 确保目标目录存在
42
+ fs.mkdirSync(targetDir, { recursive: true });
43
+
44
+ // 解压
45
+ await extract(tmpZip, { dir: path.resolve(targetDir) });
46
+
47
+ // 清理临时文件
48
+ try {
49
+ fs.unlinkSync(tmpZip);
50
+ } catch (e) {
51
+ // 忽略清理失败
52
+ }
53
+
54
+ return { skillId, version: actualVersion, targetDir };
55
+ }
56
+
57
+ export default async function install(target, options) {
58
+ // 解析 target: skillId@version 或 skillId
59
+ let skillId, version;
60
+ if (target.includes('@')) {
61
+ const parts = target.split('@');
62
+ skillId = parts[0];
63
+ version = parts[1];
64
+ } else {
65
+ skillId = target;
66
+ version = 'latest';
67
+ }
68
+
69
+ // 目标目录:选项指定或当前目录
70
+ // 由于 zip 包内已包含 skillId 文件夹,我们解压到当前目录即可
71
+ const targetDir = options?.dir || process.cwd();
72
+
73
+ const spinner = ora(`正在下载 ${skillId}${version !== 'latest' ? '@' + version : ''}...`).start();
74
+
75
+ try {
76
+ const result = await downloadAndExtract(skillId, version, targetDir);
77
+ spinner.succeed(chalk.green(`已安装 ${result.skillId} ${result.version} 到 ${path.join(result.targetDir, skillId)}`));
78
+ } catch (err) {
79
+ spinner.fail(chalk.red(`安装失败:${err.message}`));
80
+ process.exit(1);
81
+ }
82
+ }
@@ -0,0 +1,54 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import prompts from 'prompts';
4
+ import { getConfig } from '../config.js';
5
+ import { saveCredentials } from '../auth.js';
6
+ import { createClient } from '../api.js';
7
+
8
+ export default async function login() {
9
+ const { baseUrl } = getConfig();
10
+ const client = createClient();
11
+
12
+ console.log(chalk.cyan('\n📋 登录指引:'));
13
+ console.log(` 1. 在浏览器中打开 ${chalk.underline(baseUrl + '/login?from=cli')}`);
14
+ console.log(' 2. 登录后获取 CLI 验证码');
15
+ console.log(' 3. 在下方输入验证码\n');
16
+
17
+ const response = await prompts({
18
+ type: 'text',
19
+ name: 'code',
20
+ message: '请输入 8 位验证码(如 8A2B-9C4F):',
21
+ validate: value => {
22
+ const pattern = /^[A-Z0-9]{4}-[A-Z0-9]{4}$/i;
23
+ return pattern.test(value) ? true : '验证码格式错误,请输入 8 位验证码(如 8A2B-9C4F)';
24
+ }
25
+ });
26
+
27
+ // 用户取消输入
28
+ if (!response.code) {
29
+ console.log(chalk.yellow('\n已取消登录'));
30
+ process.exit(0);
31
+ }
32
+
33
+ const spinner = ora('正在验证...').start();
34
+
35
+ try {
36
+ const result = await client.post('/auth/cli-code/verify', {
37
+ code: response.code.toUpperCase()
38
+ });
39
+
40
+ if (result.ok && result.token && result.user) {
41
+ saveCredentials({
42
+ token: result.token,
43
+ username: result.user.username
44
+ });
45
+ spinner.succeed(chalk.green(`登录成功,当前账号:${result.user.username}`));
46
+ } else {
47
+ spinner.fail(chalk.red('验证失败,请检查验证码是否正确'));
48
+ process.exit(1);
49
+ }
50
+ } catch (err) {
51
+ spinner.fail(chalk.red(`登录失败:${err.message}`));
52
+ process.exit(1);
53
+ }
54
+ }
@@ -0,0 +1,7 @@
1
+ import chalk from 'chalk';
2
+ import { removeCredentials } from '../auth.js';
3
+
4
+ export default async function logout() {
5
+ removeCredentials();
6
+ console.log(chalk.green('✅ 已登出,本地凭证已清除'));
7
+ }
@@ -0,0 +1,151 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { randomBytes } from 'node:crypto';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import archiver from 'archiver';
8
+ import { loadCredentials } from '../auth.js';
9
+ import { createClient } from '../api.js';
10
+
11
+ /**
12
+ * 从 SKILL.md 提取 name 和 description
13
+ * - name: 取第一个 # 标题
14
+ * - description: 取标题后的第一段非空文本(前 200 字符)
15
+ */
16
+ function parseSkillMd(content) {
17
+ const lines = content.split('\n');
18
+ let name = null;
19
+ let description = null;
20
+ let foundTitle = false;
21
+
22
+ for (const line of lines) {
23
+ const trimmed = line.trim();
24
+
25
+ // 匹配 # 标题
26
+ if (!foundTitle && trimmed.startsWith('# ')) {
27
+ name = trimmed.slice(2).trim();
28
+ foundTitle = true;
29
+ continue;
30
+ }
31
+
32
+ // 找标题后的第一段非空文本
33
+ if (foundTitle && trimmed && !trimmed.startsWith('#')) {
34
+ description = trimmed.slice(0, 200);
35
+ break;
36
+ }
37
+ }
38
+
39
+ return { name, description };
40
+ }
41
+
42
+ /**
43
+ * 将文件夹打包为 zip
44
+ * @param {string} dirPath - 要打包的目录路径
45
+ * @param {string} outputPath - 输出的 zip 文件路径
46
+ * @param {string} dirName - zip 包内的目录名称(顶层文件夹)
47
+ */
48
+ function zipDirectory(dirPath, outputPath, dirName) {
49
+ return new Promise((resolve, reject) => {
50
+ const output = fs.createWriteStream(outputPath);
51
+ const archive = archiver('zip', { zlib: { level: 9 } });
52
+
53
+ output.on('close', () => resolve(archive.pointer()));
54
+ archive.on('error', reject);
55
+
56
+ archive.pipe(output);
57
+ // 使用 dirName 作为 zip 包内的顶层文件夹名称
58
+ archive.directory(dirPath, dirName);
59
+ archive.finalize();
60
+ });
61
+ }
62
+
63
+ export default async function publish(directory, options) {
64
+ // 1. 验证凭证
65
+ const credentials = loadCredentials();
66
+ if (!credentials?.token) {
67
+ console.log(chalk.red('❌ 请先登录:skb login'));
68
+ process.exit(1);
69
+ }
70
+
71
+ // 2. 验证目录存在
72
+ const resolvedDir = path.resolve(directory);
73
+ if (!fs.existsSync(resolvedDir)) {
74
+ console.log(chalk.red(`❌ 目录不存在:${resolvedDir}`));
75
+ process.exit(1);
76
+ }
77
+
78
+ const stat = fs.statSync(resolvedDir);
79
+ if (!stat.isDirectory()) {
80
+ console.log(chalk.red(`❌ 路径不是目录:${resolvedDir}`));
81
+ process.exit(1);
82
+ }
83
+
84
+ // 3. 验证 SKILL.md 存在
85
+ const skillMdPath = path.join(resolvedDir, 'SKILL.md');
86
+ if (!fs.existsSync(skillMdPath)) {
87
+ console.log(chalk.red(`❌ 目录内缺少 SKILL.md 文件:${skillMdPath}`));
88
+ process.exit(1);
89
+ }
90
+
91
+ // 4. 从文件夹名提取 skill_id
92
+ const skillId = path.basename(resolvedDir);
93
+
94
+ // 5. 从 SKILL.md 提取 name 和 description
95
+ const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8');
96
+ const parsed = parseSkillMd(skillMdContent);
97
+
98
+ const name = options.name || parsed.name || skillId;
99
+ const description = options.description || parsed.description || '';
100
+ const changelog = options.changelog;
101
+
102
+ console.log(chalk.cyan(`📦 准备发布 Skill: ${skillId}`));
103
+ console.log(chalk.gray(` 名称: ${name}`));
104
+ console.log(chalk.gray(` 描述: ${description || '(无)'}`));
105
+ console.log(chalk.gray(` 更新说明: ${changelog}`));
106
+
107
+ // 6. 打包为 zip
108
+ const spinner = ora('正在打包...').start();
109
+ const tmpZipPath = path.join(os.tmpdir(), `skb-${randomBytes(8).toString('hex')}.zip`);
110
+
111
+ try {
112
+ const size = await zipDirectory(resolvedDir, tmpZipPath, skillId);
113
+ spinner.text = `打包完成 (${(size / 1024).toFixed(1)} KB),正在上传...`;
114
+
115
+ // 7. 上传
116
+ const client = createClient();
117
+
118
+ // 读取 zip 文件内容
119
+ const zipBuffer = fs.readFileSync(tmpZipPath);
120
+ const zipBlob = new Blob([zipBuffer], { type: 'application/zip' });
121
+
122
+ // 使用原生 FormData
123
+ const formData = new FormData();
124
+ formData.append('zip_file', zipBlob, 'skill.zip');
125
+ formData.append('skill_id', skillId);
126
+ formData.append('name', name);
127
+ formData.append('description', description);
128
+ formData.append('changelog', changelog || '');
129
+
130
+ const result = await client.postForm('/skills/publish', formData);
131
+
132
+ if (result.ok) {
133
+ spinner.succeed(chalk.green(`发布成功!Skill: ${result.skill_id}, 版本: ${result.version}`));
134
+ } else {
135
+ spinner.fail(chalk.red('发布失败'));
136
+ process.exit(1);
137
+ }
138
+ } catch (err) {
139
+ spinner.fail(chalk.red(`发布失败:${err.message}`));
140
+ process.exit(1);
141
+ } finally {
142
+ // 8. 清理临时文件
143
+ try {
144
+ if (fs.existsSync(tmpZipPath)) {
145
+ fs.unlinkSync(tmpZipPath);
146
+ }
147
+ } catch {
148
+ // 忽略清理错误
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,63 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { createClient } from '../api.js';
4
+
5
+ export default async function search(keyword) {
6
+ const client = createClient();
7
+ const spinner = ora('正在搜索...').start();
8
+
9
+ try {
10
+ const result = await client.get(`/skills?q=${encodeURIComponent(keyword)}`);
11
+ spinner.stop();
12
+
13
+ if (!result.skills || result.skills.length === 0) {
14
+ console.log(chalk.yellow('未找到匹配的 Skill'));
15
+ return;
16
+ }
17
+
18
+ // 表格展示
19
+ const skills = result.skills;
20
+
21
+ // 计算列宽
22
+ const idWidth = Math.max(4, ...skills.map(s => s.id.length));
23
+ const nameWidth = Math.max(6, ...skills.map(s => s.name.length));
24
+ const versionWidth = Math.max(8, ...skills.map(s => (s.latest_version || '-').length));
25
+ const descWidth = 40;
26
+
27
+ // 表头
28
+ const header = [
29
+ 'ID'.padEnd(idWidth),
30
+ '名称'.padEnd(nameWidth),
31
+ '最新版本'.padEnd(versionWidth),
32
+ '描述'
33
+ ].join(' ');
34
+
35
+ const separator = '-'.repeat(idWidth + nameWidth + versionWidth + descWidth + 6);
36
+
37
+ console.log(chalk.cyan(`\n找到 ${result.total || skills.length} 个 Skill:\n`));
38
+ console.log(chalk.bold(header));
39
+ console.log(separator);
40
+
41
+ for (const skill of skills) {
42
+ // 截断描述到 40 字符
43
+ let desc = skill.description || '';
44
+ if (desc.length > descWidth) {
45
+ desc = desc.slice(0, descWidth - 3) + '...';
46
+ }
47
+
48
+ const row = [
49
+ skill.id.padEnd(idWidth),
50
+ skill.name.padEnd(nameWidth),
51
+ (skill.latest_version || '-').padEnd(versionWidth),
52
+ desc
53
+ ].join(' ');
54
+
55
+ console.log(row);
56
+ }
57
+
58
+ console.log('');
59
+ } catch (err) {
60
+ spinner.fail(chalk.red(`搜索失败:${err.message}`));
61
+ process.exit(1);
62
+ }
63
+ }
@@ -0,0 +1,31 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { createClient } from '../api.js';
4
+ import { downloadAndExtract } from './install.js';
5
+
6
+ export default async function update(skillId, options) {
7
+ const client = createClient();
8
+ const targetDir = options?.dir || process.cwd();
9
+
10
+ const spinner = ora(`正在检查 ${skillId} 的最新版本...`).start();
11
+
12
+ try {
13
+ // 获取 Skill 信息
14
+ const skillInfo = await client.get(`/skills/${encodeURIComponent(skillId)}`);
15
+ const latestVersion = skillInfo.latest_version;
16
+
17
+ if (!latestVersion) {
18
+ spinner.fail(chalk.red(`Skill ${skillId} 没有可用版本`));
19
+ process.exit(1);
20
+ }
21
+
22
+ spinner.text = `正在下载 ${skillId}@${latestVersion}...`;
23
+
24
+ // 直接下载最新版本(由于没有本地版本跟踪,每次都执行更新)
25
+ const result = await downloadAndExtract(skillId, latestVersion, targetDir);
26
+ spinner.succeed(chalk.green(`已更新 ${result.skillId} 到 ${result.version}`));
27
+ } catch (err) {
28
+ spinner.fail(chalk.red(`更新失败:${err.message}`));
29
+ process.exit(1);
30
+ }
31
+ }
package/lib/config.js ADDED
@@ -0,0 +1,17 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ export function getConfig() {
5
+ let baseUrl = process.env.SKB_BASE_URL || 'http://localhost:8000';
6
+ // 去除末尾斜杠
7
+ baseUrl = baseUrl.replace(/\/+$/, '');
8
+
9
+ const credentialsDir = path.join(os.homedir(), '.skill-base');
10
+
11
+ return {
12
+ baseUrl,
13
+ apiUrl: `${baseUrl}/api/v1`,
14
+ credentialsDir,
15
+ credentialsPath: path.join(credentialsDir, 'credentials.json')
16
+ };
17
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "skill-base-cli",
3
+ "version": "1.0.0",
4
+ "description": "Skill Base CLI - 命令行工具,用于搜索、安装、更新和发布 AI Agent Skills",
5
+ "type": "module",
6
+ "bin": {
7
+ "skb": "./bin/skb.js"
8
+ },
9
+ "keywords": [
10
+ "skill",
11
+ "agent",
12
+ "ai",
13
+ "cli",
14
+ "skill-base"
15
+ ],
16
+ "author": "",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": ""
21
+ },
22
+ "homepage": "",
23
+ "files": [
24
+ "bin",
25
+ "lib"
26
+ ],
27
+ "dependencies": {
28
+ "archiver": "^7.0.0",
29
+ "commander": "^12.1.0",
30
+ "chalk": "^5.3.0",
31
+ "ora": "^8.0.1",
32
+ "prompts": "^2.4.2",
33
+ "form-data": "^4.0.0",
34
+ "extract-zip": "^2.0.1"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ }
39
+ }