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.
- package/ai-global +73 -903
- 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
|
-
|
|
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
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 "$
|
|
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
|
-
".
|
|
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
|
-
#
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
485
|
-
|
|
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
|
-
|
|
615
|
-
|
|
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
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
|
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
|
-
|
|
978
|
-
|
|
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
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|