skill-linker 2.0.0 → 3.0.1

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
@@ -1,113 +1,103 @@
1
- # AI Agent Skill Installer
1
+ # AI Agent Skill Installer (skill-linker)
2
2
 
3
- 一個互動式 CLI 工具,用於將 AI Agent Skills 快速連結(Symlink)到各種 AI Agent 的專案或全域目錄中。
3
+ [![npm version](https://img.shields.io/npm/v/skill-linker.svg)](https://www.npmjs.com/package/skill-linker)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org/)
6
+
7
+ 一個現代化的互動式 CLI 工具,用於將 AI Agent Skills 快速連結(Symlink)到各種 AI Agent 的專案或全域目錄中。
4
8
 
5
9
  ## ✨ 功能特色
6
10
 
7
- - **多 Agent 支援**:支援 Claude Code, GitHub Copilot, Antigravity, Cursor, Windsurf, OpenCode 等。
11
+ - **現代化 TUI 介面**:使用 `prompts` 提供流暢的互動體驗。
12
+ - **模糊搜尋 (Fuzzy Search)**:在選擇 Repository 時,直接輸入文字即可即時過濾清單。
13
+ - **智慧偵測**:自動偵測系統中已安裝的 Agent,並在選單中預設勾選。
14
+ - **多 Agent 支援**:支援 Claude Code, GitHub Copilot, Antigravity, Cursor, Windsurf, OpenCode, Gemini CLI 等。
8
15
  - **雙重範圍 (Scope)**:可選擇安裝到當前 `專案目錄 (Project)` 或 `全域目錄 (Global)`。
9
- - **自動 Clone**:使用 `--from` 參數可直接從 GitHub Clone Skill
10
- - **Skill Library 支援**:自動偵測統一的 Skill 存放區。
16
+ - **自動 Clone**:支援從 GitHub Clone 並自動處理 Multi-skill Repos
17
+ - **完全相容 npx**:無需安裝,隨插即用。
11
18
 
12
19
  ## 🚀 快速開始
13
20
 
14
21
  ### 方式 1:使用 npx (推薦)
15
22
 
16
- 無需安裝,直接執行:
17
-
18
23
  ```bash
19
- # 互動式選擇本地 Skill
24
+ # 啟動互動式安裝介面 (選擇本地或新 Clone)
20
25
  npx skill-linker
21
26
 
27
+ # 瀏覽並從庫中 (AgentSkills/) 挑選已下載的 Skill
28
+ npx skill-linker list
29
+ # 或使用縮寫
30
+ npx skill-linker -l
31
+
22
32
  # 從 GitHub Clone 並安裝
23
33
  npx skill-linker --from https://github.com/user/my-skill
24
34
 
25
- # 指定本地路徑
35
+ # 指定本地路徑 (如果是自己 clone 下來的指定目錄)
26
36
  npx skill-linker /path/to/my-skill
27
37
  ```
28
38
 
29
- ### 方式 2:Clone 此專案
39
+ ### 方式 2:本地開發/安裝
30
40
 
31
41
  ```bash
32
- git clone https://github.com/user/skill-installer.git
33
- cd skill-installer
34
- ./link-skill.sh
42
+ git clone https://github.com/raybird/skill-linker.git
43
+ cd skill-linker
44
+ npm install
45
+ npm link # 之後可直接使用 skill-linker 指令
35
46
  ```
36
47
 
37
48
  ## 🛠️ 命令說明
38
49
 
39
50
  ```
40
- Usage: link-skill.sh [OPTIONS] [SKILL_PATH]
41
-
42
- Options:
43
- --from <github_url> 從 GitHub Clone Skill 後再連結
44
- --list 列出已 Clone 的 Repos 並選擇 Skills
45
- --help 顯示說明
46
-
47
- Examples:
48
- ./link-skill.sh # 互動式選擇
49
- ./link-skill.sh --list # 瀏覽已 Clone 的 Repos
50
- ./link-skill.sh /path/to/skill # 指定本地 Skill
51
- ./link-skill.sh --from https://github.com/user/my-skill
52
- ./link-skill.sh --from https://github.com/anthropics/skills/tree/main/skills/pdf
53
- ```
51
+ Usage: skill-linker [options] [command] [skill-path]
54
52
 
55
- ### Multi-Skill Repo 支援
53
+ Interactive CLI to link AI Agent Skills to various agents
56
54
 
57
- 對於包含多個 Skills 的 Repo(如 `anthropics/skills`),腳本會:
58
- 1. 自動偵測 `skills/` 子目錄
59
- 2. 列出所有可用的 Skills 讓您選擇
60
- 3. 或者您可以直接在 URL 中指定子路徑(如 `/tree/main/skills/pdf`)
55
+ Arguments:
56
+ skill-path 指定本地 Skill 目錄路徑
61
57
 
62
- ### 📋 List Mode - 瀏覽已 Clone 的 Repos
63
-
64
- 使用 `--list` 參數可以瀏覽 Skill Library 中已 clone 的所有 repos:
58
+ Options:
59
+ -V, --version 顯示版本號
60
+ --from <github-url> 先從 GitHub Clone Skill 後再進行連結
61
+ -l, --list 列出庫中可用的 Skills (可互動選擇)
62
+ -h, --help 顯示說明
65
63
 
66
- ```bash
67
- npx skill-linker --list
64
+ Commands:
65
+ list 列出庫中所有可用的 Repos 與其 Skills
68
66
  ```
69
67
 
70
- 操作流程:
71
- 1. 顯示所有已 clone 的 repos(以 `owner/repo` 格式)
72
- 2. 選擇要使用的 repo
73
- 3. 如果該 repo 包含多個 skills,會列出讓您選擇
74
- 4. 選擇後繼續正常的 Agent 安裝流程
75
-
76
- 這對於管理多個已下載的 skill repos 特別有用!
77
-
78
- ## 📦 推薦的 Public Skill Repos
68
+ ### 📋 瀏覽模式 (List Mode)
79
69
 
80
- | Repo | 說明 |
81
- |------|------|
82
- | [anthropics/skills](https://github.com/anthropics/skills) | Claude 官方 Skills (pdf, docx, pptx, xlsx...) |
83
- | [obra/superpowers](https://github.com/obra/superpowers) | 開發流程 Skills (TDD, debugging, code-review...) |
70
+ 如果您想從之前透過 `--from` 下載過的庫 (`~/Documents/AgentSkills`) 中挑選 Skill 來安裝,請使用 `list` 子指令:
84
71
 
85
72
  ```bash
86
- # 安裝 Anthropic 的 PDF Skill
87
- npx skill-linker --from https://github.com/anthropics/skills/tree/main/skills/pdf
73
+ npx skill-linker list
74
+ ```
88
75
 
89
- # 安裝 obra 的所有開發 Skills (可互動選擇)
90
- npx skill-linker --from https://github.com/obra/superpowers
76
+ 或使用選項:
77
+ ```bash
78
+ npx skill-linker -l
91
79
  ```
92
80
 
93
- ## 📂 Skill Library
81
+ 1. **第一層**:選擇已 Clone 的 Repository (會標註是否有 `skills/` 子目錄)。
82
+ 2. **第二層**:如果該 Repo 包含多個 Skills,會進階列出供您查看。
83
+
84
+ > 💡 **提示**:如果您已經手動 `git clone` 了某個 Skill Repo,也可以直接指定路徑安裝:
85
+ > ```bash
86
+ > npx skill-linker /path/to/your-cloned-repo
87
+ > ```
94
88
 
95
- 使用 `--from` 參數時,Skills 會自動存放到 `~/Documents/AgentSkills`,並以 **owner/repo** 結構分層:
89
+ ## 📂 Skill Library 管理
90
+
91
+ 當您使用 `--from` 參數時,Skills 會自動存放到 `~/Documents/AgentSkills`,並以 **owner/repo** 結構分層:
96
92
 
97
93
  ```
98
94
  ~/Documents/AgentSkills/
99
95
  ├── anthropics/
100
96
  │ └── skills/ # https://github.com/anthropics/skills
101
- ├── obra/
102
- │ └── superpowers/ # https://github.com/obra/superpowers
103
97
  └── your-org/
104
98
  └── your-skill/ # https://github.com/your-org/your-skill
105
99
  ```
106
100
 
107
- 這種命名空間結構可避免不同帳號擁有相同 repo 名稱時的衝突。
108
-
109
- 腳本會自動偵測此目錄並列出可用的 Skills。
110
-
111
101
  ## 🛠️ 支援的 Agent 與路徑
112
102
 
113
103
  | 平台 / 工具 | 專案目錄 | 全域目錄 |
@@ -121,10 +111,30 @@ npx skill-linker --from https://github.com/obra/superpowers
121
111
  | **Gemini CLI** | `.gemini/skills/` | `~/.gemini/skills/` |
122
112
  | **Windsurf** | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
123
113
 
114
+ ## 📦 推薦的 Public Skill Repos
115
+
116
+ ### Claude 官方 Skills (pdf, docx, pptx, xlsx...)
117
+ [anthropics/skills](https://github.com/anthropics/skills)
118
+ ```bash
119
+ npx skill-linker --from https://github.com/anthropics/skills
120
+ ```
121
+
122
+ ### moltbot 的 AI Agent Skills (來自 clawdhub.com)
123
+ [moltbot/skills](https://github.com/moltbot/skills)
124
+ ```bash
125
+ npx skill-linker --from https://github.com/moltbot/skills
126
+ ```
127
+
128
+ ### 精選的 AI Skills 工具箱
129
+ [obra/superpowers](https://github.com/obra/superpowers)
130
+ ```bash
131
+ npx skill-linker --from https://github.com/obra/superpowers
132
+ ```
133
+
124
134
  ## ⚠️ 注意事項
125
135
 
126
- 1. **Windows 使用者**:請使用 WSL 或 Git Bash 執行此工具。
127
- 2. **Git Clone First**:`--from` 參數會自動處理 clone,但如果不使用該參數,請確保 Skill 已在本地。
136
+ 1. **權限問題**:在建立 Symlink 時,請確保您有對應目錄的寫入權限。
137
+ 2. **環境需求**:需安裝 Node.js 18.0.0 以上版本。
128
138
 
129
139
  ## 授權
130
140
 
package/bin/cli.js CHANGED
@@ -1,48 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * skill-linker CLI
5
- * Node.js wrapper for link-skill.sh
4
+ * skill-linker CLI entry point
5
+ * Modern Node.js implementation with interactive TUI
6
6
  */
7
7
 
8
- const { spawn } = require('child_process');
9
- const path = require('path');
10
- const fs = require('fs');
11
-
12
- // Get the path to the shell script
13
- const scriptPath = path.join(__dirname, '..', 'link-skill.sh');
14
-
15
- // Check if bash is available
16
- const isWindows = process.platform === 'win32';
17
-
18
- if (isWindows) {
19
- console.error('\x1b[31m[ERROR]\x1b[0m This tool requires Bash.');
20
- console.error('On Windows, please use WSL (Windows Subsystem for Linux) or Git Bash.');
21
- console.error('');
22
- console.error('Example with WSL:');
23
- console.error(' wsl npx skill-linker');
24
- process.exit(1);
25
- }
26
-
27
- // Check if script exists
28
- if (!fs.existsSync(scriptPath)) {
29
- console.error('\x1b[31m[ERROR]\x1b[0m Shell script not found:', scriptPath);
30
- process.exit(1);
31
- }
32
-
33
- // Forward all arguments to the shell script
34
- const args = process.argv.slice(2);
35
-
36
- const child = spawn('bash', [scriptPath, ...args], {
37
- stdio: 'inherit',
38
- cwd: process.cwd()
39
- });
40
-
41
- child.on('error', (err) => {
42
- console.error('\x1b[31m[ERROR]\x1b[0m Failed to run script:', err.message);
43
- process.exit(1);
44
- });
45
-
46
- child.on('close', (code) => {
47
- process.exit(code || 0);
48
- });
8
+ require('../src/cli.js');
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "skill-linker",
3
- "version": "2.0.0",
3
+ "version": "3.0.1",
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": {
7
- "skill-linker": "./bin/cli.js"
7
+ "skill-linker": "bin/cli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "test": "echo \"No tests yet\""
@@ -25,16 +25,22 @@
25
25
  ],
26
26
  "author": "",
27
27
  "license": "MIT",
28
+ "dependencies": {
29
+ "chalk": "^4.1.2",
30
+ "commander": "^12.0.0",
31
+ "execa": "^5.1.1",
32
+ "prompts": "^2.4.2"
33
+ },
28
34
  "repository": {
29
35
  "type": "git",
30
- "url": "git+https://github.com/raybird/skill-installer.git"
36
+ "url": "git+https://github.com/raybird/skill-linker.git"
31
37
  },
32
38
  "engines": {
33
- "node": ">=14.0.0"
39
+ "node": ">=18.0.0"
34
40
  },
35
41
  "files": [
36
42
  "bin/",
37
- "link-skill.sh",
43
+ "src/",
38
44
  "README.md"
39
45
  ]
40
- }
46
+ }
package/src/cli.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const chalk = require('chalk');
5
+ const install = require('./commands/install');
6
+ const list = require('./commands/list');
7
+
8
+ // Package info
9
+ const packageJson = require('../package.json');
10
+
11
+ program
12
+ .name('skill-linker')
13
+ .description('Interactive CLI to link AI Agent Skills to various agents')
14
+ .version(packageJson.version);
15
+
16
+ // Default command (install)
17
+ program
18
+ .argument('[skill-path]', 'Path to skill directory')
19
+ .option('--from <github-url>', 'Clone skill from GitHub URL first, then link')
20
+ .option('-l, --list', 'List available skills in library')
21
+ .action(async (skillPath, options) => {
22
+ // Handle --list flag
23
+ if (options.list) {
24
+ await list();
25
+ return;
26
+ }
27
+
28
+ // Run install command
29
+ await install({
30
+ skill: skillPath,
31
+ from: options.from
32
+ });
33
+ });
34
+
35
+ // Standalone list command
36
+ program
37
+ .command('list')
38
+ .description('List all available skills in the library')
39
+ .action(async () => {
40
+ await list();
41
+ });
42
+
43
+ // Parse command line arguments
44
+ program.parse(process.argv);
45
+
@@ -0,0 +1,260 @@
1
+ const prompts = require('prompts');
2
+ const chalk = require('chalk');
3
+ const path = require('path');
4
+ const { findSkills, findRepos, dirExists, ensureDir, createSymlink, listDirectories } = require('../utils/file-system');
5
+ const { getAllAgents, detectInstalledAgents } = require('../utils/agents');
6
+ const { DEFAULT_LIB_PATH, cloneOrUpdateRepo, pullRepo } = require('../utils/git');
7
+
8
+ /**
9
+ * Main install command
10
+ * @param {Object} options - Command options
11
+ */
12
+ async function install(options) {
13
+ let skillPaths = [];
14
+
15
+ // Handle --from flag: Clone from GitHub
16
+ if (options.from) {
17
+ console.log(chalk.blue('[INFO]'), `Cloning from ${options.from}...`);
18
+
19
+ try {
20
+ const { skillPath: clonedPath, targetPath, needsUpdate, hasSubpath } = await cloneOrUpdateRepo(options.from);
21
+
22
+ if (needsUpdate) {
23
+ const { shouldUpdate } = await prompts({
24
+ type: 'confirm',
25
+ name: 'shouldUpdate',
26
+ message: `Repository already exists. Update with git pull?`,
27
+ initial: false
28
+ });
29
+
30
+ if (shouldUpdate) {
31
+ await pullRepo(targetPath);
32
+ console.log(chalk.green('[SUCCESS]'), 'Repository updated!');
33
+ }
34
+ }
35
+
36
+ // If no subpath, check for skills/ subdirectory
37
+ if (!hasSubpath && dirExists(path.join(targetPath, 'skills'))) {
38
+ const subSkills = listDirectories(path.join(targetPath, 'skills'));
39
+
40
+ if (subSkills.length > 0) {
41
+ const { selectedSkills } = await prompts({
42
+ type: 'multiselect',
43
+ name: 'selectedSkills',
44
+ message: 'This repo contains multiple skills. Select skills to install:',
45
+ choices: [
46
+ ...subSkills.map(s => ({ title: s, value: path.join(targetPath, 'skills', s) })),
47
+ { title: 'Link entire repo', value: targetPath }
48
+ ],
49
+ hint: '- Space to select. Return to submit'
50
+ });
51
+
52
+ if (selectedSkills && selectedSkills.length > 0) {
53
+ skillPaths = selectedSkills;
54
+ } else {
55
+ // If nothing selected, maybe they just hit enter without selection? Default to repo path?
56
+ // Or better, error out if multiselect returns empty.
57
+ // Let's assume empty selection means exit or user made mistake.
58
+ // But to be safe, if they chose "Link entire repo" in multiselect (which is weird), handle it.
59
+ // Actually multiselect is better for picking subsets.
60
+ // If empty, let's fall back to entire repo (or maybe error).
61
+ // Let's error to be consistent with agent selection.
62
+ }
63
+ } else {
64
+ skillPaths = [targetPath];
65
+ }
66
+ } else {
67
+ skillPaths = [clonedPath];
68
+ }
69
+
70
+ console.log(chalk.green('[SUCCESS]'), 'Clone completed!');
71
+ } catch (error) {
72
+ console.error(chalk.red('[ERROR]'), error.message);
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ // If no skill path provided, show library selection
78
+ if (skillPaths.length === 0 && options.skill) {
79
+ skillPaths = [options.skill];
80
+ }
81
+
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);
87
+ }
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);
95
+ }
96
+
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
+ );
113
+ }
114
+ });
115
+
116
+ if (!selectedRepo) {
117
+ console.log(chalk.yellow('[WARNING]'), 'No repository selected. Exiting.');
118
+ process.exit(0);
119
+ }
120
+
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
+ });
137
+
138
+ if (!selectedSubSkills || selectedSubSkills.length === 0) {
139
+ console.log(chalk.yellow('[WARNING]'), 'No skills selected. Exiting.');
140
+ process.exit(0);
141
+ }
142
+ skillPaths = selectedSubSkills;
143
+ } else {
144
+ skillPaths = [selectedRepo.path];
145
+ }
146
+ } else {
147
+ skillPaths = [selectedRepo.path];
148
+ }
149
+ }
150
+
151
+ // Validate skill paths
152
+ for (const p of skillPaths) {
153
+ if (!dirExists(p)) {
154
+ console.error(chalk.red('[ERROR]'), `Skill directory not found: ${p}`);
155
+ process.exit(1);
156
+ }
157
+ }
158
+
159
+ if (skillPaths.length > 1) {
160
+ console.log(chalk.blue('[INFO]'), `Selected ${skillPaths.length} skills`);
161
+ } else {
162
+ const skillName = path.basename(skillPaths[0]);
163
+ console.log(chalk.blue('[INFO]'), `Selected Skill: ${chalk.cyan(skillName)} (${skillPaths[0]})`);
164
+ }
165
+
166
+ // Agent selection
167
+ const agents = getAllAgents();
168
+ const installedIndices = detectInstalledAgents();
169
+
170
+ const { selectedAgents } = await prompts({
171
+ type: 'multiselect',
172
+ name: 'selectedAgents',
173
+ message: 'Select agents to install to (Space to select, Enter to confirm):',
174
+ choices: agents.map((agent, index) => ({
175
+ title: agent.name + (installedIndices.includes(index) ? chalk.green(' (Installed)') : ''),
176
+ value: index,
177
+ selected: installedIndices.includes(index)
178
+ })),
179
+ hint: '- Space to select. Return to submit'
180
+ });
181
+
182
+ if (!selectedAgents || selectedAgents.length === 0) {
183
+ console.log(chalk.yellow('[WARNING]'), 'No agents selected. Exiting.');
184
+ process.exit(0);
185
+ }
186
+
187
+ // Process each selected agent
188
+ for (const agentIndex of selectedAgents) {
189
+ const agent = agents[agentIndex];
190
+
191
+ console.log('');
192
+ console.log(chalk.blue('[INFO]'), `Configuring for ${chalk.cyan(agent.name)}...`);
193
+
194
+ const { scope } = await prompts({
195
+ type: 'select',
196
+ name: 'scope',
197
+ message: 'Select scope:',
198
+ choices: [
199
+ { title: `Project (${agent.projectDir})`, value: 'project' },
200
+ { title: `Global (${agent.globalDir})`, value: 'global' },
201
+ { title: 'Both', value: 'both' },
202
+ { title: 'Skip', value: 'skip' }
203
+ ]
204
+ });
205
+
206
+ if (scope === 'skip') continue;
207
+
208
+ const targets = [];
209
+ if (scope === 'project' || scope === 'both') {
210
+ targets.push(path.join(process.cwd(), agent.projectDir));
211
+ }
212
+ if (scope === 'global' || scope === 'both') {
213
+ targets.push(agent.globalDir);
214
+ }
215
+
216
+ for (const targetBase of targets) {
217
+ ensureDir(targetBase);
218
+
219
+ // Loop through all selected skills and link them
220
+ for (const sPath of skillPaths) {
221
+ const sName = path.basename(sPath);
222
+ const targetLink = path.join(targetBase, sName);
223
+
224
+ if (dirExists(targetLink)) {
225
+ // Check if already correct link to avoid prompt
226
+ // But for simplicity, we prompt or skip.
227
+ // To avoid spamming prompts for multiple skills, maybe auto-overwrite or ask once?
228
+ // Let's ask individually for safety for now, or maybe just log and skip if overwrite not confirmed.
229
+ // Actually, prompting for every file in a loop is annoying.
230
+ // Let's check overlap first? Or just try createSymlink which handles unlink.
231
+
232
+ // Let's prompt once per agent/target if any conflicts? Too complex.
233
+ // Simple approach: Prompt for each conflict.
234
+ const { overwrite } = await prompts({
235
+ type: 'confirm',
236
+ name: 'overwrite',
237
+ message: `${targetLink} already exists. Overwrite?`,
238
+ initial: false
239
+ });
240
+
241
+ if (!overwrite) {
242
+ console.log(chalk.blue('[INFO]'), `Skipping ${sName}...`);
243
+ continue;
244
+ }
245
+ }
246
+
247
+ if (createSymlink(sPath, targetLink)) {
248
+ console.log(chalk.green('[SUCCESS]'), `Linked ${sName}`);
249
+ } else {
250
+ console.error(chalk.red('[ERROR]'), `Failed to link ${sName}`);
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ console.log('');
257
+ console.log(chalk.green('[SUCCESS]'), 'All operations completed.');
258
+ }
259
+
260
+ module.exports = install;
@@ -0,0 +1,78 @@
1
+ const chalk = require('chalk');
2
+ const prompts = require('prompts');
3
+ const path = require('path');
4
+ const { findRepos, listDirectories, dirExists } = require('../utils/file-system');
5
+ const { DEFAULT_LIB_PATH } = require('../utils/git');
6
+
7
+ /**
8
+ * List command - shows repos, then skills within selected repo
9
+ */
10
+ async function list() {
11
+ if (!dirExists(DEFAULT_LIB_PATH)) {
12
+ console.error(chalk.red('[ERROR]'), `Skill library not found: ${DEFAULT_LIB_PATH}`);
13
+ console.log(chalk.blue('[INFO]'), 'Use --from <github_url> to clone skills first.');
14
+ process.exit(1);
15
+ }
16
+
17
+ const repos = findRepos(DEFAULT_LIB_PATH);
18
+
19
+ if (repos.length === 0) {
20
+ console.log(chalk.yellow('[WARNING]'), `No repos found in ${DEFAULT_LIB_PATH}`);
21
+ console.log(chalk.blue('[INFO]'), 'Use --from <github_url> to clone skills first.');
22
+ return;
23
+ }
24
+
25
+ console.log('');
26
+ console.log(chalk.blue('[INFO]'), `Repositories in library (${DEFAULT_LIB_PATH}):`);
27
+ console.log('');
28
+
29
+ // Show repos with indication if they have skills/ directory
30
+ const { selectedRepo } = await prompts({
31
+ type: 'select',
32
+ name: 'selectedRepo',
33
+ message: 'Select a repository to view:',
34
+ choices: repos.map(repo => ({
35
+ title: `${repo.name}${repo.hasSkillsDir ? chalk.dim(' (has skills/)') : ''}`,
36
+ value: repo,
37
+ description: repo.path
38
+ }))
39
+ });
40
+
41
+ if (!selectedRepo) {
42
+ console.log(chalk.yellow('[INFO]'), 'No repository selected.');
43
+ return;
44
+ }
45
+
46
+ console.log('');
47
+ console.log(chalk.blue('[INFO]'), `Repository: ${chalk.cyan(selectedRepo.name)}`);
48
+ console.log(chalk.dim(`Path: ${selectedRepo.path}`));
49
+ console.log('');
50
+
51
+ // If repo has skills/ subdirectory, list the skills
52
+ if (selectedRepo.hasSkillsDir) {
53
+ const skillsDir = path.join(selectedRepo.path, 'skills');
54
+ const skills = listDirectories(skillsDir);
55
+
56
+ if (skills.length === 0) {
57
+ console.log(chalk.yellow('[WARNING]'), 'No skills found in skills/ directory');
58
+ return;
59
+ }
60
+
61
+ console.log(chalk.blue('[INFO]'), 'Skills in this repository:');
62
+ console.log('');
63
+
64
+ skills.forEach((skill, index) => {
65
+ console.log(` ${index + 1}. ${chalk.cyan(skill)}`);
66
+ console.log(` ${chalk.dim(path.join(skillsDir, skill))}`);
67
+ });
68
+
69
+ console.log('');
70
+ } else {
71
+ console.log(chalk.blue('[INFO]'), 'This is a single-skill repository (no skills/ subdirectory)');
72
+ console.log(chalk.dim('The entire repository acts as one skill'));
73
+ console.log('');
74
+ }
75
+ }
76
+
77
+ module.exports = list;
78
+