skillpull 0.4.2 → 0.4.5

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 +74 -57
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillpull",
3
- "version": "0.4.2",
3
+ "version": "0.4.5",
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.2"
4
+ VERSION="0.4.5"
5
5
  MANIFEST_FILE=".skillpull.json"
6
6
  TMPDIR_PREFIX="skillpull"
7
7
  CONFIG_DIR="$HOME/.config/skillpull"
@@ -60,6 +60,20 @@ sedi() {
60
60
  # Sets SELECTED_ITEMS=("item1" "item3") with user's choices
61
61
  SELECTED_ITEMS=()
62
62
 
63
+ _read_key() {
64
+ local old_settings; old_settings="$(stty -g 2>/dev/null)" || true
65
+ stty -icanon -echo min 1 time 0 2>/dev/null || true
66
+ local char; char="$(dd bs=1 count=1 2>/dev/null)"
67
+ # Check for escape sequence
68
+ if [[ "$char" == $'\033' ]]; then
69
+ stty -icanon -echo min 0 time 1 2>/dev/null || true
70
+ local seq; seq="$(dd bs=2 count=1 2>/dev/null)"
71
+ char="${char}${seq}"
72
+ fi
73
+ [[ -n "$old_settings" ]] && stty "$old_settings" 2>/dev/null || true
74
+ printf '%s' "$char"
75
+ }
76
+
63
77
  select_menu() {
64
78
  local prompt="$1"; shift
65
79
  local items=("$@")
@@ -75,15 +89,12 @@ select_menu() {
75
89
 
76
90
  SELECTED_ITEMS=()
77
91
 
78
- # Save terminal state
79
- local old_stty; old_stty="$(stty -g 2>/dev/null)" || true
80
-
81
92
  # Hide cursor
82
93
  printf '\033[?25l' >&2
83
94
 
84
95
  # Print prompt
85
96
  printf "\n ${CYAN}%s${RESET}\n" "$prompt" >&2
86
- printf " ${DIM}↑↓ move Space select Enter confirm${RESET}\n\n" >&2
97
+ printf " ${DIM}j/k move Space select Enter confirm${RESET}\n\n" >&2
87
98
 
88
99
  # Draw initial list
89
100
  for ((i=0; i<count; i++)); do
@@ -98,17 +109,7 @@ select_menu() {
98
109
 
99
110
  # Input loop
100
111
  while true; do
101
- # Read single key
102
- local key=""
103
- IFS= read -rsn1 key 2>/dev/null || true
104
-
105
- # Handle escape sequences (arrow keys)
106
- if [[ "$key" == $'\033' ]]; then
107
- local seq1="" seq2=""
108
- IFS= read -rsn1 -t 0.1 seq1 2>/dev/null || true
109
- IFS= read -rsn1 -t 0.1 seq2 2>/dev/null || true
110
- key="${key}${seq1}${seq2}"
111
- fi
112
+ local key; key="$(_read_key)"
112
113
 
113
114
  case "$key" in
114
115
  $'\033[A'|k) # Up
@@ -124,7 +125,7 @@ select_menu() {
124
125
  selected="${selected:0:$cursor}0${selected:$((cursor+1))}"
125
126
  fi
126
127
  ;;
127
- '') # Enter - confirm
128
+ ''|$'\n'|$'\r') # Enter - confirm
128
129
  break
129
130
  ;;
130
131
  esac
@@ -147,9 +148,6 @@ select_menu() {
147
148
  # Show cursor
148
149
  printf '\033[?25h' >&2
149
150
 
150
- # Restore terminal
151
- [[ -n "$old_stty" ]] && stty "$old_stty" 2>/dev/null || true
152
-
153
151
  # Collect selected items
154
152
  for ((i=0; i<count; i++)); do
155
153
  if [[ "${selected:$i:1}" == "1" ]]; then
@@ -211,8 +209,9 @@ remove_alias() {
211
209
  err "Alias '$name' not found"
212
210
  return 1
213
211
  fi
214
- # Remove the alias entry
215
- sedi "s|\"${name}\":\"[^\"]*\",\?||" "$CONFIG_FILE"
212
+ # Remove the alias entry (try with trailing comma first, then without)
213
+ sedi "s|\"${name}\":\"[^\"]*\",||" "$CONFIG_FILE"
214
+ sedi "s|\"${name}\":\"[^\"]*\"||" "$CONFIG_FILE"
216
215
  # Clean up double commas and trailing commas
217
216
  sedi 's/,,/,/g; s/,}/}/g; s/{,/{/g' "$CONFIG_FILE"
218
217
  info "Alias '$name' removed"
@@ -403,12 +402,26 @@ write_manifest_entry() {
403
402
  ' "$manifest" > "$tmp"
404
403
  mv "$tmp" "$manifest"
405
404
  else
406
- # Append before closing braces
407
- sedi '/"skills":/,$ {
408
- /^ }/ i\'"$entry"',
409
- }' "$manifest"
405
+ # Append new entry before closing braces using awk
406
+ awk -v new="$entry" '
407
+ /^ }/ && !done { printf "%s,\n", new; done=1 }
408
+ { print }
409
+ ' "$manifest" > "$tmp"
410
+ mv "$tmp" "$manifest"
410
411
  # Fix trailing comma before closing brace
411
- sedi ':a;N;$!ba;s/,\n }/\n }/g' "$manifest"
412
+ local tmp2; tmp2="$(mktemp)"
413
+ awk '
414
+ { lines[NR] = $0; n = NR }
415
+ END {
416
+ for (i=1; i<=n; i++) {
417
+ if (i < n && lines[i] ~ /,$/ && lines[i+1] ~ /^ }/) {
418
+ sub(/,$/, "", lines[i])
419
+ }
420
+ print lines[i]
421
+ }
422
+ }
423
+ ' "$manifest" > "$tmp2"
424
+ mv "$tmp2" "$manifest"
412
425
  fi
413
426
  }
