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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: luv-cli
3
- Version: 0.4.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.