skill-linker 1.0.0 → 2.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.
Files changed (3) hide show
  1. package/README.md +52 -5
  2. package/link-skill.sh +163 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -41,24 +41,71 @@ Usage: link-skill.sh [OPTIONS] [SKILL_PATH]
41
41
 
42
42
  Options:
43
43
  --from <github_url> 從 GitHub Clone Skill 後再連結
44
+ --list 列出已 Clone 的 Repos 並選擇 Skills
44
45
  --help 顯示說明
45
46
 
46
47
  Examples:
47
48
  ./link-skill.sh # 互動式選擇
49
+ ./link-skill.sh --list # 瀏覽已 Clone 的 Repos
48
50
  ./link-skill.sh /path/to/skill # 指定本地 Skill
49
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
50
53
  ```
51
54
 
52
- ## 📂 Skill Library
55
+ ### Multi-Skill Repo 支援
56
+
57
+ 對於包含多個 Skills 的 Repo(如 `anthropics/skills`),腳本會:
58
+ 1. 自動偵測 `skills/` 子目錄
59
+ 2. 列出所有可用的 Skills 讓您選擇
60
+ 3. 或者您可以直接在 URL 中指定子路徑(如 `/tree/main/skills/pdf`)
61
+
62
+ ### 📋 List Mode - 瀏覽已 Clone 的 Repos
63
+
64
+ 使用 `--list` 參數可以瀏覽 Skill Library 中已 clone 的所有 repos:
65
+
66
+ ```bash
67
+ npx skill-linker --list
68
+ ```
69
+
70
+ 操作流程:
71
+ 1. 顯示所有已 clone 的 repos(以 `owner/repo` 格式)
72
+ 2. 選擇要使用的 repo
73
+ 3. 如果該 repo 包含多個 skills,會列出讓您選擇
74
+ 4. 選擇後繼續正常的 Agent 安裝流程
75
+
76
+ 這對於管理多個已下載的 skill repos 特別有用!
53
77
 
54
- 建議將您的 Public Skills 統一存放在 `~/Documents/AgentSkills`:
78
+ ## 📦 推薦的 Public Skill Repos
79
+
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...) |
55
84
 
56
85
  ```bash
