ai-global 1.4.3 → 1.4.4

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/ai-global +73 -903
  2. package/package.json +1 -1
package/ai-global CHANGED
@@ -3,8 +3,16 @@
3
3
  # AI Global: Unified AI Tools Configuration Manager
4
4
  # https://github.com/nanxiaobei/ai-global
5
5
 
6
- set -e
6
+ VERSION="1.4.3"
7
7
 
8
+ # Colors
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ BLUE='\033[0;34m'
13
+ NC='\033[0m'
14
+
15
+ # Directories
8
16
  CONFIG_DIR="$HOME/.ai-global"
9
17
  BACKUP_DIR="$CONFIG_DIR/backups"
10
18
  GLOBAL_MD="$CONFIG_DIR/global.md"
@@ -14,36 +22,13 @@ RULES_DIR="$CONFIG_DIR/rules"
14
22
  COMMANDS_DIR="$CONFIG_DIR/commands"
15
23
  PROMPTS_DIR="$CONFIG_DIR/prompts"
16
24
 
17
- # Version
18
- VERSION=""
19
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
- PACKAGE_JSON="$SCRIPT_DIR/package.json"
21
-
22
- if [[ -f "$PACKAGE_JSON" ]]; then
23
- VERSION=$(grep '"version"' "$PACKAGE_JSON" 2>/dev/null | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
24
- fi
25
-
26
- if [[ -z "$VERSION" ]]; then
27
- VERSION="unknown"
28
- fi
29
-
30
- # Colors
31
- RED='\033[0;31m'
32
- GREEN='\033[0;32m'
33
- YELLOW='\033[0;33m'
34
- BLUE='\033[0;34m'
35
- CYAN='\033[0;36m'
36
- MAGENTA='\033[0;35m'
37
- BRIGHT_RED='\033[1;31m'
38
- BRIGHT_GREEN='\033[1;32m'
39
- BRIGHT_YELLOW='\033[1;33m'
40
- BRIGHT_BLUE='\033[1;34m'
41
- BRIGHT_MAGENTA='\033[1;35m'
42
- BRIGHT_CYAN='\033[1;36m'
43
- NC='\033[0m'
25
+ # Logging functions
26
+ log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
27
+ log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
28
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
29
+ log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
44
30
 
45
- # Tool color palette (using xterm-256 colors for more variety)
46
- # We pick a spread of colors from the 256-color palette (avoiding too dark/grayscale)
31
+ # Tool colors
47
32
  TOOL_COLORS=(
48
33
  "\033[38;5;39m" "\033[38;5;214m" "\033[38;5;118m" "\033[38;5;171m" "\033[38;5;208m"
49
34
  "\033[38;5;45m" "\033[38;5;190m" "\033[38;5;161m" "\033[38;5;111m" "\033[38;5;220m"
@@ -53,20 +38,8 @@ TOOL_COLORS=(
53
38
  "\033[38;5;121m" "\033[38;5;227m" "\033[38;5;165m" "\033[38;5;33m" "\033[38;5;216m"
54
39
  "\033[38;5;159m" "\033[38;5;178m" "\033[38;5;162m" "\033[38;5;117m" "\033[38;5;221m"
55
40
  "\033[38;5;78m" "\033[38;5;203m" "\033[38;5;113m" "\033[38;5;142m" "\033[38;5;211m"
56
- "\033[38;5;159m" "\033[38;5;178m" "\033[38;5;162m" "\033[38;5;117m" "\033[38;5;221m"
57
- "\033[38;5;78m" "\033[38;5;203m" "\033[38;5;113m" "\033[38;5;142m" "\033[38;5;211m"
58
41
  )
59
42
 
60
- beautify_path() {
61
- local path="$1"
62
- if [[ "$path" == "$HOME"* ]]; then
63
- local beautified="~${path#$HOME}"
64
- echo "${beautified//\/\///}"
65
- else
66
- echo "$path"
67
- fi
68
- }
69
-
70
43
  get_tool_color() {
71
44
  local name="$1"
72
45
  local sum=0
@@ -77,30 +50,17 @@ get_tool_color() {
77
50
  echo -e "${TOOL_COLORS[$((sum % ${#TOOL_COLORS[@]}))]}"
78
51
  }
79
52
 
80
- # Extract name from frontmatter (name: "...")
81
- extract_meta_name() {
82
- local file="$1"
83
- local default_name="$2"
84
- if [[ ! -f "$file" ]]; then
85
- echo "$default_name"
86
- return
87
- fi
88
- # Match name: "value" or name: value
89
- local extracted=$(grep -m 1 "^name:" "$file" | sed -E 's/^name:[[:space:]]*["'"'"'"'']?([^"'"'"'"'']+)["'"'"'"'']?/\1/' | xargs 2>/dev/null || true)
90
- if [[ -n "$extracted" ]]; then
91
- echo "$extracted"
53
+ beautify_path() {
54
+ local path="$1"
55
+ if [[ "$path" == "$HOME"* ]]; then
56
+ local beautified="~${path#$HOME}"
57
+ echo "${beautified//\/\///}"
92
58
  else
93
- echo "$default_name"
59
+ echo "$path"
94
60
  fi
95
61
  }
96
62
 
97
- log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
98
- log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
99
- log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
100
- log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
101
-
102
63
  # Known AI tool patterns
103
- # Format: dir|name|instr_file|skills_dir|agents_dir|rules_dir|commands_dir|prompts_dir
104
64
  declare -a KNOWN_PATTERNS=(
105
65
  ".claude|Claude Code|CLAUDE.md|skills|.|.|commands|."
106
66
  ".cursor|Cursor|rules/global.md|skills|.|.|.|prompts"
@@ -112,7 +72,7 @@ declare -a KNOWN_PATTERNS=(
112
72
  ".opencode|OpenCode|instructions.md|.|.|.|.|."
113
73
  ".qoder|Qoder|instructions.md|.|.|.|.|."
114
74
  ".qodo|Qodo|instructions.md|.|agents|.|.|."
115
- ".config/github-copilot|GitHub Copilot|instructions.md|.|.|.|.|."
75
+ ".github|GitHub Copilot|copilot-instructions.md|.|.|.|.|."
116
76
  ".aider|Aider|.aider.conf.yml|.|.|.|.|."
117
77
  ".continue|Continue|config.json|.|.|rules|.|prompts"
118
78
  ".codeium|Codeium|config.json|.|.|.|.|."
@@ -142,805 +102,47 @@ declare -a KNOWN_PATTERNS=(
142
102
  ".config/opencode|OpenCode Config|instructions.md|.|.|.|.|."
143
103
  ".augment|Augment|instructions.md|.|agents|rules|.|."
144
104
  ".agents|Codebuff|knowledge.md|.|agents|.|.|."
145
- ".codebuddy|CodeBuddy|settings.json|.|agents|.|.|.
105
+ ".codebuddy|CodeBuddy|settings.json|.|agents|.|.|."
146
106
  )
147
107
 
148
- # Backup a file or directory
149
- backup_item() {
150
- local source="$1"
151
- local tool_dir="$2"
152
- local type="$3"
153
-
154
- [[ ! -e "$source" ]] && return 0
155
- [[ -L "$source" ]] && return 0
156
-
157
- mkdir -p "$BACKUP_DIR"
158
-
159
- local backup_name=$(echo "$tool_dir" | tr '/' '_')
160
- local timestamp=$(date +%s)
161
- local backup_path="$BACKUP_DIR/${backup_name}.${type}.${timestamp}"
162
-
163
- if [[ -d "$source" ]]; then
164
- cp -r "$source" "$backup_path" 2>/dev/null || return 0
165
- else
166
- cp "$source" "$backup_path" 2>/dev/null || return 0
167
- fi
168
-
169
- log_ok "Backed up: $source"
170
- }
171
-
172
- # Merge items from a tool to shared directory (dedup by filename)
173
- merge_items() {
174
- local source_dir="$1"
175
- local target_dir="$2"
176
- local type="$3"
177
- local tool_name="$4"
178
-
179
- [[ ! -d "$source_dir" ]] && return
180
- [[ -L "$source_dir" ]] && return
181
-
182
- local merged_count=0
183
-
184
- for item in "$source_dir"/*; do
185
- [[ ! -e "$item" ]] && continue
186
- local name=$(basename "$item")
187
- local target="$target_dir/$name"
188
-
189
- [[ -e "$target" ]] && continue
190
-
191
- if [[ -d "$item" ]]; then
192
- cp -r "$item" "$target"
193
- else
194
- cp "$item" "$target"
195
- fi
196
- ((merged_count++))
197
- done
198
-
199
- if [[ $merged_count -gt 0 ]]; then
200
- local tool_color=$(get_tool_color "$tool_name")
201
- log_ok "Merged $merged_count $type from ${tool_color}${tool_name}${NC}"
202
- fi
203
- }
204
-
205
- # Count items in directory (dirs and files)
206
- count_items() {
207
- local dir="$1"
208
- if [[ -d "$dir" ]]; then
209
- ls -1 "$dir" 2>/dev/null | wc -l | tr -d ' '
210
- else
211
- echo "0"
212
- fi
213
- }
214
-
215
- # Create symlink
216
- create_symlink() {
217
- local source="$1"
218
- local target="$2"
108
+ # Create directories
109
+ mkdir -p "$CONFIG_DIR" "$BACKUP_DIR" "$SKILLS_DIR" "$AGENTS_DIR" "$RULES_DIR" "$COMMANDS_DIR" "$PROMPTS_DIR"
219
110
 
220
- [[ ! -e "$source" ]] && return
221
-
222
- local target_dir=$(dirname "$target")
223
- mkdir -p "$target_dir"
224
-
225
- # If target exists and is a real file/dir, it should have been backed up by backup_item already.
226
- # We remove it to make room for the symlink, avoiding in-place backups.
227
- if [[ -e "$target" ]] && [[ ! -L "$target" ]]; then
228
- rm -rf "$target"
229
- fi
230
-
231
- [[ -L "$target" ]] && rm "$target"
232
- ln -s "$source" "$target"
233
- }
234
-
235
- # Show symlink status
236
- show_status() {
237
- log_info "Symlink status:"
238
- echo ""
239
-
240
- local total_links=0
241
-
242
- # Instructions
243
- local instr_output=""
244
- for pattern in "${KNOWN_PATTERNS[@]}"; do
245
- local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
246
- IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
247
-
248
- if [[ "$p_instr" != "." ]] && [[ "$p_instr" != *.json ]] && [[ "$p_instr" != *.yml ]]; then
249
- local target="$HOME/$p_dir/$p_instr"
250
- if [[ -L "$target" ]]; then
251
- local link_target=$(readlink "$target" 2>/dev/null || true)
252
- if [[ "$link_target" == *".ai-global"* ]]; then
253
- local tool_color=$(get_tool_color "$p_name")
254
- instr_output+=" ${tool_color}$(beautify_path "$target")${NC}\n"
255
- ((total_links++))
256
- fi
257
- fi
258
- fi
259
- done
260
-
261
- if [[ -n "$instr_output" ]]; then
262
- echo -e "${BLUE}[global.md]${NC}"
263
- echo -e -n "$instr_output"
264
- fi
265
-
266
- for type_name in skills agents rules commands prompts; do
267
- local type_output=""
268
- for pattern in "${KNOWN_PATTERNS[@]}"; do
269
- local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
270
- IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
271
-
272
- local type_dir=""
273
- case "$type_name" in
274
- skills) type_dir="$p_skills" ;;
275
- agents) type_dir="$p_agents" ;;
276
- rules) type_dir="$p_rules" ;;
277
- commands) type_dir="$p_cmds" ;;
278
- prompts) type_dir="$p_prompts" ;;
279
- esac
280
-
281
- if [[ "$type_dir" != "." ]]; then
282
- local target="$HOME/$p_dir/$type_dir"
283
- if [[ -L "$target" ]]; then
284
- local link_target=$(readlink "$target" 2>/dev/null || true)
285
- if [[ "$link_target" == *".ai-global"* ]]; then
286
- local tool_color=$(get_tool_color "$p_name")
287
- type_output+=" ${tool_color}$(beautify_path "$target")${NC}\n"
288
- ((total_links++))
289
- fi
290
- fi
291
- fi
292
- done
293
-
294
- if [[ -n "$type_output" ]]; then
295
- echo -e "\n${BLUE}[$type_name]${NC}"
296
- echo -e -n "$type_output"
297
- fi
298
- done
299
-
300
- if [[ $total_links -eq 0 ]]; then
301
- echo " No active symlinks found."
302
- fi
303
-
304
- echo ""
305
- log_info "Shared items: skills=$(count_items "$SKILLS_DIR"), agents=$(count_items "$AGENTS_DIR"), rules=$(count_items "$RULES_DIR"), commands=$(count_items "$COMMANDS_DIR"), prompts=$(count_items "$PROMPTS_DIR")"
306
- }
307
-
308
- # List supported tools
309
- list_supported() {
310
- log_info "Supported AI tools:"
311
- echo ""
312
- printf " ${BLUE}%-20s %-22s %-10s %-10s %-10s %-10s %-10s %s${NC}\n" "Tool" "Directory" "Skills" "Agents" "Rules" "Cmds" "Prompts" "Status"
313
- echo " --------------------------------------------------------------------------------------------------------------------------------"
314
-
315
- for pattern in "${KNOWN_PATTERNS[@]}"; do
316
- local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
317
- IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
318
- local full_path="$HOME/$p_dir"
319
-
320
- local s_str="." a_str="." r_str="." c_str="." p_str="."
321
- if [[ -d "$full_path" ]]; then
322
- [[ "$p_skills" != "." && -d "$full_path/$p_skills" ]] && s_str="✓"
323
- [[ "$p_agents" != "." && -d "$full_path/$p_agents" ]] && a_str="✓"
324
- [[ "$p_rules" != "." && -d "$full_path/$p_rules" ]] && r_str="✓"
325
- [[ "$p_cmds" != "." && -d "$full_path/$p_cmds" ]] && c_str="✓"
326
- # Prompts can be a file or dir
327
- [[ "$p_prompts" != "." && -e "$full_path/$p_prompts" ]] && p_str="✓"
328
- fi
329
-
330
- local status=""
331
- local tool_color=""
332
- if [[ -d "$full_path" ]]; then
333
- status="${GREEN}Installed${NC}"
334
- tool_color=$(get_tool_color "$p_name")
335
- else
336
- status="${YELLOW}Not found${NC}"
337
- tool_color="${NC}"
338
- fi
339
-
340
- # Use manual padding because printf handles multibyte characters (✓) by byte count in Bash 3.2.
341
- # Each category block matches the header's "%-10s " (11 characters total).
342
- # We use indicator + 10 spaces = 11 characters.
343
- printf " %b%-20s %-22s%b %s %s %s %s %s %b\n" \
344
- "$tool_color" "$p_name" "$p_dir" "$NC" "$s_str" "$a_str" "$r_str" "$c_str" "$p_str" "$status"
345
- done
346
- echo ""
347
- }
348
-
349
- # List available backups
350
- list_backups() {
351
- log_info "Available backups:"
352
- echo ""
353
-
354
- # Use ls -A to catch hidden files/dirs (starting with .)
355
- local backups_list=$(ls -A "$BACKUP_DIR" 2>/dev/null || true)
356
-
357
- if [[ -z "$backups_list" ]]; then
358
- echo " No backups found"
359
- echo ""
360
- return
361
- fi
362
-
363
- printf " ${BLUE}%-25s %-12s %s${NC}\n" "Tool" "Type" "Backup File"
364
- echo " --------------------------------------------------------------------"
365
-
366
- while read -r filename; do
367
- [[ -z "$filename" ]] && continue
368
- local tool_name=""
369
- local type=""
370
-
371
- # Improved regex to handle various path characters
372
- if [[ "$filename" =~ ^(.+)\.([^\.]+)\.([0-9]+)$ ]]; then
373
- tool_name="${BASH_REMATCH[1]}"
374
- type="${BASH_REMATCH[2]}"
375
- else
376
- tool_name="$filename"
377
- type="unknown"
378
- fi
379
-
380
- local tool_color=$(get_tool_color "${tool_name//_/ }")
381
- # Print the backup filename as a path prefixed with ~/ using beautify_path
382
- local backup_path=$(beautify_path "$BACKUP_DIR/$filename")
383
- printf " %s%-25s %-12s %s%b\n" "$tool_color" "$tool_name" "$type" "$backup_path" "$NC"
384
- done <<< "$backups_list"
385
- echo ""
386
- }
387
-
388
- # Collect and merge instructions from all tools
389
- collect_instructions() {
390
- local merged_content=""
391
- local found_count=0
392
-
393
- for pattern in "${KNOWN_PATTERNS[@]}"; do
394
- IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
395
-
396
- if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
397
- local actual_path="$HOME/$dir_name/$instr_file"
398
- [[ -L "$actual_path" ]] && continue
399
-
400
- if [[ -f "$actual_path" ]]; then
401
- local content=$(cat "$actual_path" 2>/dev/null)
402
- if [[ -n "$content" ]]; then
403
- if [[ $found_count -gt 0 ]]; then
404
- merged_content+="\n\n---\n\n"
405
- fi
406
- merged_content+="# From $tool_name\n\n$content"
407
- ((found_count++))
408
- fi
409
- fi
410
- fi
411
- done
412
-
413
- if [[ $found_count -gt 0 ]]; then
414
- echo -e "$merged_content" > "$GLOBAL_MD"
415
- log_ok "Created: $GLOBAL_MD"
416
- fi
417
- }
418
-
419
- # Update: scan, merge and link tools
111
+ # Main function
420
112
  update_tools() {
421
113
  log_info "Scanning for AI tools..."
422
114
  echo ""
423
-
424
- mkdir -p "$SKILLS_DIR" "$AGENTS_DIR" "$RULES_DIR" "$COMMANDS_DIR" "$PROMPTS_DIR" "$BACKUP_DIR"
425
-
426
- collect_instructions
427
-
115
+
428
116
  local tool_count=0
429
-
117
+
430
118
  for pattern in "${KNOWN_PATTERNS[@]}"; do
431
119
  IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
432
120
  local full_path="$HOME/$dir_name"
433
-
434
- if [[ -d "$full_path" ]]; then
435
- local color=$(get_tool_color "$tool_name")
436
- echo -e "${GREEN}[OK]${NC} ${color}Found: $tool_name${NC}"
437
- ((tool_count++))
438
-
439
- if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
440
- local actual_path="$HOME/$dir_name/$instr_file"
441
- backup_item "$actual_path" "$dir_name" "instructions"
442
- fi
443
-
444
- for type_name in skills agents rules commands prompts; do
445
- local type_dir=""
446
- case "$type_name" in
447
- skills) type_dir="$skills" ;;
448
- agents) type_dir="$agents" ;;
449
- rules) type_dir="$rules" ;;
450
- commands) type_dir="$commands" ;;
451
- prompts) type_dir="$prompts" ;;
452
- esac
453
- if [[ "$type_dir" != "." ]]; then
454
- local path="$HOME/$dir_name/$type_dir"
455
- backup_item "$path" "$dir_name" "$type_name"
456
- local target_dir=""
457
- case "$type_name" in
458
- skills) target_dir="$SKILLS_DIR" ;;
459
- agents) target_dir="$AGENTS_DIR" ;;
460
- rules) target_dir="$RULES_DIR" ;;
461
- commands) target_dir="$COMMANDS_DIR" ;;
462
- prompts) target_dir="$PROMPTS_DIR" ;;
463
- esac
464
- merge_items "$path" "$target_dir" "$type_name" "$tool_name"
465
- fi
466
- done
467
- fi
468
- done
469
-
470
- if [[ $tool_count -eq 0 ]]; then
471
- log_info "No AI tools found."
472
- return
473
- fi
474
-
475
- echo ""
476
- log_info "Creating symlinks..."
477
-
478
- for pattern in "${KNOWN_PATTERNS[@]}"; do
479
- IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
480
- local full_path="$HOME/$dir_name"
481
-
121
+
482
122
  if [[ -d "$full_path" ]]; then
483
123
  local tool_color=$(get_tool_color "$tool_name")
484
- if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
485
- local target="$HOME/$dir_name/$instr_file"
486
- create_symlink "$GLOBAL_MD" "$target"
487
- echo " Linked: $target"
488
- fi
489
-
490
- for type_name in skills agents rules commands prompts; do
491
- local type_dir=""
492
- local source_dir=""
493
- case "$type_name" in
494
- skills) type_dir="$skills"; source_dir="$SKILLS_DIR" ;;
495
- agents) type_dir="$agents"; source_dir="$AGENTS_DIR" ;;
496
- rules) type_dir="$rules"; source_dir="$RULES_DIR" ;;
497
- commands) type_dir="$commands"; source_dir="$COMMANDS_DIR" ;;
498
- prompts) type_dir="$prompts"; source_dir="$PROMPTS_DIR" ;;
499
- esac
500
- if [[ "$type_dir" != "." ]]; then
501
- local target="$HOME/$dir_name/$type_dir"
502
- create_symlink "$source_dir" "$target"
503
- echo " Linked: $type_dir"
504
- fi
505
- done
506
- fi
507
- done
508
-
509
- echo ""
510
- log_info "Done! Shared: skills=$(count_items "$SKILLS_DIR"), agents=$(count_items "$AGENTS_DIR"), rules=$(count_items "$RULES_DIR"), commands=$(count_items "$COMMANDS_DIR"), prompts=$(count_items "$PROMPTS_DIR")"
511
- }
512
-
513
- # Unlink a single tool
514
- unlink_single_tool() {
515
- local tool_name="$1"
516
- local dir_name="$2"
517
- local instr_file="$3"
518
- local skills="$4"
519
- local agents="$5"
520
- local rules="$6"
521
- local commands="$7"
522
- local prompts="$8"
523
- local silent="${9:-false}"
524
-
525
- local backup_name=$(echo "$dir_name" | tr '/' '_')
526
- local worked=false
527
-
528
- # Check for instructions link
529
- if [[ "$instr_file" != "." ]]; then
530
- local target="$HOME/$dir_name/$instr_file"
531
- if [[ -L "$target" ]]; then
532
- local link_target=$(readlink "$target" 2>/dev/null || true)
533
- if [[ "$link_target" == *".ai-global"* ]]; then
534
- rm "$target"
535
- local backup_file=$(ls -t "$BACKUP_DIR"/${backup_name}.instructions.* 2>/dev/null | head -1)
536
- [[ -f "$backup_file" ]] && cp "$backup_file" "$target"
537
- worked=true
538
- fi
539
- fi
540
- fi
541
-
542
- # Check for components links
543
- for type_name in skills agents rules commands prompts; do
544
- local type_dir=""
545
- case "$type_name" in
546
- skills) type_dir="$skills" ;;
547
- agents) type_dir="$agents" ;;
548
- rules) type_dir="$rules" ;;
549
- commands) type_dir="$commands" ;;
550
- prompts) type_dir="$prompts" ;;
551
- esac
552
- if [[ "$type_dir" != "." ]]; then
553
- local target="$HOME/$dir_name/$type_dir"
554
- if [[ -L "$target" ]]; then
555
- local link_target=$(readlink "$target" 2>/dev/null || true)
556
- if [[ "$link_target" == *".ai-global"* ]]; then
557
- rm "$target"
558
- local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.${type_name}.* 2>/dev/null | head -1)
559
- if [[ -d "$backup_file" ]]; then
560
- cp -r "$backup_file" "$target"
561
- fi
562
- worked=true
563
- fi
564
- fi
565
- fi
566
- done
567
-
568
- # Check for backups persistence
569
- local has_backups=false
570
- if [[ -n "$(ls "$BACKUP_DIR"/${backup_name}.* 2>/dev/null)" ]]; then
571
- has_backups=true
572
- rm -rf "$BACKUP_DIR"/${backup_name}.* 2>/dev/null || true
573
- fi
574
-
575
- if [[ "$worked" == true ]] || [[ "$has_backups" == true ]]; then
576
- if [[ "$silent" != "true" ]]; then
577
- local color=$(get_tool_color "$tool_name")
578
- echo -e "${GREEN}[OK]${NC} ${color}Unlinked: $tool_name${NC}"
579
- fi
580
- return 0
581
- fi
582
-
583
- return 1
584
- }
585
-
586
- # Unlink all tools
587
- unlink_all_tools() {
588
- log_info "Unlinking tools..."
589
- echo ""
590
-
591
- local unlinked_count=0
592
- # Scan all known patterns to find and remove any symlinks
593
- for pattern in "${KNOWN_PATTERNS[@]}"; do
594
- IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
595
- if unlink_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"; then
596
- ((unlinked_count++))
597
- fi
598
- done
599
-
600
- # Final sweep for any remaining symlinks pointing to .ai-global
601
- find "$HOME" -maxdepth 3 -type l 2>/dev/null | while read -r link; do
602
- local target=$(readlink "$link" 2>/dev/null || true)
603
- if [[ "$target" == *".ai-global"* ]]; then
604
- rm "$link"
605
- log_ok "Removed unknown symlink: $(beautify_path "$link")"
606
- ((unlinked_count++))
124
+ echo -e " ${tool_color}${tool_name}${NC} found in $(beautify_path "$full_path")"
125
+ ((tool_count++))
607
126
  fi
608
127
  done
609
-
610
- # Clear all backups as requested
611
- rm -rf "$BACKUP_DIR"/* 2>/dev/null || true
612
-
128
+
613
129
  echo ""
614
- if [[ $unlinked_count -gt 0 ]]; then
615
- echo "Unlinked items and cleared backups. Shared data preserved."
616
- else
617
- log_info "No active symlinks found. Backups cleared."
618
- fi
619
- }
620
-
621
- # Unlink a specific tool
622
- unlink_tool() {
623
- local tool_query="$1"
624
-
625
- if [[ -z "$tool_query" ]]; then
626
- echo "Usage: ai-global unlink [tool] or ai-global unlink all"
627
- echo ""
628
- list_backups
629
- return 1
630
- fi
631
-
632
- if [[ "$tool_query" == "all" ]]; then
633
- unlink_all_tools
634
- return
635
- fi
636
-
637
- local found=false
638
-
639
- for pattern in "${KNOWN_PATTERNS[@]}"; do
640
- IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
641
- local tool_lower=$(echo "$tool_name" | tr '[:upper:]' '[:lower:]')
642
- local query_lower=$(echo "$tool_query" | tr '[:upper:]' '[:lower:]')
643
-
644
- if [[ "$tool_lower" == *"$query_lower"* ]] || [[ "$dir_name" == *"$query_lower"* ]]; then
645
- if ! unlink_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"; then
646
- log_info "$tool_name is not currently linked."
647
- fi
648
- found=true
649
- break
650
- fi
651
- done
652
-
653
- if [[ "$found" == false ]]; then
654
- log_error "Tool not found: $tool_query"
655
- echo ""
656
- echo "Use 'ai-global list' to see supported tools"
657
- return 1
658
- fi
659
- }
660
-
661
- # Check if input is a GitHub reference
662
- is_github_ref() {
663
- local input="$1"
664
- # Match: user/repo, https://github.com/user/repo, github.com/user/repo
665
- if [[ "$input" =~ ^https?://github\.com/ ]] || \
666
- [[ "$input" =~ ^github\.com/ ]] || \
667
- [[ "$input" =~ ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+(/.*)?$ ]]; then
668
- return 0
669
- fi
670
- return 1
671
- }
672
-
673
- # Parse GitHub reference to get owner, repo, and optional path
674
- parse_github_ref() {
675
- local input="$1"
676
-
677
- # Remove https://github.com/ or github.com/ prefix
678
- input="${input#https://github.com/}"
679
- input="${input#http://github.com/}"
680
- input="${input#github.com/}"
681
-
682
- # Remove /blob/main/ or /blob/master/ or /tree/main/ etc for file/dir paths
683
- input=$(echo "$input" | sed -E 's|/blob/[^/]+/|/|; s|/tree/[^/]+/|/|')
684
-
685
- echo "$input"
130
+ log_info "Found $tool_count AI tools"
131
+ log_info "Shared directories: skills=$(count_items "$SKILLS_DIR"), agents=$(count_items "$AGENTS_DIR"), rules=$(count_items "$RULES_DIR"), commands=$(count_items "$COMMANDS_DIR"), prompts=$(count_items "$PROMPTS_DIR")"
686
132
  }
687
133
 
688
- # Download from GitHub
689
- download_from_github() {
690
- local type="$1"
691
- local ref="$2"
692
- local target_dir="$3"
693
-
694
- local parsed=$(parse_github_ref "$ref")
695
- local owner=$(echo "$parsed" | cut -d'/' -f1)
696
- local repo=$(echo "$parsed" | cut -d'/' -f2)
697
- local path=$(echo "$parsed" | cut -d'/' -f3-)
698
-
699
- if [[ -z "$owner" ]] || [[ -z "$repo" ]]; then
700
- log_error "Invalid GitHub reference: $ref"
701
- return 1
702
- fi
703
-
704
- # If path points to a specific file
705
- if [[ -n "$path" ]] && [[ "$path" == *.md ]]; then
706
- local filename=$(basename "$path")
707
- local raw_url="https://raw.githubusercontent.com/$owner/$repo/main/$path"
708
-
709
- log_info "Downloading: $raw_url"
710
-
711
- if curl -fsSL "$raw_url" -o "$target_dir/$filename" 2>/dev/null; then
712
- log_ok "Added $type: $target_dir/$filename"
713
- else
714
- # Try master branch
715
- raw_url="https://raw.githubusercontent.com/$owner/$repo/master/$path"
716
- if curl -fsSL "$raw_url" -o "$target_dir/$filename" 2>/dev/null; then
717
- log_ok "Added $type: $target_dir/$filename"
718
- else
719
- log_error "Failed to download: $ref"
720
- return 1
721
- fi
722
- fi
723
- else
724
- # Clone entire repo or subdirectory
725
- local tmp_dir=$(mktemp -d)
726
- local clone_url="https://github.com/$owner/$repo.git"
727
-
728
- log_info "Cloning: $clone_url"
729
-
730
- if git clone --depth 1 --single-branch "$clone_url" "$tmp_dir/$repo"; then
731
- local source_dir="$tmp_dir/$repo"
732
- [[ -n "$path" ]] && source_dir="$tmp_dir/$repo/$path"
733
-
734
- if [[ -d "$source_dir" ]]; then
735
- local count=0
736
- local meta_file=""
737
- case "$type" in
738
- skill) meta_file="SKILL.md" ;;
739
- agent) meta_file="AGENT.md" ;;
740
- esac
741
-
742
- # 1. Check if root contains metadata file
743
- if [[ -n "$meta_file" ]] && [[ -f "$source_dir/$meta_file" ]]; then
744
- local name=$(extract_meta_name "$source_dir/$meta_file" "$(basename "$source_dir")")
745
- local target_path="$target_dir/$name"
746
- mkdir -p "$target_path"
747
- cp -R "$source_dir"/* "$target_path/"
748
- count=1
749
- fi
750
-
751
- # 2. Check type subdirectories (skills/, agents/)
752
- if [[ $count -eq 0 ]] && [[ -z "$path" ]]; then
753
- local search_dirs=""
754
- case "$type" in
755
- skill) search_dirs="skills skill" ;;
756
- agent) search_dirs="agents agent" ;;
757
- rule) search_dirs="rules rule" ;;
758
- esac
759
-
760
- for dir in $search_dirs; do
761
- if [[ -d "$source_dir/$dir" ]]; then
762
- # Look for subdirectories containing SKILL.md/AGENT.md
763
- if [[ -n "$meta_file" ]]; then
764
- for d in "$source_dir/$dir"/*; do
765
- [[ ! -d "$d" ]] && continue
766
- if [[ -f "$d/$meta_file" ]]; then
767
- local name=$(extract_meta_name "$d/$meta_file" "$(basename "$d")")
768
- mkdir -p "$target_dir/$name"
769
- cp -R "$d"/* "$target_dir/$name/"
770
- ((count++))
771
- fi
772
- done
773
- fi
774
-
775
- # If we found items or if it's "rules" (no metadata file needed usually), we are done with this dir
776
- if [[ $count -gt 0 ]] || [[ "$type" == "rule" ]]; then
777
- source_dir="$source_dir/$dir"
778
- break
779
- fi
780
- fi
781
- done
782
- fi
783
-
784
- # 3. Check src/ directory if still nothing (for skills)
785
- if [[ $count -eq 0 ]] && [[ "$type" == "skill" ]] && [[ -d "$source_dir/src" ]]; then
786
- if [[ -f "$source_dir/src/$meta_file" ]]; then
787
- local name=$(extract_meta_name "$source_dir/src/$meta_file" "$(basename "$source_dir")")
788
- mkdir -p "$target_dir/$name"
789
- cp -R "$source_dir/src"/* "$target_dir/$name/"
790
- count=1
791
- fi
792
- fi
793
-
794
- # Fallback check (rules only): copy individual .md files
795
- if [[ $count -eq 0 ]] && [[ "$type" == "rule" ]]; then
796
- for file in "$source_dir"/*.md; do
797
- [[ ! -f "$file" ]] && continue
798
- local filename=$(basename "$file")
799
- if [[ "$filename" == "README.md" ]]; then
800
- local other_mds=$(ls "$source_dir"/*.md 2>/dev/null | grep -v "README.md" | wc -l)
801
- [[ $other_mds -gt 0 ]] && continue
802
- fi
803
- cp "$file" "$target_dir/$filename"
804
- ((count++))
805
- done
806
- fi
807
-
808
- if [[ $count -gt 0 ]]; then
809
- echo "Added $count items from $owner/$repo"
810
- else
811
- # Show actual searched path
812
- local searched_path="${source_dir#$tmp_dir/$repo}"
813
- searched_path="${searched_path#/}"
814
- log_warn "No $type found organized in $owner/$repo${searched_path:+/$searched_path}"
815
- fi
816
- else
817
- log_error "Path not found: $path"
818
- rm -rf "$tmp_dir"
819
- return 1
820
- fi
821
-
822
- rm -rf "$tmp_dir"
823
- else
824
- rm -rf "$tmp_dir"
825
- log_error "Failed to clone: $clone_url"
826
- return 1
827
- fi
828
- fi
829
- }
830
-
831
- # Add item to a type directory
832
- add_item() {
833
- local type="$1"
834
- local input="$2"
835
-
836
- if [[ -z "$input" ]]; then
837
- log_error "Usage: ai-global $type <file|github-repo>"
838
- echo ""
839
- echo "Examples:"
840
- echo " ai-global $type react.md"
841
- echo " ai-global $type /path/to/file.md"
842
- echo " ai-global $type user/repo"
843
- echo " ai-global $type https://github.com/user/repo"
844
- echo " ai-global $type user/repo/path/to/file.md"
845
- return 1
846
- fi
847
-
848
- local target_dir=""
849
- case "$type" in
850
- skill) target_dir="$SKILLS_DIR" ;;
851
- agent) target_dir="$AGENTS_DIR" ;;
852
- rule) target_dir="$RULES_DIR" ;;
853
- command) target_dir="$COMMANDS_DIR" ;;
854
- prompt) target_dir="$PROMPTS_DIR" ;;
855
- esac
856
-
857
- mkdir -p "$target_dir"
858
-
859
- # Check if it's a GitHub reference
860
- if is_github_ref "$input"; then
861
- download_from_github "$type" "$input" "$target_dir"
862
- elif [[ -f "$input" ]]; then
863
- # Local file
864
- local basename=$(basename "$input")
865
- cp "$input" "$target_dir/$basename"
866
- log_ok "Added $type: $target_dir/$basename"
134
+ count_items() {
135
+ local dir="$1"
136
+ if [[ -d "$dir" ]]; then
137
+ find "$dir" -type f 2>/dev/null | wc -l
867
138
  else
868
- # Create new file
869
- local target_file="$target_dir/$input"
870
- if [[ ! "$input" == *.md ]]; then
871
- target_file="$target_dir/${input}.md"
872
- fi
873
- touch "$target_file"
874
- log_ok "Created $type: $target_file"
875
- echo "Edit: $target_file"
876
- fi
877
- }
878
-
879
- # Uninstall
880
- uninstall() {
881
- log_warn "This will:"
882
- echo " 1. Unlink all tools to original configuration"
883
- echo " 2. Remove ~/.ai-global directory"
884
- echo " 3. Remove ai-global from PATH"
885
- echo ""
886
- read -p "Are you sure? (y/N) " -r
887
- echo ""
888
-
889
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
890
- log_info "Cancelled"
891
- return
139
+ echo "0"
892
140
  fi
893
-
894
- unlink_all_tools
895
-
896
- [[ -L /usr/local/bin/ai-global ]] && rm -f /usr/local/bin/ai-global
897
- [[ -L "$HOME/.local/bin/ai-global" ]] && rm -f "$HOME/.local/bin/ai-global"
898
-
899
- rm -rf "$CONFIG_DIR"
900
-
901
- log_ok "AI Global uninstalled"
902
141
  }
903
142
 
904
143
  # Show version
905
144
  show_version() {
906
- echo "ai-global version $VERSION"
907
- }
908
-
909
- # Upgrade
910
- upgrade() {
911
- log_info "Checking for updates..."
912
-
913
- local remote_version
914
- remote_version=$(curl -fsSL "https://raw.githubusercontent.com/nanxiaobei/ai-global/main/package.json" 2>/dev/null | grep '"version"' | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
915
-
916
- if [[ -z "$remote_version" ]]; then
917
- log_warn "Could not check for updates"
918
- return 1
919
- fi
920
-
921
- if [[ "$remote_version" == "$VERSION" ]]; then
922
- log_ok "Already at latest version ($VERSION)"
923
- return 0
924
- fi
925
-
926
- log_info "Upgrading: $VERSION -> $remote_version"
927
-
928
- local current_script="$0"
929
- # If running via symlink, update the target
930
- if [[ -L "$current_script" ]]; then
931
- current_script=$(readlink "$current_script")
932
- fi
933
-
934
- local tmp_file=$(mktemp)
935
- if curl -fsSL "https://raw.githubusercontent.com/nanxiaobei/ai-global/main/ai-global" -o "$tmp_file" 2>/dev/null; then
936
- chmod +x "$tmp_file"
937
- mv "$tmp_file" "$current_script"
938
- log_ok "Upgraded to v$remote_version"
939
- else
940
- rm -f "$tmp_file"
941
- log_error "Failed to download update"
942
- return 1
943
- fi
145
+ echo "AI Global v$VERSION"
944
146
  }
945
147
 
946
148
  # Show help
@@ -948,72 +150,40 @@ show_help() {
948
150
  echo -e "${BLUE}AI Global: Unified AI Tools Configuration Manager${NC} v$VERSION"
949
151
  echo ""
950
152
  echo -e "${BLUE}USAGE:${NC}"
951
- echo -e " ai-global [command]"
952
- echo ""
953
- echo -e "${BLUE}CORE COMMANDS:${NC}"
954
- echo -e " ${GREEN}(default)${NC} Scan, merge, update symlinks"
955
- echo -e " ${GREEN}status${NC} Show symlink status"
956
- echo -e " ${GREEN}list${NC} List all supported AI tools"
957
- echo -e " ${GREEN}backups${NC} List available backups"
958
- echo -e " ${GREEN}unlink <key>${NC} Restore a tool's original config"
959
- echo -e " ${GREEN}unlink all${NC} Restore all tools"
960
- echo ""
961
- echo -e "${BLUE}RESOURCE MANAGEMENT:${NC}"
962
- echo -e " ${GREEN}skill <user/repo>${NC} Add a skill"
963
- echo -e " ${GREEN}agent <source>${NC} Add an agent"
964
- echo -e " ${GREEN}rule <source>${NC} Add a rule"
965
- echo -e " ${GREEN}command <source>${NC} Add a command"
966
- echo -e " ${GREEN}prompt <source>${NC} Add a prompt"
967
- echo ""
968
- echo -e "${BLUE}SYSTEM COMMANDS:${NC}"
969
- echo -e " ${GREEN}upgrade${NC} Upgrade ai-global to latest version"
970
- echo -e " ${GREEN}uninstall${NC} Completely remove ai-global"
971
- echo -e " ${GREEN}version${NC} Show version"
972
- echo -e " ${GREEN}help${NC} Show this help"
153
+ echo " ai-global [command]"
973
154
  echo ""
155
+ echo -e "${BLUE}COMMANDS:${NC}"
156
+ echo " [default] Scan and show tool status"
157
+ echo " status Show symlink status"
158
+ echo " list List all supported AI tools"
159
+ echo " version Show version"
160
+ echo " help Show this help"
974
161
  }
975
162
 
976
- # Main
977
- main() {
978
- local cmd="${1:-update}"
979
-
980
- if [[ "$1" == "-v" ]] || [[ "$1" == "--version" ]] || [[ "$1" == "version" ]]; then
163
+ # Main command handler
164
+ case "${1:-}" in
165
+ "version"|"-v"|"--version")
981
166
  show_version
982
- exit 0
983
- fi
984
-
985
- case "$cmd" in
986
- help|--help|-h) show_help; exit 0 ;;
987
- list) list_supported; exit 0 ;;
988
- version|-v|--version) show_version; exit 0 ;;
989
- skill|agent|rule|command|prompt|unlink|status|backups|upgrade|uninstall)
990
- if [[ ! -d "$CONFIG_DIR" ]]; then
991
- log_info "No configuration found. Running initial scan..."
992
- update_tools
993
- [[ "$cmd" == "skill" || "$cmd" == "agent" || "$cmd" == "rule" || "$cmd" == "command" || "$cmd" == "prompt" || "$cmd" == "status" ]] || exit 0
994
- fi
995
- ;;
996
- version|-v|--version|help|--help|-h) ;;
997
- *) cmd="update" ;;
998
- esac
999
-
1000
- case "$cmd" in
1001
- update) update_tools ;;
1002
- status) show_status ;;
1003
- list) list_supported ;;
1004
- backups) list_backups ;;
1005
- unlink) unlink_tool "$2" ;;
1006
- skill) add_item "skill" "$2" ;;
1007
- agent) add_item "agent" "$2" ;;
1008
- rule) add_item "rule" "$2" ;;
1009
- command) add_item "command" "$2" ;;
1010
- prompt) add_item "prompt" "$2" ;;
1011
- upgrade) upgrade ;;
1012
- uninstall) uninstall ;;
1013
- version|-v|--version) show_version ;;
1014
- help|--help|-h) show_help ;;
1015
- *) log_error "Unknown command: $cmd"; show_help; exit 1 ;;
1016
- esac
1017
- }
1018
-
1019
- main "$@"
167
+ ;;
168
+ "help"|"-h"|"--help")
169
+ show_help
170
+ ;;
171
+ "status")
172
+ update_tools
173
+ ;;
174
+ "list")
175
+ echo "Supported AI Tools:"
176
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
177
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
178
+ echo " - $tool_name"
179
+ done
180
+ ;;
181
+ ""|"update")
182
+ update_tools
183
+ ;;
184
+ *)
185
+ echo "Unknown command: $1"
186
+ echo "Use 'ai-global help' for usage"
187
+ exit 1
188
+ ;;
189
+ esac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-global",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "description": "Unified configuration manager for AI coding assistants",
5
5
  "bin": {
6
6
  "ai-global": "ai-global"