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.
- rig_cli-1.0.0/.githooks/pre-commit +4 -0
- rig_cli-1.0.0/.github/workflows/publish.yml +80 -0
- rig_cli-1.0.0/.gitignore +10 -0
- rig_cli-1.0.0/.importlinter +13 -0
- rig_cli-1.0.0/.jscpd.json +13 -0
- rig_cli-1.0.0/Makefile +32 -0
- rig_cli-1.0.0/PKG-INFO +503 -0
- rig_cli-1.0.0/README.md +488 -0
- rig_cli-1.0.0/TASKS.md +103 -0
- rig_cli-1.0.0/examples/rig.json +26 -0
- rig_cli-1.0.0/examples/rig.mk +36 -0
- rig_cli-1.0.0/examples/stack.json +26 -0
- rig_cli-1.0.0/examples/stack.mk +37 -0
- rig_cli-1.0.0/pyproject.toml +47 -0
- rig_cli-1.0.0/rig.schema.json +159 -0
- rig_cli-1.0.0/ruff.toml +53 -0
- rig_cli-1.0.0/scripts/anti_tamper/__init__.py +5 -0
- rig_cli-1.0.0/scripts/anti_tamper/configs.py +32 -0
- rig_cli-1.0.0/scripts/anti_tamper/deps.py +85 -0
- rig_cli-1.0.0/scripts/anti_tamper/pragmas.py +56 -0
- rig_cli-1.0.0/scripts/anti_tamper/runner.py +46 -0
- rig_cli-1.0.0/scripts/anti_tamper/schemas.py +148 -0
- rig_cli-1.0.0/scripts/check_anti_tamper.py +15 -0
- rig_cli-1.0.0/scripts/check_cycles.py +69 -0
- rig_cli-1.0.0/scripts/check_density.py +107 -0
- rig_cli-1.0.0/scripts/check_lines.py +78 -0
- rig_cli-1.0.0/src/rig/__init__.py +6 -0
- rig_cli-1.0.0/src/rig/__main__.py +8 -0
- rig_cli-1.0.0/src/rig/cli.py +107 -0
- rig_cli-1.0.0/src/rig/commands/__init__.py +23 -0
- rig_cli-1.0.0/src/rig/commands/check.py +125 -0
- rig_cli-1.0.0/src/rig/commands/common.py +149 -0
- rig_cli-1.0.0/src/rig/commands/dispatch.py +87 -0
- rig_cli-1.0.0/src/rig/commands/down/__init__.py +139 -0
- rig_cli-1.0.0/src/rig/commands/down/runner.py +136 -0
- rig_cli-1.0.0/src/rig/commands/init.py +148 -0
- rig_cli-1.0.0/src/rig/commands/logs.py +146 -0
- rig_cli-1.0.0/src/rig/commands/prune.py +142 -0
- rig_cli-1.0.0/src/rig/commands/ps.py +149 -0
- rig_cli-1.0.0/src/rig/commands/status.py +149 -0
- rig_cli-1.0.0/src/rig/commands/up/__init__.py +111 -0
- rig_cli-1.0.0/src/rig/commands/up/context.py +36 -0
- rig_cli-1.0.0/src/rig/commands/up/loop.py +150 -0
- rig_cli-1.0.0/src/rig/commands/up/relink.py +88 -0
- rig_cli-1.0.0/src/rig/commands/up/rollback.py +49 -0
- rig_cli-1.0.0/src/rig/commands/up/runner.py +118 -0
- rig_cli-1.0.0/src/rig/commands/up/service.py +136 -0
- rig_cli-1.0.0/src/rig/compose/__init__.py +1 -0
- rig_cli-1.0.0/src/rig/compose/client.py +144 -0
- rig_cli-1.0.0/src/rig/compose/context.py +56 -0
- rig_cli-1.0.0/src/rig/compose/discovery.py +121 -0
- rig_cli-1.0.0/src/rig/compose/docker.py +117 -0
- rig_cli-1.0.0/src/rig/compose/starter.py +145 -0
- rig_cli-1.0.0/src/rig/compose/stopper.py +71 -0
- rig_cli-1.0.0/src/rig/compose/supervisor.py +78 -0
- rig_cli-1.0.0/src/rig/core/__init__.py +1 -0
- rig_cli-1.0.0/src/rig/core/constants.py +65 -0
- rig_cli-1.0.0/src/rig/core/env.py +83 -0
- rig_cli-1.0.0/src/rig/core/errors.py +74 -0
- rig_cli-1.0.0/src/rig/core/identity.py +141 -0
- rig_cli-1.0.0/src/rig/core/locks.py +112 -0
- rig_cli-1.0.0/src/rig/core/state.py +150 -0
- rig_cli-1.0.0/src/rig/core/terminal.py +145 -0
- rig_cli-1.0.0/src/rig/manifest/__init__.py +1 -0
- rig_cli-1.0.0/src/rig/manifest/detector.py +138 -0
- rig_cli-1.0.0/src/rig/manifest/inspect.py +18 -0
- rig_cli-1.0.0/src/rig/manifest/loader.py +146 -0
- rig_cli-1.0.0/src/rig/manifest/models.py +129 -0
- rig_cli-1.0.0/src/rig/manifest/parser.py +123 -0
- rig_cli-1.0.0/src/rig/manifest/schema.py +89 -0
- rig_cli-1.0.0/src/rig/net/__init__.py +1 -0
- rig_cli-1.0.0/src/rig/net/health.py +76 -0
- rig_cli-1.0.0/src/rig/net/ports.py +141 -0
- rig_cli-1.0.0/src/rig/net/probe.py +56 -0
- rig_cli-1.0.0/src/rig/net/registry.py +132 -0
- rig_cli-1.0.0/src/rig/parser.py +72 -0
- rig_cli-1.0.0/src/rig/proc/__init__.py +1 -0
- rig_cli-1.0.0/src/rig/proc/process.py +133 -0
- rig_cli-1.0.0/src/rig/proc/record.py +54 -0
- rig_cli-1.0.0/src/rig/proc/spawn.py +130 -0
- rig_cli-1.0.0/src/rig/proc/teardown.py +139 -0
- rig_cli-1.0.0/tests/commands/lifecycle/test_down_json_envelope.py +111 -0
- rig_cli-1.0.0/tests/commands/lifecycle/test_down_reclaim_compose.py +90 -0
- rig_cli-1.0.0/tests/commands/lifecycle/test_down_reclaim_orphan.py +116 -0
- rig_cli-1.0.0/tests/commands/lifecycle/test_down_reclaim_prune_ps.py +127 -0
- rig_cli-1.0.0/tests/commands/lifecycle/test_down_state_dependencies.py +102 -0
- rig_cli-1.0.0/tests/commands/lifecycle/test_down_unverifiable.py +117 -0
- rig_cli-1.0.0/tests/commands/lifecycle/test_up_recovery_ordering.py +135 -0
- rig_cli-1.0.0/tests/commands/lifecycle/test_up_recovery_scoped.py +126 -0
- rig_cli-1.0.0/tests/commands/retry/test_up_idempotent.py +149 -0
- rig_cli-1.0.0/tests/commands/retry/test_up_port_retry.py +111 -0
- rig_cli-1.0.0/tests/commands/retry/test_up_rollback.py +149 -0
- rig_cli-1.0.0/tests/commands/test_cmd_down.py +123 -0
- rig_cli-1.0.0/tests/commands/test_cmd_init_compose.py +65 -0
- rig_cli-1.0.0/tests/commands/test_cmd_init_schema.py +118 -0
- rig_cli-1.0.0/tests/commands/test_cmd_prune.py +149 -0
- rig_cli-1.0.0/tests/commands/test_cmd_ps_width.py +110 -0
- rig_cli-1.0.0/tests/commands/test_cmd_status.py +135 -0
- rig_cli-1.0.0/tests/commands/test_cmd_up.py +80 -0
- rig_cli-1.0.0/tests/commands/test_cmd_up_recovery.py +98 -0
- rig_cli-1.0.0/tests/commands/test_logs.py +150 -0
- rig_cli-1.0.0/tests/commands/test_scope_dispatch.py +77 -0
- rig_cli-1.0.0/tests/commands/test_surface.py +74 -0
- rig_cli-1.0.0/tests/compose/docker/test_client_settings.py +90 -0
- rig_cli-1.0.0/tests/compose/docker/test_context_pinning.py +131 -0
- rig_cli-1.0.0/tests/compose/docker/test_context_resolution.py +58 -0
- rig_cli-1.0.0/tests/compose/docker/test_context_switch.py +100 -0
- rig_cli-1.0.0/tests/compose/docker/test_endpoint.py +120 -0
- rig_cli-1.0.0/tests/compose/docker/test_endpoint_pinning.py +103 -0
- rig_cli-1.0.0/tests/compose/reclaim/test_compose_readiness.py +94 -0
- rig_cli-1.0.0/tests/compose/reclaim/test_compose_start_cleanup.py +89 -0
- rig_cli-1.0.0/tests/compose/reclaim/test_prune_reclaim.py +72 -0
- rig_cli-1.0.0/tests/compose/reclaim/test_replica_reclaim.py +112 -0
- rig_cli-1.0.0/tests/compose/reclaim/test_stop_record_fallback.py +102 -0
- rig_cli-1.0.0/tests/compose/status/test_client_status.py +141 -0
- rig_cli-1.0.0/tests/compose/status/test_container_status.py +111 -0
- rig_cli-1.0.0/tests/compose/status/test_docker_status_daemon.py +141 -0
- rig_cli-1.0.0/tests/compose/status/test_print_status.py +147 -0
- rig_cli-1.0.0/tests/compose/status/test_process_status.py +69 -0
- rig_cli-1.0.0/tests/compose/status/test_status_fallbacks.py +140 -0
- rig_cli-1.0.0/tests/compose/test_lifecycle.py +114 -0
- rig_cli-1.0.0/tests/compose/test_lifecycle_teardown.py +133 -0
- rig_cli-1.0.0/tests/compose/test_reclaim.py +140 -0
- rig_cli-1.0.0/tests/compose/test_required_vars.py +140 -0
- rig_cli-1.0.0/tests/compose/test_required_vars_teardown.py +112 -0
- rig_cli-1.0.0/tests/compose/test_scoping.py +58 -0
- rig_cli-1.0.0/tests/compose/test_service_env.py +124 -0
- rig_cli-1.0.0/tests/compose/test_startup_interrupt.py +122 -0
- rig_cli-1.0.0/tests/compose/test_startup_interrupt_cmd_up.py +86 -0
- rig_cli-1.0.0/tests/compose/test_up_interrupt.py +121 -0
- rig_cli-1.0.0/tests/conftest.py +41 -0
- rig_cli-1.0.0/tests/core/test_instance_id.py +46 -0
- rig_cli-1.0.0/tests/core/test_lock_contention.py +104 -0
- rig_cli-1.0.0/tests/core/test_lock_lifecycle.py +61 -0
- rig_cli-1.0.0/tests/core/test_runtime_dir.py +35 -0
- rig_cli-1.0.0/tests/core/test_state.py +76 -0
- rig_cli-1.0.0/tests/core/test_terminal.py +117 -0
- rig_cli-1.0.0/tests/manifest/deltalytic/test_bindings.py +67 -0
- rig_cli-1.0.0/tests/manifest/deltalytic/test_layout.py +46 -0
- rig_cli-1.0.0/tests/manifest/test_command_parsing.py +103 -0
- rig_cli-1.0.0/tests/manifest/test_healthcheck_and_ordering.py +84 -0
- rig_cli-1.0.0/tests/manifest/test_modes.py +100 -0
- rig_cli-1.0.0/tests/manifest/test_scopes.py +118 -0
- rig_cli-1.0.0/tests/manifest/test_validation.py +39 -0
- rig_cli-1.0.0/tests/net/test_central_port_registry.py +101 -0
- rig_cli-1.0.0/tests/net/test_friendly_ports.py +91 -0
- rig_cli-1.0.0/tests/net/test_listeners.py +43 -0
- rig_cli-1.0.0/tests/net/test_ports.py +47 -0
- rig_cli-1.0.0/tests/net/test_socket_transfer.py +114 -0
- rig_cli-1.0.0/tests/net/test_sticky_ports.py +106 -0
- rig_cli-1.0.0/tests/proc/test_env_isolation.py +98 -0
- rig_cli-1.0.0/tests/proc/test_external_tool_safety.py +148 -0
- rig_cli-1.0.0/tests/proc/test_health.py +104 -0
- rig_cli-1.0.0/tests/proc/test_identity.py +140 -0
- rig_cli-1.0.0/tests/proc/test_lifecycle_hygiene.py +99 -0
- rig_cli-1.0.0/tests/proc/test_mode_switch.py +87 -0
- rig_cli-1.0.0/tests/proc/test_recovery.py +139 -0
- rig_cli-1.0.0/tests/proc/test_runtime_recovery.py +139 -0
- rig_cli-1.0.0/tests/proc/test_supervisor.py +124 -0
- rig_cli-1.0.0/tests/proc/test_teardown.py +123 -0
- rig_cli-1.0.0/uv.lock +692 -0
|
@@ -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
|
rig_cli-1.0.0/.gitignore
ADDED
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
|