ai-global 1.0.0 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +66 -64
  2. package/ai-global +307 -238
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,49 +6,55 @@ Unified configuration manager for AI coding assistants. Edit one file, sync to a
6
6
 
7
7
  ## Supported Tools
8
8
 
9
- | Tool | Instructions | Skills | Agents | Rules | Commands | Prompts |
10
- |------|:------:|:------:|:------:|:-----:|:--------:|:-------:|
11
- | Claude Code | | | | | | |
12
- | Cursor | | | | | | |
13
- | GitHub Copilot | | | | | | |
14
- | Factory Droid | | | | | | |
15
- | Gemini CLI | | | | | | |
16
- | Windsurf | | | | | | |
17
- | Kiro | | | | | | |
18
- | Qodo | | | | | | |
19
- | Antigravity | | | | | | |
20
- | Continue | | | | | | |
21
- | Cline | | | | | | |
22
- | Roo Code | | | | | | |
23
- | Sourcegraph Cody | | | | | | |
24
- | CodeGPT | | | | | | |
25
- | GPT Engineer | | | | | | |
26
- | Smol Developer | | | | | | |
27
- | Amp | | | | | | |
28
- | Trae | | | | | | |
29
- | OpenCode | | | | | | |
30
- | OpenAI Codex | | | | | | |
31
- | Aider | | | | | | |
32
- | Codeium | | | | | | |
33
- | TabNine | | | | | | |
34
- | Zed | | | | | | |
35
- | Aide | | | | | | |
36
- | PearAI | | | | | | |
37
- | Supermaven | | | | | | |
38
- | CodeStory | | | | | | |
39
- | Double | | | | | | |
40
- | Blackbox AI | | | | | | |
41
- | Amazon Q | | | | | | |
42
- | Copilot Workspace | | | | | | |
43
- | Goose AI | | | | | | |
44
- | Mentat | | | | | | |
45
- | Melty | | | | | | |
46
- | Void | | | | | | |
47
- | Qoder | | | | | | |
9
+ | Tool | Instructions | Skills | Agents | Rules | Commands | Prompts |
10
+ | ----------------- | :----------: | :----: | :----: | :---: | :------: | :-----: |
11
+ | Claude Code | | | | | | |
12
+ | Cursor | | | | | | |
13
+ | GitHub Copilot | | | | | | |
14
+ | Factory Droid | | | | | | |
15
+ | Gemini CLI | | | | | | |
16
+ | Windsurf | | | | | | |
17
+ | Kiro | | | | | | |
18
+ | Qodo | | | | | | |
19
+ | Antigravity | | | | | | |
20
+ | Continue | | | | | | |
21
+ | Cline | | | | | | |
22
+ | Roo Code | | | | | | |
23
+ | Sourcegraph Cody | | | | | | |
24
+ | CodeGPT | | | | | | |
25
+ | GPT Engineer | | | | | | |
26
+ | Smol Developer | | | | | | |
27
+ | Amp | | | | | | |
28
+ | Trae | | | | | | |
29
+ | OpenCode | | | | | | |
30
+ | OpenAI Codex | | | | | | |
31
+ | Aider | | | | | | |
32
+ | Codeium | | | | | | |
33
+ | TabNine | | | | | | |
34
+ | Zed | | | | | | |
35
+ | Aide | | | | | | |
36
+ | PearAI | | | | | | |
37
+ | Supermaven | | | | | | |
38
+ | CodeStory | | | | | | |
39
+ | Double | | | | | | |
40
+ | Blackbox AI | | | | | | |
41
+ | Amazon Q | | | | | | |
42
+ | Copilot Workspace | | | | | | |
43
+ | Goose AI | | | | | | |
44
+ | Mentat | | | | | | |
45
+ | Melty | | | | | | |
46
+ | Void | | | | | | |
47
+ | Qoder | | | | | | |
48
48
 
49
49
  ## Installation
50
50
 
51
- ### npm / pnpm / yarn / bun
51
+ ### curl
52
+
53
+ ```bash
54
+ curl -fsSL https://raw.githubusercontent.com/nanxiaobei/ai-global/main/install.sh | bash
55
+ ```
56
+
57
+ ### npm
52
58
 
53
59
  ```bash
54
60
  npm install -g ai-global
@@ -60,12 +66,6 @@ yarn global add ai-global
60
66
  bun add -g ai-global
61
67
  ```
62
68
 
63
- ### curl
64
-
65
- ```bash
66
- curl -fsSL https://raw.githubusercontent.com/nanxiaobei/ai-global/main/install.sh | bash
67
- ```
68
-
69
69
  ## Usage
70
70
 
71
71
  ### First run
