Created
June 4, 2026 17:46
-
-
Save iconifyit/9f41e2148ab6eb8c7e7d0d2fa4465683 to your computer and use it in GitHub Desktop.
Symlinks individual rules, skills, and workflows from master .agents to repo-level .agents
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| function _agentify_help() { | |
| cat <<EOF | |
| agentify | |
| Initializes and syncs a local project's .agents/ directory | |
| against a globally managed agents repository. | |
| FEATURES | |
| - Initializes .agents/ if it does not exist | |
| - Symlinks shared rules/skills/workflows from global agents repo | |
| - Ensures local STATE.md exists | |
| - Registers global AGENTS.md inheritance | |
| - Regenerates AGENTS.md index | |
| - Supports dry-run mode | |
| - Supports force mode for replacing existing local paths | |
| - Protects existing local non-symlink directories/files by default | |
| GLOBAL STRUCTURE | |
| ~/github/@agents/ | |
| ├── AGENTS.md | |
| └── .agents/ | |
| ├── rules/ | |
| ├── skills/ | |
| └── workflows/ | |
| LOCAL PROJECT STRUCTURE | |
| ./project/ | |
| └── .agents/ | |
| ├── STATE.md | |
| ├── rules -> symlink | |
| ├── skills -> symlink | |
| └── workflows -> symlink | |
| USAGE | |
| agentify | |
| Agentify current directory using default target: | |
| - claude | |
| agentify --targets claude cursor | |
| Configure multiple targets. | |
| agentify --dry-run | |
| Show what would happen without making changes. | |
| agentify --force | |
| Replace existing local rules/skills/workflows paths even if they are | |
| real directories or files. | |
| agentify --force --dry-run | |
| Preview destructive replacement without making changes. | |
| agentify --targets claude codex --dry-run | |
| Preview a multi-target configuration. | |
| agentify -h | |
| agentify --help | |
| Show help. | |
| SUPPORTED TARGETS | |
| - claude | |
| - windsurf | |
| - cursor | |
| - codex | |
| - copilot | |
| REQUIREMENTS | |
| - sync-agents installed and available on PATH | |
| - global agents repo exists at: | |
| ~/github/@agents | |
| NOTES | |
| - Shared resources are symlinked from the global repo. | |
| - STATE.md remains project-local. | |
| - Existing local non-symlink directories/files are NEVER replaced unless | |
| --force is explicitly passed. | |
| - This avoids accidental destruction of local project-specific agents. | |
| - This avoids nested links like: | |
| .agents/rules/rules | |
| EOF | |
| } | |
| function ensure_symlink() { | |
| local LOCAL_PATH="$1" | |
| local TARGET_PATH="$2" | |
| local DRY_RUN="$3" | |
| local FORCE="$4" | |
| if [[ ! -e "$LOCAL_PATH" ]]; then | |
| if [[ "$DRY_RUN" == true ]]; then | |
| echo "Dry run: would create symlink:" | |
| echo " $LOCAL_PATH -> $TARGET_PATH" | |
| else | |
| ln -s "$TARGET_PATH" "$LOCAL_PATH" | |
| fi | |
| return 0 | |
| fi | |
| if [[ -L "$LOCAL_PATH" ]]; then | |
| local CURRENT_TARGET | |
| CURRENT_TARGET="$(readlink "$LOCAL_PATH")" | |
| if [[ "$CURRENT_TARGET" == "$TARGET_PATH" ]]; then | |
| echo "Symlink already correct:" | |
| echo " $LOCAL_PATH" | |
| return 0 | |
| fi | |
| if [[ "$DRY_RUN" == true ]]; then | |
| echo "Dry run: would replace symlink:" | |
| echo " $LOCAL_PATH" | |
| echo " from: $CURRENT_TARGET" | |
| echo " to: $TARGET_PATH" | |
| else | |
| rm "$LOCAL_PATH" | |
| ln -s "$TARGET_PATH" "$LOCAL_PATH" | |
| fi | |
| return 0 | |
| fi | |
| if [[ "$FORCE" == true ]]; then | |
| if [[ "$DRY_RUN" == true ]]; then | |
| echo "Dry run: would remove existing non-symlink path:" | |
| echo " $LOCAL_PATH" | |
| echo "Dry run: would create symlink:" | |
| echo " $LOCAL_PATH -> $TARGET_PATH" | |
| else | |
| rm -rf "$LOCAL_PATH" | |
| ln -s "$TARGET_PATH" "$LOCAL_PATH" | |
| fi | |
| return 0 | |
| fi | |
| echo | |
| echo "Refusing to replace non-symlink path:" | |
| echo " $LOCAL_PATH" | |
| echo | |
| echo "This path already exists and is not a symlink." | |
| echo "Replacing it could destroy local agent content." | |
| echo | |
| echo "Use --force if replacing this path is intentional." | |
| return 1 | |
| } | |
| function agentify() { | |
| local AGENTS_GLOBAL_ROOT="$HOME/github/@agents" | |
| local AGENTS_GLOBAL="$AGENTS_GLOBAL_ROOT/.agents" | |
| local AGENTS_GLOBAL_FILE="$AGENTS_GLOBAL_ROOT/AGENTS.md" | |
| local PROJECT_DIR | |
| PROJECT_DIR="$(pwd)" | |
| local DRY_RUN=false | |
| local FORCE=false | |
| local VALID_TARGETS=( | |
| "claude" | |
| "windsurf" | |
| "cursor" | |
| "codex" | |
| "copilot" | |
| ) | |
| local TARGETS=("claude") | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --targets) | |
| shift | |
| TARGETS=() | |
| while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do | |
| TARGETS+=("$1") | |
| shift | |
| done | |
| ;; | |
| --dir) | |
| shift | |
| PROJECT_DIR="$(cd "$1" && pwd)" | |
| shift | |
| ;; | |
| --dry-run) | |
| DRY_RUN=true | |
| shift | |
| ;; | |
| --force) | |
| FORCE=true | |
| shift | |
| ;; | |
| -h|--help) | |
| _agentify_help | |
| return 0 | |
| ;; | |
| *) | |
| echo "Unknown argument: $1" | |
| echo | |
| echo "Run 'agentify --help' for usage." | |
| return 1 | |
| ;; | |
| esac | |
| done | |
| for target in "${TARGETS[@]}"; do | |
| local valid=false | |
| for allowed in "${VALID_TARGETS[@]}"; do | |
| if [[ "$target" == "$allowed" ]]; then | |
| valid=true | |
| break | |
| fi | |
| done | |
| if [[ "$valid" == false ]]; then | |
| echo "Invalid target: $target" | |
| echo | |
| echo "Valid targets:" | |
| printf ' - %s\n' "${VALID_TARGETS[@]}" | |
| return 1 | |
| fi | |
| done | |
| local TARGETS_ARG | |
| TARGETS_ARG="$(IFS=,; echo "${TARGETS[*]}")" | |
| local AGENTS_LOCAL="$PROJECT_DIR/.agents" | |
| echo | |
| echo "This will agentify the current directory:" | |
| echo " $PROJECT_DIR" | |
| echo | |
| echo "Mode:" | |
| if [[ "$DRY_RUN" == true ]]; then | |
| echo " dry-run" | |
| else | |
| echo " apply" | |
| fi | |
| echo | |
| echo "Force:" | |
| if [[ "$FORCE" == true ]]; then | |
| echo " enabled" | |
| else | |
| echo " disabled" | |
| fi | |
| echo | |
| echo "Targets:" | |
| printf ' - %s\n' "${TARGETS[@]}" | |
| echo | |
| echo "Global agents root:" | |
| echo " $AGENTS_GLOBAL_ROOT" | |
| echo | |
| echo "The following paths will be configured:" | |
| echo " $AGENTS_LOCAL/rules -> $AGENTS_GLOBAL/rules" | |
| echo " $AGENTS_LOCAL/skills -> $AGENTS_GLOBAL/skills" | |
| echo " $AGENTS_LOCAL/workflows -> $AGENTS_GLOBAL/workflows" | |
| echo | |
| echo "The following file will be ensured:" | |
| echo " $AGENTS_LOCAL/STATE.md" | |
| echo | |
| echo "The following inheritance link will be added:" | |
| echo " global -> $AGENTS_GLOBAL_FILE" | |
| echo | |
| if [[ "$DRY_RUN" != true ]]; then | |
| read -r -p "Continue? [y/N] " confirm | |
| case "$confirm" in | |
| y|Y|yes|YES) | |
| ;; | |
| *) | |
| echo "Aborted." | |
| return 0 | |
| ;; | |
| esac | |
| fi | |
| if ! command -v sync-agents >/dev/null 2>&1; then | |
| echo "Error: sync-agents command not found." | |
| return 1 | |
| fi | |
| if [[ ! -d "$AGENTS_LOCAL" ]]; then | |
| if ! command -v sync-agents >/dev/null 2>&1; then | |
| echo "Error: sync-agents command not found." | |
| return 1 | |
| fi | |
| if [[ "$DRY_RUN" == true ]]; then | |
| echo "Dry run: would initialize:" | |
| echo " $AGENTS_LOCAL" | |
| else | |
| sync-agents init --dir "$PROJECT_DIR" | |
| fi | |
| fi | |
| if [[ "$DRY_RUN" == true ]]; then | |
| echo "Dry run: would ensure:" | |
| echo " $AGENTS_LOCAL/STATE.md" | |
| else | |
| touch "$AGENTS_LOCAL/STATE.md" | |
| fi | |
| ensure_symlink \ | |
| "$AGENTS_LOCAL/rules" \ | |
| "$AGENTS_GLOBAL/rules" \ | |
| "$DRY_RUN" \ | |
| "$FORCE" || return 1 | |
| ensure_symlink \ | |
| "$AGENTS_LOCAL/skills" \ | |
| "$AGENTS_GLOBAL/skills" \ | |
| "$DRY_RUN" \ | |
| "$FORCE" || return 1 | |
| ensure_symlink \ | |
| "$AGENTS_LOCAL/workflows" \ | |
| "$AGENTS_GLOBAL/workflows" \ | |
| "$DRY_RUN" \ | |
| "$FORCE" || return 1 | |
| if [[ "$DRY_RUN" == true ]]; then | |
| sync-agents inherit global "$AGENTS_GLOBAL_FILE" --dry-run --dir "$PROJECT_DIR" || true | |
| sync-agents index --dry-run --dir "$PROJECT_DIR" | |
| sync-agents sync --targets "$TARGETS_ARG" --dry-run --dir "$PROJECT_DIR" | |
| else | |
| sync-agents inherit global "$AGENTS_GLOBAL_FILE" --dir "$PROJECT_DIR" || true | |
| sync-agents index --dir "$PROJECT_DIR" | |
| sync-agents sync --targets "$TARGETS_ARG" --dir "$PROJECT_DIR" | |
| fi | |
| echo | |
| if [[ "$DRY_RUN" == true ]]; then | |
| echo "Agentify dry run complete." | |
| else | |
| echo "Agentify complete." | |
| fi | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment