luv-cli 0.4.0__tar.gz → 0.5.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.4.0 → luv_cli-0.5.0}/PKG-INFO +18 -2
- {luv_cli-0.4.0 → luv_cli-0.5.0}/README.md +17 -1
- {luv_cli-0.4.0 → luv_cli-0.5.0}/docs/configuration.md +24 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/docs/remote-sessions.md +79 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/docs/sessions.md +7 -1
- {luv_cli-0.4.0 → luv_cli-0.5.0}/luv/__init__.py +870 -9
- {luv_cli-0.4.0 → luv_cli-0.5.0}/pyproject.toml +1 -1
- {luv_cli-0.4.0 → luv_cli-0.5.0}/tests/test_agents.py +445 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/.claude/settings.json +0 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/.failproofai/policies-config.json +0 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/.github/workflows/publish.yml +0 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/.gitignore +0 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/LICENSE +0 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/claude-output.txt +0 -0
- {luv_cli-0.4.0 → luv_cli-0.5.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: luv-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -121,7 +121,9 @@ also keeps reusing its workspace — it names a workspace, where a URL names a P
|
|
|
121
121
|
| `luv config set\|get\|unset <key> [value]` | Read or write a single setting |
|
|
122
122
|
| `luv config list` | Show all settings |
|
|
123
123
|
| `luv --init` | Configure default GitHub org only |
|
|
124
|
-
| `luv ls [--host H] [--prune] [--no-pr]` | List every live session on every host, with each one's PR link |
|
|
124
|
+
| `luv ls [--host H] [--prune] [--no-pr] [--no-ports]` | List every live session on every host, with each one's PR link and detected ports |
|
|
125
|
+
| `luv ports [<repo> [n]] [--watch [N]]` | Show ports the sessions are listening on; naming one forwards it to localhost |
|
|
126
|
+
| `luv ports --off [<repo> [n]]` | Drop forwards again |
|
|
125
127
|
| `luv continue [<repo> [number]]` | Attach to a live session |
|
|
126
128
|
| `luv handover [<repo> [n]] --to HOST` | Move a session to another machine and resume it there |
|
|
127
129
|
| `luv rm <session\|workspace>...` | Kill a session and delete its workspace on its host |
|
|
@@ -235,6 +237,20 @@ luv continue # reattach exactly where you left off
|
|
|
235
237
|
|
|
236
238
|
Use `--local` for a one-off local run, `-s HOST` to target a different machine, and `-i PATH` for a different SSH key.
|
|
237
239
|
|
|
240
|
+
### Reaching servers the agent started
|
|
241
|
+
|
|
242
|
+
Agents working on a remote start servers there. luv finds them and forwards them to this machine on the same port number wherever it's free, so the URL in the agent's output is the URL that works for you.
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
luv ls # PORTS column: what was detected, on every session
|
|
246
|
+
luv ports myrepo 42 # forward that session's ports to localhost
|
|
247
|
+
luv ports --off myrepo 42 # drop them again
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The session you're **attached to** is forwarded automatically as servers come and go, and announces each one on the tmux status line (`luv: localhost:3000 → dashboard`) — nothing to run. Everything else is opted in by name, because a busy host carries dozens of sessions and they shouldn't all claim local ports uninvited.
|
|
251
|
+
|
|
252
|
+
Both processes under the session's tmux pane and Docker Compose stacks belonging to the workspace are found; forwards bind `127.0.0.1`, and they outlive detaching so a long-running session stays reachable.
|
|
253
|
+
|
|
238
254
|
### Handing a session to another machine
|
|
239
255
|
|
|
240
256
|
Start something on your laptop, then move it — workspace, uncommitted work, and the agent's conversation — to a machine that won't go to sleep:
|
|
@@ -98,7 +98,9 @@ also keeps reusing its workspace — it names a workspace, where a URL names a P
|
|
|
98
98
|
| `luv config set\|get\|unset <key> [value]` | Read or write a single setting |
|
|
99
99
|
| `luv config list` | Show all settings |
|
|
100
100
|
| `luv --init` | Configure default GitHub org only |
|
|
101
|
-
| `luv ls [--host H] [--prune] [--no-pr]` | List every live session on every host, with each one's PR link |
|
|
101
|
+
| `luv ls [--host H] [--prune] [--no-pr] [--no-ports]` | List every live session on every host, with each one's PR link and detected ports |
|
|
102
|
+
| `luv ports [<repo> [n]] [--watch [N]]` | Show ports the sessions are listening on; naming one forwards it to localhost |
|
|
103
|
+
| `luv ports --off [<repo> [n]]` | Drop forwards again |
|
|
102
104
|
| `luv continue [<repo> [number]]` | Attach to a live session |
|
|
103
105
|
| `luv handover [<repo> [n]] --to HOST` | Move a session to another machine and resume it there |
|
|
104
106
|
| `luv rm <session\|workspace>...` | Kill a session and delete its workspace on its host |
|
|
@@ -212,6 +214,20 @@ luv continue # reattach exactly where you left off
|
|
|
212
214
|
|
|
213
215
|
Use `--local` for a one-off local run, `-s HOST` to target a different machine, and `-i PATH` for a different SSH key.
|
|
214
216
|
|
|
217
|
+
### Reaching servers the agent started
|
|
218
|
+
|
|
219
|
+
Agents working on a remote start servers there. luv finds them and forwards them to this machine on the same port number wherever it's free, so the URL in the agent's output is the URL that works for you.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
luv ls # PORTS column: what was detected, on every session
|
|
223
|
+
luv ports myrepo 42 # forward that session's ports to localhost
|
|
224
|
+
luv ports --off myrepo 42 # drop them again
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The session you're **attached to** is forwarded automatically as servers come and go, and announces each one on the tmux status line (`luv: localhost:3000 → dashboard`) — nothing to run. Everything else is opted in by name, because a busy host carries dozens of sessions and they shouldn't all claim local ports uninvited.
|
|
228
|
+
|
|
229
|
+
Both processes under the session's tmux pane and Docker Compose stacks belonging to the workspace are found; forwards bind `127.0.0.1`, and they outlive detaching so a long-running session stays reachable.
|
|
230
|
+
|
|
215
231
|
### Handing a session to another machine
|
|
216
232
|
|
|
217
233
|
Start something on your laptop, then move it — workspace, uncommitted work, and the agent's conversation — to a machine that won't go to sleep:
|
|
@@ -109,6 +109,30 @@ or `ssh_opts` may be set here and takes precedence over the `remote.*` value of
|
|
|
109
109
|
the same name. This is what makes `-s otherbox` usable: without it, one global
|
|
110
110
|
key would be wrong for every host but the default.
|
|
111
111
|
|
|
112
|
+
### `ports`
|
|
113
|
+
|
|
114
|
+
*object.* Port forwarding. Servers an agent starts on a remote host are
|
|
115
|
+
detected and tunnelled to this machine — see
|
|
116
|
+
[remote-sessions.md](remote-sessions.md#reaching-servers-the-agent-started).
|
|
117
|
+
Every key is optional:
|
|
118
|
+
|
|
119
|
+
| Key | Default | Meaning |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `auto` | `true` | Forward the session you are attached to, as servers come and go. `false` leaves everything to `luv ports`. |
|
|
122
|
+
| `interval` | `10` | Seconds between re-checks while attached. Minimum 2. |
|
|
123
|
+
| `bind` | `"127.0.0.1"` | Local address the forwards listen on. `"0.0.0.0"` would publish the agent's dev server to your whole network. |
|
|
124
|
+
| `min` | `1024` | Ports below this are ignored; a dev server is not on 80. |
|
|
125
|
+
| `ignore` | `[]` | Specific ports never to forward. |
|
|
126
|
+
| `max_per_session` | `12` | Cap on forwards per session, so one stack cannot eat the local port space. |
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
luv config set ports.interval 5
|
|
130
|
+
luv config set ports.ignore '[5432, 6379]'
|
|
131
|
+
luv config set ports.auto false
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Unlike `remote.*`, these are global — there is no `remote.hosts.<name>.ports`.
|
|
135
|
+
|
|
112
136
|
## Resolution order
|
|
113
137
|
|
|
114
138
|
**Host:** `-s HOST` → `remote.host`. If neither, luv runs locally.
|
|
@@ -256,6 +256,85 @@ luv -i ~/.ssh/other_key myrepo # use a different key this once
|
|
|
256
256
|
Detach from a session with `Ctrl-b d` (tmux's default prefix). The agent keeps
|
|
257
257
|
running.
|
|
258
258
|
|
|
259
|
+
## Reaching servers the agent started
|
|
260
|
+
|
|
261
|
+
An agent working on the remote starts servers there — a dev server, a compose
|
|
262
|
+
stack, a database. luv finds them and tunnels them to this machine, on the same
|
|
263
|
+
port number wherever it is free, so the URL the agent prints in its own output
|
|
264
|
+
is the URL that works here.
|
|
265
|
+
|
|
266
|
+
**The session you are attached to is forwarded automatically.** It is re-checked
|
|
267
|
+
every few seconds, so a server started ten minutes into a session is reachable
|
|
268
|
+
shortly after it comes up, without running anything. When a forward appears the
|
|
269
|
+
session says so on its tmux status line:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
luv: localhost:3000 → dashboard, localhost:8080 → server
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The ports are also set on the tmux session as `@luv_ports`, if you would rather
|
|
276
|
+
have them permanently in your own `status-right`.
|
|
277
|
+
|
|
278
|
+
**Everything else is opted in by name.** A busy host carries dozens of sessions
|
|
279
|
+
and they should not all get a piece of this machine's port space uninvited:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
luv ls # PORTS column: what was detected, forwarded or not
|
|
283
|
+
luv ports # detail view; refreshes what is already forwarded
|
|
284
|
+
luv ports myrepo 42 # forward this session's ports
|
|
285
|
+
luv ports myrepo 42 --watch # ...and keep it up to date, printing each change
|
|
286
|
+
luv ports --off myrepo 42 # drop them again
|
|
287
|
+
luv ports --off # drop everything
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
HOST WORKSPACE REMOTE LOCAL URL SERVICE STATE
|
|
292
|
+
box myrepo-box-42 3000 3001 http://localhost:3001 dashboard up
|
|
293
|
+
box myrepo-box-42 5173 5173 http://localhost:5173 vite up
|
|
294
|
+
box other-box-17 5432 5432 - postgres detected
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Forwards outlive detaching — the session is still running and `localhost:3000`
|
|
298
|
+
should keep working — so `luv ports myrepo 42` is also how a long-running
|
|
299
|
+
session stays reachable after you close the window.
|
|
300
|
+
|
|
301
|
+
`REMOTE` and `LOCAL` differ when the local number was already taken, by another
|
|
302
|
+
session or by something of your own; luv walks up from the remote number and
|
|
303
|
+
remembers what it chose, so the URL stays put across runs.
|
|
304
|
+
|
|
305
|
+
### What gets found
|
|
306
|
+
|
|
307
|
+
Two sources, because neither sees everything:
|
|
308
|
+
|
|
309
|
+
- **Processes under the session's tmux pane.** A server the agent started
|
|
310
|
+
itself — `npm run dev`, `python -m http.server` — is a descendant of the pane,
|
|
311
|
+
which is what ties the port to a session. Only your own processes are visible
|
|
312
|
+
this way, so other people's services on a shared box never appear.
|
|
313
|
+
- **Docker Compose stacks belonging to the workspace.** A published port is held
|
|
314
|
+
by `docker-proxy` rather than by anything under the pane, so the Compose
|
|
315
|
+
project label answers instead. Both the project luv starts (`luv-<workspace>`)
|
|
316
|
+
and the one an agent gets by running `docker compose up` itself (named after
|
|
317
|
+
the workspace folder) count as the workspace's.
|
|
318
|
+
|
|
319
|
+
A port that is only *exposed* to the Compose network — `9009/tcp` with no host
|
|
320
|
+
mapping — has nothing on the host to point a tunnel at and is not offered. Add a
|
|
321
|
+
`ports:` entry to the compose file if you want it.
|
|
322
|
+
|
|
323
|
+
Forwards bind `127.0.0.1` on this machine, so nothing is republished to your
|
|
324
|
+
network. See [`ports.*`](configuration.md#ports) to change the interval, the
|
|
325
|
+
bind address, or which ports are skipped, and `ports.auto` to turn the automatic
|
|
326
|
+
half off entirely.
|
|
327
|
+
|
|
328
|
+
### When it doesn't work
|
|
329
|
+
|
|
330
|
+
- **Nothing detected, and the host is otherwise fine.** The host needs a luv new
|
|
331
|
+
enough to know `luv --listening`; an older one reports nothing rather than
|
|
332
|
+
failing. Upgrade luv there.
|
|
333
|
+
- **`warning: <host> refuses port forwarding`.** That host's `sshd_config` has
|
|
334
|
+
`AllowTcpForwarding no`.
|
|
335
|
+
- **A forward that stopped working.** The watcher logs to `~/.luv/ports.log`; it
|
|
336
|
+
cannot print while a full-screen agent UI owns the terminal.
|
|
337
|
+
|
|
259
338
|
## Handing a session to another machine
|
|
260
339
|
|
|
261
340
|
`luv handover` relocates a running workspace. The usual direction is laptop →
|
|
@@ -42,7 +42,10 @@ the session (which luv does as soon as the clone lands) breaks nothing.
|
|
|
42
42
|
"pr_number": 42,
|
|
43
43
|
"pr_url": "https://github.com/exosphere/myrepo/pull/42",
|
|
44
44
|
"pr_state": "OPEN",
|
|
45
|
-
"pr_checked": 1753408899
|
|
45
|
+
"pr_checked": 1753408899,
|
|
46
|
+
"ports": [3000, 5173],
|
|
47
|
+
"forwards": [{"remote": 3000, "local": 3001, "label": "dashboard"}],
|
|
48
|
+
"ports_checked": 1753408899
|
|
46
49
|
}
|
|
47
50
|
]
|
|
48
51
|
}
|
|
@@ -65,6 +68,9 @@ the session (which luv does as soon as the clone lands) breaks nothing.
|
|
|
65
68
|
| `pr_checked` | Unix time GitHub was last asked about it |
|
|
66
69
|
| `pr_hint` | PR number known at dispatch (`-l` / `-pr` only); absent otherwise. Set even when the folder isn't yet known, which is always the case for `-l` |
|
|
67
70
|
| `adopted` | Present when this machine found the session rather than starting it |
|
|
71
|
+
| `ports` | Ports the session was last seen listening on, on its own host |
|
|
72
|
+
| `forwards` | Live tunnels to this machine: `remote` port, the `local` one it landed on, and the service `label`. Persisted so a URL stays put across runs, and so a forward survives detaching |
|
|
73
|
+
| `ports_checked` | Unix time the forwards were last brought in line |
|
|
68
74
|
|
|
69
75
|
`attached`, `activity`, and `live` are recomputed on every reconcile and are
|
|
70
76
|
deliberately **not** written back to the file.
|