@@ -75,6 +75,7 @@ ai-global
75
75
  ```
76
76
 
77
77
  This will:
78
+
78
79
  1. Scan your system for installed AI tools
79
80
  2. Backup original configs to `~/.ai-global/backups/`
80
81
  3. Merge instructions/skills/agents/rules/commands/prompts from all tools
@@ -90,23 +91,23 @@ Changes take effect immediately - all tools read the same file via symlinks.
90
91
 
91
92
  ### Commands
92
93
 
93
- | Command | Description |
94
- |---------|-------------|
95
- | `ai-global` | Scan, merge and update symlinks (default) |
96
- | `ai-global status` | Show symlink status |
97
- | `ai-global list` | List supported tools |
98
- | `ai-global backups` | List available backups |
99
- | `ai-global restore <tool>` | Restore a tool's original config |
100
- | `ai-global restore all` | Restore all tools |
101
- | `ai-global skill <source>` | Add a skill (file or GitHub repo) |
102
- | `ai-global agent <source>` | Add an agent |
103
- | `ai-global rule <source>` | Add a rule |
104
- | `ai-global command <source>` | Add a command |
105
- | `ai-global prompt <source>` | Add a prompt |
106
- | `ai-global upgrade` | Upgrade to latest version |
107
- | `ai-global uninstall` | Completely remove ai-global |
108
- | `ai-global version` | Show version |
109
- | `ai-global help` | Show help |
94
+ | Command | Description |
95
+ | ---------------------------- | ----------------------------------------- |
96
+ | `ai-global` | Scan, merge and update symlinks (default) |
97
+ | `ai-global status` | Show symlink status |
98
+ | `ai-global list` | List supported tools |
99
+ | `ai-global backups` | List available backups |
100
+ | `ai-global unlink <tool>` | Unlink a tool's original config |
101
+ | `ai-global unlink all` | Unlink all tools |
102
+ | `ai-global skill <source>` | Add a skill (file or GitHub repo) |
103
+ | `ai-global agent <source>` | Add an agent |
104
+ | `ai-global rule <source>` | Add a rule |
105
+ | `ai-global command <source>` | Add a command |
106
+ | `ai-global prompt <source>` | Add a prompt |
107
+ | `ai-global upgrade` | Upgrade to latest version |
108
+ | `ai-global uninstall` | Completely remove ai-global |
109
+ | `ai-global version` | Show version |
110
+ | `ai-global help` | Show help |
110
111
 
111
112
  ### Add skill/agent/rule/command/prompt
112
113
 
@@ -170,7 +171,8 @@ ai-global uninstall
170
171
  ```
171
172
 
172
173
  This will:
173
- 1. Restore all tools to original configuration
174
+
175
+ 1. Unlink all tools to original configuration
174
176
  2. Remove `~/.ai-global` directory
175
177
  3. Remove `ai-global` command
176
178
 
package/ai-global CHANGED
@@ -6,9 +6,8 @@
6
6
  set -e
7
7
 
8
8
  CONFIG_DIR="$HOME/.ai-global"
9
- KNOWN_TOOLS_FILE="$CONFIG_DIR/.known-tools"
10
9
  BACKUP_DIR="$CONFIG_DIR/backups"
11
- INSTRUCTIONS_MD="$CONFIG_DIR/instructions.md"
10
+ GLOBAL_MD="$CONFIG_DIR/global.md"
12
11
  SKILLS_DIR="$CONFIG_DIR/skills"
13
12
  AGENTS_DIR="$CONFIG_DIR/agents"
14
13
  RULES_DIR="$CONFIG_DIR/rules"
@@ -39,8 +38,50 @@ GREEN='\033[0;32m'
39
38
  YELLOW='\033[0;33m'
40
39
  BLUE='\033[0;34m'
41
40
  CYAN='\033[0;36m'
41
+ MAGENTA='\033[0;35m'
42
+ BRIGHT_RED='\033[1;31m'
43
+ BRIGHT_GREEN='\033[1;32m'
44
+ BRIGHT_YELLOW='\033[1;33m'
45
+ BRIGHT_BLUE='\033[1;34m'
46
+ BRIGHT_MAGENTA='\033[1;35m'
47
+ BRIGHT_CYAN='\033[1;36m'
42
48
  NC='\033[0m'
43
49
 