414
427
 
@@ -479,28 +492,6 @@ cmd_pull() {
479
492
  return 1
480
493
  fi
481
494
  skills=("${matched[@]}")
482
- elif [[ ${#skills[@]} -gt 1 && -t 0 ]]; then
483
- # Multiple skills, no filter, interactive terminal -> let user choose
484
- local names=()
485
- for sd in "${skills[@]}"; do
486
- names+=("$(skill_display_name "$sd")")
487
- done
488
- select_menu "Select skills to install:" "${names[@]}"
489
- if [[ ${#SELECTED_ITEMS[@]} -eq 0 ]]; then
490
- warn "No skills selected"
491
- return 0
492
- fi
493
- local matched=()
494
- for sd in "${skills[@]}"; do
495
- local sn; sn="$(skill_display_name "$sd")"
496
- for sel in "${SELECTED_ITEMS[@]}"; do
497
- if [[ "$sn" == "$sel" ]]; then
498
- matched+=("$sd")
499
- break
500
- fi
501
- done
502
- done
503
- skills=("${matched[@]}")
504
495
  fi
505
496
 
506
497
  mkdir -p "$target_dir"
@@ -690,7 +681,20 @@ cmd_remove() {
690
681
  local tmp; tmp="$(mktemp)"
691
682
  grep -v "\"${skill_name}\"" "$manifest" > "$tmp" || true
692
683
  mv "$tmp" "$manifest"
693
- sedi ':a;N;$!ba;s/,\n }/\n }/g' "$manifest"
684
+ # Fix trailing comma before closing brace
685
+ local tmp2; tmp2="$(mktemp)"
686
+ awk '
687
+ { lines[NR] = $0; n = NR }
688
+ END {
689
+ for (i=1; i<=n; i++) {
690
+ if (i < n && lines[i] ~ /,$/ && lines[i+1] ~ /^ }/) {
691
+ sub(/,$/, "", lines[i])
692
+ }
693
+ print lines[i]
694
+ }
695
+ }
696
+ ' "$manifest" > "$tmp2"
697
+ mv "$tmp2" "$manifest"
694
698
  fi
695
699
 
696
700
  info "Removed: $skill_name from $target_dir"
@@ -1053,7 +1057,7 @@ HELP
1053
1057
  # ── Argument parsing & dispatch ──
1054
1058
  main() {
1055
1059
  local cmd="" repo_url="" skill_name="" custom_path=""
1056
- local force=0 dry_run=0 is_global=0 use_all=0
1060
+ local force=0 dry_run=0 is_global=0 use_all=0 agent_explicit=0
1057
1061
  local agents=()
1058
1062
  local alias_args=()
1059
1063
  QUIET=0; BRANCH=""
@@ -1091,11 +1095,11 @@ main() {
1091
1095
  --force|-f) force=1; shift ;;
1092
1096
  --dry-run) dry_run=1; shift ;;
1093
1097
  --quiet|-q) QUIET=1; shift ;;
1094
- --claude) agents+=("claude"); shift ;;
1095
- --codex) agents+=("codex"); shift ;;
1096
- --kiro) agents+=("kiro"); shift ;;
1097
- --cursor) agents+=("cursor"); shift ;;
1098
- --all) use_all=1; shift ;;
1098
+ --claude) agents+=("claude"); agent_explicit=1; shift ;;
1099
+ --codex) agents+=("codex"); agent_explicit=1; shift ;;
1100
+ --kiro) agents+=("kiro"); agent_explicit=1; shift ;;
1101
+ --cursor) agents+=("cursor"); agent_explicit=1; shift ;;
1102
+ --all) use_all=1; agent_explicit=1; shift ;;
1099
1103
  list) cmd="list"; shift ;;
1100
1104
  installed) cmd="installed"; shift ;;
1101
1105
  remove) cmd="remove"; shift ;;
@@ -1136,7 +1140,20 @@ main() {
1136
1140
  if [[ "$use_all" == "1" ]]; then
1137
1141
  agents=("claude" "codex" "kiro" "cursor")
1138
1142
  elif [[ ${#agents[@]} -eq 0 ]]; then
1139
- agents=("$DEFAULT_AGENT")
1143
+ if [[ "$agent_explicit" == "0" && -t 0 && "$cmd" != "installed" && "$cmd" != "update" ]]; then
1144
+ # Interactive terminal, no agent flag -> let user choose
1145
+ select_menu "Install to which tools?" "claude (.claude/skills)" "codex (.codex/skills)" "kiro (.kiro/skills)" "cursor (.cursor/rules)"
1146
+ if [[ ${#SELECTED_ITEMS[@]} -eq 0 ]]; then
1147
+ warn "No tools selected"
1148
+ exit 0
1149
+ fi
1150
+ for sel in "${SELECTED_ITEMS[@]}"; do
1151
+ local agent_name; agent_name="$(echo "$sel" | awk '{print $1}')"
1152
+ agents+=("$agent_name")
1153
+ done
1154
+ else
1155
+ agents=("$DEFAULT_AGENT")
1156
+ fi
1140
1157
  fi
1141
1158
 
1142
1159
  case "${cmd:-}" in