skillpull 0.4.7 → 0.5.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 +102 -27
  2. package/package.json +1 -1
  3. package/skillpull +143 -58
package/README.md CHANGED
@@ -9,33 +9,49 @@ npx skillpull tianhaocui/ai-skills
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- git clone https://github.com/tianhaocui/skillpull.git
13
- cd skillpull
14
- bash install.sh
12
+ npm i -g skillpull
15
13
  ```
16
14
 
17
- Or via npm:
15
+ Or clone and install manually:
18
16
 
19
17
  ```bash
20
- npm i -g skillpull
18
+ git clone https://github.com/tianhaocui/skillpull.git
19
+ cd skillpull
20
+ bash install.sh
21
21
  ```
22
22
 
23
- ## Usage
23
+ ## Quick Start
24
24
 
25
25
  ```bash
26
- # GitHub shortname
27
- skillpull user/repo
26
+ # 1. Setup global default skill repo
27
+ skillpull init --global
28
28
 
29
- # Pull a specific skill
30
- skillpull user/repo my-skill
29
+ # 2. Setup project config in current directory
30
+ skillpull init
31
31
 
32
- # List available skills in a repo
33
- skillpull list user/repo
34
-
35
- # Search GitHub for skill repos
36
- skillpull search coding-standards
32
+ # 3. Pull skills
33
+ skillpull
37
34
  ```
38
35
 
36
+ ## Commands
37
+
38
+ | Command | Description |
39
+ |---|---|
40
+ | `skillpull init --global` | Setup global default skill repo |
41
+ | `skillpull init` | Setup project config (`.skillpullrc` in current dir) |
42
+ | `skillpull <source> [skill]` | Pull skills from a Git repo |
43
+ | `skillpull list [source]` | List available skills in a repo |
44
+ | `skillpull search <keyword>` | Search GitHub for skill repos |
45
+ | `skillpull update` | Update all installed skills to latest |
46
+ | `skillpull push [target-repo]` | Push local skills back to a remote repo |
47
+ | `skillpull installed` | Show locally installed skills |
48
+ | `skillpull remove <skill>` | Remove an installed skill |
49
+ | `skillpull registry <repo>` | Set or view global default skill repo |
50
+ | `skillpull alias add <name> <url>` | Save a repo shortcut |
51
+ | `skillpull alias list` | List saved aliases |
52
+ | `skillpull alias rm <name>` | Remove an alias |
53
+ | `skillpull uninstall` | Remove skillpull from system |
54
+
39
55
  ## Source Formats
40
56
 
41
57
  | Format | Example |
@@ -48,7 +64,7 @@ skillpull search coding-standards
48
64
 
49
65
  ## Targets
50
66
 
51
- By default, skills install to `.claude/skills/`. Use flags to target other tools:
67
+ First run will show an interactive menu to select target tools. Use flags to skip the prompt:
52
68
 
53
69
  ```bash
54
70
  skillpull user/repo --claude # .claude/skills/ (default)
@@ -58,37 +74,70 @@ skillpull user/repo --cursor # .cursor/rules/ (auto-converts to .mdc)
58
74
  skillpull user/repo --all # All of the above
59
75
  ```
60
76
 
61
- ## Registry
77
+ Global (user-level) install:
78
+
79
+ ```bash
80
+ skillpull user/repo --global # ~/.claude/skills/, ~/.codex/skills/, etc.
81
+ ```
82
+
83
+ ## Project Scope
84
+
85
+ Skill repos can organize skills into common and project-specific folders:
86
+
87
+ ```
88
+ skill-repo/
89
+ skills/ # common, pulled for all projects
90
+ common-skill-a/SKILL.md
91
+ common-skill-b/SKILL.md
92
+ my-app/ # project-specific
93
+ app-skill/SKILL.md
94
+ backend/ # project-specific
95
+ api-skill/SKILL.md
96
+ ```
97
+
98
+ The `skills/` folder at the repo root holds shared skills. Other folders are project-specific — only pulled when matching the configured project name.
62
99
 
63
- Set a default skill repo so you can pull by skill name alone:
100
+ Configure a default project in `.skillpullrc` via `skillpull init`, or pass it per-command:
64
101
 
65
102
  ```bash
