ai-global 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # AI Config
2
+
3
+ [中文文档](README_CN.md)
4
+
5
+ Unified configuration manager for AI coding assistants. Edit one file, sync to all your AI tools.
6
+
7
+ ## Supported Tools
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 | ✓ | | | | | |
48
+
49
+ ## Installation
50
+
51
+ ### npm / pnpm / yarn / bun
52
+
53
+ ```bash
54
+ npm install -g ai-global
55
+ # or
56
+ pnpm add -g ai-global
57
+ # or
58
+ yarn global add ai-global
59
+ # or
60
+ bun add -g ai-global
61
+ ```
62
+
63
+ ### curl
64
+
65
+ ```bash
66
+ curl -fsSL https://raw.githubusercontent.com/nanxiaobei/ai-global/main/install.sh | bash
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ### First run
72
+
73
+ ```bash
74
+ ai-global
75
+ ```
76
+
77
+ This will:
78
+ 1. Scan your system for installed AI tools
79
+ 2. Backup original configs to `~/.ai-global/backups/`
80
+ 3. Merge instructions/skills/agents/rules/commands/prompts from all tools
81
+ 4. Create symlinks from each tool's config to shared directories
82
+
83
+ ### Edit instructions
84
+
85
+ ```
86
+ ~/.ai-global/instructions.md
87
+ ```
88
+
89
+ Changes take effect immediately - all tools read the same file via symlinks.
90
+
91
+ ### Commands
92
+
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 |
110
+
111
+ ### Add skill/agent/rule/command/prompt
112
+
113
+ ```bash
114
+ # From local file
115
+ ai-global skill react.md
116
+ ai-global agent coder.md
117
+
118
+ # From GitHub repo (clone all .md files)
119
+ ai-global skill user/repo
120
+ ai-global skill https://github.com/user/repo
121
+
122
+ # From GitHub subdirectory
123
+ ai-global skill user/repo/skills
124
+
125
+ # From GitHub single file
126
+ ai-global skill user/repo/skills/react.md
127
+ ```
128
+
129
+ ## How it works
130
+
131
+ ```
132
+ ~/.ai-global/
133
+ ├── instructions.md <- Shared instructions (edit this)
134
+ ├── skills/ <- Shared skills (merged from all tools)
135
+ ├── agents/ <- Shared agents
136
+ ├── rules/ <- Shared rules
137
+ ├── commands/ <- Shared slash commands
138
+ ├── prompts/ <- Shared prompt templates
139
+ └── backups/ <- Original configs (backup)
140
+
141
+ Each AI tool's config directory contains symlinks:
142
+
143
+ ~/.claude/
144
+ ├── CLAUDE.md -> ~/.ai-global/instructions.md (symlink)
145
+ ├── skills/ -> ~/.ai-global/skills/ (symlink)
146
+ └── commands/ -> ~/.ai-global/commands/ (symlink)
147
+
148
+ ~/.cursor/
149
+ ├── rules/global.md -> ~/.ai-global/instructions.md (symlink)
150
+ ├── skills/ -> ~/.ai-global/skills/ (symlink)
151
+ └── prompts/ -> ~/.ai-global/prompts/ (symlink)
152
+
153
+ ... and more tools
154
+ ```
155
+
156
+ ## Merge behavior
157
+
158
+ When you run `ai-global`, it merges items from all tools by filename:
159
+
160
+ - Cursor has skills: `react.md`, `typescript.md`
161
+ - Claude has skills: `typescript.md`, `python.md`
162
+ - Result in `~/.ai-global/skills/`: `react.md`, `typescript.md`, `python.md`
163
+
164
+ The first file found wins (dedup by filename).
165
+
166
+ ## Uninstall
167
+
168
+ ```bash
169
+ ai-global uninstall
170
+ ```
171
+
172
+ This will:
173
+ 1. Restore all tools to original configuration
174
+ 2. Remove `~/.ai-global` directory
175
+ 3. Remove `ai-global` command
176
+
177
+ If installed via npm:
178
+
179
+ ```bash
180
+ ai-global uninstall
181
+ npm uninstall -g ai-global
182
+ ```
183
+
184
+ ## Example instructions.md
185
+
186
+ ```markdown
187
+ # AI Assistant Instructions
188
+
189
+ - Use pnpm as the package manager
190
+ - Reply in English
191
+ - Follow existing code style
192
+ - Use TypeScript with `type` for type definitions
193
+ ```
194
+
195
+ ## License
196
+
197
+ MIT
package/ai-global ADDED
@@ -0,0 +1,919 @@
1
+ #!/bin/bash
2
+
3
+ # AI-Global: Unified AI Tools Configuration Manager
4
+ # https://github.com/nanxiaobei/ai-global
5
+
6
+ set -e
7
+
8
+ CONFIG_DIR="$HOME/.ai-global"
9
+ KNOWN_TOOLS_FILE="$CONFIG_DIR/.known-tools"
10
+ BACKUP_DIR="$CONFIG_DIR/backups"
11
+ INSTRUCTIONS_MD="$CONFIG_DIR/instructions.md"
12
+ SKILLS_DIR="$CONFIG_DIR/skills"
13
+ AGENTS_DIR="$CONFIG_DIR/agents"
14
+ RULES_DIR="$CONFIG_DIR/rules"
15
+ COMMANDS_DIR="$CONFIG_DIR/commands"
16
+ PROMPTS_DIR="$CONFIG_DIR/prompts"
17
+
18
+ # Version (read from package.json)
19
+ # Prefer node for correct JSON parsing; fallback to grep/sed; finally fallback to a default.
20
+ VERSION=""
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+ PACKAGE_JSON="$SCRIPT_DIR/package.json"
23
+
24
+ if [[ -f "$PACKAGE_JSON" ]]; then
25
+ if command -v node >/dev/null 2>&1; then
26
+ VERSION=$(node -p "try{require(process.env.PACKAGE_JSON).version||''}catch(e){''}" 2>/dev/null PACKAGE_JSON="$PACKAGE_JSON")
27
+ fi
28
+
29
+ if [[ -z "$VERSION" ]]; then
30
+ VERSION=$(grep '"version"' "$PACKAGE_JSON" 2>/dev/null | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
31
+ fi
32
+ fi
33
+
34
+ VERSION=${VERSION:-""}
35
+
36
+ # Colors
37
+ RED='\033[0;31m'
38
+ GREEN='\033[0;32m'
39
+ YELLOW='\033[0;33m'
40
+ BLUE='\033[0;34m'
41
+ CYAN='\033[0;36m'
42
+ NC='\033[0m'
43
+
44
+ log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
45
+ log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
46
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
47
+ log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
48
+
49
+ # Known AI tool patterns
50
+ # Format: dir|name|instr_file|skills_dir|agents_dir|rules_dir|commands_dir|prompts_dir
51
+ declare -a KNOWN_PATTERNS=(
52
+ ".claude|Claude Code|CLAUDE.md|skills|.|.|commands|."
53
+ ".cursor|Cursor|rules/global.md|skills|.|.|.|prompts"
54
+ ".factory|Factory Droid|droids/global.md|.|.|.|.|."
55
+ ".amp|Amp|instructions.md|.|.|.|.|."
56
+ ".antigravity|Antigravity|CLAUDE.md|skills|.|.|commands|."
57
+ ".gemini|Gemini CLI|GEMINI.md|skills|.|.|commands|."
58
+ ".kiro|Kiro|steering/global.md|.|agents|.|.|."
59
+ ".opencode|OpenCode|instructions.md|.|.|.|.|."
60
+ ".qoder|Qoder|instructions.md|.|.|.|.|."
61
+ ".qodo|Qodo|instructions.md|.|agents|.|.|."
62
+ ".config/github-copilot|GitHub Copilot|instructions.md|.|.|.|.|."
63
+ ".aider|Aider|.aider.conf.yml|.|.|.|.|."
64
+ ".continue|Continue|config.json|.|.|rules|.|prompts"
65
+ ".codeium|Codeium|config.json|.|.|.|.|."
66
+ ".tabnine|TabNine|tabnine_config.json|.|.|.|.|."
67
+ ".sourcegraph|Sourcegraph Cody|cody.json|.|.|.|commands|."
68
+ ".codegpt|CodeGPT|settings.json|.|.|.|.|prompts"
69
+ ".windsurf|Windsurf|instructions.md|skills|agents|rules|.|."
70
+ ".trae|Trae|instructions.md|.|.|.|.|."
71
+ ".melty|Melty|instructions.md|.|.|.|.|."
72
+ ".void|Void|instructions.md|.|.|.|.|."
73
+ ".roo|Roo Code|instructions.md|.|.|rules|.|."
74
+ ".zed|Zed|settings.json|.|.|.|.|."
75
+ ".cline|Cline|instructions.md|.|.|rules|.|prompts"
76
+ ".aide|Aide|instructions.md|.|.|.|.|."
77
+ ".pear|PearAI|instructions.md|.|.|.|.|."
78
+ ".supermaven|Supermaven|config.json|.|.|.|.|."
79
+ ".codestory|CodeStory|instructions.md|.|.|.|.|."
80
+ ".double|Double|instructions.md|.|.|.|.|."
81
+ ".blackbox|Blackbox AI|config.json|.|.|.|.|."
82
+ ".amazonq|Amazon Q|instructions.md|.|.|.|.|."
83
+ ".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"
89
+ ".config/opencode|OpenCode Config|instructions.md|.|.|.|.|."
90
+ )
91
+
92
+ # Backup a file or directory
93
+ backup_item() {
94
+ local source="$1"
95
+ local tool_dir="$2"
96
+ local type="$3"
97
+
98
+ [[ ! -e "$source" ]] && return 0
99
+ [[ -L "$source" ]] && return 0
100
+
101
+ mkdir -p "$BACKUP_DIR"
102
+
103
+ local backup_name=$(echo "$tool_dir" | tr '/' '_')
104
+ local timestamp=$(date +%s)
105
+ local backup_path="$BACKUP_DIR/${backup_name}.${type}.${timestamp}"
106
+
107
+ if [[ -d "$source" ]]; then
108
+ cp -r "$source" "$backup_path" 2>/dev/null || return 0
109
+ else
110
+ cp "$source" "$backup_path" 2>/dev/null || return 0
111
+ fi
112
+
113
+ log_ok "Backed up: $source"
114
+ }
115
+
116
+ # Merge items from a tool to shared directory (dedup by filename)
117
+ merge_items() {
118
+ local source_dir="$1"
119
+ local target_dir="$2"
120
+ local type="$3"
121
+
122
+ [[ ! -d "$source_dir" ]] && return
123
+ [[ -L "$source_dir" ]] && return
124
+
125
+ local merged_count=0
126
+
127
+ for item in "$source_dir"/*; do
128
+ [[ ! -e "$item" ]] && continue
129
+ local name=$(basename "$item")
130
+ local target="$target_dir/$name"
131
+
132
+ [[ -e "$target" ]] && continue
133
+
134
+ if [[ -d "$item" ]]; then
135
+ cp -r "$item" "$target"
136
+ else
137
+ cp "$item" "$target"
138
+ fi
139
+ ((merged_count++))
140
+ done
141
+
142
+ if [[ $merged_count -gt 0 ]]; then
143
+ log_ok "Merged $merged_count $type from $source_dir"
144
+ fi
145
+ }
146
+
147
+ # Create symlink
148
+ create_symlink() {
149
+ local source="$1"
150
+ local target="$2"
151
+
152
+ [[ ! -e "$source" ]] && return
153
+
154
+ local target_dir=$(dirname "$target")
155
+ mkdir -p "$target_dir"
156
+
157
+ 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"
161
+ fi
162
+
163
+ [[ -L "$target" ]] && rm "$target"
164
+ ln -s "$source" "$target"
165
+ }
166
+
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
+ # Show symlink status
178
+ show_status() {
179
+ log_info "Symlink status:"
180
+ echo ""
181
+
182
+ if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
183
+ log_info "No tools configured. Run 'ai-global' first."
184
+ return
185
+ fi
186
+
187
+ echo -e "${BLUE}[instructions]${NC}"
188
+ while IFS= read -r dir_name; do
189
+ [[ -z "$dir_name" ]] && continue
190
+ 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)"
201
+ fi
202
+ fi
203
+ break
204
+ fi
205
+ done
206
+ done < "$KNOWN_TOOLS_FILE"
207
+
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"
237
+ done
238
+
239
+ 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")"
241
+ }
242
+
243
+ # List supported tools
244
+ list_supported() {
245
+ log_info "Supported AI tools:"
246
+ echo ""
247
+ printf " ${BLUE}%-22s${NC} %-20s %-6s %-6s %-6s %-6s %-6s %s\n" "Directory" "Tool" "Skills" "Agents" "Rules" "Cmds" "Prompts" "Status"
248
+ echo " ----------------------------------------------------------------------------------------------------------------"
249
+
250
+ 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"
253
+
254
+ local s="." a="." r="." c="." p="."
255
+ [[ "$skills" != "." ]] && s="✓"
256
+ [[ "$agents" != "." ]] && a="✓"
257
+ [[ "$rules" != "." ]] && r="✓"
258
+ [[ "$commands" != "." ]] && c="✓"
259
+ [[ "$prompts" != "." ]] && p="✓"
260
+
261
+ local status=""
262
+ if [[ -d "$full_path" ]]; then
263
+ status="${GREEN}Installed${NC}"
264
+ else
265
+ status="${YELLOW}Not found${NC}"
266
+ fi
267
+
268
+ printf " %-22s %-20s %-6s %-6s %-6s %-6s %-6s %b\n" "$dir_name" "$tool_name" "$s" "$a" "$r" "$c" "$p" "$status"
269
+ done
270
+ echo ""
271
+ }
272
+
273
+ # List available backups
274
+ list_backups() {
275
+ log_info "Available backups:"
276
+ echo ""
277
+
278
+ if [[ ! -d "$BACKUP_DIR" ]] || [[ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]]; then
279
+ echo " No backups found"
280
+ echo ""
281
+ return
282
+ fi
283
+
284
+ printf " ${BLUE}%-25s${NC} %-12s %s\n" "Tool" "Type" "Backup File"
285
+ echo " ----------------------------------------------------------------"
286
+
287
+ for backup in "$BACKUP_DIR"/*; do
288
+ [[ ! -e "$backup" ]] && continue
289
+ local filename=$(basename "$backup")
290
+ local tool_name=""
291
+ local type=""
292
+
293
+ if [[ "$filename" =~ ^(.+)\.([^.]+)\.([0-9]+)$ ]]; then
294
+ tool_name="${BASH_REMATCH[1]}"
295
+ type="${BASH_REMATCH[2]}"
296
+ else
297
+ tool_name="$filename"
298
+ type="unknown"
299
+ fi
300
+
301
+ printf " %-25s %-12s %s\n" "$tool_name" "$type" "$filename"
302
+ done
303
+ echo ""
304
+ }
305
+
306
+ # Collect and merge instructions from all tools
307
+ collect_instructions() {
308
+ local merged_content=""
309
+ local found_count=0
310
+
311
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
312
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
313
+
314
+ if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
315
+ local actual_path="$HOME/$dir_name/$instr_file"
316
+ [[ -L "$actual_path" ]] && continue
317
+
318
+ if [[ -f "$actual_path" ]]; then
319
+ local content=$(cat "$actual_path" 2>/dev/null)
320
+ if [[ -n "$content" ]]; then
321
+ if [[ $found_count -gt 0 ]]; then
322
+ merged_content+="\n\n---\n\n"
323
+ fi
324
+ merged_content+="# From $tool_name\n\n$content"
325
+ ((found_count++))
326
+ fi
327
+ fi
328
+ fi
329
+ done
330
+
331
+ if [[ $found_count -gt 0 ]]; then
332
+ echo -e "$merged_content" > "$INSTRUCTIONS_MD"
333
+ log_ok "Merged instructions from $found_count tool(s)"
334
+ else
335
+ cat > "$INSTRUCTIONS_MD" << 'EOF'
336
+ # AI Assistant Instructions
337
+
338
+ <!-- Add your instructions here. They will sync to all AI tools. -->
339
+ EOF
340
+ log_ok "Created: $INSTRUCTIONS_MD"
341
+ fi
342
+ }
343
+
344
+ # Update: scan, merge and link tools
345
+ update_tools() {
346
+ log_info "Scanning for AI tools..."
347
+ echo ""
348
+
349
+ mkdir -p "$SKILLS_DIR" "$AGENTS_DIR" "$RULES_DIR" "$COMMANDS_DIR" "$PROMPTS_DIR" "$BACKUP_DIR"
350
+
351
+ if [[ ! -f "$INSTRUCTIONS_MD" ]]; then
352
+ collect_instructions
353
+ fi
354
+
355
+ > "$KNOWN_TOOLS_FILE"
356
+
357
+ local tool_count=0
358
+
359
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
360
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
361
+ local full_path="$HOME/$dir_name"
362
+
363
+ if [[ -d "$full_path" ]]; then
364
+ log_ok "Found: $tool_name"
365
+ ((tool_count++))
366
+
367
+ if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
368
+ local actual_path="$HOME/$dir_name/$instr_file"
369
+ backup_item "$actual_path" "$dir_name" "instructions"
370
+ fi
371
+
372
+ for type_name in skills agents rules commands prompts; do
373
+ local type_dir=""
374
+ case "$type_name" in
375
+ skills) type_dir="$skills" ;;
376
+ agents) type_dir="$agents" ;;
377
+ rules) type_dir="$rules" ;;
378
+ commands) type_dir="$commands" ;;
379
+ prompts) type_dir="$prompts" ;;
380
+ esac
381
+ if [[ "$type_dir" != "." ]]; then
382
+ local path="$HOME/$dir_name/$type_dir"
383
+ backup_item "$path" "$dir_name" "$type_name"
384
+ local target_dir=""
385
+ case "$type_name" in
386
+ skills) target_dir="$SKILLS_DIR" ;;
387
+ agents) target_dir="$AGENTS_DIR" ;;
388
+ rules) target_dir="$RULES_DIR" ;;
389
+ commands) target_dir="$COMMANDS_DIR" ;;
390
+ prompts) target_dir="$PROMPTS_DIR" ;;
391
+ esac
392
+ merge_items "$path" "$target_dir" "$type_name"
393
+ fi
394
+ done
395
+
396
+ echo "$dir_name" >> "$KNOWN_TOOLS_FILE"
397
+ fi
398
+ done
399
+
400
+ if [[ $tool_count -eq 0 ]]; then
401
+ log_info "No AI tools found."
402
+ return
403
+ fi
404
+
405
+ echo ""
406
+ log_info "Creating symlinks..."
407
+
408
+ while IFS= read -r dir_name; do
409
+ [[ -z "$dir_name" ]] && continue
410
+
411
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
412
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
413
+
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"
419
+ 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"
442
+
443
+ 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")"
445
+ }
446
+
447
+ # Restore a single tool
448
+ restore_single_tool() {
449
+ local tool_name="$1"
450
+ local dir_name="$2"
451
+ local instr_file="$3"
452
+ local skills="$4"
453
+ local agents="$5"
454
+ local rules="$6"
455
+ local commands="$7"
456
+ local prompts="$8"
457
+
458
+ log_ok "Restoring: $tool_name"
459
+ local backup_name=$(echo "$dir_name" | tr '/' '_')
460
+
461
+ if [[ "$instr_file" != "." ]]; then
462
+ local target="$HOME/$dir_name/$instr_file"
463
+ 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"
467
+ fi
468
+ fi
469
+
470
+ for type_name in skills agents rules commands prompts; do
471
+ local type_dir=""
472
+ case "$type_name" in
473
+ skills) type_dir="$skills" ;;
474
+ agents) type_dir="$agents" ;;
475
+ rules) type_dir="$rules" ;;
476
+ commands) type_dir="$commands" ;;
477
+ prompts) type_dir="$prompts" ;;
478
+ esac
479
+ if [[ "$type_dir" != "." ]]; then
480
+ local target="$HOME/$dir_name/$type_dir"
481
+ 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"
488
+ fi
489
+ fi
490
+ fi
491
+ done
492
+ }
493
+
494
+ # Restore all tools
495
+ restore_all_tools() {
496
+ log_info "Restoring all tools..."
497
+ echo ""
498
+
499
+ if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
500
+ log_info "No tools to restore"
501
+ return
502
+ fi
503
+
504
+ local restored_count=0
505
+
506
+ while IFS= read -r dir_name; do
507
+ [[ -z "$dir_name" ]] && continue
508
+
509
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
510
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
511
+
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"
519
+
520
+ rm -f "$KNOWN_TOOLS_FILE"
521
+
522
+ echo ""
523
+ log_info "Restored $restored_count tool(s). Run 'ai-global' to re-link."
524
+ }
525
+
526
+ # Restore a specific tool
527
+ restore_tool() {
528
+ local tool_query="$1"
529
+
530
+ if [[ -z "$tool_query" ]]; then
531
+ log_error "Usage: ai-global restore <tool> or ai-global restore all"
532
+ echo ""
533
+ list_backups
534
+ return 1
535
+ fi
536
+
537
+ if [[ "$tool_query" == "all" ]]; then
538
+ restore_all_tools
539
+ return
540
+ fi
541
+
542
+ local found=false
543
+
544
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
545
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
546
+ local tool_lower=$(echo "$tool_name" | tr '[:upper:]' '[:lower:]')
547
+ local query_lower=$(echo "$tool_query" | tr '[:upper:]' '[:lower:]')
548
+
549
+ 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"
556
+ fi
557
+
558
+ log_info "$tool_name restored"
559
+ found=true
560
+ break
561
+ fi
562
+ done
563
+
564
+ if [[ "$found" == false ]]; then
565
+ log_error "Tool not found: $tool_query"
566
+ echo ""
567
+ echo "Use 'ai-global list' to see supported tools"
568
+ return 1
569
+ fi
570
+ }
571
+
572
+ # Check if input is a GitHub reference
573
+ is_github_ref() {
574
+ local input="$1"
575
+ # Match: user/repo, https://github.com/user/repo, github.com/user/repo
576
+ if [[ "$input" =~ ^https?://github\.com/ ]] || \
577
+ [[ "$input" =~ ^github\.com/ ]] || \
578
+ [[ "$input" =~ ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+(/.*)?$ ]]; then
579
+ return 0
580
+ fi
581
+ return 1
582
+ }
583
+
584
+ # Parse GitHub reference to get owner, repo, and optional path
585
+ parse_github_ref() {
586
+ local input="$1"
587
+
588
+ # Remove https://github.com/ or github.com/ prefix
589
+ input="${input#https://github.com/}"
590
+ input="${input#http://github.com/}"
591
+ input="${input#github.com/}"
592
+
593
+ # Remove /blob/main/ or /blob/master/ or /tree/main/ etc for file/dir paths
594
+ input=$(echo "$input" | sed -E 's|/blob/[^/]+/|/|; s|/tree/[^/]+/|/|')
595
+
596
+ echo "$input"
597
+ }
598
+
599
+ # Download from GitHub
600
+ download_from_github() {
601
+ local type="$1"
602
+ local ref="$2"
603
+ local target_dir="$3"
604
+
605
+ local parsed=$(parse_github_ref "$ref")
606
+ local owner=$(echo "$parsed" | cut -d'/' -f1)
607
+ local repo=$(echo "$parsed" | cut -d'/' -f2)
608
+ local path=$(echo "$parsed" | cut -d'/' -f3-)
609
+
610
+ if [[ -z "$owner" ]] || [[ -z "$repo" ]]; then
611
+ log_error "Invalid GitHub reference: $ref"
612
+ return 1
613
+ fi
614
+
615
+ # If path points to a specific file
616
+ if [[ -n "$path" ]] && [[ "$path" == *.md ]]; then
617
+ local filename=$(basename "$path")
618
+ local raw_url="https://raw.githubusercontent.com/$owner/$repo/main/$path"
619
+
620
+ log_info "Downloading: $raw_url"
621
+
622
+ if curl -fsSL "$raw_url" -o "$target_dir/$filename" 2>/dev/null; then
623
+ log_ok "Added $type: $target_dir/$filename"
624
+ else
625
+ # Try master branch
626
+ raw_url="https://raw.githubusercontent.com/$owner/$repo/master/$path"
627
+ if curl -fsSL "$raw_url" -o "$target_dir/$filename" 2>/dev/null; then
628
+ log_ok "Added $type: $target_dir/$filename"
629
+ else
630
+ log_error "Failed to download: $ref"
631
+ return 1
632
+ fi
633
+ fi
634
+ else
635
+ # Clone entire repo or subdirectory
636
+ local tmp_dir=$(mktemp -d)
637
+ local clone_url="https://github.com/$owner/$repo.git"
638
+
639
+ log_info "Cloning: $clone_url"
640
+
641
+ if git clone --depth 1 --single-branch "$clone_url" "$tmp_dir/$repo"; then
642
+ local source_dir="$tmp_dir/$repo"
643
+ [[ -n "$path" ]] && source_dir="$tmp_dir/$repo/$path"
644
+
645
+ if [[ -d "$source_dir" ]]; then
646
+ # Auto-detect type subdirectory (e.g., skills/, agents/, rules/)
647
+ if [[ -z "$path" ]]; then
648
+ local type_dirs=""
649
+ 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" ;;
655
+ esac
656
+
657
+ # Prefer type subdirectory, fallback to root
658
+ for dir in $type_dirs; do
659
+ if [[ -d "$source_dir/$dir" ]]; then
660
+ source_dir="$source_dir/$dir"
661
+ log_info "Found $dir/ directory"
662
+ break
663
+ fi
664
+ done
665
+ fi
666
+
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
686
+ fi
687
+
688
+ # Fallback: copy top-level .md files from the selected source directory
689
+ if [[ $count -eq 0 ]]; then
690
+ for file in "$source_dir"/*.md; do
691
+ [[ ! -f "$file" ]] && continue
692
+ local filename=$(basename "$file")
693
+ cp "$file" "$target_dir/$filename"
694
+ ((count++))
695
+ done
696
+ fi
697
+
698
+ if [[ $count -gt 0 ]]; then
699
+ log_ok "Added $count ${type}(s) from $owner/$repo"
700
+ else
701
+ # Show actual searched path
702
+ local searched_path="${source_dir#$tmp_dir/$repo}"
703
+ searched_path="${searched_path#/}"
704
+ log_warn "No skills found in $owner/$repo${searched_path:+/$searched_path}"
705
+ fi
706
+ else
707
+ log_error "Path not found: $path"
708
+ rm -rf "$tmp_dir"
709
+ return 1
710
+ fi
711
+
712
+ rm -rf "$tmp_dir"
713
+ else
714
+ rm -rf "$tmp_dir"
715
+ log_error "Failed to clone: $clone_url"
716
+ return 1
717
+ fi
718
+ fi
719
+ }
720
+
721
+ # Add item to a type directory
722
+ add_item() {
723
+ local type="$1"
724
+ local input="$2"
725
+
726
+ if [[ -z "$input" ]]; then
727
+ log_error "Usage: ai-global $type <file|github-repo>"
728
+ echo ""
729
+ echo "Examples:"
730
+ echo " ai-global $type react.md"
731
+ echo " ai-global $type /path/to/file.md"
732
+ echo " ai-global $type user/repo"
733
+ echo " ai-global $type https://github.com/user/repo"
734
+ echo " ai-global $type user/repo/path/to/file.md"
735
+ return 1
736
+ fi
737
+
738
+ local target_dir=""
739
+ case "$type" in
740
+ skill) target_dir="$SKILLS_DIR" ;;
741
+ agent) target_dir="$AGENTS_DIR" ;;
742
+ rule) target_dir="$RULES_DIR" ;;
743
+ command) target_dir="$COMMANDS_DIR" ;;
744
+ prompt) target_dir="$PROMPTS_DIR" ;;
745
+ esac
746
+
747
+ mkdir -p "$target_dir"
748
+
749
+ # Check if it's a GitHub reference
750
+ if is_github_ref "$input"; then
751
+ download_from_github "$type" "$input" "$target_dir"
752
+ elif [[ -f "$input" ]]; then
753
+ # Local file
754
+ local basename=$(basename "$input")
755
+ cp "$input" "$target_dir/$basename"
756
+ log_ok "Added $type: $target_dir/$basename"
757
+ else
758
+ # Create new file
759
+ local target_file="$target_dir/$input"
760
+ if [[ ! "$input" == *.md ]]; then
761
+ target_file="$target_dir/${input}.md"
762
+ fi
763
+ touch "$target_file"
764
+ log_ok "Created $type: $target_file"
765
+ echo "Edit: $target_file"
766
+ fi
767
+ }
768
+
769
+ # Uninstall
770
+ uninstall() {
771
+ log_warn "This will:"
772
+ echo " 1. Restore all tools to original configuration"
773
+ echo " 2. Remove ~/.ai-global directory"
774
+ echo " 3. Remove ai-global from PATH"
775
+ echo ""
776
+ read -p "Are you sure? (y/N) " -r
777
+ echo ""
778
+
779
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
780
+ log_info "Cancelled"
781
+ return
782
+ fi
783
+
784
+ if [[ -f "$KNOWN_TOOLS_FILE" ]]; then
785
+ restore_all_tools
786
+ fi
787
+
788
+ [[ -L /usr/local/bin/ai-global ]] && rm -f /usr/local/bin/ai-global
789
+ [[ -L "$HOME/.local/bin/ai-global" ]] && rm -f "$HOME/.local/bin/ai-global"
790
+
791
+ rm -rf "$CONFIG_DIR"
792
+
793
+ log_ok "AI-Global uninstalled"
794
+ }
795
+
796
+ # Show version
797
+ show_version() {
798
+ echo "ai-global version $VERSION"
799
+ }
800
+
801
+ # Upgrade
802
+ upgrade() {
803
+ log_info "Checking for updates..."
804
+
805
+ local remote_version
806
+ 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/')
807
+
808
+ if [[ -z "$remote_version" ]]; then
809
+ log_warn "Could not check for updates"
810
+ return 1
811
+ fi
812
+
813
+ if [[ "$remote_version" == "$VERSION" ]]; then
814
+ log_ok "Already at latest version ($VERSION)"
815
+ return 0
816
+ fi
817
+
818
+ log_info "Upgrading: $VERSION -> $remote_version"
819
+
820
+ local tmp_file=$(mktemp)
821
+ if curl -fsSL "https://raw.githubusercontent.com/nanxiaobei/ai-global/main/ai-global" -o "$tmp_file" 2>/dev/null; then
822
+ chmod +x "$tmp_file"
823
+ mv "$tmp_file" "$CONFIG_DIR/ai-global"
824
+ log_ok "Upgraded to v$remote_version"
825
+ else
826
+ rm -f "$tmp_file"
827
+ log_error "Failed to download update"
828
+ return 1
829
+ fi
830
+ }
831
+
832
+ # Show help
833
+ 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
875
+ }
876
+
877
+ # Main
878
+ main() {
879
+ local cmd="${1:-update}"
880
+
881
+ if [[ "$1" == "-v" ]] || [[ "$1" == "--version" ]] || [[ "$1" == "version" ]]; then
882
+ show_version
883
+ exit 0
884
+ fi
885
+
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..."
893
+ update_tools
894
+ [[ "$cmd" == "skill" || "$cmd" == "agent" || "$cmd" == "rule" || "$cmd" == "command" || "$cmd" == "prompt" ]] || exit 0
895
+ ;;
896
+ *) cmd="update" ;;
897
+ esac
898
+ fi
899
+
900
+ case "$cmd" in
901
+ update) update_tools ;;
902
+ status) show_status ;;
903
+ list) list_supported ;;
904
+ backups) list_backups ;;
905
+ restore) restore_tool "$2" ;;
906
+ skill) add_item "skill" "$2" ;;
907
+ agent) add_item "agent" "$2" ;;
908
+ rule) add_item "rule" "$2" ;;
909
+ command) add_item "command" "$2" ;;
910
+ prompt) add_item "prompt" "$2" ;;
911
+ upgrade) upgrade ;;
912
+ uninstall) uninstall ;;
913
+ version|-v|--version) show_version ;;
914
+ help|--help|-h) show_help ;;
915
+ *) log_error "Unknown command: $cmd"; show_help; exit 1 ;;
916
+ esac
917
+ }
918
+
919
+ main "$@"
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "ai-global",
3
+ "version": "1.0.0",
4
+ "description": "Unified configuration manager for AI coding assistants",
5
+ "bin": {
6
+ "ai-global": "ai-global"
7
+ },
8
+ "scripts": {
9
+ "postinstall": "node scripts/postinstall.js"
10
+ },
11
+ "keywords": [
12
+ "ai",
13
+ "claude",
14
+ "cursor",
15
+ "copilot",
16
+ "gemini",
17
+ "coding-assistant",
18
+ "config",
19
+ "symlink"
20
+ ],
21
+ "author": "nanxiaobei",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/nanxiaobei/ai-global.git"
26
+ },
27
+ "homepage": "https://github.com/nanxiaobei/ai-global#readme",
28
+ "engines": {
29
+ "node": ">=14"
30
+ },
31
+ "os": [
32
+ "darwin",
33
+ "linux"
34
+ ],
35
+ "files": [
36
+ "ai-global",
37
+ "scripts/"
38
+ ]
39
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const configDir = path.join(os.homedir(), '.ai-global');
8
+
9
+ // Create config directory
10
+ if (!fs.existsSync(configDir)) {
11
+ fs.mkdirSync(configDir, { recursive: true });
12
+ }
13
+
14
+ console.log('');
15
+ console.log('\x1b[32m[OK]\x1b[0m AI-Link installed!');
16
+ console.log('');
17
+ console.log('Run \x1b[36mai-link\x1b[0m to scan and link all your AI tools');
18
+ console.log('');