repo-nexus 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Repo Nexus Contributors
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 ADDED
@@ -0,0 +1,273 @@
1
+ # Repo Nexus (`rnex`)
2
+
3
+ [![CI](https://github.com/nu-nenoi/repo-nexus/actions/workflows/ci.yml/badge.svg)](https://github.com/nu-nenoi/repo-nexus/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/repo-nexus.svg)](https://www.npmjs.com/package/repo-nexus)
5
+ [![License: MIT](https://img.shields.io/github/license/nu-nenoi/repo-nexus)](LICENSE)
6
+ [![POSIX Compatible](https://img.shields.io/badge/POSIX-compatible-success)](#)
7
+ [![Platform](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux-lightgrey)](#)
8
+
9
+ A tooling-independent, zero-dependency workspace orchestrator for multiple repositories with shared, auto-synced AI context across projects using Unix symlinks.
10
+
11
+ - **No git submodules, subtrees, or nested git friction**
12
+ - **No IDE lock-in** (works across VS Code, Cursor, Antigravity, Claude Code, Zed, terminal agents)
13
+ - **AI provider-agnostic** (shares `AGENTS.md`, Copilot instructions, Cursor rules, and custom prompts)
14
+ - **Automatic AI context injection** on repository registration
15
+ - **Auto-detects existing AI configs** (`CLAUDE.md`, `.cursorrules`, `.windsurfrules`, Copilot instructions, etc.)
16
+ - **Context-aware**: Finds and uses `rnex.yaml` automatically from your current directory or parent tree
17
+ - **Zero dependencies** (pure POSIX shell CLI)
18
+
19
+ ---
20
+
21
+ ## How It Works: The Two Symlink Flows
22
+
23
+ ```
24
+ ┌─────────────────────────────────────────────────────────────┐
25
+ │ 1. SCOPE IN — Bring member repos INTO Repo Nexus │
26
+ │ │
27
+ │ my-workspace/ │
28
+ │ repos/ │
29
+ │ backend/ ──(symlink)──> ~/code/backend-api │
30
+ │ frontend/ ──(symlink)──> ~/code/web-app │
31
+ │ AGENTS.md (universal AI instructions) │
32
+ │ rnex.yaml (manifest: AI files + repos) │
33
+ │ │
34
+ │ Agent opens workspace → sees all repos in one place. │
35
+ │ Edits through symlinks modify the original files directly. │
36
+ └─────────────────────────────────────────────────────────────┘
37
+
38
+ ┌─────────────────────────────────────────────────────────────┐
39
+ │ 2. INJECT OUT — Auto-sync AI context INTO member repos │
40
+ │ │
41
+ │ ~/code/backend-api/ │
42
+ │ AGENTS.md ──(symlink)──> my-workspace/AGENTS.md │
43
+ │ src/ (real codebase) │
44
+ │ │
45
+ │ Open backend-api standalone → AI tools automatically see │
46
+ │ shared instructions as if they were local files. │
47
+ └─────────────────────────────────────────────────────────────┘
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Key Principles
53
+
54
+ 1. **Symlink Write-Through**:
55
+ Symlinks are transparent pointers. When an AI agent or developer edits `repos/backend/src/index.ts`, the OS resolves the link and writes directly to the source repository on disk.
56
+
57
+ 2. **Autonomous Git Repositories**:
58
+ Each member repository retains its own Git history, branches, and remotes. Git operations (commit, push, pull) are executed directly inside each member repo.
59
+
60
+ 3. **Single Source of Truth for AI Context**:
61
+ Edit `AGENTS.md` in Repo Nexus, and changes immediately reflect across all member repositories.
62
+
63
+ 4. **Dynamic Workspace Scope**:
64
+ Easily show or hide member repos from the active workspace without modifying disk contents.
65
+
66
+ ---
67
+
68
+ ## Installation
69
+
70
+ You can install `rnex` using either the native zero-dependency installer or via npm:
71
+
72
+ ### Option 1: Native Installer (Zero Dependencies)
73
+
74
+ Clone the repository and run the built-in installer:
75
+
76
+ ```bash
77
+ git clone https://github.com/nu-nenoi/repo-nexus.git
78
+ cd repo-nexus
79
+ ./rnex install
80
+ ```
81
+ *(Installs `rnex` and `repo-nexus` symlinks into `~/.local/bin`, or pass a custom directory like `./rnex install /usr/local/bin`)*
82
+
83
+ ### Option 2: Via npm
84
+
85
+ ```bash
86
+ npm install -g repo-nexus
87
+ ```
88
+ *(Installs both `repo-nexus` and `rnex` executable commands globally)*
89
+
90
+ ### Option 3: Shell Alias
91
+
92
+ Add to your `~/.zshrc` or `~/.bashrc`:
93
+ ```bash
94
+ alias rnex="/path/to/repo-nexus/rnex"
95
+ alias repo-nexus="/path/to/repo-nexus/rnex"
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Quick Start
101
+
102
+ ```bash
103
+ # 1. Initialize a new workspace
104
+ mkdir my-workspace && cd my-workspace
105
+ rnex init
106
+
107
+ # 2. Register repositories
108
+ rnex add backend ~/code/backend-api
109
+ rnex add frontend ../web-app # relative paths work too
110
+
111
+ # 3. Check workspace health
112
+ rnex status
113
+ ```
114
+
115
+ During `rnex init`, the CLI automatically scans for existing AI configuration files (like `CLAUDE.md`, `.cursorrules`, `.github/copilot-instructions.md`) and adds them to your config.
116
+
117
+ ---
118
+
119
+ ## Everyday Usage
120
+
121
+ ```bash
122
+ # Register a repository (links into scope & auto-injects AI context)
123
+ rnex add my-app ~/code/my-app
124
+
125
+ # Inspect workspace status & linked AI files
126
+ rnex status
127
+
128
+ # List all registered repositories
129
+ rnex list
130
+
131
+ # Temporarily hide a repo from active indexing/agent scope
132
+ rnex hide my-app
133
+
134
+ # Restore a hidden repo back to active scope
135
+ rnex show my-app
136
+
137
+ # Reconcile/repair all symlinks across all repos (idempotent)
138
+ rnex sync
139
+
140
+ # Unregister a repository (removes scope link & cleans up injected AI files)
141
+ rnex remove my-app
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Workspace Configuration (`rnex.yaml`)
147
+
148
+ The configuration file defines repo symlink directory, AI context files to sync, and registered repositories:
149
+
150
+ ```yaml
151
+ # Directory for repository symlinks (relative or absolute)
152
+ repos_dir: ./repos
153
+
154
+ # AI context files to automatically sync into every member repo
155
+ ai_files:
156
+ - AGENTS.md
157
+ - .github/copilot-instructions.md
158
+ # - .cursorrules
159
+ # - CLAUDE.md
160
+
161
+ # Member repositories
162
+ repos:
163
+ backend:
164
+ path: /Users/dev/code/backend-api
165
+ scope: visible
166
+ frontend:
167
+ path: ../web-app
168
+ scope: visible
169
+ analytics:
170
+ path: /Users/dev/code/analytics
171
+ scope: hidden
172
+ ```
173
+
174
+ > See [`docs/rnex.example.yaml`](docs/rnex.example.yaml) for a comprehensive example with all options and supported AI tool configs.
175
+
176
+ ---
177
+
178
+ ## CLI Command Reference
179
+
180
+ ### Global Options
181
+ | Option | Description |
182
+ |:---|:---|
183
+ | `-c, --config <file>` | Explicit path to `rnex.yaml` (executes in that workspace directory) |
184
+ | `-h, --help` | Display command help and usage instructions |
185
+ | `-v, --version` | Display version |
186
+
187
+ ### Commands
188
+ | Command | Description |
189
+ |:---|:---|
190
+ | `rnex init [dir]` | Initialize a new workspace in current (or target) directory |
191
+ | `rnex install [dir]` | Install `rnex` & `repo-nexus` globally into `~/.local/bin` (or custom dir) |
192
+ | `rnex add <name> <path>` | Register repo, create scope symlink, and auto-inject AI context |
193
+ | `rnex remove <name>` | Unregister repo, unlink from scope, and clean up injected AI files |
194
+ | `rnex list` | List all registered repos and visibility scopes |
195
+ | `rnex status` | Display status of AI context files, active member repos, and paths |
196
+ | `rnex show <name>` | Make a hidden repo visible in workspace |
197
+ | `rnex hide <name>` | Hide a repo from active workspace indexing |
198
+ | `rnex sync` | Reconcile all scope symlinks and AI context files from config |
199
+
200
+ ---
201
+
202
+ ## Operating on External Workspaces via `--config`
203
+
204
+ Run `rnex` commands targeting any workspace without changing directories:
205
+
206
+ ```bash
207
+ # Inspect status of a workspace stored elsewhere
208
+ rnex -c /path/to/my-workspace/rnex.yaml status
209
+
210
+ # Add a repository to an external workspace
211
+ rnex --config /path/to/my-workspace/rnex.yaml add api-service ~/code/api
212
+ ```
213
+
214
+ Any command executed with an explicit config file operates within the directory where that config lives.
215
+
216
+ ---
217
+
218
+ ## Supported AI Configuration Files
219
+
220
+ `rnex init` auto-detects these files and includes them in your workspace config:
221
+
222
+ | File / Directory | AI Tool |
223
+ |:---|:---|
224
+ | `AGENTS.md` | Universal AI agent instructions |
225
+ | `CLAUDE.md` | Claude Code |
226
+ | `.cursorrules` | Cursor |
227
+ | `.cursor/rules/` | Cursor (directory) |
228
+ | `.windsurfrules` | Windsurf / Codeium |
229
+ | `.windsurf/rules/` | Windsurf (directory) |
230
+ | `.github/copilot-instructions.md` | GitHub Copilot |
231
+ | `.aider.conf.yml` | Aider |
232
+ | `CONVENTIONS.md` | Coding conventions |
233
+ | `.clinerules` | Cline / Roo Code |
234
+ | `CODEX.md` | Codex |
235
+ | `.continue/` | Continue.dev |
236
+ | `SKILL.md` | Skills (emerging standard) |
237
+
238
+ ---
239
+
240
+ ## Project Structure
241
+
242
+ ```
243
+ repo-nexus/
244
+ ├── rnex.yaml # Workspace configuration
245
+ ├── rnex # CLI executable (POSIX shell)
246
+ ├── AGENTS.md # Universal AI coding guidelines
247
+ ├── docs/
248
+ │ ├── AGENTS.sample.md # Template for AGENTS.md
249
+ │ └── rnex.example.yaml # Full config reference with examples
250
+ ├── .github/
251
+ │ ├── workflows/ci.yml # GitHub Actions CI workflow
252
+ │ └── ISSUE_TEMPLATE/ # Bug report and feature request templates
253
+ ├── tests/
254
+ │ └── test_cli.sh # Automated CLI test suite (11 tests)
255
+ ├── toolkit/ # Shared prompts, scripts, templates
256
+ ├── package.json # npm package manifest
257
+ ├── LICENSE # MIT License
258
+ └── README.md
259
+ ```
260
+
261
+ ---
262
+
263
+ ## Running Tests
264
+
265
+ ```bash
266
+ ./tests/test_cli.sh
267
+ ```
268
+
269
+ ---
270
+
271
+ ## License
272
+
273
+ [MIT](LICENSE)
@@ -0,0 +1,10 @@
1
+ # Multi-Repo AI Workspace Context
2
+
3
+ This workspace operates as a **Repo Nexus** linking multiple independent repositories via symlinks.
4
+
5
+ ## Rules for AI Coding Assistants
6
+
7
+ 1. **Symlink Write-Through**: Files edited under `repos/<name>/` directly modify the target repository.
8
+ 2. **Repo Boundaries**: Each repository is isolated. Do not introduce cross-repo imports unless explicitly architected.
9
+ 3. **Shared Context**: This file is symlinked across member repositories. Keep instructions universal. Use local instructions for repo-specific rules.
10
+ 4. **Git Operations**: Commit and push changes directly within the respective member repository root.
@@ -0,0 +1,90 @@
1
+ # ==============================================================================
2
+ # rnex.yaml — Repo Nexus Workspace Configuration (Full Example)
3
+ # ==============================================================================
4
+ # This file is the central manifest for a Repo Nexus workspace.
5
+ # Place it in your workspace root directory and run `rnex sync` to apply.
6
+ #
7
+ # For more information: https://github.com/nu-nenoi/repo-nexus
8
+
9
+ # ------------------------------------------------------------------------------
10
+ # repos_dir — Directory for repository symlinks
11
+ # ------------------------------------------------------------------------------
12
+ # Relative paths are resolved relative to this config file's location.
13
+ # Default: ./repos
14
+ #
15
+ # Examples:
16
+ # repos_dir: ./repos # relative (recommended)
17
+ # repos_dir: ./workspace/repos # nested relative path
18
+ # repos_dir: /opt/dev/repos # absolute path
19
+
20
+ repos_dir: ./repos
21
+
22
+ # ------------------------------------------------------------------------------
23
+ # ai_files — AI context files to auto-sync into member repositories
24
+ # ------------------------------------------------------------------------------
25
+ # Each file listed here is symlinked from the workspace root into every
26
+ # registered member repository. This keeps AI instructions consistent
27
+ # across all your projects.
28
+ #
29
+ # Paths are relative to the workspace root (where this config file lives).
30
+ # Both files and directories are supported.
31
+ #
32
+ # Well-known AI configuration files by tool:
33
+ # AGENTS.md — Universal AI agent instructions
34
+ # CLAUDE.md — Claude Code project instructions
35
+ # .cursorrules — Cursor editor rules
36
+ # .cursor/rules/ — Cursor rules directory
37
+ # .windsurfrules — Windsurf / Codeium rules
38
+ # .windsurf/rules/ — Windsurf rules directory
39
+ # .github/copilot-instructions.md — GitHub Copilot custom instructions
40
+ # .aider.conf.yml — Aider configuration
41
+ # CONVENTIONS.md — Coding conventions (Aider / general)
42
+ # .clinerules — Cline / Roo Code rules
43
+ # CODEX.md — Codex project instructions
44
+ # .continue/ — Continue.dev configuration directory
45
+ # SKILL.md — Skills definition (emerging standard)
46
+
47
+ ai_files:
48
+ - AGENTS.md
49
+ - .github/copilot-instructions.md
50
+ # - .cursorrules
51
+ # - CLAUDE.md
52
+ # - .windsurfrules
53
+ # - CONVENTIONS.md
54
+ # - toolkit/prompts/review-checklist.md
55
+
56
+ # ------------------------------------------------------------------------------
57
+ # repos — Member repositories managed by Repo Nexus
58
+ # ------------------------------------------------------------------------------
59
+ # Each repo has:
60
+ # path: Absolute or relative path to the repository on disk.
61
+ # Relative paths are resolved relative to this config file.
62
+ # scope: "visible" (default) creates a symlink in repos_dir.
63
+ # "hidden" removes the symlink but keeps the registration.
64
+ #
65
+ # Examples with absolute paths:
66
+ # backend-api:
67
+ # path: /Users/dev/projects/backend-api
68
+ # scope: visible
69
+ #
70
+ # Examples with relative paths:
71
+ # frontend:
72
+ # path: ../frontend-app
73
+ # scope: visible
74
+ #
75
+ # shared-lib:
76
+ # path: ../../libs/shared
77
+ # scope: hidden
78
+
79
+ repos:
80
+ # backend-api:
81
+ # path: /Users/dev/projects/backend-api
82
+ # scope: visible
83
+ #
84
+ # frontend-app:
85
+ # path: ../frontend-app
86
+ # scope: visible
87
+ #
88
+ # analytics:
89
+ # path: /Users/dev/projects/analytics-service
90
+ # scope: hidden
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "repo-nexus",
3
+ "version": "0.1.0",
4
+ "description": "Tooling-independent multi-repo workspace manager with auto-synced AI context via symlinks",
5
+ "bin": {
6
+ "repo-nexus": "./rnex",
7
+ "rnex": "./rnex"
8
+ },
9
+ "files": [
10
+ "rnex",
11
+ "README.md",
12
+ "LICENSE",
13
+ "docs/"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/nu-nenoi/repo-nexus.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/nu-nenoi/repo-nexus/issues"
21
+ },
22
+ "homepage": "https://github.com/nu-nenoi/repo-nexus#readme",
23
+ "keywords": [
24
+ "repo",
25
+ "repository",
26
+ "workspace",
27
+ "multi-repo",
28
+ "monorepo",
29
+ "symlink",
30
+ "ai-context",
31
+ "cli",
32
+ "agents",
33
+ "copilot",
34
+ "cursor",
35
+ "developer-tools"
36
+ ],
37
+ "license": "MIT",
38
+ "author": "Anton Shramkov",
39
+ "engines": {
40
+ "node": ">=14"
41
+ },
42
+ "os": [
43
+ "darwin",
44
+ "linux"
45
+ ]
46
+ }
package/rnex ADDED
@@ -0,0 +1,690 @@
1
+ #!/bin/sh
2
+ # ============================================================================
3
+ # rnex — Repo Nexus CLI
4
+ # Tooling-independent multi-repo workspace manager with auto-synced AI context.
5
+ # Zero dependencies. POSIX-compatible.
6
+ # ============================================================================
7
+ set -e
8
+
9
+ RNEX_VERSION="0.1.0"
10
+ RNEX_CONFIG_FILE="rnex.yaml"
11
+
12
+ # ---- Terminal Colours (auto-detected) -------------------------------------
13
+
14
+ if [ -t 1 ]; then
15
+ _R='\033[0;31m' _G='\033[0;32m' _Y='\033[1;33m' _B='\033[0;34m' _C='\033[0;36m'
16
+ _BOLD='\033[1m' _DIM='\033[2m' _NC='\033[0m'
17
+ else
18
+ _R='' _G='' _Y='' _B='' _C='' _BOLD='' _DIM='' _NC=''
19
+ fi
20
+
21
+ # ---- Logging ---------------------------------------------------------------
22
+
23
+ log_ok() { printf "${_G}[✓]${_NC} %s\n" "$1"; }
24
+ log_warn() { printf "${_Y}[!]${_NC} %s\n" "$1"; }
25
+ log_err() { printf "${_R}[✗]${_NC} %s\n" "$1" >&2; }
26
+ log_info() { printf "${_C}[i]${_NC} %s\n" "$1"; }
27
+ log_dim() { printf "${_DIM} %s${_NC}\n" "$1"; }
28
+ die() { log_err "$1"; exit 1; }
29
+
30
+ # ---- Path Utilities --------------------------------------------------------
31
+
32
+ resolve_path() {
33
+ _input="$1"
34
+ case "$_input" in
35
+ \~/*) _input="$HOME/${_input#\~/}" ;;
36
+ \~) _input="$HOME" ;;
37
+ esac
38
+ case "$_input" in
39
+ /*)
40
+ printf '%s' "$_input"
41
+ ;;
42
+ *)
43
+ if [ -d "$_input" ]; then
44
+ printf '%s' "$(cd "$_input" 2>/dev/null && pwd -P)"
45
+ else
46
+ _parent="$(cd "$(dirname "$_input")" 2>/dev/null && pwd -P)"
47
+ _base="$(basename "$_input")"
48
+ if [ "$_parent" = "/" ]; then
49
+ printf '/%s' "$_base"
50
+ else
51
+ printf '%s/%s' "$_parent" "$_base"
52
+ fi
53
+ fi
54
+ ;;
55
+ esac
56
+ }
57
+
58
+ # Resolve location of this script (follows symlinks)
59
+ resolve_script_path() {
60
+ _src="$1"
61
+ while [ -L "$_src" ]; do
62
+ _dir="$(cd -P "$(dirname "$_src")" && pwd)"
63
+ _src="$(readlink "$_src")"
64
+ case "$_src" in
65
+ /*) ;;
66
+ *) _src="$_dir/$_src" ;;
67
+ esac
68
+ done
69
+ _dir="$(cd -P "$(dirname "$_src")" && pwd)"
70
+ printf '%s/%s' "$_dir" "$(basename "$_src")"
71
+ }
72
+
73
+ SCRIPT_LOCATION="$(resolve_script_path "$0")"
74
+
75
+ # ---- Workspace Detection (Context-Aware) ----------------------------------
76
+
77
+ # Search for rnex.yaml in current directory or parent directories
78
+ find_workspace_dir() {
79
+ _curr="$(pwd -P 2>/dev/null || pwd)"
80
+ while [ -n "$_curr" ] && [ "$_curr" != "/" ]; do
81
+ if [ -f "$_curr/$RNEX_CONFIG_FILE" ]; then
82
+ printf '%s' "$_curr"
83
+ return 0
84
+ fi
85
+ _curr="$(dirname "$_curr")"
86
+ done
87
+ if [ -f "/$RNEX_CONFIG_FILE" ]; then
88
+ printf '/'
89
+ return 0
90
+ fi
91
+ return 1
92
+ }
93
+
94
+ # Locate workspace root for active commands
95
+ require_workspace() {
96
+ if [ -n "$CUSTOM_CONFIG" ]; then
97
+ _cfg_path="$(resolve_path "$CUSTOM_CONFIG")"
98
+ if [ ! -f "$_cfg_path" ]; then
99
+ die "Specified config file does not exist: $_cfg_path"
100
+ fi
101
+ NEXUS_DIR="$(cd -P "$(dirname "$_cfg_path")" 2>/dev/null && pwd -P)"
102
+ WORKSPACE_YAML="$_cfg_path"
103
+ _load_repos_dir
104
+ log_info "Using workspace at ${_BOLD}$NEXUS_DIR${_NC} ${_DIM}(via config: $WORKSPACE_YAML)${_NC}"
105
+ return 0
106
+ fi
107
+
108
+ _found_dir="$(find_workspace_dir || true)"
109
+ if [ -z "$_found_dir" ]; then
110
+ log_err "No '$RNEX_CONFIG_FILE' found in current directory ($PWD) or parent directories."
111
+ log_dim "To initialize a new Repo Nexus workspace here, run:"
112
+ log_dim " rnex init"
113
+ log_dim "Or specify a workspace config file directly:"
114
+ log_dim " rnex --config /path/to/$RNEX_CONFIG_FILE <command>"
115
+ log_dim "Or change directory to an existing workspace:"
116
+ log_dim " cd /path/to/workspace"
117
+ exit 1
118
+ fi
119
+
120
+ NEXUS_DIR="$_found_dir"
121
+ WORKSPACE_YAML="$NEXUS_DIR/$RNEX_CONFIG_FILE"
122
+ _load_repos_dir
123
+
124
+ log_info "Using workspace at ${_BOLD}$NEXUS_DIR${_NC} ${_DIM}($RNEX_CONFIG_FILE found)${_NC}"
125
+ }
126
+
127
+ # Parse repos_dir from config, default to ./repos
128
+ _load_repos_dir() {
129
+ _rd=""
130
+ if [ -f "$WORKSPACE_YAML" ]; then
131
+ _rd="$(awk '/^repos_dir:/ { sub(/^repos_dir:[ ]*/, ""); sub(/[ ]*#.*/, ""); print }' "$WORKSPACE_YAML")"
132
+ fi
133
+ _rd="${_rd:-./repos}"
134
+ case "$_rd" in
135
+ /*) REPOS_DIR="$_rd" ;;
136
+ *) REPOS_DIR="$NEXUS_DIR/$_rd" ;;
137
+ esac
138
+ }
139
+
140
+ # ---- YAML Parser / Manipulator (for rnex.yaml) ----------------------------
141
+
142
+ # Parse list of ai_files from rnex.yaml
143
+ yaml_list_ai_files() {
144
+ [ -f "$WORKSPACE_YAML" ] || return 0
145
+ cat "$WORKSPACE_YAML" | awk '
146
+ /^ai_files:/ { a=1; next }
147
+ a && /^[^ #]/ { exit }
148
+ a && /^[ ]*-[ ]*/ {
149
+ sub(/^[ ]*-[ ]*/, "");
150
+ sub(/[ ]*#.*/, "");
151
+ if (length($0) > 0) print $0
152
+ }
153
+ '
154
+ }
155
+
156
+ # Parse list of repos from rnex.yaml
157
+ yaml_list_repos() {
158
+ [ -f "$WORKSPACE_YAML" ] || return 0
159
+ cat "$WORKSPACE_YAML" | awk '
160
+ /^repos:/ { r=1; next }
161
+ r && /^[^ #]/ { exit }
162
+ r && /^ [a-zA-Z0-9_.-]+:[ ]*$/ {
163
+ sub(/^ /, ""); sub(/:.*/, ""); print
164
+ }
165
+ '
166
+ }
167
+
168
+ # Get a field value for a repo
169
+ yaml_get() {
170
+ _repo="$1"; _field="$2"
171
+ [ -f "$WORKSPACE_YAML" ] || return 0
172
+ cat "$WORKSPACE_YAML" | awk -v repo="$_repo" -v field="$_field" '
173
+ /^repos:/ { r=1; next }
174
+ r && /^[^ #]/ { exit }
175
+ r && match($0, "^ " repo ":[ ]*$") { t=1; next }
176
+ r && t && /^ [^ #]/ { exit }
177
+ r && t && match($0, "^ " field ":") {
178
+ sub(/^ [a-zA-Z0-9_.-]+:[ ]*/, ""); print
179
+ }
180
+ '
181
+ }
182
+
183
+ yaml_has_repo() {
184
+ yaml_list_repos | grep -qx "$1" 2>/dev/null
185
+ }
186
+
187
+ yaml_add_repo() {
188
+ _name="$1"; _path="$2"
189
+ if ! grep -q '^repos:' "$WORKSPACE_YAML"; then
190
+ printf '\nrepos:\n' >> "$WORKSPACE_YAML"
191
+ fi
192
+ printf ' %s:\n path: %s\n scope: visible\n' \
193
+ "$_name" "$_path" >> "$WORKSPACE_YAML"
194
+ }
195
+
196
+ yaml_remove_repo() {
197
+ _name="$1"
198
+ _tmp="$(mktemp)"
199
+ awk -v name="$_name" '
200
+ /^repos:/ { r=1; print; next }
201
+ r && /^[^ #]/ { r=0 }
202
+ r && match($0, "^ " name ":[ ]*$") { skip=1; next }
203
+ r && skip && /^ [^ #]/ { skip=0 }
204
+ skip && /^ / { next }
205
+ { print }
206
+ ' "$WORKSPACE_YAML" > "$_tmp" && mv "$_tmp" "$WORKSPACE_YAML"
207
+ }
208
+
209
+ yaml_set() {
210
+ _repo="$1"; _field="$2"; _value="$3"
211
+ _tmp="$(mktemp)"
212
+ awk -v repo="$_repo" -v field="$_field" -v value="$_value" '
213
+ /^repos:/ { r=1; print; next }
214
+ r && /^[^ #]/ { r=0 }
215
+ r && match($0, "^ " repo ":[ ]*$") { t=1; print; next }
216
+ r && t && /^ [^ #]/ { t=0 }
217
+ r && t && match($0, "^ " field ":") {
218
+ printf " %s: %s\n", field, value; next
219
+ }
220
+ { print }
221
+ ' "$WORKSPACE_YAML" > "$_tmp" && mv "$_tmp" "$WORKSPACE_YAML"
222
+ }
223
+
224
+ # ---- Symlink Operations ---------------------------------------------------
225
+
226
+ ensure_repos_dir() { mkdir -p "$REPOS_DIR"; }
227
+
228
+ create_scope_link() {
229
+ _name="$1"
230
+ _path="$(yaml_get "$_name" "path")"
231
+ [ -n "$_path" ] || die "No path recorded for repo '$_name'"
232
+ [ -d "$_path" ] || { log_warn "Path does not exist: $_path"; return 1; }
233
+ rm -f "$REPOS_DIR/$_name"
234
+ ln -s "$_path" "$REPOS_DIR/$_name"
235
+ log_ok "Linked scope: repos/$_name → $_path"
236
+ }
237
+
238
+ remove_scope_link() {
239
+ _name="$1"
240
+ if [ -L "$REPOS_DIR/$_name" ]; then
241
+ rm -f "$REPOS_DIR/$_name"
242
+ log_ok "Unlinked scope: repos/$_name"
243
+ fi
244
+ }
245
+
246
+ inject_ai_files() {
247
+ _name="$1"
248
+ _repo_path="$(yaml_get "$_name" "path")"
249
+ [ -n "$_repo_path" ] || die "No path recorded for repo '$_name'"
250
+ [ -d "$_repo_path" ] || die "Repo path does not exist: $_repo_path"
251
+
252
+ yaml_list_ai_files | while read -r _item; do
253
+ [ -n "$_item" ] || continue
254
+
255
+ _source="$NEXUS_DIR/$_item"
256
+ _target="$_repo_path/$_item"
257
+
258
+ if [ ! -e "$_source" ]; then
259
+ log_warn "AI file not found in nexus root, skipping: $_item"
260
+ continue
261
+ fi
262
+
263
+ if [ -e "$_target" ] && [ ! -L "$_target" ]; then
264
+ log_warn "Existing non-symlink file found, skipping: $_target"
265
+ continue
266
+ fi
267
+
268
+ _parent="$(dirname "$_target")"
269
+ [ -d "$_parent" ] || mkdir -p "$_parent"
270
+
271
+ rm -f "$_target"
272
+ ln -s "$_source" "$_target"
273
+ log_ok "Injected AI context: $_item → $(basename "$_repo_path")/$_item"
274
+ done
275
+
276
+ _gitignore_add "$_repo_path"
277
+ }
278
+
279
+ eject_ai_files() {
280
+ _name="$1"
281
+ _repo_path="$(yaml_get "$_name" "path")"
282
+ [ -n "$_repo_path" ] || die "No path recorded for repo '$_name'"
283
+
284
+ yaml_list_ai_files | while read -r _item; do
285
+ [ -n "$_item" ] || continue
286
+ _target="$_repo_path/$_item"
287
+ if [ -L "$_target" ]; then
288
+ rm -f "$_target"
289
+ log_ok "Ejected AI context: $_item from $(basename "$_repo_path")"
290
+ fi
291
+ done
292
+ }
293
+
294
+ _gitignore_add() {
295
+ _repo_path="$1"
296
+ _gi="$_repo_path/.gitignore"
297
+ _header_written=0
298
+
299
+ yaml_list_ai_files | while read -r _item; do
300
+ [ -n "$_item" ] || continue
301
+ if [ -f "$_gi" ] && grep -qxF "$_item" "$_gi" 2>/dev/null; then
302
+ continue
303
+ fi
304
+ if [ "$_header_written" -eq 0 ]; then
305
+ if [ ! -f "$_gi" ] || ! grep -q '# repo-nexus ai context' "$_gi" 2>/dev/null; then
306
+ printf '\n# repo-nexus ai context (injected symlinks)\n' >> "$_gi"
307
+ fi
308
+ _header_written=1
309
+ fi
310
+ printf '%s\n' "$_item" >> "$_gi"
311
+ log_dim "Added '$_item' to .gitignore in $(basename "$_repo_path")"
312
+ done
313
+ }
314
+
315
+ # ============================================================================
316
+ # Commands
317
+ # ============================================================================
318
+
319
+ # ---- Known AI config files for auto-detection ------------------------------
320
+ # Each entry: <file-or-dir-path>|<description>
321
+ _AI_KNOWN_FILES="
322
+ AGENTS.md|Universal AI agent instructions
323
+ CLAUDE.md|Claude Code instructions
324
+ .cursorrules|Cursor rules
325
+ .cursor/rules|Cursor rules directory
326
+ .windsurfrules|Windsurf rules
327
+ .windsurf/rules|Windsurf rules directory
328
+ .github/copilot-instructions.md|GitHub Copilot instructions
329
+ .aider.conf.yml|Aider configuration
330
+ CONVENTIONS.md|Coding conventions (Aider / general)
331
+ .clinerules|Cline / Roo Code rules
332
+ CODEX.md|Codex instructions
333
+ .continue|Continue.dev configuration
334
+ SKILL.md|Skills definition (emerging standard)
335
+ "
336
+
337
+ cmd_init() {
338
+ _target_dir="${1:-$PWD}"
339
+ _target_dir="$(resolve_path "$_target_dir")"
340
+ mkdir -p "$_target_dir"
341
+
342
+ _target_yaml="$_target_dir/$RNEX_CONFIG_FILE"
343
+
344
+ if [ -f "$_target_yaml" ]; then
345
+ log_warn "Workspace already initialized at $_target_dir ($RNEX_CONFIG_FILE exists)"
346
+ log_dim "To reconcile symlinks and AI files, run:"
347
+ log_dim " rnex sync"
348
+ return 0
349
+ fi
350
+
351
+ # ---- Create AGENTS.md from sample if not present -------------------------
352
+ # (done before scanning so it gets auto-detected)
353
+ _target_agents="$_target_dir/AGENTS.md"
354
+ if [ ! -f "$_target_agents" ]; then
355
+ _script_dir="$(dirname "$SCRIPT_LOCATION")"
356
+ _sample="$_script_dir/docs/AGENTS.sample.md"
357
+ if [ -f "$_sample" ]; then
358
+ cp "$_sample" "$_target_agents"
359
+ log_ok "Created AGENTS.md from sample template"
360
+ else
361
+ cat <<'AGENTS_EOF' > "$_target_agents"
362
+ # Multi-Repo AI Workspace Context
363
+
364
+ This workspace operates as a **Repo Nexus** linking multiple independent repositories via symlinks.
365
+
366
+ ## Rules for AI Coding Assistants
367
+
368
+ 1. **Symlink Write-Through**: Files edited under `repos/<name>/` directly modify the target repository.
369
+ 2. **Repo Boundaries**: Each repository is isolated. Do not introduce cross-repo imports unless explicitly architected.
370
+ 3. **Shared Context**: This file is symlinked across member repositories. Keep instructions universal. Use local instructions for repo-specific rules.
371
+ 4. **Git Operations**: Commit and push changes directly within the respective member repository root.
372
+ AGENTS_EOF
373
+ log_ok "Created AGENTS.md with default template"
374
+ fi
375
+ else
376
+ log_info "AGENTS.md already exists, skipping creation"
377
+ fi
378
+
379
+ # ---- Auto-detect existing AI config files --------------------------------
380
+ printf "\n${_BOLD}Scanning for existing AI configuration files...${_NC}\n"
381
+
382
+ # Display detected files
383
+ echo "$_AI_KNOWN_FILES" | while IFS='|' read -r _pattern _desc; do
384
+ [ -n "$_pattern" ] || continue
385
+ if [ -e "$_target_dir/$_pattern" ]; then
386
+ printf " ${_G}✓${_NC} Found: ${_BOLD}%s${_NC} ${_DIM}(%s)${_NC}\n" "$_pattern" "$_desc"
387
+ fi
388
+ done
389
+
390
+ # Build ai_files list from detection
391
+ _ai_yaml_lines=""
392
+ _any_detected=0
393
+ for _pattern in $(echo "$_AI_KNOWN_FILES" | awk -F'|' '{ gsub(/^[ ]+/, "", $1); if (length($1) > 0) print $1 }'); do
394
+ if [ -e "$_target_dir/$_pattern" ]; then
395
+ _ai_yaml_lines="${_ai_yaml_lines} - ${_pattern}
396
+ "
397
+ _any_detected=1
398
+ fi
399
+ done
400
+
401
+ if [ "$_any_detected" -eq 0 ]; then
402
+ log_info "No existing AI config files detected."
403
+ _ai_yaml_lines=" - AGENTS.md
404
+ "
405
+ fi
406
+
407
+ # ---- Generate config file ------------------------------------------------
408
+ cat > "$_target_yaml" <<YAML_EOF
409
+ # Repo Nexus workspace configuration
410
+ # See docs/rnex.example.yaml for a comprehensive example with all options.
411
+
412
+ # Directory for repository symlinks (relative to this file or absolute)
413
+ repos_dir: ./repos
414
+
415
+ # AI context files to automatically sync into member repositories.
416
+ # These files are symlinked from this workspace root into each registered repo.
417
+ ai_files:
418
+ ${_ai_yaml_lines}
419
+ # Member repositories managed by Repo Nexus
420
+ repos:
421
+ YAML_EOF
422
+
423
+ # ---- Create repos directory -----------------------------------------------
424
+ _repos_dir="$_target_dir/repos"
425
+ mkdir -p "$_repos_dir"
426
+
427
+ printf '\n'
428
+ log_ok "Initialized Repo Nexus workspace at ${_BOLD}$_target_dir${_NC}"
429
+ log_dim "Created $RNEX_CONFIG_FILE and repos/"
430
+ printf '\n'
431
+ log_info "Next steps:"
432
+ log_dim "1. Edit $RNEX_CONFIG_FILE to configure your AI context files"
433
+ log_dim "2. Add repositories with: rnex add <name> <path>"
434
+ log_dim "3. See docs/rnex.example.yaml for a full configuration reference"
435
+ }
436
+
437
+ cmd_add() {
438
+ require_workspace
439
+ _name="$1"; _path="$2"
440
+ [ -n "$_name" ] || die "Usage: rnex add <name> <path>"
441
+ [ -n "$_path" ] || die "Usage: rnex add <name> <path>"
442
+
443
+ yaml_has_repo "$_name" && die "Repo '$_name' is already registered in $RNEX_CONFIG_FILE"
444
+
445
+ _path="$(resolve_path "$_path")"
446
+ [ -d "$_path" ] || die "Directory does not exist: $_path"
447
+
448
+ ensure_repos_dir
449
+ yaml_add_repo "$_name" "$_path"
450
+ create_scope_link "$_name"
451
+ inject_ai_files "$_name"
452
+ printf '\n'
453
+ log_ok "Registered and synchronized repo '${_name}'"
454
+ }
455
+
456
+ cmd_remove() {
457
+ require_workspace
458
+ _name="$1"
459
+ [ -n "$_name" ] || die "Usage: rnex remove <name>"
460
+ yaml_has_repo "$_name" || die "Repo '$_name' is not registered"
461
+
462
+ eject_ai_files "$_name"
463
+ remove_scope_link "$_name"
464
+ yaml_remove_repo "$_name"
465
+ log_ok "Unregistered repo '$_name' from $RNEX_CONFIG_FILE"
466
+ }
467
+
468
+ cmd_show() {
469
+ require_workspace
470
+ _name="$1"
471
+ [ -n "$_name" ] || die "Usage: rnex show <name>"
472
+ yaml_has_repo "$_name" || die "Repo '$_name' is not registered"
473
+
474
+ ensure_repos_dir
475
+ create_scope_link "$_name"
476
+ yaml_set "$_name" "scope" "visible"
477
+ }
478
+
479
+ cmd_hide() {
480
+ require_workspace
481
+ _name="$1"
482
+ [ -n "$_name" ] || die "Usage: rnex hide <name>"
483
+ yaml_has_repo "$_name" || die "Repo '$_name' is not registered"
484
+
485
+ remove_scope_link "$_name"
486
+ yaml_set "$_name" "scope" "hidden"
487
+ }
488
+
489
+ cmd_list() {
490
+ require_workspace
491
+ printf "${_B}%-25s %-12s %s${_NC}\n" "NAME" "SCOPE" "PATH"
492
+ printf '%-25s %-12s %s\n' "----" "-----" "----"
493
+ yaml_list_repos | while read -r _name; do
494
+ _scope="$(yaml_get "$_name" "scope")"
495
+ _path="$(yaml_get "$_name" "path")"
496
+ printf '%-25s %-12s %s\n' \
497
+ "$_name" "${_scope:-visible}" "$_path"
498
+ done
499
+ }
500
+
501
+ cmd_status() {
502
+ require_workspace
503
+ ensure_repos_dir
504
+
505
+ printf "\n${_B}Repo Nexus Workspace Status${_NC}\n"
506
+ printf '=%.0s' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
507
+ printf '\n\n'
508
+
509
+ printf "${_BOLD}Configured AI Context Files ($RNEX_CONFIG_FILE):${_NC}\n"
510
+ yaml_list_ai_files | while read -r _item; do
511
+ if [ -e "$NEXUS_DIR/$_item" ]; then
512
+ printf " ${_G}✓${_NC} %s\n" "$_item"
513
+ else
514
+ printf " ${_R}✗${_NC} %s (missing from nexus root)\n" "$_item"
515
+ fi
516
+ done
517
+ printf '\n'
518
+
519
+ printf "${_BOLD}Member Repositories:${_NC}\n"
520
+ if [ -z "$(yaml_list_repos)" ]; then
521
+ printf " ${_DIM}(No repositories registered yet. Use 'rnex add <name> <path>')${_NC}\n"
522
+ else
523
+ yaml_list_repos | while read -r _name; do
524
+ _scope="$(yaml_get "$_name" "scope")"
525
+ _path="$(yaml_get "$_name" "path")"
526
+ _link="$REPOS_DIR/$_name"
527
+
528
+ if [ "$_scope" = "hidden" ]; then
529
+ printf " ${_Y}○${_NC} %-22s ${_DIM}hidden${_NC}\n" "$_name"
530
+ elif [ -L "$_link" ] && [ -d "$_link" ]; then
531
+ printf " ${_G}●${_NC} %-22s ${_G}active${_NC} → %s\n" "$_name" "$_path"
532
+ elif [ -L "$_link" ]; then
533
+ printf " ${_R}✗${_NC} %-22s ${_R}BROKEN LINK${_NC} → %s\n" "$_name" "$_path"
534
+ else
535
+ printf " ${_R}✗${_NC} %-22s ${_R}MISSING LINK${_NC} (run 'rnex sync')\n" "$_name"
536
+ fi
537
+ done
538
+ fi
539
+
540
+ printf "\n${_DIM}Workspace: %s${_NC}\n" "$NEXUS_DIR"
541
+ printf "${_DIM}Repos Dir: %s${_NC}\n" "$REPOS_DIR"
542
+ printf "${_DIM}Config: %s${_NC}\n\n" "$WORKSPACE_YAML"
543
+ }
544
+
545
+ cmd_sync() {
546
+ require_workspace
547
+ ensure_repos_dir
548
+
549
+ log_info "Synchronizing workspace from $RNEX_CONFIG_FILE..."
550
+ yaml_list_repos | while read -r _name; do
551
+ _scope="$(yaml_get "$_name" "scope")"
552
+
553
+ if [ "$_scope" = "hidden" ]; then
554
+ remove_scope_link "$_name"
555
+ else
556
+ create_scope_link "$_name" || true
557
+ fi
558
+
559
+ inject_ai_files "$_name" || true
560
+ done
561
+
562
+ log_ok "Sync complete — all scope links and AI context files reconciled"
563
+ }
564
+
565
+ cmd_install() {
566
+ _target_bin="${1:-$HOME/.local/bin}"
567
+ _target_bin="$(resolve_path "$_target_bin")"
568
+
569
+ mkdir -p "$_target_bin" 2>/dev/null || true
570
+ if [ -d "$_target_bin" ] && [ -w "$_target_bin" ]; then
571
+ if [ -e "$_target_bin/rnex" ] && [ "$(resolve_script_path "$_target_bin/rnex" 2>/dev/null)" = "$SCRIPT_LOCATION" ]; then
572
+ log_ok "'rnex' is already installed at ${_BOLD}$_target_bin/rnex${_NC}"
573
+ else
574
+ rm -f "$_target_bin/rnex" "$_target_bin/repo-nexus"
575
+ ln -sf "$SCRIPT_LOCATION" "$_target_bin/rnex" 2>/dev/null || cp "$SCRIPT_LOCATION" "$_target_bin/rnex"
576
+ ln -sf "$SCRIPT_LOCATION" "$_target_bin/repo-nexus" 2>/dev/null || cp "$SCRIPT_LOCATION" "$_target_bin/repo-nexus"
577
+ chmod +x "$_target_bin/rnex" "$_target_bin/repo-nexus" 2>/dev/null || true
578
+ log_ok "Installed 'rnex' and 'repo-nexus' into ${_BOLD}$_target_bin${_NC}"
579
+ fi
580
+ else
581
+ log_warn "Cannot write to $_target_bin."
582
+ log_info "You can manually create an alias in your shell profile (~/.zshrc or ~/.bashrc):"
583
+ log_dim " alias rnex=\"$SCRIPT_LOCATION\""
584
+ log_dim " alias repo-nexus=\"$SCRIPT_LOCATION\""
585
+ return 1
586
+ fi
587
+
588
+ case ":$PATH:" in
589
+ *":$_target_bin:"*)
590
+ log_ok "$_target_bin is in your PATH"
591
+ ;;
592
+ *)
593
+ log_warn "$_target_bin is not in your current PATH."
594
+ log_dim "Add this line to your shell profile (~/.zshrc or ~/.bashrc):"
595
+ log_dim " export PATH=\"$_target_bin:\$PATH\""
596
+ ;;
597
+ esac
598
+ }
599
+
600
+ cmd_version() {
601
+ printf "rnex %s\n" "$RNEX_VERSION"
602
+ }
603
+
604
+ cmd_help() {
605
+ cat <<EOF
606
+
607
+ rnex — Repo Nexus CLI (v$RNEX_VERSION)
608
+ Tooling-independent multi-repo workspace manager with shared AI context.
609
+
610
+ USAGE
611
+ rnex [options] <command> [arguments]
612
+
613
+ OPTIONS
614
+ -c, --config <file> Path to $RNEX_CONFIG_FILE (executes in the directory where config lives)
615
+ -h, --help Show help information
616
+ -v, --version Show version
617
+
618
+ WORKSPACE & SYSTEM SETUP
619
+ init [dir] Initialize a new Repo Nexus workspace in current (or specified) dir
620
+ install [dir] Install rnex & repo-nexus globally into ~/.local/bin (or custom dir)
621
+ sync Reconcile scope links & AI files from $RNEX_CONFIG_FILE
622
+
623
+ REPO MANAGEMENT
624
+ add <name> <path> Register local repo, link to scope & auto-inject AI context
625
+ remove <name> Unregister repo, unlink scope & clean up injected AI files
626
+ list List all registered member repos
627
+ status Inspect workspace health, active repos, and AI context files
628
+
629
+ VISIBILITY CONTROL
630
+ show <name> Make repo visible in workspace (creates repos/<name> link)
631
+ hide <name> Hide repo from workspace scope (removes repos/<name> link)
632
+
633
+ EXAMPLES
634
+ rnex init # Initialize workspace in current folder
635
+ rnex install # Install rnex CLI globally to ~/.local/bin
636
+ rnex add my-app ~/code/my-app # Register repo (links & syncs AI context)
637
+ rnex add my-app ../my-app # Register repo (relative path)
638
+ rnex --config /path/to/rnex.yaml list # Operate on a specific workspace directly
639
+ rnex hide my-app # Temporarily hide from active indexing
640
+ rnex show my-app # Bring back into active scope
641
+ rnex sync # Reconcile all links from $RNEX_CONFIG_FILE
642
+
643
+ EOF
644
+ }
645
+
646
+ # ============================================================================
647
+ # Entry Point & Option Parsing
648
+ # ============================================================================
649
+
650
+ CUSTOM_CONFIG=""
651
+ [ -n "$RNEX_CONFIG" ] && CUSTOM_CONFIG="$RNEX_CONFIG"
652
+
653
+ # Filter --config / -c flags
654
+ ORIG_COUNT=$#
655
+ i=0
656
+ while [ "$i" -lt "$ORIG_COUNT" ]; do
657
+ i=$((i + 1))
658
+ case "$1" in
659
+ --config=*)
660
+ CUSTOM_CONFIG="${1#*=}"
661
+ shift
662
+ ;;
663
+ --config|-c)
664
+ [ -n "$2" ] || die "Option $1 requires a file path argument"
665
+ CUSTOM_CONFIG="$2"
666
+ shift 2
667
+ i=$((i + 1))
668
+ ;;
669
+ *)
670
+ _arg="$1"
671
+ shift
672
+ set -- "$@" "$_arg"
673
+ ;;
674
+ esac
675
+ done
676
+
677
+ case "${1:-help}" in
678
+ init) shift; cmd_init "$@" ;;
679
+ install) shift; cmd_install "$@" ;;
680
+ add) shift; cmd_add "$@" ;;
681
+ remove) shift; cmd_remove "$@" ;;
682
+ show) shift; cmd_show "$@" ;;
683
+ hide) shift; cmd_hide "$@" ;;
684
+ list) shift; cmd_list "$@" ;;
685
+ status) shift; cmd_status "$@" ;;
686
+ sync) shift; cmd_sync "$@" ;;
687
+ version|-v|--version) cmd_version ;;
688
+ help|-h|--help) cmd_help ;;
689
+ *) die "Unknown command: $1 (try 'rnex help')" ;;
690
+ esac