66
- # Set default registry
67
- skillpull registry tianhaocui/ai-skills
103
+ # Uses project from .skillpullrc
104
+ skillpull
68
105
 
69
- # Now just use the skill name
70
- skillpull my-skill
106
+ # Override for a specific pull
107
+ skillpull user/repo --project my-app
71
108
  ```
72
109
 
73
- ## Aliases
110
+ Both `skills/` (common) and the project subfolder's skills are pulled together.
74
111
 
75
- Save frequently used repos as aliases:
112
+ ## Registry & Aliases
76
113
 
77
114
  ```bash
115
+ # Set a default repo so you can pull by skill name alone
116
+ skillpull registry tianhaocui/ai-skills
117
+ skillpull my-skill # pulls from registry
118
+
119
+ # Save frequently used repos as aliases
78
120
  skillpull alias add work git@github.com:myorg/skills.git
79
121
  skillpull @work my-skill
122
+ ```
123
+
124
+ ## Push
125
+
126
+ Push local skills back to a remote Git repo:
80
127
 
81
- skillpull alias list
82
- skillpull alias rm work
128
+ ```bash
129
+ skillpull push user/repo
130
+ skillpull push # uses registry if set
83
131
  ```
84
132
 
85
133
  ## Options
86
134
 
87
135
  | Flag | Description |
88
136
  |---|---|
89
- | `--global, -g` | Install to user-level directory |
137
+ | `--global, -g` | Install to user-level directory / global init |
90
138
  | `--path <dir>` | Install to a custom directory |
91
139
  | `--branch <ref>` | Use a specific branch/tag/commit |
140
+ | `--project <name>` | Include project-specific skills (defaults to `.skillpullrc`) |
92
141
  | `--force, -f` | Overwrite existing skills |
93
142
  | `--dry-run` | Preview without making changes |
94
143
  | `--quiet, -q` | Suppress non-error output |
@@ -115,6 +164,32 @@ description: What this skill does
115
164
  Skill content here...
116
165
  ```
117
166
 
167
+ ## Config
168
+
169
+ Two-tier configuration: global + per-project.
170
+
171
+ Global (`~/.config/skillpull/config.json`):
172
+
173
+ ```json
174
+ {
175
+ "aliases": {},
176
+ "registry": "https://github.com/user/repo"
177
+ }
178
+ ```
179
+
180
+ Project (`.skillpullrc` in project root):
181
+
182
+ ```json
183
+ {
184
+ "registry": "https://github.com/team/project-skills",
185
+ "project": "my-app"
186
+ }
187
+ ```
188
+
189
+ Priority: `--project` CLI flag > `.skillpullrc` > global config.
190
+
191
+ Installed skills are tracked per-directory in `.skillpull.json` manifests.
192
+
118
193
  ## Requirements
119
194
 
