skill-linker 4.0.0 → 4.0.4

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
@@ -124,7 +124,7 @@ npx skill-linker list --repo skill-name --json
124
124
  | **GitHub Copilot** | `.github/skills/` | `~/.copilot/skills/` |
125
125
  | **Google Antigravity** | `.agent/skills/` | `~/.gemini/antigravity/skills/` |
126
126
  | **Cursor** | `.cursor/skills/` | `~/.cursor/skills/` |
127
- | **OpenCode** | `.opencode/skill/` | `~/.config/opencode/skill/` |
127
+ | **OpenCode** | `.opencode/skills/` | `~/.config/opencode/skills/` |
128
128
  | **OpenAI Codex** | `.codex/skills/` | `~/.codex/skills/` |
129
129
  | **Gemini CLI** | `.gemini/skills/` | `~/.gemini/skills/` |
130
130
  | **Windsurf** | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-linker",
3
- "version": "4.0.0",
3
+ "version": "4.0.4",
4
4
  "description": "CLI to link AI Agent Skills to various agents (Claude, Copilot, Antigravity, Cursor, etc.)",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -6,7 +6,11 @@ const {
6
6
  createSymlink,
7
7
  listDirectories,
8
8
  } = require("../utils/file-system");
9
- const { getAllAgents, detectInstalledAgents } = require("../utils/agents");
9
+ const {
10
+ getAllAgents,
11
+ detectInstalledAgents,
12
+ findAgentIndex,
13
+ } = require("../utils/agents");
10
14
  const { cloneOrUpdateRepo, pullRepo } = require("../utils/git");
11
15
 
12
16
  /**
@@ -99,10 +103,8 @@ async function install(options) {
99
103
  if (options.agents && options.agents.length > 0) {
100
104
  selectedAgents = options.agents
101
105
  .map((agentName) => {
102
- const idx = agents.findIndex(
103
- (a) => a.name.toLowerCase() === agentName.toLowerCase(),
104
- );
105
- if (idx === -1) {
106
+ const idx = findAgentIndex(agentName);
107
+ if (idx === null) {
106
108
  console.log(
107
109
  chalk.yellow("[WARNING]"),
108
110
  `Unknown agent: ${agentName}, skipping...`,
@@ -1,69 +1,93 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const os = require("os");
4
4
 
5
5
  /**
6
6
  * Supported AI Agent configurations
7
- * Format: { name, projectDir, globalDir }
7
+ * Format: { name, projectDir, globalDir, aliases }
8
8
  */
9
9
  const AGENTS = [
10
- {
11
- name: 'Claude Code',
12
- projectDir: '.claude/skills',
13
- globalDir: path.join(os.homedir(), '.claude/skills')
14
- },
15
- {
16
- name: 'GitHub Copilot',
17
- projectDir: '.github/skills',
18
- globalDir: path.join(os.homedir(), '.copilot/skills')
19
- },
20
- {
21
- name: 'Google Antigravity',
22
- projectDir: '.agent/skills',
23
- globalDir: path.join(os.homedir(), '.gemini/antigravity/skills')
24
- },
25
- {
26
- name: 'Cursor',
27
- projectDir: '.cursor/skills',
28
- globalDir: path.join(os.homedir(), '.cursor/skills')
29
- },
30
- {
31
- name: 'OpenCode',
32
- projectDir: '.opencode/skill',
33
- globalDir: path.join(os.homedir(), '.config/opencode/skill')
34
- },
35
- {
36
- name: 'OpenAI Codex',
37
- projectDir: '.codex/skills',
38
- globalDir: path.join(os.homedir(), '.codex/skills')
39
- },
40
- {
41
- name: 'Gemini CLI',
42
- projectDir: '.gemini/skills',
43
- globalDir: path.join(os.homedir(), '.gemini/skills')
44
- },
45
- {
46
- name: 'Windsurf',
47
- projectDir: '.windsurf/skills',
48
- globalDir: path.join(os.homedir(), '.codeium/windsurf/skills')
49
- }
10
+ {
11
+ name: "Claude Code",
12
+ projectDir: ".claude/skills",
13
+ globalDir: path.join(os.homedir(), ".claude/skills"),
14
+ aliases: ["claude", "claude-code", "anthropic"],
15
+ },
16
+ {
17
+ name: "GitHub Copilot",
18
+ projectDir: ".github/skills",
19
+ globalDir: path.join(os.homedir(), ".copilot/skills"),
20
+ aliases: ["copilot", "github", "gh-copilot"],
21
+ },
22
+ {
23
+ name: "Google Antigravity",
24
+ projectDir: ".agent/skills",
25
+ globalDir: path.join(os.homedir(), ".gemini/antigravity/skills"),
26
+ aliases: ["antigravity", "gemini-antigravity"],
27
+ },
28
+ {
29
+ name: "Cursor",
30
+ projectDir: ".cursor/skills",
31
+ globalDir: path.join(os.homedir(), ".cursor/skills"),
32
+ aliases: ["cursor"],
33
+ },
34
+ {
35
+ name: "OpenCode",
36
+ projectDir: ".opencode/skills",
37
+ globalDir: path.join(os.homedir(), ".config/opencode/skills"),
38
+ aliases: ["opencode", "open-code"],
39
+ },
40
+ {
41
+ name: "OpenAI Codex",
42
+ projectDir: ".codex/skills",
43
+ globalDir: path.join(os.homedir(), ".codex/skills"),
44
+ aliases: ["codex", "openai", "openai-codex"],
45
+ },
46
+ {
47
+ name: "Gemini CLI",
48
+ projectDir: ".gemini/skills",
49
+ globalDir: path.join(os.homedir(), ".gemini/skills"),
50
+ aliases: ["gemini", "gemini-cli", "google-gemini"],
51
+ },
52
+ {
53
+ name: "Windsurf",
54
+ projectDir: ".windsurf/skills",
55
+ globalDir: path.join(os.homedir(), ".codeium/windsurf/skills"),
56
+ aliases: ["windsurf", "codeium"],
57
+ },
50
58
  ];
