skill-market-cli 1.0.1 → 1.1.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/src/index.js CHANGED
@@ -9,36 +9,71 @@ const upload = require('./commands/upload');
9
9
  const update = require('./commands/update');
10
10
  const remove = require('./commands/delete');
11
11
  const runExample = require('./commands/run-example');
12
+ const token = require('./commands/token');
12
13
  const { getConfig } = require('./auth/token-store');
14
+ const { applyServerMode, getServerModesHelp } = require('./config/server-modes');
15
+ const apiClient = require('./api/client');
13
16
 
14
17
  const program = new Command();
15
18
 
16
19
  program
17
20
  .name('skill-market-cli')
18
- .description('CLI tool for managing skills on Skill Market')
21
+ .description(`Skill Market 命令行:管理技能与登录
22
+
23
+ 全局网络(对所有子命令生效):
24
+ --mode <环境> ${getServerModesHelp()}
25
+ 未指定时优先使用 ~/.skill-market-cli/config.json 中保存的环境(登录后写入),否则为 production。
26
+
27
+ 示例:
28
+ skill-market-cli --mode development login
29
+ skill-market-cli login --mode development
30
+ node bin/skill-market-cli.js list --mode development`)
19
31
  .version(pkg.version, '-v, --version')
20
32
  .option('-c, --config <path>', 'config file path')
21
- .hook('preAction', (thisCommand) => {
22
- // 显示欢迎信息
23
- const config = getConfig();
24
- if (config.user && thisCommand.args[0] !== 'login') {
25
- console.log(chalk.gray(`Logged in as: ${config.user.name}`));
26
- }
27
- });
33
+ .option('--mode <environment>', getServerModesHelp());
34
+
35
+ program.hook('preAction', () => {
36
+ const opts = program.opts();
37
+ const config = getConfig();
38
+ const effectiveMode =
39
+ opts.mode !== undefined && opts.mode !== null && opts.mode !== ''
40
+ ? opts.mode
41
+ : (config.mode || 'production');
42
+ try {
43
+ applyServerMode(effectiveMode);
44
+ apiClient.reinit();
45
+ } catch (e) {
46
+ console.error(chalk.red(e.message));
47
+ process.exit(1);
48
+ }
49
+ });
50
+
51
+ program.hook('preAction', (thisCommand) => {
52
+ const config = getConfig();
53
+ if (config.user && thisCommand.args[0] !== 'login') {
54
+ console.log(chalk.gray(`已登录:${config.user.name}`));
55
+ }
56
+ });
28
57
 
29
58
  // Login command
30
59
  program
31
60
  .command('login')
32
- .description('Login to Skill Market')
33
- .option('--no-open', 'Do not open browser automatically')
61
+ .description('浏览器 OAuth 登录(使用全局 --mode 指向的环境)')
62
+ .option('--no-open', '不自动打开浏览器,仅打印授权链接')
34
63
  .action(login);
35
64
 
36
65
  // Logout command
37
66
  program
38
67
  .command('logout')
39
- .description('Logout from Skill Market')
68
+ .description('清除本地登录状态并尝试撤销服务端令牌')
40
69
  .action(logout);
41
70
 
71
+ // Token command
72
+ program
73
+ .command('token [subcommand] [value]')
74
+ .description('管理 Personal Access Token(用于 CLI 免登录)')
75
+ .action(token);
76
+
42
77
  // List command
43
78
  program
44
79
  .command('list')
@@ -54,11 +89,12 @@ program
54
89
  program
55
90
  .command('upload <path>')
56
91
  .alias('up')
57
- .description('Upload a new skill')
92
+ .description('上传 Skill(交互补全字段;用户案例 + 自动采集轨迹后提交 AI 渠道)')
58
93
  .option('-n, --name <name>', 'Skill name')
59
94
  .option('-d, --description <desc>', 'Skill description/purpose')
60
95
  .option('-t, --tags <tags>', 'Tags (comma separated)')
61
96
  .option('-m, --model <model>', 'Recommended model')
97
+ .option('-y, --yes', '非交互:跳过最终确认,适合脚本/CI')
62
98
  .action(upload);
63
99
 
64
100
  // Update command
@@ -100,7 +136,7 @@ program
100
136
  if (fs.existsSync(guidePath)) {
101
137
  console.log(fs.readFileSync(guidePath, 'utf-8'));
102
138
  } else {
103
- console.log(chalk.yellow('Guide not found. Please visit https://kirigaya.cn/ktools/skillmanager'));
139
+ console.log(chalk.yellow('未找到本地指南,请访问:https://kirigaya.cn/ktools/skillmanager'));
104
140
  }
105
141
  });
106
142
 
