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.
- package/README.md +72 -70
- package/ai-global +380 -273
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# AI
|
|
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
|
|
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
|
-
###
|
|
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/
|
|
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
|
|
94
|
-
|
|
95
|
-
| `ai-global`
|
|
96
|
-
| `ai-global status`
|
|
97
|
-
| `ai-global list`
|
|
98
|
-
| `ai-global backups`
|
|
99
|
-
| `ai-global
|
|
100
|
-
| `ai-global
|
|
101
|
-
| `ai-global skill <source>`
|
|
102
|
-
| `ai-global agent <source>`
|
|
103
|
-
| `ai-global rule <source>`
|
|
104
|
-
| `ai-global command <source>` | Add a command
|
|
105
|
-
| `ai-global prompt <source>`
|
|
106
|
-
| `ai-global upgrade`
|
|
107
|
-
| `ai-global uninstall`
|
|
108
|
-
| `ai-global version`
|
|
109
|
-
| `ai-global 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
|
-
├──
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
88
|
-
".smol|Smol Developer|instructions.md
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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=$(
|
|
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
|
|
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
|
-
|
|
252
|
-
|
|
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
|
|
255
|
-
[[ "$
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
285
|
-
echo "
|
|
365
|
+
printf " ${BLUE}%-25s %-12s %s${NC}\n" "Tool" "Type" "Backup File"
|
|
366
|
+
echo " --------------------------------------------------------------------"
|
|
286
367
|
|
|
287
|
-
|
|
288
|
-
[[
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
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" > "$
|
|
414
|
+
echo -e "$merged_content" > "$GLOBAL_MD"
|
|
333
415
|
log_ok "Merged instructions from $found_count tool(s)"
|
|
334
416
|
else
|
|
335
|
-
cat > "$
|
|
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: $
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
409
|
-
|
|
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
|
-
|
|
412
|
-
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
422
|
-
|
|
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=$(
|
|
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
|
-
#
|
|
448
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
#
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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 [[
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
590
|
+
return 1
|
|
591
|
+
}
|
|
505
592
|
|
|
506
|
-
|
|
507
|
-
|
|
593
|
+
# Unlink all tools
|
|
594
|
+
unlink_all_tools() {
|
|
595
|
+
log_info "Unlinking tools..."
|
|
596
|
+
echo ""
|
|
508
597
|
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
617
|
+
# Clear all backups as requested
|
|
618
|
+
rm -rf "$BACKUP_DIR"/* 2>/dev/null || true
|
|
521
619
|
|
|
522
620
|
echo ""
|
|
523
|
-
|
|
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
|
-
#
|
|
527
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
551
|
-
|
|
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
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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)
|
|
651
|
-
agent)
|
|
652
|
-
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
|
-
|
|
658
|
-
for dir in $type_dirs; do
|
|
767
|
+
for dir in $search_dirs; do
|
|
659
768
|
if [[ -d "$source_dir/$dir" ]]; then
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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" "$
|
|
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
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
(default)
|
|
841
|
-
status
|
|
842
|
-
list
|
|
843
|
-
backups
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
log_info "No
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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
|
-
|
|
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" ;;
|