skillpull 0.4.6 → 0.4.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 +87 -22
- package/package.json +1 -1
- package/skillpull +110 -15
package/README.md
CHANGED
|
@@ -8,33 +8,51 @@ npx skillpull tianhaocui/ai-skills
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g skillpull
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or clone and install manually:
|
|
16
|
+
|
|
11
17
|
```bash
|
|
12
18
|
git clone https://github.com/tianhaocui/skillpull.git
|
|
13
19
|
cd skillpull
|
|
14
20
|
bash install.sh
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
## Quick Start
|
|
18
24
|
|
|
19
25
|
```bash
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
# Interactive setup: configure default repo and project scope
|
|
27
|
+
skillpull init
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
# Pull skills (uses registry if configured)
|
|
30
|
+
skillpull
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
# GitHub shortname
|
|
32
|
+
# Pull from a specific repo
|
|
27
33
|
skillpull user/repo
|
|
28
34
|
|
|
29
35
|
# Pull a specific skill
|
|
30
36
|
skillpull user/repo my-skill
|
|
37
|
+
```
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
skillpull list user/repo
|
|
39
|
+
## Commands
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
| Command | Description |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `skillpull init` | Interactive setup: default repo + project scope |
|
|
44
|
+
| `skillpull <source> [skill]` | Pull skills from a Git repo |
|
|
45
|
+
| `skillpull list [source]` | List available skills in a repo |
|
|
46
|
+
| `skillpull search <keyword>` | Search GitHub for skill repos |
|
|
47
|
+
| `skillpull update` | Update all installed skills to latest |
|
|
48
|
+
| `skillpull push [target-repo]` | Push local skills back to a remote repo |
|
|
49
|
+
| `skillpull installed` | Show locally installed skills |
|
|
50
|
+
| `skillpull remove <skill>` | Remove an installed skill |
|
|
51
|
+
| `skillpull registry <repo>` | Set or view default skill repo |
|
|
52
|
+
| `skillpull alias add <name> <url>` | Save a repo shortcut |
|
|
53
|
+
| `skillpull alias list` | List saved aliases |
|
|
54
|
+
| `skillpull alias rm <name>` | Remove an alias |
|
|
55
|
+
| `skillpull uninstall` | Remove skillpull from system |
|
|
38
56
|
|
|
39
57
|
## Source Formats
|
|
40
58
|
|
|
@@ -48,7 +66,7 @@ skillpull search coding-standards
|
|
|
48
66
|
|
|
49
67
|
## Targets
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
First run will show an interactive menu to select target tools. Use flags to skip the prompt:
|
|
52
70
|
|
|
53
71
|
```bash
|
|
54
72
|
skillpull user/repo --claude # .claude/skills/ (default)
|
|
@@ -58,28 +76,60 @@ skillpull user/repo --cursor # .cursor/rules/ (auto-converts to .mdc)
|
|
|
58
76
|
skillpull user/repo --all # All of the above
|
|
59
77
|
```
|
|
60
78
|
|
|
61
|
-
|
|
79
|
+
Global (user-level) install:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
skillpull user/repo --global # ~/.claude/skills/, ~/.codex/skills/, etc.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Project Scope
|
|
86
|
+
|
|
87
|
+
Skill repos can organize skills into common and project-specific folders:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
skill-repo/
|
|
91
|
+
skills/ # common, pulled for all projects
|
|
92
|
+
common-skill-a/SKILL.md
|
|
93
|
+
common-skill-b/SKILL.md
|
|
94
|
+
my-app/ # project-specific
|
|
95
|
+
app-skill/SKILL.md
|
|
96
|
+
backend/ # project-specific
|
|
97
|
+
api-skill/SKILL.md
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The `skills/` folder at the repo root holds shared skills. Other folders are project-specific — only pulled when matching the configured project name.
|
|
62
101
|
|
|
63
|
-
|
|
102
|
+
Configure a default project during `skillpull init`, or pass it per-command:
|
|
64
103
|
|
|
65
104
|
```bash
|
|
66
|
-
#
|
|
67
|
-
skillpull
|
|
105
|
+
# Uses default project from config
|
|
106
|
+
skillpull
|
|
68
107
|
|
|
69
|
-
#
|
|
70
|
-
skillpull my-
|
|
108
|
+
# Override for a specific pull
|
|
109
|
+
skillpull user/repo --project my-app
|
|
71
110
|
```
|
|
72
111
|
|
|
73
|
-
|
|
112
|
+
Both `skills/` (common) and the project subfolder's skills are pulled together.
|
|
74
113
|
|
|
75
|
-
|
|
114
|
+
## Registry & Aliases
|
|
76
115
|
|
|
77
116
|
```bash
|
|
117
|
+
# Set a default repo so you can pull by skill name alone
|
|
118
|
+
skillpull registry tianhaocui/ai-skills
|
|
119
|
+
skillpull my-skill # pulls from registry
|
|
120
|
+
|
|
121
|
+
# Save frequently used repos as aliases
|
|
78
122
|
skillpull alias add work git@github.com:myorg/skills.git
|
|
79
123
|
skillpull @work my-skill
|
|
124
|
+
```
|
|
80
125
|
|
|
81
|
-
|
|
82
|
-
|
|
126
|
+
## Push
|
|
127
|
+
|
|
128
|
+
Push local skills back to a remote Git repo:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
skillpull push user/repo
|
|
132
|
+
skillpull push # uses registry if set
|
|
83
133
|
```
|
|
84
134
|
|
|
85
135
|
## Options
|
|
@@ -89,6 +139,7 @@ skillpull alias rm work
|
|
|
89
139
|
| `--global, -g` | Install to user-level directory |
|
|
90
140
|
| `--path <dir>` | Install to a custom directory |
|
|
91
141
|
| `--branch <ref>` | Use a specific branch/tag/commit |
|
|
142
|
+
| `--project <name>` | Include project-specific skills (defaults to `init` config) |
|
|
92
143
|
| `--force, -f` | Overwrite existing skills |
|
|
93
144
|
| `--dry-run` | Preview without making changes |
|
|
94
145
|
| `--quiet, -q` | Suppress non-error output |
|
|
@@ -115,6 +166,20 @@ description: What this skill does
|
|
|
115
166
|
Skill content here...
|
|
116
167
|
```
|
|
117
168
|
|
|
169
|
+
## Config
|
|
170
|
+
|
|
171
|
+
Config is stored at `~/.config/skillpull/config.json`:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"aliases": {},
|
|
176
|
+
"registry": "https://github.com/user/repo",
|
|
177
|
+
"project": "my-app"
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Installed skills are tracked per-directory in `.skillpull.json` manifests.
|
|
182
|
+
|
|
118
183
|
## Requirements
|
|
119
184
|
|
|
120
185
|
- macOS, Linux, or Windows (via [WSL](https://learn.microsoft.com/en-us/windows/wsl/) or [Git Bash](https://gitforwindows.org/))
|
package/package.json
CHANGED
package/skillpull
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
VERSION="0.4.
|
|
4
|
+
VERSION="0.4.8"
|
|
5
5
|
MANIFEST_FILE=".skillpull.json"
|
|
6
6
|
TMPDIR_PREFIX="skillpull"
|
|
7
7
|
CONFIG_DIR="$HOME/.config/skillpull"
|
|
@@ -166,7 +166,11 @@ make_tmp() { _TMPDIR="$(mktemp -d "/tmp/${TMPDIR_PREFIX}.XXXXXX")"; }
|
|
|
166
166
|
# ── Config helpers ──
|
|
167
167
|
ensure_config() {
|
|
168
168
|
mkdir -p "$CONFIG_DIR"
|
|
169
|
-
[[ -f "$CONFIG_FILE" ]] || echo '{"aliases":{},"registry":""}' > "$CONFIG_FILE"
|
|
169
|
+
[[ -f "$CONFIG_FILE" ]] || echo '{"aliases":{},"registry":"","project":""}' > "$CONFIG_FILE"
|
|
170
|
+
# Migrate: add "project" key if missing
|
|
171
|
+
if ! grep -q '"project"' "$CONFIG_FILE" 2>/dev/null; then
|
|
172
|
+
sedi 's/}$/,"project":""}/' "$CONFIG_FILE"
|
|
173
|
+
fi
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
read_config_key() {
|
|
@@ -246,6 +250,18 @@ set_registry() {
|
|
|
246
250
|
info "Default registry set to: $url"
|
|
247
251
|
}
|
|
248
252
|
|
|
253
|
+
set_project() {
|
|
254
|
+
local name="$1"
|
|
255
|
+
ensure_config
|
|
256
|
+
local esc_name; esc_name="$(_json_escape "$name")"
|
|
257
|
+
sedi "s|\"project\":\"[^\"]*\"|\"project\":\"${esc_name}\"|" "$CONFIG_FILE"
|
|
258
|
+
if [[ -n "$name" ]]; then
|
|
259
|
+
info "Default project set to: $name"
|
|
260
|
+
else
|
|
261
|
+
info "Default project cleared"
|
|
262
|
+
fi
|
|
263
|
+
}
|
|
264
|
+
|
|
249
265
|
# ── URL resolution ──
|
|
250
266
|
# Supports: full URL, user/repo shortname, @alias, bare skill name (from registry)
|
|
251
267
|
resolve_repo_url() {
|
|
@@ -309,6 +325,31 @@ discover_skills() {
|
|
|
309
325
|
-exec dirname {} \; 2>/dev/null | sort -u
|
|
310
326
|
}
|
|
311
327
|
|
|
328
|
+
# Scoped discovery: skills/ folder (common) + optional project-specific skills
|
|
329
|
+
discover_skills_scoped() {
|
|
330
|
+
local dir="$1" project="${2:-}"
|
|
331
|
+
local results=()
|
|
332
|
+
|
|
333
|
+
# Common skills: $dir/skills/*/SKILL.md
|
|
334
|
+
local d
|
|
335
|
+
for d in "$dir/skills"/*/SKILL.md; do
|
|
336
|
+
[[ -f "$d" ]] || continue
|
|
337
|
+
local skill_dir; skill_dir="$(dirname "$d")"
|
|
338
|
+
results+=("$skill_dir")
|
|
339
|
+
done
|
|
340
|
+
|
|
341
|
+
# Project-specific skills: $dir/$project/*/SKILL.md
|
|
342
|
+
if [[ -n "$project" ]]; then
|
|
343
|
+
for d in "$dir/$project"/*/SKILL.md; do
|
|
344
|
+
[[ -f "$d" ]] || continue
|
|
345
|
+
local skill_dir; skill_dir="$(dirname "$d")"
|
|
346
|
+
results+=("$skill_dir")
|
|
347
|
+
done
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
printf '%s\n' "${results[@]}" 2>/dev/null | sort -u
|
|
351
|
+
}
|
|
352
|
+
|
|
312
353
|
# ── SKILL.md → .mdc conversion (for Cursor) ──
|
|
313
354
|
convert_skill_to_mdc() {
|
|
314
355
|
local skill_md="$1" output_file="$2"
|
|
@@ -452,7 +493,7 @@ get_branch() {
|
|
|
452
493
|
# ── Commands ──
|
|
453
494
|
|
|
454
495
|
cmd_pull() {
|
|
455
|
-
local repo_url="$1" skill_filter="${2:-}" target_dir="$3" force="${4:-0}" dry_run="${5:-0}" agent="${6:-claude}"
|
|
496
|
+
local repo_url="$1" skill_filter="${2:-}" target_dir="$3" force="${4:-0}" dry_run="${5:-0}" agent="${6:-claude}" project="${7:-}"
|
|
456
497
|
local fmt; fmt="$(agent_format "$agent")"
|
|
457
498
|
make_tmp
|
|
458
499
|
local tmpdir="$_TMPDIR"
|
|
@@ -464,9 +505,17 @@ cmd_pull() {
|
|
|
464
505
|
local branch; branch="$(get_branch "$tmpdir")"
|
|
465
506
|
local skills=()
|
|
466
507
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
508
|
+
# Use scoped discovery if project is set, otherwise discover all
|
|
509
|
+
if [[ -n "$project" ]]; then
|
|
510
|
+
while IFS= read -r d; do
|
|
511
|
+
[[ -n "$d" ]] && skills+=("$d")
|
|
512
|
+
done < <(discover_skills_scoped "$tmpdir" "$project")
|
|
513
|
+
[[ -n "$project" ]] && dim "Project scope: $project"
|
|
514
|
+
else
|
|
515
|
+
while IFS= read -r d; do
|
|
516
|
+
[[ -n "$d" ]] && skills+=("$d")
|
|
517
|
+
done < <(discover_skills "$tmpdir")
|
|
518
|
+
fi
|
|
470
519
|
|
|
471
520
|
if [[ ${#skills[@]} -eq 0 ]]; then
|
|
472
521
|
err "No skills found in repository"
|
|
@@ -551,7 +600,7 @@ cmd_pull() {
|
|
|
551
600
|
}
|
|
552
601
|
|
|
553
602
|
cmd_list() {
|
|
554
|
-
local repo_url="$1"
|
|
603
|
+
local repo_url="$1" project="${2:-}"
|
|
555
604
|
make_tmp
|
|
556
605
|
local tmpdir="$_TMPDIR"
|
|
557
606
|
|
|
@@ -559,9 +608,16 @@ cmd_list() {
|
|
|
559
608
|
clone_repo "$repo_url" "${BRANCH:-}" "$tmpdir" || return 1
|
|
560
609
|
|
|
561
610
|
local skills=()
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
611
|
+
if [[ -n "$project" ]]; then
|
|
612
|
+
while IFS= read -r d; do
|
|
613
|
+
[[ -n "$d" ]] && skills+=("$d")
|
|
614
|
+
done < <(discover_skills_scoped "$tmpdir" "$project")
|
|
615
|
+
dim "Project scope: $project"
|
|
616
|
+
else
|
|
617
|
+
while IFS= read -r d; do
|
|
618
|
+
[[ -n "$d" ]] && skills+=("$d")
|
|
619
|
+
done < <(discover_skills "$tmpdir")
|
|
620
|
+
fi
|
|
565
621
|
|
|
566
622
|
if [[ ${#skills[@]} -eq 0 ]]; then
|
|
567
623
|
err "No skills found in repository"
|
|
@@ -923,6 +979,33 @@ cmd_init() {
|
|
|
923
979
|
set_registry "$resolved"
|
|
924
980
|
fi
|
|
925
981
|
|
|
982
|
+
# ── Project scope ──
|
|
983
|
+
local current_project; current_project="$(read_config_key "project")"
|
|
984
|
+
echo ""
|
|
985
|
+
printf " ${CYAN}Project scope${RESET} ${DIM}(optional)${RESET}\n"
|
|
986
|
+
printf " ${DIM}If your skill repo has project-specific subfolders, set a default here.${RESET}\n"
|
|
987
|
+
printf " ${DIM}Skills from both root level and the project subfolder will be pulled.${RESET}\n\n"
|
|
988
|
+
|
|
989
|
+
if [[ -n "$current_project" ]]; then
|
|
990
|
+
printf " Current: ${GREEN}%s${RESET}\n\n" "$current_project"
|
|
991
|
+
printf " Project name (Enter to keep, 'none' to clear): "
|
|
992
|
+
else
|
|
993
|
+
printf " Project name (Enter to skip): "
|
|
994
|
+
fi
|
|
995
|
+
|
|
996
|
+
read -r project_input
|
|
997
|
+
project_input="$(echo "$project_input" | tr -d '[:cntrl:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
998
|
+
|
|
999
|
+
if [[ -z "$project_input" ]]; then
|
|
1000
|
+
if [[ -n "$current_project" ]]; then
|
|
1001
|
+
dim "Keeping current project: $current_project"
|
|
1002
|
+
fi
|
|
1003
|
+
elif [[ "$project_input" == "none" ]]; then
|
|
1004
|
+
set_project ""
|
|
1005
|
+
else
|
|
1006
|
+
set_project "$project_input"
|
|
1007
|
+
fi
|
|
1008
|
+
|
|
926
1009
|
echo ""
|
|
927
1010
|
printf " ${CYAN}What's next:${RESET}\n"
|
|
928
1011
|
printf " skillpull list List available skills\n"
|
|
@@ -1034,6 +1117,8 @@ OPTIONS:
|
|
|
1034
1117
|
--global, -g Install to user-level directory
|
|
1035
1118
|
--path <dir> Install to a custom directory
|
|
1036
1119
|
--branch <ref> Use a specific branch/tag/commit
|
|
1120
|
+
--project <name> Include project-specific skills from <name>/ subfolder
|
|
1121
|
+
(defaults to value set in 'skillpull init')
|
|
1037
1122
|
--force, -f Overwrite existing skills
|
|
1038
1123
|
--dry-run Preview without making changes
|
|
1039
1124
|
--quiet, -q Suppress non-error output
|
|
@@ -1041,7 +1126,7 @@ OPTIONS:
|
|
|
1041
1126
|
--version Show version
|
|
1042
1127
|
|
|
1043
1128
|
EXAMPLES:
|
|
1044
|
-
skillpull init # Setup default skill repo
|
|
1129
|
+
skillpull init # Setup default skill repo & project
|
|
1045
1130
|
skillpull tianhaocui/ai-skills # GitHub shortname
|
|
1046
1131
|
skillpull @work plan-first-development --global # From alias
|
|
1047
1132
|
skillpull search coding-standards # Search GitHub
|
|
@@ -1051,13 +1136,14 @@ EXAMPLES:
|
|
|
1051
1136
|
skillpull tianhaocui/ai-skills --cursor # Convert to .mdc
|
|
1052
1137
|
skillpull update # Update all installed skills
|
|
1053
1138
|
skillpull push tianhaocui/ai-skills # Push local skills to remote
|
|
1139
|
+
skillpull user/repo --project my-app # Common + project-specific skills
|
|
1054
1140
|
HELP
|
|
1055
1141
|
}
|
|
1056
1142
|
|
|
1057
1143
|
# ── Argument parsing & dispatch ──
|
|
1058
1144
|
main() {
|
|
1059
1145
|
local cmd="" repo_url="" skill_name="" custom_path=""
|
|
1060
|
-
local force=0 dry_run=0 is_global=0 use_all=0 agent_explicit=0
|
|
1146
|
+
local force=0 dry_run=0 is_global=0 use_all=0 agent_explicit=0 project_name=""
|
|
1061
1147
|
local agents=()
|
|
1062
1148
|
local alias_args=()
|
|
1063
1149
|
QUIET=0; BRANCH=""
|
|
@@ -1092,6 +1178,7 @@ main() {
|
|
|
1092
1178
|
--global|-g) is_global=1; shift ;;
|
|
1093
1179
|
--path) [[ $# -lt 2 ]] && { err "--path requires a directory"; exit 1; }; custom_path="$2"; shift 2 ;;
|
|
1094
1180
|
--branch) [[ $# -lt 2 ]] && { err "--branch requires a ref"; exit 1; }; BRANCH="$2"; shift 2 ;;
|
|
1181
|
+
--project) [[ $# -lt 2 ]] && { err "--project requires a name"; exit 1; }; project_name="$2"; shift 2 ;;
|
|
1095
1182
|
--force|-f) force=1; shift ;;
|
|
1096
1183
|
--dry-run) dry_run=1; shift ;;
|
|
1097
1184
|
--quiet|-q) QUIET=1; shift ;;
|
|
@@ -1140,7 +1227,7 @@ main() {
|
|
|
1140
1227
|
if [[ "$use_all" == "1" ]]; then
|
|
1141
1228
|
agents=("claude" "codex" "kiro" "cursor")
|
|
1142
1229
|
elif [[ ${#agents[@]} -eq 0 ]]; then
|
|
1143
|
-
if [[ "$agent_explicit" == "0" && -t 0 && "$cmd"
|
|
1230
|
+
if [[ "$agent_explicit" == "0" && -t 0 && ( "$cmd" == "pull" || "$cmd" == "" || "$cmd" == "remove" || "$cmd" == "push" ) ]]; then
|
|
1144
1231
|
# Interactive terminal, no agent flag -> let user choose
|
|
1145
1232
|
select_menu "Install to which tools?" "claude (.claude/skills)" "codex (.codex/skills)" "kiro (.kiro/skills)" "cursor (.cursor/rules)"
|
|
1146
1233
|
if [[ ${#SELECTED_ITEMS[@]} -eq 0 ]]; then
|
|
@@ -1158,6 +1245,10 @@ main() {
|
|
|
1158
1245
|
|
|
1159
1246
|
case "${cmd:-}" in
|
|
1160
1247
|
pull|"")
|
|
1248
|
+
# Fall back to configured project if --project not specified
|
|
1249
|
+
if [[ -z "$project_name" ]]; then
|
|
1250
|
+
project_name="$(read_config_key "project")"
|
|
1251
|
+
fi
|
|
1161
1252
|
if [[ -z "$repo_url" ]]; then
|
|
1162
1253
|
# No source given, try registry
|
|
1163
1254
|
local reg; reg="$(read_config_key "registry")"
|
|
@@ -1177,10 +1268,14 @@ main() {
|
|
|
1177
1268
|
local target_dir
|
|
1178
1269
|
target_dir="$(resolve_target_dir "$agent" "$is_global" "$custom_path")" || continue
|
|
1179
1270
|
dim "Target: $agent -> $target_dir"
|
|
1180
|
-
cmd_pull "$resolved" "$skill_name" "$target_dir" "$force" "$dry_run" "$agent"
|
|
1271
|
+
cmd_pull "$resolved" "$skill_name" "$target_dir" "$force" "$dry_run" "$agent" "$project_name"
|
|
1181
1272
|
done
|
|
1182
1273
|
;;
|
|
1183
1274
|
list)
|
|
1275
|
+
# Fall back to configured project if --project not specified
|
|
1276
|
+
if [[ -z "$project_name" ]]; then
|
|
1277
|
+
project_name="$(read_config_key "project")"
|
|
1278
|
+
fi
|
|
1184
1279
|
if [[ -z "$repo_url" ]]; then
|
|
1185
1280
|
local reg; reg="$(read_config_key "registry")"
|
|
1186
1281
|
if [[ -z "$reg" ]]; then
|
|
@@ -1191,7 +1286,7 @@ main() {
|
|
|
1191
1286
|
repo_url="$reg"
|
|
1192
1287
|
fi
|
|
1193
1288
|
local resolved; resolved="$(resolve_repo_url "$repo_url")" || exit 1
|
|
1194
|
-
cmd_list "$resolved"
|
|
1289
|
+
cmd_list "$resolved" "$project_name"
|
|
1195
1290
|
;;
|
|
1196
1291
|
search)
|
|
1197
1292
|
cmd_search "${skill_name:-}"
|