@brickhouse-tech/sync-agents 0.1.2 → 0.1.4
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/LICENSE +21 -0
- package/README.md +16 -4
- package/package.json +1 -1
- package/src/md/SKILL_TEMPLATE.md +23 -0
- package/src/md/STATE_TEMPLATE.md +5 -31
- package/src/md/WORKFLOW_TEMPLATE.md +24 -0
- package/src/sh/sync-agents.sh +212 -17
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brickhouse Tech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# sync-agents
|
|
2
2
|
|
|
3
|
-
One set of agent rules to rule them all. `sync-agents` keeps your AI coding agent configurations in a single `.agents/` directory and syncs them to agent-specific directories (`.claude/`, `.windsurf/`) via symlinks. This ensures all agents follow the same rules, skills, and workflows without duplicating files.
|
|
3
|
+
One set of agent rules to rule them all. `sync-agents` keeps your AI coding agent configurations in a single `.agents/` directory and syncs them to agent-specific directories (`.claude/`, `.windsurf/`, `.cursor/`, `.github/copilot/`) via symlinks. This ensures all agents follow the same rules, skills, and workflows without duplicating files.
|
|
4
4
|
|
|
5
5
|
AGENTS.md serves as an auto-generated index of everything in `.agents/` and is symlinked to CLAUDE.md for Claude compatibility.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
+
### npm
|
|
10
|
+
|
|
9
11
|
```bash
|
|
10
12
|
npm install @brickhouse-tech/sync-agents
|
|
11
13
|
```
|
|
@@ -16,6 +18,13 @@ or globally:
|
|
|
16
18
|
npm install -g @brickhouse-tech/sync-agents
|
|
17
19
|
```
|
|
18
20
|
|
|
21
|
+
### Standalone (no npm required)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
curl -fsSL https://raw.githubusercontent.com/brickhouse-tech/sync-agents/main/src/sh/sync-agents.sh -o /usr/local/bin/sync-agents
|
|
25
|
+
chmod +x /usr/local/bin/sync-agents
|
|
26
|
+
```
|
|
27
|
+
|
|
19
28
|
## Topology
|
|
20
29
|
|
|
21
30
|
`.agents/` is the source of truth. It contains all rules, skills, workflows, and state for your agents:
|
|
@@ -37,7 +46,7 @@ npm install -g @brickhouse-tech/sync-agents
|
|
|
37
46
|
└── STATE.md
|
|
38
47
|
```
|
|
39
48
|
|
|
40
|
-
Running `sync-agents sync` creates symlinks from `.agents/` subdirectories into `.claude
|
|
49
|
+
Running `sync-agents sync` creates symlinks from `.agents/` subdirectories into `.claude/`, `.windsurf/`, `.cursor/`, and `.github/copilot/`. Any changes to `.agents/` are automatically reflected in the target directories because they are symlinks, not copies.
|
|
41
50
|
|
|
42
51
|
AGENTS.md is also symlinked to CLAUDE.md so that Claude reads the index natively.
|
|
43
52
|
|
|
@@ -50,7 +59,10 @@ AGENTS.md is also symlinked to CLAUDE.md so that Claude reads the index natively
|
|
|
50
59
|
| Command | Description |
|
|
51
60
|
|---|---|
|
|
52
61
|
| `init` | Initialize the `.agents/` directory structure with `rules/`, `skills/`, `workflows/`, `STATE.md`, and generate `AGENTS.md` |
|
|
53
|
-
| `sync` | Create symlinks from `.agents/` into
|
|
62
|
+
| `sync` | Create symlinks from `.agents/` into all target directories, and symlink `AGENTS.md` to `CLAUDE.md` |
|
|
63
|
+
| `watch` | Watch `.agents/` for changes and auto-regenerate `AGENTS.md` |
|
|
64
|
+
| `import <url>` | Import a rule/skill/workflow from a URL |
|
|
65
|
+
| `hook` | Install a pre-commit git hook for auto-sync |
|
|
54
66
|
| `status` | Show the current sync status of all targets and symlinks |
|
|
55
67
|
| `add <type> <name>` | Add a new rule, skill, or workflow from a template (type is `rule`, `skill`, or `workflow`) |
|
|
56
68
|
| `index` | Regenerate `AGENTS.md` by scanning the contents of `.agents/` |
|
|
@@ -63,7 +75,7 @@ AGENTS.md is also symlinked to CLAUDE.md so that Claude reads the index natively
|
|
|
63
75
|
| `-h`, `--help` | Show help message |
|
|
64
76
|
| `-v`, `--version` | Show version |
|
|
65
77
|
| `-d`, `--dir <path>` | Set project root directory (default: current directory) |
|
|
66
|
-
| `--targets <list>` | Comma-separated list of sync targets (default: `claude,windsurf`) |
|
|
78
|
+
| `--targets <list>` | Comma-separated list of sync targets (default: `claude,windsurf,cursor,copilot`) |
|
|
67
79
|
| `--dry-run` | Show what would be done without making changes |
|
|
68
80
|
| `--force` | Overwrite existing files and symlinks |
|
|
69
81
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# ${NAME}
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
Brief description of what this skill enables.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
When to use this skill and how to invoke it.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Example usage or invocation here.
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Notes
|
|
22
|
+
|
|
23
|
+
Any caveats, prerequisites, or related skills.
|
package/src/md/STATE_TEMPLATE.md
CHANGED
|
@@ -1,42 +1,16 @@
|
|
|
1
|
-
|
|
2
1
|
---
|
|
3
2
|
trigger: always_on
|
|
4
3
|
---
|
|
5
4
|
|
|
6
5
|
# State
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
The goal is to improve the performance of the agent by providing it with a clear and structured representation of its current situation. This allows the agent to make informed decisions and take appropriate actions to achieve its goals. The state is also used to track the progress of the agent and identify any issues or areas for improvement. By maintaining an up-to-date state, the agent can continuously learn and evolve, becoming more effective and efficient in its xtasks.
|
|
11
|
-
|
|
12
|
-
## TRACKING Agent State
|
|
13
|
-
|
|
14
|
-
Every state tracking will begin with a header of `### YYYYMMDDHHMMSS STATE: <STATE_NAME or OBJECTIVE` followed by a description of the state, any relevant information about the agent's performance, issues, or updates, and any actions taken or planned to address the current state. This format allows for easy tracking and monitoring of the agent's progress over time, as well as providing a clear record of the agent's history and development. By maintaining a detailed and organized state history, the agent can learn from past experiences and make informed decisions to improve its performance in the future.>
|
|
15
|
-
|
|
16
|
-
The format above will be written below the `## STATE HISTORY BELOW` header in the STATE.md file. This allows for easy tracking and monitoring of the agent's progress over time, as well as providing a clear record of the agent's history and development. By maintaining a detailed and organized state history, the agent can learn from past experiences and make informed decisions to improve its performance in the future.
|
|
17
|
-
|
|
18
|
-
## Formatted Agent State
|
|
7
|
+
Track project progress, current objectives, and resumption context.
|
|
8
|
+
Update this file regularly so agents can pick up where they left off.
|
|
19
9
|
|
|
20
|
-
|
|
10
|
+
## Format
|
|
21
11
|
|
|
22
|
-
|
|
12
|
+
### YYYYMMDDHHMMSS STATE: <objective>
|
|
23
13
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```json
|
|
27
|
-
{
|
|
28
|
-
"agent_name": "string",
|
|
29
|
-
"goals": ["string"],
|
|
30
|
-
"skills": ["string"],
|
|
31
|
-
"workflows": ["string"],
|
|
32
|
-
"issues": ["string"],
|
|
33
|
-
"last_updated": "timestamp"
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
to `.agents/state.json`
|
|
14
|
+
Description of current state, progress, blockers, and next steps.
|
|
37
15
|
|
|
38
16
|
## STATE HISTORY BELOW
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# ${NAME}
|
|
6
|
+
|
|
7
|
+
## Trigger
|
|
8
|
+
|
|
9
|
+
Describe when this workflow should be activated.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. Step one
|
|
14
|
+
2. Step two
|
|
15
|
+
3. Step three
|
|
16
|
+
|
|
17
|
+
## Conditions
|
|
18
|
+
|
|
19
|
+
- Pre-conditions that must be met
|
|
20
|
+
- Any guards or checks
|
|
21
|
+
|
|
22
|
+
## Output
|
|
23
|
+
|
|
24
|
+
What the workflow produces or changes when complete.
|
package/src/sh/sync-agents.sh
CHANGED
|
@@ -8,14 +8,18 @@ PACKAGE_JSON="${SCRIPT_DIR}/../../package.json"
|
|
|
8
8
|
TEMPLATES_DIR="${SCRIPT_DIR}/../md"
|
|
9
9
|
|
|
10
10
|
# Pull version from package.json
|
|
11
|
+
# Try relative path first (works in repo / npm local install),
|
|
12
|
+
# then resolve via node for global installs where the symlink target differs.
|
|
11
13
|
if [[ -f "$PACKAGE_JSON" ]]; then
|
|
12
14
|
VERSION="$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$PACKAGE_JSON" | head -1)"
|
|
15
|
+
elif command -v node >/dev/null 2>&1; then
|
|
16
|
+
VERSION="$(node -p "require('@brickhouse-tech/sync-agents/package.json').version" 2>/dev/null || echo "unknown")"
|
|
13
17
|
else
|
|
14
18
|
VERSION="unknown"
|
|
15
19
|
fi
|
|
16
20
|
|
|
17
21
|
# Agent target directories
|
|
18
|
-
TARGETS=("claude" "windsurf")
|
|
22
|
+
TARGETS=("claude" "windsurf" "cursor" "copilot")
|
|
19
23
|
|
|
20
24
|
# Colors (disabled if not a terminal)
|
|
21
25
|
if [[ -t 1 ]]; then
|
|
@@ -33,6 +37,27 @@ info() { echo -e "${GREEN}[info]${RESET} $*"; }
|
|
|
33
37
|
warn() { echo -e "${YELLOW}[warn]${RESET} $*"; }
|
|
34
38
|
error() { echo -e "${RED}[error]${RESET} $*" >&2; }
|
|
35
39
|
|
|
40
|
+
# Resolve target directory path (copilot uses .github/copilot/ instead of .copilot/)
|
|
41
|
+
resolve_target_dir() {
|
|
42
|
+
local target="$1"
|
|
43
|
+
local root="$2"
|
|
44
|
+
if [[ "$target" == "copilot" ]]; then
|
|
45
|
+
echo "$root/.github/copilot"
|
|
46
|
+
else
|
|
47
|
+
echo "$root/.$target"
|
|
48
|
+
fi
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Resolve relative path from target dir back to .agents/ (accounts for depth)
|
|
52
|
+
resolve_agents_rel() {
|
|
53
|
+
local target="$1"
|
|
54
|
+
if [[ "$target" == "copilot" ]]; then
|
|
55
|
+
echo "../../$AGENTS_DIR"
|
|
56
|
+
else
|
|
57
|
+
echo "../$AGENTS_DIR"
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
36
61
|
usage() {
|
|
37
62
|
cat <<EOF
|
|
38
63
|
${BOLD}sync-agents${RESET} v${VERSION} - One set of agent rules to rule them all.
|
|
@@ -42,17 +67,20 @@ ${BOLD}USAGE${RESET}
|
|
|
42
67
|
|
|
43
68
|
${BOLD}COMMANDS${RESET}
|
|
44
69
|
init Initialize .agents/ directory structure and AGENTS.md
|
|
45
|
-
sync Sync .agents/ to
|
|
70
|
+
sync Sync .agents/ to agent directories via symlinks
|
|
46
71
|
status Show current sync status
|
|
47
72
|
add <type> <name> Add a new rule, skill, or workflow from template
|
|
48
73
|
index Regenerate AGENTS.md index from .agents/ contents
|
|
49
74
|
clean Remove all synced symlinks (does not remove .agents/)
|
|
75
|
+
watch Watch .agents/ for changes and auto-regenerate index
|
|
76
|
+
import <url> Import a rule/skill/workflow from a URL
|
|
77
|
+
hook Install a pre-commit git hook for auto-sync
|
|
50
78
|
|
|
51
79
|
${BOLD}OPTIONS${RESET}
|
|
52
80
|
-h, --help Show this help message
|
|
53
81
|
-v, --version Show version
|
|
54
82
|
-d, --dir <path> Set project root directory (default: current directory)
|
|
55
|
-
--targets <list> Comma-separated targets
|
|
83
|
+
--targets <list> Comma-separated targets (overrides .agents/config)
|
|
56
84
|
--dry-run Show what would be done without making changes
|
|
57
85
|
--force Overwrite existing files/symlinks
|
|
58
86
|
|
|
@@ -170,6 +198,19 @@ STATE_EOF
|
|
|
170
198
|
warn "$AGENTS_DIR/STATE.md already exists, skipping"
|
|
171
199
|
fi
|
|
172
200
|
|
|
201
|
+
# Create default config if it doesn't exist
|
|
202
|
+
if [[ ! -f "$PROJECT_ROOT/$AGENTS_DIR/config" ]]; then
|
|
203
|
+
cat > "$PROJECT_ROOT/$AGENTS_DIR/config" <<CONFIG_EOF
|
|
204
|
+
# sync-agents configuration
|
|
205
|
+
# Comma-separated list of sync targets (available: claude, windsurf, cursor, copilot)
|
|
206
|
+
# Override per-command with: sync-agents sync --targets claude,cursor
|
|
207
|
+
targets = claude,windsurf,cursor,copilot
|
|
208
|
+
CONFIG_EOF
|
|
209
|
+
info "Created $AGENTS_DIR/config"
|
|
210
|
+
else
|
|
211
|
+
warn "$AGENTS_DIR/config already exists, skipping"
|
|
212
|
+
fi
|
|
213
|
+
|
|
173
214
|
# Generate AGENTS.md if it doesn't exist
|
|
174
215
|
if [[ ! -f "$PROJECT_ROOT/$AGENTS_MD" ]]; then
|
|
175
216
|
generate_agents_md
|
|
@@ -210,8 +251,19 @@ cmd_add() {
|
|
|
210
251
|
exit 1
|
|
211
252
|
fi
|
|
212
253
|
|
|
213
|
-
# Use
|
|
214
|
-
|
|
254
|
+
# Use type-specific template (RULE_TEMPLATE, SKILL_TEMPLATE, WORKFLOW_TEMPLATE)
|
|
255
|
+
local template_name
|
|
256
|
+
case "$type" in
|
|
257
|
+
rules) template_name="RULE_TEMPLATE.md" ;;
|
|
258
|
+
skills) template_name="SKILL_TEMPLATE.md" ;;
|
|
259
|
+
workflows) template_name="WORKFLOW_TEMPLATE.md" ;;
|
|
260
|
+
*) template_name="RULE_TEMPLATE.md" ;;
|
|
261
|
+
esac
|
|
262
|
+
|
|
263
|
+
if [[ -f "$TEMPLATES_DIR/$template_name" ]]; then
|
|
264
|
+
sed "s/\${NAME}/$name/g" "$TEMPLATES_DIR/$template_name" > "$filepath"
|
|
265
|
+
elif [[ -f "$TEMPLATES_DIR/RULE_TEMPLATE.md" ]]; then
|
|
266
|
+
# Fallback to rule template if type-specific template missing
|
|
215
267
|
sed "s/\${NAME}/$name/g" "$TEMPLATES_DIR/RULE_TEMPLATE.md" > "$filepath"
|
|
216
268
|
else
|
|
217
269
|
cat > "$filepath" <<TMPL_EOF
|
|
@@ -240,14 +292,16 @@ cmd_sync() {
|
|
|
240
292
|
info "Syncing $AGENTS_DIR/ to agent directories..."
|
|
241
293
|
|
|
242
294
|
for target in "${ACTIVE_TARGETS[@]}"; do
|
|
243
|
-
local target_dir
|
|
244
|
-
|
|
295
|
+
local target_dir
|
|
296
|
+
target_dir="$(resolve_target_dir "$target" "$PROJECT_ROOT")"
|
|
297
|
+
local agents_rel
|
|
298
|
+
agents_rel="$(resolve_agents_rel "$target")"
|
|
299
|
+
info "Syncing to ${target_dir#"$PROJECT_ROOT"/}/"
|
|
245
300
|
|
|
246
301
|
# Sync subdirectories: rules, skills, workflows
|
|
247
302
|
for subdir in rules skills workflows; do
|
|
248
303
|
if [[ -d "$agents_abs/$subdir" ]]; then
|
|
249
|
-
local source_rel
|
|
250
|
-
source_rel="$(python3 -c "import os.path; print(os.path.relpath('$agents_abs/$subdir', '$(dirname "$target_dir/$subdir")'))" 2>/dev/null || echo "../$AGENTS_DIR/$subdir")"
|
|
304
|
+
local source_rel="$agents_rel/$subdir"
|
|
251
305
|
create_symlink "$source_rel" "$target_dir/$subdir" "$DRY_RUN"
|
|
252
306
|
fi
|
|
253
307
|
done
|
|
@@ -297,9 +351,11 @@ cmd_status() {
|
|
|
297
351
|
|
|
298
352
|
# Check each target
|
|
299
353
|
for target in "${TARGETS[@]}"; do
|
|
300
|
-
local target_dir
|
|
354
|
+
local target_dir
|
|
355
|
+
target_dir="$(resolve_target_dir "$target" "$PROJECT_ROOT")"
|
|
356
|
+
local display_dir="${target_dir#"$PROJECT_ROOT"/}"
|
|
301
357
|
if [[ -d "$target_dir" ]] || [[ -L "$target_dir/rules" ]]; then
|
|
302
|
-
echo -e "${CYAN}
|
|
358
|
+
echo -e "${CYAN}${display_dir}/${RESET}"
|
|
303
359
|
for subdir in rules skills workflows; do
|
|
304
360
|
if [[ -L "$target_dir/$subdir" ]]; then
|
|
305
361
|
local link_target
|
|
@@ -312,7 +368,7 @@ cmd_status() {
|
|
|
312
368
|
fi
|
|
313
369
|
done
|
|
314
370
|
else
|
|
315
|
-
echo -e "${RED}[not synced]${RESET}
|
|
371
|
+
echo -e "${RED}[not synced]${RESET} ${display_dir}/"
|
|
316
372
|
fi
|
|
317
373
|
done
|
|
318
374
|
}
|
|
@@ -323,22 +379,141 @@ cmd_index() {
|
|
|
323
379
|
info "Regenerated $AGENTS_MD"
|
|
324
380
|
}
|
|
325
381
|
|
|
382
|
+
cmd_watch() {
|
|
383
|
+
ensure_agents_dir
|
|
384
|
+
|
|
385
|
+
local watch_dir="$PROJECT_ROOT/$AGENTS_DIR"
|
|
386
|
+
|
|
387
|
+
if command -v fswatch >/dev/null 2>&1; then
|
|
388
|
+
info "Watching $AGENTS_DIR/ for changes... (Ctrl+C to stop)"
|
|
389
|
+
cmd_index
|
|
390
|
+
fswatch -o "$watch_dir" | while read -r _; do
|
|
391
|
+
info "Change detected, regenerating index..."
|
|
392
|
+
cmd_index
|
|
393
|
+
done
|
|
394
|
+
elif command -v inotifywait >/dev/null 2>&1; then
|
|
395
|
+
info "Watching $AGENTS_DIR/ for changes... (Ctrl+C to stop)"
|
|
396
|
+
cmd_index
|
|
397
|
+
inotifywait -m -r -e modify,create,delete,move --format '%w%f' "$watch_dir" | while read -r _; do
|
|
398
|
+
info "Change detected, regenerating index..."
|
|
399
|
+
cmd_index
|
|
400
|
+
done
|
|
401
|
+
else
|
|
402
|
+
error "Neither fswatch (macOS) nor inotifywait (Linux) found."
|
|
403
|
+
error "Install with: brew install fswatch OR apt install inotify-tools"
|
|
404
|
+
exit 1
|
|
405
|
+
fi
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
cmd_import() {
|
|
409
|
+
local url="${1:-}"
|
|
410
|
+
if [[ -z "$url" ]]; then
|
|
411
|
+
error "Usage: sync-agents import <url>"
|
|
412
|
+
exit 1
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
ensure_agents_dir
|
|
416
|
+
|
|
417
|
+
local filename
|
|
418
|
+
filename="$(basename "$url")"
|
|
419
|
+
if [[ "$filename" != *.md ]]; then
|
|
420
|
+
filename="${filename}.md"
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
# Auto-detect type from URL path
|
|
424
|
+
local type=""
|
|
425
|
+
case "$url" in
|
|
426
|
+
*/rules/*) type="rules" ;;
|
|
427
|
+
*/skills/*) type="skills" ;;
|
|
428
|
+
*/workflows/*) type="workflows" ;;
|
|
429
|
+
esac
|
|
430
|
+
|
|
431
|
+
if [[ -z "$type" ]]; then
|
|
432
|
+
echo "Could not detect type from URL. Choose:"
|
|
433
|
+
echo " 1) rule"
|
|
434
|
+
echo " 2) skill"
|
|
435
|
+
echo " 3) workflow"
|
|
436
|
+
read -rp "Selection (1-3): " choice
|
|
437
|
+
case "$choice" in
|
|
438
|
+
1) type="rules" ;;
|
|
439
|
+
2) type="skills" ;;
|
|
440
|
+
3) type="workflows" ;;
|
|
441
|
+
*) error "Invalid selection"; exit 1 ;;
|
|
442
|
+
esac
|
|
443
|
+
fi
|
|
444
|
+
|
|
445
|
+
mkdir -p "$PROJECT_ROOT/$AGENTS_DIR/$type"
|
|
446
|
+
local dest="$PROJECT_ROOT/$AGENTS_DIR/$type/$filename"
|
|
447
|
+
|
|
448
|
+
info "Importing $url → $AGENTS_DIR/$type/$filename"
|
|
449
|
+
|
|
450
|
+
if ! curl -fsSL "$url" -o "$dest"; then
|
|
451
|
+
error "Failed to download: $url"
|
|
452
|
+
exit 1
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
info "Imported successfully."
|
|
456
|
+
cmd_index
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
cmd_hook() {
|
|
460
|
+
if [[ ! -d "$PROJECT_ROOT/.git" ]]; then
|
|
461
|
+
error "Not a git repository (no .git/ found)."
|
|
462
|
+
exit 1
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
local hook_dir="$PROJECT_ROOT/.git/hooks"
|
|
466
|
+
local hook_file="$hook_dir/pre-commit"
|
|
467
|
+
mkdir -p "$hook_dir"
|
|
468
|
+
|
|
469
|
+
local marker="sync-agents start"
|
|
470
|
+
|
|
471
|
+
if [[ -f "$hook_file" ]] && grep -q "$marker" "$hook_file"; then
|
|
472
|
+
info "Git hook already installed in $hook_file"
|
|
473
|
+
return 0
|
|
474
|
+
fi
|
|
475
|
+
|
|
476
|
+
local hook_block
|
|
477
|
+
hook_block="$(cat <<'HOOK'
|
|
478
|
+
|
|
479
|
+
# --- sync-agents start ---
|
|
480
|
+
if command -v sync-agents >/dev/null 2>&1; then
|
|
481
|
+
sync-agents sync 2>/dev/null
|
|
482
|
+
sync-agents index 2>/dev/null
|
|
483
|
+
git add AGENTS.md CLAUDE.md .claude/ .windsurf/ .cursor/ .github/copilot/ 2>/dev/null || true
|
|
484
|
+
fi
|
|
485
|
+
# --- sync-agents end ---
|
|
486
|
+
HOOK
|
|
487
|
+
)"
|
|
488
|
+
|
|
489
|
+
if [[ -f "$hook_file" ]]; then
|
|
490
|
+
echo "$hook_block" >> "$hook_file"
|
|
491
|
+
info "Appended sync-agents hook to existing $hook_file"
|
|
492
|
+
else
|
|
493
|
+
printf '#!/bin/sh\n%s\n' "$hook_block" > "$hook_file"
|
|
494
|
+
chmod +x "$hook_file"
|
|
495
|
+
info "Created git hook: $hook_file"
|
|
496
|
+
fi
|
|
497
|
+
}
|
|
498
|
+
|
|
326
499
|
cmd_clean() {
|
|
327
500
|
info "Removing synced symlinks..."
|
|
328
501
|
|
|
329
502
|
for target in "${ACTIVE_TARGETS[@]}"; do
|
|
330
|
-
local target_dir
|
|
503
|
+
local target_dir
|
|
504
|
+
target_dir="$(resolve_target_dir "$target" "$PROJECT_ROOT")"
|
|
505
|
+
local display_dir="${target_dir#"$PROJECT_ROOT"/}"
|
|
331
506
|
for subdir in rules skills workflows; do
|
|
332
507
|
if [[ -L "$target_dir/$subdir" ]]; then
|
|
333
508
|
rm "$target_dir/$subdir"
|
|
334
|
-
info "Removed:
|
|
509
|
+
info "Removed: ${display_dir}/$subdir"
|
|
335
510
|
fi
|
|
336
511
|
done
|
|
337
512
|
|
|
338
513
|
# Remove target dir if empty
|
|
339
514
|
if [[ -d "$target_dir" ]] && [[ -z "$(ls -A "$target_dir" 2>/dev/null)" ]]; then
|
|
340
515
|
rmdir "$target_dir"
|
|
341
|
-
info "Removed empty directory:
|
|
516
|
+
info "Removed empty directory: ${display_dir}/"
|
|
342
517
|
fi
|
|
343
518
|
done
|
|
344
519
|
|
|
@@ -529,11 +704,22 @@ main() {
|
|
|
529
704
|
PROJECT_ROOT="$(find_project_root)"
|
|
530
705
|
fi
|
|
531
706
|
|
|
532
|
-
# Resolve active targets
|
|
707
|
+
# Resolve active targets (priority: --targets flag > .agents/config > built-in defaults)
|
|
533
708
|
if [[ -n "$custom_targets" ]]; then
|
|
534
709
|
IFS=',' read -ra ACTIVE_TARGETS <<< "$custom_targets"
|
|
535
710
|
else
|
|
536
|
-
|
|
711
|
+
local config_file="$PROJECT_ROOT/$AGENTS_DIR/config"
|
|
712
|
+
if [[ -f "$config_file" ]]; then
|
|
713
|
+
local config_targets
|
|
714
|
+
config_targets="$(sed -n 's/^targets *= *//p' "$config_file" | tr -d ' ')"
|
|
715
|
+
if [[ -n "$config_targets" ]]; then
|
|
716
|
+
IFS=',' read -ra ACTIVE_TARGETS <<< "$config_targets"
|
|
717
|
+
else
|
|
718
|
+
ACTIVE_TARGETS=("${TARGETS[@]}")
|
|
719
|
+
fi
|
|
720
|
+
else
|
|
721
|
+
ACTIVE_TARGETS=("${TARGETS[@]}")
|
|
722
|
+
fi
|
|
537
723
|
fi
|
|
538
724
|
|
|
539
725
|
# Dispatch command
|
|
@@ -556,6 +742,15 @@ main() {
|
|
|
556
742
|
clean)
|
|
557
743
|
cmd_clean
|
|
558
744
|
;;
|
|
745
|
+
watch)
|
|
746
|
+
cmd_watch
|
|
747
|
+
;;
|
|
748
|
+
import)
|
|
749
|
+
cmd_import "$@"
|
|
750
|
+
;;
|
|
751
|
+
hook)
|
|
752
|
+
cmd_hook
|
|
753
|
+
;;
|
|
559
754
|
"")
|
|
560
755
|
usage
|
|
561
756
|
exit 0
|