57
- mkdir -p ~/Documents/AgentSkills
58
- cd ~/Documents/AgentSkills
59
- git clone https://github.com/user/my-awesome-skill.git
86
+ # 安裝 Anthropic 的 PDF Skill
87
+ npx skill-linker --from https://github.com/anthropics/skills/tree/main/skills/pdf
88
+
89
+ # 安裝 obra 的所有開發 Skills (可互動選擇)
90
+ npx skill-linker --from https://github.com/obra/superpowers
60
91
  ```
61
92
 
93
+ ## 📂 Skill Library
94
+
95
+ 使用 `--from` 參數時,Skills 會自動存放到 `~/Documents/AgentSkills`,並以 **owner/repo** 結構分層:
96
+
97
+ ```
98
+ ~/Documents/AgentSkills/
99
+ ├── anthropics/
100
+ │ └── skills/ # https://github.com/anthropics/skills
101
+ ├── obra/
102
+ │ └── superpowers/ # https://github.com/obra/superpowers
103
+ └── your-org/
104
+ └── your-skill/ # https://github.com/your-org/your-skill
105
+ ```
106
+
107
+ 這種命名空間結構可避免不同帳號擁有相同 repo 名稱時的衝突。
108
+
62
109
  腳本會自動偵測此目錄並列出可用的 Skills。
63
110
 
64
111
  ## 🛠️ 支援的 Agent 與路徑
package/link-skill.sh CHANGED
@@ -5,6 +5,7 @@
5
5
  DEFAULT_LIB_PATH="$HOME/Documents/AgentSkills"
6
6
  SKILL_PATH=""
7
7
  FROM_URL=""
8
+ LIST_MODE=false
8
9
 
9
10
  # Helper function for colored output
10
11
  print_info() { echo -e "\033[1;34m[INFO]\033[0m $1"; }
@@ -18,16 +19,95 @@ show_help() {
18
19
  echo ""
19
20
  echo "Options:"
20
21
  echo " --from <github_url> Clone skill from GitHub URL first, then link"
22
+ echo " --list List cloned repos and select skills to link"
21
23
  echo " --help Show this help message"
22
24
  echo ""
23
25
  echo "Examples:"
24
26
  echo " ./link-skill.sh # Interactive selection from library"
25
27
  echo " ./link-skill.sh /path/to/skill # Link specific local skill"
26
28
  echo " ./link-skill.sh --from https://github.com/user/my-skill"
29
+ echo " ./link-skill.sh --from https://github.com/anthropics/skills/tree/main/skills/pdf"
30
+ echo ""
31
+ echo "Notes:"
32
+ echo " - If the cloned repo has a 'skills/' subdirectory, you can pick a specific skill"
33
+ echo " - GitHub URLs with /tree/branch/path are supported for direct subpath access"
27
34
  echo ""
28
35
  exit 0
29
36
  }
30
37
 
38
+ # List repos function
39
+ list_repos() {
40
+ if [ ! -d "$DEFAULT_LIB_PATH" ]; then
41
+ print_error "Skill Library not found: $DEFAULT_LIB_PATH"
42
+ print_info "Use --from <github_url> to clone skills first."
43
+ exit 1
44
+ fi
45
+
46
+ # Find all repos (owner/repo structure)
47
+ repos=()
48
+ repo_paths=()
49
+ for owner_dir in "$DEFAULT_LIB_PATH"/*/; do
50
+ [ -d "$owner_dir" ] || continue
51
+ owner=$(basename "$owner_dir")
52
+ for repo_dir in "$owner_dir"*/; do
53
+ [ -d "$repo_dir" ] || continue
54
+ repo=$(basename "$repo_dir")
55
+ repos+=("$owner/$repo")
56
+ repo_paths+=("$repo_dir")
57
+ done
58
+ done
59
+
60
+ if [ ${#repos[@]} -eq 0 ]; then
61
+ print_warning "No repos found in $DEFAULT_LIB_PATH"
62
+ print_info "Use --from <github_url> to clone skills first."
63
+ exit 0
64
+ fi
65
+
66
+ echo ""
67
+ print_info "Cloned Repos in Skill Library:"
68
+ echo ""
69
+ select repo_name in "${repos[@]}"; do
70
+ if [ -n "$repo_name" ]; then
71
+ idx=$((REPLY - 1))
72
+ selected_repo_path="${repo_paths[$idx]}"
73
+ break
74
+ else
75
+ echo "Invalid selection. Please try again."
76
+ fi
77
+ done
78
+
79
+ # Check for skills/ subdirectory
80
+ SKILLS_DIR="${selected_repo_path}skills"
81
+ if [ -d "$SKILLS_DIR" ]; then
82
+ print_info "Skills in $repo_name:"
83
+ sub_skills=("$SKILLS_DIR"/*/)
84
+ if [ ${#sub_skills[@]} -gt 0 ]; then
85
+ sub_skill_names=()
86
+ for s in "${sub_skills[@]}"; do
87
+ [ -d "$s" ] && sub_skill_names+=("$(basename "$s")")
88
+ done
89
+
90
+ echo ""
91
+ select sub_skill_name in "${sub_skill_names[@]}" "Link entire repo"; do
92
+ if [ -n "$sub_skill_name" ]; then
93
+ if [ "$sub_skill_name" == "Link entire repo" ]; then
94
+ SKILL_PATH="${selected_repo_path%/}"
95
+ else
96
+ SKILL_PATH="$SKILLS_DIR/$sub_skill_name"
97
+ fi
98
+ break
99
+ else
100
+ echo "Invalid selection. Please try again."
101
+ fi
102
+ done
103
+ else
104
+ SKILL_PATH="${selected_repo_path%/}"
105
+ fi
106
+ else
107
+ SKILL_PATH="${selected_repo_path%/}"
108
+ fi
109
+ }
110
+
31
111
  # Parse arguments
32
112
  while [[ $# -gt 0 ]]; do
33
113
  case $1 in
@@ -35,6 +115,10 @@ while [[ $# -gt 0 ]]; do
35
115
  FROM_URL="$2"
36
116
  shift 2
37
117
  ;;
118
+ --list|-l)
119
+ LIST_MODE=true
120
+ shift
121
+ ;;
38
122
  --help|-h)
39
123
  show_help
40
124
  ;;
@@ -45,11 +129,42 @@ while [[ $# -gt 0 ]]; do
45
129
  esac
46
130
  done
47
131
 
132
+ # Handle --list mode
133
+ if [ "$LIST_MODE" = true ]; then
134
+ list_repos
135
+ fi
136
+
48
137
  # 1. Handle --from flag: Clone from GitHub
49
138
  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"
139
+ # Parse GitHub URL - check for /tree/branch/path format
140
+ SUBPATH=""
141
+ CLEAN_URL="$FROM_URL"
142
+
143
+ if [[ "$FROM_URL" =~ (.+)/tree/([^/]+)/(.+)$ ]]; then
144
+ # URL contains /tree/branch/path - extract subpath
145
+ BASE_REPO="${BASH_REMATCH[1]}"
146
+ BRANCH="${BASH_REMATCH[2]}"
147
+ SUBPATH="${BASH_REMATCH[3]}"
148
+ CLEAN_URL="$BASE_REPO"
149
+ print_info "Detected subpath: $SUBPATH (branch: $BRANCH)"
150
+ fi
151
+
152
+ # Extract owner/repo from URL for namespaced storage
153
+ # Supports: https://github.com/owner/repo or https://github.com/owner/repo.git
154
+ if [[ "$CLEAN_URL" =~ github\.com[/:]([^/]+)/([^/]+)(\.git)?$ ]]; then
155
+ REPO_OWNER="${BASH_REMATCH[1]}"
156
+ REPO_NAME="${BASH_REMATCH[2]%.git}"
157
+ else
158
+ # Fallback: just use basename
159
+ REPO_OWNER=""
160
+ REPO_NAME=$(basename "$CLEAN_URL" .git)
161
+ fi
162
+
163
+ if [ -n "$REPO_OWNER" ]; then
164
+ TARGET_CLONE_PATH="$DEFAULT_LIB_PATH/$REPO_OWNER/$REPO_NAME"
165
+ else
166
+ TARGET_CLONE_PATH="$DEFAULT_LIB_PATH/$REPO_NAME"
167
+ fi
53
168
 
54
169
  # Ensure library directory exists
55
170
  mkdir -p "$DEFAULT_LIB_PATH"
@@ -62,8 +177,8 @@ if [ -n "$FROM_URL" ]; then
62
177
  git -C "$TARGET_CLONE_PATH" pull
63
178
  fi
64
179
  else
65
- print_info "Cloning $FROM_URL to $TARGET_CLONE_PATH..."
66
- git clone "$FROM_URL" "$TARGET_CLONE_PATH"
180
+ print_info "Cloning $CLEAN_URL to $TARGET_CLONE_PATH..."
181
+ git clone "$CLEAN_URL" "$TARGET_CLONE_PATH"
67
182
  if [ $? -ne 0 ]; then
68
183
  print_error "Failed to clone repository"
69
184
  exit 1
@@ -71,7 +186,49 @@ if [ -n "$FROM_URL" ]; then
71
186
  print_success "Clone completed!"
72
187
  fi
73
188
 
74
- SKILL_PATH="$TARGET_CLONE_PATH"
189
+ # If subpath was specified in URL, use it directly
190
+ if [ -n "$SUBPATH" ]; then
191
+ SKILL_PATH="$TARGET_CLONE_PATH/$SUBPATH"
192
+ if [ ! -d "$SKILL_PATH" ]; then
193
+ print_error "Subpath not found: $SKILL_PATH"
194
+ exit 1
195
+ fi
196
+ else
197
+ # Check if this is a multi-skill repo (has skills/ subdirectory)
198
+ SKILLS_DIR="$TARGET_CLONE_PATH/skills"
199
+ if [ -d "$SKILLS_DIR" ]; then
200
+ print_info "Detected multi-skill repository. Listing available skills..."
201
+
202
+ sub_skills=("$SKILLS_DIR"/*/)
203
+ if [ ${#sub_skills[@]} -eq 0 ]; then
204
+ print_warning "No skills found in $SKILLS_DIR, using repo root"
205
+ SKILL_PATH="$TARGET_CLONE_PATH"
206
+ else
207
+ # Extract skill names for display
208
+ sub_skill_names=()
209
+ for s in "${sub_skills[@]}"; do
210
+ sub_skill_names+=("$(basename "$s")")
211
+ done
212
+
213
+ echo ""
214
+ echo "Available Skills in this repo:"
215
+ select sub_skill_name in "${sub_skill_names[@]}" "Link entire repo"; do
216
+ if [ -n "$sub_skill_name" ]; then
217
+ if [ "$sub_skill_name" == "Link entire repo" ]; then
218
+ SKILL_PATH="$TARGET_CLONE_PATH"
219
+ else
220
+ SKILL_PATH="$SKILLS_DIR/$sub_skill_name"
221
+ fi
222
+ break
223
+ else
224
+ echo "Invalid selection. Please try again."
225
+ fi
226
+ done
227
+ fi
228
+ else
229
+ SKILL_PATH="$TARGET_CLONE_PATH"
230
+ fi
231
+ fi
75
232
  fi
76
233
 
77
234
  # 2. Determine Source Skill Path (if not already set by --from)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-linker",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
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": {