@brickhouse-tech/sync-agents 0.1.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,108 @@
1
+ # sync-agents
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.
4
+
5
+ AGENTS.md serves as an auto-generated index of everything in `.agents/` and is symlinked to CLAUDE.md for Claude compatibility.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @brickhouse-tech/sync-agents
11
+ ```
12
+
13
+ or globally:
14
+
15
+ ```bash
16
+ npm install -g @brickhouse-tech/sync-agents
17
+ ```
18
+
19
+ ## Topology
20
+
21
+ `.agents/` is the source of truth. It contains all rules, skills, workflows, and state for your agents:
22
+
23
+ ```
24
+ .agents/
25
+ ├── rules/
26
+ │ ├── rule1.md
27
+ │ ├── rule2.md
28
+ │ └── ...
29
+ ├── skills/
30
+ │ ├── skill1.md
31
+ │ ├── skill2.md
32
+ │ └── ...
33
+ ├── workflows/
34
+ │ ├── workflow1.md
35
+ │ ├── workflow2.md
36
+ │ └── ...
37
+ └── STATE.md
38
+ ```
39
+
40
+ Running `sync-agents sync` creates symlinks from `.agents/` subdirectories into `.claude/` and `.windsurf/`. Any changes to `.agents/` are automatically reflected in the target directories because they are symlinks, not copies.
41
+
42
+ AGENTS.md is also symlinked to CLAUDE.md so that Claude reads the index natively.
43
+
44
+ ## STATE.md
45
+
46
+ `.agents/STATE.md` tracks the current state of your project from the agent's perspective. It serves as a resumption point after failures or interruptions -- the agent can read STATE.md to determine where it left off and what tasks remain. Update it regularly to keep agents in sync with progress.
47
+
48
+ ## Commands
49
+
50
+ | Command | Description |
51
+ |---|---|
52
+ | `init` | Initialize the `.agents/` directory structure with `rules/`, `skills/`, `workflows/`, `STATE.md`, and generate `AGENTS.md` |
53
+ | `sync` | Create symlinks from `.agents/` into `.claude/` and `.windsurf/`, and symlink `AGENTS.md` to `CLAUDE.md` |
54
+ | `status` | Show the current sync status of all targets and symlinks |
55
+ | `add <type> <name>` | Add a new rule, skill, or workflow from a template (type is `rule`, `skill`, or `workflow`) |
56
+ | `index` | Regenerate `AGENTS.md` by scanning the contents of `.agents/` |
57
+ | `clean` | Remove all synced symlinks and empty target directories (does not remove `.agents/`) |
58
+
59
+ ## Options
60
+
61
+ | Option | Description |
62
+ |---|---|
63
+ | `-h`, `--help` | Show help message |
64
+ | `-v`, `--version` | Show version |
65
+ | `-d`, `--dir <path>` | Set project root directory (default: current directory) |
66
+ | `--targets <list>` | Comma-separated list of sync targets (default: `claude,windsurf`) |
67
+ | `--dry-run` | Show what would be done without making changes |
68
+ | `--force` | Overwrite existing files and symlinks |
69
+
70
+ ## Usage
71
+
72
+ ```bash
73
+ # Initialize .agents/ structure in the current project
74
+ sync-agents init
75
+
76
+ # Add a new rule
77
+ sync-agents add rule no-eval
78
+
79
+ # Add a new skill
80
+ sync-agents add skill debugging
81
+
82
+ # Add a new workflow
83
+ sync-agents add workflow deploy
84
+
85
+ # Sync to all targets (.claude/ and .windsurf/)
86
+ sync-agents sync
87
+
88
+ # Sync to a specific target only
89
+ sync-agents sync --targets claude
90
+
91
+ # Preview sync without making changes
92
+ sync-agents sync --dry-run
93
+
94
+ # Force overwrite existing symlinks
95
+ sync-agents sync --force
96
+
97
+ # Check sync status
98
+ sync-agents status
99
+
100
+ # Regenerate the AGENTS.md index
101
+ sync-agents index
102
+
103
+ # Remove all synced symlinks
104
+ sync-agents clean
105
+
106
+ # Work in a different directory
107
+ sync-agents sync --dir /path/to/project
108
+ ```
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@brickhouse-tech/sync-agents",
3
+ "version": "0.1.0",
4
+ "description": "AWS CFT Utilities to fill the gaps for Cloud Formation",
5
+ "keywords": [
6
+ "cft",
7
+ "aws",
8
+ "cloudformation"
9
+ ],
10
+ "homepage": "https://github.com/brickhouse-tech/sync-agents#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/brickhouse-tech/sync-agents/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/brickhouse-tech/sync-agents.git"
17
+ },
18
+ "author": "Nicholas McCready",
19
+ "bin": {
20
+ "sync-agents": "src/sh/sync-agents.sh"
21
+ },
22
+ "files": [
23
+ "src/**/*"
24
+ ],
25
+ "scripts": {
26
+ "test": "npx bats test/"
27
+ },
28
+ "dependencies": {
29
+ },
30
+ "devDependencies": {
31
+ "@commitlint/cli": "^20",
32
+ "@commitlint/config-conventional": "^20",
33
+ "commitlint": "20",
34
+ "sort-package-json": "3.6"
35
+ }
36
+ }
@@ -0,0 +1,6 @@
1
+
2
+ ---
3
+ trigger: always_on
4
+ ---
5
+
6
+ # ${NAME}
@@ -0,0 +1,42 @@
1
+
2
+ ---
3
+ trigger: always_on
4
+ ---
5
+
6
+ # State
7
+
8
+ This is the state template for sync-agents. It is used to define the state of the agent and its environment. The state markdown file itself is a human readble source for the agent to read and understand its current situation. It can be used to track the progress of the agent, identify any issues or challenges it may be facing, and provide a clear overview of its goals and objectives. The state is an important tool for the agent to use in order to make informed decisions and take appropriate actions to achieve its goals. By maintaining an up-to-date state, the agent can continuously learn and evolve, becoming more effective and efficient in its tasks.
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
19
+
20
+ The formatted agent state will be a structured representation of the agent's current situation, including its goals, objectives, performance metrics, and any relevant information about its environment. This formatted state will be used by the agent to make informed decisions and take appropriate actions to achieve its goals. By maintaining an up-to-date and well-structured formatted state, the agent can continuously learn and evolve, becoming more effective and efficient in its tasks. The formatted state will also be used to track the progress of the agent and identify any issues or areas for improvement, allowing for continuous optimization of the agent's performance.
21
+
22
+ Example:
23
+
24
+ The state is structured as follows:
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`
37
+
38
+ ## STATE HISTORY BELOW
39
+
40
+
41
+
42
+
@@ -0,0 +1,569 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ AGENTS_DIR=".agents"
5
+ AGENTS_MD="AGENTS.md"
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ PACKAGE_JSON="${SCRIPT_DIR}/../../package.json"
8
+ TEMPLATES_DIR="${SCRIPT_DIR}/../md"
9
+
10
+ # Pull version from package.json
11
+ if [[ -f "$PACKAGE_JSON" ]]; then
12
+ VERSION="$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$PACKAGE_JSON" | head -1)"
13
+ else
14
+ VERSION="unknown"
15
+ fi
16
+
17
+ # Agent target directories
18
+ TARGETS=("claude" "windsurf")
19
+
20
+ # Colors (disabled if not a terminal)
21
+ if [[ -t 1 ]]; then
22
+ BOLD='\033[1m'
23
+ GREEN='\033[0;32m'
24
+ YELLOW='\033[0;33m'
25
+ RED='\033[0;31m'
26
+ CYAN='\033[0;36m'
27
+ RESET='\033[0m'
28
+ else
29
+ BOLD='' GREEN='' YELLOW='' RED='' CYAN='' RESET=''
30
+ fi
31
+
32
+ info() { echo -e "${GREEN}[info]${RESET} $*"; }
33
+ warn() { echo -e "${YELLOW}[warn]${RESET} $*"; }
34
+ error() { echo -e "${RED}[error]${RESET} $*" >&2; }
35
+
36
+ usage() {
37
+ cat <<EOF
38
+ ${BOLD}sync-agents${RESET} v${VERSION} - One set of agent rules to rule them all.
39
+
40
+ ${BOLD}USAGE${RESET}
41
+ sync-agents <command> [options]
42
+
43
+ ${BOLD}COMMANDS${RESET}
44
+ init Initialize .agents/ directory structure and AGENTS.md
45
+ sync Sync .agents/ to .claude/ and .windsurf/ via symlinks
46
+ status Show current sync status
47
+ add <type> <name> Add a new rule, skill, or workflow from template
48
+ index Regenerate AGENTS.md index from .agents/ contents
49
+ clean Remove all synced symlinks (does not remove .agents/)
50
+
51
+ ${BOLD}OPTIONS${RESET}
52
+ -h, --help Show this help message
53
+ -v, --version Show version
54
+ -d, --dir <path> Set project root directory (default: current directory)
55
+ --targets <list> Comma-separated targets to sync (default: claude,windsurf)
56
+ --dry-run Show what would be done without making changes
57
+ --force Overwrite existing files/symlinks
58
+
59
+ ${BOLD}EXAMPLES${RESET}
60
+ sync-agents init # Initialize .agents/ structure
61
+ sync-agents add rule no-eval # Add a new rule called "no-eval"
62
+ sync-agents sync # Sync to .claude/ and .windsurf/
63
+ sync-agents sync --targets claude
64
+ sync-agents status # Show current state
65
+ sync-agents clean # Remove synced symlinks
66
+
67
+ EOF
68
+ }
69
+
70
+ # --------------------------------------------------------------------------
71
+ # Helpers
72
+ # --------------------------------------------------------------------------
73
+
74
+ find_project_root() {
75
+ local dir="${1:-.}"
76
+ dir="$(cd "$dir" && pwd)"
77
+
78
+ # Walk up to find a directory with .agents or .git
79
+ while [[ "$dir" != "/" ]]; do
80
+ if [[ -d "$dir/$AGENTS_DIR" ]] || [[ -d "$dir/.git" ]]; then
81
+ echo "$dir"
82
+ return 0
83
+ fi
84
+ dir="$(dirname "$dir")"
85
+ done
86
+
87
+ # Fallback to current directory
88
+ pwd
89
+ }
90
+
91
+ ensure_agents_dir() {
92
+ if [[ ! -d "$PROJECT_ROOT/$AGENTS_DIR" ]]; then
93
+ error ".agents/ directory not found. Run 'sync-agents init' first."
94
+ exit 1
95
+ fi
96
+ }
97
+
98
+ create_symlink() {
99
+ local source="$1"
100
+ local target="$2"
101
+ local dry_run="${3:-false}"
102
+
103
+ if [[ "$dry_run" == "true" ]]; then
104
+ echo " would link: $target -> $source"
105
+ return 0
106
+ fi
107
+
108
+ local target_dir
109
+ target_dir="$(dirname "$target")"
110
+ mkdir -p "$target_dir"
111
+
112
+ if [[ -L "$target" ]]; then
113
+ local existing
114
+ existing="$(readlink "$target")"
115
+ if [[ "$existing" == "$source" ]]; then
116
+ return 0 # Already correct
117
+ fi
118
+ if [[ "$FORCE" == "true" ]]; then
119
+ rm "$target"
120
+ else
121
+ warn "Symlink already exists: $target -> $existing (use --force to overwrite)"
122
+ return 1
123
+ fi
124
+ elif [[ -e "$target" ]]; then
125
+ if [[ "$FORCE" == "true" ]]; then
126
+ rm -rf "$target"
127
+ else
128
+ warn "File already exists: $target (use --force to overwrite)"
129
+ return 1
130
+ fi
131
+ fi
132
+
133
+ ln -sf "$source" "$target"
134
+ info "Linked: $target -> $source"
135
+ }
136
+
137
+ # --------------------------------------------------------------------------
138
+ # Commands
139
+ # --------------------------------------------------------------------------
140
+
141
+ cmd_init() {
142
+ info "Initializing $AGENTS_DIR/ directory structure..."
143
+
144
+ mkdir -p "$PROJECT_ROOT/$AGENTS_DIR/rules"
145
+ mkdir -p "$PROJECT_ROOT/$AGENTS_DIR/skills"
146
+ mkdir -p "$PROJECT_ROOT/$AGENTS_DIR/workflows"
147
+
148
+ # Copy STATE.md template if STATE.md doesn't exist
149
+ if [[ ! -f "$PROJECT_ROOT/$AGENTS_DIR/STATE.md" ]]; then
150
+ if [[ -f "$TEMPLATES_DIR/STATE_TEMPLATE.md" ]]; then
151
+ cp "$TEMPLATES_DIR/STATE_TEMPLATE.md" "$PROJECT_ROOT/$AGENTS_DIR/STATE.md"
152
+ info "Created $AGENTS_DIR/STATE.md from template"
153
+ else
154
+ # Inline fallback if template not found
155
+ cat > "$PROJECT_ROOT/$AGENTS_DIR/STATE.md" <<'STATE_EOF'
156
+
157
+ ---
158
+ trigger: always_on
159
+ ---
160
+
161
+ # State
162
+
163
+ ## STATE HISTORY BELOW
164
+
165
+
166
+ STATE_EOF
167
+ info "Created $AGENTS_DIR/STATE.md"
168
+ fi
169
+ else
170
+ warn "$AGENTS_DIR/STATE.md already exists, skipping"
171
+ fi
172
+
173
+ # Generate AGENTS.md if it doesn't exist
174
+ if [[ ! -f "$PROJECT_ROOT/$AGENTS_MD" ]]; then
175
+ generate_agents_md
176
+ info "Created $AGENTS_MD"
177
+ else
178
+ warn "$AGENTS_MD already exists, skipping (run 'sync-agents index' to regenerate)"
179
+ fi
180
+
181
+ info "Initialization complete. Directory structure:"
182
+ print_tree "$PROJECT_ROOT/$AGENTS_DIR"
183
+ }
184
+
185
+ cmd_add() {
186
+ local type="${1:-}"
187
+ local name="${2:-}"
188
+
189
+ if [[ -z "$type" ]] || [[ -z "$name" ]]; then
190
+ error "Usage: sync-agents add <rule|skill|workflow> <name>"
191
+ exit 1
192
+ fi
193
+
194
+ case "$type" in
195
+ rule|rules) type="rules" ;;
196
+ skill|skills) type="skills" ;;
197
+ workflow|workflows) type="workflows" ;;
198
+ *)
199
+ error "Unknown type: $type. Must be one of: rule, skill, workflow"
200
+ exit 1
201
+ ;;
202
+ esac
203
+
204
+ ensure_agents_dir
205
+
206
+ local filepath="$PROJECT_ROOT/$AGENTS_DIR/$type/$name.md"
207
+
208
+ if [[ -f "$filepath" ]] && [[ "$FORCE" != "true" ]]; then
209
+ error "File already exists: $filepath (use --force to overwrite)"
210
+ exit 1
211
+ fi
212
+
213
+ # Use RULE_TEMPLATE for all types, substituting name
214
+ if [[ -f "$TEMPLATES_DIR/RULE_TEMPLATE.md" ]]; then
215
+ sed "s/\${NAME}/$name/g" "$TEMPLATES_DIR/RULE_TEMPLATE.md" > "$filepath"
216
+ else
217
+ cat > "$filepath" <<TMPL_EOF
218
+
219
+ ---
220
+ trigger: always_on
221
+ ---
222
+
223
+ # $name
224
+ TMPL_EOF
225
+ fi
226
+
227
+ info "Created $type: $filepath"
228
+
229
+ # Regenerate index
230
+ generate_agents_md
231
+ info "Updated $AGENTS_MD index"
232
+ }
233
+
234
+ cmd_sync() {
235
+ ensure_agents_dir
236
+
237
+ local agents_abs
238
+ agents_abs="$(cd "$PROJECT_ROOT/$AGENTS_DIR" && pwd)"
239
+
240
+ info "Syncing $AGENTS_DIR/ to agent directories..."
241
+
242
+ for target in "${ACTIVE_TARGETS[@]}"; do
243
+ local target_dir="$PROJECT_ROOT/.$target"
244
+ info "Syncing to .${target}/"
245
+
246
+ # Sync subdirectories: rules, skills, workflows
247
+ for subdir in rules skills workflows; do
248
+ 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")"
251
+ create_symlink "$source_rel" "$target_dir/$subdir" "$DRY_RUN"
252
+ fi
253
+ done
254
+ done
255
+
256
+ # Symlink AGENTS.md -> CLAUDE.md
257
+ if [[ -f "$PROJECT_ROOT/$AGENTS_MD" ]]; then
258
+ create_symlink "$AGENTS_MD" "$PROJECT_ROOT/CLAUDE.md" "$DRY_RUN"
259
+ fi
260
+
261
+ info "Sync complete."
262
+ }
263
+
264
+ cmd_status() {
265
+ echo -e "${BOLD}sync-agents${RESET} v${VERSION}"
266
+ echo ""
267
+
268
+ # Check .agents/ directory
269
+ if [[ -d "$PROJECT_ROOT/$AGENTS_DIR" ]]; then
270
+ echo -e "${GREEN}[ok]${RESET} $AGENTS_DIR/ exists"
271
+ print_tree "$PROJECT_ROOT/$AGENTS_DIR"
272
+ else
273
+ echo -e "${RED}[missing]${RESET} $AGENTS_DIR/ not found"
274
+ fi
275
+
276
+ echo ""
277
+
278
+ # Check AGENTS.md
279
+ if [[ -f "$PROJECT_ROOT/$AGENTS_MD" ]]; then
280
+ echo -e "${GREEN}[ok]${RESET} $AGENTS_MD exists"
281
+ else
282
+ echo -e "${RED}[missing]${RESET} $AGENTS_MD not found"
283
+ fi
284
+
285
+ # Check CLAUDE.md symlink
286
+ if [[ -L "$PROJECT_ROOT/CLAUDE.md" ]]; then
287
+ local link_target
288
+ link_target="$(readlink "$PROJECT_ROOT/CLAUDE.md")"
289
+ echo -e "${GREEN}[ok]${RESET} CLAUDE.md -> $link_target"
290
+ elif [[ -f "$PROJECT_ROOT/CLAUDE.md" ]]; then
291
+ echo -e "${YELLOW}[warn]${RESET} CLAUDE.md exists but is not a symlink"
292
+ else
293
+ echo -e "${RED}[missing]${RESET} CLAUDE.md not found"
294
+ fi
295
+
296
+ echo ""
297
+
298
+ # Check each target
299
+ for target in "${TARGETS[@]}"; do
300
+ local target_dir="$PROJECT_ROOT/.$target"
301
+ if [[ -d "$target_dir" ]] || [[ -L "$target_dir/rules" ]]; then
302
+ echo -e "${CYAN}.$target/${RESET}"
303
+ for subdir in rules skills workflows; do
304
+ if [[ -L "$target_dir/$subdir" ]]; then
305
+ local link_target
306
+ link_target="$(readlink "$target_dir/$subdir")"
307
+ echo -e " ${GREEN}[synced]${RESET} $subdir -> $link_target"
308
+ elif [[ -d "$target_dir/$subdir" ]]; then
309
+ echo -e " ${YELLOW}[local]${RESET} $subdir (not symlinked)"
310
+ else
311
+ echo -e " ${RED}[missing]${RESET} $subdir"
312
+ fi
313
+ done
314
+ else
315
+ echo -e "${RED}[not synced]${RESET} .$target/"
316
+ fi
317
+ done
318
+ }
319
+
320
+ cmd_index() {
321
+ ensure_agents_dir
322
+ generate_agents_md
323
+ info "Regenerated $AGENTS_MD"
324
+ }
325
+
326
+ cmd_clean() {
327
+ info "Removing synced symlinks..."
328
+
329
+ for target in "${ACTIVE_TARGETS[@]}"; do
330
+ local target_dir="$PROJECT_ROOT/.$target"
331
+ for subdir in rules skills workflows; do
332
+ if [[ -L "$target_dir/$subdir" ]]; then
333
+ rm "$target_dir/$subdir"
334
+ info "Removed: .$target/$subdir"
335
+ fi
336
+ done
337
+
338
+ # Remove target dir if empty
339
+ if [[ -d "$target_dir" ]] && [[ -z "$(ls -A "$target_dir" 2>/dev/null)" ]]; then
340
+ rmdir "$target_dir"
341
+ info "Removed empty directory: .$target/"
342
+ fi
343
+ done
344
+
345
+ # Remove CLAUDE.md symlink
346
+ if [[ -L "$PROJECT_ROOT/CLAUDE.md" ]]; then
347
+ rm "$PROJECT_ROOT/CLAUDE.md"
348
+ info "Removed: CLAUDE.md symlink"
349
+ fi
350
+
351
+ info "Clean complete."
352
+ }
353
+
354
+ # --------------------------------------------------------------------------
355
+ # Index generator
356
+ # --------------------------------------------------------------------------
357
+
358
+ generate_agents_md() {
359
+ local outfile="$PROJECT_ROOT/$AGENTS_MD"
360
+ local agents_dir="$PROJECT_ROOT/$AGENTS_DIR"
361
+
362
+ cat > "$outfile" <<'HEADER'
363
+
364
+ ---
365
+ trigger: always_on
366
+ ---
367
+
368
+ # AGENTS
369
+
370
+ > Auto-generated by [sync-agents](https://github.com/brickhouse-tech/sync-agents). Do not edit manually.
371
+ > Run `sync-agents index` to regenerate.
372
+
373
+ This file indexes all rules, skills, and workflows defined in `.agents/`.
374
+
375
+ HEADER
376
+
377
+ # Rules
378
+ echo "## Rules" >> "$outfile"
379
+ echo "" >> "$outfile"
380
+ if compgen -G "$agents_dir/rules/*.md" > /dev/null 2>&1; then
381
+ for f in "$agents_dir/rules/"*.md; do
382
+ local name
383
+ name="$(basename "$f" .md)"
384
+ echo "- [$name](.agents/rules/$name.md)" >> "$outfile"
385
+ done
386
+ else
387
+ echo "_No rules defined yet. Add one with \`sync-agents add rule <name>\`._" >> "$outfile"
388
+ fi
389
+ echo "" >> "$outfile"
390
+
391
+ # Skills
392
+ echo "## Skills" >> "$outfile"
393
+ echo "" >> "$outfile"
394
+ if compgen -G "$agents_dir/skills/*.md" > /dev/null 2>&1; then
395
+ for f in "$agents_dir/skills/"*.md; do
396
+ local name
397
+ name="$(basename "$f" .md)"
398
+ echo "- [$name](.agents/skills/$name.md)" >> "$outfile"
399
+ done
400
+ else
401
+ echo "_No skills defined yet. Add one with \`sync-agents add skill <name>\`._" >> "$outfile"
402
+ fi
403
+ echo "" >> "$outfile"
404
+
405
+ # Workflows
406
+ echo "## Workflows" >> "$outfile"
407
+ echo "" >> "$outfile"
408
+ if compgen -G "$agents_dir/workflows/*.md" > /dev/null 2>&1; then
409
+ for f in "$agents_dir/workflows/"*.md; do
410
+ local name
411
+ name="$(basename "$f" .md)"
412
+ echo "- [$name](.agents/workflows/$name.md)" >> "$outfile"
413
+ done
414
+ else
415
+ echo "_No workflows defined yet. Add one with \`sync-agents add workflow <name>\`._" >> "$outfile"
416
+ fi
417
+ echo "" >> "$outfile"
418
+
419
+ # State reference
420
+ echo "## State" >> "$outfile"
421
+ echo "" >> "$outfile"
422
+ echo "- [STATE.md](.agents/STATE.md)" >> "$outfile"
423
+ echo "" >> "$outfile"
424
+ }
425
+
426
+ # --------------------------------------------------------------------------
427
+ # Tree printer (lightweight, no dependency on `tree`)
428
+ # --------------------------------------------------------------------------
429
+
430
+ print_tree() {
431
+ local dir="$1"
432
+ local prefix="${2:-}"
433
+ local entries=()
434
+
435
+ # Collect entries
436
+ while IFS= read -r entry; do
437
+ entries+=("$entry")
438
+ done < <(ls -1A "$dir" 2>/dev/null | sort)
439
+
440
+ local count=${#entries[@]}
441
+ if [[ $count -eq 0 ]]; then
442
+ return 0
443
+ fi
444
+ local i=0
445
+
446
+ for entry in "${entries[@]}"; do
447
+ i=$((i + 1))
448
+ local connector="├── "
449
+ local child_prefix="│ "
450
+ if [[ $i -eq $count ]]; then
451
+ connector="└── "
452
+ child_prefix=" "
453
+ fi
454
+
455
+ if [[ -d "$dir/$entry" ]]; then
456
+ echo "${prefix}${connector}${entry}/"
457
+ print_tree "$dir/$entry" "${prefix}${child_prefix}"
458
+ elif [[ -L "$dir/$entry" ]]; then
459
+ local link_target
460
+ link_target="$(readlink "$dir/$entry")"
461
+ echo "${prefix}${connector}${entry} -> ${link_target}"
462
+ else
463
+ echo "${prefix}${connector}${entry}"
464
+ fi
465
+ done
466
+ }
467
+
468
+ # --------------------------------------------------------------------------
469
+ # Main
470
+ # --------------------------------------------------------------------------
471
+
472
+ main() {
473
+ local command=""
474
+ local custom_dir=""
475
+ local custom_targets=""
476
+ DRY_RUN="false"
477
+ FORCE="false"
478
+
479
+ # Parse arguments
480
+ while [[ $# -gt 0 ]]; do
481
+ case "$1" in
482
+ -h|--help)
483
+ usage
484
+ exit 0
485
+ ;;
486
+ -v|--version)
487
+ echo "sync-agents v${VERSION}"
488
+ exit 0
489
+ ;;
490
+ -d|--dir)
491
+ custom_dir="$2"
492
+ shift 2
493
+ ;;
494
+ --targets)
495
+ custom_targets="$2"
496
+ shift 2
497
+ ;;
498
+ --dry-run)
499
+ DRY_RUN="true"
500
+ shift
501
+ ;;
502
+ --force)
503
+ FORCE="true"
504
+ shift
505
+ ;;
506
+ -*)
507
+ error "Unknown option: $1"
508
+ usage
509
+ exit 1
510
+ ;;
511
+ *)
512
+ if [[ -z "$command" ]]; then
513
+ command="$1"
514
+ else
515
+ # Collect remaining args for subcommands
516
+ break
517
+ fi
518
+ shift
519
+ ;;
520
+ esac
521
+ done
522
+
523
+ # Resolve project root
524
+ if [[ -n "$custom_dir" ]]; then
525
+ PROJECT_ROOT="$(cd "$custom_dir" && pwd)"
526
+ else
527
+ PROJECT_ROOT="$(find_project_root)"
528
+ fi
529
+
530
+ # Resolve active targets
531
+ if [[ -n "$custom_targets" ]]; then
532
+ IFS=',' read -ra ACTIVE_TARGETS <<< "$custom_targets"
533
+ else
534
+ ACTIVE_TARGETS=("${TARGETS[@]}")
535
+ fi
536
+
537
+ # Dispatch command
538
+ case "${command:-}" in
539
+ init)
540
+ cmd_init
541
+ ;;
542
+ sync)
543
+ cmd_sync
544
+ ;;
545
+ status)
546
+ cmd_status
547
+ ;;
548
+ add)
549
+ cmd_add "$@"
550
+ ;;
551
+ index)
552
+ cmd_index
553
+ ;;
554
+ clean)
555
+ cmd_clean
556
+ ;;
557
+ "")
558
+ usage
559
+ exit 0
560
+ ;;
561
+ *)
562
+ error "Unknown command: $command"
563
+ usage
564
+ exit 1
565
+ ;;
566
+ esac
567
+ }
568
+
569
+ main "$@"