@haolin-ai/skillman 1.0.7 → 1.0.8
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 +16 -1
- package/package.json +1 -1
- package/src/cli.js +8 -2
- package/src/i18n.js +3 -0
- package/src/scanner.js +42 -24
package/README.md
CHANGED
|
@@ -47,9 +47,22 @@ skillman install git@github.com:org/repo.git
|
|
|
47
47
|
### Commands
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
+
# Initialize a new skill template
|
|
51
|
+
skillman init [skill-name]
|
|
52
|
+
skillman init my-skill -v 2.0.0 -d "Description" -a "Author"
|
|
53
|
+
|
|
50
54
|
# List all available agents
|
|
51
55
|
skillman agents
|
|
52
56
|
|
|
57
|
+
# List installed skills
|
|
58
|
+
skillman list
|
|
59
|
+
|
|
60
|
+
# Update a skill
|
|
61
|
+
skillman update my-skill
|
|
62
|
+
|
|
63
|
+
# Uninstall a skill
|
|
64
|
+
skillman uninstall my-skill
|
|
65
|
+
|
|
53
66
|
# Preview installation without making changes
|
|
54
67
|
skillman --dry-run
|
|
55
68
|
|
|
@@ -63,6 +76,8 @@ skillman --version
|
|
|
63
76
|
## Features
|
|
64
77
|
|
|
65
78
|
- **Multi-Agent Support**: Works with Claude Code, OpenClaw, Qoder, Codex, Cursor, and more
|
|
79
|
+
- **Skill Template Generator**: Quickly create new skills with `skillman init`
|
|
80
|
+
- **Version Management**: Track and manage skill versions with `list`, `update`, and `uninstall` commands
|
|
66
81
|
- **Workspace History**: Remembers previously used workspace paths for quick selection
|
|
67
82
|
- **Bilingual Support**: Automatically switches between English and Chinese based on system language
|
|
68
83
|
- **Dry-Run Mode**: Preview installations before applying changes
|
|
@@ -91,7 +106,7 @@ git clone <repo-url>
|
|
|
91
106
|
cd skillman
|
|
92
107
|
|
|
93
108
|
# Install dependencies
|
|
94
|
-
|
|
109
|
+
npm install
|
|
95
110
|
|
|
96
111
|
# Run locally
|
|
97
112
|
node bin/skillman.mjs
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import path from 'path';
|
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { select, confirm, input, Separator, checkbox } from '@inquirer/prompts';
|
|
9
9
|
import pkg from '../package.json' with { type: 'json' };
|
|
10
|
-
import { scanSkills } from './scanner.js';
|
|
10
|
+
import { scanSkills, parseSkillFile } from './scanner.js';
|
|
11
11
|
import { installSkill } from './installer.js';
|
|
12
12
|
import { loadAgents } from './config.js';
|
|
13
13
|
import { t } from './i18n.js';
|
|
@@ -431,7 +431,13 @@ async function continueInstallMultiple(selectedSkills, dryRun) {
|
|
|
431
431
|
let shouldInstall = true;
|
|
432
432
|
try {
|
|
433
433
|
await fs.access(targetDir);
|
|
434
|
-
|
|
434
|
+
// Get existing skill version
|
|
435
|
+
const existingSkillFile = path.join(targetDir, 'SKILL.md');
|
|
436
|
+
const existingSkill = await parseSkillFile(existingSkillFile);
|
|
437
|
+
const currentVer = existingSkill?.version || '?';
|
|
438
|
+
const newVer = skill.version || '?';
|
|
439
|
+
log.warn(`${skill.name} ${t('msg.already_exists') || 'already exists'}`);
|
|
440
|
+
console.log(` ${c.gray}Current:${c.reset} v${currentVer} ${c.gray}→${c.reset} ${c.gray}Installing:${c.reset} v${newVer}`);
|
|
435
441
|
const overwrite = await confirm({ message: t('prompt.overwrite') + '?', default: false });
|
|
436
442
|
if (!overwrite) {
|
|
437
443
|
log.info(t('msg.skipped') || 'Skipped');
|
package/src/i18n.js
CHANGED
|
@@ -72,6 +72,7 @@ const translations = {
|
|
|
72
72
|
'msg.updating': '正在更新',
|
|
73
73
|
'msg.uninstalling': '正在卸载',
|
|
74
74
|
'msg.cancelled': '已取消',
|
|
75
|
+
'msg.already_exists': '已存在',
|
|
75
76
|
'msg.init_skill': '初始化 Skill 模板',
|
|
76
77
|
'msg.created': '已创建',
|
|
77
78
|
'msg.init_hint': '编辑 SKILL.md 来自定义你的 skill',
|
|
@@ -144,6 +145,7 @@ const translations = {
|
|
|
144
145
|
'msg.preview_summary': 'Preview Summary',
|
|
145
146
|
'msg.dry_run_hint': 'Running with --dry-run, no changes made',
|
|
146
147
|
'msg.skill_exists': 'Skill already exists',
|
|
148
|
+
'msg.already_exists': 'already exists',
|
|
147
149
|
'msg.install_cancelled': 'Installation cancelled',
|
|
148
150
|
'msg.downloading': 'Downloading...',
|
|
149
151
|
'msg.downloaded': 'Downloaded',
|
|
@@ -158,6 +160,7 @@ const translations = {
|
|
|
158
160
|
'msg.updating': 'Updating',
|
|
159
161
|
'msg.uninstalling': 'Uninstalling',
|
|
160
162
|
'msg.cancelled': 'Cancelled',
|
|
163
|
+
'msg.already_exists': 'already exists',
|
|
161
164
|
'msg.init_skill': 'Initializing skill template',
|
|
162
165
|
'msg.created': 'Created',
|
|
163
166
|
'msg.init_hint': 'Edit SKILL.md to customize your skill',
|
package/src/scanner.js
CHANGED
|
@@ -10,6 +10,42 @@ import path from 'path';
|
|
|
10
10
|
// Common skill container directories
|
|
11
11
|
const SKILL_CONTAINERS = ['skills', '.agents/skills', '.claude/skills'];
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Parse a single SKILL.md file to extract metadata
|
|
15
|
+
* @param {string} skillFile - Path to SKILL.md
|
|
16
|
+
* @returns {Promise<Object|null>} Parsed skill info or null if invalid
|
|
17
|
+
*/
|
|
18
|
+
export async function parseSkillFile(skillFile) {
|
|
19
|
+
try {
|
|
20
|
+
const content = await fs.readFile(skillFile, 'utf-8');
|
|
21
|
+
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
22
|
+
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
23
|
+
|
|
24
|
+
// Parse version from metadata block (supports quoted and unquoted)
|
|
25
|
+
const metadataMatch = content.match(/metadata:[\s\S]*?(?=\n\w|$)/);
|
|
26
|
+
let version;
|
|
27
|
+
if (metadataMatch) {
|
|
28
|
+
const versionInMeta = metadataMatch[0].match(/^\s+version:\s*(.+)$/m);
|
|
29
|
+
if (versionInMeta) {
|
|
30
|
+
// Remove quotes if present (both single and double)
|
|
31
|
+
version = versionInMeta[1].trim().replace(/^["']|["']$/g, '');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (nameMatch) {
|
|
36
|
+
return {
|
|
37
|
+
name: nameMatch[1].trim(),
|
|
38
|
+
description: descMatch ? descMatch[1].trim() : '',
|
|
39
|
+
version: version
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// File doesn't exist or can't be read
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
13
49
|
/**
|
|
14
50
|
* Scan a single directory for skills
|
|
15
51
|
* @param {string} dir - Directory to scan
|
|
@@ -28,30 +64,12 @@ async function scanSingleDir(dir) {
|
|
|
28
64
|
const skillPath = path.join(dir, entry.name);
|
|
29
65
|
const skillFile = path.join(skillPath, 'SKILL.md');
|
|
30
66
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let version;
|
|
38
|
-
if (metadataMatch) {
|
|
39
|
-
const versionInMeta = metadataMatch[0].match(/^\s+version:\s*(.+)$/m);
|
|
40
|
-
if (versionInMeta) {
|
|
41
|
-
version = versionInMeta[1].trim();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (nameMatch) {
|
|
46
|
-
skills.push({
|
|
47
|
-
name: nameMatch[1].trim(),
|
|
48
|
-
path: skillPath,
|
|
49
|
-
description: descMatch ? descMatch[1].trim() : '',
|
|
50
|
-
version: version
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
} catch {
|
|
54
|
-
// No SKILL.md or parse error, skip
|
|
67
|
+
const skillInfo = await parseSkillFile(skillFile);
|
|
68
|
+
if (skillInfo) {
|
|
69
|
+
skills.push({
|
|
70
|
+
...skillInfo,
|
|
71
|
+
path: skillPath
|
|
72
|
+
});
|
|
55
73
|
}
|
|
56
74
|
}
|
|
57
75
|
} catch {
|