@vikasagarwal101/skill-sync 1.0.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.
- package/README.md +206 -0
- package/bin/skill-sync.js +38 -0
- package/bin/skill-sync.sh +1250 -0
- package/install.sh +516 -0
- package/lib/authoring.sh +200 -0
- package/lib/core.sh +221 -0
- package/lib/health.sh +136 -0
- package/lib/inspect.sh +210 -0
- package/lib/sync.sh +281 -0
- package/lib/targets.sh +73 -0
- package/package.json +42 -0
- package/rules/skill-sync.md +96 -0
- package/scripts/bundle.sh +28 -0
- package/skill-sync +131 -0
package/lib/inspect.sh
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
cmd_info() {
|
|
2
|
+
local name="$1"
|
|
3
|
+
|
|
4
|
+
local src_label
|
|
5
|
+
src_label=$(source_for_skill "$name" 2>/dev/null) || {
|
|
6
|
+
echo "'$name' not found in any canonical source"
|
|
7
|
+
# Check if it's orphaned in a target
|
|
8
|
+
for tlabel in "${!TARGETS[@]}"; do
|
|
9
|
+
if [ -d "${TARGETS[$tlabel]}/$name" ]; then
|
|
10
|
+
echo " ⚠️ Found orphaned copy in $tlabel (no canonical source)"
|
|
11
|
+
fi
|
|
12
|
+
done
|
|
13
|
+
return 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
local src_path
|
|
17
|
+
src_path=$(source_path_for_skill "$name")
|
|
18
|
+
local desc
|
|
19
|
+
desc=$(extract_frontmatter_field "$src_path/SKILL.md" "description")
|
|
20
|
+
[ -z "$desc" ] && desc="(no description)"
|
|
21
|
+
|
|
22
|
+
echo "$name"
|
|
23
|
+
echo " Source: $src_label ($src_path)"
|
|
24
|
+
echo " Description: $desc"
|
|
25
|
+
|
|
26
|
+
# Size and files
|
|
27
|
+
local size files
|
|
28
|
+
size=$(du -sh "$src_path" 2>/dev/null | cut -f1)
|
|
29
|
+
files=$(find "$src_path" -type f | wc -l)
|
|
30
|
+
echo " Files: $files ($size)"
|
|
31
|
+
|
|
32
|
+
# Frontmatter health
|
|
33
|
+
if has_frontmatter "$src_path/SKILL.md"; then
|
|
34
|
+
echo " Frontmatter: ✅ present"
|
|
35
|
+
else
|
|
36
|
+
echo " Frontmatter: ❌ MISSING"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Targets / sync state
|
|
40
|
+
local manifest_targets="-"
|
|
41
|
+
[ -f "$MANIFEST" ] && manifest_targets=$(manifest_get_field "$name" "targets")
|
|
42
|
+
[ -z "$manifest_targets" ] && manifest_targets="(not in manifest)"
|
|
43
|
+
echo " Manifest: $manifest_targets"
|
|
44
|
+
|
|
45
|
+
echo " Copies:"
|
|
46
|
+
for tlabel in "${!TARGETS[@]}"; do
|
|
47
|
+
local tdir="${TARGETS[$tlabel]}"
|
|
48
|
+
if [ -d "$tdir/$name" ]; then
|
|
49
|
+
local sh th
|
|
50
|
+
sh=$(md5_content "$src_path")
|
|
51
|
+
th=$(md5_content "$tdir/$name")
|
|
52
|
+
if [ "$sh" = "$th" ]; then
|
|
53
|
+
echo " $tlabel: ✅ in sync"
|
|
54
|
+
else
|
|
55
|
+
echo " $tlabel: 🔄 DRIFTED"
|
|
56
|
+
fi
|
|
57
|
+
else
|
|
58
|
+
echo " $tlabel: — (not deployed)"
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
cmd_status() {
|
|
64
|
+
ensure_manifest
|
|
65
|
+
printf "%-30s %-10s %-15s %s\n" "SKILL" "SOURCE" "TARGETS" "STATE"
|
|
66
|
+
printf "%-30s %-10s %-15s %s\n" "$(printf '%.0s-' {1..30})" "$(printf '%.0s-' {1..10})" "$(printf '%.0s-' {1..15})" "$(printf '%.0s-' {1..5})"
|
|
67
|
+
|
|
68
|
+
local all_ok=1
|
|
69
|
+
|
|
70
|
+
while IFS='|' read -r name source targets; do
|
|
71
|
+
name=$(echo "$name" | xargs)
|
|
72
|
+
[[ "$name" =~ ^# ]] && continue
|
|
73
|
+
[ -z "$name" ] && continue
|
|
74
|
+
source=$(echo "$source" | xargs)
|
|
75
|
+
targets=$(echo "$targets" | xargs)
|
|
76
|
+
|
|
77
|
+
local state=""
|
|
78
|
+
if [ "$targets" = "-" ]; then
|
|
79
|
+
state="canonical"
|
|
80
|
+
else
|
|
81
|
+
IFS=',' read -ra tgt_arr <<< "$targets"
|
|
82
|
+
for tlabel in "${tgt_arr[@]}"; do
|
|
83
|
+
tlabel=$(echo "$tlabel" | xargs)
|
|
84
|
+
local tdir="${TARGETS[$tlabel]}"
|
|
85
|
+
local src_path
|
|
86
|
+
src_path=$(source_path_for_skill "$name" 2>/dev/null)
|
|
87
|
+
if [ -z "$src_path" ]; then
|
|
88
|
+
state="$state ⚠️"
|
|
89
|
+
all_ok=0
|
|
90
|
+
elif [ -d "$tdir/$name" ]; then
|
|
91
|
+
if [ "$(md5_content "$src_path")" = "$(md5_content "$tdir/$name")" ]; then
|
|
92
|
+
state="$state ✅"
|
|
93
|
+
else
|
|
94
|
+
state="$state 🔄"
|
|
95
|
+
all_ok=0
|
|
96
|
+
fi
|
|
97
|
+
else
|
|
98
|
+
state="$state ❌"
|
|
99
|
+
all_ok=0
|
|
100
|
+
fi
|
|
101
|
+
done
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
printf "%-30s %-10s %-15s %s\n" "$name" "$source" "$targets" "$state"
|
|
105
|
+
done < "$MANIFEST"
|
|
106
|
+
|
|
107
|
+
echo ""
|
|
108
|
+
[ $all_ok -eq 1 ] && echo "All synced ✅" || echo "Issues found — run 'skill-sync sync' to fix"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
cmd_list() {
|
|
112
|
+
local show_all="${1:-}"
|
|
113
|
+
|
|
114
|
+
if [ "$show_all" != "--all" ]; then
|
|
115
|
+
# List manifest skills only
|
|
116
|
+
ensure_manifest
|
|
117
|
+
printf "%-30s %-10s %s\n" "SKILL" "SOURCE" "TARGETS"
|
|
118
|
+
printf "%-30s %-10s %s\n" "-----" "------" "-------"
|
|
119
|
+
while IFS='|' read -r name source targets; do
|
|
120
|
+
name=$(echo "$name" | xargs)
|
|
121
|
+
[[ "$name" =~ ^# ]] && continue
|
|
122
|
+
[ -z "$name" ] && continue
|
|
123
|
+
source=$(echo "$source" | xargs)
|
|
124
|
+
targets=$(echo "$targets" | xargs)
|
|
125
|
+
[ "$targets" != "-" ] && printf "%-30s %-10s %s\n" "$name" "$source" "$targets"
|
|
126
|
+
done < "$MANIFEST"
|
|
127
|
+
return
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# List ALL skills from all locations
|
|
131
|
+
printf "%-30s %-10s %-10s %s\n" "SKILL" "SOURCE" "COPIES" "DESCRIPTION"
|
|
132
|
+
printf "%-30s %-10s %-10s %s\n" "-----" "------" "------" "-----------"
|
|
133
|
+
|
|
134
|
+
for name in $(all_skill_names | sort); do
|
|
135
|
+
local src_label src_path copies=""
|
|
136
|
+
src_label=$(source_for_skill "$name" 2>/dev/null) || src_label="orphan"
|
|
137
|
+
src_path=$(source_path_for_skill "$name" 2>/dev/null) || src_path=""
|
|
138
|
+
|
|
139
|
+
for tlabel in "${!TARGETS[@]}"; do
|
|
140
|
+
[ -d "${TARGETS[$tlabel]}/$name" ] && copies="$copies $tlabel"
|
|
141
|
+
done
|
|
142
|
+
[ -z "$copies" ] && copies="-"
|
|
143
|
+
|
|
144
|
+
local desc=""
|
|
145
|
+
if [ -n "$src_path" ] && [ -f "$src_path/SKILL.md" ]; then
|
|
146
|
+
desc=$(extract_frontmatter_field "$src_path/SKILL.md" "description" 2>/dev/null)
|
|
147
|
+
fi
|
|
148
|
+
[ -z "$desc" ] && desc="—"
|
|
149
|
+
|
|
150
|
+
printf "%-30s %-10s %-10s %.60s\n" "$name" "$src_label" "$copies" "$desc"
|
|
151
|
+
done
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
cmd_search() {
|
|
155
|
+
local query="$*"
|
|
156
|
+
[ -z "$query" ] && { echo "Usage: skill-sync search <query>"; return 1; }
|
|
157
|
+
query=$(echo "$query" | tr '[:upper:]' '[:lower:]')
|
|
158
|
+
|
|
159
|
+
printf "%-30s %-10s %.70s\n" "SKILL" "SOURCE" "DESCRIPTION"
|
|
160
|
+
printf "%-30s %-10s %.70s\n" "-----" "------" "-----------"
|
|
161
|
+
|
|
162
|
+
local found=0
|
|
163
|
+
for name in $(all_skill_names | sort); do
|
|
164
|
+
local src_path desc lname
|
|
165
|
+
src_path=$(source_path_for_skill "$name" 2>/dev/null) || continue
|
|
166
|
+
desc=$(extract_frontmatter_field "$src_path/SKILL.md" "description" 2>/dev/null)
|
|
167
|
+
[ -z "$desc" ] && desc="—"
|
|
168
|
+
lname=$(echo "$name $desc" | tr '[:upper:]' '[:lower:]')
|
|
169
|
+
|
|
170
|
+
if [[ "$lname" == *"$query"* ]]; then
|
|
171
|
+
local src_label
|
|
172
|
+
src_label=$(source_for_skill "$name")
|
|
173
|
+
printf "%-30s %-10s %.70s\n" "$name" "$src_label" "$desc"
|
|
174
|
+
found=$((found + 1))
|
|
175
|
+
fi
|
|
176
|
+
done
|
|
177
|
+
|
|
178
|
+
echo ""
|
|
179
|
+
[ $found -eq 0 ] && echo "No matches for '$query'" || echo "$found skill(s) found"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
cmd_diff() {
|
|
183
|
+
local name="$1"
|
|
184
|
+
|
|
185
|
+
local src_path
|
|
186
|
+
src_path=$(source_path_for_skill "$name") || {
|
|
187
|
+
echo "'$name' not found in any source"
|
|
188
|
+
return 1
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
local has_targets=0
|
|
192
|
+
for tlabel in "${!TARGETS[@]}"; do
|
|
193
|
+
if [ -d "${TARGETS[$tlabel]}/$name" ]; then
|
|
194
|
+
has_targets=1
|
|
195
|
+
echo "── diff: $name (source → $tlabel) ──"
|
|
196
|
+
diff -rq "$src_path" "${TARGETS[$tlabel]}/$name" 2>/dev/null || true
|
|
197
|
+
echo ""
|
|
198
|
+
|
|
199
|
+
# Show SKILL.md content diff if different
|
|
200
|
+
if ! diff -q "$src_path/SKILL.md" "${TARGETS[$tlabel]}/$name/SKILL.md" >/dev/null 2>&1; then
|
|
201
|
+
echo "── SKILL.md content diff ──"
|
|
202
|
+
diff -u "${TARGETS[$tlabel]}/$name/SKILL.md" "$src_path/SKILL.md" 2>/dev/null || true
|
|
203
|
+
echo ""
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
done
|
|
207
|
+
|
|
208
|
+
[ $has_targets -eq 0 ] && echo "'$name' has no target copies to diff against"
|
|
209
|
+
}
|
|
210
|
+
|
package/lib/sync.sh
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
cmd_init() {
|
|
2
|
+
mkdir -p "$CONFIG_DIR"
|
|
3
|
+
echo "# skill-sync manifest" > "$MANIFEST"
|
|
4
|
+
echo "# Format: skill_name | source | targets" >> "$MANIFEST"
|
|
5
|
+
echo "# Sources: agents, opencode, codex | Targets: claude (or - for canonical-only)" >> "$MANIFEST"
|
|
6
|
+
echo "#" >> "$MANIFEST"
|
|
7
|
+
|
|
8
|
+
# Build from current state
|
|
9
|
+
local added=0
|
|
10
|
+
for name in $(all_skill_names | sort); do
|
|
11
|
+
local src
|
|
12
|
+
src=$(source_for_skill "$name" 2>/dev/null || echo "orphan")
|
|
13
|
+
|
|
14
|
+
# Determine targets: which target dirs contain this skill
|
|
15
|
+
local tgt_list="-"
|
|
16
|
+
for tlabel in "${!TARGETS[@]}"; do
|
|
17
|
+
local tdir="${TARGETS[$tlabel]}"
|
|
18
|
+
if dir_exists "$tdir/$name"; then
|
|
19
|
+
if [ "$tgt_list" = "-" ]; then
|
|
20
|
+
tgt_list="$tlabel"
|
|
21
|
+
else
|
|
22
|
+
tgt_list="$tgt_list,$tlabel"
|
|
23
|
+
fi
|
|
24
|
+
fi
|
|
25
|
+
done
|
|
26
|
+
|
|
27
|
+
# Skip codex system skills
|
|
28
|
+
[ "$src" = "codex" ] && {
|
|
29
|
+
local cdir="${SOURCES[codex]}"
|
|
30
|
+
[ -d "$cdir/.system/$name" ] && continue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
printf "%-30s | %-10s | %s\n" "$name" "$src" "$tgt_list" >> "$MANIFEST"
|
|
34
|
+
added=$((added + 1))
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
echo "Manifest generated: $MANIFEST"
|
|
38
|
+
echo " $added skills indexed"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
cmd_sync_all() {
|
|
42
|
+
ensure_manifest
|
|
43
|
+
local total=0 synced=0 updated=0 failed=0
|
|
44
|
+
|
|
45
|
+
echo "Syncing all manifest skills..."
|
|
46
|
+
echo ""
|
|
47
|
+
|
|
48
|
+
while IFS='|' read -r name source targets; do
|
|
49
|
+
# Skip comments/headers
|
|
50
|
+
name=$(echo "$name" | xargs)
|
|
51
|
+
[[ "$name" =~ ^# ]] && continue
|
|
52
|
+
[ -z "$name" ] && continue
|
|
53
|
+
source=$(echo "$source" | xargs)
|
|
54
|
+
targets=$(echo "$targets" | xargs)
|
|
55
|
+
[ "$targets" = "-" ] && continue
|
|
56
|
+
|
|
57
|
+
total=$((total + 1))
|
|
58
|
+
|
|
59
|
+
local src_path
|
|
60
|
+
src_path=$(source_path_for_skill "$name") || {
|
|
61
|
+
echo " ⚠️ $name — source not found"
|
|
62
|
+
failed=$((failed + 1))
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Sync to each target
|
|
67
|
+
IFS=',' read -ra tgt_arr <<< "$targets"
|
|
68
|
+
for tlabel in "${tgt_arr[@]}"; do
|
|
69
|
+
tlabel=$(echo "$tlabel" | xargs)
|
|
70
|
+
local tdir="${TARGETS[$tlabel]}"
|
|
71
|
+
[ -d "$tdir" ] || mkdir -p "$tdir"
|
|
72
|
+
|
|
73
|
+
local src_hash tgt_hash
|
|
74
|
+
src_hash=$(md5_content "$src_path")
|
|
75
|
+
if [ -d "$tdir/$name" ]; then
|
|
76
|
+
tgt_hash=$(md5_content "$tdir/$name")
|
|
77
|
+
else
|
|
78
|
+
tgt_hash="none"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if [ "$src_hash" = "$tgt_hash" ]; then
|
|
82
|
+
echo " ✅ $name → $tlabel (in sync)"
|
|
83
|
+
synced=$((synced + 1))
|
|
84
|
+
else
|
|
85
|
+
_safe_rm "$tdir/$name"
|
|
86
|
+
_safe_cp "$src_path" "$tdir/$name"
|
|
87
|
+
echo " 🔄 $name → $tlabel (updated)"
|
|
88
|
+
updated=$((updated + 1))
|
|
89
|
+
fi
|
|
90
|
+
done
|
|
91
|
+
done < "$MANIFEST"
|
|
92
|
+
|
|
93
|
+
echo ""
|
|
94
|
+
echo "Done: $total skills, $updated updated, $synced already in sync, $failed failed"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
cmd_sync_one() {
|
|
98
|
+
local name="$1"
|
|
99
|
+
ensure_manifest
|
|
100
|
+
|
|
101
|
+
local targets
|
|
102
|
+
targets=$(manifest_get_field "$name" "targets")
|
|
103
|
+
[ -z "$targets" ] && {
|
|
104
|
+
echo "'$name' not in manifest. Use: skill-sync add $name"
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
[ "$targets" = "-" ] && {
|
|
108
|
+
echo "'$name' has no sync targets (canonical-only)."
|
|
109
|
+
return 0
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
local src_path
|
|
113
|
+
src_path=$(source_path_for_skill "$name") || {
|
|
114
|
+
echo "Source not found for '$name'"
|
|
115
|
+
return 1
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
IFS=',' read -ra tgt_arr <<< "$targets"
|
|
119
|
+
for tlabel in "${tgt_arr[@]}"; do
|
|
120
|
+
tlabel=$(echo "$tlabel" | xargs)
|
|
121
|
+
local tdir="${TARGETS[$tlabel]}"
|
|
122
|
+
[ -d "$tdir" ] || mkdir -p "$tdir"
|
|
123
|
+
|
|
124
|
+
if [ -d "$tdir/$name" ] && [ "$(md5_content "$src_path")" = "$(md5_content "$tdir/$name")" ]; then
|
|
125
|
+
echo "✅ $name → $tlabel (already in sync)"
|
|
126
|
+
else
|
|
127
|
+
_safe_rm "$tdir/$name"
|
|
128
|
+
_safe_cp "$src_path" "$tdir/$name"
|
|
129
|
+
echo "🔄 $name → $tlabel (updated)"
|
|
130
|
+
fi
|
|
131
|
+
done
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
cmd_add() {
|
|
135
|
+
local name="$1"
|
|
136
|
+
shift || true
|
|
137
|
+
local to_target="claude"
|
|
138
|
+
|
|
139
|
+
while [[ $# -gt 0 ]]; do
|
|
140
|
+
case "$1" in
|
|
141
|
+
--to) to_target="$2"; shift 2 ;;
|
|
142
|
+
*) shift ;;
|
|
143
|
+
esac
|
|
144
|
+
done
|
|
145
|
+
|
|
146
|
+
local src_label
|
|
147
|
+
src_label=$(source_for_skill "$name" 2>/dev/null) || {
|
|
148
|
+
echo "Skill '$name' not found in any source:"
|
|
149
|
+
for l in "${SOURCE_PRIORITY[@]}"; do echo " ${SOURCES[$l]}/$name/"; done
|
|
150
|
+
return 1
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
local src_path
|
|
154
|
+
src_path=$(source_path_for_skill "$name")
|
|
155
|
+
|
|
156
|
+
local tdir="${TARGETS[$to_target]}"
|
|
157
|
+
[ -d "$tdir" ] || mkdir -p "$tdir"
|
|
158
|
+
|
|
159
|
+
_safe_rm "$tdir/$name"
|
|
160
|
+
_safe_cp "$src_path" "$tdir/$name"
|
|
161
|
+
|
|
162
|
+
manifest_add "$name" "$src_label" "$to_target"
|
|
163
|
+
echo "✅ '$name' added → $to_target (from $src_label)"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
cmd_remove() {
|
|
167
|
+
local name="$1"
|
|
168
|
+
shift || true
|
|
169
|
+
local from_target="" purge=0
|
|
170
|
+
|
|
171
|
+
while [[ $# -gt 0 ]]; do
|
|
172
|
+
case "$1" in
|
|
173
|
+
--from) from_target="$2"; shift 2 ;;
|
|
174
|
+
--purge) purge=1; shift ;;
|
|
175
|
+
*) shift ;;
|
|
176
|
+
esac
|
|
177
|
+
done
|
|
178
|
+
|
|
179
|
+
# Safety: resolve the skill and show where it currently exists
|
|
180
|
+
local src_label="" src_path=""
|
|
181
|
+
src_label=$(source_for_skill "$name" 2>/dev/null) && src_path=$(source_path_for_skill "$name")
|
|
182
|
+
|
|
183
|
+
echo "Removing: $name"
|
|
184
|
+
echo ""
|
|
185
|
+
|
|
186
|
+
# Show current state
|
|
187
|
+
if [ -n "$src_path" ]; then
|
|
188
|
+
echo " Source: $src_label ($src_path)"
|
|
189
|
+
else
|
|
190
|
+
echo " Source: — (no canonical source)"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
local target_hits=""
|
|
194
|
+
for tlabel in "${!TARGETS[@]}"; do
|
|
195
|
+
if [ -d "${TARGETS[$tlabel]}/$name" ]; then
|
|
196
|
+
target_hits="$target_hits $tlabel"
|
|
197
|
+
fi
|
|
198
|
+
done
|
|
199
|
+
if [ -n "$target_hits" ]; then
|
|
200
|
+
echo " Targets:$target_hits"
|
|
201
|
+
else
|
|
202
|
+
echo " Targets: — (not deployed)"
|
|
203
|
+
fi
|
|
204
|
+
echo ""
|
|
205
|
+
|
|
206
|
+
# If skill doesn't exist anywhere
|
|
207
|
+
if [ -z "$src_path" ] && [ -z "$target_hits" ]; then
|
|
208
|
+
echo "Skill '$name' not found anywhere."
|
|
209
|
+
manifest_remove "$name"
|
|
210
|
+
return 0
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Phase 1: Remove from targets
|
|
214
|
+
local removed_any=0
|
|
215
|
+
|
|
216
|
+
if [ -n "$from_target" ]; then
|
|
217
|
+
# Remove from specific target only
|
|
218
|
+
local tdir="${TARGETS[$from_target]:-}"
|
|
219
|
+
if [ -z "$tdir" ]; then
|
|
220
|
+
echo "Unknown target '$from_target'"
|
|
221
|
+
return 1
|
|
222
|
+
fi
|
|
223
|
+
if [ -d "$tdir/$name" ]; then
|
|
224
|
+
_safe_rm "$tdir/$name"
|
|
225
|
+
echo " 🗑️ Removed from $from_target"
|
|
226
|
+
removed_any=1
|
|
227
|
+
else
|
|
228
|
+
echo " '$name' was not in $from_target"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# Update manifest: remove this target from the list
|
|
232
|
+
local current_targets
|
|
233
|
+
current_targets=$(manifest_get_field "$name" "targets")
|
|
234
|
+
local new_targets=""
|
|
235
|
+
IFS=',' read -ra remain <<< "$current_targets"
|
|
236
|
+
for t in "${remain[@]}"; do
|
|
237
|
+
t=$(echo "$t" | xargs)
|
|
238
|
+
[ "$t" = "$from_target" ] && continue
|
|
239
|
+
if [ -z "$new_targets" ]; then new_targets="$t"; else new_targets="$new_targets,$t"; fi
|
|
240
|
+
done
|
|
241
|
+
[ -z "$new_targets" ] && new_targets="-"
|
|
242
|
+
[ -n "$src_label" ] && manifest_add "$name" "$src_label" "$new_targets"
|
|
243
|
+
else
|
|
244
|
+
# Remove from ALL targets
|
|
245
|
+
for tlabel in "${!TARGETS[@]}"; do
|
|
246
|
+
local tdir="${TARGETS[$tlabel]}"
|
|
247
|
+
if [ -d "$tdir/$name" ]; then
|
|
248
|
+
_safe_rm "$tdir/$name"
|
|
249
|
+
echo " 🗑️ Removed from $tlabel"
|
|
250
|
+
removed_any=1
|
|
251
|
+
fi
|
|
252
|
+
done
|
|
253
|
+
[ $removed_any -eq 0 ] && echo " (was not deployed to any target)"
|
|
254
|
+
|
|
255
|
+
# Update manifest: set targets to -
|
|
256
|
+
[ -n "$src_label" ] && manifest_add "$name" "$src_label" "-"
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
# Phase 2: Purge from canonical source
|
|
260
|
+
if [ $purge -eq 1 ]; then
|
|
261
|
+
if [ -n "$src_path" ] && [ -d "$src_path" ]; then
|
|
262
|
+
_safe_rm "$src_path"
|
|
263
|
+
echo " 💀 Purged from $src_label source"
|
|
264
|
+
manifest_remove "$name"
|
|
265
|
+
elif [ -z "$src_path" ]; then
|
|
266
|
+
echo " (no canonical source to purge)"
|
|
267
|
+
fi
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
echo ""
|
|
271
|
+
echo "Done."
|
|
272
|
+
|
|
273
|
+
# Show what remains
|
|
274
|
+
if [ $purge -eq 0 ] && [ -n "$src_label" ]; then
|
|
275
|
+
echo " Skill still exists in source: $src_label"
|
|
276
|
+
echo " Re-deploy with: skill-sync add $name"
|
|
277
|
+
elif [ $purge -eq 1 ]; then
|
|
278
|
+
echo " Skill fully removed from all locations."
|
|
279
|
+
fi
|
|
280
|
+
}
|
|
281
|
+
|
package/lib/targets.sh
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# ── Config file writer ─────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
_save_config() {
|
|
4
|
+
mkdir -p "$CONFIG_DIR"
|
|
5
|
+
{
|
|
6
|
+
echo "# Auto-generated by skill-sync."
|
|
7
|
+
echo "# This file is sourced at startup. Edit manually or use target commands."
|
|
8
|
+
echo ""
|
|
9
|
+
declare -p SOURCES
|
|
10
|
+
declare -p TARGETS
|
|
11
|
+
declare -p SOURCE_PRIORITY
|
|
12
|
+
} > "$CONFIG_FILE"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# ── New commands ───────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
cmd_target() {
|
|
18
|
+
local action="${1:-list}"
|
|
19
|
+
|
|
20
|
+
case "$action" in
|
|
21
|
+
list)
|
|
22
|
+
echo "Sync targets:"
|
|
23
|
+
echo ""
|
|
24
|
+
printf "%-15s %s\n" "LABEL" "PATH"
|
|
25
|
+
printf "%-15s %s\n" "-----" "----"
|
|
26
|
+
for tlabel in "${!TARGETS[@]}"; do
|
|
27
|
+
printf "%-15s %s\n" "$tlabel" "${TARGETS[$tlabel]}"
|
|
28
|
+
done
|
|
29
|
+
echo ""
|
|
30
|
+
echo "Sources:"
|
|
31
|
+
printf "%-15s %s\n" "LABEL" "PATH"
|
|
32
|
+
printf "%-15s %s\n" "-----" "----"
|
|
33
|
+
for slabel in "${SOURCE_PRIORITY[@]}"; do
|
|
34
|
+
printf "%-15s %s\n" "$slabel" "${SOURCES[$slabel]}"
|
|
35
|
+
done
|
|
36
|
+
;;
|
|
37
|
+
add)
|
|
38
|
+
local label="$2" path="$3"
|
|
39
|
+
[ -z "$label" ] || [ -z "$path" ] && {
|
|
40
|
+
echo "Usage: skill-sync target add <label> <path>"
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
43
|
+
# Expand ~ in path
|
|
44
|
+
path="${path/#\~/$HOME}"
|
|
45
|
+
[ -n "${TARGETS[$label]:-}" ] && {
|
|
46
|
+
echo "Target '$label' already exists: ${TARGETS[$label]}"
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
49
|
+
TARGETS["$label"]="$path"
|
|
50
|
+
mkdir -p "$path"
|
|
51
|
+
_save_config
|
|
52
|
+
echo "✅ Added target '$label' → $path"
|
|
53
|
+
echo " Config saved to $CONFIG_FILE"
|
|
54
|
+
;;
|
|
55
|
+
remove)
|
|
56
|
+
local label="$2"
|
|
57
|
+
[ -z "$label" ] && { echo "Usage: skill-sync target remove <label>"; return 1; }
|
|
58
|
+
[ -z "${TARGETS[$label]:-}" ] && {
|
|
59
|
+
echo "Target '$label' not found"
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
unset "TARGETS[$label]"
|
|
63
|
+
_save_config
|
|
64
|
+
echo "🗑️ Removed target '$label'"
|
|
65
|
+
echo " (skill directories untouched, config updated)"
|
|
66
|
+
;;
|
|
67
|
+
*)
|
|
68
|
+
echo "Usage: skill-sync target [list|add|remove]"
|
|
69
|
+
return 1
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
}
|
|
73
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vikasagarwal101/skill-sync",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Manage AI agent skills across multiple directories",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/vikasagarwal101/skill-sync.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"ai",
|
|
12
|
+
"agent",
|
|
13
|
+
"skills",
|
|
14
|
+
"claude",
|
|
15
|
+
"codex",
|
|
16
|
+
"opencode",
|
|
17
|
+
"skill-sync",
|
|
18
|
+
"agents-md"
|
|
19
|
+
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"skill-sync": "bin/skill-sync.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/",
|
|
25
|
+
"lib/",
|
|
26
|
+
"rules/",
|
|
27
|
+
"skill-sync",
|
|
28
|
+
"install.sh",
|
|
29
|
+
"scripts/",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"prepublishOnly": "bash scripts/bundle.sh",
|
|
34
|
+
"prepack": "bash scripts/bundle.sh"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=14"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|