@nnnggel/skills-management 1.0.1 → 1.0.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [简体中文](./README.zh-CN.md) | English
4
4
 
5
- `skm` is a powerful CLI tool designed to manage and synchronize "skills" (prompt libraries, instruction sets, or capability modules) across various AI coding agents and projects. It serves as a central hub to download skills from GitHub and selectively link them into your local AI project configurations.
5
+ `skm` is a powerful CLI tool designed to manage and synchronize "skills" (prompt libraries, instruction sets, or capability modules) across various AI coding agents and projects. It serves as a central hub to add skills from GitHub or local directories and selectively link them into your local AI project configurations.
6
6
 
7
7
  It supports multiple AI environments including **OpenCode**, **Cursor**, **Gemini**, **Antigravity**, **Claude**, and **GitHub** projects.
8
8
 
@@ -12,11 +12,14 @@ It supports multiple AI environments including **OpenCode**, **Cursor**, **Gemin
12
12
 
13
13
  - **Global Skill Repository**: Centralized management of your AI skills.
14
14
  - **GitHub Integration**: Add skills directly from GitHub repositories or specific subdirectories (sparse checkout).
15
+ - **Local Skill Support**: Add skills from local directories for development and debugging.
15
16
  - **Version Control**: Check for updates and synchronize changes from remote repositories.
16
17
  - **Project Detection**: Automatically detects the AI project type in your current directory.
17
18
  - **Symbolic Linking**: Efficiently links skills to projects without duplication, keeping them in sync.
18
19
  - **Project Isolation**: Manage different sets of skills for different projects.
19
20
 
21
+ ![Screenshot](repo.png)
22
+
20
23
  ## 💡 Why skm?
21
24
 
22
25
  - **Centralized Efficiency**: Skills are cloned only once (`~/.skills-management/repo`) and shared across infinite projects via symbolic links. This saves significant disk space and ensures all your projects use the same curated version of a skill.
@@ -24,7 +27,7 @@ It supports multiple AI environments including **OpenCode**, **Cursor**, **Gemin
24
27
  - **Automatic Environment Awareness**: You don't need to know where Cursor, Claude, or OpenCode stores their skills. `skm` automatically detects your project environment and handles the directory structures for you.
25
28
  - **Clean & Non-Intrusive**: Since skills are symlinked, your project folder stays clean. No extra `.git` folders or heavy files are added to your project's version control.
26
29
  - **Self-healing**: Proactively detects and helps you clean up broken symbolic links if a global skill is deleted.
27
- - **Version Sync**: One-click check for updates across all your global skills to keep your local "AI brain" sharp.
30
+ - **Version Sync**: Check for remote updates to keep your local "AI brain" sharp.
28
31
 
29
32
  ## 📦 Installation
30
33
 
@@ -57,10 +60,18 @@ skm
57
60
 
58
61
  Select **"1. repo"** from the main menu to manage your global collection of skills.
59
62
 
