ai-global 1.0.0 → 1.2.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 +72 -70
  2. package/ai-global +380 -273
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # AI Config
1
+ # AI Global
2
2
 
3
3
  [中文文档](README_CN.md)
4
4
 
@@ -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
@@ -83,30 +84,30 @@ This will:
83
84
  ### Edit instructions
84
85
 
85
86
  ```
86
- ~/.ai-global/instructions.md
87
+ ~/.ai-global/global.md
87
88
  ```
88
89
 
89
90
  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
 
@@ -130,7 +131,7 @@ ai-global skill user/repo/skills/react.md
130
131
 
131
132
  ```
132
133
  ~/.ai-global/
133
- ├── instructions.md <- Shared instructions (edit this)
134
+ ├── global.md <- Shared instructions (edit this)
134
135
  ├── skills/ <- Shared skills (merged from all tools)
135
136
  ├── agents/ <- Shared agents
136
137
  ├── rules/ <- Shared rules
@@ -141,12 +142,12 @@ ai-global skill user/repo/skills/react.md
141
142
  Each AI tool's config directory contains symlinks:
142
143
 
143
144
  ~/.claude/
144
- ├── CLAUDE.md -> ~/.ai-global/instructions.md (symlink)
145
+ ├── CLAUDE.md -> ~/.ai-global/global.md (symlink)
145
146
  ├── skills/ -> ~/.ai-global/skills/ (symlink)
146
147
  └── commands/ -> ~/.ai-global/commands/ (symlink)
147
148
 
148
149
  ~/.cursor/
149
- ├── rules/global.md -> ~/.ai-global/instructions.md (symlink)
150
+ ├── rules/global.md -> ~/.ai-global/global.md (symlink)
150
151
  ├── skills/ -> ~/.ai-global/skills/ (symlink)
151
152
  └── prompts/ -> ~/.ai-global/prompts/ (symlink)
152
153
 
@@ -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
 
@@ -181,7 +183,7 @@ ai-global uninstall
181
183
  npm uninstall -g ai-global
182
184
  ```
183
185
 
184
- ## Example instructions.md
186
+ ## Example global.md
185
187
 
