rig-cli 1.0.0__tar.gz

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.
Files changed (161) hide show
  1. rig_cli-1.0.0/.githooks/pre-commit +4 -0
  2. rig_cli-1.0.0/.github/workflows/publish.yml +80 -0
  3. rig_cli-1.0.0/.gitignore +10 -0
  4. rig_cli-1.0.0/.importlinter +13 -0
  5. rig_cli-1.0.0/.jscpd.json +13 -0
  6. rig_cli-1.0.0/Makefile +32 -0
  7. rig_cli-1.0.0/PKG-INFO +503 -0
  8. rig_cli-1.0.0/README.md +488 -0
  9. rig_cli-1.0.0/TASKS.md +103 -0
  10. rig_cli-1.0.0/examples/rig.json +26 -0
  11. rig_cli-1.0.0/examples/rig.mk +36 -0
  12. rig_cli-1.0.0/examples/stack.json +26 -0
  13. rig_cli-1.0.0/examples/stack.mk +37 -0
  14. rig_cli-1.0.0/pyproject.toml +47 -0
  15. rig_cli-1.0.0/rig.schema.json +159 -0
  16. rig_cli-1.0.0/ruff.toml +53 -0
  17. rig_cli-1.0.0/scripts/anti_tamper/__init__.py +5 -0
  18. rig_cli-1.0.0/scripts/anti_tamper/configs.py +32 -0
  19. rig_cli-1.0.0/scripts/anti_tamper/deps.py +85 -0
  20. rig_cli-1.0.0/scripts/anti_tamper/pragmas.py +56 -0
  21. rig_cli-1.0.0/scripts/anti_tamper/runner.py +46 -0
  22. rig_cli-1.0.0/scripts/anti_tamper/schemas.py +148 -0
  23. rig_cli-1.0.0/scripts/check_anti_tamper.py +15 -0
  24. rig_cli-1.0.0/scripts/check_cycles.py +69 -0
  25. rig_cli-1.0.0/scripts/check_density.py +107 -0
  26. rig_cli-1.0.0/scripts/check_lines.py +78 -0
  27. rig_cli-1.0.0/src/rig/__init__.py +6 -0
  28. rig_cli-1.0.0/src/rig/__main__.py +8 -0
  29. rig_cli-1.0.0/src/rig/cli.py +107 -0
  30. rig_cli-1.0.0/src/rig/commands/__init__.py +23 -0
  31. rig_cli-1.0.0/src/rig/commands/check.py +125 -0
  32. rig_cli-1.0.0/src/rig/commands/common.py +149 -0
  33. rig_cli-1.0.0/src/rig/commands/dispatch.py +87 -0
  34. rig_cli-1.0.0/src/rig/commands/down/__init__.py +139 -0
  35. rig_cli-1.0.0/src/rig/commands/down/runner.py +136 -0
  36. rig_cli-1.0.0/src/rig/commands/init.py +148 -0
  37. rig_cli-1.0.0/src/rig/commands/logs.py +146 -0
  38. rig_cli-1.0.0/src/rig/commands/prune.py +142 -0
  39. rig_cli-1.0.0/src/rig/commands/ps.py +149 -0
  40. rig_cli-1.0.0/src/rig/commands/status.py +149 -0
  41. rig_cli-1.0.0/src/rig/commands/up/__init__.py +111 -0
  42. rig_cli-1.0.0/src/rig/commands/up/context.py +36 -0
  43. rig_cli-1.0.0/src/rig/commands/up/loop.py +150 -0
  44. rig_cli-1.0.0/src/rig/commands/up/relink.py +88 -0
  45. rig_cli-1.0.0/src/rig/commands/up/rollback.py +49 -0
  46. rig_cli-1.0.0/src/rig/commands/up/runner.py +118 -0
  47. rig_cli-1.0.0/src/rig/commands/up/service.py +136 -0
  48. rig_cli-1.0.0/src/rig/compose/__init__.py +1 -0
  49. rig_cli-1.0.0/src/rig/compose/client.py +144 -0
  50. rig_cli-1.0.0/src/rig/compose/context.py +56 -0
  51. rig_cli-1.0.0/src/rig/compose/discovery.py +121 -0
  52. rig_cli-1.0.0/src/rig/compose/docker.py +117 -0
  53. rig_cli-1.0.0/src/rig/compose/starter.py +145 -0
  54. rig_cli-1.0.0/src/rig/compose/stopper.py +71 -0
  55. rig_cli-1.0.0/src/rig/compose/supervisor.py +78 -0
  56. rig_cli-1.0.0/src/rig/core/__init__.py +1 -0
  57. rig_cli-1.0.0/src/rig/core/constants.py +65 -0
  58. rig_cli-1.0.0/src/rig/core/env.py +83 -0
  59. rig_cli-1.0.0/src/rig/core/errors.py +74 -0
  60. rig_cli-1.0.0/src/rig/core/identity.py +141 -0
  61. rig_cli-1.0.0/src/rig/core/locks.py +112 -0
  62. rig_cli-1.0.0/src/rig/core/state.py +150 -0
  63. rig_cli-1.0.0/src/rig/core/terminal.py +145 -0
  64. rig_cli-1.0.0/src/rig/manifest/__init__.py +1 -0
  65. rig_cli-1.0.0/src/rig/manifest/detector.py +138 -0
  66. rig_cli-1.0.0/src/rig/manifest/inspect.py +18 -0
  67. rig_cli-1.0.0/src/rig/manifest/loader.py +146 -0
  68. rig_cli-1.0.0/src/rig/manifest/models.py +129 -0
  69. rig_cli-1.0.0/src/rig/manifest/parser.py +123 -0
  70. rig_cli-1.0.0/src/rig/manifest/schema.py +89 -0
  71. rig_cli-1.0.0/src/rig/net/__init__.py +1 -0
  72. rig_cli-1.0.0/src/rig/net/health.py +76 -0
  73. rig_cli-1.0.0/src/rig/net/ports.py +141 -0
  74. rig_cli-1.0.0/src/rig/net/probe.py +56 -0
  75. rig_cli-1.0.0/src/rig/net/registry.py +132 -0
  76. rig_cli-1.0.0/src/rig/parser.py +72 -0
  77. rig_cli-1.0.0/src/rig/proc/__init__.py +1 -0
  78. rig_cli-1.0.0/src/rig/proc/process.py +133 -0
  79. rig_cli-1.0.0/src/rig/proc/record.py +54 -0
  80. rig_cli-1.0.0/src/rig/proc/spawn.py +130 -0
  81. rig_cli-1.0.0/src/rig/proc/teardown.py +139 -0
  82. rig_cli-1.0.0/tests/commands/lifecycle/test_down_json_envelope.py +111 -0
  83. rig_cli-1.0.0/tests/commands/lifecycle/test_down_reclaim_compose.py +90 -0
  84. rig_cli-1.0.0/tests/commands/lifecycle/test_down_reclaim_orphan.py +116 -0
  85. rig_cli-1.0.0/tests/commands/lifecycle/test_down_reclaim_prune_ps.py +127 -0
  86. rig_cli-1.0.0/tests/commands/lifecycle/test_down_state_dependencies.py +102 -0
  87. rig_cli-1.0.0/tests/commands/lifecycle/test_down_unverifiable.py +117 -0
  88. rig_cli-1.0.0/tests/commands/lifecycle/test_up_recovery_ordering.py +135 -0
  89. rig_cli-1.0.0/tests/commands/lifecycle/test_up_recovery_scoped.py +126 -0
  90. rig_cli-1.0.0/tests/commands/retry/test_up_idempotent.py +149 -0
  91. rig_cli-1.0.0/tests/commands/retry/test_up_port_retry.py +111 -0
  92. rig_cli-1.0.0/tests/commands/retry/test_up_rollback.py +149 -0
  93. rig_cli-1.0.0/tests/commands/test_cmd_down.py +123 -0
  94. rig_cli-1.0.0/tests/commands/test_cmd_init_compose.py +65 -0
  95. rig_cli-1.0.0/tests/commands/test_cmd_init_schema.py +118 -0
  96. rig_cli-1.0.0/tests/commands/test_cmd_prune.py +149 -0
  97. rig_cli-1.0.0/tests/commands/test_cmd_ps_width.py +110 -0
  98. rig_cli-1.0.0/tests/commands/test_cmd_status.py +135 -0
  99. rig_cli-1.0.0/tests/commands/test_cmd_up.py +80 -0
  100. rig_cli-1.0.0/tests/commands/test_cmd_up_recovery.py +98 -0
  101. rig_cli-1.0.0/tests/commands/test_logs.py +150 -0
  102. rig_cli-1.0.0/tests/commands/test_scope_dispatch.py +77 -0
  103. rig_cli-1.0.0/tests/commands/test_surface.py +74 -0
  104. rig_cli-1.0.0/tests/compose/docker/test_client_settings.py +90 -0
  105. rig_cli-1.0.0/tests/compose/docker/test_context_pinning.py +131 -0
  106. rig_cli-1.0.0/tests/compose/docker/test_context_resolution.py +58 -0
  107. rig_cli-1.0.0/tests/compose/docker/test_context_switch.py +100 -0
  108. rig_cli-1.0.0/tests/compose/docker/test_endpoint.py +120 -0
  109. rig_cli-1.0.0/tests/compose/docker/test_endpoint_pinning.py +103 -0
  110. rig_cli-1.0.0/tests/compose/reclaim/test_compose_readiness.py +94 -0
  111. rig_cli-1.0.0/tests/compose/reclaim/test_compose_start_cleanup.py +89 -0
  112. rig_cli-1.0.0/tests/compose/reclaim/test_prune_reclaim.py +72 -0
  113. rig_cli-1.0.0/tests/compose/reclaim/test_replica_reclaim.py +112 -0
  114. rig_cli-1.0.0/tests/compose/reclaim/test_stop_record_fallback.py +102 -0
  115. rig_cli-1.0.0/tests/compose/status/test_client_status.py +141 -0
  116. rig_cli-1.0.0/tests/compose/status/test_container_status.py +111 -0
  117. rig_cli-1.0.0/tests/compose/status/test_docker_status_daemon.py +141 -0
  118. rig_cli-1.0.0/tests/compose/status/test_print_status.py +147 -0
  119. rig_cli-1.0.0/tests/compose/status/test_process_status.py +69 -0
  120. rig_cli-1.0.0/tests/compose/status/test_status_fallbacks.py +140 -0
  121. rig_cli-1.0.0/tests/compose/test_lifecycle.py +114 -0
  122. rig_cli-1.0.0/tests/compose/test_lifecycle_teardown.py +133 -0
  123. rig_cli-1.0.0/tests/compose/test_reclaim.py +140 -0
  124. rig_cli-1.0.0/tests/compose/test_required_vars.py +140 -0
  125. rig_cli-1.0.0/tests/compose/test_required_vars_teardown.py +112 -0
  126. rig_cli-1.0.0/tests/compose/test_scoping.py +58 -0
  127. rig_cli-1.0.0/tests/compose/test_service_env.py +124 -0
  128. rig_cli-1.0.0/tests/compose/test_startup_interrupt.py +122 -0
  129. rig_cli-1.0.0/tests/compose/test_startup_interrupt_cmd_up.py +86 -0
  130. rig_cli-1.0.0/tests/compose/test_up_interrupt.py +121 -0
  131. rig_cli-1.0.0/tests/conftest.py +41 -0
  132. rig_cli-1.0.0/tests/core/test_instance_id.py +46 -0
  133. rig_cli-1.0.0/tests/core/test_lock_contention.py +104 -0
  134. rig_cli-1.0.0/tests/core/test_lock_lifecycle.py +61 -0
  135. rig_cli-1.0.0/tests/core/test_runtime_dir.py +35 -0
  136. rig_cli-1.0.0/tests/core/test_state.py +76 -0
  137. rig_cli-1.0.0/tests/core/test_terminal.py +117 -0
  138. rig_cli-1.0.0/tests/manifest/deltalytic/test_bindings.py +67 -0
  139. rig_cli-1.0.0/tests/manifest/deltalytic/test_layout.py +46 -0
  140. rig_cli-1.0.0/tests/manifest/test_command_parsing.py +103 -0
  141. rig_cli-1.0.0/tests/manifest/test_healthcheck_and_ordering.py +84 -0
  142. rig_cli-1.0.0/tests/manifest/test_modes.py +100 -0
  143. rig_cli-1.0.0/tests/manifest/test_scopes.py +118 -0
  144. rig_cli-1.0.0/tests/manifest/test_validation.py +39 -0
  145. rig_cli-1.0.0/tests/net/test_central_port_registry.py +101 -0
  146. rig_cli-1.0.0/tests/net/test_friendly_ports.py +91 -0
  147. rig_cli-1.0.0/tests/net/test_listeners.py +43 -0
  148. rig_cli-1.0.0/tests/net/test_ports.py +47 -0
  149. rig_cli-1.0.0/tests/net/test_socket_transfer.py +114 -0
  150. rig_cli-1.0.0/tests/net/test_sticky_ports.py +106 -0
  151. rig_cli-1.0.0/tests/proc/test_env_isolation.py +98 -0
  152. rig_cli-1.0.0/tests/proc/test_external_tool_safety.py +148 -0
  153. rig_cli-1.0.0/tests/proc/test_health.py +104 -0
  154. rig_cli-1.0.0/tests/proc/test_identity.py +140 -0
  155. rig_cli-1.0.0/tests/proc/test_lifecycle_hygiene.py +99 -0
  156. rig_cli-1.0.0/tests/proc/test_mode_switch.py +87 -0
  157. rig_cli-1.0.0/tests/proc/test_recovery.py +139 -0
  158. rig_cli-1.0.0/tests/proc/test_runtime_recovery.py +139 -0
  159. rig_cli-1.0.0/tests/proc/test_supervisor.py +124 -0
  160. rig_cli-1.0.0/tests/proc/test_teardown.py +123 -0
  161. rig_cli-1.0.0/uv.lock +692 -0
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ set -e
3
+ echo "Running code quality gates pre-commit hook..."
4
+ make check-quality
@@ -0,0 +1,80 @@
1
+ name: Publish to PyPI and GitHub Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch:
8
+ inputs:
9
+ tag:
10
+ description: 'Tag or version to release (e.g. v1.0.0)'
11
+ required: false
12
+ default: 'v1.0.0'
13
+
14
+ permissions:
15
+ contents: write
16
+
17
+ jobs:
18
+ verify:
19
+ name: Verify Quality Gates & Tests
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Python 3.12
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ - name: Install uv
30
+ uses: astral-sh/setup-uv@v5
31
+ with:
32
+ enable-cache: true
33
+
34
+ - name: Install dependencies
35
+ run: uv sync --all-extras --dev
36
+
37
+ - name: Run quality checks and tests
38
+ run: make check-quality
39
+
40
+ publish:
41
+ name: Build & Publish to PyPI
42
+ needs: verify
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+
47
+ - name: Install uv
48
+ uses: astral-sh/setup-uv@v5
49
+ with:
50
+ enable-cache: true
51
+
52
+ - name: Build distributions
53
+ run: uv build
54
+
55
+ - name: Publish to PyPI
56
+ uses: pypa/gh-action-pypi-publish@release/v1
57
+ with:
58
+ password: ${{ secrets.PYPI_API_TOKEN }}
59
+ packages-dir: dist/
60
+
61
+ release:
62
+ name: Create GitHub Release
63
+ needs: [verify, publish]
64
+ runs-on: ubuntu-latest
65
+ permissions:
66
+ contents: write
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+
70
+ - name: Create GitHub Release
71
+ env:
72
+ GH_TOKEN: ${{ github.token }}
73
+ run: |
74
+ tag="${{ github.ref_name }}"
75
+ if [[ "$tag" == "main" || "$tag" != v* ]]; then
76
+ tag="${{ inputs.tag || 'v1.0.0' }}"
77
+ fi
78
+ if ! gh release view "$tag" >/dev/null 2>&1; then
79
+ gh release create "$tag" --title "$tag" --generate-notes
80
+ fi
@@ -0,0 +1,10 @@
1
+ .local-run/
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+ .coverage
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .DS_Store
@@ -0,0 +1,13 @@
1
+ [importlinter]
2
+ root_package = rig
3
+
4
+ [importlinter:contract:1]
5
+ name = Architectural Layers
6
+ type = layers
7
+ layers =
8
+ rig.commands
9
+ rig.manifest
10
+ rig.compose
11
+ rig.proc
12
+ rig.net
13
+ rig.core
@@ -0,0 +1,13 @@
1
+ {
2
+ "threshold": 0,
3
+ "minLines": 5,
4
+ "minTokens": 40,
5
+ "reporters": ["console"],
6
+ "ignore": [
7
+ "**/node_modules/**",
8
+ "**/.venv/**",
9
+ "**/.pytest_cache/**",
10
+ "**/.ruff_cache/**"
11
+ ],
12
+ "absolute": true
13
+ }
rig_cli-1.0.0/Makefile ADDED
@@ -0,0 +1,32 @@
1
+ .DEFAULT_GOAL := help
2
+ .PHONY: test check-quality install-hooks help
3
+
4
+ help:
5
+ @echo "rig commands:"
6
+ @echo " make test - Run test suite"
7
+ @echo " make check-quality - Run all quality gates in series"
8
+ @echo " make install-hooks - Configure git pre-commit hook"
9
+
10
+ install-hooks:
11
+ @git config core.hooksPath .githooks
12
+ @chmod +x .githooks/pre-commit
13
+ @echo "✅ Pre-commit hook configured via core.hooksPath=.githooks"
14
+
15
+ test:
16
+ uv run pytest tests/
17
+
18
+ check-quality:
19
+ uv run python scripts/check_anti_tamper.py
20
+ uv run python scripts/check_cycles.py
21
+ uv run ruff check --config ruff.toml --ignore-noqa src/
22
+ uv run ruff check --config ruff.toml .
23
+ uv run ruff format --config ruff.toml --check .
24
+ npx jscpd@^4.0.0 src/ scripts/ --threshold 0 --min-tokens 40 --min-lines 5 --format python
25
+ uv run python scripts/check_density.py
26
+ uv run python scripts/check_lines.py src 150
27
+ uv run python scripts/check_lines.py scripts 150
28
+ uv run pylint --rcfile=pyproject.toml --recursive=y --persistent=n --disable=all --enable=C0302,R1702,E0001,F0001,F0010 src/ scripts/
29
+ uv run lint-imports
30
+ uv run deptry .
31
+ uv run --isolated --python 3.10 python -c "import rig"
32
+ uv run pytest --cov=src/rig --cov-fail-under=80 tests/
rig_cli-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,503 @@
1
+ Metadata-Version: 2.5
2
+ Name: rig-cli
3
+ Version: 1.0.0
4
+ Summary: Zero-dependency local dev environment and process runner with dynamic port allocation, socket inheritance, and verified lifecycle management.
5
+ Requires-Python: >=3.10
6
+ Provides-Extra: dev
7
+ Requires-Dist: deptry>=0.20.0; extra == 'dev'
8
+ Requires-Dist: grimp>=3.0; extra == 'dev'
9
+ Requires-Dist: import-linter>=2.0; extra == 'dev'
10
+ Requires-Dist: pylint>=3.0.0; extra == 'dev'
11
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
12
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
13
+ Requires-Dist: ruff>=0.16.0; extra == 'dev'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # rig
17
+
18
+ A zero-dependency, zero-daemon developer environment supervisor and process runner for multi-service repositories. Featuring machine-wide supervision, dynamic port allocation, zero-race socket inheritance, multi-stack modes (`native`, `container`), and verified lifecycle management.
19
+
20
+ ---
21
+
22
+ ## Why `rig`?
23
+
24
+ Modern multi-service local development often suffers from:
25
+ - **Port collisions**: Multiple developers or multiple checkouts of the same repo colliding on static ports like `3000` or `8000`.
26
+ - **Zombie processes**: Dev servers left orphaned after an interrupted test run, keeping ports bound and blocking subsequent runs.
27
+ - **Race conditions on port binding**: Allocating an ephemeral port, closing the probe socket, and having another process grab it before the service can bind.
28
+ - **Hidden global state**: No way to see what services or test instances are running across all checkouts on your machine.
29
+ - **Heavy or brittle supervisors**: Requiring background daemons (systemd/dockerd/supervisord) or complex Node/Ruby process supervisors just to launch a Python API and a frontend dev server.
30
+
31
+ `rig` solves this with:
32
+ 1. **Zero External Runtime Dependencies**: Standard library Python 3.10+ only (`socket`, `subprocess`, `os`, `signal`, `json`, `fcntl`, `shlex`, `dataclasses`, `pathlib`).
33
+ 2. **Zero Persistent Daemons**: Fully file-backed atomic registry (`~/.local/state/rig/instances/`) and non-blocking file locks (`flock`). Fast, crash-resilient, and stateless.
34
+ 3. **Machine-Wide Supervision**: Inspect all active projects across your machine (`rig ps`), stop any named project (`rig down <project>`), or tear down all active instances at once (`rig down --all`).
35
+ 4. **Multi-Stack Modes (`native` vs `container`)**: Define base services and mode overlays in a single `rig.json`. Switch modes cleanly with mutex collision protection (`rig up --mode container --switch`).
36
+ 5. **Zero-Race Socket Inheritance (`type: "fd"`)**: Binds listening sockets on kernel port `0`, holds them open, and passes the descriptors directly into child processes (`--fd {fd}`). The port is never released between allocation and service start.
37
+ 6. **Human-Friendly & Sticky Ports**: Allocates clean, typing-friendly ports (`3000` for frontend, `8000` for backend) by default, supports explicit `preferred_port` in `rig.json`, and maintains sticky port leases in instance state across restarts.
38
+ 7. **One-Command Setup**: `rig init [--up]` automatically scans your repository for Docker Compose, FastAPI, Flask, Django, Vite, or Next.js and generates a validated `rig.json`.
39
+ 8. **AI-Friendly Protocol**: Universal `--json` output envelope (`ok`, `schema`, `data`/`error`) and deterministic exit codes (`0` to `6`, `130`) designed for autonomous agents and CLI automation.
40
+
41
+ ---
42
+
43
+ ## Installation
44
+
45
+ ### Install as a Standalone Global Tool from PyPI (Recommended)
46
+
47
+ Using `pipx`:
48
+ ```bash
49
+ pipx install rig-cli
50
+ ```
51
+
52
+ Using `uv`:
53
+ ```bash
54
+ uv tool install rig-cli
55
+ ```
56
+
57
+ Using standard `pip`:
58
+ ```bash
59
+ pip install --user rig-cli
60
+ ```
61
+
62
+ Both `rig` and `rig-cli` commands are automatically available on your `$PATH`.
63
+
64
+ ### Install from Git
65
+
66
+ Using `uv`:
67
+ ```bash
68
+ uv tool install --force "git+https://github.com/evgesha9400/rig.git"
69
+ ```
70
+
71
+ Using `pipx` / `pip`:
72
+ ```bash
73
+ pip install --user "git+https://github.com/evgesha9400/rig.git"
74
+ ```
75
+
76
+ ### Add to a Specific Project
77
+
78
+ ```bash
79
+ uv add "git+https://github.com/evgesha9400/rig.git"
80
+ ```
81
+
82
+ ### Direct Drop-in (Zero Installation)
83
+ Since `rig` is a single self-contained module with zero third-party dependencies, you can copy `src/rig/cli.py` directly into any repository (e.g. `scripts/rig.py`):
84
+ ```bash
85
+ curl -fsSL https://raw.githubusercontent.com/evgesha9400/rig/main/src/rig/cli.py -o scripts/rig.py
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Quick Start
91
+
92
+ ### 1. Initialize a Project
93
+
94
+ Run `rig init` in your repository root. `rig` inspects your files, detects existing backends, frontends, and Docker Compose configurations, and writes a tailored `rig.json`:
95
+
96
+ ```bash
97
+ rig init
98
+ # Or initialize and start services immediately:
99
+ rig init --up
100
+ ```
101
+
102
+ You can preview the detected configuration without writing files:
103
+ ```bash
104
+ rig init --dry-run
105
+ ```
106
+
107
+ ### 2. Verify Your Environment
108
+
109
+ Run pre-flight static verification to ensure working directories exist, binaries are executable, Docker Compose files are present, and dependency graphs contain no cycles:
110
+
111
+ ```bash
112
+ rig check
113
+ ```
114
+
115
+ ### 3. Start & Supervise Services
116
+
117
+ ```bash
118
+ # Start all services in the active or default mode
119
+ rig up
120
+
121
+ # Check status of the local checkout
122
+ rig status
123
+
124
+ # Inspect service logs
125
+ rig logs backend -n 50
126
+
127
+ # View all running projects and instances across your machine
128
+ rig ps
129
+
130
+ # Stop all services in the local checkout
131
+ rig down
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Global Machine-Wide Supervision
137
+
138
+ `rig` maintains a machine-wide state registry under `$XDG_STATE_HOME/rig/instances/` (default: `~/.local/state/rig/instances/`). Every project instance records its directory, PID, PGID, active mode, and allocated ports.
139
+
140
+ ### Inspect All Projects (`rig ps`)
141
+
142
+ ```bash
143
+ rig ps
144
+ ```
145
+ Example output:
146
+ ```text
147
+ PROJECT INSTANCE MODE STATUS ACTIVE PORTS ROOT
148
+ deltalytic 68d374ab9c34 native running 2/2 backend:54123, ui:54124 /Users/alice/projects/deltalytic
149
+ my-api a1b2c3d4e5f6 container running 1/1 db:5432 /Users/alice/work/my-api
150
+ ```
151
+
152
+ `STATUS` reports the instance as a whole: `running` when every recorded service is up, `partial` when only some are, `stopped` when none are, and `orphaned` when the checkout directory no longer exists.
153
+
154
+ Add `--health` to probe HTTP endpoints for live health checks:
155
+ ```bash
156
+ rig ps --health
157
+ ```
158
+
159
+ ### Targeted Teardown
160
+
161
+ Stop a project from anywhere on your machine, even if you are not inside its directory:
162
+
163
+ ```bash
164
+ # Stop by project name slug
165
+ rig down deltalytic
166
+
167
+ # Stop by specific instance ID
168
+ rig down 68d374ab9c34
169
+
170
+ # Stop ALL running instances across the entire machine
171
+ rig down --all
172
+ ```
173
+
174
+ `rig` stores process group IDs (`PGID`) and Docker Compose project references in its state registry, allowing it to cleanly terminate orphaned services even if the original working tree was deleted (`rm -rf`).
175
+
176
+ ### Cleanup Stale Instances
177
+
178
+ ```bash
179
+ # Clean up dead instances whose processes are no longer running
180
+ rig prune
181
+
182
+ # Force-kill any lingering processes in unmanaged instances and prune
183
+ rig prune --force
184
+ ```
185
+
186
+ `prune` reclaims an instance's recorded state but keeps its `checkout.lock` file,
187
+ so a concurrent `rig` command can never take a lock on a file nobody else can
188
+ see. `prune --force` stops services dependents-first and exits `1` while
189
+ preserving any dependency whose dependent refused to stop.
190
+
191
+ ---
192
+
193
+ ## Multi-Stack Modes (`native` vs `container`)
194
+
195
+ `rig` supports multi-stack modes within a single `rig.json`. For example, you can run database dependencies in containers while developing application code natively, or run the entire stack in containers.
196
+
197
+ ### Example `rig.json` with Modes:
198
+
199
+ ```json
200
+ {
201
+ "$schema": "https://raw.githubusercontent.com/evgesha9400/rig/main/rig.schema.json",
202
+ "project": "my-app",
203
+ "default_mode": "native",
204
+ "services": {
205
+ "db": {
206
+ "type": "compose",
207
+ "compose_file": "docker-compose.yml",
208
+ "compose_service": "postgres",
209
+ "health_tcp": 5432
210
+ }
211
+ },
212
+ "modes": {
213
+ "native": {
214
+ "services": {
215
+ "backend": {
216
+ "type": "fd",
217
+ "cwd": "backend",
218
+ "command": ".venv/bin/python -m app.main --fd {fd}",
219
+ "health": "/healthz",
220
+ "depends_on": ["db"]
221
+ },
222
+ "frontend": {
223
+ "type": "port",
224
+ "cwd": "frontend",
225
+ "command": "npm run dev -- --port {port}",
226
+ "health": "/",
227
+ "depends_on": ["backend"],
228
+ "env": {
229
+ "VITE_API_PORT": "{backend_port}"
230
+ }
231
+ }
232
+ }
233
+ },
234
+ "container": {
235
+ "services": {
236
+ "backend": {
237
+ "type": "compose",
238
+ "compose_file": "docker-compose.yml",
239
+ "compose_service": "backend",
240
+ "health": "http://127.0.0.1:8000/healthz",
241
+ "depends_on": ["db"]
242
+ },
243
+ "frontend": {
244
+ "type": "compose",
245
+ "compose_file": "docker-compose.yml",
246
+ "compose_service": "frontend",
247
+ "health": "http://127.0.0.1:3000/",
248
+ "depends_on": ["backend"]
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ ### Switching Modes Safely
257
+
258
+ `rig` prevents accidental multi-mode conflicts. If services are currently running in `native` mode, attempting to start `container` mode without stopping the old services will be safely rejected:
259
+
260
+ ```bash
261
+ # Fails with exit code 3 (E_MODE_CONFLICT) to prevent colliding processes:
262
+ rig up --mode container
263
+
264
+ # Cleanly tears down native services first and boots container mode:
265
+ rig up --mode container --switch
266
+ ```
267
+
268
+ ---
269
+
270
+ ## AI Agent & Automation Protocol
271
+
272
+ `rig` is designed from the ground up for reliable operation by AI coding assistants, orchestrators, and CI pipelines:
273
+
274
+ ### Universal `--json` Envelope
275
+
276
+ Every command accepts `--json` and emits a predictable schema:
277
+
278
+ ```json
279
+ {
280
+ "schema": "rig.ps/1",
281
+ "ok": true,
282
+ "data": [
283
+ {
284
+ "project": "my-app",
285
+ "instance_id": "68d374ab9c34",
286
+ "mode": "native",
287
+ "state": "running",
288
+ "services_count": 2,
289
+ "services_active": 2,
290
+ "root": "/path/to/my-app",
291
+ "ports": {"backend": 54123, "frontend": 54124}
292
+ }
293
+ ]
294
+ }
295
+ ```
296
+
297
+ Errors emit structured details with recovery hints:
298
+ ```json
299
+ {
300
+ "schema": "rig.error/1",
301
+ "ok": false,
302
+ "error": {
303
+ "code": "E_MODE_CONFLICT",
304
+ "message": "Instance is running in mode 'native'; cannot start mode 'container'",
305
+ "hint": "Pass --switch to stop the active mode first, or run 'rig down' before starting a new mode.",
306
+ "details": {"active_mode": "native", "requested_mode": "container"}
307
+ }
308
+ }
309
+ ```
310
+
311
+ ### Deterministic Exit Codes
312
+
313
+ | Exit Code | Constant | Meaning |
314
+ |---|---|---|
315
+ | `0` | `EXIT_OK` | Command completed successfully. |
316
+ | `1` | `EXIT_OP_FAILED` | Service failed to start, healthcheck timed out, or teardown failed. |
317
+ | `2` | `EXIT_USAGE` | Invalid command line arguments or invalid manifest syntax. |
318
+ | `3` | `EXIT_MUTEX_CONFLICT` | Instance lock busy (`checkout.lock`) or mode conflict without `--switch`. |
319
+ | `4` | `EXIT_NOT_FOUND` | Project, service, or instance target not found. |
320
+ | `5` | `EXIT_REFUSED` | Operation refused (e.g. destructive action without confirmation). |
321
+ | `6` | `EXIT_EXTERNAL_TOOL` | Missing external requirement (`docker`, `compose`, `lsof`). |
322
+ | `130` | `EXIT_INTERRUPTED` | Interrupted by signal (`SIGINT`, `SIGTERM`). |
323
+
324
+ ---
325
+
326
+ ## Manifest Reference (`rig.json`)
327
+
328
+ To inspect or validate manifest configurations against the formal JSON Schema:
329
+ ```bash
330
+ rig schema
331
+ ```
332
+
333
+ ### Root Fields
334
+
335
+ | Field | Type | Required | Description |
336
+ |---|---|---|---|
337
+ | `project` | string | Yes | Project identifier slug used for isolation and Docker Compose naming. |
338
+ | `default_mode` | string | No | Mode to use when `--mode` is omitted (defaults to first mode in `modes` or `native`). |
339
+ | `services` | object | No | Base services active across all modes. |
340
+ | `modes` | object | No | Dictionary of mode configurations (`{"native": {"services": {...}}, "container": ...}`). |
341
+
342
+ ### Service Fields
343
+
344
+ | Field | Type | Required | Description |
345
+ |---|---|---|---|
346
+ | `type` | `"fd"` \| `"port"` \| `"compose"` | Yes | Port allocation strategy. |
347
+ | `command` | string | For `fd` / `port` | Command line to execute. Supports `{fd}`, `{port}`, and `{<service>_port}` placeholders. |
348
+ | `cwd` | string | No | Working directory relative to repository root (defaults to `.`). |
349
+ | `health` | string | No | HTTP path to poll for 200 OK (e.g. `/healthz`, `/`). |
350
+ | `health_tcp` | integer | No | TCP port to poll for socket connection (ideal for databases like Postgres/Redis). |
351
+ | `depends_on` | string[] | No | Services that must be healthy before this service starts. |
352
+ | `aliases` | string[] | No | Alternative names for scope targeting (e.g. `["ui"]` for `frontend`). |
353
+ | `env` | map | No | Environment variables. Supports `{<service>_port}` placeholders. |
354
+ | `env_files` | string[] | No | Dotenv-style files, relative to the repository root, loaded before `env`. |
355
+ | `inherit` | string[] | No | Ambient environment variables to pass through beyond the base safe allowlist. |
356
+ | `compose_file` | string | For `compose` | Path to Docker Compose file. |
357
+ | `compose_service`| string | For `compose` | Name of service inside Docker Compose file. |
358
+ | `compose_port` | integer | No | Container port whose published host port is recorded as the service URL. |
359
+ | `docker_context`| string | No | Docker context every command for this service is pinned to. |
360
+
361
+ ### Environment and Docker Endpoint for `compose` Services
362
+
363
+ `env`, `env_files` and `inherit` apply to `compose` services as well as to `fd`
364
+ and `port` services. The resulting environment is handed to `docker compose`
365
+ itself, so it drives `${VAR}` interpolation inside the compose file and reaches
366
+ the containers.
367
+
368
+ That environment is an allowlist, so no ambient `DOCKER_*`, `COMPOSE_*` or
369
+ application variable can leak in and point a service at another project's
370
+ resources. The Docker client settings (`DOCKER_CONFIG`, `DOCKER_CERT_PATH`,
371
+ `DOCKER_TLS_VERIFY`) are the exception: they are passed through so a TLS or
372
+ rootless setup can still reach its own daemon.
373
+
374
+ Those client settings are recorded with the service, and every later plain
375
+ `docker` command — the label query, the inspection, `stop` and `rm` — is given
376
+ the recorded ones instead of whatever the terminal holds. A service started
377
+ against its own `DOCKER_CONFIG` therefore stays reachable for `rig status` and
378
+ `rig down`, and a `DOCKER_CONFIG` exported afterwards cannot redirect them.
379
+
380
+ The Docker endpoint in force at startup — `DOCKER_HOST` and the Docker context
381
+ — is recorded with the service. Every later status query and teardown is pinned
382
+ to that endpoint, so a `DOCKER_HOST` that changes between `rig up` and `rig
383
+ down` can never send the query to a daemon that does not hold the container.
384
+
385
+ The endpoint is chosen in Docker's own order of precedence:
386
+
387
+ 1. the `docker_context` the manifest declares;
388
+ 2. the ambient `DOCKER_CONTEXT`, which is read even though the service
389
+ environment is an allowlist, so `DOCKER_CONTEXT=colima rig up` is honoured;
390
+ 3. the ambient `DOCKER_HOST`, when neither of the above names a context;
391
+ 4. otherwise the active context, resolved with `docker context show`.
392
+
393
+ Whenever a context decides, it is recorded alone and no host is recorded with
394
+ it, because `--context` outranks `DOCKER_HOST`. A later `docker context use
395
+ colima` therefore does not strand the container: `rig status`, `rig down` and
396
+ `rig prune` still reach the context that holds it.
397
+
398
+ ---
399
+
400
+ ## Stable & Human-Friendly Port Allocation
401
+
402
+ `rig` eliminates random ephemeral ports (e.g. `58472`) and maintains stable, human-friendly ports across restarts:
403
+
404
+ ### 1. Precedence Hierarchy
405
+ 1. **Explicit `preferred_port` (or `port`)**: Defined per service in `rig.json` (e.g. `"preferred_port": 3000`).
406
+ 2. **Sticky Leased Port**: Rig persists assigned ports in instance state (`~/.local/state/rig/instances/<instance>/state.json`), reusing the same port across `rig down` and `rig up`.
407
+ 3. **Role-Based Friendly Defaults**:
408
+ - `frontend` / `web` / `ui` / `client` / `vite` / `next`: starts at `3000`
409
+ - `backend` / `api` / `server` / `app` / `worker`: starts at `8000`
410
+ - `docs` / `storybook` / `admin`: starts at `4000`
411
+ - other / unmatched: starts at `5000`
412
+ 4. **Collision-Safe Probing**: If the target port is occupied (e.g. by another checkout running simultaneously), `rig` probes `port + 1`, `port + 2`, etc., avoiding collisions automatically without jumping to high ephemeral numbers.
413
+
414
+ ### 2. Configuration Example
415
+
416
+ ```json
417
+ {
418
+ "project": "my-app",
419
+ "services": {
420
+ "frontend": {
421
+ "type": "port",
422
+ "preferred_port": 3000,
423
+ "command": ["npm", "run", "dev", "--", "--port", "{port}"]
424
+ },
425
+ "backend": {
426
+ "type": "fd",
427
+ "preferred_port": 8000,
428
+ "app": "main:app"
429
+ }
430
+ }
431
+ }
432
+ ```
433
+
434
+ ---
435
+
436
+ ## How Socket Inheritance Works (`type: "fd"`)
437
+
438
+ When a service specifies `type: "fd"`, `rig`:
439
+ 1. Creates a TCP socket bound to `127.0.0.1:0`. The OS kernel allocates a free ephemeral port immediately.
440
+ 2. Marks the socket listening (`listen(128)`).
441
+ 3. Keeps the descriptor open and passes it via `subprocess.Popen(pass_fds=[fd])`.
442
+ 4. Passes the integer descriptor to the command line via `--fd {fd}`.
443
+
444
+ ### Python / Uvicorn Example:
445
+
446
+ ```python
447
+ import argparse
448
+ import socket
449
+ import uvicorn
450
+
451
+ parser = argparse.ArgumentParser()
452
+ parser.add_argument("--fd", type=int, default=None)
453
+ args = parser.parse_args()
454
+
455
+ if args.fd is not None:
456
+ sock = socket.fromfd(args.fd, socket.AF_INET, socket.SOCK_STREAM)
457
+ uvicorn.run("myapp.main:app", fd=sock.fileno())
458
+ else:
459
+ uvicorn.run("myapp.main:app", host="127.0.0.1", port=8000)
460
+ ```
461
+
462
+ ---
463
+
464
+ ## Symmetrical `Makefile` Integration
465
+
466
+ ```makefile
467
+ RIG ?= rig
468
+
469
+ up:
470
+ @$(RIG) up
471
+
472
+ down:
473
+ @$(RIG) down
474
+
475
+ status:
476
+ @$(RIG) status
477
+
478
+ ps:
479
+ @$(RIG) ps
480
+
481
+ check:
482
+ @$(RIG) check
483
+
484
+ logs:
485
+ @tail -n 200 -F .local-run/logs/*.log
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Development & Testing
491
+
492
+ ```bash
493
+ # Clone the repository
494
+ git clone https://github.com/evgesha9400/rig.git
495
+ cd rig
496
+
497
+ # Run full test suite with uv
498
+ uv run --with pytest pytest tests/
499
+ ```
500
+
501
+ ## License
502
+
503
+ MIT