sophhub 0.1.0 → 0.1.2

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,122 @@
1
+ # SophHub CLI
2
+
3
+ SophHub CLI 是一个命令行工具,用于浏览和下载 AI Agent Skill。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g sophhub
9
+ ```
10
+
11
+ 或者用 `npx` 免安装直接运行:
12
+
13
+ ```bash
14
+ npx sophhub list
15
+ ```
16
+
17
+ 要求 Node.js >= 18。
18
+
19
+ ## 使用
20
+
21
+ ### 查看所有 Skill
22
+
23
+ ```bash
24
+ sophhub list
25
+ ```
26
+
27
+ 按类型筛选:
28
+
29
+ ```bash
30
+ sophhub list --type builtin
31
+ sophhub list --type store
32
+ ```
33
+
34
+ 以 JSON 格式输出(方便脚本和管道消费):
35
+
36
+ ```bash
37
+ sophhub list --json
38
+ sophhub list --type store --json
39
+ ```
40
+
41
+ ### 下载 Skill
42
+
43
+ 下载到当前目录:
44
+
45
+ ```bash
46
+ sophhub download weather
47
+ ```
48
+
49
+ 指定输出目录:
50
+
51
+ ```bash
52
+ sophhub download didi-ride -o ./my-skills
53
+ ```
54
+
55
+ ### 查看版本
56
+
57
+ ```bash
58
+ sophhub --version
59
+ ```
60
+
61
+ ### 查看帮助
62
+
63
+ ```bash
64
+ sophhub --help
65
+ sophhub list --help
66
+ sophhub download --help
67
+ ```
68
+
69
+ ## 配置
70
+
71
+ 可通过 `~/.sophhubrc.json` 配置默认行为:
72
+
73
+ ```json
74
+ {
75
+ "defaultOutput": "."
76
+ }
77
+ ```
78
+
79
+ | 字段 | 说明 | 默认值 |
80
+ | --- | --- | --- |
81
+ | `defaultOutput` | download 默认输出目录 | `.`(当前目录) |
82
+
83
+ 命令行参数 `-o` 优先级高于配置文件。
84
+
85
+ ## 开发
86
+
87
+ ```bash
88
+ # 克隆仓库
89
+ git clone git@gitlab.sophgo.vip:llm-open-platform/sophclaw-skills.git
90
+ cd sophclaw-skills/cli
91
+
92
+ # 安装依赖
93
+ npm install
94
+
95
+ # 准备内置 skill(从仓库根目录拷贝到 cli/skills/)
96
+ bash scripts/prepublish.sh
97
+
98
+ # 全局注册命令(符号链接,修改代码即时生效)
99
+ npm link
100
+
101
+ # 调试
102
+ sophhub list
103
+ sophhub download weather -o /tmp/test
104
+
105
+ # 调试完毕后卸载
106
+ npm unlink -g sophhub
107
+ ```
108
+
109
+ ## 发版
110
+
111
+ ```bash
112
+ cd cli
113
+
114
+ # 升版本(patch / minor / major)
115
+ npm version patch
116
+
117
+ # 发布(自动触发 prepublish 打包 skills)
118
+ npm publish
119
+
120
+ # 推送 tag
121
+ git push && git push --tags
122
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sophhub",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "SophHub CLI - Manage and download AI Agent skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,27 +1,27 @@
1
1
  # Skill Versions
2
2
 