@@ -0,0 +1,44 @@
1
+ const chalk = require('chalk');
2
+
3
+ /**
4
+ * 对用户 prompt 执行一次「示例运行」,采集 AI 侧轨迹(thinking / toolcall / message)。
5
+ * 生产环境可替换为真实模型 API;当前为可预测的模拟数据,便于联调。
6
+ */
7
+ async function runExampleAndCollect(prompt, model) {
8
+ const modelLabel = model && String(model).trim() ? String(model).trim() : 'default';
9
+
10
+ console.log(chalk.gray('\n正在运行用户案例以采集轨迹与输出…'));
11
+ console.log(chalk.gray(`推荐模型:${modelLabel}`));
12
+
13
+ const simulatedResponses = [
14
+ {
15
+ type: 'thinking',
16
+ content: `分析用户请求:「${prompt}」。将拆解步骤并调用合适工具完成目标。`
17
+ },
18
+ {
19
+ type: 'toolcall',
20
+ toolName: 'read_file',
21
+ toolInput: { path: './README.md' }
22
+ },
23
+ {
24
+ type: 'toolcall',
25
+ toolName: 'write_file',
26
+ toolInput: {
27
+ path: './output.txt',
28
+ content: `已处理:${prompt}`
29
+ }
30
+ },
31
+ {
32
+ type: 'message',
33
+ content: `已根据你的请求完成处理:「${prompt}」。\n\n步骤摘要:\n1. 理解需求\n2. 读取上下文\n3. 生成结果\n\n以上为演示轨迹,上传时将一并提交。`
34
+ }
35
+ ];
36
+
37
+ await new Promise((resolve) => setTimeout(resolve, 400));
38
+
39
+ console.log(chalk.gray(`已采集 ${simulatedResponses.length} 条轨迹节点(thinking / toolcall / message)。`));
40
+
41
+ return simulatedResponses;
42
+ }
43
+
44
+ module.exports = { runExampleAndCollect };
@@ -0,0 +1,77 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const YAML = require('yaml');
4
+
5
+ /**
6
+ * 解析 SKILL.md:frontmatter + ## Usage Examples 下简单分块(仅 prompt 文本)
7
+ */
8
+ function parseSkillMarkdown(content) {
9
+ let frontmatter = null;
10
+ let examples = [];
11
+
12
+ const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/);
13
+ if (frontmatterMatch) {
14
+ try {
15
+ frontmatter = YAML.parse(frontmatterMatch[1]);
16
+ } catch {
17
+ // ignore
18
+ }
19
+ }
20
+
21
+ const examplesMatch = content.match(/## Usage Examples?\s*\n([\s\S]*?)(?=##|$)/i);
22
+ if (examplesMatch) {
23
+ const exampleText = examplesMatch[1];
24
+ const exampleBlocks = exampleText.split(/\n\n+/).filter((b) => b.trim());
25
+ examples = exampleBlocks.map((block) => {
26
+ const lines = block.split('\n').filter((l) => l.trim());
27
+ return { prompt: lines.join('\n') };
28
+ });
29
+ }
30
+
31
+ return { frontmatter, examples };
32
+ }
33
+
34
+ /**
35
+ * 读取 run-example 生成的 .skill-examples.json,转为接口所需 usageExamples
36
+ */
37
+ function loadDotSkillExamples(skillDir) {
38
+ const filePath = path.join(skillDir, '.skill-examples.json');
39
+ if (!fs.existsSync(filePath)) {
40
+ return null;
41
+ }
42
+ try {
43
+ const data = fs.readJsonSync(filePath);
44
+ if (!data || !Array.isArray(data.examples)) {
45
+ return null;
46
+ }
47
+ return data.examples.map((ex) => ({
48
+ prompt: ex.prompt,
49
+ aiResponses: ex.aiResponses || [],
50
+ model: ex.model || data.model || ''
51
+ }));
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * 将仅有 prompt 的条目补全为完整 UsageExample(可选采集轨迹)
59
+ */
60
+ function promptOnlyExamples(examples) {
61
+ if (!examples || !examples.length) {
62
+ return [];
63
+ }
64
+ return examples
65
+ .map((ex) => ({
66
+ prompt: (ex.prompt || '').trim(),
67
+ aiResponses: ex.aiResponses,
68
+ model: ex.model
69
+ }))
70
+ .filter((ex) => ex.prompt);
71
+ }
72
+
73
+ module.exports = {
74
+ parseSkillMarkdown,
75
+ loadDotSkillExamples,
76
+ promptOnlyExamples
77
+ };
@@ -0,0 +1,26 @@
1
+ {
2
+ "model": "deepseek-chat",
3
+ "examples": [
4
+ {
5
+ "prompt": "请根据我仓库里的 SKILL.md 帮我执行上传,并告诉我你要确认哪些字段。",
6
+ "aiResponses": [
7
+ {
8
+ "type": "thinking",
9
+ "content": "用户要用 skill-market-cli 上传 SKILL。需先读 frontmatter,核对 name、描述、tags、model、rootUrl;至少一条用户案例与 thinking/toolcall/message 轨迹;再执行 upload 交互或脚本。"
10
+ },
11
+ {
12
+ "type": "toolcall",
13
+ "toolName": "read_file",
14
+ "toolInput": {
15
+ "path": "./SKILL.md"
16
+ }
17
+ },
18
+ {
19
+ "type": "message",
20
+ "content": "我会先读取 `SKILL.md` 的 frontmatter,列出缺失项;请你确认一条「最终用户会如何提问」的测试案例。然后运行 `skill-market-cli upload <skill 目录>`,在交互中补全字段并自动采集轨迹后提交到 Skill Market。"
21
+ }
22
+ ],
23
+ "model": "deepseek-chat"
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: skill-market-upload
3
+ purpose: 指导 AI Agent 使用 skill-market-cli 将本地 SKILL 上传至 Skill Market,含用户案例与轨迹采集要求
4
+ tags:
5
+ - skill-market
6
+ - cli
7
+ - upload
8
+ model: deepseek-chat
9
+ rootUrl: https://raw.githubusercontent.com/LSTM-Kirigaya/skill-market-cli/refs/heads/main/src/skills/skill-market-upload/SKILL.md
10
+ ---
11
+
12
+ # Skill Market 上传助手(给 AI Agent 用)
13
+
14
+ 本说明面向 **代表用户操作终端的 AI Agent**。目标:在已登录 `skill-market-cli` 的前提下,通过 **`skill-market-cli upload`** 完成上传,并满足服务端对 **AI 渠道** 的必填规则。
15
+
16
+ ## 你必须知道的约束
17
+
18
+ 1. **登录**:用户须先执行 `skill-market-cli login`(可用 `--mode development` 指向本地)。凭证在 `~/.skill-market-cli/config.json`。
19
+ 2. **上传走 AI 渠道**:CLI 使用 `POST /api/skill/ai/upload`,要求 **全部字段非空**,且 **`tags`、`usageExamples` 不得为空数组**。
20
+ 3. **用户案例(必填)**:`usageExamples` 中每一项必须包含:
21
+ - **`prompt`**:终端用户会如何向该 Skill 提问(由用户或你根据上下文代写,但必须经用户确认)。
22
+ - **`aiResponses`**:一次「示例运行」采集到的轨迹(thinking / toolcall / message)。上传命令会在本地 **自动调用采集逻辑** 生成(当前为可替换的模拟实现,结构需与后端一致)。
23
+ - **`model`**:推荐模型名(如 `deepseek-chat`)。
24
+ 4. **无法仅从文件推断的字段**:若 SKILL.md 未写全,上传流程会 **交互询问**:名称、描述、标签、`rootUrl`、推荐模型等。Agent 应结合仓库上下文、README、用户口述 **帮用户预填**,并在询问环节确认。
25
+
26
+ ## 推荐工作流(Agent)
27
+
28
+ 1. 确认仓库中存在 **`SKILL.md`**(目录则路径指向该目录)。
29
+ 2. 读取 frontmatter,整理候选:`name`、`purpose`/`description`、`tags`、`model`、`rootUrl`。
30
+ 3. 与用户确认 **至少一条「用户会如何提问」的测试案例**(可多轮补充)。案例文本即 `prompt`。
31
+ 4. 在终端执行上传(勿省略路径):
32
+ ```bash
33
+ skill-market-cli upload <path-to-skill-dir-or-SKILL.md>
34
+ ```
35
+ 5. 按 CLI 提示补全缺失字段;当提示采集轨迹时,**允许命令自动运行**(会调用内置采集器写入 `aiResponses`)。
36
+ 6. 上传成功后,CLI 会在技能目录写入 **`.skill-examples.json`**,便于复查与再次上传。
37
+
38
+ ## 与 `run-example` 的关系
39
+
40
+ - 可先运行 `skill-market-cli run-example <path>` 预采集,生成 `.skill-examples.json`;再执行 `upload` 时会 **自动合并** 该文件。
41
+ - 若未预先运行,`upload` 会引导用户 **逐条输入案例并自动采集轨迹**,无需用户手动拼 JSON。
42
+
43
+ ## 禁止事项
44
+
45
+ - 不要编造不存在的 Git 仓库 URL;`rootUrl` 可用 `file:///...` 指向本地 `SKILL.md` 的绝对路径(CLI 默认值),或用户提供的 raw URL。
46
+ - 不要跳过「用户案例」;没有案例与轨迹,AI 渠道上传会失败。
47
+
48
+ ## Usage Examples
49
+
50
+ ### Example 1
51
+
52
+ **User:** 请根据我仓库里的 SKILL.md 帮我执行上传,并告诉我你要确认哪些字段。
53
+
54
+ **AI:** 我会先读取 `SKILL.md` 的 frontmatter,列出缺失的 name、描述、标签、模型与 rootUrl;请你至少提供一条「最终用户会对该 Skill 说的话」作为测试案例。然后我在终端运行 `skill-market-cli upload .`,在交互中替你填入并确认,自动完成轨迹采集后提交。