50
+ # Tool color palette (using xterm-256 colors for more variety)
51
+ # We pick a spread of colors from the 256-color palette (avoiding too dark/grayscale)
52
+ TOOL_COLORS=(
53
+ "\033[38;5;39m" "\033[38;5;214m" "\033[38;5;118m" "\033[38;5;171m" "\033[38;5;208m"
54
+ "\033[38;5;45m" "\033[38;5;190m" "\033[38;5;161m" "\033[38;5;111m" "\033[38;5;220m"
55
+ "\033[38;5;75m" "\033[38;5;202m" "\033[38;5;112m" "\033[38;5;141m" "\033[38;5;210m"
56
+ "\033[38;5;47m" "\033[38;5;226m" "\033[38;5;201m" "\033[38;5;81m" "\033[38;5;215m"
57
+ "\033[38;5;51m" "\033[38;5;184m" "\033[38;5;197m" "\033[38;5;105m" "\033[38;5;209m"
58
+ "\033[38;5;121m" "\033[38;5;227m" "\033[38;5;165m" "\033[38;5;33m" "\033[38;5;216m"
59
+ "\033[38;5;159m" "\033[38;5;178m" "\033[38;5;162m" "\033[38;5;117m" "\033[38;5;221m"
60
+ "\033[38;5;78m" "\033[38;5;203m" "\033[38;5;113m" "\033[38;5;142m" "\033[38;5;211m"
61
+ "\033[38;5;159m" "\033[38;5;178m" "\033[38;5;162m" "\033[38;5;117m" "\033[38;5;221m"
62
+ "\033[38;5;78m" "\033[38;5;203m" "\033[38;5;113m" "\033[38;5;142m" "\033[38;5;211m"
63
+ )
64
+
65
+ beautify_path() {
66
+ local path="$1"
67
+ if [[ "$path" == "$HOME"* ]]; then
68
+ local beautified="~${path#$HOME}"
69
+ echo "${beautified//\/\///}"
70
+ else
71
+ echo "$path"
72
+ fi
73
+ }
74
+
75
+ get_tool_color() {
76
+ local name="$1"
77
+ local sum=0
78
+ # Simple hash to pick a stable color index
79
+ for (( i=0; i<${#name}; i++ )); do
80
+ sum=$((sum + $(printf '%d' "'${name:$i:1}")))
81
+ done
82
+ echo -e "${TOOL_COLORS[$((sum % ${#TOOL_COLORS[@]}))]}"
83
+ }
84
+
44
85
  log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
45
86
  log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
46
87
  log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
@@ -81,11 +122,11 @@ declare -a KNOWN_PATTERNS=(
81
122
  ".blackbox|Blackbox AI|config.json|.|.|.|.|."
82
123
  ".amazonq|Amazon Q|instructions.md|.|.|.|.|."
83
124
  ".copilot-workspace|Copilot Workspace|instructions.md|.|.|.|.|."
84
- ".codex|OpenAI Codex|instructions.md|.|.|.|.|."
85
- ".goose|Goose AI|instructions.md|.|.|.|.|."
86
- ".mentat|Mentat|instructions.md|.|.|.|.|."
87
- ".gpt-engineer|GPT Engineer|instructions.md|.|.|.|.|prompts"
88
- ".smol|Smol Developer|instructions.md|.|.|.|.|prompts"
125
+ ".codex|OpenAI Codex|instructions.md|skills|agents|rules|.|."
126
+ ".goose|Goose AI|instructions.md|skills|agents|rules|.|."
127
+ ".mentat|Mentat|instructions.md|skills|agents|rules|.|."
128
+ ".gpt-engineer|GPT Engineer|instructions.md|skills|agents|rules|.|prompts"
129
+ ".smol|Smol Developer|instructions.md|skills|agents|rules|.|prompts"
89
130
  ".config/opencode|OpenCode Config|instructions.md|.|.|.|.|."
90
131
  )
91
132
 
@@ -118,6 +159,7 @@ merge_items() {
118
159
  local source_dir="$1"
119
160
  local target_dir="$2"
120
161
  local type="$3"
162
+ local tool_name="$4"
121
163
 
122
164
  [[ ! -d "$source_dir" ]] && return
123
165
  [[ -L "$source_dir" ]] && return
@@ -140,7 +182,18 @@ merge_items() {
140
182
  done
141
183
 
142
184
  if [[ $merged_count -gt 0 ]]; then
143
- log_ok "Merged $merged_count $type from $source_dir"
185
+ local tool_color=$(get_tool_color "$tool_name")
186
+ log_ok "Merged $merged_count $type from ${tool_color}${tool_name}${NC}"
187
+ fi
188
+ }
189
+
190
+ # Count items in directory (dirs and files)
191
+ count_items() {
192
+ local dir="$1"
193
+ if [[ -d "$dir" ]]; then
194
+ ls -1 "$dir" 2>/dev/null | wc -l | tr -d ' '
195
+ else
196
+ echo "0"
144
197
  fi
145
198
  }
146
199
 
@@ -154,118 +207,126 @@ create_symlink() {
154
207
  local target_dir=$(dirname "$target")
155
208
  mkdir -p "$target_dir"
156
209
 
210
+ # If target exists and is a real file/dir, it should have been backed up by backup_item already.
211
+ # We remove it to make room for the symlink, avoiding in-place backups.
157
212
  if [[ -e "$target" ]] && [[ ! -L "$target" ]]; then
158
- local backup="${target}.backup.$(date +%Y%m%d%H%M%S)"
159
- mv "$target" "$backup"
160
- log_warn "Backed up: $target"
213
+ rm -rf "$target"
161
214
  fi
162
215
 
163
216
  [[ -L "$target" ]] && rm "$target"
164
217
  ln -s "$source" "$target"
165
218
  }
166
219
 
167
- # Count files in directory
168
- count_files() {
169
- local dir="$1"
170
- if [[ -d "$dir" ]]; then
171
- find "$dir" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' '
172
- else
173
- echo "0"
174
- fi
175
- }
176
-
177
220
  # Show symlink status
178
221
  show_status() {
179
222
  log_info "Symlink status:"
180
223
  echo ""
181
224
 
182
- if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
183
- log_info "No tools configured. Run 'ai-global' first."
184
- return
225
+ local total_links=0
226
+
227
+ # Instructions
228
+ local instr_output=""
229
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
230
+ local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
231
+ IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
232
+
233
+ if [[ "$p_instr" != "." ]] && [[ "$p_instr" != *.json ]] && [[ "$p_instr" != *.yml ]]; then
234
+ local target="$HOME/$p_dir/$p_instr"
235
+ if [[ -L "$target" ]]; then
236
+ local link_target=$(readlink "$target" 2>/dev/null || true)
237
+ if [[ "$link_target" == *".ai-global"* ]]; then
238
+ local tool_color=$(get_tool_color "$p_name")
239
+ instr_output+=" ${tool_color}✓ $(beautify_path "$target")${NC}\n"
240
+ ((total_links++))
241
+ fi
242
+ fi
243
+ fi
244
+ done
245
+
246
+ if [[ -n "$instr_output" ]]; then
247
+ echo -e "${BLUE}[global.md]${NC}"
248
+ echo -e -n "$instr_output"
185
249
  fi
186
250
 
187
- echo -e "${BLUE}[instructions]${NC}"
188
- while IFS= read -r dir_name; do
189
- [[ -z "$dir_name" ]] && continue
251
+ for type_name in skills agents rules commands prompts; do
252
+ local type_output=""
190
253
  for pattern in "${KNOWN_PATTERNS[@]}"; do
191
- IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
192
- if [[ "$dir_name" == "$known_dir" ]]; then
193
- if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
194
- local target="$HOME/$dir_name/$instr_file"
195
- if [[ -L "$target" ]]; then
196
- echo -e " ${GREEN}✓${NC} $target"
197
- elif [[ -e "$target" ]]; then
198
- echo -e " ${YELLOW}!${NC} $target (not a symlink)"
199
- else
200
- echo -e " ${RED}✗${NC} $target (missing)"
254
+ local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
255
+ IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
256
+
257
+ local type_dir=""
258
+ case "$type_name" in
259
+ skills) type_dir="$p_skills" ;;
260
+ agents) type_dir="$p_agents" ;;
261
+ rules) type_dir="$p_rules" ;;
262
+ commands) type_dir="$p_cmds" ;;
263
+ prompts) type_dir="$p_prompts" ;;
264
+ esac
265
+
266
+ if [[ "$type_dir" != "." ]]; then
267
+ local target="$HOME/$p_dir/$type_dir"
268
+ if [[ -L "$target" ]]; then
269
+ local link_target=$(readlink "$target" 2>/dev/null || true)
270
+ if [[ "$link_target" == *".ai-global"* ]]; then
271
+ local tool_color=$(get_tool_color "$p_name")
272
+ type_output+=" ${tool_color}✓ $(beautify_path "$target")${NC}\n"
273
+ ((total_links++))
201
274
  fi
