skillpull 0.4.6 → 0.4.7

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 (2) hide show
  1. package/package.json +1 -1
  2. package/skillpull +110 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillpull",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
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.6"
4
+ VERSION="0.4.7"
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: root-level skills + optional project-specific skills
329
+ discover_skills_scoped() {
330
+ local dir="$1" project="${2:-}"
331
+ local results=()
332
+
333
+ # Root-level skills: $dir/*/SKILL.md (direct children only)
334
+ local d
335
+ for d in "$dir"/*/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
- while IFS= read -r d; do
468
- [[ -n "$d" ]] && skills+=("$d")
469
- done < <(discover_skills "$tmpdir")
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
- while IFS= read -r d; do
563
- [[ -n "$d" ]] && skills+=("$d")
564
- done < <(discover_skills "$tmpdir")
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" != "installed" && "$cmd" != "update" ]]; then
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:-}"