60
- - **Add Skill**: Enter a GitHub URL to download a skill.
61
- - Supports full repositories: `https://github.com/user/repo`
62
- - Supports specific subfolders: `https://github.com/user/repo/tree/main/path/to/skill`
63
- - **Update Skills**: Checks for newer commits on the remote GitHub repository and updates your local copy.
63
+ #### Add Skill
64
+
65
+ **GitHub Type**:
66
+ - Full repositories: `https://github.com/user/repo` (example: https://github.com/blader/humanizer)
67
+ - Specific subfolders: `https://github.com/user/repo/tree/main/path/to/skill` (example: https://github.com/anthropics/skills/tree/main/skills/pdf)
68
+
69
+ **Local Type**:
70
+ - Local directories: `/local/path/to/skill` (example: `~/Desktop/myskill`)
71
+
72
+ #### Other Operations
73
+
74
+ - **Update Skills**: Checks for newer commits on the remote GitHub repository and updates your local copy (GitHub type only).
64
75
  - **Delete Skills**: Remove skills from your global repository.
65
76
 
66
77
  ### 2. Project Skill Management (`list`)
@@ -96,8 +107,8 @@ Select **"2. list(type)"** to manage skills for the current project.
96
107
 
97
108
  - **`config.json`**: Global configuration file. It stores system-level settings and metadata.
98
109
  - **`repo/`**: The heart of the management system.
99
- - **`versions.json`**: The skill registry. It tracks all added skills, their unique IDs, current commit hashes (for version control), and original paths.
100
- - **`github__[user]__[repo]__[subpath]/`**: Local Git repositories. `skm` flattens the ID (replacing `/` with `__`) to create safe directory names.
110
+ - **`skills.json`**: The skill registry. It tracks all added skills, their unique IDs, current commit hashes (for version control), and original paths.
111
+ - **`[type]__[name]/`**: Local skill repository storage. `skm` flattens the ID (replacing `/` with `__`) to create safe directory names. For example, `github:user/repo/path` becomes `github__user__repo__path`, and `local:myskill` becomes `local__myskill`.
101
112
  - **Note**: `skm` handles complex paths like `github:user/repo/path/to/skill` by creating a unique folder like `github__user__repo__path__to__skill`.
102
113
 
103
114
  ### Cross-Platform Compatibility (Windows)
@@ -105,7 +116,13 @@ Select **"2. list(type)"** to manage skills for the current project.
105
116
  - On **macOS/Linux**, it uses standard symbolic links.
106
117
  - On **Windows**, it automatically uses **Directory Junctions** (a type of symbolic link for folders) to ensure capability without requiring Administrator privileges or Developer Mode.
107
118
 
119
+ ---
120
+
121
+ ## 📝 Changelog
108
122
 
123
+ ### v1.0.2
124
+ - Local Skill Support: Add skills directly from local directories
125
+ - Menu Navigation Optimization: Unified numeric menu style, added return options, improved terminal compatibility
109
126
 
110
127
  ## 📄 License
111
128
 
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  简体中文 | [English](./README.md)
4
4
 
5
- `skm` 是一个强大的命令行工具,用于在各种 AI 编程代理和项目中管理与同步“技能”(Skills,即提示词库、指令集或能力模块)。它作为一个中心枢纽,帮助你从 GitHub 下载技能,并将其选择性地链接到本地 AI 项目配置中。
5
+ `skm` 是一个强大的命令行工具,用于在各种 AI 编程代理和项目中管理与同步“技能”(Skills,即提示词库、指令集或能力模块)。它作为一个中心枢纽,帮助你从 GitHub 或本地目录添加技能,并将其选择性地链接到本地 AI 项目配置中。
6
6
 
7
7
  支持多种 AI 环境,包括 **OpenCode**、**Cursor**、**Gemini**、**Antigravity**、**Claude** 和 **GitHub** 项目。
8
8
 
@@ -12,11 +12,14 @@
12
12
 
13
13
  - **全局技能仓库**:集中管理所有的 AI 技能。
14
14
  - **GitHub 集成**:直接从 GitHub 仓库或指定子目录添加技能(支持稀疏检出)。
15
+ - **本地技能支持**:支持从本地目录添加技能,方便开发和调试。
15
16
  - **版本控制**:检查远程更新并同步技能版本。
16
17
  - **项目检测**:自动检测当前目录下的 AI 项目类型。
17
18
  - **软链接管理**:通过符号链接将技能高效注入项目,避免文件复制,保持同步。
18
19
  - **项目隔离**:为不同项目配置不同的技能组合。
19
20
 
21
+ ![示意图](repo.png)
22
+
20
23
  ## 💡 为什么选择 skm?
21
24
 
22
25
  - **核心效率**:技能仅克隆一次(存储在 `~/.skills-management/repo`),通过符号链接在无限个项目间共享。这不仅极大地节省了磁盘空间,还确保了所有项目使用的技能版本高度统一。
@@ -24,7 +27,7 @@
24
27
  - **自动化环境感知**:你不需要记住 Cursor、Claude 或 OpenCode 把技能存在哪里。`skm` 会自动识别你的项目环境并处理对应的目录结构。
25
28
  - **整洁且无侵入**:由于使用的是软链接,你的项目文件夹保持纯净。不会有额外的 `.git` 文件夹或庞大的二进制文件进入你的项目版本控制。
26
29
  - **自我维护**:主动检测并帮助你清理失效的软链接(例如当全局技能被删除时)。
27
- - **版本同步**:一键检查所有全局技能的远程更新,确保你的“AI 大脑”始终处于最新状态。
30
+ - **版本同步**:检查技能的远程更新,确保你的“AI 大脑”始终处于最新状态。
28
31
 
29
32
  ## 📦 安装指南
30
33
 
@@ -59,10 +62,18 @@ skm
59
62
 
60
63
  在主菜单选择 **"1. repo"** 来管理你的全局技能库。
61
64
 
62
- - **添加技能 (Add skill)**:输入 GitHub URL 下载技能。
63
- - 支持完整仓库:`https://github.com/user/repo`
64
- - 支持特定子目录:`https://github.com/user/repo/tree/main/path/to/skill`
65
- - **更新技能 (Update)**:自动检查远程仓库是否有新提交,并更新本地副本。
65
+ #### 添加技能 (Add skill)
66
+
67
+ **GitHub 类型**:
68
+ - 支持完整仓库:`https://github.com/user/repo` (例子:https://github.com/blader/humanizer)
69
+ - 支持特定子目录:`https://github.com/user/repo/tree/main/path/to/skill` (例子:https://github.com/anthropics/skills/tree/main/skills/pdf)
70
+
71
+ **Local 类型**:
72
+ - 支持本地目录:`/local/path/to/skill` (例子:`~/Desktop/myskill`)
73
+
74
+ #### 其他操作
75
+
76
+ - **更新技能 (Update)**:自动检查远程仓库是否有新提交,并更新本地副本(仅支持 GitHub 类型)。
66
77
  - **删除技能 (Delete)**:从全局仓库中移除技能。
67
78
 
68
79
  ### 2. 项目技能管理 (`list`)
@@ -98,8 +109,8 @@ skm
98
109
 
99
110
  - **`config.json`**: 全局配置文件,存储系统级设置和元数据。
100
111
  - **`repo/`**: 管理系统的核心目录。
101
- - **`versions.json`**: 技能注册表(数据库)。记录了所有已添加技能的 ID、当前 Commit Hash(用于版本追踪)、检出类型和路径信息。
102
- - **`github__[user]__[repo]__[subpath]/`**: 本地 Git 仓库存储中心。`skm` 会将 ID 扁平化(将 `/` 替换为 `__`)以创建安全的文件目录名。例如 `github:user/repo/path` 会被存储为 `github__user__repo__path`。
112
+ - **`skills.json`**: 技能注册表(数据库)。记录了所有已添加技能的 ID、当前 Commit Hash(用于版本追踪)、检出类型和路径信息。
113
+ - **`[type]__[name]/`**: 本地 Skill 仓库存储中心。`skm` 会将 ID 扁平化(将 `/` 替换为 `__`)以创建安全的文件目录名。例如 `github:user/repo/path` 会被存储为 `github__user__repo__path`,`local:myskill` 会被存储为 `local__myskill`。
103
114
 
104
115
  ### Windows 兼容性说明
105
116
  `skm` 完全兼容 Windows 系统。
@@ -107,6 +118,14 @@ skm
107
118
  - 在 **Windows** 上,工具会自动使用 **Directory Junctions** (目录联接点)。这是 Windows NTFS 文件系统的一种特性,类似于目录的软链接,但通常**不需要管理员权限**即可创建,确保了最佳的开箱即用体验。
108
119
 
109
120
 
121
+ ---
122
+
123
+ ## 📝 更新日志
124
+
125
+ ### v1.0.2
126
+ - 支持本地技能 (Local Skill):可以直接从本地目录添加技能
127
+ - 菜单引导优化:统一使用数字菜单,添加返回选项,提升终端兼容性
128
+
110
129
  ## 📄 License
111
130
 
112
131
  ISC
package/dist/index.js CHANGED
@@ -129,6 +129,7 @@ var ProjectDetector = class _ProjectDetector {
129
129
  var import_inquirer = __toESM(require("inquirer"));
130
130
  var import_fs_extra5 = __toESM(require("fs-extra"));
131
131
  var import_path4 = __toESM(require("path"));
132
+ var import_os2 = __toESM(require("os"));
132
133
 
133
134
  // src/core/skills.ts
134
135
  var import_fs_extra3 = __toESM(require("fs-extra"));
@@ -136,7 +137,7 @@ var import_path3 = __toESM(require("path"));
136
137
  var SkillRegistry = class {
137
138
  versionsFile;
138
139
  constructor(configManager) {
139
- this.versionsFile = import_path3.default.join(configManager.getRepoDir(), "versions.json");
140
+ this.versionsFile = import_path3.default.join(configManager.getRepoDir(), "skills.json");
140
141
  }
141
142
  getStoredSkills() {
142
143
  if (!import_fs_extra3.default.existsSync(this.versionsFile)) {
@@ -147,9 +148,12 @@ var SkillRegistry = class {
147
148
  saveSkills(skills) {
148
149
  import_fs_extra3.default.writeJsonSync(this.versionsFile, skills, { spaces: 2 });
149
150
  }
150
- addSkill(id, commitId, type, skillPath) {
151
+ addSkill(id, type, commitId, skillPath) {
151
152
  const skills = this.getStoredSkills();
152
- skills[id] = { commitId, type, path: skillPath };
153
+ const skillData = { type };
154
+ if (commitId) skillData.commitId = commitId;
155
+ if (skillPath) skillData.path = skillPath;
156
+ skills[id] = skillData;
153
157
  this.saveSkills(skills);
154
158
  }
155
159
  removeSkill(id) {
@@ -349,7 +353,11 @@ async function repoMenu() {
349
353
  } else {
350
354
  skills.forEach((s, index) => {
351
355
  const num = (index + 1).toString();
352
- console.log(`${num}. ${s.id}:${s.commitId.substring(0, 7)}`);
356
+ if (s.type === "local") {
357
+ console.log(`${num}. ${s.id}`);
358
+ } else {
359
+ console.log(`${num}. ${s.id}:${s.commitId?.substring(0, 7) || "N/A"}`);
360
+ }
353
361
  menuMap[num] = { action: "manage", skill: s };
354
362
  });
355
363
  }
@@ -416,18 +424,23 @@ async function checkSingleSkillUpdate(skill) {
416
424
  async function manageSkill(skill) {
417
425
  console.log(`
418
426
  === Manage: ${skill.id} ===`);
419
- const update = await checkSingleSkillUpdate(skill);
420
- const hasUpdate = update !== null;
421
427
  const menuMap = {
422
428
  "0": "back"
423
429
  };
424
430
  let optionNum = 1;
425
- if (hasUpdate && update) {
426
- console.log(`${optionNum}. Update (${skill.commitId.substring(0, 7)} -> ${update.remoteHead.substring(0, 7)})`);
427
- menuMap[optionNum.toString()] = "update";
428
- optionNum++;
431
+ let update = null;
432
+ if (skill.type === "github") {
433
+ update = await checkSingleSkillUpdate(skill);
434
+ const hasUpdate = update !== null;
435
+ if (hasUpdate && update) {
436
+ console.log(`${optionNum}. Update (${skill.commitId?.substring(0, 7)} -> ${update.remoteHead.substring(0, 7)})`);
437
+ menuMap[optionNum.toString()] = "update";
438
+ optionNum++;
439
+ } else {
440
+ console.log("(No updates available)");
441
+ }
429
442
  } else {
430
- console.log("(No updates available)");
443
+ console.log("(Local skill - updates not supported)");
431
444
  }
432
445
  console.log(`${optionNum}. Delete`);
433
446
  menuMap[optionNum.toString()] = "delete";
@@ -462,22 +475,120 @@ async function updateSingleSkill(skill, update) {
462
475
  }
463
476
  }
464
477
  async function addSkillInteractive(url) {
478
+ if (url) {
479
+ await addGitHubSkillInteractive(url);
480
+ return;
481
+ }
482
+ console.log("\\n=== Select Skill Type ===");
483
+ console.log("1. GitHub Repository");
484
+ console.log("2. Local Directory");
485
+ console.log("0. Return");
486
+ const typeAnswer = await import_inquirer.default.prompt([
487
+ {
488
+ type: "input",
489
+ name: "type",
490
+ message: "Select option:",
491
+ validate: (input) => ["0", "1", "2"].includes(input) || "Please enter 0, 1 or 2"
492
+ }
493
+ ]);
494
+ if (typeAnswer.type === "0") {
495
+ return;
496
+ } else if (typeAnswer.type === "2") {
497
+ await addLocalSkillInteractive();
498
+ } else {
499
+ await addGitHubSkillInteractive();
500
+ }
501
+ }
502
+ async function addLocalSkillInteractive() {
503
+ const configManager = new ConfigManager();
504
+ const skillRegistry = new SkillRegistry(configManager);
505
+ console.log("(Enter empty to return)");
506
+ const answer = await import_inquirer.default.prompt([
507
+ {
508
+ type: "input",
509
+ name: "path",
510
+ message: "Enter local skill path:"
511
+ }
512
+ ]);
513
+ if (!answer.path || answer.path.trim() === "") {
514
+ return;
515
+ }
516
+ const localPath = import_path4.default.resolve(answer.path);
517
+ if (!import_fs_extra5.default.existsSync(localPath)) {
518
+ console.error("Error: Path does not exist.");
519
+ return;
520
+ }
521
+ if (!import_fs_extra5.default.statSync(localPath).isDirectory()) {
522
+ console.error("Error: Path is not a directory.");
523
+ return;
524
+ }
525
+ const skillMdPath = import_path4.default.join(localPath, "SKILL.md");
526
+ if (!import_fs_extra5.default.existsSync(skillMdPath)) {
527
+ console.error("Error: SKILL.md not found in the specified directory.");
528
+ return;
529
+ }
530
+ const skillName = import_path4.default.basename(localPath);
531
+ const id = `local:${skillName}`;
532
+ const existingSkill = skillRegistry.getSkill(id);
533
+ if (existingSkill) {
534
+ const confirm = await import_inquirer.default.prompt([
535
+ {
536
+ type: "confirm",
537
+ name: "overwrite",
538
+ message: `Skill ${id} already exists. Overwrite?`,
539
+ default: false
540
+ }
541
+ ]);
542
+ if (!confirm.overwrite) {
543
+ console.log("Operation cancelled.");
544
+ return;
545
+ }
546
+ await import_fs_extra5.default.remove(configManager.getRepoPath(id));
547
+ }
548
+ const destPath = configManager.getRepoPath(id);
549
+ console.log(`Copying ${skillName} to repository...`);
550
+ await import_fs_extra5.default.copy(localPath, destPath);
551
+ skillRegistry.addSkill(id, "local");
552
+ console.log(`Skill ${id} added successfully.`);
553
+ console.log("\\nHow to handle the original directory?");
554
+ console.log("1. Keep (do nothing)");
555
+ console.log("2. Link (replace with symlink to repo)");
556
+ console.log("3. Delete");
557
+ const handleAnswer = await import_inquirer.default.prompt([
558
+ {
559
+ type: "input",
560
+ name: "action",
561
+ message: "Select option:",
562
+ validate: (input) => ["1", "2", "3"].includes(input) || "Please enter 1, 2 or 3"
563
+ }
564
+ ]);
565
+ if (handleAnswer.action === "3") {
566
+ await import_fs_extra5.default.remove(localPath);
567
+ console.log("Original directory deleted.");
568
+ } else if (handleAnswer.action === "2") {
569
+ await import_fs_extra5.default.remove(localPath);
570
+ const symlinkType = import_os2.default.platform() === "win32" ? "junction" : "dir";
571
+ await import_fs_extra5.default.ensureSymlink(destPath, localPath, symlinkType);
572
+ console.log("Original directory replaced with symlink.");
573
+ }
574
+ }
575
+ async function addGitHubSkillInteractive(url) {
465
576
  const configManager = new ConfigManager();
466
577
  const skillRegistry = new SkillRegistry(configManager);
467
578
  const gitManager = new GitManager();
468
579
  let repoUrl = url;
469
580
  if (!repoUrl) {
581
+ console.log("(Enter empty to return)");
470
582
  const answer = await import_inquirer.default.prompt([
471
583
  {
472
584
  type: "input",
473
585
  name: "url",
474
- message: "Enter GitHub URL:",
475
- validate: (input) => input.length > 0 || "URL is required"
586
+ message: "Enter GitHub URL:"
476
587
  }
477
588
  ]);
478
589
  repoUrl = answer.url;
479
590
  }
480
- if (!repoUrl) return;
591
+ if (!repoUrl || repoUrl.trim() === "") return;
481
592
  try {
482
593
  await gitManager.checkGitVersion();
483
594
  const gitInfo = gitManager.normalizeUrl(repoUrl);
@@ -528,7 +639,7 @@ async function addSkillInteractive(url) {
528
639
  } else {
529
640
  commitId = await gitManager.getLocalPathCommitId(destPath, ".");
530
641
  }
531
- skillRegistry.addSkill(id, commitId, "github", gitInfo.path);
642
+ skillRegistry.addSkill(id, "github", commitId, gitInfo.path);
532
643
  console.log(`Skill ${id} added successfully.`);
533
644
  } catch (error) {
534
645
  console.error(`Failed to add skill: ${error.message}`);
@@ -577,7 +688,7 @@ async function deleteSkill(id) {
577
688
  var import_inquirer2 = __toESM(require("inquirer"));
578
689
  var import_fs_extra6 = __toESM(require("fs-extra"));
579
690
  var import_path5 = __toESM(require("path"));
580
- var import_os2 = __toESM(require("os"));
691
+ var import_os3 = __toESM(require("os"));
581
692
  async function linkSkillToProject(skillId, projectInfo) {
582
693
  const configManager = new ConfigManager();
583
694
  const skillRegistry = new SkillRegistry(configManager);
@@ -603,7 +714,7 @@ async function linkSkillToProject(skillId, projectInfo) {
603
714
  return;
604
715
  }
605
716
  try {
606
- const type = import_os2.default.platform() === "win32" ? "junction" : "dir";
717
+ const type = import_os3.default.platform() === "win32" ? "junction" : "dir";
607
718
  await import_fs_extra6.default.ensureSymlink(targetPath, linkPath, type);
608
719
  console.log(`Linked ${skill.id}`);
609
720
  } catch (error) {
@@ -780,19 +891,14 @@ async function mainMenu() {
780
891
  }
781
892
  async function main() {
782
893
  const args = process.argv.slice(2);
783
- if (args.includes("--config")) {
784
- await showConfig();
785
- return;
786
- }
787
- if (args.includes("--help") || args.includes("-h")) {
788
- console.log(`
789
- Skills Management CLI (skm)
790
-
791
- Usage:
792
- skm Enter interactive mode
793
- skm --config View configuration
794
- `);
795
- return;
894
+ if (args.length > 0) {
895
+ if (args.includes("--config")) {
896
+ await showConfig();
897
+ return;
898
+ }
899
+ console.error(`Error: Unknown argument(s): ${args.join(" ")}`);
900
+ console.error("Usage: skm [--config]");
901
+ process.exit(1);
796
902
  }
797
903
  await mainMenu();
798
904
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nnnggel/skills-management",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "main": "index.js",
5
5
  "description": "A CLI tool to manage and synchronize AI coding agent skills",
6
6
  "bin": {