ai-link 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,173 @@
1
+ # AI-Link
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 (38+)
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
+ | + 20 more... | ✓ | | | | | |
28
+
29
+ ## Installation
30
+
31
+ ### npm / pnpm / yarn / bun
32
+
33
+ ```bash
34
+ npm install -g ai-link
35
+ # or
36
+ pnpm add -g ai-link
37
+ # or
38
+ yarn global add ai-link
39
+ # or
40
+ bun add -g ai-link
41
+ ```
42
+
43
+ ### curl
44
+
45
+ ```bash
46
+ curl -fsSL https://raw.githubusercontent.com/nanxiaobei/ai-link/main/install.sh | bash
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ### First run
52
+
53
+ ```bash
54
+ ai-link
55
+ ```
56
+
57
+ This will:
58
+ 1. Scan your system for installed AI tools
59
+ 2. Backup original configs to `~/.ai-config/backups/`
60
+ 3. Merge instructions/skills/agents/rules/commands/prompts from all tools
61
+ 4. Create symlinks from each tool's config to shared directories
62
+
63
+ ### Edit instructions
64
+
65
+ ```bash
66
+ vim ~/.ai-config/instructions.md
67
+ ```
68
+
69
+ Changes take effect immediately - all tools read the same file via symlinks.
70
+
71
+ ### Add a skill
72
+
73
+ ```bash
74
+ vim ~/.ai-config/skills/my-skill.md
75
+ ```
76
+
77
+ The skill will be available in all tools that support skills.
78
+
79
+ ### Commands
80
+
81
+ | Command | Description |
82
+ |---------|-------------|
83
+ | `ai-link` | Scan, merge and update symlinks (default) |
84
+ | `ai-link status` | Show symlink status |
85
+ | `ai-link list` | List supported tools |
86
+ | `ai-link backups` | List available backups |
87
+ | `ai-link restore <tool>` | Restore a tool's original config |
88
+ | `ai-link restore all` | Restore all tools |
89
+ | `ai-link add <dir> [file]` | Add a custom tool |
90
+ | `ai-link upgrade` | Upgrade ai-link to latest version |
91
+ | `ai-link uninstall` | Completely remove ai-link |
92
+ | `ai-link version` | Show version |
93
+ | `ai-link help` | Show help |
94
+
95
+ ### Add custom tool
96
+
97
+ ```bash
98
+ # Add a tool with default instructions.md
99
+ ai-link add .mytool
100
+
101
+ # Add a tool with custom instructions file
102
+ ai-link add .mytool INSTRUCTIONS.md
103
+ ```
104
+
105
+ ## How it works
106
+
107
+ ```
108
+ ~/.ai-config/
109
+ ├── instructions.md <- Your instructions (edit this)
110
+ ├── skills/ <- Shared skills (merged from all tools)
111
+ ├── agents/ <- Shared agents
112
+ ├── rules/ <- Shared rules
113
+ ├── commands/ <- Shared slash commands
114
+ ├── prompts/ <- Shared prompt templates
115
+ └── backups/ <- Original configs (backup)
116
+
117
+ Each AI tool's config directory contains symlinks:
118
+
119
+ ~/.claude/
120
+ ├── CLAUDE.md -> ~/.ai-config/instructions.md (symlink)
121
+ ├── skills/ -> ~/.ai-config/skills/ (symlink)
122
+ └── commands/ -> ~/.ai-config/commands/ (symlink)
123
+
124
+ ~/.cursor/
125
+ ├── rules/global.md -> ~/.ai-config/instructions.md (symlink)
126
+ ├── skills/ -> ~/.ai-config/skills/ (symlink)
127
+ └── prompts/ -> ~/.ai-config/prompts/ (symlink)
128
+
129
+ ... and 30+ more tools
130
+ ```
131
+
132
+ ## Merge behavior
133
+
134
+ When you run `ai-link`, it merges items from all tools by filename:
135
+
136
+ - Cursor has skills: `react.md`, `typescript.md`
137
+ - Claude has skills: `typescript.md`, `python.md`
138
+ - Result in `~/.ai-config/skills/`: `react.md`, `typescript.md`, `python.md`
139
+
140
+ The first file found wins (dedup by filename).
141
+
142
+ ## Uninstall
143
+
144
+ ```bash
145
+ ai-link uninstall
146
+ ```
147
+
148
+ This will:
149
+ 1. Restore all tools to their original configuration
150
+ 2. Remove `~/.ai-config` directory
151
+ 3. Remove `ai-link` command
152
+
153
+ If installed via npm:
154
+
155
+ ```bash
156
+ ai-link uninstall
157
+ npm uninstall -g ai-link
158
+ ```
159
+
160
+ ## Example instructions.md
161
+
162
+ ```markdown
163
+ # AI Assistant Instructions
164
+
165
+ - Use pnpm as the package manager
166
+ - Reply in English
167
+ - Follow existing code style
168
+ - Use TypeScript with `type` for type definitions
169
+ ```
170
+
171
+ ## License
172
+
173
+ MIT
package/ai-link ADDED
@@ -0,0 +1,921 @@
1
+ #!/bin/bash
2
+
3
+ # AI-Link: Unified AI Tools Configuration Manager
4
+ # https://github.com/nanxiaobei/ai-link
5
+
6
+ set -e
7
+
8
+ CONFIG_DIR="$HOME/.ai-config"
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
+ # Colors
19
+ RED='\033[0;31m'
20
+ GREEN='\033[0;32m'
21
+ YELLOW='\033[0;33m'
22
+ BLUE='\033[0;34m'
23
+ CYAN='\033[0;36m'
24
+ NC='\033[0m'
25
+
26
+ log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
27
+ log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
28
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
29
+ log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
30
+ log_new() { echo -e "${CYAN}[NEW]${NC} $1"; }
31
+
32
+ # Known AI tool patterns
33
+ # Format: dir|name|instr_file|skills_dir|agents_dir|rules_dir|commands_dir|prompts_dir
34
+ declare -a KNOWN_PATTERNS=(
35
+ ".claude|Claude Code|CLAUDE.md|skills|.|.|commands|."
36
+ ".cursor|Cursor|rules/global.md|skills|.|.|.|prompts"
37
+ ".factory|Factory Droid|droids/global.md|.|.|.|.|."
38
+ ".amp|Amp|instructions.md|.|.|.|.|."
39
+ ".antigravity|Antigravity|CLAUDE.md|skills|.|.|commands|."
40
+ ".gemini|Gemini CLI|GEMINI.md|skills|.|.|commands|."
41
+ ".kiro|Kiro|steering/global.md|.|agents|.|.|."
42
+ ".opencode|OpenCode|instructions.md|.|.|.|.|."
43
+ ".qoder|Qoder|instructions.md|.|.|.|.|."
44
+ ".qodo|Qodo|instructions.md|.|agents|.|.|."
45
+ ".config/github-copilot|GitHub Copilot|instructions.md|.|.|.|.|."
46
+ ".aider|Aider|.aider.conf.yml|.|.|.|.|."
47
+ ".continue|Continue|config.json|.|.|rules|.|prompts"
48
+ ".codeium|Codeium|config.json|.|.|.|.|."
49
+ ".tabnine|TabNine|tabnine_config.json|.|.|.|.|."
50
+ ".sourcegraph|Sourcegraph Cody|cody.json|.|.|.|commands|."
51
+ ".codegpt|CodeGPT|settings.json|.|.|.|.|prompts"
52
+ ".windsurf|Windsurf|instructions.md|skills|agents|rules|.|."
53
+ ".trae|Trae|instructions.md|.|.|.|.|."
54
+ ".melty|Melty|instructions.md|.|.|.|.|."
55
+ ".void|Void|instructions.md|.|.|.|.|."
56
+ ".roo|Roo Code|instructions.md|.|.|rules|.|."
57
+ ".zed|Zed|settings.json|.|.|.|.|."
58
+ ".cline|Cline|instructions.md|.|.|rules|.|prompts"
59
+ ".aide|Aide|instructions.md|.|.|.|.|."
60
+ ".pear|PearAI|instructions.md|.|.|.|.|."
61
+ ".supermaven|Supermaven|config.json|.|.|.|.|."
62
+ ".codestory|CodeStory|instructions.md|.|.|.|.|."
63
+ ".double|Double|instructions.md|.|.|.|.|."
64
+ ".blackbox|Blackbox AI|config.json|.|.|.|.|."
65
+ ".amazonq|Amazon Q|instructions.md|.|.|.|.|."
66
+ ".copilot-workspace|Copilot Workspace|instructions.md|.|.|.|.|."
67
+ ".codex|OpenAI Codex|instructions.md|.|.|.|.|."
68
+ ".goose|Goose AI|instructions.md|.|.|.|.|."
69
+ ".mentat|Mentat|instructions.md|.|.|.|.|."
70
+ ".gpt-engineer|GPT Engineer|instructions.md|.|.|.|.|prompts"
71
+ ".smol|Smol Developer|instructions.md|.|.|.|.|prompts"
72
+ ".config/opencode|OpenCode Config|instructions.md|.|.|.|.|."
73
+ )
74
+
75
+ expand_path() {
76
+ echo "${1/#\~/$HOME}"
77
+ }
78
+
79
+ # Backup a file or directory
80
+ backup_item() {
81
+ local source="$1"
82
+ local tool_dir="$2"
83
+ local type="$3"
84
+
85
+ [[ ! -e "$source" ]] && return 0
86
+ [[ -L "$source" ]] && return 0
87
+
88
+ mkdir -p "$BACKUP_DIR"
89
+
90
+ local backup_name=$(echo "$tool_dir" | tr '/' '_')
91
+ local timestamp=$(date +%s)
92
+ local backup_path="$BACKUP_DIR/${backup_name}.${type}.${timestamp}"
93
+
94
+ if [[ -d "$source" ]]; then
95
+ cp -r "$source" "$backup_path" 2>/dev/null || return 0
96
+ else
97
+ cp "$source" "$backup_path" 2>/dev/null || return 0
98
+ fi
99
+
100
+ log_ok "Backed up: $source"
101
+ }
102
+
103
+ # Merge items from a tool to shared directory (dedup by filename)
104
+ merge_items() {
105
+ local source_dir="$1"
106
+ local target_dir="$2"
107
+ local type="$3"
108
+
109
+ [[ ! -d "$source_dir" ]] && return
110
+ [[ -L "$source_dir" ]] && return
111
+
112
+ local merged_count=0
113
+
114
+ for item in "$source_dir"/*; do
115
+ [[ ! -e "$item" ]] && continue
116
+ local name=$(basename "$item")
117
+ local target="$target_dir/$name"
118
+
119
+ # Skip if already exists (dedup by name)
120
+ if [[ -e "$target" ]]; then
121
+ continue
122
+ fi
123
+
124
+ if [[ -d "$item" ]]; then
125
+ cp -r "$item" "$target"
126
+ else
127
+ cp "$item" "$target"
128
+ fi
129
+ ((merged_count++))
130
+ done
131
+
132
+ if [[ $merged_count -gt 0 ]]; then
133
+ log_ok "Merged $merged_count $type from $source_dir"
134
+ fi
135
+ }
136
+
137
+ # Create symlink (remove existing first)
138
+ create_symlink() {
139
+ local source="$1"
140
+ local target="$2"
141
+
142
+ [[ ! -e "$source" ]] && return
143
+
144
+ local target_dir=$(dirname "$target")
145
+ mkdir -p "$target_dir"
146
+
147
+ # If target exists and is not a symlink, backup first
148
+ if [[ -e "$target" ]] && [[ ! -L "$target" ]]; then
149
+ local backup="${target}.backup.$(date +%Y%m%d%H%M%S)"
150
+ mv "$target" "$backup"
151
+ log_warn "Backed up: $target"
152
+ fi
153
+
154
+ # Remove existing symlink
155
+ [[ -L "$target" ]] && rm "$target"
156
+
157
+ ln -s "$source" "$target"
158
+ }
159
+
160
+ # Count files in directory
161
+ count_files() {
162
+ local dir="$1"
163
+ if [[ -d "$dir" ]]; then
164
+ find "$dir" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' '
165
+ else
166
+ echo "0"
167
+ fi
168
+ }
169
+
170
+ # Show symlink status
171
+ show_status() {
172
+ log_info "Symlink status:"
173
+ echo ""
174
+
175
+ if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
176
+ log_info "No tools configured. Run 'ai-link' first."
177
+ return
178
+ fi
179
+
180
+ # Global
181
+ echo -e "${BLUE}[instructions]${NC}"
182
+ while IFS= read -r dir_name; do
183
+ [[ -z "$dir_name" ]] && continue
184
+
185
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
186
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
187
+
188
+ if [[ "$dir_name" == "$known_dir" ]]; then
189
+ if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
190
+ local target="$HOME/$dir_name/$instr_file"
191
+ if [[ -L "$target" ]]; then
192
+ echo -e " ${GREEN}✓${NC} $target"
193
+ elif [[ -e "$target" ]]; then
194
+ echo -e " ${YELLOW}!${NC} $target (not a symlink)"
195
+ else
196
+ echo -e " ${RED}✗${NC} $target (missing)"
197
+ fi
198
+ fi
199
+ break
200
+ fi
201
+ done
202
+ done < "$KNOWN_TOOLS_FILE"
203
+
204
+ # Skills
205
+ echo -e "\n${BLUE}[skills]${NC}"
206
+ while IFS= read -r dir_name; do
207
+ [[ -z "$dir_name" ]] && continue
208
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
209
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
210
+ if [[ "$dir_name" == "$known_dir" ]] && [[ "$skills" != "." ]]; then
211
+ local target="$HOME/$dir_name/$skills"
212
+ if [[ -L "$target" ]]; then
213
+ echo -e " ${GREEN}✓${NC} $target"
214
+ elif [[ -e "$target" ]]; then
215
+ echo -e " ${YELLOW}!${NC} $target (not a symlink)"
216
+ else
217
+ echo -e " ${RED}✗${NC} $target (missing)"
218
+ fi
219
+ break
220
+ fi
221
+ done
222
+ done < "$KNOWN_TOOLS_FILE"
223
+
224
+ # Agents
225
+ echo -e "\n${BLUE}[agents]${NC}"
226
+ while IFS= read -r dir_name; do
227
+ [[ -z "$dir_name" ]] && continue
228
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
229
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
230
+ if [[ "$dir_name" == "$known_dir" ]] && [[ "$agents" != "." ]]; then
231
+ local target="$HOME/$dir_name/$agents"
232
+ if [[ -L "$target" ]]; then
233
+ echo -e " ${GREEN}✓${NC} $target"
234
+ elif [[ -e "$target" ]]; then
235
+ echo -e " ${YELLOW}!${NC} $target (not a symlink)"
236
+ else
237
+ echo -e " ${RED}✗${NC} $target (missing)"
238
+ fi
239
+ break
240
+ fi
241
+ done
242
+ done < "$KNOWN_TOOLS_FILE"
243
+
244
+ # Rules
245
+ echo -e "\n${BLUE}[rules]${NC}"
246
+ while IFS= read -r dir_name; do
247
+ [[ -z "$dir_name" ]] && continue
248
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
249
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
250
+ if [[ "$dir_name" == "$known_dir" ]] && [[ "$rules" != "." ]]; then
251
+ local target="$HOME/$dir_name/$rules"
252
+ if [[ -L "$target" ]]; then
253
+ echo -e " ${GREEN}✓${NC} $target"
254
+ elif [[ -e "$target" ]]; then
255
+ echo -e " ${YELLOW}!${NC} $target (not a symlink)"
256
+ else
257
+ echo -e " ${RED}✗${NC} $target (missing)"
258
+ fi
259
+ break
260
+ fi
261
+ done
262
+ done < "$KNOWN_TOOLS_FILE"
263
+
264
+ # Commands
265
+ echo -e "\n${BLUE}[commands]${NC}"
266
+ while IFS= read -r dir_name; do
267
+ [[ -z "$dir_name" ]] && continue
268
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
269
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
270
+ if [[ "$dir_name" == "$known_dir" ]] && [[ "$commands" != "." ]]; then
271
+ local target="$HOME/$dir_name/$commands"
272
+ if [[ -L "$target" ]]; then
273
+ echo -e " ${GREEN}✓${NC} $target"
274
+ elif [[ -e "$target" ]]; then
275
+ echo -e " ${YELLOW}!${NC} $target (not a symlink)"
276
+ else
277
+ echo -e " ${RED}✗${NC} $target (missing)"
278
+ fi
279
+ break
280
+ fi
281
+ done
282
+ done < "$KNOWN_TOOLS_FILE"
283
+
284
+ # Prompts
285
+ echo -e "\n${BLUE}[prompts]${NC}"
286
+ while IFS= read -r dir_name; do
287
+ [[ -z "$dir_name" ]] && continue
288
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
289
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
290
+ if [[ "$dir_name" == "$known_dir" ]] && [[ "$prompts" != "." ]]; then
291
+ local target="$HOME/$dir_name/$prompts"
292
+ if [[ -L "$target" ]]; then
293
+ echo -e " ${GREEN}✓${NC} $target"
294
+ elif [[ -e "$target" ]]; then
295
+ echo -e " ${YELLOW}!${NC} $target (not a symlink)"
296
+ else
297
+ echo -e " ${RED}✗${NC} $target (missing)"
298
+ fi
299
+ break
300
+ fi
301
+ done
302
+ done < "$KNOWN_TOOLS_FILE"
303
+
304
+ echo ""
305
+ 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")"
306
+ }
307
+
308
+ # List supported tools
309
+ list_supported() {
310
+ log_info "Supported AI tools:"
311
+ echo ""
312
+ printf " ${BLUE}%-22s${NC} %-20s %-6s %-6s %-6s %-6s %-6s %s\n" "Directory" "Tool" "Skills" "Agents" "Rules" "Cmds" "Prompts" "Status"
313
+ echo " ----------------------------------------------------------------------------------------------------------------"
314
+
315
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
316
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
317
+ local full_path="$HOME/$dir_name"
318
+ local status=""
319
+
320
+ local s="." a="." r="." c="." p="."
321
+ [[ "$skills" != "." ]] && s="✓"
322
+ [[ "$agents" != "." ]] && a="✓"
323
+ [[ "$rules" != "." ]] && r="✓"
324
+ [[ "$commands" != "." ]] && c="✓"
325
+ [[ "$prompts" != "." ]] && p="✓"
326
+
327
+ if [[ -d "$full_path" ]]; then
328
+ status="${GREEN}Installed${NC}"
329
+ else
330
+ status="${YELLOW}Not found${NC}"
331
+ fi
332
+
333
+ printf " %-22s %-20s %-6s %-6s %-6s %-6s %-6s %b\n" "$dir_name" "$tool_name" "$s" "$a" "$r" "$c" "$p" "$status"
334
+ done
335
+ echo ""
336
+ }
337
+
338
+ # List available backups
339
+ list_backups() {
340
+ log_info "Available backups:"
341
+ echo ""
342
+
343
+ if [[ ! -d "$BACKUP_DIR" ]] || [[ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]]; then
344
+ echo " No backups found"
345
+ echo ""
346
+ return
347
+ fi
348
+
349
+ printf " ${BLUE}%-25s${NC} %-12s %s\n" "Tool" "Type" "Backup File"
350
+ echo " ----------------------------------------------------------------"
351
+
352
+ for backup in "$BACKUP_DIR"/*; do
353
+ [[ ! -e "$backup" ]] && continue
354
+ local filename=$(basename "$backup")
355
+ local tool_name=""
356
+ local type=""
357
+
358
+ if [[ "$filename" =~ ^(.+)\.([^.]+)\.([0-9]+)$ ]]; then
359
+ tool_name="${BASH_REMATCH[1]}"
360
+ type="${BASH_REMATCH[2]}"
361
+ else
362
+ tool_name="$filename"
363
+ type="unknown"
364
+ fi
365
+
366
+ printf " %-25s %-12s %s\n" "$tool_name" "$type" "$filename"
367
+ done
368
+ echo ""
369
+ }
370
+
371
+ # Collect and merge instructions from all tools
372
+ collect_instructions() {
373
+ local merged_content=""
374
+ local found_count=0
375
+
376
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
377
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
378
+
379
+ if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
380
+ local actual_path="$HOME/$dir_name/$instr_file"
381
+
382
+ # Skip if it's already a symlink (from previous run)
383
+ [[ -L "$actual_path" ]] && continue
384
+
385
+ if [[ -f "$actual_path" ]]; then
386
+ local content=$(cat "$actual_path" 2>/dev/null)
387
+ if [[ -n "$content" ]]; then
388
+ if [[ $found_count -gt 0 ]]; then
389
+ merged_content+="\n\n---\n\n"
390
+ fi
391
+ merged_content+="# From $tool_name\n\n$content"
392
+ ((found_count++))
393
+ fi
394
+ fi
395
+ fi
396
+ done
397
+
398
+ if [[ $found_count -gt 0 ]]; then
399
+ echo -e "$merged_content" > "$INSTRUCTIONS_MD"
400
+ log_ok "Merged instructions from $found_count tool(s)"
401
+ else
402
+ cat > "$INSTRUCTIONS_MD" << 'EOF'
403
+ # AI Assistant Instructions
404
+
405
+ <!-- Add your instructions here. They will sync to all AI tools. -->
406
+ EOF
407
+ log_ok "Created: $INSTRUCTIONS_MD"
408
+ fi
409
+ }
410
+
411
+ # Update: scan, merge and link tools
412
+ update_tools() {
413
+ log_info "Scanning for AI tools..."
414
+ echo ""
415
+
416
+ mkdir -p "$SKILLS_DIR" "$AGENTS_DIR" "$RULES_DIR" "$COMMANDS_DIR" "$PROMPTS_DIR" "$BACKUP_DIR"
417
+
418
+ # Create or merge instructions.md if not exists
419
+ if [[ ! -f "$INSTRUCTIONS_MD" ]]; then
420
+ collect_instructions
421
+ fi
422
+
423
+ # Clear known tools for fresh scan
424
+ > "$KNOWN_TOOLS_FILE"
425
+
426
+ local tool_count=0
427
+
428
+ # Scan all tools, backup and merge
429
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
430
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
431
+ local full_path="$HOME/$dir_name"
432
+
433
+ if [[ -d "$full_path" ]]; then
434
+ log_ok "Found: $tool_name"
435
+ ((tool_count++))
436
+
437
+ # Backup instructions file (only if not already a symlink)
438
+ if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
439
+ local actual_path="$HOME/$dir_name/$instr_file"
440
+ backup_item "$actual_path" "$dir_name" "instructions"
441
+ fi
442
+
443
+ # Backup and merge skills
444
+ if [[ "$skills" != "." ]]; then
445
+ local path="$HOME/$dir_name/$skills"
446
+ backup_item "$path" "$dir_name" "skills"
447
+ merge_items "$path" "$SKILLS_DIR" "skills"
448
+ fi
449
+
450
+ # Backup and merge agents
451
+ if [[ "$agents" != "." ]]; then
452
+ local path="$HOME/$dir_name/$agents"
453
+ backup_item "$path" "$dir_name" "agents"
454
+ merge_items "$path" "$AGENTS_DIR" "agents"
455
+ fi
456
+
457
+ # Backup and merge rules
458
+ if [[ "$rules" != "." ]]; then
459
+ local path="$HOME/$dir_name/$rules"
460
+ backup_item "$path" "$dir_name" "rules"
461
+ merge_items "$path" "$RULES_DIR" "rules"
462
+ fi
463
+
464
+ # Backup and merge commands
465
+ if [[ "$commands" != "." ]]; then
466
+ local path="$HOME/$dir_name/$commands"
467
+ backup_item "$path" "$dir_name" "commands"
468
+ merge_items "$path" "$COMMANDS_DIR" "commands"
469
+ fi
470
+
471
+ # Backup and merge prompts
472
+ if [[ "$prompts" != "." ]]; then
473
+ local path="$HOME/$dir_name/$prompts"
474
+ backup_item "$path" "$dir_name" "prompts"
475
+ merge_items "$path" "$PROMPTS_DIR" "prompts"
476
+ fi
477
+
478
+ echo "$dir_name" >> "$KNOWN_TOOLS_FILE"
479
+ fi
480
+ done
481
+
482
+ if [[ $tool_count -eq 0 ]]; then
483
+ log_info "No AI tools found."
484
+ return
485
+ fi
486
+
487
+ # Create symlinks for all tools
488
+ echo ""
489
+ log_info "Creating symlinks..."
490
+
491
+ while IFS= read -r dir_name; do
492
+ [[ -z "$dir_name" ]] && continue
493
+
494
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
495
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
496
+
497
+ if [[ "$dir_name" == "$known_dir" ]]; then
498
+ # Link global file
499
+ if [[ "$instr_file" != "." ]] && [[ "$instr_file" != *.json ]] && [[ "$instr_file" != *.yml ]]; then
500
+ local target="$HOME/$dir_name/$instr_file"
501
+ create_symlink "$INSTRUCTIONS_MD" "$target"
502
+ log_ok "$target -> instructions.md"
503
+ fi
504
+
505
+ # Link skills
506
+ if [[ "$skills" != "." ]]; then
507
+ local target="$HOME/$dir_name/$skills"
508
+ create_symlink "$SKILLS_DIR" "$target"
509
+ log_ok "$target -> skills/"
510
+ fi
511
+
512
+ # Link agents
513
+ if [[ "$agents" != "." ]]; then
514
+ local target="$HOME/$dir_name/$agents"
515
+ create_symlink "$AGENTS_DIR" "$target"
516
+ log_ok "$target -> agents/"
517
+ fi
518
+
519
+ # Link rules
520
+ if [[ "$rules" != "." ]]; then
521
+ local target="$HOME/$dir_name/$rules"
522
+ create_symlink "$RULES_DIR" "$target"
523
+ log_ok "$target -> rules/"
524
+ fi
525
+
526
+ # Link commands
527
+ if [[ "$commands" != "." ]]; then
528
+ local target="$HOME/$dir_name/$commands"
529
+ create_symlink "$COMMANDS_DIR" "$target"
530
+ log_ok "$target -> commands/"
531
+ fi
532
+
533
+ # Link prompts
534
+ if [[ "$prompts" != "." ]]; then
535
+ local target="$HOME/$dir_name/$prompts"
536
+ create_symlink "$PROMPTS_DIR" "$target"
537
+ log_ok "$target -> prompts/"
538
+ fi
539
+
540
+ break
541
+ fi
542
+ done
543
+ done < "$KNOWN_TOOLS_FILE"
544
+
545
+ echo ""
546
+ 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")"
547
+ }
548
+
549
+ # Restore a single tool
550
+ restore_single_tool() {
551
+ local tool_name="$1"
552
+ local dir_name="$2"
553
+ local instr_file="$3"
554
+ local skills="$4"
555
+ local agents="$5"
556
+ local rules="$6"
557
+ local commands="$7"
558
+ local prompts="$8"
559
+
560
+ log_ok "Restoring: $tool_name"
561
+ local backup_name=$(echo "$dir_name" | tr '/' '_')
562
+
563
+ # Restore global file
564
+ if [[ "$instr_file" != "." ]]; then
565
+ local target="$HOME/$dir_name/$instr_file"
566
+ if [[ -L "$target" ]]; then
567
+ rm "$target"
568
+ local backup_file=$(ls -t "$BACKUP_DIR"/${backup_name}.instructions.* 2>/dev/null | head -1)
569
+ [[ -f "$backup_file" ]] && cp "$backup_file" "$target"
570
+ fi
571
+ fi
572
+
573
+ # Restore skills
574
+ if [[ "$skills" != "." ]]; then
575
+ local target="$HOME/$dir_name/$skills"
576
+ if [[ -L "$target" ]]; then
577
+ rm "$target"
578
+ local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.skills.* 2>/dev/null | head -1)
579
+ if [[ -d "$backup_file" ]]; then
580
+ cp -r "$backup_file" "$target"
581
+ else
582
+ mkdir -p "$target"
583
+ fi
584
+ fi
585
+ fi
586
+
587
+ # Restore agents
588
+ if [[ "$agents" != "." ]]; then
589
+ local target="$HOME/$dir_name/$agents"
590
+ if [[ -L "$target" ]]; then
591
+ rm "$target"
592
+ local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.agents.* 2>/dev/null | head -1)
593
+ if [[ -d "$backup_file" ]]; then
594
+ cp -r "$backup_file" "$target"
595
+ else
596
+ mkdir -p "$target"
597
+ fi
598
+ fi
599
+ fi
600
+
601
+ # Restore rules
602
+ if [[ "$rules" != "." ]]; then
603
+ local target="$HOME/$dir_name/$rules"
604
+ if [[ -L "$target" ]]; then
605
+ rm "$target"
606
+ local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.rules.* 2>/dev/null | head -1)
607
+ if [[ -d "$backup_file" ]]; then
608
+ cp -r "$backup_file" "$target"
609
+ else
610
+ mkdir -p "$target"
611
+ fi
612
+ fi
613
+ fi
614
+
615
+ # Restore commands
616
+ if [[ "$commands" != "." ]]; then
617
+ local target="$HOME/$dir_name/$commands"
618
+ if [[ -L "$target" ]]; then
619
+ rm "$target"
620
+ local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.commands.* 2>/dev/null | head -1)
621
+ if [[ -d "$backup_file" ]]; then
622
+ cp -r "$backup_file" "$target"
623
+ else
624
+ mkdir -p "$target"
625
+ fi
626
+ fi
627
+ fi
628
+
629
+ # Restore prompts
630
+ if [[ "$prompts" != "." ]]; then
631
+ local target="$HOME/$dir_name/$prompts"
632
+ if [[ -L "$target" ]]; then
633
+ rm "$target"
634
+ local backup_file=$(ls -td "$BACKUP_DIR"/${backup_name}.prompts.* 2>/dev/null | head -1)
635
+ if [[ -d "$backup_file" ]]; then
636
+ cp -r "$backup_file" "$target"
637
+ else
638
+ mkdir -p "$target"
639
+ fi
640
+ fi
641
+ fi
642
+ }
643
+
644
+ # Restore all tools
645
+ restore_all_tools() {
646
+ log_info "Restoring all tools..."
647
+ echo ""
648
+
649
+ if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
650
+ log_info "No tools to restore"
651
+ return
652
+ fi
653
+
654
+ local restored_count=0
655
+
656
+ while IFS= read -r dir_name; do
657
+ [[ -z "$dir_name" ]] && continue
658
+
659
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
660
+ IFS='|' read -r known_dir tool_name instr_file skills agents rules commands prompts <<< "$pattern"
661
+
662
+ if [[ "$dir_name" == "$known_dir" ]]; then
663
+ restore_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"
664
+ ((restored_count++))
665
+ break
666
+ fi
667
+ done
668
+ done < "$KNOWN_TOOLS_FILE"
669
+
670
+ rm -f "$KNOWN_TOOLS_FILE"
671
+
672
+ echo ""
673
+ log_info "Restored $restored_count tool(s). Run 'ai-link' to re-link."
674
+ }
675
+
676
+ # Restore a specific tool
677
+ restore_tool() {
678
+ local tool_query="$1"
679
+
680
+ if [[ -z "$tool_query" ]]; then
681
+ log_error "Usage: ai-link restore <tool> or ai-link restore all"
682
+ echo ""
683
+ list_backups
684
+ return 1
685
+ fi
686
+
687
+ if [[ "$tool_query" == "all" ]]; then
688
+ restore_all_tools
689
+ return
690
+ fi
691
+
692
+ # Find matching tool
693
+ local found=false
694
+
695
+ for pattern in "${KNOWN_PATTERNS[@]}"; do
696
+ IFS='|' read -r dir_name tool_name instr_file skills agents rules commands prompts <<< "$pattern"
697
+ local tool_lower=$(echo "$tool_name" | tr '[:upper:]' '[:lower:]')
698
+ local query_lower=$(echo "$tool_query" | tr '[:upper:]' '[:lower:]')
699
+
700
+ if [[ "$tool_lower" == *"$query_lower"* ]] || [[ "$dir_name" == *"$query_lower"* ]]; then
701
+ log_info "Restoring $tool_name..."
702
+ restore_single_tool "$tool_name" "$dir_name" "$instr_file" "$skills" "$agents" "$rules" "$commands" "$prompts"
703
+
704
+ # Remove from known tools
705
+ if [[ -f "$KNOWN_TOOLS_FILE" ]]; then
706
+ grep -v "^$dir_name$" "$KNOWN_TOOLS_FILE" > "$KNOWN_TOOLS_FILE.tmp" 2>/dev/null || true
707
+ mv "$KNOWN_TOOLS_FILE.tmp" "$KNOWN_TOOLS_FILE"
708
+ fi
709
+
710
+ log_info "$tool_name restored"
711
+ found=true
712
+ break
713
+ fi
714
+ done
715
+
716
+ if [[ "$found" == false ]]; then
717
+ log_error "Tool not found: $tool_query"
718
+ echo ""
719
+ echo "Use 'ai-link list' to see supported tools"
720
+ return 1
721
+ fi
722
+ }
723
+
724
+ # Uninstall ai-link completely
725
+ uninstall() {
726
+ log_warn "This will:"
727
+ echo " 1. Restore all tools to original configuration"
728
+ echo " 2. Remove ~/.ai-config directory"
729
+ echo " 3. Remove ai-link from PATH"
730
+ echo ""
731
+ read -p "Are you sure? (y/N) " -r
732
+ echo ""
733
+
734
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
735
+ log_info "Cancelled"
736
+ return
737
+ fi
738
+
739
+ # Restore all tools first
740
+ if [[ -f "$KNOWN_TOOLS_FILE" ]]; then
741
+ restore_all_tools
742
+ fi
743
+
744
+ # Remove from PATH
745
+ [[ -L /usr/local/bin/ai-link ]] && rm -f /usr/local/bin/ai-link
746
+ [[ -L "$HOME/.local/bin/ai-link" ]] && rm -f "$HOME/.local/bin/ai-link"
747
+
748
+ # Remove config directory
749
+ rm -rf "$CONFIG_DIR"
750
+
751
+ log_ok "AI-Link uninstalled"
752
+ }
753
+
754
+ # Version
755
+ VERSION="1.0.0"
756
+
757
+ # Show version
758
+ show_version() {
759
+ echo "ai-link version $VERSION"
760
+ }
761
+
762
+ # Upgrade ai-link
763
+ upgrade_ailink() {
764
+ log_info "Checking for updates..."
765
+
766
+ # Get version from remote package.json
767
+ local remote_version
768
+ remote_version=$(curl -fsSL "https://raw.githubusercontent.com/nanxiaobei/ai-link/main/package.json" 2>/dev/null | grep '"version"' | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
769
+
770
+ if [[ -z "$remote_version" ]]; then
771
+ log_warn "Could not check for updates"
772
+ return 1
773
+ fi
774
+
775
+ if [[ "$remote_version" == "$VERSION" ]]; then
776
+ log_ok "Already at latest version ($VERSION)"
777
+ return 0
778
+ fi
779
+
780
+ log_info "Upgrading: $VERSION -> $remote_version"
781
+
782
+ # Download new version
783
+ local tmp_file=$(mktemp)
784
+ if curl -fsSL "https://raw.githubusercontent.com/nanxiaobei/ai-link/main/ai-link" -o "$tmp_file" 2>/dev/null; then
785
+ chmod +x "$tmp_file"
786
+ mv "$tmp_file" "$CONFIG_DIR/ai-link"
787
+ log_ok "Upgraded to v$remote_version"
788
+ else
789
+ rm -f "$tmp_file"
790
+ log_error "Failed to download update"
791
+ return 1
792
+ fi
793
+ }
794
+
795
+ # Add custom tool
796
+ add_tool() {
797
+ local dir_name="$1"
798
+ local instr_file="${2:-instructions.md}"
799
+
800
+ if [[ -z "$dir_name" ]]; then
801
+ log_error "Usage: ai-link add <directory> [instructions_file]"
802
+ echo ""
803
+ echo "Examples:"
804
+ echo " ai-link add .mytool"
805
+ echo " ai-link add .mytool INSTRUCTIONS.md"
806
+ return 1
807
+ fi
808
+
809
+ local full_path="$HOME/$dir_name"
810
+
811
+ if [[ ! -d "$full_path" ]]; then
812
+ log_error "Directory not found: $full_path"
813
+ return 1
814
+ fi
815
+
816
+ # Check if already known
817
+ if [[ -f "$KNOWN_TOOLS_FILE" ]] && grep -q "^$dir_name$" "$KNOWN_TOOLS_FILE"; then
818
+ log_warn "$dir_name is already configured"
819
+ return 0
820
+ fi
821
+
822
+ log_info "Adding custom tool: $dir_name"
823
+
824
+ # Backup existing instructions file
825
+ local actual_path="$full_path/$instr_file"
826
+ if [[ -f "$actual_path" ]] && [[ ! -L "$actual_path" ]]; then
827
+ backup_item "$actual_path" "$dir_name" "instructions"
828
+ fi
829
+
830
+ # Create symlink
831
+ mkdir -p "$(dirname "$actual_path")"
832
+ [[ -L "$actual_path" ]] && rm "$actual_path"
833
+ ln -s "$INSTRUCTIONS_MD" "$actual_path"
834
+ log_ok "$actual_path -> instructions.md"
835
+
836
+ # Add to known tools
837
+ echo "$dir_name" >> "$KNOWN_TOOLS_FILE"
838
+
839
+ log_ok "Added: $dir_name"
840
+ }
841
+
842
+ # Show help
843
+ show_help() {
844
+ cat << EOF
845
+ AI-Link: Unified AI Tools Configuration Manager v$VERSION
846
+
847
+ Usage: ai-link [command]
848
+
849
+ Commands:
850
+ update Scan, merge and update symlinks (default)
851
+ status Show symlink status
852
+ list List all supported AI tools
853
+ backups List available backups
854
+ restore <tool> Restore a tool's original config
855
+ restore all Restore all tools
856
+ add <dir> [file] Add a custom tool
857
+ upgrade Upgrade ai-link to latest version
858
+ uninstall Completely remove ai-link
859
+ version Show version
860
+ help Show this help
861
+
862
+ Shared directories:
863
+ ~/.ai-config/instructions.md Instructions for all tools
864
+ ~/.ai-config/skills/ Reusable skills
865
+ ~/.ai-config/agents/ Custom agents
866
+ ~/.ai-config/rules/ Code rules
867
+ ~/.ai-config/commands/ Slash commands
868
+ ~/.ai-config/prompts/ Prompt templates
869
+
870
+ Examples:
871
+ ai-link # Scan, merge and update symlinks
872
+ ai-link status # Check symlink status
873
+ ai-link add .mytool # Add custom tool
874
+ ai-link restore claude # Restore Claude's config
875
+ ai-link restore all # Restore all tools
876
+
877
+ EOF
878
+ }
879
+
880
+ # Main
881
+ main() {
882
+ local cmd="${1:-update}"
883
+
884
+ # Handle version flag anywhere
885
+ if [[ "$1" == "-v" ]] || [[ "$1" == "--version" ]] || [[ "$1" == "version" ]]; then
886
+ show_version
887
+ exit 0
888
+ fi
889
+
890
+ if [[ ! -f "$KNOWN_TOOLS_FILE" ]]; then
891
+ if [[ "$cmd" == "help" ]] || [[ "$cmd" == "--help" ]] || [[ "$cmd" == "-h" ]]; then
892
+ show_help
893
+ exit 0
894
+ fi
895
+ if [[ "$cmd" == "list" ]]; then
896
+ list_supported
897
+ exit 0
898
+ fi
899
+ if [[ "$cmd" == "version" ]] || [[ "$cmd" == "-v" ]] || [[ "$cmd" == "--version" ]]; then
900
+ show_version
901
+ exit 0
902
+ fi
903
+ cmd="update"
904
+ fi
905
+
906
+ case "$cmd" in
907
+ update) update_tools ;;
908
+ status) show_status ;;
909
+ list) list_supported ;;
910
+ backups) list_backups ;;
911
+ restore) restore_tool "$2" ;;
912
+ add) add_tool "$2" "$3" ;;
913
+ upgrade) upgrade_ailink ;;
914
+ uninstall) uninstall ;;
915
+ version|-v|--version) show_version ;;
916
+ help|--help|-h) show_help ;;
917
+ *) log_error "Unknown command: $cmd"; show_help; exit 1 ;;
918
+ esac
919
+ }
920
+
921
+ main "$@"
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "ai-link",
3
+ "version": "1.0.0",
4
+ "description": "Unified configuration manager for AI coding assistants",
5
+ "bin": {
6
+ "ai-link": "./ai-link"
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": "",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/nanxiaobei/ai-link.git"
26
+ },
27
+ "homepage": "https://github.com/nanxiaobei/ai-link#readme",
28
+ "engines": {
29
+ "node": ">=14"
30
+ },
31
+ "os": [
32
+ "darwin",
33
+ "linux"
34
+ ],
35
+ "files": [
36
+ "ai-link",
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-config');
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('');