51
59
 
60
+ /**
61
+ * Find agent index by name or alias (case-insensitive)
62
+ * @param {string} name - Agent name or alias
63
+ * @returns {number|null} Agent index or null if not found
64
+ */
65
+ function findAgentIndex(nameOrAlias) {
66
+ const lower = nameOrAlias.toLowerCase();
67
+ const idx = AGENTS.findIndex(
68
+ (agent) =>
69
+ agent.name.toLowerCase() === lower ||
70
+ (agent.aliases &&
71
+ agent.aliases.some((alias) => alias.toLowerCase() === lower)),
72
+ );
73
+ return idx !== -1 ? idx : null;
74
+ }
75
+
52
76
  /**
53
77
  * Detect which agents are installed on the system
54
78
  * @returns {Array} List of detected agent indices
55
79
  */
56
80
  function detectInstalledAgents() {
57
- const installed = [];
81
+ const installed = [];
58
82
 
59
- AGENTS.forEach((agent, index) => {
60
- // Check if global directory exists
61
- if (fs.existsSync(agent.globalDir)) {
62
- installed.push(index);
63
- }
64
- });
83
+ AGENTS.forEach((agent, index) => {
84
+ // Check if global directory exists
85
+ if (fs.existsSync(agent.globalDir)) {
86
+ installed.push(index);
87
+ }
88
+ });
65
89
 
66
- return installed;
90
+ return installed;
67
91
  }
68
92
 
69
93
  /**
@@ -72,7 +96,7 @@ function detectInstalledAgents() {
72
96
  * @returns {Object} Agent configuration
73
97
  */
74
98
  function getAgent(index) {
75
- return AGENTS[index];
99
+ return AGENTS[index];
76
100
  }
77
101
 
78
102
  /**
@@ -80,12 +104,13 @@ function getAgent(index) {
80
104
  * @returns {Array} All agent configurations
81
105
  */
82
106
  function getAllAgents() {
83
- return AGENTS;
107
+ return AGENTS;
84
108
  }
85
109
 
86
110
  module.exports = {
87
- AGENTS,
88
- detectInstalledAgents,
89
- getAgent,
90
- getAllAgents
111
+ AGENTS,
112
+ detectInstalledAgents,
113
+ getAgent,
114
+ getAllAgents,
115
+ findAgentIndex,
91
116
  };
package/src/utils/git.js CHANGED
@@ -1,9 +1,9 @@
1
- const execa = require('execa');
2
- const path = require('path');
3
- const os = require('os');
4
- const { dirExists } = require('./file-system');
1
+ const execa = require("execa");
2
+ const path = require("path");
3
+ const os = require("os");
4
+ const { dirExists } = require("./file-system");
5
5
 
6
- const DEFAULT_LIB_PATH = path.join(os.homedir(), 'Documents/AgentSkills');
6
+ const DEFAULT_LIB_PATH = path.join(os.homedir(), "Documents/AgentSkills");
7
7
 
8
8
  /**
9
9
  * Parse GitHub URL to extract owner, repo, branch, and subpath
@@ -11,46 +11,50 @@ const DEFAULT_LIB_PATH = path.join(os.homedir(), 'Documents/AgentSkills');
11
11
  * @returns {Object} Parsed components
12
12
  */