3
- | Skill | Type | Version | Updated At |
4
- | --- | --- | --- | --- |
5
- | clawhub | builtin | 1.0.0 | 2026-04-08 |
6
- | flight-booking | builtin | 1.1.0 | 2026-04-08 |
7
- | inventory-management | builtin | 1.0.0 | 2026-04-08 |
8
- | schedule-reminder | builtin | 1.0.0 | 2026-04-08 |
9
- | skill-creator | builtin | 1.0.0 | 2026-04-08 |
10
- | sophnet-customer-management | builtin | 1.0.0 | 2026-04-08 |
11
- | sophnet-customized-marketing | builtin | 1.0.0 | 2026-04-08 |
12
- | sophnet-face-search | builtin | 1.0.0 | 2026-04-08 |
13
- | sophnet-image-edit | builtin | 1.0.0 | 2026-04-08 |
14
- | sophnet-image-generate | builtin | 1.0.0 | 2026-04-08 |
15
- | sophnet-image-ocr | builtin | 1.0.0 | 2026-04-08 |
16
- | sophnet-infinite-talk | builtin | 1.0.0 | 2026-04-08 |
17
- | sophnet-oss | builtin | 1.0.0 | 2026-04-08 |
18
- | sophnet-qa-install | builtin | 2.0.0 | 2026-04-08 |
19
- | sophnet-training-install | builtin | 2.0.0 | 2026-04-08 |
20
- | sophnet-tts | builtin | 1.0.0 | 2026-04-08 |
21
- | sophnet-video-generate | builtin | 1.0.0 | 2026-04-08 |
22
- | video-understand | builtin | 1.0.0 | 2026-04-08 |
23
- | weather | builtin | 1.0.0 | 2026-04-08 |
24
- | web-scraper | builtin | 1.0.0 | 2026-04-08 |
25
- | website-builder | builtin | 1.0.0 | 2026-04-08 |
26
- | didi-ride | store | 1.0.0 | 2026-04-09 |
27
- | flyai | store | 1.0.10 | 2026-04-09 |
3
+ | Skill | Type | Version | Updated At | Display Name | Description |
4
+ | --- | --- | --- | --- | --- | --- |
5
+ | clawhub | builtin | 1.0.0 | 2026-04-08 | | |
6
+ | flight-booking | builtin | 1.1.0 | 2026-04-08 | | |
7
+ | inventory-management | builtin | 1.0.0 | 2026-04-08 | | |
8
+ | schedule-reminder | builtin | 1.0.0 | 2026-04-08 | | |
9
+ | skill-creator | builtin | 1.0.0 | 2026-04-08 | | |
10
+ | sophnet-customer-management | builtin | 1.0.0 | 2026-04-08 | | |
11
+ | sophnet-customized-marketing | builtin | 1.0.0 | 2026-04-08 | | |
12
+ | sophnet-face-search | builtin | 1.0.0 | 2026-04-08 | | |
13
+ | sophnet-image-edit | builtin | 1.0.0 | 2026-04-08 | | |
14
+ | sophnet-image-generate | builtin | 1.0.0 | 2026-04-08 | | |
15
+ | sophnet-image-ocr | builtin | 1.0.0 | 2026-04-08 | | |
16
+ | sophnet-infinite-talk | builtin | 1.0.0 | 2026-04-08 | | |
17
+ | sophnet-oss | builtin | 1.0.0 | 2026-04-08 | | |
18
+ | sophnet-qa-install | builtin | 2.0.0 | 2026-04-08 | | |
19
+ | sophnet-training-install | builtin | 2.0.0 | 2026-04-08 | | |
20
+ | sophnet-tts | builtin | 1.0.0 | 2026-04-08 | | |
21
+ | sophnet-video-generate | builtin | 1.0.0 | 2026-04-08 | | |
22
+ | video-understand | builtin | 1.0.0 | 2026-04-08 | | |
23
+ | weather | builtin | 1.0.0 | 2026-04-08 | | |
24
+ | web-scraper | builtin | 1.0.0 | 2026-04-08 | | |
25
+ | website-builder | builtin | 1.0.0 | 2026-04-08 | | |
26
+ | didi-ride | store | 1.0.0 | 2026-04-09 | 滴滴出行 | 打车、路线规划、周边搜索、订单查询 |
27
+ | flyai | store | 1.0.10 | 2026-04-09 | 飞猪旅行 | 机票、酒店、景点门票、旅行搜索与预订 |
@@ -5,7 +5,6 @@ import readline from 'node:readline/promises';
5
5
  import { findSkill } from '../utils/versions.js';
6
6
  import { getSkillDir } from '../utils/paths.js';
7
7
  import { loadConfig } from '../utils/config.js';
8
- import { downloadFromGitlab } from '../utils/gitlab.js';
9
8
 
10
9
  async function confirmOverwrite(destDir) {
11
10
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -19,41 +18,17 @@ async function confirmOverwrite(destDir) {
19
18
  }
20
19
  }
21
20
 
