relayctl 0.1.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.
- relayctl-0.1.0/.github/workflows/ci.yml +94 -0
- relayctl-0.1.0/.github/workflows/release.yml +145 -0
- relayctl-0.1.0/.gitignore +11 -0
- relayctl-0.1.0/.relay.example.toml +15 -0
- relayctl-0.1.0/PKG-INFO +258 -0
- relayctl-0.1.0/README.md +250 -0
- relayctl-0.1.0/justfile +16 -0
- relayctl-0.1.0/pyproject.toml +29 -0
- relayctl-0.1.0/pyrightconfig.json +4 -0
- relayctl-0.1.0/scripts/smoke_fake.py +468 -0
- relayctl-0.1.0/src/relayctl/__init__.py +3 -0
- relayctl-0.1.0/src/relayctl/_remote_runner.py +254 -0
- relayctl-0.1.0/src/relayctl/argv_paths.py +300 -0
- relayctl-0.1.0/src/relayctl/cli.py +1212 -0
- relayctl-0.1.0/src/relayctl/config.py +689 -0
- relayctl-0.1.0/src/relayctl/conflict_policy.py +121 -0
- relayctl-0.1.0/src/relayctl/feedback.py +75 -0
- relayctl-0.1.0/src/relayctl/identity.py +43 -0
- relayctl-0.1.0/src/relayctl/locking.py +111 -0
- relayctl-0.1.0/src/relayctl/mutagen.py +425 -0
- relayctl-0.1.0/src/relayctl/pullback_plan.py +148 -0
- relayctl-0.1.0/src/relayctl/remote_execution.py +1337 -0
- relayctl-0.1.0/src/relayctl/remote_runner_manifest.py +207 -0
- relayctl-0.1.0/src/relayctl/root.py +73 -0
- relayctl-0.1.0/src/relayctl/setup_checks.py +422 -0
- relayctl-0.1.0/src/relayctl/ssh.py +172 -0
- relayctl-0.1.0/src/relayctl/startup_cache.py +289 -0
- relayctl-0.1.0/src/relayctl/transport.py +184 -0
- relayctl-0.1.0/src/relayctl/workspace.py +127 -0
- relayctl-0.1.0/tests/conftest.py +23 -0
- relayctl-0.1.0/tests/integration/test_fake_transport.py +1594 -0
- relayctl-0.1.0/tests/integration/test_init_cli.py +105 -0
- relayctl-0.1.0/tests/test_cli_help.py +309 -0
- relayctl-0.1.0/tests/test_config.py +429 -0
- relayctl-0.1.0/tests/test_config_errors.py +280 -0
- relayctl-0.1.0/tests/test_conflict_policy.py +87 -0
- relayctl-0.1.0/tests/test_docs_contract.py +90 -0
- relayctl-0.1.0/tests/test_exit_codes.py +103 -0
- relayctl-0.1.0/tests/test_init.py +569 -0
- relayctl-0.1.0/tests/test_locking.py +113 -0
- relayctl-0.1.0/tests/test_path_classification.py +186 -0
- relayctl-0.1.0/tests/test_path_rewrite.py +327 -0
- relayctl-0.1.0/tests/test_pullback_plan.py +103 -0
- relayctl-0.1.0/tests/test_relay_surface_audit.py +98 -0
- relayctl-0.1.0/tests/test_remote_execution.py +1986 -0
- relayctl-0.1.0/tests/test_remote_runner.py +81 -0
- relayctl-0.1.0/tests/test_remote_runner_manifest.py +188 -0
- relayctl-0.1.0/tests/test_root_detection.py +84 -0
- relayctl-0.1.0/tests/test_setup_checks.py +473 -0
- relayctl-0.1.0/tests/test_shell_mode.py +344 -0
- relayctl-0.1.0/tests/test_smoke_fake.py +339 -0
- relayctl-0.1.0/tests/test_ssh.py +230 -0
- relayctl-0.1.0/tests/test_symlink_policy.py +197 -0
- relayctl-0.1.0/tests/test_transport.py +70 -0
- relayctl-0.1.0/tests/test_workspace_identity.py +82 -0
- relayctl-0.1.0/uv.lock +117 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
name: Test
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Check out repository
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
|
|
26
|
+
- name: Set up uv
|
|
27
|
+
uses: astral-sh/setup-uv@v5
|
|
28
|
+
|
|
29
|
+
- name: Sync development environment
|
|
30
|
+
run: uv sync --group dev
|
|
31
|
+
|
|
32
|
+
- name: Run pytest
|
|
33
|
+
run: uv run pytest -q
|
|
34
|
+
|
|
35
|
+
build:
|
|
36
|
+
name: Build distributions
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- name: Check out repository
|
|
41
|
+
uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- name: Set up Python
|
|
44
|
+
uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.12"
|
|
47
|
+
|
|
48
|
+
- name: Set up uv
|
|
49
|
+
uses: astral-sh/setup-uv@v5
|
|
50
|
+
|
|
51
|
+
- name: Build wheel and sdist
|
|
52
|
+
run: uv build
|
|
53
|
+
|
|
54
|
+
- name: Upload distributions
|
|
55
|
+
if: always()
|
|
56
|
+
uses: actions/upload-artifact@v4
|
|
57
|
+
with:
|
|
58
|
+
name: relayctl-distributions
|
|
59
|
+
path: dist/
|
|
60
|
+
if-no-files-found: ignore
|
|
61
|
+
|
|
62
|
+
install-smoke:
|
|
63
|
+
name: Install smoke
|
|
64
|
+
needs:
|
|
65
|
+
- test
|
|
66
|
+
- build
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
|
|
69
|
+
steps:
|
|
70
|
+
- name: Set up Python
|
|
71
|
+
uses: actions/setup-python@v5
|
|
72
|
+
with:
|
|
73
|
+
python-version: "3.12"
|
|
74
|
+
|
|
75
|
+
- name: Download distributions
|
|
76
|
+
uses: actions/download-artifact@v4
|
|
77
|
+
with:
|
|
78
|
+
name: relayctl-distributions
|
|
79
|
+
path: dist/
|
|
80
|
+
|
|
81
|
+
- name: Install built wheel in clean virtualenv
|
|
82
|
+
run: python -m venv .venv-smoke
|
|
83
|
+
|
|
84
|
+
- name: Upgrade pip in clean virtualenv
|
|
85
|
+
run: .venv-smoke/bin/python -m pip install --upgrade pip
|
|
86
|
+
|
|
87
|
+
- name: Install built wheel
|
|
88
|
+
run: .venv-smoke/bin/python -m pip install dist/*.whl
|
|
89
|
+
|
|
90
|
+
- name: Verify wheel entrypoint help
|
|
91
|
+
run: .venv-smoke/bin/relayctl --help
|
|
92
|
+
|
|
93
|
+
- name: Verify doctor help from installed CLI
|
|
94
|
+
run: .venv-smoke/bin/relayctl doctor --help
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
tags:
|
|
7
|
+
- "v*.*.*b*"
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
name: Test
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Check out repository
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
|
|
26
|
+
- name: Set up uv
|
|
27
|
+
uses: astral-sh/setup-uv@v5
|
|
28
|
+
|
|
29
|
+
- name: Sync development environment
|
|
30
|
+
run: uv sync --group dev
|
|
31
|
+
|
|
32
|
+
- name: Run pytest
|
|
33
|
+
run: uv run pytest -q
|
|
34
|
+
|
|
35
|
+
build:
|
|
36
|
+
name: Build distributions
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- name: Check out repository
|
|
41
|
+
uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- name: Set up Python
|
|
44
|
+
uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.12"
|
|
47
|
+
|
|
48
|
+
- name: Set up uv
|
|
49
|
+
uses: astral-sh/setup-uv@v5
|
|
50
|
+
|
|
51
|
+
- name: Build wheel and sdist
|
|
52
|
+
run: uv build
|
|
53
|
+
|
|
54
|
+
- name: Upload distributions
|
|
55
|
+
if: always()
|
|
56
|
+
uses: actions/upload-artifact@v4
|
|
57
|
+
with:
|
|
58
|
+
name: relayctl-distributions
|
|
59
|
+
path: dist/
|
|
60
|
+
if-no-files-found: ignore
|
|
61
|
+
|
|
62
|
+
install-smoke:
|
|
63
|
+
name: Install smoke
|
|
64
|
+
needs:
|
|
65
|
+
- test
|
|
66
|
+
- build
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
|
|
69
|
+
steps:
|
|
70
|
+
- name: Set up Python
|
|
71
|
+
uses: actions/setup-python@v5
|
|
72
|
+
with:
|
|
73
|
+
python-version: "3.12"
|
|
74
|
+
|
|
75
|
+
- name: Download distributions
|
|
76
|
+
uses: actions/download-artifact@v4
|
|
77
|
+
with:
|
|
78
|
+
name: relayctl-distributions
|
|
79
|
+
path: dist/
|
|
80
|
+
|
|
81
|
+
- name: Install built wheel in clean virtualenv
|
|
82
|
+
run: python -m venv .venv-smoke
|
|
83
|
+
|
|
84
|
+
- name: Upgrade pip in clean virtualenv
|
|
85
|
+
run: .venv-smoke/bin/python -m pip install --upgrade pip
|
|
86
|
+
|
|
87
|
+
- name: Install built wheel
|
|
88
|
+
run: .venv-smoke/bin/python -m pip install dist/*.whl
|
|
89
|
+
|
|
90
|
+
- name: Verify wheel entrypoint help
|
|
91
|
+
run: .venv-smoke/bin/relayctl --help
|
|
92
|
+
|
|
93
|
+
- name: Verify doctor help from installed CLI
|
|
94
|
+
run: .venv-smoke/bin/relayctl doctor --help
|
|
95
|
+
|
|
96
|
+
release-dry-run:
|
|
97
|
+
name: Release dry run
|
|
98
|
+
if: github.event_name == 'workflow_dispatch'
|
|
99
|
+
needs:
|
|
100
|
+
- test
|
|
101
|
+
- build
|
|
102
|
+
- install-smoke
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
|
|
105
|
+
steps:
|
|
106
|
+
- name: Set up Python
|
|
107
|
+
uses: actions/setup-python@v5
|
|
108
|
+
with:
|
|
109
|
+
python-version: "3.12"
|
|
110
|
+
|
|
111
|
+
- name: Set up uv
|
|
112
|
+
uses: astral-sh/setup-uv@v5
|
|
113
|
+
|
|
114
|
+
- name: Download distributions
|
|
115
|
+
uses: actions/download-artifact@v4
|
|
116
|
+
with:
|
|
117
|
+
name: relayctl-distributions
|
|
118
|
+
path: dist/
|
|
119
|
+
|
|
120
|
+
- name: Validate release artifacts without publishing
|
|
121
|
+
run: uvx twine check dist/*
|
|
122
|
+
|
|
123
|
+
publish-pypi:
|
|
124
|
+
name: Publish beta to PyPI
|
|
125
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && contains(github.ref_name, 'b')
|
|
126
|
+
needs:
|
|
127
|
+
- test
|
|
128
|
+
- build
|
|
129
|
+
- install-smoke
|
|
130
|
+
runs-on: ubuntu-latest
|
|
131
|
+
environment:
|
|
132
|
+
name: pypi
|
|
133
|
+
url: https://pypi.org/p/relayctl
|
|
134
|
+
permissions:
|
|
135
|
+
id-token: write
|
|
136
|
+
|
|
137
|
+
steps:
|
|
138
|
+
- name: Download distributions
|
|
139
|
+
uses: actions/download-artifact@v4
|
|
140
|
+
with:
|
|
141
|
+
name: relayctl-distributions
|
|
142
|
+
path: dist/
|
|
143
|
+
|
|
144
|
+
- name: Publish distributions to PyPI
|
|
145
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[remote]
|
|
2
|
+
host = "user@example.com"
|
|
3
|
+
workspace_base = "~/.relay/workspaces"
|
|
4
|
+
shell = "/bin/sh"
|
|
5
|
+
profile = "~/.profile"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
root = "."
|
|
9
|
+
exclude = [".git/", ".relay/", ".DS_Store", "__pycache__/", ".pytest_cache/"]
|
|
10
|
+
|
|
11
|
+
[pull]
|
|
12
|
+
enabled = true
|
|
13
|
+
conflict_dir = ".relay/conflicts"
|
|
14
|
+
|
|
15
|
+
# Relay uses .relay/ local runtime state only. Any stale .seas/ directory is ignored.
|
relayctl-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: relayctl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Relay: local-first remote execution over SSH with managed workspace sync
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: rich>=13.9.0
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# Relay
|
|
10
|
+
|
|
11
|
+
`Relay` is a local-first remote execution tool for any SSH-accessible host. It makes a remote workspace feel local by handling workspace sync, path mapping, staged transfers, and remote command execution for you.
|
|
12
|
+
|
|
13
|
+
Use it when you want to keep editing locally but run builds, tests, or tools on a remote Linux machine.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Relay is a Python-based CLI that depends on several external tools. We recommend installing it using `pipx` or `uv tool` to keep it isolated from your system Python.
|
|
18
|
+
|
|
19
|
+
### 1. Install external prerequisites
|
|
20
|
+
|
|
21
|
+
Relay does not install these for you. Ensure they are available in your `PATH`:
|
|
22
|
+
|
|
23
|
+
- **Local machine**: `ssh`, `mutagen`, and `rsync`.
|
|
24
|
+
- **Remote host**: `/bin/sh`, `python3`, and `rsync`.
|
|
25
|
+
|
|
26
|
+
See the [Prerequisites](#prerequisites) section for details.
|
|
27
|
+
|
|
28
|
+
### 2. Install Relay
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Using uv (recommended)
|
|
32
|
+
uv tool install relayctl
|
|
33
|
+
|
|
34
|
+
# Using pipx
|
|
35
|
+
pipx install relayctl
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 3. Verify installation
|
|
39
|
+
|
|
40
|
+
Run the doctor command to check your environment:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
relayctl doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If you have leftover `seas` artifacts (like a `.seas.toml` config or a `.seas/` directory), `relayctl` will report them as cleanup issues. These legacy surfaces are unsupported and must be removed or renamed before you can use Relay.
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
1. Ensure [prerequisites](#prerequisites) are met.
|
|
51
|
+
2. Install `relayctl` via `pipx` or `uv tool`.
|
|
52
|
+
3. Run `relayctl init` in your project root and answer the setup prompt.
|
|
53
|
+
4. Run a command: `relayctl -- pwd` (the `--` separator is required).
|
|
54
|
+
|
|
55
|
+
By default, `relayctl`, `relayctl --shell`, and related top-level commands print live progress updates to stderr as they move through setup, sync, upload, execution, and pullback. Add `--verbose` to see the individual underlying command steps as well.
|
|
56
|
+
|
|
57
|
+
## Configuration (`.relay.toml`)
|
|
58
|
+
|
|
59
|
+
Place a `.relay.toml` file in your project root to configure the remote host and sync behavior. `relayctl init` can create it interactively, and you can also start from `.relay.example.toml`.
|
|
60
|
+
|
|
61
|
+
```toml
|
|
62
|
+
[remote]
|
|
63
|
+
host = "user@example.com"
|
|
64
|
+
workspace_base = "~/.relay/workspaces"
|
|
65
|
+
shell = "/bin/sh"
|
|
66
|
+
profile = "~/.profile"
|
|
67
|
+
|
|
68
|
+
[project]
|
|
69
|
+
root = "."
|
|
70
|
+
exclude = [".git/", ".relay/", ".DS_Store", "__pycache__/", ".pytest_cache/"]
|
|
71
|
+
|
|
72
|
+
[pull]
|
|
73
|
+
enabled = true
|
|
74
|
+
conflict_dir = ".relay/conflicts"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`[project]` controls what gets mirrored into the managed remote workspace. Keep machine-local or generated files in `exclude` so Mutagen does not try to sync them.
|
|
78
|
+
|
|
79
|
+
The canonical global config path is `~/.config/relay/config.toml`. If an older `.seas.toml` or `~/.config/seas/config.toml` is still present, `relayctl` fails fast and asks you to rename or delete the legacy file before continuing.
|
|
80
|
+
|
|
81
|
+
If a stale local `.seas/` runtime directory is still present, `relayctl` warns and ignores it. Relay only creates and uses `.relay/` runtime state.
|
|
82
|
+
|
|
83
|
+
### Root Detection
|
|
84
|
+
`relayctl` finds your project root by looking for the nearest `.git` directory. If no `.git` directory is found, it defaults to the current working directory. You can override this with the `--root` flag.
|
|
85
|
+
|
|
86
|
+
## Workspace Synchronization
|
|
87
|
+
|
|
88
|
+
`Relay` uses a managed Mutagen workspace for continuous, high-performance file synchronization. This is the only supported synchronization model.
|
|
89
|
+
|
|
90
|
+
### Key Characteristics
|
|
91
|
+
- **Automatic Session Management**: `relayctl` automatically creates and manages a labeled Mutagen session for your project on the first run.
|
|
92
|
+
- **Derived Remote Workspace**: The remote workspace path is derived automatically from `remote.workspace_base` and a unique workspace identity. You do not need to configure a manual remote directory.
|
|
93
|
+
- **Sync Ownership**: Mutagen owns the synchronization of all files within the project root. Relay-native conflict handling and pullback do not apply to in-project files.
|
|
94
|
+
- **External Path Staging**: `relayctl` still handles the staging of absolute paths from outside your project (e.g., `/tmp/data.txt` in your command argv) using `rsync`.
|
|
95
|
+
- **Drift Handling**: If the managed session's configuration (host, paths, ignores) changes, `relayctl` will detect the "drift". In interactive terminals, it will prompt to recreate the session; in non-interactive environments, it will fail with a clear error.
|
|
96
|
+
|
|
97
|
+
### Lifecycle Commands
|
|
98
|
+
Use `relayctl mutagen` to inspect and control the managed session:
|
|
99
|
+
- `relayctl mutagen status`: Show the current session status (absent, healthy, or drifted).
|
|
100
|
+
- `relayctl mutagen flush`: Force a synchronization cycle (useful before running a command if you just saved a file).
|
|
101
|
+
- `relayctl mutagen reset`: Manually recreate the session (requires interactive confirmation).
|
|
102
|
+
- `relayctl mutagen terminate`: Permanently remove the managed session for the current project.
|
|
103
|
+
|
|
104
|
+
## testing
|
|
105
|
+
|
|
106
|
+
Use the repo's existing commands when validating changes:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uv run pytest -q
|
|
110
|
+
just test
|
|
111
|
+
just smoke-fake
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`just smoke-remote` is available for a real-host smoke test, but it requires a valid local `.relay.toml` and a reachable SSH target.
|
|
115
|
+
|
|
116
|
+
## Release automation
|
|
117
|
+
|
|
118
|
+
GitHub Actions now verifies Relay in two stages:
|
|
119
|
+
|
|
120
|
+
- `CI` runs `uv run pytest -q`, builds distributions with `uv build`, and install-smokes the built wheel in a clean virtualenv by invoking the installed `relayctl` entrypoint.
|
|
121
|
+
- `Release` re-runs the same verification before any release action.
|
|
122
|
+
|
|
123
|
+
Use the `Release` workflow's manual `workflow_dispatch` path for a non-publishing beta dry run. Actual PyPI publishing is reserved for beta tags matching `v*.*.*b*`, and the publish job is gated behind the test, build, and install-smoke jobs with trusted publishing enabled through GitHub's `pypi` environment.
|
|
124
|
+
|
|
125
|
+
Before the first real beta publish, register `.github/workflows/release.yml` as a trusted publisher for the `relayctl` project on PyPI and protect the repository's `pypi` environment so release approval stays gated.
|
|
126
|
+
|
|
127
|
+
## Usage
|
|
128
|
+
|
|
129
|
+
### Root command model
|
|
130
|
+
Use bare `relayctl -- ...` for argv-safe execution, and explicit top-level subcommands when you want a different flow:
|
|
131
|
+
|
|
132
|
+
- `relayctl -- CMD [ARG ...]`: primary bare argv mode with automatic path rewriting.
|
|
133
|
+
- `relayctl --shell -- 'SHELL TEXT'`: explicit shell-text mode (shorthand `-s`).
|
|
134
|
+
- `relayctl ssh`: open an interactive shell in the managed workspace.
|
|
135
|
+
- `relayctl init`: write project config.
|
|
136
|
+
- `relayctl mutagen`: inspect and control the managed Mutagen session.
|
|
137
|
+
- `relayctl doctor`: check local prerequisites and stale legacy artifacts, plus remote prerequisites when a concrete host is configured.
|
|
138
|
+
|
|
139
|
+
If you type an ambiguous root command like `relayctl echo ok`, Relay will ask you to choose between `relayctl -- echo ok` and `relayctl --shell -- 'echo ok'`.
|
|
140
|
+
|
|
141
|
+
### `relayctl doctor`
|
|
142
|
+
Use `relayctl doctor` to run local diagnostics before your first real remote run or when troubleshooting an environment. When a concrete host is configured, doctor also checks the remote prerequisite contract.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
relayctl doctor
|
|
146
|
+
relayctl doctor --json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Doctor always checks local `ssh`, Mutagen usability, and conditional `rsync` support. It also reports stale local `.seas/` directories and matching active legacy Seas-managed Mutagen sessions as cleanup issues. If your config still uses the placeholder host, doctor stays usable for local-only diagnostics and skips remote checks. If a concrete host is configured, doctor also verifies remote `/bin/sh`, `python3`, and staged-transfer `rsync` support. It never tries to install OS packages for you.
|
|
150
|
+
|
|
151
|
+
### `relayctl` (Bare Argv Mode)
|
|
152
|
+
Use bare `relayctl -- ...` for standard commands where you want automatic path rewriting (argv).
|
|
153
|
+
|
|
154
|
+
`relayctl` requires an explicit `--` separator before command argv. If omitted, argparse exits with a plain usage error.
|
|
155
|
+
|
|
156
|
+
Relative argv paths are resolved against your local cwd first. If the resolved path stays inside the project root, Relay rewrites it into the mirrored remote workspace; if it resolves outside the root, Relay stages it as a local external path. Use `remote:` explicitly when you want a relative token to stay relative to the remote cwd instead.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
relayctl -- make test
|
|
160
|
+
relayctl -- python3 -m pytest -q
|
|
161
|
+
relayctl --verbose -- make build
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Deterministic Path Rules:**
|
|
165
|
+
|
|
166
|
+
| Input Pattern | Interpretation | Remote Result |
|
|
167
|
+
| :--- | :--- | :--- |
|
|
168
|
+
| `relative/path` | Resolve against local CWD first | Mirror rewrite if in root; otherwise staged as local external |
|
|
169
|
+
| `../outside/path` | Relative path resolving outside project root | Staged to remote slot and rewritten |
|
|
170
|
+
| `/abs/path/in/root` | Inside project root | Mapped to remote mirror path |
|
|
171
|
+
| `/abs/path/outside` | Outside project root | Staged to remote slot and rewritten |
|
|
172
|
+
| `local:relative/or/absolute` | Force local interpretation | Resolve locally, then apply mirror/staging rules |
|
|
173
|
+
| `remote:relative/or/absolute` | Force literal remote path | Passed through unchanged |
|
|
174
|
+
|
|
175
|
+
- **Symlinks** that escape the project root are rejected for safety.
|
|
176
|
+
- **Attached forms** (e.g., `--flag=/tmp/x`) are not rewritten in this version.
|
|
177
|
+
|
|
178
|
+
> **Note on Redirection**: Shell features like `< input.txt` or `| grep ...` are handled by your *local* shell before `relayctl` runs. To use these features on the remote host, use `relayctl --shell`.
|
|
179
|
+
|
|
180
|
+
### `relayctl --shell` (Shell Mode)
|
|
181
|
+
Use `relayctl --shell` (or `-s`) when you need complex shell features like pipes, redirects, or multiple commands in a single string (shell-mode).
|
|
182
|
+
|
|
183
|
+
`relayctl --shell` requires an explicit `--` separator before shell text. If omitted, argparse exits with a plain usage error.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
relayctl --shell -- 'g++ -o main main.cpp && ./main < input.txt'
|
|
187
|
+
relayctl --shell --verbose -- 'g++ -o main main.cpp && ./main < input.txt'
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Guardrails and limitations:**
|
|
191
|
+
- **No shell-text parsing**: `relayctl --shell` does not look inside your shell string to rewrite paths (no shell-text parsing).
|
|
192
|
+
- **Explicit staging**: If you need files from outside your project, use the `--stage` flag (explicit --stage).
|
|
193
|
+
- **Environment**: `relayctl --shell` exports `RELAY_STAGE_DIR`, `RELAY_REMOTE_ROOT`, `RELAY_REMOTE_CWD`, and `RELAY_RUN_ID` to the remote environment.
|
|
194
|
+
- **Legacy runtime state**: stale local `.seas/` directories are ignored with a warning and are never migrated into `.relay/`.
|
|
195
|
+
- **limitations**: `relayctl --shell` does not support automatic path rewriting; all paths must be relative to the project root or explicitly staged.
|
|
196
|
+
|
|
197
|
+
### `relayctl ssh` (Interactive Mode)
|
|
198
|
+
Use `relayctl ssh` to open an interactive shell in your remote workspace.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
relayctl ssh
|
|
202
|
+
relayctl ssh --workdir remote:/tmp
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Guardrails:**
|
|
206
|
+
- **Interactive-only**: `relayctl ssh` does not support a command tail or shell-text payload.
|
|
207
|
+
|
|
208
|
+
### Shared Flags
|
|
209
|
+
- `--workdir PATH`: Set the remote working directory. Supports `remote:/abs/path` for literal remote paths. Available for bare `relayctl`, `relayctl --shell`, and `relayctl ssh`.
|
|
210
|
+
- `--env KEY=VAL`: Set a remote environment variable. Can be repeated. Keys starting with `RELAY_` are reserved. Available for bare `relayctl` and `relayctl --shell`.
|
|
211
|
+
|
|
212
|
+
## Conflict Handling (Staged External Paths)
|
|
213
|
+
|
|
214
|
+
For absolute paths outside your project root (staged external paths), `relayctl` automatically pulls changed files back to your local machine after the command finishes.
|
|
215
|
+
|
|
216
|
+
If a local file was modified while the remote command was running, `relayctl` will not overwrite it. Instead:
|
|
217
|
+
1. The remote version is saved in `.relay/conflicts/<run-id>/`.
|
|
218
|
+
2. A conflict summary is printed.
|
|
219
|
+
3. `relayctl` exits with code `92`.
|
|
220
|
+
|
|
221
|
+
Files within the project root are managed by Mutagen, which handles synchronization and conflict resolution continuously.
|
|
222
|
+
|
|
223
|
+
## exit code
|
|
224
|
+
|
|
225
|
+
- `0-255`: Remote command exit code is returned unchanged unless a wrapper-reserved condition below applies.
|
|
226
|
+
- `90`: Local setup/config/runtime/report error.
|
|
227
|
+
- `91`: Workspace lock timeout (prevents concurrent mirror corruption).
|
|
228
|
+
- `92`: Pull conflict detected after a successful remote command.
|
|
229
|
+
|
|
230
|
+
## feedback
|
|
231
|
+
|
|
232
|
+
- **Default live feedback**: concise phase updates are printed to stderr so you can see progress before the remote command starts producing output.
|
|
233
|
+
- **Verbose mode**: pass `--verbose` to print the underlying command steps in addition to the standard phase updates.
|
|
234
|
+
- **Remote command output**: stdout and stderr from the remote command still stream normally; progress text stays on stderr.
|
|
235
|
+
|
|
236
|
+
## troubleshooting
|
|
237
|
+
|
|
238
|
+
- **Locking**: If a previous run crashed, you might need to manually remove the `.lock` file in the remote workspace directory.
|
|
239
|
+
- **Excludes**: Check your `[project].exclude` list if files aren't appearing on the remote.
|
|
240
|
+
- **SSH/Rsync**: Ensure you have SSH keys set up for passwordless login to the remote host. If you use staged external paths or `--stage`, make sure `rsync` exists on both the local and remote machines.
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
## workflow
|
|
244
|
+
|
|
245
|
+
**Example workflows:**
|
|
246
|
+
```bash
|
|
247
|
+
# Run a normal command with argv-safe path handling
|
|
248
|
+
relayctl -- make test
|
|
249
|
+
|
|
250
|
+
# Run a shell pipeline remotely
|
|
251
|
+
relayctl --shell -- 'make build && ./bin/app < input.txt | tee output.txt'
|
|
252
|
+
|
|
253
|
+
# Stage an external local file for one remote run
|
|
254
|
+
relayctl --shell --stage /tmp/data.csv -- 'python3 scripts/process.py "$RELAY_STAGE_DIR/1/data.csv"'
|
|
255
|
+
|
|
256
|
+
# Interactive session
|
|
257
|
+
relayctl ssh
|
|
258
|
+
```
|