202
275
  fi
203
- break
204
276
  fi
205
277
  done
206
- done < "$KNOWN_TOOLS_FILE"
207
278
 
208
- for type_name in skills agents rules commands prompts; do
209
- echo -e "\n${BLUE}[$type_name]${NC}"
210
- while IFS= read -r dir_name; do
211
- [[ -z "$dir_name" ]] && continue
212
- for pattern in "${KNOWN_PATTERNS[@]}"; do
213
- IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
214
- if [[ "$dir_name" == "$known_dir" ]]; then
215
- local type_dir=""
216
- case "$type_name" in
217
- skills) type_dir="$skills" ;;
218
- agents) type_dir="$agents" ;;
219
- rules) type_dir="$rules" ;;
220
- commands) type_dir="$commands" ;;
221
- prompts) type_dir="$prompts" ;;
222
- esac
223
- if [[ "$type_dir" != "." ]]; then
224
- local target="$HOME/$dir_name/$type_dir"
225
- if [[ -L "$target" ]]; then
226
- echo -e " ${GREEN}✓${NC} $target"
227
- elif [[ -e "$target" ]]; then
228
- echo -e " ${YELLOW}!${NC} $target (not a symlink)"
229
- else
230
- echo -e " ${RED}✗${NC} $target (missing)"
231
- fi
232
- fi
233
- break
234
- fi
235
- done
236
- done < "$KNOWN_TOOLS_FILE"
279
+ if [[ -n "$type_output" ]]; then
280
+ echo -e "\n${BLUE}[$type_name]${NC}"
281
+ echo -e -n "$type_output"
282
+ fi
237
283
  done
238
284
 
285
+ if [[ $total_links -eq 0 ]]; then
286
+ echo " No active symlinks found."
287
+ fi
288
+
239
289
  echo ""
240
- log_info "Shared: skills=$(count_files "$SKILLS_DIR"), agents=$(count_files "$AGENTS_DIR"), rules=$(count_files "$RULES_DIR"), commands=$(count_files "$COMMANDS_DIR"), prompts=$(count_files "$PROMPTS_DIR")"
290
+ 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")"
241
291
  }
242
292
 
243
293
  # List supported tools
244
294
  list_supported() {
245
295
  log_info "Supported AI tools:"
246
296
  echo ""
247
- printf " ${BLUE}%-22s${NC} %-20s %-6s %-6s %-6s %-6s %-6s %s\n" "Directory" "Tool" "Skills" "Agents" "Rules" "Cmds" "Prompts" "Status"
248
- echo " ----------------------------------------------------------------------------------------------------------------"
297
+ printf " ${BLUE}%-22s %-20s %-10s %-10s %-10s %-10s %-10s %s${NC}\n" "Directory" "Tool" "Skills" "Agents" "Rules" "Cmds" "Prompts" "Status"
298
+ echo " --------------------------------------------------------------------------------------------------------------------------------"
249
299
 
250
300
  for pattern in "${KNOWN_PATTERNS[@]}"; do
251
- IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
252
- local full_path="$HOME/$dir_name"
301
+ local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
302
+ IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
303
+ local full_path="$HOME/$p_dir"
253
304
 
254
- local s="." a="." r="." c="." p="."
255
- [[ "$skills" != "." ]] && s="✓"
256
- [[ "$agents" != "." ]] && a="✓"
257
- [[ "$rules" != "." ]] && r="✓"
258
- [[ "$commands" != "." ]] && c="✓"
259
- [[ "$prompts" != "." ]] && p="✓"
305
+ local s_str="." a_str="." r_str="." c_str="." p_str="."
306
+ if [[ -d "$full_path" ]]; then
307
+ [[ "$p_skills" != "." && -d "$full_path/$p_skills" ]] && s_str="✓"
308
+ [[ "$p_agents" != "." && -d "$full_path/$p_agents" ]] && a_str="✓"
309
+ [[ "$p_rules" != "." && -d "$full_path/$p_rules" ]] && r_str="✓"
310
+ [[ "$p_cmds" != "." && -d "$full_path/$p_cmds" ]] && c_str="✓"
311
+ # Prompts can be a file or dir
312
+ [[ "$p_prompts" != "." && -e "$full_path/$p_prompts" ]] && p_str="✓"
313
+ fi
260
314
 
261
315
  local status=""
316
+ local tool_color=""
262
317
  if [[ -d "$full_path" ]]; then
263
318
  status="${GREEN}Installed${NC}"
319
+ tool_color=$(get_tool_color "$p_name")
264
320
  else
265
321
  status="${YELLOW}Not found${NC}"
322
+ tool_color="${NC}"
266
323
  fi
267
324
 
268
- printf " %-22s %-20s %-6s %-6s %-6s %-6s %-6s %b\n" "$dir_name" "$tool_name" "$s" "$a" "$r" "$c" "$p" "$status"
325
+ # Use manual padding because printf handles multibyte characters (✓) by byte count in Bash 3.2.
326
+ # Each category block matches the header's "%-10s " (11 characters total).
327
+ # We use indicator + 10 spaces = 11 characters.
328
+ printf " %b%-22s %-20s%b %s %s %s %s %s %b\n" \
329
+ "$tool_color" "$p_dir" "$p_name" "$NC" "$s_str" "$a_str" "$r_str" "$c_str" "$p_str" "$status"
269
330
  done
270
331
  echo ""
271
332
  }
