@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/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
+ }