skill-linker 3.0.7 → 3.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
@@ -9,6 +9,7 @@
9
9
  ## ✨ 功能特色
10
10
 
11
11
  - **現代化 TUI 介面**:使用 `prompts` 提供流暢的互動體驗。
12
+ - **智慧來源選擇**:執行 `npx skill-linker` 時自動偵測,可選擇從本地庫或 GitHub Clone。
12
13
  - **模糊搜尋 (Fuzzy Search)**:在選擇 Repository 時,直接輸入文字即可即時過濾清單。
13
14
  - **智慧偵測**:自動偵測系統中已安裝的 Agent,並在選單中預設勾選。
14
15
  - **多 Agent 支援**:支援 Claude Code, GitHub Copilot, Antigravity, Cursor, Windsurf, OpenCode, Gemini CLI 等。
@@ -21,7 +22,8 @@
21
22
  ### 方式 1:使用 npx (推薦)
22
23
 
23
24
  ```bash
24
- # 啟動互動式安裝介面 (選擇本地或新 Clone)
25
+ # 啟動互動式安裝介面
26
+ # 第一步會詢問:從本地庫選擇 或 從 GitHub Clone
25
27
  npx skill-linker
26
28
 
27
29
  # 瀏覽並從庫中 (AgentSkills/) 挑選已下載的 Skill
@@ -29,7 +31,7 @@ npx skill-linker list
29
31
  # 或使用縮寫
30
32
  npx skill-linker -l
31
33
 
32
- # GitHub Clone 並安裝
34
+ # 直接從 GitHub Clone 並安裝 (跳過來源選擇)
33
35
  npx skill-linker --from https://github.com/user/my-skill
34
36
 
35
37
  # 指定本地路徑 (如果是自己 clone 下來的指定目錄)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-linker",
3
- "version": "3.0.7",
3
+ "version": "3.0.8",
4
4
  "description": "Interactive CLI to link AI Agent Skills to various agents (Claude, Copilot, Antigravity, Cursor, etc.)",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -80,71 +80,158 @@ async function install(options) {
80
80
  }
81
81
 
82
82
  if (skillPaths.length === 0) {
83
- if (!dirExists(DEFAULT_LIB_PATH)) {
84
- console.error(chalk.red('[ERROR]'), `Skill library not found: ${DEFAULT_LIB_PATH}`);
85
- console.log(chalk.blue('[INFO]'), 'Use --from <github_url> to clone skills first.');
86
- process.exit(1);
83
+ // First, ask user to choose source: local library or GitHub
84
+ const hasLocalLibrary = dirExists(DEFAULT_LIB_PATH) && findRepos(DEFAULT_LIB_PATH).length > 0;
85
+
86
+ const sourceChoices = [
87
+ { title: 'Clone from GitHub', value: 'github' }
88
+ ];
89
+
90
+ if (hasLocalLibrary) {
91
+ sourceChoices.unshift({ title: 'Select from local library', value: 'local' });
87
92
  }
88
-
89
- const repos = findRepos(DEFAULT_LIB_PATH);
90
-
91
- if (repos.length === 0) {
92
- console.error(chalk.red('[ERROR]'), `No repos found in ${DEFAULT_LIB_PATH}`);
93
- console.log(chalk.blue('[INFO]'), 'Use --from <github_url> to clone skills first.');
94
- process.exit(1);
93
+
94
+ const { source } = await prompts({
95
+ type: 'select',
96
+ name: 'source',
97
+ message: 'Where do you want to get skills from?',
98
+ choices: sourceChoices
99
+ });
100
+
101
+ if (!source) {
102
+ console.log(chalk.yellow('[WARNING]'), 'No source selected. Exiting.');
103
+ process.exit(0);
95
104
  }
105
+
106
+ // Handle GitHub source
107
+ if (source === 'github') {
108
+ const { githubUrl } = await prompts({
109
+ type: 'text',
110
+ name: 'githubUrl',
111
+ message: 'Enter GitHub URL:',
112
+ validate: value => value.trim() !== '' || 'Please enter a valid GitHub URL'
113
+ });
114
+
115
+ if (!githubUrl) {
116
+ console.log(chalk.yellow('[WARNING]'), 'No URL provided. Exiting.');
117
+ process.exit(0);
118
+ }
119
+
120
+ // Use the same logic as --from flag
121
+ console.log(chalk.blue('[INFO]'), `Cloning from ${githubUrl}...`);
122
+
123
+ try {
124
+ const { skillPath: clonedPath, targetPath, needsUpdate, hasSubpath } = await cloneOrUpdateRepo(githubUrl);
125
+
126
+ if (needsUpdate) {
127
+ const { shouldUpdate } = await prompts({
128
+ type: 'confirm',
129
+ name: 'shouldUpdate',
130
+ message: `Repository already exists. Update with git pull?`,
131
+ initial: false
132
+ });
133
+
134
+ if (shouldUpdate) {
135
+ await pullRepo(targetPath);
136
+ console.log(chalk.green('[SUCCESS]'), 'Repository updated!');
137
+ }
138
+ }
139
+
140
+ // If no subpath, check for skills/ subdirectory
141
+ if (!hasSubpath && dirExists(path.join(targetPath, 'skills'))) {
142
+ const subSkills = listDirectories(path.join(targetPath, 'skills'));
143
+
144
+ if (subSkills.length > 0) {
145
+ const { selectedSkills } = await prompts({
146
+ type: 'multiselect',
147
+ name: 'selectedSkills',
148
+ message: 'This repo contains multiple skills. Select skills to install:',
149
+ choices: [
150
+ ...subSkills.map(s => ({ title: s, value: path.join(targetPath, 'skills', s) })),
151
+ { title: 'Link entire repo', value: targetPath }
152
+ ],
153
+ hint: '- Space to select. Return to submit'
154
+ });
155
+
156
+ if (selectedSkills && selectedSkills.length > 0) {
157
+ skillPaths = selectedSkills;
158
+ }
159
+ } else {
160
+ skillPaths = [targetPath];
161
+ }
162
+ } else {
163
+ skillPaths = [clonedPath];
164
+ }
165
+
166
+ console.log(chalk.green('[SUCCESS]'), 'Clone completed!');
167
+ } catch (error) {
168
+ console.error(chalk.red('[ERROR]'), error.message);
169
+ process.exit(1);
170
+ }
171
+ }
172
+
173
+ // Handle local library source
174
+ if (source === 'local') {
175
+ const repos = findRepos(DEFAULT_LIB_PATH);
176
+
177
+ if (repos.length === 0) {
178
+ console.error(chalk.red('[ERROR]'), `No repos found in ${DEFAULT_LIB_PATH}`);
179
+ console.log(chalk.blue('[INFO]'), 'Use --from <github_url> to clone skills first.');
180
+ process.exit(1);
181
+ }
182
+
183
+ console.log('');
184
+
185
+ // 1. Select Repository
186
+ const { selectedRepo } = await prompts({
187
+ type: 'autocomplete',
188
+ name: 'selectedRepo',
189
+ message: 'Select a repository:',
190
+ choices: repos.map(repo => ({
191
+ title: `${repo.name}${repo.hasSkillsDir ? chalk.dim(' (has skills/)') : ''}`,
192
+ value: repo
193
+ })),
194
+ suggest: (input, choices) => {
195
+ const inputLower = input.toLowerCase();
196
+ return Promise.resolve(
197
+ choices.filter(choice => choice.title.toLowerCase().includes(inputLower))
198
+ );
199
+ }
200
+ });
96
201
 
97
- console.log('');
98
-
99
- // 1. Select Repository
100
- const { selectedRepo } = await prompts({
101
- type: 'autocomplete',
102
- name: 'selectedRepo',
103
- message: 'Select a repository:',
104
- choices: repos.map(repo => ({
105
- title: `${repo.name}${repo.hasSkillsDir ? chalk.dim(' (has skills/)') : ''}`,
106
- value: repo
107
- })),
108
- suggest: (input, choices) => {
109
- const inputLower = input.toLowerCase();
110
- return Promise.resolve(
111
- choices.filter(choice => choice.title.toLowerCase().includes(inputLower))
112
- );
202
+ if (!selectedRepo) {
203
+ console.log(chalk.yellow('[WARNING]'), 'No repository selected. Exiting.');
204
+ process.exit(0);
113
205
  }
114
- });
115
206
 
116
- if (!selectedRepo) {
117
- console.log(chalk.yellow('[WARNING]'), 'No repository selected. Exiting.');
118
- process.exit(0);
119
- }
207
+ // 2. Select Sub-skills (if applicable)
208
+ if (selectedRepo.hasSkillsDir) {
209
+ const skillsDir = path.join(selectedRepo.path, 'skills');
210
+ const subSkills = listDirectories(skillsDir);
120
211
 
121
- // 2. Select Sub-skills (if applicable)
122
- if (selectedRepo.hasSkillsDir) {
123
- const skillsDir = path.join(selectedRepo.path, 'skills');
124
- const subSkills = listDirectories(skillsDir);
125
-
126
- if (subSkills.length > 0) {
127
- const { selectedSubSkills } = await prompts({
128
- type: 'multiselect',
129
- name: 'selectedSubSkills',
130
- message: `Select skills from ${chalk.cyan(selectedRepo.name)} (Space to select):`,
131
- choices: [
132
- ...subSkills.map(s => ({ title: s, value: path.join(skillsDir, s) })),
133
- { title: 'Link entire repo', value: selectedRepo.path }
134
- ],
135
- hint: '- Space to select. Return to submit'
136
- });
212
+ if (subSkills.length > 0) {
213
+ const { selectedSubSkills } = await prompts({
214
+ type: 'multiselect',
215
+ name: 'selectedSubSkills',
216
+ message: `Select skills from ${chalk.cyan(selectedRepo.name)} (Space to select):`,
217
+ choices: [
218
+ ...subSkills.map(s => ({ title: s, value: path.join(skillsDir, s) })),
219
+ { title: 'Link entire repo', value: selectedRepo.path }
220
+ ],
221
+ hint: '- Space to select. Return to submit'
222
+ });
137
223
 
138
- if (!selectedSubSkills || selectedSubSkills.length === 0) {
139
- console.log(chalk.yellow('[WARNING]'), 'No skills selected. Exiting.');
140
- process.exit(0);
224
+ if (!selectedSubSkills || selectedSubSkills.length === 0) {
225
+ console.log(chalk.yellow('[WARNING]'), 'No skills selected. Exiting.');
226
+ process.exit(0);
227
+ }
228
+ skillPaths = selectedSubSkills;
229
+ } else {
230
+ skillPaths = [selectedRepo.path];
141
231
  }
142
- skillPaths = selectedSubSkills;
143
232
  } else {
144
233
  skillPaths = [selectedRepo.path];
145
234
  }
146
- } else {
147
- skillPaths = [selectedRepo.path];
148
235
  }
149
236
  }
150
237