@@ -275,22 +336,25 @@ list_backups() {
275
336
  log_info "Available backups:"
276
337
  echo ""
277
338
 
278
- if [[ ! -d "$BACKUP_DIR" ]] || [[ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]]; then
339
+ # Use ls -A to catch hidden files/dirs (starting with .)
340
+ local backups_list=$(ls -A "$BACKUP_DIR" 2>/dev/null || true)
341
+
342
+ if [[ -z "$backups_list" ]]; then
279
343
  echo " No backups found"
280
344
  echo ""
281
345
  return
282
346
  fi
283
347
 
284
- printf " ${BLUE}%-25s${NC} %-12s %s\n" "Tool" "Type" "Backup File"
285
- echo " ----------------------------------------------------------------"
348
+ printf " ${BLUE}%-25s %-12s %s${NC}\n" "Tool" "Type" "Backup File"
349
+ echo " --------------------------------------------------------------------"
286
350
 
287
- for backup in "$BACKUP_DIR"/*; do
288
- [[ ! -e "$backup" ]] && continue
289
- local filename=$(basename "$backup")
351
+ while read -r filename; do
352
+ [[ -z "$filename" ]] && continue
290
353
  local tool_name=""
291
354
  local type=""
292
355
 
293
- if [[ "$filename" =~ ^(.+)\.([^.]+)\.([0-9]+)$ ]]; then
356
+ # Improved regex to handle various path characters
357
+ if [[ "$filename" =~ ^(.+)\.([^\.]+)\.([0-9]+)$ ]]; then
294
358
  tool_name="${BASH_REMATCH[1]}"
295
359
  type="${BASH_REMATCH[2]}"
296
360
  else
@@ -298,8 +362,9 @@ list_backups() {
298
362
  type="unknown"
299
363
  fi
300
364
 
301
- printf " %-25s %-12s %s\n" "$tool_name" "$type" "$filename"
302
- done
365
+ local tool_color=$(get_tool_color "${tool_name//_/ }")
366
+ printf " %s%-25s %-12s %s%b\n" "$tool_color" "$tool_name" "$type" "$filename" "$NC"
367
+ done <<< "$backups_list"
303
368
  echo ""
304
369
  }
305
370
 
@@ -329,15 +394,15 @@ collect_instructions() {
329
394
  done
330
395
 
331
396
  if [[ $found_count -gt 0 ]]; then
332
- echo -e "$merged_content" > "$INSTRUCTIONS_MD"
397
+ echo -e "$merged_content" > "$GLOBAL_MD"
333
398
  log_ok "Merged instructions from $found_count tool(s)"
334
399
  else
335
- cat > "$INSTRUCTIONS_MD" << 'EOF'
400
+ cat > "$GLOBAL_MD" << 'EOF'
336
401
  # AI Assistant Instructions
337
402
 
338
403
  <!-- Add your instructions here. They will sync to all AI tools. -->
339
404
  EOF
340
- log_ok "Created: $INSTRUCTIONS_MD"
405
+ log_ok "Created: $GLOBAL_MD"
341
406
  fi
342
407
  }
343
408
 
@@ -348,11 +413,7 @@ update_tools() {
348
413
 
349
414
  mkdir -p "$SKILLS_DIR" "$AGENTS_DIR" "$RULES_DIR" "$COMMANDS_DIR" "$PROMPTS_DIR" "$BACKUP_DIR"
350
415
 
351
- if [[ ! -f "$INSTRUCTIONS_MD" ]]; then
352
- collect_instructions
353
- fi
354
-
355
- > "$KNOWN_TOOLS_FILE"
416
+ collect_instructions
356
417
 
357
418
  local tool_count=0
358
419
 
@@ -361,7 +422,8 @@ update_tools() {
361
422
  local full_path="$HOME/$dir_name"
362
423
 
363
424
  if [[ -d "$full_path" ]]; then
364
- log_ok "Found: $tool_name"
425
+ local color=$(get_tool_color "$tool_name")
426
+ echo -e "${GREEN}[OK]${NC} ${color}Found: $tool_name${NC}"
365
427
  ((tool_count++))
366
428
 
367
429
  if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
@@ -389,11 +451,9 @@ update_tools() {
389
451
  commands) target_dir="$COMMANDS_DIR" ;;
390
452
  prompts) target_dir="$PROMPTS_DIR" ;;
391
453
  esac
392
- merge_items "$path" "$target_dir" "$type_name"
454
+ merge_items "$path" "$target_dir" "$type_name" "$tool_name"
393
455
  fi
394
456
  done
395
-
396
- echo "$dir_name" >> "$KNOWN_TOOLS_FILE"
397
457
  fi
398
458
  done
399
459
 
@@ -405,47 +465,43 @@ update_tools() {
405
465
  echo ""
406
466
  log_info "Creating symlinks..."
407
467
 
408
- while IFS= read -r dir_name; do
409
- [[ -z "$dir_name" ]] && continue
468
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
469
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
470
+ local full_path="$HOME/$dir_name"
410
471
 
411
- for pattern in "${KNOWN_PATTERNS[@]}"; do
412
- IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
472
+ if [[ -d "$full_path" ]]; then
473
+ local tool_color=$(get_tool_color "$tool_name")
474
+ if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
475
+ local target="$HOME/$dir_name/$instr_file"
476
+ create_symlink "$GLOBAL_MD" "$target"
477
+ printf " ${tool_color}✓ %-40s -> %s${NC}\n" "$(beautify_path "$target")" "$(beautify_path "$GLOBAL_MD")"
478
+ fi
413
479
 
414
- if [[ "$dir_name" == "$known_dir" ]]; then
415
- if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
416
- local target="$HOME/$dir_name/$instr_file"
417
- create_symlink "$INSTRUCTIONS_MD" "$target"
418
- log_ok "$target -> instructions.md"
480
+ for type_name in skills agents rules commands prompts; do
481
+ local type_dir=""
482
+ local source_dir=""
483
+ case "$type_name" in
484
+ skills) type_dir="$skills"; source_dir="$SKILLS_DIR" ;;
485
+ agents) type_dir="$agents"; source_dir="$AGENTS_DIR" ;;
486
+ rules) type_dir="$rules"; source_dir="$RULES_DIR" ;;
487
+ commands) type_dir="$commands"; source_dir="$COMMANDS_DIR" ;;
488
+ prompts) type_dir="$prompts"; source_dir="$PROMPTS_DIR" ;;
489
+ esac
490
+ if [[ "$type_dir" != "." ]]; then
491
+ local target="$HOME/$dir_name/$type_dir"
492
+ create_symlink "$source_dir" "$target"
493
+ printf " ${tool_color}✓ %-40s -> %s${NC}\n" "$(beautify_path "$target")" "$(beautify_path "$source_dir")/"
419
494
  fi
420
-
421
- for type_name in skills agents rules commands prompts; do
422
- local type_dir=""
423
- local source_dir=""
424
- case "$type_name" in
425
- skills) type_dir="$skills"; source_dir="$SKILLS_DIR" ;;
426
- agents) type_dir="$agents"; source_dir="$AGENTS_DIR" ;;
427
- rules) type_dir="$rules"; source_dir="$RULES_DIR" ;;
428
- commands) type_dir="$commands"; source_dir="$COMMANDS_DIR" ;;
429
- prompts) type_dir="$prompts"; source_dir="$PROMPTS_DIR" ;;
430
- esac
431
- if [[ "$type_dir" != "." ]]; then
432
- local target="$HOME/$dir_name/$type_dir"
433
- create_symlink "$source_dir" "$target"
434
- log_ok "$target -> $type_name/"
435
- fi
436
- done
437
-
438
- break
439
- fi
440
- done
441
- done < "$KNOWN_TOOLS_FILE"
495
+ done
496
+ fi
497
+ done
442
498
 
443
499
  echo ""
444
- log_info "Done! Shared: skills=$(count_files "$SKILLS_DIR"), agents=$(count_files "$AGENTS_DIR"), rules=$(count_files "$RULES_DIR"), commands=$(count_files "$COMMANDS_DIR"), prompts=$(count_files "$PROMPTS_DIR")"
500
+ 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")"
445
501
  }
446
502
 
447
- # Restore a single tool
448
- restore_single_tool() {
503
+ # Unlink a single tool
504
+ unlink_single_tool() {
449
505
  local tool_name="$1"
450
506
  local dir_name="$2"
451
507
  local instr_file="$3"
@@ -454,19 +510,26 @@ restore_single_tool() {
454
510
  local rules="$6"
455
511
  local commands="$7"
456
512
  local prompts="$8"
513
+ local silent="${9:-false}"
457
514
 
458
- log_ok "Restoring: $tool_name"
459
515
  local backup_name=$(echo "$dir_name" | tr '/' '_')
516
+ local worked=false
460
517
 
518
+ # Check for instructions link
461
519
  if [[ "$instr_file" != "." ]]; then
462
520
  local target="$HOME/$dir_name/$instr_file"
463
521
  if [[ -L "$target" ]]; then
464
- rm "$target"
465
- local backup_file=$(ls -t "$BACKUP_DIR"/${backup_name}.instructions.* 2>/dev/null | head -1)
466
- [[ -f "$backup_file" ]] && cp "$backup_file" "$target"
522
+ local link_target=$(readlink "$target" 2>/dev/null || true)
523
+ if [[ "$link_target" == *".ai-global"* ]]; then
524
+ rm "$target"
525
+ local backup_file=$(ls -t "$BACKUP_DIR"/${backup_name}.instructions.* 2>/dev/null | head -1)
526
+ [[ -f "$backup_file" ]] && cp "$backup_file" "$target"
527
+ worked=true
528
+ fi
467
529
  fi
468
530
  fi
469
531
 
532
+ # Check for components links
470
533
  for type_name in skills agents rules commands prompts; do
471
534
  local type_dir=""
472
535
  case "$type_name" in
@@ -479,63 +542,85 @@ restore_single_tool() {
479
542
  if [[ "$type_dir" != "." ]]; then
480
543
  local target="$HOME/$dir_name/$type_dir"
481
544
  if [[ -L "$target" ]]; then
482
- rm "$target"
483
- local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.${type_name}.* 2>/dev/null | head -1)
484
- if [[ -d "$backup_file" ]]; then
485
- cp -r "$backup_file" "$target"
486
- else
487
- mkdir -p "$target"
545
+ local link_target=$(readlink "$target" 2>/dev/null || true)
546
+ if [[ "$link_target" == *".ai-global"* ]]; then
547
+ rm "$target"
548
+ local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.${type_name}.* 2>/dev/null | head -1)
549
+ if [[ -d "$backup_file" ]]; then
550
+ cp -r "$backup_file" "$target"
551
+ fi
552
+ worked=true
488
553
  fi
489
554
  fi
490
555
  fi
491
556
  done
492
- }
493
557
 
494
- # Restore all tools
495
- restore_all_tools() {
496
- log_info "Restoring all tools..."
497
- echo ""
558
+ # Check for backups persistence
559
+ local has_backups=false
560
+ if [[ -n "$(ls "$BACKUP_DIR"/${backup_name}.* 2>/dev/null)" ]]; then
561
+ has_backups=true
562
+ rm -rf "$BACKUP_DIR"/${backup_name}.* 2>/dev/null || true
563
+ fi
498
564
 
499
- if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
500
- log_info "No tools to restore"
501
- return
565
+ if [[ "$worked" == true ]] || [[ "$has_backups" == true ]]; then
566
+ if [[ "$silent" != "true" ]]; then
567
+ local color=$(get_tool_color "$tool_name")
568
+ echo -e "${GREEN}[OK]${NC} ${color}Unlinked: $tool_name${NC}"
569
+ fi
570
+ return 0
502
571
  fi
503
572
 
504
- local restored_count=0
573
+ return 1
574
+ }
505
575
 
506
- while IFS= read -r dir_name; do
507
- [[ -z "$dir_name" ]] && continue
576
+ # Unlink all tools
577
+ unlink_all_tools() {
578
+ log_info "Unlinking tools..."
579
+ echo ""
508
580
 
509
- for pattern in "${KNOWN_PATTERNS[@]}"; do
510
- IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
581
+ local unlinked_count=0
582
+ # Scan all known patterns to find and remove any symlinks
583
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
584
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
585
+ if unlink_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"; then
586
+ ((unlinked_count++))
587
+ fi
588
+ done
511
589
 
512
- if [[ "$dir_name" == "$known_dir" ]]; then
513
- restore_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"
514
- ((restored_count++))
515
- break
516
- fi
517
- done
518
- done < "$KNOWN_TOOLS_FILE"
590
+ # Final sweep for any remaining symlinks pointing to .ai-global
591
+ find "$HOME" -maxdepth 3 -type l 2>/dev/null | while read -r link; do
592
+ local target=$(readlink "$link" 2>/dev/null || true)
593
+ if [[ "$target" == *".ai-global"* ]]; then
594
+ rm "$link"
595
+ log_ok "Removed unknown symlink: $(beautify_path "$link")"
596
+ ((unlinked_count++))
597
+ fi
598
+ done
519
599
 
520
- rm -f "$KNOWN_TOOLS_FILE"
600
+ # Clear all backups as requested
601
+ rm -rf "$BACKUP_DIR"/* 2>/dev/null || true
521
602
 
522
603
  echo ""
523
- log_info "Restored $restored_count tool(s). Run 'ai-global' to re-link."
604
+ if [[ $unlinked_count -gt 0 ]]; then
605
+ log_info "Unlinked $unlinked_count item(s) and cleared backups. Shared data preserved."
606
+ else
607
+ log_info "No active symlinks found. Backups cleared."
608
+ fi
524
609
  }
525
610
 
526
- # Restore a specific tool
527
- restore_tool() {
611
+ # Unlink a specific tool
612
+ unlink_tool() {
528
613
  local tool_query="$1"
529
614
 
530
615
  if [[ -z "$tool_query" ]]; then
531
- log_error "Usage: ai-global restore <tool> or ai-global restore all"
616
+ log_error "Usage: ai-global unlink <tool> or ai-global unlink all"
532
617
  echo ""
533
618
  list_backups
534
619
  return 1
535
620
  fi
536
621
 
537
622
  if [[ "$tool_query" == "all" ]]; then
538
- restore_all_tools
623
+ unlink_all_tools
539
624
  return
540
625
  fi
541
626
 
@@ -547,15 +632,9 @@ restore_tool() {
547
632
  local query_lower=$(echo "$tool_query" | tr '[:upper:]' '[:lower:]')
548
633
 
549
634
  if [[ "$tool_lower" == *"$query_lower"* ]] || [[ "$dir_name" == *"$query_lower"* ]]; then
550
- log_info "Restoring $tool_name..."
551
- restore_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"
552
-
553
- if [[ -f "$KNOWN_TOOLS_FILE" ]]; then
554
- grep -v "^$dir_name$" "$KNOWN_TOOLS_FILE" > "$KNOWN_TOOLS_FILE.tmp" 2>/dev/null || true
555
- mv "$KNOWN_TOOLS_FILE.tmp" "$KNOWN_TOOLS_FILE"
635
+ if ! unlink_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"; then
636
+ log_info "$tool_name is not currently linked."
556
637
  fi
557
-
558
- log_info "$tool_name restored"
559
638
  found=true
560
639
  break
561
640
  fi
@@ -769,7 +848,7 @@ add_item() {
769
848
  # Uninstall
770
849
  uninstall() {
771
850
  log_warn "This will:"
772
- echo " 1. Restore all tools to original configuration"
851
+ echo " 1. Unlink all tools to original configuration"
773
852
  echo " 2. Remove ~/.ai-global directory"
774
853
  echo " 3. Remove ai-global from PATH"
775
854
  echo ""
@@ -781,9 +860,7 @@ uninstall() {
781
860
  return
782
861
  fi
783
862
 
784
- if [[ -f "$KNOWN_TOOLS_FILE" ]]; then
785
- restore_all_tools
786
- fi
863
+ unlink_all_tools
787
864
 
788
865
  [[ -L /usr/local/bin/ai-global ]] && rm -f /usr/local/bin/ai-global
789
866
  [[ -L "$HOME/.local/bin/ai-global" ]] && rm -f "$HOME/.local/bin/ai-global"
@@ -817,10 +894,16 @@ upgrade() {
817
894
 
818
895
  log_info "Upgrading: $VERSION -> $remote_version"
819
896
 
897
+ local current_script="$0"
898
+ # If running via symlink, update the target
899
+ if [[ -L "$current_script" ]]; then
900
+ current_script=$(readlink "$current_script")
901
+ fi
902
+
820
903
  local tmp_file=$(mktemp)
821
904
  if curl -fsSL "https://raw.githubusercontent.com/nanxiaobei/ai-global/main/ai-global" -o "$tmp_file" 2>/dev/null; then
822
905
  chmod +x "$tmp_file"
823
- mv "$tmp_file" "$CONFIG_DIR/ai-global"
906
+ mv "$tmp_file" "$current_script"
824
907
  log_ok "Upgraded to v$remote_version"
825
908
  else
826
909
  rm -f "$tmp_file"
@@ -831,47 +914,32 @@ upgrade() {
831
914
 
832
915
  # Show help
833
916
  show_help() {
834
- cat << EOF
835
- AI-Global: Unified AI Tools Configuration Manager v$VERSION
836
-
837
- Usage: ai-global [command]
838
-
839
- Commands:
840
- (default) Scan, merge and update symlinks
841
- status Show symlink status
842
- list List all supported AI tools
843
- backups List available backups
844
- restore <tool> Restore a tool's original config
845
- restore all Restore all tools
846
- skill <source> Add a skill (file, GitHub repo, or new)
847
- agent <source> Add an agent
848
- rule <source> Add a rule
849
- command <source> Add a command
850
- prompt <source> Add a prompt
851
- upgrade Upgrade ai-global to latest version
852
- uninstall Completely remove ai-global
853
- version Show version
854
- help Show this help
855
-
856
- Shared directories:
857
- ~/.ai-global/instructions.md Instructions for all tools
858
- ~/.ai-global/skills/ Reusable skills
859
- ~/.ai-global/agents/ Custom agents
860
- ~/.ai-global/rules/ Code rules
861
- ~/.ai-global/commands/ Slash commands
862
- ~/.ai-global/prompts/ Prompt templates
863
-
864
- Examples:
865
- ai-global # Scan, merge and update symlinks
866
- ai-global skill react.md # Add a local skill file
867
- ai-global skill user/repo # Add skills from GitHub repo (auto-detects skills/ dir)
868
- ai-global agent coder.md # Add an agent
869
- ai-global restore claude # Restore Claude's config
870
-
871
- Note: When adding from GitHub, it auto-detects type subdirectories
872
- (e.g., skills/, agents/, rules/) if they exist.
873
-
874
- EOF
917
+ echo -e "${BLUE}AI-Global: Unified AI Tools Configuration Manager${NC} v$VERSION"
918
+ echo ""
919
+ echo -e "${BLUE}USAGE:${NC}"
920
+ echo -e " ai-global [command]"
921
+ echo ""
922
+ echo -e "${BLUE}CORE COMMANDS:${NC}"
923
+ echo -e " ${GREEN}(default)${NC} Scan, merge and update symlinks"
924
+ echo -e " ${GREEN}status${NC} Show symlink status"
925
+ echo -e " ${GREEN}list${NC} List all supported AI tools"
926
+ echo -e " ${GREEN}backups${NC} List available backups"
927
+ echo -e " ${GREEN}unlink <tool>${NC} Unlink a tool's original config"
928
+ echo -e " ${GREEN}unlink all${NC} Unlink all tools"
929
+ echo ""
930
+ echo -e "${BLUE}RESOURCE MANAGEMENT:${NC}"
931
+ echo -e " ${GREEN}skill <source>${NC} Add a skill (file, GitHub repo, or new)"
932
+ echo -e " ${GREEN}agent <source>${NC} Add an agent"
933
+ echo -e " ${GREEN}rule <source>${NC} Add a rule"
934
+ echo -e " ${GREEN}command <source>${NC} Add a command"
935
+ echo -e " ${GREEN}prompt <source>${NC} Add a prompt"
936
+ echo ""
937
+ echo -e "${BLUE}SYSTEM COMMANDS:${NC}"
938
+ echo -e " ${GREEN}upgrade${NC} Upgrade ai-global to latest version"
939
+ echo -e " ${GREEN}uninstall${NC} Completely remove ai-global"
940
+ echo -e " ${GREEN}version${NC} Show version"
941
+ echo -e " ${GREEN}help${NC} Show this help"
942
+ echo ""
875
943
  }
876
944
 
877
945
  # Main
@@ -883,26 +951,27 @@ main() {
883
951
  exit 0
884
952
  fi
885
953
 
886
- if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
887
- case "$cmd" in
888
- help|--help|-h) show_help; exit 0 ;;
889
- list) list_supported; exit 0 ;;
890
- version|-v|--version) show_version; exit 0 ;;
891
- skill|agent|rule|command|prompt|restore|status|backups)
892
- log_info "No tools configured yet. Running initial scan..."
954
+ case "$cmd" in
955
+ help|--help|-h) show_help; exit 0 ;;
956
+ list) list_supported; exit 0 ;;
957
+ version|-v|--version) show_version; exit 0 ;;
958
+ skill|agent|rule|command|prompt|unlink|status|backups|upgrade|uninstall)
959
+ if [[ ! -d "$CONFIG_DIR" ]]; then
960
+ log_info "No configuration found. Running initial scan..."
893
961
  update_tools
894
- [[ "$cmd" == "skill" || "$cmd" == "agent" || "$cmd" == "rule" || "$cmd" == "command" || "$cmd" == "prompt" ]] || exit 0
895
- ;;
896
- *) cmd="update" ;;
897
- esac
898
- fi
962
+ [[ "$cmd" == "skill" || "$cmd" == "agent" || "$cmd" == "rule" || "$cmd" == "command" || "$cmd" == "prompt" || "$cmd" == "status" ]] || exit 0
963
+ fi
964
+ ;;
965
+ version|-v|--version|help|--help|-h) ;;
966
+ *) cmd="update" ;;
967
+ esac
899
968
 
900
969
  case "$cmd" in
901
970
  update) update_tools ;;
902
971
  status) show_status ;;
903
972
  list) list_supported ;;
904
973
  backups) list_backups ;;
905
- restore) restore_tool "$2" ;;
974
+ unlink) unlink_tool "$2" ;;
906
975
  skill) add_item "skill" "$2" ;;
907
976
  agent) add_item "agent" "$2" ;;
908
977
  rule) add_item "rule" "$2" ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-global",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Unified configuration manager for AI coding assistants",
5
5
  "bin": {
6
6
  "ai-global": "ai-global"