13
13
  function parseGitHubUrl(url) {
14
- let cleanUrl = url;
15
- let subpath = '';
16
- let branch = 'main';
14
+ let cleanUrl = url;
15
+ let subpath = "";
16
+ let branch = "main";
17
17
 
18
- // Check for /tree/branch/path format
19
- const treeMatch = url.match(/(.+)\/tree\/([^/]+)\/(.+)$/);
20
- if (treeMatch) {
21
- cleanUrl = treeMatch[1];
22
- branch = treeMatch[2];
23
- subpath = treeMatch[3];
24
- }
18
+ // Check for /tree/branch/path format
19
+ const treeMatch = url.match(/(.+)\/tree\/([^/]+)\/(.+)$/);
20
+ if (treeMatch) {
21
+ cleanUrl = treeMatch[1];
22
+ branch = treeMatch[2];
23
+ subpath = treeMatch[3];
24
+ }
25
25
 
26
- // Extract owner/repo
27
- const repoMatch = cleanUrl.match(/github\.com[/:]([^/]+)\/([^/]+?)(\.git)?$/);
26
+ // Extract owner/repo
27
+ const repoMatch = cleanUrl.match(/github\.com[/:]([^/]+)\/([^/]+?)(\.git)?$/);
28
28
 
29
- if (!repoMatch) {
30
- throw new Error('Invalid GitHub URL format');
31
- }
29
+ if (!repoMatch) {
30
+ throw new Error("Invalid GitHub URL format");
31
+ }
32
32
 
33
- return {
34
- owner: repoMatch[1],
35
- repo: repoMatch[2].replace('.git', ''),
36
- branch,
37
- subpath,
38
- cleanUrl
39
- };
33
+ return {
34
+ owner: repoMatch[1],
35
+ repo: repoMatch[2].replace(".git", ""),
36
+ branch,
37
+ subpath,
38
+ cleanUrl,
39
+ };
40
40
  }
41
41
 
42
42
  /**
43
43
  * Clone a GitHub repository
44
44
  * @param {string} url - GitHub URL
45
45
  * @param {string} targetPath - Target directory
46
+ * @param {boolean} shallow - Use shallow clone (default true)
46
47
  * @returns {Promise<void>}
47
48
  */
48
- async function cloneRepo(url, targetPath) {
49
- try {
50
- await execa('git', ['clone', url, targetPath]);
51
- } catch (error) {
52
- throw new Error(`Failed to clone repository: ${error.message}`);
53
- }
49
+ async function cloneRepo(url, targetPath, shallow = true) {
50
+ try {
51
+ const args = shallow
52
+ ? ["clone", "--depth", "1", url, targetPath]
53
+ : ["clone", url, targetPath];
54
+ await execa("git", args);
55
+ } catch (error) {
56
+ throw new Error(`Failed to clone repository: ${error.message}`);
57
+ }
54
58
  }
55
59
 
56
60
  /**
@@ -59,11 +63,11 @@ async function cloneRepo(url, targetPath) {
59
63
  * @returns {Promise<void>}
60
64
  */
61
65
  async function pullRepo(repoPath) {
62
- try {
63
- await execa('git', ['-C', repoPath, 'pull']);
64
- } catch (error) {
65
- throw new Error(`Failed to pull repository: ${error.message}`);
66
- }
66
+ try {
67
+ await execa("git", ["-C", repoPath, "pull", "--rebase"]);
68
+ } catch (error) {
69
+ throw new Error(`Failed to pull repository: ${error.message}`);
70
+ }
67
71
  }
68
72
 
69
73
  /**
@@ -72,37 +76,37 @@ async function pullRepo(repoPath) {
72
76
  * @returns {Promise<{skillPath: string, needsUpdate: boolean}>}
73
77
  */
74
78
  async function cloneOrUpdateRepo(url) {
75
- const parsed = parseGitHubUrl(url);
76
- const targetPath = path.join(DEFAULT_LIB_PATH, parsed.owner, parsed.repo);
79
+ const parsed = parseGitHubUrl(url);
80
+ const targetPath = path.join(DEFAULT_LIB_PATH, parsed.owner, parsed.repo);
77
81
 
78
- let needsUpdate = false;
82
+ let needsUpdate = false;
79
83
 
80
- if (dirExists(targetPath)) {
81
- // Repo exists, ask if user wants to update
82
- needsUpdate = true;
83
- } else {
84
- // Clone new repo
85
- await cloneRepo(parsed.cleanUrl, targetPath);
86
- }
84
+ if (dirExists(targetPath)) {
85
+ // Repo exists, ask if user wants to update
86
+ needsUpdate = true;
87
+ } else {
88
+ // Clone new repo
89
+ await cloneRepo(parsed.cleanUrl, targetPath);
90
+ }
87
91
 
88
- // Determine final skill path
89
- let skillPath = targetPath;
90
- if (parsed.subpath) {
91
- skillPath = path.join(targetPath, parsed.subpath);
92
- }
92
+ // Determine final skill path
93
+ let skillPath = targetPath;
94
+ if (parsed.subpath) {
95
+ skillPath = path.join(targetPath, parsed.subpath);
96
+ }
93
97
 
94
- return {
95
- skillPath,
96
- targetPath,
97
- needsUpdate,
98
- hasSubpath: !!parsed.subpath
99
- };
98
+ return {
99
+ skillPath,
100
+ targetPath,
101
+ needsUpdate,
102
+ hasSubpath: !!parsed.subpath,
103
+ };
100
104
  }
101
105
 
102
106
  module.exports = {
103
- DEFAULT_LIB_PATH,
104
- parseGitHubUrl,
105
- cloneRepo,
106
- pullRepo,
107
- cloneOrUpdateRepo
107
+ DEFAULT_LIB_PATH,
108
+ parseGitHubUrl,
109
+ cloneRepo,
110
+ pullRepo,
111
+ cloneOrUpdateRepo,
108
112
  };