186
188
  ```markdown
187
189
  # AI Assistant Instructions
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,67 @@ 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
+ local len=${#name}
79
+ for (( i=0; i<len; i++ )); do
80
+ sum=$((sum + $(printf '%d' "'${name:i:1}")))
81
+ done
82
+ echo -e "${TOOL_COLORS[$((sum % ${#TOOL_COLORS[@]}))]}"
83
+ }
84
+
85
+ # Extract name from frontmatter (name: "...")
86
+ extract_meta_name() {
87
+ local file="$1"
88
+ local default_name="$2"
89
+ if [[ ! -f "$file" ]]; then
90
+ echo "$default_name"
91
+ return
92
+ fi
93
+ # Match name: "value" or name: value
94
+ local extracted=$(grep -m 1 "^name:" "$file" | sed -E 's/^name:[[:space:]]*["'\'']?([^"'\'']+)["'\'']?/\1/' | xargs 2>/dev/null || true)
95
+ if [[ -n "$extracted" ]]; then
96
+ echo "$extracted"
97
+ else
98
+ echo "$default_name"
99
+ fi
100
+ }
101
+
44
102
  log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
45
103
  log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
46
104
  log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
@@ -81,11 +139,11 @@ declare -a KNOWN_PATTERNS=(
81
139
  ".blackbox|Blackbox AI|config.json|.|.|.|.|."
82
140
  ".amazonq|Amazon Q|instructions.md|.|.|.|.|."
83
141
  ".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"
142
+ ".codex|OpenAI Codex|instructions.md|skills|agents|rules|.|."
143
+ ".goose|Goose AI|instructions.md|skills|agents|rules|.|."
144
+ ".mentat|Mentat|instructions.md|skills|agents|rules|.|."
145
+ ".gpt-engineer|GPT Engineer|instructions.md|skills|agents|rules|.|prompts"
146
+ ".smol|Smol Developer|instructions.md|skills|agents|rules|.|prompts"
89
147
  ".config/opencode|OpenCode Config|instructions.md|.|.|.|.|."
90
148
  )
91
149
 
@@ -118,6 +176,7 @@ merge_items() {
118
176
  local source_dir="$1"
119
177
  local target_dir="$2"
120
178
  local type="$3"
179
+ local tool_name="$4"
121
180
 
122
181
  [[ ! -d "$source_dir" ]] && return
123
182
  [[ -L "$source_dir" ]] && return
@@ -140,7 +199,18 @@ merge_items() {
140
199
  done
141
200
 
142
201
  if [[ $merged_count -gt 0 ]]; then
143
- log_ok "Merged $merged_count $type from $source_dir"
202
+ local tool_color=$(get_tool_color "$tool_name")
203
+ log_ok "Merged $merged_count $type from ${tool_color}${tool_name}${NC}"
204
+ fi
205
+ }
206
+
207
+ # Count items in directory (dirs and files)
208
+ count_items() {
209
+ local dir="$1"
210
+ if [[ -d "$dir" ]]; then
211
+ ls -1 "$dir" 2>/dev/null | wc -l | tr -d ' '
212
+ else
213
+ echo "0"
144
214
  fi
145
215
  }
146
216
 
@@ -154,118 +224,126 @@ create_symlink() {
154
224
  local target_dir=$(dirname "$target")
155
225
  mkdir -p "$target_dir"
156
226
 
227
+ # If target exists and is a real file/dir, it should have been backed up by backup_item already.
228
+ # We remove it to make room for the symlink, avoiding in-place backups.
157
229
  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"
230
+ rm -rf "$target"
161
231
  fi
162
232
 
163
233
  [[ -L "$target" ]] && rm "$target"
164
234
  ln -s "$source" "$target"
165
235
  }
166
236
 
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
237
  # Show symlink status
178
238
  show_status() {
179
239
  log_info "Symlink status:"
180
240
  echo ""
181
241
 
182
- if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
183
- log_info "No tools configured. Run 'ai-global' first."
184
- return
242
+ local total_links=0
243
+
244
+ # Instructions
245
+ local instr_output=""
246
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
247
+ local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
248
+ IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
249
+
250
+ if [[ "$p_instr" != "." ]] && [[ "$p_instr" != *.json ]] && [[ "$p_instr" != *.yml ]]; then
251
+ local target="$HOME/$p_dir/$p_instr"
252
+ if [[ -L "$target" ]]; then
253
+ local link_target=$(readlink "$target" 2>/dev/null || true)
254
+ if [[ "$link_target" == *".ai-global"* ]]; then
255
+ local tool_color=$(get_tool_color "$p_name")
256
+ instr_output+=" ${tool_color}✓ $(beautify_path "$target")${NC}\n"
257
+ ((total_links++))
258
+ fi
259
+ fi
260
+ fi
261
+ done
262
+
263
+ if [[ -n "$instr_output" ]]; then
264
+ echo -e "${BLUE}[global.md]${NC}"
265
+ echo -e -n "$instr_output"
185
266
  fi
186
267
 
187
- echo -e "${BLUE}[instructions]${NC}"
188
- while IFS= read -r dir_name; do
189
- [[ -z "$dir_name" ]] && continue
268
+ for type_name in skills agents rules commands prompts; do
269
+ local type_output=""
190
270
  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)"
271
+ local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
272
+ IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
273
+
274
+ local type_dir=""
275
+ case "$type_name" in
276
+ skills) type_dir="$p_skills" ;;
277
+ agents) type_dir="$p_agents" ;;
278
+ rules) type_dir="$p_rules" ;;
279
+ commands) type_dir="$p_cmds" ;;
280
+ prompts) type_dir="$p_prompts" ;;
281
+ esac
282
+
283
+ if [[ "$type_dir" != "." ]]; then
284
+ local target="$HOME/$p_dir/$type_dir"
285
+ if [[ -L "$target" ]]; then
286
+ local link_target=$(readlink "$target" 2>/dev/null || true)
287
+ if [[ "$link_target" == *".ai-global"* ]]; then
288
+ local tool_color=$(get_tool_color "$p_name")
289
+ type_output+=" ${tool_color}✓ $(beautify_path "$target")${NC}\n"
290
+ ((total_links++))
201
291
  fi
202
292
  fi
203
- break
204
293
  fi
205
294
  done
206
- done < "$KNOWN_TOOLS_FILE"
207
295
 
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"
296
+ if [[ -n "$type_output" ]]; then
297
+ echo -e "\n${BLUE}[$type_name]${NC}"
298
+ echo -e -n "$type_output"
299
+ fi
237
300
  done
238
301
 
302
+ if [[ $total_links -eq 0 ]]; then
303
+ echo " No active symlinks found."
304
+ fi
305
+
239
306
  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")"
307
+ 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
308
  }
242
309
 
243
310
  # List supported tools
244
311
  list_supported() {
245
312
  log_info "Supported AI tools:"
246
313
  echo ""
247
- printf " ${BLUE}%-22s${NC} %-20s %-6s %-6s %-6s %-6s %-6s %s\n" "Directory" "Tool" "Skills" "Agents" "Rules" "Cmds" "Prompts" "Status"
248
- echo " ----------------------------------------------------------------------------------------------------------------"
314
+ printf " ${BLUE}%-22s %-20s %-10s %-10s %-10s %-10s %-10s %s${NC}\n" "Directory" "Tool" "Skills" "Agents" "Rules" "Cmds" "Prompts" "Status"
315
+ echo " --------------------------------------------------------------------------------------------------------------------------------"
249
316
 
250
317
  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"
318
+ local p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts
319
+ IFS='|' read -r p_dir p_name p_instr p_skills p_agents p_rules p_cmds p_prompts <<< "$pattern"
320
+ local full_path="$HOME/$p_dir"
253
321
 
254
- local s="." a="." r="." c="." p="."
255
- [[ "$skills" != "." ]] && s="✓"
256
- [[ "$agents" != "." ]] && a="✓"
257
- [[ "$rules" != "." ]] && r="✓"
258
- [[ "$commands" != "." ]] && c="✓"
259
- [[ "$prompts" != "." ]] && p="✓"
322
+ local s_str="." a_str="." r_str="." c_str="." p_str="."
323
+ if [[ -d "$full_path" ]]; then
324
+ [[ "$p_skills" != "." && -d "$full_path/$p_skills" ]] && s_str="✓"
325
+ [[ "$p_agents" != "." && -d "$full_path/$p_agents" ]] && a_str="✓"
326
+ [[ "$p_rules" != "." && -d "$full_path/$p_rules" ]] && r_str="✓"
327
+ [[ "$p_cmds" != "." && -d "$full_path/$p_cmds" ]] && c_str="✓"
328
+ # Prompts can be a file or dir
329
+ [[ "$p_prompts" != "." && -e "$full_path/$p_prompts" ]] && p_str="✓"
330
+ fi
260
331
 
261
332
  local status=""
333
+ local tool_color=""
262
334
  if [[ -d "$full_path" ]]; then
263
335
  status="${GREEN}Installed${NC}"
336
+ tool_color=$(get_tool_color "$p_name")
264
337
  else
265
338
  status="${YELLOW}Not found${NC}"
339
+ tool_color="${NC}"
266
340
  fi
267
341
 
268
- printf " %-22s %-20s %-6s %-6s %-6s %-6s %-6s %b\n" "$dir_name" "$tool_name" "$s" "$a" "$r" "$c" "$p" "$status"
342
+ # Use manual padding because printf handles multibyte characters (✓) by byte count in Bash 3.2.
343
+ # Each category block matches the header's "%-10s " (11 characters total).
344
+ # We use indicator + 10 spaces = 11 characters.
345
+ printf " %b%-22s %-20s%b %s %s %s %s %s %b\n" \
346
+ "$tool_color" "$p_dir" "$p_name" "$NC" "$s_str" "$a_str" "$r_str" "$c_str" "$p_str" "$status"
269
347
  done
270
348
  echo ""
271
349
  }
@@ -275,22 +353,25 @@ list_backups() {
275
353
  log_info "Available backups:"
276
354
  echo ""
277
355
 
278
- if [[ ! -d "$BACKUP_DIR" ]] || [[ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]]; then
356
+ # Use ls -A to catch hidden files/dirs (starting with .)
357
+ local backups_list=$(ls -A "$BACKUP_DIR" 2>/dev/null || true)
358
+
359
+ if [[ -z "$backups_list" ]]; then
279
360
  echo " No backups found"
280
361
  echo ""
281
362
  return
282
363
  fi
283
364
 
284
- printf " ${BLUE}%-25s${NC} %-12s %s\n" "Tool" "Type" "Backup File"
285
- echo " ----------------------------------------------------------------"
365
+ printf " ${BLUE}%-25s %-12s %s${NC}\n" "Tool" "Type" "Backup File"
366
+ echo " --------------------------------------------------------------------"
286
367
 
287
- for backup in "$BACKUP_DIR"/*; do
288
- [[ ! -e "$backup" ]] && continue
289
- local filename=$(basename "$backup")
368
+ while read -r filename; do
369
+ [[ -z "$filename" ]] && continue
290
370
  local tool_name=""
291
371
  local type=""
292
372
 
293
- if [[ "$filename" =~ ^(.+)\.([^.]+)\.([0-9]+)$ ]]; then
373
+ # Improved regex to handle various path characters
374
+ if [[ "$filename" =~ ^(.+)\.([^\.]+)\.([0-9]+)$ ]]; then
294
375
  tool_name="${BASH_REMATCH[1]}"
295
376
  type="${BASH_REMATCH[2]}"
296
377
  else
@@ -298,8 +379,9 @@ list_backups() {
298
379
  type="unknown"
299
380
  fi
300
381
 
301
- printf " %-25s %-12s %s\n" "$tool_name" "$type" "$filename"
302
- done
382
+ local tool_color=$(get_tool_color "${tool_name//_/ }")
383
+ printf " %s%-25s %-12s %s%b\n" "$tool_color" "$tool_name" "$type" "$filename" "$NC"
384
+ done <<< "$backups_list"
303
385
  echo ""
304
386
  }
305
387
 
@@ -329,15 +411,15 @@ collect_instructions() {
329
411
  done
330
412
 
331
413
  if [[ $found_count -gt 0 ]]; then
332
- echo -e "$merged_content" > "$INSTRUCTIONS_MD"
414
+ echo -e "$merged_content" > "$GLOBAL_MD"
333
415
  log_ok "Merged instructions from $found_count tool(s)"
334
416
  else
335
- cat > "$INSTRUCTIONS_MD" << 'EOF'
417
+ cat > "$GLOBAL_MD" << 'EOF'
336
418
  # AI Assistant Instructions
337
419
 
338
420
  <!-- Add your instructions here. They will sync to all AI tools. -->
339
421
  EOF
340
- log_ok "Created: $INSTRUCTIONS_MD"
422
+ log_ok "Created: $GLOBAL_MD"
341
423
  fi
342
424
  }
343
425
 
@@ -348,11 +430,7 @@ update_tools() {
348
430
 
349
431
  mkdir -p "$SKILLS_DIR" "$AGENTS_DIR" "$RULES_DIR" "$COMMANDS_DIR" "$PROMPTS_DIR" "$BACKUP_DIR"
350
432
 
351
- if [[ ! -f "$INSTRUCTIONS_MD" ]]; then
352
- collect_instructions
353
- fi
354
-
355
- > "$KNOWN_TOOLS_FILE"
433
+ collect_instructions
356
434
 
357
435
  local tool_count=0
358
436
 
@@ -361,7 +439,8 @@ update_tools() {
361
439
  local full_path="$HOME/$dir_name"
362
440
 
363
441
  if [[ -d "$full_path" ]]; then
364
- log_ok "Found: $tool_name"
442
+ local color=$(get_tool_color "$tool_name")
443
+ echo -e "${GREEN}[OK]${NC} ${color}Found: $tool_name${NC}"
365
444
  ((tool_count++))
366
445
 
367
446
  if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
@@ -389,11 +468,9 @@ update_tools() {
389
468
  commands) target_dir="$COMMANDS_DIR" ;;
390
469
  prompts) target_dir="$PROMPTS_DIR" ;;
391
470
  esac
392
- merge_items "$path" "$target_dir" "$type_name"
471
+ merge_items "$path" "$target_dir" "$type_name" "$tool_name"
393
472
  fi
394
473
  done
395
-
396
- echo "$dir_name" >> "$KNOWN_TOOLS_FILE"
397
474
  fi
398
475
  done
399
476
 
@@ -405,47 +482,43 @@ update_tools() {
405
482
  echo ""
406
483
  log_info "Creating symlinks..."
407
484
 
408
- while IFS= read -r dir_name; do
409
- [[ -z "$dir_name" ]] && continue
485
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
486
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
487
+ local full_path="$HOME/$dir_name"
410
488
 
411
- for pattern in "${KNOWN_PATTERNS[@]}"; do
412
- IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
489
+ if [[ -d "$full_path" ]]; then
490
+ local tool_color=$(get_tool_color "$tool_name")
491
+ if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
492
+ local target="$HOME/$dir_name/$instr_file"
493
+ create_symlink "$GLOBAL_MD" "$target"
494
+ printf " ${tool_color}✓ %-40s -> %s${NC}\n" "$(beautify_path "$target")" "$(beautify_path "$GLOBAL_MD")"
495
+ fi
413
496
 
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"
497
+ for type_name in skills agents rules commands prompts; do
498
+ local type_dir=""
499
+ local source_dir=""
500
+ case "$type_name" in
501
+ skills) type_dir="$skills"; source_dir="$SKILLS_DIR" ;;
502
+ agents) type_dir="$agents"; source_dir="$AGENTS_DIR" ;;
503
+ rules) type_dir="$rules"; source_dir="$RULES_DIR" ;;
504
+ commands) type_dir="$commands"; source_dir="$COMMANDS_DIR" ;;
505
+ prompts) type_dir="$prompts"; source_dir="$PROMPTS_DIR" ;;
506
+ esac
507
+ if [[ "$type_dir" != "." ]]; then
508
+ local target="$HOME/$dir_name/$type_dir"
509
+ create_symlink "$source_dir" "$target"
510
+ printf " ${tool_color}✓ %-40s -> %s${NC}\n" "$(beautify_path "$target")" "$(beautify_path "$source_dir")/"
419
511
  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"
512
+ done
513
+ fi
514
+ done
442
515
 
443
516
  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")"
517
+ 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
518
  }
446
519
 
447
- # Restore a single tool
448
- restore_single_tool() {
520
+ # Unlink a single tool
521
+ unlink_single_tool() {
449
522
  local tool_name="$1"
450
523
  local dir_name="$2"
451
524
  local instr_file="$3"
@@ -454,19 +527,26 @@ restore_single_tool() {
454
527
  local rules="$6"
455
528
  local commands="$7"
456
529
  local prompts="$8"
530
+ local silent="${9:-false}"
457
531
 
458
- log_ok "Restoring: $tool_name"
459
532
  local backup_name=$(echo "$dir_name" | tr '/' '_')
533
+ local worked=false
460
534
 
535
+ # Check for instructions link
461
536
  if [[ "$instr_file" != "." ]]; then
462
537
  local target="$HOME/$dir_name/$instr_file"
463
538
  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"
539
+ local link_target=$(readlink "$target" 2>/dev/null || true)
540
+ if [[ "$link_target" == *".ai-global"* ]]; then
541
+ rm "$target"
542
+ local backup_file=$(ls -t "$BACKUP_DIR"/${backup_name}.instructions.* 2>/dev/null | head -1)
543
+ [[ -f "$backup_file" ]] && cp "$backup_file" "$target"
544
+ worked=true
545
+ fi
467
546
  fi
468
547
  fi
469
548
 
549
+ # Check for components links
470
550
  for type_name in skills agents rules commands prompts; do
471
551
  local type_dir=""
472
552
  case "$type_name" in
@@ -479,63 +559,85 @@ restore_single_tool() {
479
559
  if [[ "$type_dir" != "." ]]; then
480
560
  local target="$HOME/$dir_name/$type_dir"
481
561
  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"
562
+ local link_target=$(readlink "$target" 2>/dev/null || true)
563
+ if [[ "$link_target" == *".ai-global"* ]]; then
564
+ rm "$target"
565
+ local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.${type_name}.* 2>/dev/null | head -1)
566
+ if [[ -d "$backup_file" ]]; then
567
+ cp -r "$backup_file" "$target"
568
+ fi
569
+ worked=true
488
570
  fi
489
571
  fi
490
572
  fi
491
573
  done
492
- }
493
574
 
494
- # Restore all tools
495
- restore_all_tools() {
496
- log_info "Restoring all tools..."
497
- echo ""
575
+ # Check for backups persistence
576
+ local has_backups=false
577
+ if [[ -n "$(ls "$BACKUP_DIR"/${backup_name}.* 2>/dev/null)" ]]; then
578
+ has_backups=true
579
+ rm -rf "$BACKUP_DIR"/${backup_name}.* 2>/dev/null || true
580
+ fi
498
581
 
499
- if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
500
- log_info "No tools to restore"
501
- return
582
+ if [[ "$worked" == true ]] || [[ "$has_backups" == true ]]; then
583
+ if [[ "$silent" != "true" ]]; then
584
+ local color=$(get_tool_color "$tool_name")
585
+ echo -e "${GREEN}[OK]${NC} ${color}Unlinked: $tool_name${NC}"
586
+ fi
587
+ return 0
502
588
  fi
503
589
 
504
- local restored_count=0
590
+ return 1
591
+ }
505
592
 
506
- while IFS= read -r dir_name; do
507
- [[ -z "$dir_name" ]] && continue
593
+ # Unlink all tools
594
+ unlink_all_tools() {
595
+ log_info "Unlinking tools..."
596
+ echo ""
508
597
 
509
- for pattern in "${KNOWN_PATTERNS[@]}"; do
510
- IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
598
+ local unlinked_count=0
599
+ # Scan all known patterns to find and remove any symlinks
600
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
601
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
602
+ if unlink_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"; then
603
+ ((unlinked_count++))
604
+ fi
605
+ done
511
606
 
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"
607
+ # Final sweep for any remaining symlinks pointing to .ai-global
608
+ find "$HOME" -maxdepth 3 -type l 2>/dev/null | while read -r link; do
609
+ local target=$(readlink "$link" 2>/dev/null || true)
610
+ if [[ "$target" == *".ai-global"* ]]; then
611
+ rm "$link"
612
+ log_ok "Removed unknown symlink: $(beautify_path "$link")"
613
+ ((unlinked_count++))
614
+ fi
615
+ done
519
616
 
520
- rm -f "$KNOWN_TOOLS_FILE"
617
+ # Clear all backups as requested
618
+ rm -rf "$BACKUP_DIR"/* 2>/dev/null || true
521
619
 
522
620
  echo ""
523
- log_info "Restored $restored_count tool(s). Run 'ai-global' to re-link."
621
+ if [[ $unlinked_count -gt 0 ]]; then
622
+ log_info "Unlinked $unlinked_count item(s) and cleared backups. Shared data preserved."
623
+ else
624
+ log_info "No active symlinks found. Backups cleared."
625
+ fi
524
626
  }
525
627
 
526
- # Restore a specific tool
527
- restore_tool() {
628
+ # Unlink a specific tool
629
+ unlink_tool() {
528
630
  local tool_query="$1"
529
631
 
530
632
  if [[ -z "$tool_query" ]]; then
531
- log_error "Usage: ai-global restore <tool> or ai-global restore all"
633
+ log_error "Usage: ai-global unlink <tool> or ai-global unlink all"
532
634
  echo ""
533
635
  list_backups
534
636
  return 1
535
637
  fi
536
638
 
537
639
  if [[ "$tool_query" == "all" ]]; then
538
- restore_all_tools
640
+ unlink_all_tools
539
641
  return
540
642
  fi
541
643
 
@@ -547,15 +649,9 @@ restore_tool() {
547
649
  local query_lower=$(echo "$tool_query" | tr '[:upper:]' '[:lower:]')
548
650
 
549
651
  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"
652
+ if ! unlink_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"; then
653
+ log_info "$tool_name is not currently linked."
556
654
  fi
557
-
558
- log_info "$tool_name restored"
559
655
  found=true
560
656
  break
561
657
  fi
@@ -643,53 +739,74 @@ download_from_github() {
643
739
  [[ -n "$path" ]] && source_dir="$tmp_dir/$repo/$path"
644
740
 
645
741
  if [[ -d "$source_dir" ]]; then
646
- # Auto-detect type subdirectory (e.g., skills/, agents/, rules/)
647
- if [[ -z "$path" ]]; then
648
- local type_dirs=""
742
+ local count=0
743
+ local meta_file=""
744
+ case "$type" in
745
+ skill) meta_file="SKILL.md" ;;
746
+ agent) meta_file="AGENT.md" ;;
747
+ esac
748
+
749
+ # 1. Check if root contains metadata file
750
+ if [[ -n "$meta_file" ]] && [[ -f "$source_dir/$meta_file" ]]; then
751
+ local name=$(extract_meta_name "$source_dir/$meta_file" "$(basename "$source_dir")")
752
+ local target_path="$target_dir/$name"
753
+ mkdir -p "$target_path"
754
+ cp -R "$source_dir"/* "$target_path/"
755
+ count=1
756
+ fi
757
+
758
+ # 2. Check type subdirectories (skills/, agents/)
759
+ if [[ $count -eq 0 ]] && [[ -z "$path" ]]; then
760
+ local search_dirs=""
649
761
  case "$type" in
650
- skill) type_dirs="skills skill" ;;
651
- agent) type_dirs="agents agent" ;;
652
- rule) type_dirs="rules rule" ;;
653
- command) type_dirs="commands command" ;;
654
- prompt) type_dirs="prompts prompt" ;;
762
+ skill) search_dirs="skills skill" ;;
763
+ agent) search_dirs="agents agent" ;;
764
+ rule) search_dirs="rules rule" ;;
655
765
  esac
656
766
 
657
- # Prefer type subdirectory, fallback to root
658
- for dir in $type_dirs; do
767
+ for dir in $search_dirs; do
659
768
  if [[ -d "$source_dir/$dir" ]]; then
660
- source_dir="$source_dir/$dir"
661
- log_info "Found $dir/ directory"
662
- break
769
+ # Look for subdirectories containing SKILL.md/AGENT.md
770
+ if [[ -n "$meta_file" ]]; then
771
+ for d in "$source_dir/$dir"/*; do
772
+ [[ ! -d "$d" ]] && continue
773
+ if [[ -f "$d/$meta_file" ]]; then
774
+ local name=$(extract_meta_name "$d/$meta_file" "$(basename "$d")")
775
+ mkdir -p "$target_dir/$name"
776
+ cp -R "$d"/* "$target_dir/$name/"
777
+ ((count++))
778
+ fi
779
+ done
780
+ fi
781
+
782
+ # If we found items or if it's "rules" (no metadata file needed usually), we are done with this dir
783
+ if [[ $count -gt 0 ]] || [[ "$type" == "rule" ]]; then
784
+ source_dir="$source_dir/$dir"
785
+ break
786
+ fi
663
787
  fi
664
788
  done
665
789
  fi
666
790
 
667
- local count=0
668
-
669
- # Special case: many skill repos are structured as:
670
- # skills/<skill-name>/SKILL.md
671
- if [[ "$type" == "skill" ]] && [[ "$(basename "$source_dir")" == "skills" ]]; then
672
- for d in "$source_dir"/*; do
673
- [[ ! -d "$d" ]] && continue
674
- local skill_md="$d/SKILL.md"
675
- [[ ! -f "$skill_md" ]] && continue
676
-
677
- local skill_name=$(basename "$d")
678
-
679
- # Copy the entire skill directory so assets/examples are preserved
680
- local target_skill_dir="$target_dir/$skill_name"
681
- mkdir -p "$target_skill_dir"
682
- cp -R "$d/" "$target_skill_dir/"
683
-
684
- ((count++))
685
- done
791
+ # 3. Check src/ directory if still nothing (for skills)
792
+ if [[ $count -eq 0 ]] && [[ "$type" == "skill" ]] && [[ -d "$source_dir/src" ]]; then
793
+ if [[ -f "$source_dir/src/$meta_file" ]]; then
794
+ local name=$(extract_meta_name "$source_dir/src/$meta_file" "$(basename "$source_dir")")
795
+ mkdir -p "$target_dir/$name"
796
+ cp -R "$source_dir/src"/* "$target_dir/$name/"
797
+ count=1
798
+ fi
686
799
  fi
687
800
 
688
- # Fallback: copy top-level .md files from the selected source directory
689
- if [[ $count -eq 0 ]]; then
801
+ # Fallback check (rules only): copy individual .md files
802
+ if [[ $count -eq 0 ]] && [[ "$type" == "rule" ]]; then
690
803
  for file in "$source_dir"/*.md; do
691
804
  [[ ! -f "$file" ]] && continue
692
805
  local filename=$(basename "$file")
806
+ if [[ "$filename" == "README.md" ]]; then
807
+ local other_mds=$(ls "$source_dir"/*.md 2>/dev/null | grep -v "README.md" | wc -l)
808
+ [[ $other_mds -gt 0 ]] && continue
809
+ fi
693
810
  cp "$file" "$target_dir/$filename"
694
811
  ((count++))
695
812
  done
@@ -701,7 +818,7 @@ download_from_github() {
701
818
  # Show actual searched path
702
819
  local searched_path="${source_dir#$tmp_dir/$repo}"
703
820
  searched_path="${searched_path#/}"
704
- log_warn "No skills found in $owner/$repo${searched_path:+/$searched_path}"
821
+ log_warn "No $type found organized in $owner/$repo${searched_path:+/$searched_path}"
705
822
  fi
706
823
  else
707
824
  log_error "Path not found: $path"
@@ -769,7 +886,7 @@ add_item() {
769
886
  # Uninstall
770
887
  uninstall() {
771
888
  log_warn "This will:"
772
- echo " 1. Restore all tools to original configuration"
889
+ echo " 1. Unlink all tools to original configuration"
773
890
  echo " 2. Remove ~/.ai-global directory"
774
891
  echo " 3. Remove ai-global from PATH"
775
892
  echo ""
@@ -781,9 +898,7 @@ uninstall() {
781
898
  return
782
899
  fi
783
900
 
784
- if [[ -f "$KNOWN_TOOLS_FILE" ]]; then
785
- restore_all_tools
786
- fi
901
+ unlink_all_tools
787
902
 
788
903
  [[ -L /usr/local/bin/ai-global ]] && rm -f /usr/local/bin/ai-global
789
904
  [[ -L "$HOME/.local/bin/ai-global" ]] && rm -f "$HOME/.local/bin/ai-global"
@@ -817,10 +932,16 @@ upgrade() {
817
932
 
818
933
  log_info "Upgrading: $VERSION -> $remote_version"
819
934
 
935
+ local current_script="$0"
936
+ # If running via symlink, update the target
937
+ if [[ -L "$current_script" ]]; then
938
+ current_script=$(readlink "$current_script")
939
+ fi
940
+
820
941
  local tmp_file=$(mktemp)
821
942
  if curl -fsSL "https://raw.githubusercontent.com/nanxiaobei/ai-global/main/ai-global" -o "$tmp_file" 2>/dev/null; then
822
943
  chmod +x "$tmp_file"
823
- mv "$tmp_file" "$CONFIG_DIR/ai-global"
944
+ mv "$tmp_file" "$current_script"
824
945
  log_ok "Upgraded to v$remote_version"
825
946
  else
826
947
  rm -f "$tmp_file"
@@ -831,47 +952,32 @@ upgrade() {
831
952
 
832
953
  # Show help
833
954
  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
955
+ echo -e "${BLUE}AI-Global: Unified AI Tools Configuration Manager${NC} v$VERSION"
956
+ echo ""
957
+ echo -e "${BLUE}USAGE:${NC}"
958
+ echo -e " ai-global [command]"
959
+ echo ""
960
+ echo -e "${BLUE}CORE COMMANDS:${NC}"
961
+ echo -e " ${GREEN}(default)${NC} Scan, merge and update symlinks"
962
+ echo -e " ${GREEN}status${NC} Show symlink status"
963
+ echo -e " ${GREEN}list${NC} List all supported AI tools"
964
+ echo -e " ${GREEN}backups${NC} List available backups"
965
+ echo -e " ${GREEN}unlink <tool>${NC} Unlink a tool's original config"
966
+ echo -e " ${GREEN}unlink all${NC} Unlink all tools"
967
+ echo ""
968
+ echo -e "${BLUE}RESOURCE MANAGEMENT:${NC}"
969
+ echo -e " ${GREEN}skill <source>${NC} Add a skill (file, GitHub repo, or new)"
970
+ echo -e " ${GREEN}agent <source>${NC} Add an agent"
971
+ echo -e " ${GREEN}rule <source>${NC} Add a rule"
972
+ echo -e " ${GREEN}command <source>${NC} Add a command"
973
+ echo -e " ${GREEN}prompt <source>${NC} Add a prompt"
974
+ echo ""
975
+ echo -e "${BLUE}SYSTEM COMMANDS:${NC}"
976
+ echo -e " ${GREEN}upgrade${NC} Upgrade ai-global to latest version"
977
+ echo -e " ${GREEN}uninstall${NC} Completely remove ai-global"
978
+ echo -e " ${GREEN}version${NC} Show version"
979
+ echo -e " ${GREEN}help${NC} Show this help"
980
+ echo ""
875
981
  }
876
982
 
877
983
  # Main
@@ -883,26 +989,27 @@ main() {
883
989
  exit 0
884
990
  fi
885
991
 
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..."
992
+ case "$cmd" in
993
+ help|--help|-h) show_help; exit 0 ;;
994
+ list) list_supported; exit 0 ;;
995
+ version|-v|--version) show_version; exit 0 ;;
996
+ skill|agent|rule|command|prompt|unlink|status|backups|upgrade|uninstall)
997
+ if [[ ! -d "$CONFIG_DIR" ]]; then
998
+ log_info "No configuration found. Running initial scan..."
893
999
  update_tools
894
- [[ "$cmd" == "skill" || "$cmd" == "agent" || "$cmd" == "rule" || "$cmd" == "command" || "$cmd" == "prompt" ]] || exit 0
895
- ;;
896
- *) cmd="update" ;;
897
- esac
898
- fi
1000
+ [[ "$cmd" == "skill" || "$cmd" == "agent" || "$cmd" == "rule" || "$cmd" == "command" || "$cmd" == "prompt" || "$cmd" == "status" ]] || exit 0
1001
+ fi
1002
+ ;;
1003
+ version|-v|--version|help|--help|-h) ;;
1004
+ *) cmd="update" ;;
1005
+ esac
899
1006
 
900
1007
  case "$cmd" in
901
1008
  update) update_tools ;;
902
1009
  status) show_status ;;
903
1010
  list) list_supported ;;
904
1011
  backups) list_backups ;;
905
- restore) restore_tool "$2" ;;
1012
+ unlink) unlink_tool "$2" ;;
906
1013
  skill) add_item "skill" "$2" ;;
907
1014
  agent) add_item "agent" "$2" ;;
908
1015
  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.2.0",
4
4
  "description": "Unified configuration manager for AI coding assistants",
5
5
  "bin": {
6
6
  "ai-global": "ai-global"