skill-linker 1.0.0
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 +84 -0
- package/bin/cli.js +48 -0
- package/link-skill.sh +256 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# AI Agent Skill Installer
|
|
2
|
+
|
|
3
|
+
一個互動式 CLI 工具,用於將 AI Agent Skills 快速連結(Symlink)到各種 AI Agent 的專案或全域目錄中。
|
|
4
|
+
|
|
5
|
+
## ✨ 功能特色
|
|
6
|
+
|
|
7
|
+
- **多 Agent 支援**:支援 Claude Code, GitHub Copilot, Antigravity, Cursor, Windsurf, OpenCode 等。
|
|
8
|
+
- **雙重範圍 (Scope)**:可選擇安裝到當前 `專案目錄 (Project)` 或 `全域目錄 (Global)`。
|
|
9
|
+
- **自動 Clone**:使用 `--from` 參數可直接從 GitHub Clone Skill。
|
|
10
|
+
- **Skill Library 支援**:自動偵測統一的 Skill 存放區。
|
|
11
|
+
|
|
12
|
+
## 🚀 快速開始
|
|
13
|
+
|
|
14
|
+
### 方式 1:使用 npx (推薦)
|
|
15
|
+
|
|
16
|
+
無需安裝,直接執行:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 互動式選擇本地 Skill
|
|
20
|
+
npx skill-linker
|
|
21
|
+
|
|
22
|
+
# 從 GitHub Clone 並安裝
|
|
23
|
+
npx skill-linker --from https://github.com/user/my-skill
|
|
24
|
+
|
|
25
|
+
# 指定本地路徑
|
|
26
|
+
npx skill-linker /path/to/my-skill
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 方式 2:Clone 此專案
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/user/skill-installer.git
|
|
33
|
+
cd skill-installer
|
|
34
|
+
./link-skill.sh
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 🛠️ 命令說明
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Usage: link-skill.sh [OPTIONS] [SKILL_PATH]
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
--from <github_url> 從 GitHub Clone Skill 後再連結
|
|
44
|
+
--help 顯示說明
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
./link-skill.sh # 互動式選擇
|
|
48
|
+
./link-skill.sh /path/to/skill # 指定本地 Skill
|
|
49
|
+
./link-skill.sh --from https://github.com/user/my-skill
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 📂 Skill Library
|
|
53
|
+
|
|
54
|
+
建議將您的 Public Skills 統一存放在 `~/Documents/AgentSkills`:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mkdir -p ~/Documents/AgentSkills
|
|
58
|
+
cd ~/Documents/AgentSkills
|
|
59
|
+
git clone https://github.com/user/my-awesome-skill.git
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
腳本會自動偵測此目錄並列出可用的 Skills。
|
|
63
|
+
|
|
64
|
+
## 🛠️ 支援的 Agent 與路徑
|
|
65
|
+
|
|
66
|
+
| 平台 / 工具 | 專案目錄 | 全域目錄 |
|
|
67
|
+
|------------|---------|---------|
|
|
68
|
+
| **Claude Code** | `.claude/skills/` | `~/.claude/skills/` |
|
|
69
|
+
| **GitHub Copilot** | `.github/skills/` | `~/.copilot/skills/` |
|
|
70
|
+
| **Google Antigravity** | `.agent/skills/` | `~/.gemini/antigravity/skills/` |
|
|
71
|
+
| **Cursor** | `.cursor/skills/` | `~/.cursor/skills/` |
|
|
72
|
+
| **OpenCode** | `.opencode/skill/` | `~/.config/opencode/skill/` |
|
|
73
|
+
| **OpenAI Codex** | `.codex/skills/` | `~/.codex/skills/` |
|
|
74
|
+
| **Gemini CLI** | `.gemini/skills/` | `~/.gemini/skills/` |
|
|
75
|
+
| **Windsurf** | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
|
|
76
|
+
|
|
77
|
+
## ⚠️ 注意事項
|
|
78
|
+
|
|
79
|
+
1. **Windows 使用者**:請使用 WSL 或 Git Bash 執行此工具。
|
|
80
|
+
2. **Git Clone First**:`--from` 參數會自動處理 clone,但如果不使用該參數,請確保 Skill 已在本地。
|
|
81
|
+
|
|
82
|
+
## 授權
|
|
83
|
+
|
|
84
|
+
MIT License
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* skill-linker CLI
|
|
5
|
+
* Node.js wrapper for link-skill.sh
|
|
6
|
+
*/
|
|
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
|
+
});
|
package/link-skill.sh
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# link-skill.sh
|
|
4
|
+
# Interactive script to link AI Agent Skills to various AI agents
|
|
5
|
+
DEFAULT_LIB_PATH="$HOME/Documents/AgentSkills"
|
|
6
|
+
SKILL_PATH=""
|
|
7
|
+
FROM_URL=""
|
|
8
|
+
|
|
9
|
+
# Helper function for colored output
|
|
10
|
+
print_info() { echo -e "\033[1;34m[INFO]\033[0m $1"; }
|
|
11
|
+
print_success() { echo -e "\033[1;32m[SUCCESS]\033[0m $1"; }
|
|
12
|
+
print_warning() { echo -e "\033[1;33m[WARNING]\033[0m $1"; }
|
|
13
|
+
print_error() { echo -e "\033[1;31m[ERROR]\033[0m $1"; }
|
|
14
|
+
|
|
15
|
+
# Show help
|
|
16
|
+
show_help() {
|
|
17
|
+
echo "Usage: link-skill.sh [OPTIONS] [SKILL_PATH]"
|
|
18
|
+
echo ""
|
|
19
|
+
echo "Options:"
|
|
20
|
+
echo " --from <github_url> Clone skill from GitHub URL first, then link"
|
|
21
|
+
echo " --help Show this help message"
|
|
22
|
+
echo ""
|
|
23
|
+
echo "Examples:"
|
|
24
|
+
echo " ./link-skill.sh # Interactive selection from library"
|
|
25
|
+
echo " ./link-skill.sh /path/to/skill # Link specific local skill"
|
|
26
|
+
echo " ./link-skill.sh --from https://github.com/user/my-skill"
|
|
27
|
+
echo ""
|
|
28
|
+
exit 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Parse arguments
|
|
32
|
+
while [[ $# -gt 0 ]]; do
|
|
33
|
+
case $1 in
|
|
34
|
+
--from)
|
|
35
|
+
FROM_URL="$2"
|
|
36
|
+
shift 2
|
|
37
|
+
;;
|
|
38
|
+
--help|-h)
|
|
39
|
+
show_help
|
|
40
|
+
;;
|
|
41
|
+
*)
|
|
42
|
+
SKILL_PATH="$1"
|
|
43
|
+
shift
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
# 1. Handle --from flag: Clone from GitHub
|
|
49
|
+
if [ -n "$FROM_URL" ]; then
|
|
50
|
+
# Extract repo name from URL
|
|
51
|
+
REPO_NAME=$(basename "$FROM_URL" .git)
|
|
52
|
+
TARGET_CLONE_PATH="$DEFAULT_LIB_PATH/$REPO_NAME"
|
|
53
|
+
|
|
54
|
+
# Ensure library directory exists
|
|
55
|
+
mkdir -p "$DEFAULT_LIB_PATH"
|
|
56
|
+
|
|
57
|
+
if [ -d "$TARGET_CLONE_PATH" ]; then
|
|
58
|
+
print_warning "Directory already exists: $TARGET_CLONE_PATH"
|
|
59
|
+
read -p "Update with git pull? (y/N): " do_pull
|
|
60
|
+
if [[ "$do_pull" =~ ^[yY]$ ]]; then
|
|
61
|
+
print_info "Pulling latest changes..."
|
|
62
|
+
git -C "$TARGET_CLONE_PATH" pull
|
|
63
|
+
fi
|
|
64
|
+
else
|
|
65
|
+
print_info "Cloning $FROM_URL to $TARGET_CLONE_PATH..."
|
|
66
|
+
git clone "$FROM_URL" "$TARGET_CLONE_PATH"
|
|
67
|
+
if [ $? -ne 0 ]; then
|
|
68
|
+
print_error "Failed to clone repository"
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
print_success "Clone completed!"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
SKILL_PATH="$TARGET_CLONE_PATH"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# 2. Determine Source Skill Path (if not already set by --from)
|
|
78
|
+
if [ -z "$SKILL_PATH" ]; then
|
|
79
|
+
# Check if default library exists
|
|
80
|
+
if [ -d "$DEFAULT_LIB_PATH" ]; then
|
|
81
|
+
print_info "No skill path provided. Checking default library: $DEFAULT_LIB_PATH"
|
|
82
|
+
skills=("$DEFAULT_LIB_PATH"/*/)
|
|
83
|
+
|
|
84
|
+
if [ ${#skills[@]} -eq 0 ]; then
|
|
85
|
+
print_error "No skills found in $DEFAULT_LIB_PATH"
|
|
86
|
+
print_info "Please provide a skill path: ./link-skill.sh <path_to_skill>"
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Extract skill names for display
|
|
91
|
+
skill_names=()
|
|
92
|
+
for s in "${skills[@]}"; do
|
|
93
|
+
skill_names+=("$(basename "$s")")
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
echo "Available Skills:"
|
|
97
|
+
select skill_name in "${skill_names[@]}"; do
|
|
98
|
+
if [ -n "$skill_name" ]; then
|
|
99
|
+
SKILL_PATH="${DEFAULT_LIB_PATH}/${skill_name}"
|
|
100
|
+
break
|
|
101
|
+
else
|
|
102
|
+
echo "Invalid selection. Please try again."
|
|
103
|
+
fi
|
|
104
|
+
done
|
|
105
|
+
else
|
|
106
|
+
# Fallback to current directory prompt
|
|
107
|
+
read -p "Enter path to skill directory (default: current dir): " input_path
|
|
108
|
+
input_path=${input_path:-.}
|
|
109
|
+
SKILL_PATH=$(realpath "$input_path")
|
|
110
|
+
fi
|
|
111
|
+
elif [ -n "$SKILL_PATH" ] && [ -z "$FROM_URL" ]; then
|
|
112
|
+
# Path provided directly as argument
|
|
113
|
+
SKILL_PATH=$(realpath "$SKILL_PATH")
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [ ! -d "$SKILL_PATH" ]; then
|
|
117
|
+
print_error "Skill directory not found: $SKILL_PATH"
|
|
118
|
+
exit 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
SKILL_NAME=$(basename "$SKILL_PATH")
|
|
122
|
+
print_info "Selected Skill: \033[1;36m$SKILL_NAME\033[0m ($SKILL_PATH)"
|
|
123
|
+
|
|
124
|
+
# 2. Define Supported Agents
|
|
125
|
+
# Format: "Name:ProjectDir:GlobalDir"
|
|
126
|
+
AGENTS=(
|
|
127
|
+
"Claude Code:.claude/skills:$HOME/.claude/skills"
|
|
128
|
+
"GitHub Copilot:.github/skills:$HOME/.copilot/skills"
|
|
129
|
+
"Google Antigravity:.agent/skills:$HOME/.gemini/antigravity/skills"
|
|
130
|
+
"Cursor:.cursor/skills:$HOME/.cursor/skills"
|
|
131
|
+
"OpenCode:.opencode/skill:$HOME/.config/opencode/skill"
|
|
132
|
+
"OpenAI Codex:.codex/skills:$HOME/.codex/skills"
|
|
133
|
+
"Gemini CLI:.gemini/skills:$HOME/.gemini/skills"
|
|
134
|
+
"Windsurf:.windsurf/skills:$HOME/.codeium/windsurf/skills"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# 3. Agent Selection
|
|
138
|
+
echo ""
|
|
139
|
+
echo "Select Agents to install to (Space to select, Enter to confirm):"
|
|
140
|
+
# Simple multi-select implementation using arrays
|
|
141
|
+
selected_indices=()
|
|
142
|
+
while true; do
|
|
143
|
+
for i in "${!AGENTS[@]}"; do
|
|
144
|
+
agent_info="${AGENTS[$i]}"
|
|
145
|
+
agent_name="${agent_info%%:*}"
|
|
146
|
+
|
|
147
|
+
# Check if selected
|
|
148
|
+
if [[ " ${selected_indices[*]} " =~ " $i " ]]; then
|
|
149
|
+
mark="[*]"
|
|
150
|
+
else
|
|
151
|
+
mark="[ ]"
|
|
152
|
+
fi
|
|
153
|
+
echo "$i) $mark $agent_name"
|
|
154
|
+
done
|
|
155
|
+
|
|
156
|
+
echo "a) Select All"
|
|
157
|
+
echo "d) Done"
|
|
158
|
+
read -p "Select option: " choice
|
|
159
|
+
|
|
160
|
+
if [[ "$choice" == "d" ]]; then
|
|
161
|
+
break
|
|
162
|
+
elif [[ "$choice" == "a" ]]; then
|
|
163
|
+
selected_indices=()
|
|
164
|
+
for i in "${!AGENTS[@]}"; do
|
|
165
|
+
selected_indices+=("$i")
|
|
166
|
+
done
|
|
167
|
+
elif [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 0 ] && [ "$choice" -lt "${#AGENTS[@]}" ]; then
|
|
168
|
+
if [[ " ${selected_indices[*]} " =~ " $choice " ]]; then
|
|
169
|
+
# Deselect
|
|
170
|
+
new_indices=()
|
|
171
|
+
for idx in "${selected_indices[@]}"; do
|
|
172
|
+
[[ "$idx" != "$choice" ]] && new_indices+=("$idx")
|
|
173
|
+
done
|
|
174
|
+
selected_indices=("${new_indices[@]}")
|
|
175
|
+
else
|
|
176
|
+
# Select
|
|
177
|
+
selected_indices+=("$choice")
|
|
178
|
+
fi
|
|
179
|
+
else
|
|
180
|
+
echo "Invalid choice."
|
|
181
|
+
fi
|
|
182
|
+
echo "------------------------"
|
|
183
|
+
done
|
|
184
|
+
|
|
185
|
+
if [ ${#selected_indices[@]} -eq 0 ]; then
|
|
186
|
+
print_warning "No agents selected. Exiting."
|
|
187
|
+
exit 0
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# 4. Process Each Selected Agent
|
|
191
|
+
for idx in "${selected_indices[@]}"; do
|
|
192
|
+
agent_raw="${AGENTS[$idx]}"
|
|
193
|
+
IFS=':' read -r agent_name project_dir global_dir <<< "$agent_raw"
|
|
194
|
+
|
|
195
|
+
echo ""
|
|
196
|
+
print_info "Configuring for \033[1;36m$agent_name\033[0m..."
|
|
197
|
+
|
|
198
|
+
# Scope Selection
|
|
199
|
+
echo "Select Scope:"
|
|
200
|
+
echo "1) Project ($project_dir)"
|
|
201
|
+
echo "2) Global ($global_dir)"
|
|
202
|
+
echo "3) Both"
|
|
203
|
+
echo "s) Skip"
|
|
204
|
+
read -p "Choice [1-3]: " scope_choice
|
|
205
|
+
|
|
206
|
+
targets=()
|
|
207
|
+
case $scope_choice in
|
|
208
|
+
1) targets+=("$project_dir") ;;
|
|
209
|
+
2) targets+=("$global_dir") ;;
|
|
210
|
+
3) targets+=("$project_dir" "$global_dir") ;;
|
|
211
|
+
s|S) continue ;;
|
|
212
|
+
*) print_warning "Invalid choice, skipping $agent_name"; continue ;;
|
|
213
|
+
esac
|
|
214
|
+
|
|
215
|
+
for target_base in "${targets[@]}"; do
|
|
216
|
+
# Resolve path expansion if needed (already expanded in definition for Global, Project is relative)
|
|
217
|
+
if [[ "$target_base" == /* ]]; then
|
|
218
|
+
# Absolute path (Global)
|
|
219
|
+
target_dir="$target_base"
|
|
220
|
+
else
|
|
221
|
+
# Relative path (Project) - assume current dir is project root
|
|
222
|
+
target_dir="$(pwd)/$target_base"
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# Ensure target parent directory exists
|
|
226
|
+
if [ ! -d "$target_dir" ]; then
|
|
227
|
+
print_info "Creating directory: $target_dir"
|
|
228
|
+
mkdir -p "$target_dir"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
target_link="$target_dir/$SKILL_NAME"
|
|
232
|
+
|
|
233
|
+
# Check for existing link or directory
|
|
234
|
+
if [ -e "$target_link" ] || [ -L "$target_link" ]; then
|
|
235
|
+
print_warning "$target_link already exists."
|
|
236
|
+
read -p "Overwrite? (y/N): " overwrite
|
|
237
|
+
if [[ "$overwrite" =~ ^[yY]$ ]]; then
|
|
238
|
+
rm -rf "$target_link"
|
|
239
|
+
else
|
|
240
|
+
print_info "Skipping..."
|
|
241
|
+
continue
|
|
242
|
+
fi
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Create Symlink
|
|
246
|
+
ln -s "$SKILL_PATH" "$target_link"
|
|
247
|
+
if [ $? -eq 0 ]; then
|
|
248
|
+
print_success "Linked to $target_link"
|
|
249
|
+
else
|
|
250
|
+
print_error "Failed to link to $target_link"
|
|
251
|
+
fi
|
|
252
|
+
done
|
|
253
|
+
done
|
|
254
|
+
|
|
255
|
+
echo ""
|
|
256
|
+
print_success "All operations completed."
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skill-linker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interactive CLI to link AI Agent Skills to various agents (Claude, Copilot, Antigravity, Cursor, etc.)",
|
|
5
|
+
"main": "bin/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skill-linker": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"No tests yet\""
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"agent",
|
|
15
|
+
"skill",
|
|
16
|
+
"claude",
|
|
17
|
+
"copilot",
|
|
18
|
+
"cursor",
|
|
19
|
+
"antigravity",
|
|
20
|
+
"windsurf",
|
|
21
|
+
"opencode",
|
|
22
|
+
"codex",
|
|
23
|
+
"gemini",
|
|
24
|
+
"cli"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/raybird/skill-installer.git"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=14.0.0"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"bin/",
|
|
37
|
+
"link-skill.sh",
|
|
38
|
+
"README.md"
|
|
39
|
+
]
|
|
40
|
+
}
|