@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 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
- pnpm install
109
+ npm install
95
110
 
96
111
  # Run locally
97
112
  node bin/skillman.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haolin-ai/skillman",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "A CLI tool to install AI agent skills across multiple platforms",
5
5
  "type": "module",
6
6
  "bin": {
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
- log.warn(t('msg.skill_exists'));
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
- try {
32
- const content = await fs.readFile(skillFile, 'utf-8');
33
- const nameMatch = content.match(/^name:\s*(.+)$/m);
34
- const descMatch = content.match(/^description:\s*(.+)$/m);
35
- // Parse version from metadata block
36
- const metadataMatch = content.match(/metadata:[\s\S]*?(?=\n\w|$)/);
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 {