luv-cli 0.0.25__tar.gz → 0.2.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.
- {luv_cli-0.0.25 → luv_cli-0.2.0}/PKG-INFO +88 -6
- {luv_cli-0.0.25 → luv_cli-0.2.0}/README.md +87 -5
- luv_cli-0.2.0/docs/configuration.md +140 -0
- luv_cli-0.2.0/docs/remote-sessions.md +356 -0
- luv_cli-0.2.0/docs/sessions.md +212 -0
- luv_cli-0.2.0/luv/__init__.py +2015 -0
- {luv_cli-0.0.25 → luv_cli-0.2.0}/pyproject.toml +1 -1
- luv_cli-0.2.0/tests/test_agents.py +846 -0
- luv_cli-0.0.25/luv/__init__.py +0 -901
- luv_cli-0.0.25/tests/test_agents.py +0 -74
- {luv_cli-0.0.25 → luv_cli-0.2.0}/.claude/settings.json +0 -0
- {luv_cli-0.0.25 → luv_cli-0.2.0}/.failproofai/policies-config.json +0 -0
- {luv_cli-0.0.25 → luv_cli-0.2.0}/.github/workflows/publish.yml +0 -0
- {luv_cli-0.0.25 → luv_cli-0.2.0}/.gitignore +0 -0
- {luv_cli-0.0.25 → luv_cli-0.2.0}/LICENSE +0 -0
- {luv_cli-0.0.25 → luv_cli-0.2.0}/claude-output.txt +0 -0
- {luv_cli-0.0.25 → luv_cli-0.2.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: luv-cli
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Launch Claude Code or Codex agents on GitHub repos with isolated workspaces and optional Docker dev environments
|
|
5
5
|
Project-URL: Homepage, https://github.com/exospherehost/luv
|
|
6
6
|
Project-URL: Repository, https://github.com/exospherehost/luv
|
|
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
|
|
26
26
|
A CLI that launches Claude Code or Codex agents on GitHub repos with isolated workspaces and optional Docker dev environments.
|
|
27
27
|
|
|
28
|
-
`luv` clones a repo, creates a branch, and drops you into a Claude session ready to work. When the repo ships a `.luv/settings.json`, it spins up Docker Compose automatically so every command runs in the right environment.
|
|
28
|
+
`luv` clones a repo, creates a branch, and drops you into a Claude session ready to work. When the repo ships a `.luv/settings.json`, it spins up Docker Compose automatically so every command runs in the right environment. Point it at a remote machine and the whole thing runs there in a tmux session you can detach from and reattach to later.
|
|
29
29
|
|
|
30
30
|
## Install
|
|
31
31
|
|
|
@@ -42,8 +42,8 @@ pip install luv-cli
|
|
|
42
42
|
## Quick start
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
#
|
|
46
|
-
luv
|
|
45
|
+
# One-time setup: default GitHub org, and optionally a remote machine
|
|
46
|
+
luv config
|
|
47
47
|
|
|
48
48
|
# Create a new workspace and launch Claude
|
|
49
49
|
luv my-repo "add user authentication"
|
|
@@ -74,6 +74,14 @@ luv -r my-repo 42
|
|
|
74
74
|
|
|
75
75
|
# Clean up fully-merged workspaces
|
|
76
76
|
luv --clean
|
|
77
|
+
|
|
78
|
+
# Tear down a finished session: kill its tmux, delete its workspace
|
|
79
|
+
luv rm myrepo-42
|
|
80
|
+
luv rm --merged # everything whose PR has landed
|
|
81
|
+
|
|
82
|
+
# See what's running on your remote machine, and reattach
|
|
83
|
+
luv ls
|
|
84
|
+
luv continue
|
|
77
85
|
```
|
|
78
86
|
|
|
79
87
|
## How it works
|
|
@@ -85,11 +93,21 @@ luv --clean
|
|
|
85
93
|
|
|
86
94
|
All workspaces live under `~/prs/`. The number comes from the repo's GitHub issue counter to avoid collisions.
|
|
87
95
|
|
|
96
|
+
With a remote host configured, steps 1–4 happen on that machine inside a tmux session instead — see [Remote sessions](#remote-sessions).
|
|
97
|
+
|
|
88
98
|
## Commands
|
|
89
99
|
|
|
90
100
|
| Command | Description |
|
|
91
101
|
|---------|-------------|
|
|
92
|
-
| `luv
|
|
102
|
+
| `luv config` | Interactive setup: remote host, SSH key, default org |
|
|
103
|
+
| `luv config set\|get\|unset <key> [value]` | Read or write a single setting |
|
|
104
|
+
| `luv config list` | Show all settings |
|
|
105
|
+
| `luv --init` | Configure default GitHub org only |
|
|
106
|
+
| `luv ls [--host H] [--prune] [--no-pr]` | List live sessions across hosts, with each one's PR link |
|
|
107
|
+
| `luv continue [<repo> [number]]` | Attach to a live session |
|
|
108
|
+
| `luv rm <session\|workspace>...` | Kill a session and delete its workspace on its host |
|
|
109
|
+
| `luv rm --merged [--host H] [-f]` | Remove every session whose PR is merged |
|
|
110
|
+
| `luv rm --dead [--host H] [-f]` | Remove workspaces with no live session |
|
|
93
111
|
| `luv [org/]<repo> [prompt...]` | Create a new workspace and launch Claude (default) |
|
|
94
112
|
| `luv --codex [org/]<repo> [prompt...]` | Create a workspace and launch Codex in YOLO mode |
|
|
95
113
|
| `luv [org/]<repo> -b <branch> [prompt...]` | Create a workspace based off `<branch>` instead of the default |
|
|
@@ -113,6 +131,9 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
|
|
|
113
131
|
| `-m MODEL` | Model to use; Claude defaults to `claude-opus-5`, while Codex uses its configured CLI default |
|
|
114
132
|
| `-b BRANCH` | Base a new workspace off `BRANCH` (clone + branch from it); recorded in `git config luv.base` so the PR can target it |
|
|
115
133
|
| `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
|
|
134
|
+
| `-s HOST` | Run on `HOST` over SSH, overriding the configured remote host |
|
|
135
|
+
| `-i PATH` | SSH identity file to use for this invocation |
|
|
136
|
+
| `--local` | Force local execution even when a remote host is configured |
|
|
116
137
|
| `-f`, `--force` | Skip safety checks (with `--clean`) |
|
|
117
138
|
| `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
|
|
118
139
|
|
|
@@ -166,6 +187,24 @@ The `dev-environment` service **must** have the selected agent CLI (`claude` or
|
|
|
166
187
|
|
|
167
188
|
Docker mode works with all flags: `-n` opens a bash shell in the container, `-r` resumes a Claude session in the container.
|
|
168
189
|
|
|
190
|
+
## Remote sessions
|
|
191
|
+
|
|
192
|
+
Point `luv` at another machine and every workspace command runs there, inside a tmux session that survives disconnects.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
luv config # set the remote host, SSH key, and org
|
|
196
|
+
luv myrepo "fix the flaky test" # clones and launches Claude on the remote
|
|
197
|
+
# ...close the laptop, go home...
|
|
198
|
+
luv ls # see what's still running
|
|
199
|
+
luv continue # reattach exactly where you left off
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`luv` re-invokes itself on the remote over SSH, so every flag works there unchanged. The remote needs `luv`, `tmux`, `gh`, and `git` on its `PATH`.
|
|
203
|
+
|
|
204
|
+
Use `--local` for a one-off local run, `-s HOST` to target a different machine, and `-i PATH` for a different SSH key.
|
|
205
|
+
|
|
206
|
+
Full guide, including how to prepare a fresh Ubuntu box and set up SSH keys: **[docs/remote-sessions.md](docs/remote-sessions.md)**.
|
|
207
|
+
|
|
169
208
|
## Workspace cleanup
|
|
170
209
|
|
|
171
210
|
`luv --clean` scans `~/prs/` and safely removes workspaces that are fully pushed. It checks:
|
|
@@ -176,12 +215,55 @@ Docker mode works with all flags: `-n` opens a bash shell in the container, `-r`
|
|
|
176
215
|
|
|
177
216
|
Use `luv --clean -f` to skip all safety checks and delete everything. Add `--safe` (i.e. `luv --clean --safe -f`) to restrict force-delete to workspaces whose folder mtime is older than 24 hours, leaving recently-touched workspaces alone.
|
|
178
217
|
|
|
218
|
+
Workspaces with a live tmux session are skipped unless you pass `-f`.
|
|
219
|
+
|
|
220
|
+
### Removing a specific session
|
|
221
|
+
|
|
222
|
+
`luv --clean` is a garbage collector: it decides for itself what is safe to
|
|
223
|
+
delete, and never touches a workspace with a live session. `luv rm` is the
|
|
224
|
+
opposite — you name what goes, and it goes, on whichever host it lives on.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
luv rm myrepo-42 # kill its tmux session, delete ~/prs/myrepo-42 on its host
|
|
228
|
+
luv rm luv-myrepo-42 # the session name works too
|
|
229
|
+
luv rm --merged # every session whose PR has been merged
|
|
230
|
+
luv rm --dead # workspaces left behind by sessions that already exited
|
|
231
|
+
luv rm --dead --host box # scope either selector to one machine
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
A named target is taken as intent and removed without further checks —
|
|
235
|
+
including any uncommitted work in it. The `--merged` and `--dead` selectors
|
|
236
|
+
list what they matched and ask first; `-f` skips that prompt.
|
|
237
|
+
|
|
238
|
+
`--dead` is the counterpart to `luv ls`: reconciliation drops a session from the
|
|
239
|
+
registry as soon as its tmux session exits, but the clone it was working in
|
|
240
|
+
stays on the remote disk. Those orphans are invisible to `luv ls` and are
|
|
241
|
+
usually what is actually filling up the box.
|
|
242
|
+
|
|
243
|
+
Only folders named `{repo}-{N}` are ever eligible — anything else in the
|
|
244
|
+
workspace root is left alone, and a target that doesn't resolve to one is an
|
|
245
|
+
error rather than an `rm -rf`.
|
|
246
|
+
|
|
179
247
|
## Configuration
|
|
180
248
|
|
|
181
|
-
Run `luv --init` to set your default GitHub org.
|
|
249
|
+
Run `luv config` for interactive setup, or `luv --init` to set just your default GitHub org. Both save to `~/.luv/config.json`:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"org": "exosphere",
|
|
254
|
+
"prs_dir": "~/prs",
|
|
255
|
+
"remote": {
|
|
256
|
+
"host": "box",
|
|
257
|
+
"identity_file": "~/.ssh/id_ed25519",
|
|
258
|
+
"dir": "~/prs"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
182
262
|
|
|
183
263
|
You can also pass `org/repo` inline to override the default for any command (e.g., `luv other-org/my-repo`).
|
|
184
264
|
|
|
265
|
+
Every key, with defaults and resolution order: **[docs/configuration.md](docs/configuration.md)**. The session registry that backs `luv ls`: **[docs/sessions.md](docs/sessions.md)**.
|
|
266
|
+
|
|
185
267
|
## License
|
|
186
268
|
|
|
187
269
|
MIT
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A CLI that launches Claude Code or Codex agents on GitHub repos with isolated workspaces and optional Docker dev environments.
|
|
4
4
|
|
|
5
|
-
`luv` clones a repo, creates a branch, and drops you into a Claude session ready to work. When the repo ships a `.luv/settings.json`, it spins up Docker Compose automatically so every command runs in the right environment.
|
|
5
|
+
`luv` clones a repo, creates a branch, and drops you into a Claude session ready to work. When the repo ships a `.luv/settings.json`, it spins up Docker Compose automatically so every command runs in the right environment. Point it at a remote machine and the whole thing runs there in a tmux session you can detach from and reattach to later.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -19,8 +19,8 @@ pip install luv-cli
|
|
|
19
19
|
## Quick start
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
#
|
|
23
|
-
luv
|
|
22
|
+
# One-time setup: default GitHub org, and optionally a remote machine
|
|
23
|
+
luv config
|
|
24
24
|
|
|
25
25
|
# Create a new workspace and launch Claude
|
|
26
26
|
luv my-repo "add user authentication"
|
|
@@ -51,6 +51,14 @@ luv -r my-repo 42
|
|
|
51
51
|
|
|
52
52
|
# Clean up fully-merged workspaces
|
|
53
53
|
luv --clean
|
|
54
|
+
|
|
55
|
+
# Tear down a finished session: kill its tmux, delete its workspace
|
|
56
|
+
luv rm myrepo-42
|
|
57
|
+
luv rm --merged # everything whose PR has landed
|
|
58
|
+
|
|
59
|
+
# See what's running on your remote machine, and reattach
|
|
60
|
+
luv ls
|
|
61
|
+
luv continue
|
|
54
62
|
```
|
|
55
63
|
|
|
56
64
|
## How it works
|
|
@@ -62,11 +70,21 @@ luv --clean
|
|
|
62
70
|
|
|
63
71
|
All workspaces live under `~/prs/`. The number comes from the repo's GitHub issue counter to avoid collisions.
|
|
64
72
|
|
|
73
|
+
With a remote host configured, steps 1–4 happen on that machine inside a tmux session instead — see [Remote sessions](#remote-sessions).
|
|
74
|
+
|
|
65
75
|
## Commands
|
|
66
76
|
|
|
67
77
|
| Command | Description |
|
|
68
78
|
|---------|-------------|
|
|
69
|
-
| `luv
|
|
79
|
+
| `luv config` | Interactive setup: remote host, SSH key, default org |
|
|
80
|
+
| `luv config set\|get\|unset <key> [value]` | Read or write a single setting |
|
|
81
|
+
| `luv config list` | Show all settings |
|
|
82
|
+
| `luv --init` | Configure default GitHub org only |
|
|
83
|
+
| `luv ls [--host H] [--prune] [--no-pr]` | List live sessions across hosts, with each one's PR link |
|
|
84
|
+
| `luv continue [<repo> [number]]` | Attach to a live session |
|
|
85
|
+
| `luv rm <session\|workspace>...` | Kill a session and delete its workspace on its host |
|
|
86
|
+
| `luv rm --merged [--host H] [-f]` | Remove every session whose PR is merged |
|
|
87
|
+
| `luv rm --dead [--host H] [-f]` | Remove workspaces with no live session |
|
|
70
88
|
| `luv [org/]<repo> [prompt...]` | Create a new workspace and launch Claude (default) |
|
|
71
89
|
| `luv --codex [org/]<repo> [prompt...]` | Create a workspace and launch Codex in YOLO mode |
|
|
72
90
|
| `luv [org/]<repo> -b <branch> [prompt...]` | Create a workspace based off `<branch>` instead of the default |
|
|
@@ -90,6 +108,9 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
|
|
|
90
108
|
| `-m MODEL` | Model to use; Claude defaults to `claude-opus-5`, while Codex uses its configured CLI default |
|
|
91
109
|
| `-b BRANCH` | Base a new workspace off `BRANCH` (clone + branch from it); recorded in `git config luv.base` so the PR can target it |
|
|
92
110
|
| `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
|
|
111
|
+
| `-s HOST` | Run on `HOST` over SSH, overriding the configured remote host |
|
|
112
|
+
| `-i PATH` | SSH identity file to use for this invocation |
|
|
113
|
+
| `--local` | Force local execution even when a remote host is configured |
|
|
93
114
|
| `-f`, `--force` | Skip safety checks (with `--clean`) |
|
|
94
115
|
| `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
|
|
95
116
|
|
|
@@ -143,6 +164,24 @@ The `dev-environment` service **must** have the selected agent CLI (`claude` or
|
|
|
143
164
|
|
|
144
165
|
Docker mode works with all flags: `-n` opens a bash shell in the container, `-r` resumes a Claude session in the container.
|
|
145
166
|
|
|
167
|
+
## Remote sessions
|
|
168
|
+
|
|
169
|
+
Point `luv` at another machine and every workspace command runs there, inside a tmux session that survives disconnects.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
luv config # set the remote host, SSH key, and org
|
|
173
|
+
luv myrepo "fix the flaky test" # clones and launches Claude on the remote
|
|
174
|
+
# ...close the laptop, go home...
|
|
175
|
+
luv ls # see what's still running
|
|
176
|
+
luv continue # reattach exactly where you left off
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`luv` re-invokes itself on the remote over SSH, so every flag works there unchanged. The remote needs `luv`, `tmux`, `gh`, and `git` on its `PATH`.
|
|
180
|
+
|
|
181
|
+
Use `--local` for a one-off local run, `-s HOST` to target a different machine, and `-i PATH` for a different SSH key.
|
|
182
|
+
|
|
183
|
+
Full guide, including how to prepare a fresh Ubuntu box and set up SSH keys: **[docs/remote-sessions.md](docs/remote-sessions.md)**.
|
|
184
|
+
|
|
146
185
|
## Workspace cleanup
|
|
147
186
|
|
|
148
187
|
`luv --clean` scans `~/prs/` and safely removes workspaces that are fully pushed. It checks:
|
|
@@ -153,12 +192,55 @@ Docker mode works with all flags: `-n` opens a bash shell in the container, `-r`
|
|
|
153
192
|
|
|
154
193
|
Use `luv --clean -f` to skip all safety checks and delete everything. Add `--safe` (i.e. `luv --clean --safe -f`) to restrict force-delete to workspaces whose folder mtime is older than 24 hours, leaving recently-touched workspaces alone.
|
|
155
194
|
|
|
195
|
+
Workspaces with a live tmux session are skipped unless you pass `-f`.
|
|
196
|
+
|
|
197
|
+
### Removing a specific session
|
|
198
|
+
|
|
199
|
+
`luv --clean` is a garbage collector: it decides for itself what is safe to
|
|
200
|
+
delete, and never touches a workspace with a live session. `luv rm` is the
|
|
201
|
+
opposite — you name what goes, and it goes, on whichever host it lives on.
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
luv rm myrepo-42 # kill its tmux session, delete ~/prs/myrepo-42 on its host
|
|
205
|
+
luv rm luv-myrepo-42 # the session name works too
|
|
206
|
+
luv rm --merged # every session whose PR has been merged
|
|
207
|
+
luv rm --dead # workspaces left behind by sessions that already exited
|
|
208
|
+
luv rm --dead --host box # scope either selector to one machine
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
A named target is taken as intent and removed without further checks —
|
|
212
|
+
including any uncommitted work in it. The `--merged` and `--dead` selectors
|
|
213
|
+
list what they matched and ask first; `-f` skips that prompt.
|
|
214
|
+
|
|
215
|
+
`--dead` is the counterpart to `luv ls`: reconciliation drops a session from the
|
|
216
|
+
registry as soon as its tmux session exits, but the clone it was working in
|
|
217
|
+
stays on the remote disk. Those orphans are invisible to `luv ls` and are
|
|
218
|
+
usually what is actually filling up the box.
|
|
219
|
+
|
|
220
|
+
Only folders named `{repo}-{N}` are ever eligible — anything else in the
|
|
221
|
+
workspace root is left alone, and a target that doesn't resolve to one is an
|
|
222
|
+
error rather than an `rm -rf`.
|
|
223
|
+
|
|
156
224
|
## Configuration
|
|
157
225
|
|
|
158
|
-
Run `luv --init` to set your default GitHub org.
|
|
226
|
+
Run `luv config` for interactive setup, or `luv --init` to set just your default GitHub org. Both save to `~/.luv/config.json`:
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"org": "exosphere",
|
|
231
|
+
"prs_dir": "~/prs",
|
|
232
|
+
"remote": {
|
|
233
|
+
"host": "box",
|
|
234
|
+
"identity_file": "~/.ssh/id_ed25519",
|
|
235
|
+
"dir": "~/prs"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
159
239
|
|
|
160
240
|
You can also pass `org/repo` inline to override the default for any command (e.g., `luv other-org/my-repo`).
|
|
161
241
|
|
|
242
|
+
Every key, with defaults and resolution order: **[docs/configuration.md](docs/configuration.md)**. The session registry that backs `luv ls`: **[docs/sessions.md](docs/sessions.md)**.
|
|
243
|
+
|
|
162
244
|
## License
|
|
163
245
|
|
|
164
246
|
MIT
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
luv reads a single JSON file: `~/.luv/config.json`. It is created by
|
|
4
|
+
`luv config` and can be edited by hand.
|
|
5
|
+
|
|
6
|
+
- [The `luv config` command](#the-luv-config-command)
|
|
7
|
+
- [Keys](#keys)
|
|
8
|
+
- [Resolution order](#resolution-order)
|
|
9
|
+
- [Multi-host example](#multi-host-example)
|
|
10
|
+
- [Not this file](#not-this-file)
|
|
11
|
+
|
|
12
|
+
## The `luv config` command
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
luv config # interactive wizard
|
|
16
|
+
luv config list # show everything, as dotted keys
|
|
17
|
+
luv config get remote.host
|
|
18
|
+
luv config set remote.host box
|
|
19
|
+
luv config unset remote.host # back to local-only
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`set` parses the value as JSON when it can, so numbers, booleans, and lists work
|
|
23
|
+
as you'd expect:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
luv config set remote.port 2222 # → 2222 (number)
|
|
27
|
+
luv config set remote.ssh_opts '["-o","Compression=yes"]'
|
|
28
|
+
luv config set org exosphere # → "exosphere" (string)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Anything that isn't valid JSON is stored as a plain string, which is what you
|
|
32
|
+
want for hosts, paths, and org names.
|
|
33
|
+
|
|
34
|
+
`luv --init` still exists and sets only the org.
|
|
35
|
+
|
|
36
|
+
## Keys
|
|
37
|
+
|
|
38
|
+
### `org`
|
|
39
|
+
|
|
40
|
+
*string.* Default GitHub owner, used when you write `luv myrepo` instead of
|
|
41
|
+
`luv someorg/myrepo`. Set by `luv --init` or the wizard.
|
|
42
|
+
|
|
43
|
+
The **laptop** resolves this before dispatching, so a remote machine never needs
|
|
44
|
+
its own `org` configured.
|
|
45
|
+
|
|
46
|
+
### `prs_dir`
|
|
47
|
+
|
|
48
|
+
*string, default `~/prs`.* Where workspace folders are created on **this**
|
|
49
|
+
machine. `~` is expanded.
|
|
50
|
+
|
|
51
|
+
### `remote.host`
|
|
52
|
+
|
|
53
|
+
*string.* SSH destination — an `~/.ssh/config` alias (`box`), a `user@host`, or
|
|
54
|
+
a bare hostname. **Setting this turns on remote mode**: every workspace command
|
|
55
|
+
then runs there. Unset it, or pass `--local`, to run locally.
|
|
56
|
+
|
|
57
|
+
### `remote.identity_file`
|
|
58
|
+
|
|
59
|
+
*string.* Path to the SSH private key, passed as `ssh -i`. `~` is expanded by
|
|
60
|
+
luv, since `ssh` won't expand it when luv execs directly rather than through a
|
|
61
|
+
shell.
|
|
62
|
+
|
|
63
|
+
### `remote.port`
|
|
64
|
+
|
|
65
|
+
*number.* Passed as `ssh -p`. Omit for the default 22.
|
|
66
|
+
|
|
67
|
+
### `remote.dir`
|
|
68
|
+
|
|
69
|
+
*string.* Workspace root **on the remote**. Forwarded to the remote luv, which
|
|
70
|
+
uses it in place of its own `prs_dir`.
|
|
71
|
+
|
|
72
|
+
### `remote.luv_bin`
|
|
73
|
+
|
|
74
|
+
*string.* Full path to `luv` on the remote. Only needed when a login shell
|
|
75
|
+
there can't find it on `PATH` — see the
|
|
76
|
+
[troubleshooting section](remote-sessions.md#troubleshooting).
|
|
77
|
+
|
|
78
|
+
### `remote.ssh_opts`
|
|
79
|
+
|
|
80
|
+
*list of strings.* Extra arguments inserted into every `ssh` invocation, e.g.
|
|
81
|
+
`["-o", "ServerAliveInterval=30"]`. Prefer `~/.ssh/config` for anything durable.
|
|
82
|
+
|
|
83
|
+
### `remote.hosts.<name>`
|
|
84
|
+
|
|
85
|
+
*object.* Per-host overrides. Any of `identity_file`, `port`, `dir`, `luv_bin`,
|
|
86
|
+
or `ssh_opts` may be set here and takes precedence over the `remote.*` value of
|
|
87
|
+
the same name. This is what makes `-s otherbox` usable: without it, one global
|
|
88
|
+
key would be wrong for every host but the default.
|
|
89
|
+
|
|
90
|
+
## Resolution order
|
|
91
|
+
|
|
92
|
+
**Host:** `-s HOST` → `remote.host`. If neither, luv runs locally.
|
|
93
|
+
|
|
94
|
+
**Per-host settings:** `-i PATH` (identity only) → `remote.hosts.<host>.<key>` →
|
|
95
|
+
`remote.<key>`.
|
|
96
|
+
|
|
97
|
+
**Workspace root:** `_LUV_PRS_DIR` (set by the dispatcher from `remote.dir`) →
|
|
98
|
+
`prs_dir` → `~/prs`.
|
|
99
|
+
|
|
100
|
+
**Org:** `org/repo` written explicitly → `org` from config → error.
|
|
101
|
+
|
|
102
|
+
`--local` overrides everything and is rejected alongside `-s` or `-i`, since
|
|
103
|
+
those combinations contradict each other.
|
|
104
|
+
|
|
105
|
+
## Multi-host example
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"org": "exosphere",
|
|
110
|
+
"prs_dir": "~/prs",
|
|
111
|
+
"remote": {
|
|
112
|
+
"host": "box",
|
|
113
|
+
"identity_file": "~/.ssh/id_ed25519",
|
|
114
|
+
"dir": "~/prs",
|
|
115
|
+
"ssh_opts": ["-o", "ServerAliveInterval=30"],
|
|
116
|
+
"hosts": {
|
|
117
|
+
"gpu-box": {
|
|
118
|
+
"identity_file": "~/.ssh/gpu_key",
|
|
119
|
+
"port": 2222,
|
|
120
|
+
"dir": "/scratch/prs",
|
|
121
|
+
"luv_bin": "/opt/venv/bin/luv"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
With this, `luv myrepo "…"` goes to `box` on port 22 with `id_ed25519`, and
|
|
129
|
+
`luv -s gpu-box ml "…"` goes to `gpu-box` on port 2222 with `gpu_key`, cloning
|
|
130
|
+
into `/scratch/prs` and using an explicit luv binary.
|
|
131
|
+
|
|
132
|
+
## Not this file
|
|
133
|
+
|
|
134
|
+
Two other files are sometimes confused with this one:
|
|
135
|
+
|
|
136
|
+
- **`~/.luv/sessions.json`** — the session registry, written by luv itself. Not
|
|
137
|
+
configuration; see [sessions.md](sessions.md).
|
|
138
|
+
- **`<repo>/.luv/settings.json`** — a *repo-local* file, committed to the repo,
|
|
139
|
+
whose only key is `compose_file`. It turns on Docker mode for that repo and
|
|
140
|
+
has nothing to do with `~/.luv/config.json`.
|