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.
- package/package.json +1 -1
- package/skillpull +110 -15
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.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
|
-
|
|
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:-}"
|