22
- async function downloadFromBuiltin(type, skillName, outputDir) {
23
- const srcDir = getSkillDir(type, skillName);
24
-
25
- try {
26
- await fs.access(srcDir);
27
- } catch {
28
- throw new Error(
29
- `Skill directory not found in package: ${srcDir}\n` +
30
- 'The bundled skills may be outdated. Try --source gitlab for the latest version.',
31
- );
32
- }
33
-
34
- const destDir = path.join(outputDir, skillName);
35
- await fs.cp(srcDir, destDir, { recursive: true });
36
- return destDir;
37
- }
38
-
39
21
  export function registerDownloadCommand(program) {
40
22
  program
41
23
  .command('download')
42
24
  .description('Download a skill to local directory')
43
25
  .argument('<skill-name>', 'Name of the skill to download')
44
26
  .option('-o, --output <dir>', 'Target directory (default: current directory)')
45
- .option('-s, --source <source>', 'Data source: builtin (default) or gitlab')
46
27
  .action(async (skillName, opts) => {
47
28
  try {
48
29
  const config = await loadConfig();
49
- const source = opts.source || config.defaultSource || 'builtin';
50
30
  const outputDir = path.resolve(opts.output || config.defaultOutput || '.');
51
31
 
52
- if (source !== 'builtin' && source !== 'gitlab') {
53
- console.error(chalk.red(`Invalid source "${source}". Use "builtin" or "gitlab".`));
54
- process.exit(1);
55
- }
56
-
57
32
  const skill = await findSkill(skillName);
58
33
  if (!skill) {
59
34
  console.error(chalk.red(`Skill "${skillName}" not found.`));
@@ -78,22 +53,22 @@ export function registerDownloadCommand(program) {
78
53
  await fs.mkdir(outputDir, { recursive: true });
79
54
 
80
55
  console.log();
81
- console.log(chalk.gray(` Downloading ${skillName} from ${source}...`));
56
+ console.log(chalk.gray(` Downloading ${skillName}...`));
82
57
 
83
- let result;
84
- if (source === 'gitlab') {
85
- const repoUrl = config.gitlabRepo;
86
- result = await downloadFromGitlab(repoUrl, skill.type, skillName, outputDir);
87
- } else {
88
- result = await downloadFromBuiltin(skill.type, skillName, outputDir);
58
+ const srcDir = getSkillDir(skill.type, skillName);
59
+ try {
60
+ await fs.access(srcDir);
61
+ } catch {
62
+ throw new Error(`Skill directory not found in package: ${srcDir}`);
89
63
  }
90
64
 
65
+ await fs.cp(srcDir, destDir, { recursive: true });
66
+
91
67
  console.log();
92
68
  console.log(chalk.green(' ✓ Download complete'));
93
69
  console.log(chalk.gray(` Skill: ${skill.name}`));
94
70
  console.log(chalk.gray(` Version: ${skill.version}`));
95
- console.log(chalk.gray(` Source: ${source}`));
96
- console.log(chalk.gray(` Path: ${result}`));
71
+ console.log(chalk.gray(` Path: ${destDir}`));
97
72
  console.log();
98
73
  } catch (err) {
99
74
  console.error(chalk.red(`\n Failed to download: ${err.message}\n`));
@@ -35,23 +35,34 @@ export function registerListCommand(program) {
35
35
  return;
36
36
  }
37
37
 
38
+ const hasDisplayInfo = skills.some((s) => s.displayName || s.description);
39
+
40
+ const head = [
41
+ chalk.cyan('Skill'),
42
+ chalk.cyan('Type'),
43
+ chalk.cyan('Version'),
44
+ chalk.cyan('Updated At'),
45
+ ];
46
+ if (hasDisplayInfo) {
47
+ head.push(chalk.cyan('Display Name'), chalk.cyan('Description'));
48
+ }
49
+
38
50
  const table = new Table({
39
- head: [
40
- chalk.cyan('Skill'),
41
- chalk.cyan('Type'),
42
- chalk.cyan('Version'),
43
- chalk.cyan('Updated At'),
44
- ],
51
+ head,
45
52
  style: { head: [], border: [] },
46
53
  });
47
54
 
48
55
  for (const s of skills) {
49
- table.push([
56
+ const row = [
50
57
  s.name,
51
58
  s.type === 'builtin' ? chalk.blue(s.type) : chalk.green(s.type),
52
59
  s.version,
53
60
  s.updatedAt,
54
- ]);
61
+ ];
62
+ if (hasDisplayInfo) {
63
+ row.push(s.displayName, s.description);
64
+ }
65
+ table.push(row);
55
66
  }
56
67
 
57
68
  console.log();
@@ -6,8 +6,6 @@ const CONFIG_PATH = path.join(os.homedir(), '.sophhubrc.json');
6
6
 
7
7
  const DEFAULTS = {
8
8
  defaultOutput: '.',
9
- defaultSource: 'builtin',
10
- gitlabRepo: 'git@gitlab.sophgo.vip:llm-open-platform/sophclaw-skills.git',
11
9
  };
12
10
 
13
11
  export async function loadConfig() {
@@ -1,18 +1,8 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import { getVersionsPath } from './paths.js';
3
3
 
4
- /**
5
- * Parse VERSIONS.md markdown table into an array of skill objects.
6
- * Expected format:
7
- * | Skill | Type | Version | Updated At |
8
- * | --- | --- | --- | --- |
9
- * | weather | builtin | 1.0.0 | 2026-04-08 |
10
- */
11
- export async function parseVersions() {
12
- const content = await fs.readFile(getVersionsPath(), 'utf-8');
4
+ function parseMarkdownTable(content) {
13
5
  const lines = content.split('\n').filter((l) => l.trim().startsWith('|'));
14
-
15
- // skip header row and separator row (first two | lines)
16
6
  const dataLines = lines.slice(2);
17
7
 
18
8
  return dataLines
@@ -27,11 +17,18 @@ export async function parseVersions() {
27
17
  type: cells[1],
28
18
  version: cells[2],
29
19
  updatedAt: cells[3],
20
+ displayName: cells[4] || '',
21
+ description: cells[5] || '',
30
22
  };
31
23
  })
32
24
  .filter(Boolean);
33
25
  }
34
26
 
27
+ export async function parseVersions() {
28
+ const content = await fs.readFile(getVersionsPath(), 'utf-8');
29
+ return parseMarkdownTable(content);
30
+ }
31
+
35
32
  export async function findSkill(skillName) {
36
33
  const skills = await parseVersions();
37
34
  return skills.find((s) => s.name === skillName) || null;
@@ -1,67 +0,0 @@
1
- import { execFile } from 'node:child_process';
2
- import { promisify } from 'node:util';
3
- import fs from 'node:fs/promises';
4
- import path from 'node:path';
5
- import os from 'node:os';
6
-
7
- const execFileAsync = promisify(execFile);
8
-
9
- /**
10
- * Download a skill directory from GitLab using `git archive --remote`.
11
- * Falls back to sparse-checkout if archive is not supported by the server.
12
- */
13
- export async function downloadFromGitlab(repoUrl, type, skillName, outputDir) {
14
- const destDir = path.join(outputDir, skillName);
15
- const subPath = `${type}/${skillName}`;
16
-
17
- // Try git archive first (most efficient, single-command)
18
- try {
19
- await fs.mkdir(destDir, { recursive: true });
20
- const { stdout } = await execFileAsync(
21
- 'git',
22
- ['archive', '--remote', repoUrl, 'HEAD', subPath],
23
- { maxBuffer: 50 * 1024 * 1024 },
24
- );
25
-
26
- // pipe tar output through extraction
27
- const tar = execFileAsync('tar', ['-x', '-C', outputDir], {
28
- maxBuffer: 50 * 1024 * 1024,
29
- });
30
- tar.child.stdin.write(stdout);
31
- tar.child.stdin.end();
32
- await tar;
33
-
34
- // git archive extracts to {type}/{skillName}/, move to {outputDir}/{skillName}/
35
- const extracted = path.join(outputDir, type, skillName);
36
- if (extracted !== destDir) {
37
- await fs.cp(extracted, destDir, { recursive: true });
38
- await fs.rm(path.join(outputDir, type), { recursive: true, force: true });
39
- }
40
- return destDir;
41
- } catch {
42
- // archive --remote not supported, fall back to sparse checkout
43
- }
44
-
45
- return downloadViaSparseCheckout(repoUrl, subPath, destDir);
46
- }
47
-
48
- async function downloadViaSparseCheckout(repoUrl, subPath, destDir) {
49
- const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sophhub-'));
50
-
51
- try {
52
- await execFileAsync(
53
- 'git',
54
- ['clone', '--filter=blob:none', '--sparse', '--depth=1', repoUrl, tmpDir],
55
- { timeout: 120_000 },
56
- );
57
-
58
- await execFileAsync('git', ['sparse-checkout', 'set', subPath], { cwd: tmpDir });
59
-
60
- const src = path.join(tmpDir, subPath);
61
- await fs.cp(src, destDir, { recursive: true });
62
-
63
- return destDir;
64
- } finally {
65
- await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
66
- }
67
- }