120
195
  - macOS, Linux, or Windows (via [WSL](https://learn.microsoft.com/en-us/windows/wsl/) or [Git Bash](https://gitforwindows.org/))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillpull",
3
- "version": "0.4.7",
3
+ "version": "0.5.0",
4
4
  "description": "Sync AI agent skills from Git repositories to Claude, Codex, Kiro, and Cursor",
5
5
  "bin": {
6
6
  "skillpull": "./skillpull"
package/skillpull CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- VERSION="0.4.7"
4
+ VERSION="0.5.0"
5
5
  MANIFEST_FILE=".skillpull.json"
6
6
  TMPDIR_PREFIX="skillpull"
7
7
  CONFIG_DIR="$HOME/.config/skillpull"
@@ -166,10 +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":"","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"
169
+ [[ -f "$CONFIG_FILE" ]] || echo '{"aliases":{},"registry":""}' > "$CONFIG_FILE"
170
+ # Migrate: remove "project" key from global config (moved to .skillpullrc)
171
+ if grep -q '"project"' "$CONFIG_FILE" 2>/dev/null; then
172
+ sedi 's/,"project":"[^"]*"//g' "$CONFIG_FILE"
173
+ sedi 's/"project":"[^"]*",//g' "$CONFIG_FILE"
173
174
  fi
174
175
  }
175
176
 
@@ -250,16 +251,41 @@ set_registry() {
250
251
  info "Default registry set to: $url"
251
252
  }
252
253
 
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"
254
+ # ── Project-level config (.skillpullrc) ──
255
+ PROJECT_RC=".skillpullrc"
256
+
257
+ read_project_rc() {
258
+ local key="$1"
259
+ [[ ! -f "$PROJECT_RC" ]] && { echo ""; return 0; }
260
+ local val
261
+ val="$(grep -o "\"${key}\":\"[^\"]*\"" "$PROJECT_RC" 2>/dev/null | head -1 | \
262
+ sed "s/\"${key}\":\"//" | sed 's/"$//')" || true
263
+ echo "$val"
264
+ }
265
+
266
+ write_project_rc() {
267
+ local project="$1" registry="${2:-}"
268
+ local esc_project; esc_project="$(_json_escape "$project")"
269
+ local esc_registry; esc_registry="$(_json_escape "$registry")"
270
+ echo "{\"registry\":\"${esc_registry}\",\"project\":\"${esc_project}\"}" > "$PROJECT_RC"
271
+ }
272
+
273
+ # Resolve a config value: CLI > .skillpullrc > global config
274
+ resolve_config() {
275
+ local key="$1" cli_value="${2:-}"
276
+ # CLI flag takes priority
277
+ if [[ -n "$cli_value" ]]; then
278
+ echo "$cli_value"
279
+ return 0
262
280
  fi
281
+ # Project-level .skillpullrc
282
+ local rc_val; rc_val="$(read_project_rc "$key")"
283
+ if [[ -n "$rc_val" ]]; then
284
+ echo "$rc_val"
285
+ return 0
286
+ fi
287
+ # Global config
288
+ read_config_key "$key"
263
289
  }
264
290
 
265
291
  # ── URL resolution ──
@@ -292,7 +318,7 @@ resolve_repo_url() {
292
318
  fi
293
319
 
294
320
  # Bare name -> try registry
295
- local registry; registry="$(read_config_key "registry")"
321
+ local registry; registry="$(resolve_config "registry")"
296
322
  if [[ -n "$registry" ]]; then
297
323
  # Treat registry as a repo URL, skill name as filter
298
324
  echo "$registry"
@@ -325,14 +351,14 @@ discover_skills() {
325
351
  -exec dirname {} \; 2>/dev/null | sort -u
326
352
  }
327
353
 
328
- # Scoped discovery: root-level skills + optional project-specific skills
354
+ # Scoped discovery: skills/ folder (common) + optional project-specific skills
329
355
  discover_skills_scoped() {
330
356
  local dir="$1" project="${2:-}"
331
357
  local results=()
332
358
 
333
- # Root-level skills: $dir/*/SKILL.md (direct children only)
359
+ # Common skills: $dir/skills/*/SKILL.md
334
360
  local d
335
- for d in "$dir"/*/SKILL.md; do
361
+ for d in "$dir/skills"/*/SKILL.md; do
336
362
  [[ -f "$d" ]] || continue
337
363
  local skill_dir; skill_dir="$(dirname "$d")"
338
364
  results+=("$skill_dir")
@@ -813,7 +839,7 @@ cmd_push() {
813
839
 
814
840
  # Resolve push target repo
815
841
  if [[ -z "$repo_url" ]]; then
816
- repo_url="$(read_config_key "registry")"
842
+ repo_url="$(resolve_config "registry")"
817
843
  if [[ -z "$repo_url" ]]; then
818
844
  err "No target repo specified and no registry set."
819
845
  err "Usage: skillpull push <user/repo> or set registry first."
@@ -943,11 +969,11 @@ cmd_uninstall() {
943
969
  fi
944
970
  }
945
971
 
946
- cmd_init() {
972
+ cmd_init_global() {
947
973
  ensure_config
948
974
  local current; current="$(read_config_key "registry")"
949
975
 
950
- printf "\n ${CYAN}Welcome to skillpull!${RESET}\n\n"
976
+ printf "\n ${CYAN}Global setup${RESET}\n\n"
951
977
  printf " Set a default skill repository so you can pull skills by name.\n\n"
952
978
  printf " ${DIM}Examples:${RESET}\n"
953
979
  printf " ${DIM} tianhaocui/ai-skills GitHub shortname${RESET}\n"
@@ -971,7 +997,7 @@ cmd_init() {
971
997
  if [[ -n "$current" ]]; then
972
998
  dim "Keeping current registry: $current"
973
999
  else
974
- warn "No registry set. Run 'skillpull init' again when ready."
1000
+ warn "No registry set. Run 'skillpull init --global' again when ready."
975
1001
  return 0
976
1002
  fi
977
1003
  else
@@ -979,33 +1005,80 @@ cmd_init() {
979
1005
  set_registry "$resolved"
980
1006
  fi
981
1007
 
982
- # ── Project scope ──
983
- local current_project; current_project="$(read_config_key "project")"
984
1008
  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"
1009
+ printf " ${CYAN}What's next:${RESET}\n"
1010
+ printf " skillpull init Setup project config (.skillpullrc)\n"
1011
+ printf " skillpull list List available skills\n"
1012
+ printf " skillpull --help See all commands\n"
1013
+ echo ""
1014
+ }
1015
+
1016
+ cmd_init_project() {
1017
+ # Read existing .skillpullrc if present
1018
+ local current_project; current_project="$(read_project_rc "project")"
1019
+ local current_registry; current_registry="$(read_project_rc "registry")"
1020
+
1021
+ printf "\n ${CYAN}Project setup${RESET} ${DIM}(%s)${RESET}\n\n" "$PWD"
1022
+
1023
+ # ── Project name ──
1024
+ printf " ${DIM}Set the project name to match a subfolder in your skill repo.${RESET}\n"
1025
+ printf " ${DIM}Skills from both skills/ (common) and the project subfolder will be pulled.${RESET}\n\n"
988
1026
 
989
1027
  if [[ -n "$current_project" ]]; then
990
1028
  printf " Current: ${GREEN}%s${RESET}\n\n" "$current_project"
991
1029
  printf " Project name (Enter to keep, 'none' to clear): "
992
1030
  else
993
- printf " Project name (Enter to skip): "
1031
+ printf " Project name: "
994
1032
  fi
995
1033
 
996
1034
  read -r project_input
997
1035
  project_input="$(echo "$project_input" | tr -d '[:cntrl:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
998
1036
 
999
- if [[ -z "$project_input" ]]; then
1000
- if [[ -n "$current_project" ]]; then
1001
- dim "Keeping current project: $current_project"
1037
+ local new_project="$current_project"
1038
+ if [[ -n "$project_input" ]]; then
1039
+ if [[ "$project_input" == "none" ]]; then
1040
+ new_project=""
1041
+ else
1042
+ new_project="$project_input"
1002
1043
  fi
1003
- elif [[ "$project_input" == "none" ]]; then
1004
- set_project ""
1044
+ fi
1045
+
1046
+ # ── Optional registry override ──
1047
+ echo ""
1048
+ printf " ${DIM}Optional: override the global skill repo for this project.${RESET}\n"
1049
+ printf " ${DIM}Leave empty to use the global registry.${RESET}\n\n"
1050
+
1051
+ if [[ -n "$current_registry" ]]; then
1052
+ printf " Current: ${GREEN}%s${RESET}\n\n" "$current_registry"
1053
+ printf " Skill repo override (Enter to keep, 'none' to clear): "
1005
1054
  else
1006
- set_project "$project_input"
1055
+ printf " Skill repo override (Enter to skip): "
1007
1056
  fi
1008
1057
 
1058
+ read -r registry_input
1059
+ registry_input="$(echo "$registry_input" | tr -d '[:cntrl:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
1060
+
1061
+ local new_registry="$current_registry"
1062
+ if [[ -n "$registry_input" ]]; then
1063
+ if [[ "$registry_input" == "none" ]]; then
1064
+ new_registry=""
1065
+ else
1066
+ local resolved; resolved="$(resolve_repo_url "$registry_input")" || return 1
1067
+ new_registry="$resolved"
1068
+ fi
1069
+ fi
1070
+
1071
+ write_project_rc "$new_project" "$new_registry"
1072
+
1073
+ echo ""
1074
+ if [[ -n "$new_project" ]]; then
1075
+ info "Project: $new_project"
1076
+ fi
1077
+ if [[ -n "$new_registry" ]]; then
1078
+ info "Registry override: $new_registry"
1079
+ fi
1080
+ info "Saved to $PROJECT_RC"
1081
+
1009
1082
  echo ""
1010
1083
  printf " ${CYAN}What's next:${RESET}\n"
1011
1084
  printf " skillpull list List available skills\n"
@@ -1086,6 +1159,7 @@ usage() {
1086
1159
  skillpull — Sync AI agent skills from Git repositories
1087
1160
 
1088
1161
  USAGE:
1162
+ skillpull init --global
1089
1163
  skillpull init
1090
1164
  skillpull <source> [skill-name] [options]
1091
1165
  skillpull list <source>
@@ -1114,29 +1188,36 @@ TARGETS:
1114
1188
  --all Install to all supported targets at once
1115
1189
 
1116
1190
  OPTIONS:
1117
- --global, -g Install to user-level directory
1191
+ --global, -g Install to user-level directory / global init
1118
1192
  --path <dir> Install to a custom directory
1119
1193
  --branch <ref> Use a specific branch/tag/commit
1120
1194
  --project <name> Include project-specific skills from <name>/ subfolder
1121
- (defaults to value set in 'skillpull init')
1195
+ (defaults to value in .skillpullrc)
1122
1196
  --force, -f Overwrite existing skills
1123
1197
  --dry-run Preview without making changes
1124
1198
  --quiet, -q Suppress non-error output
1125
1199
  --help, -h Show this help
1126
1200
  --version Show version
1127
1201
 
1202
+ CONFIG:
1203
+ Global: ~/.config/skillpull/config.json (registry, aliases)
1204
+ Project: .skillpullrc (project name, optional registry override)
1205
+
1206
+ Resolution order: CLI flags > .skillpullrc > global config
1207
+
1128
1208
  EXAMPLES:
1129
- skillpull init # Setup default skill repo & project
1130
- skillpull tianhaocui/ai-skills # GitHub shortname
1131
- skillpull @work plan-first-development --global # From alias
1132
- skillpull search coding-standards # Search GitHub
1133
- skillpull alias add work git@github.com:me/skills # Save alias
1134
- skillpull registry tianhaocui/ai-skills # Set default source
1135
- skillpull tianhaocui/ai-skills --all # All tools at once
1136
- skillpull tianhaocui/ai-skills --cursor # Convert to .mdc
1137
- skillpull update # Update all installed skills
1138
- skillpull push tianhaocui/ai-skills # Push local skills to remote
1139
- skillpull user/repo --project my-app # Common + project-specific skills
1209
+ skillpull init --global # Setup default skill repo
1210
+ skillpull init # Setup project config (.skillpullrc)
1211
+ skillpull tianhaocui/ai-skills # GitHub shortname
1212
+ skillpull @work plan-first-development --global # From alias
1213
+ skillpull search coding-standards # Search GitHub
1214
+ skillpull alias add work git@github.com:me/skills # Save alias
1215
+ skillpull registry tianhaocui/ai-skills # Set default source
1216
+ skillpull tianhaocui/ai-skills --all # All tools at once
1217
+ skillpull tianhaocui/ai-skills --cursor # Convert to .mdc
1218
+ skillpull update # Update all installed skills
1219
+ skillpull push tianhaocui/ai-skills # Push local skills to remote
1220
+ skillpull user/repo --project my-app # Common + project-specific skills
1140
1221
  HELP
1141
1222
  }
1142
1223
 
@@ -1163,7 +1244,7 @@ main() {
1163
1244
  warn "Add to PATH: export PATH=\"$(dirname "$global_bin"):\$PATH\""
1164
1245
  fi
1165
1246
  echo ""
1166
- info "Run 'skillpull init' to set up your default skill repo."
1247
+ info "Run 'skillpull init --global' to set up your default skill repo."
1167
1248
  exit 0
1168
1249
  fi
1169
1250
 
@@ -1245,16 +1326,16 @@ main() {
1245
1326
 
1246
1327
  case "${cmd:-}" in
1247
1328
  pull|"")
1248
- # Fall back to configured project if --project not specified
1329
+ # Resolve project: CLI > .skillpullrc > (none)
1249
1330
  if [[ -z "$project_name" ]]; then
1250
- project_name="$(read_config_key "project")"
1331
+ project_name="$(read_project_rc "project")"
1251
1332
  fi
1252
1333
  if [[ -z "$repo_url" ]]; then
1253
- # No source given, try registry
1254
- local reg; reg="$(read_config_key "registry")"
1334
+ # Resolve registry: .skillpullrc > global config
1335
+ local reg; reg="$(resolve_config "registry")"
1255
1336
  if [[ -z "$reg" ]]; then
1256
1337
  err "No source specified and no registry set."
1257
- err "Run 'skillpull init' to set a default skill repo."
1338
+ err "Run 'skillpull init --global' to set a default skill repo."
1258
1339
  exit 1
1259
1340
  fi
1260
1341
  repo_url="$reg"
@@ -1272,15 +1353,15 @@ main() {
1272
1353
  done
1273
1354
  ;;
1274
1355
  list)
1275
- # Fall back to configured project if --project not specified
1356
+ # Resolve project: CLI > .skillpullrc > (none)
1276
1357
  if [[ -z "$project_name" ]]; then
1277
- project_name="$(read_config_key "project")"
1358
+ project_name="$(read_project_rc "project")"
1278
1359
  fi
1279
1360
  if [[ -z "$repo_url" ]]; then
1280
- local reg; reg="$(read_config_key "registry")"
1361
+ local reg; reg="$(resolve_config "registry")"
1281
1362
  if [[ -z "$reg" ]]; then
1282
1363
  err "No source specified and no registry set."
1283
- err "Run 'skillpull init' to set a default skill repo."
1364
+ err "Run 'skillpull init --global' to set a default skill repo."
1284
1365
  exit 1
1285
1366
  fi
1286
1367
  repo_url="$reg"
@@ -1292,7 +1373,11 @@ main() {
1292
1373
  cmd_search "${skill_name:-}"
1293
1374
  ;;
1294
1375
  init)
1295
- cmd_init
1376
+ if [[ "$is_global" == "1" ]]; then
1377
+ cmd_init_global
1378
+ else
1379
+ cmd_init_project
1380
+ fi
1296
1381
  ;;
1297
1382
  uninstall)
1298
1383
  cmd_uninstall