luv-cli 0.3.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.3.0 → luv_cli-0.5.0}/PKG-INFO +41 -8
- {luv_cli-0.3.0 → luv_cli-0.5.0}/README.md +40 -7
- {luv_cli-0.3.0 → luv_cli-0.5.0}/docs/configuration.md +24 -0
- {luv_cli-0.3.0 → luv_cli-0.5.0}/docs/remote-sessions.md +94 -7
- {luv_cli-0.3.0 → luv_cli-0.5.0}/docs/sessions.md +13 -6
- {luv_cli-0.3.0 → luv_cli-0.5.0}/luv/__init__.py +970 -39
- {luv_cli-0.3.0 → luv_cli-0.5.0}/pyproject.toml +1 -1
- {luv_cli-0.3.0 → luv_cli-0.5.0}/tests/test_agents.py +601 -1
- {luv_cli-0.3.0 → luv_cli-0.5.0}/.claude/settings.json +0 -0
- {luv_cli-0.3.0 → luv_cli-0.5.0}/.failproofai/policies-config.json +0 -0
- {luv_cli-0.3.0 → luv_cli-0.5.0}/.github/workflows/publish.yml +0 -0
- {luv_cli-0.3.0 → luv_cli-0.5.0}/.gitignore +0 -0
- {luv_cli-0.3.0 → luv_cli-0.5.0}/LICENSE +0 -0
- {luv_cli-0.3.0 → luv_cli-0.5.0}/claude-output.txt +0 -0
- {luv_cli-0.3.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
|
|
@@ -63,7 +63,7 @@ luv other-org/my-repo "fix the bug"
|
|
|
63
63
|
# Reopen workspace #42
|
|
64
64
|
luv my-repo 42
|
|
65
65
|
|
|
66
|
-
#
|
|
66
|
+
# Clone any GitHub PR by URL into a fresh workspace
|
|
67
67
|
luv -l https://github.com/org/repo/pull/123
|
|
68
68
|
|
|
69
69
|
# Open a shell instead of Claude
|
|
@@ -97,6 +97,22 @@ The machine name is what keeps two computers apart. Each one works out the next
|
|
|
97
97
|
|
|
98
98
|
With a remote host configured, steps 1–4 happen on that machine inside a tmux session instead — see [Remote sessions](#remote-sessions).
|
|
99
99
|
|
|
100
|
+
### Opening an existing PR
|
|
101
|
+
|
|
102
|
+
`luv -l <PR URL>` gives you the PR as it is *right now*: a full clone into a
|
|
103
|
+
workspace of its own, checked out on the PR's head branch. It never reuses a
|
|
104
|
+
folder that happens to be sitting there — a URL you paste is a request to look
|
|
105
|
+
at that PR, not at whatever was checked out in `myrepo-mbp-123` last week. Run
|
|
106
|
+
it twice on the same PR and you get `myrepo-mbp-123` and `myrepo-mbp-123_2`,
|
|
107
|
+
which are independent workspaces you can run side by side. When the PR comes
|
|
108
|
+
from a fork, the base repo is added as `upstream` and fetched too, so the branch
|
|
109
|
+
you need to diff or rebase against is there.
|
|
110
|
+
|
|
111
|
+
`-r` is the exception: resuming means picking up a conversation, and a
|
|
112
|
+
conversation lives in the folder it was held in, so `luv -l <URL> -r` reopens the
|
|
113
|
+
newest workspace for that PR instead of cloning another. `luv <repo> -pr <N>`
|
|
114
|
+
also keeps reusing its workspace — it names a workspace, where a URL names a PR.
|
|
115
|
+
|
|
100
116
|
## Commands
|
|
101
117
|
|
|
102
118
|
| Command | Description |
|
|
@@ -105,7 +121,9 @@ With a remote host configured, steps 1–4 happen on that machine inside a tmux
|
|
|
105
121
|
| `luv config set\|get\|unset <key> [value]` | Read or write a single setting |
|
|
106
122
|
| `luv config list` | Show all settings |
|
|
107
123
|
| `luv --init` | Configure default GitHub org only |
|
|
108
|
-
| `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 |
|
|
109
127
|
| `luv continue [<repo> [number]]` | Attach to a live session |
|
|
110
128
|
| `luv handover [<repo> [n]] --to HOST` | Move a session to another machine and resume it there |
|
|
111
129
|
| `luv rm <session\|workspace>...` | Kill a session and delete its workspace on its host |
|
|
@@ -115,8 +133,8 @@ With a remote host configured, steps 1–4 happen on that machine inside a tmux
|
|
|
115
133
|
| `luv --codex [org/]<repo> [prompt...]` | Create a workspace and launch Codex in YOLO mode |
|
|
116
134
|
| `luv [org/]<repo> -b <branch> [prompt...]` | Create a workspace based off `<branch>` instead of the default |
|
|
117
135
|
| `luv [org/]<repo> <number> [prompt]` | Reopen an existing workspace |
|
|
118
|
-
| `luv -l <PR URL> [prompt]` |
|
|
119
|
-
| `luv [org/]<repo> -pr <number> [prompt]` | Open a PR by repo + number |
|
|
136
|
+
| `luv -l <PR URL> [prompt]` | Clone any GitHub PR by URL into a fresh workspace |
|
|
137
|
+
| `luv [org/]<repo> -pr <number> [prompt]` | Open a PR by repo + number, reusing its workspace |
|
|
120
138
|
| `luv --clean` | Delete workspaces where the branch is fully pushed/merged |
|
|
121
139
|
| `luv --clean -f` | Force delete all workspaces |
|
|
122
140
|
| `luv --clean --safe -f` | Force delete only workspaces older than 24h |
|
|
@@ -219,6 +237,20 @@ luv continue # reattach exactly where you left off
|
|
|
219
237
|
|
|
220
238
|
Use `--local` for a one-off local run, `-s HOST` to target a different machine, and `-i PATH` for a different SSH key.
|
|
221
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
|
+
|
|
222
254
|
### Handing a session to another machine
|
|
223
255
|
|
|
224
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:
|
|
@@ -270,9 +302,10 @@ registry as soon as its tmux session exits, but the clone it was working in
|
|
|
270
302
|
stays on the remote disk. Those orphans are invisible to `luv ls` and are
|
|
271
303
|
usually what is actually filling up the box.
|
|
272
304
|
|
|
273
|
-
Only folders ending in `-{N}` are ever eligible — `{repo}-{machine}-{N}
|
|
274
|
-
pre-slug `{repo}-{N}` alike.
|
|
275
|
-
|
|
305
|
+
Only folders ending in `-{N}` are ever eligible — `{repo}-{machine}-{N}`, a
|
|
306
|
+
`luv -l` copy `{repo}-{machine}-{N}_2`, and pre-slug `{repo}-{N}` alike.
|
|
307
|
+
Anything else in the workspace root is left alone, and a target that doesn't
|
|
308
|
+
resolve to one is an error rather than an `rm -rf`.
|
|
276
309
|
|
|
277
310
|
## Configuration
|
|
278
311
|
|
|
@@ -40,7 +40,7 @@ luv other-org/my-repo "fix the bug"
|
|
|
40
40
|
# Reopen workspace #42
|
|
41
41
|
luv my-repo 42
|
|
42
42
|
|
|
43
|
-
#
|
|
43
|
+
# Clone any GitHub PR by URL into a fresh workspace
|
|
44
44
|
luv -l https://github.com/org/repo/pull/123
|
|
45
45
|
|
|
46
46
|
# Open a shell instead of Claude
|
|
@@ -74,6 +74,22 @@ The machine name is what keeps two computers apart. Each one works out the next
|
|
|
74
74
|
|
|
75
75
|
With a remote host configured, steps 1–4 happen on that machine inside a tmux session instead — see [Remote sessions](#remote-sessions).
|
|
76
76
|
|
|
77
|
+
### Opening an existing PR
|
|
78
|
+
|
|
79
|
+
`luv -l <PR URL>` gives you the PR as it is *right now*: a full clone into a
|
|
80
|
+
workspace of its own, checked out on the PR's head branch. It never reuses a
|
|
81
|
+
folder that happens to be sitting there — a URL you paste is a request to look
|
|
82
|
+
at that PR, not at whatever was checked out in `myrepo-mbp-123` last week. Run
|
|
83
|
+
it twice on the same PR and you get `myrepo-mbp-123` and `myrepo-mbp-123_2`,
|
|
84
|
+
which are independent workspaces you can run side by side. When the PR comes
|
|
85
|
+
from a fork, the base repo is added as `upstream` and fetched too, so the branch
|
|
86
|
+
you need to diff or rebase against is there.
|
|
87
|
+
|
|
88
|
+
`-r` is the exception: resuming means picking up a conversation, and a
|
|
89
|
+
conversation lives in the folder it was held in, so `luv -l <URL> -r` reopens the
|
|
90
|
+
newest workspace for that PR instead of cloning another. `luv <repo> -pr <N>`
|
|
91
|
+
also keeps reusing its workspace — it names a workspace, where a URL names a PR.
|
|
92
|
+
|
|
77
93
|
## Commands
|
|
78
94
|
|
|
79
95
|
| Command | Description |
|
|
@@ -82,7 +98,9 @@ With a remote host configured, steps 1–4 happen on that machine inside a tmux
|
|
|
82
98
|
| `luv config set\|get\|unset <key> [value]` | Read or write a single setting |
|
|
83
99
|
| `luv config list` | Show all settings |
|
|
84
100
|
| `luv --init` | Configure default GitHub org only |
|
|
85
|
-
| `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 |
|
|
86
104
|
| `luv continue [<repo> [number]]` | Attach to a live session |
|
|
87
105
|
| `luv handover [<repo> [n]] --to HOST` | Move a session to another machine and resume it there |
|
|
88
106
|
| `luv rm <session\|workspace>...` | Kill a session and delete its workspace on its host |
|
|
@@ -92,8 +110,8 @@ With a remote host configured, steps 1–4 happen on that machine inside a tmux
|
|
|
92
110
|
| `luv --codex [org/]<repo> [prompt...]` | Create a workspace and launch Codex in YOLO mode |
|
|
93
111
|
| `luv [org/]<repo> -b <branch> [prompt...]` | Create a workspace based off `<branch>` instead of the default |
|
|
94
112
|
| `luv [org/]<repo> <number> [prompt]` | Reopen an existing workspace |
|
|
95
|
-
| `luv -l <PR URL> [prompt]` |
|
|
96
|
-
| `luv [org/]<repo> -pr <number> [prompt]` | Open a PR by repo + number |
|
|
113
|
+
| `luv -l <PR URL> [prompt]` | Clone any GitHub PR by URL into a fresh workspace |
|
|
114
|
+
| `luv [org/]<repo> -pr <number> [prompt]` | Open a PR by repo + number, reusing its workspace |
|
|
97
115
|
| `luv --clean` | Delete workspaces where the branch is fully pushed/merged |
|
|
98
116
|
| `luv --clean -f` | Force delete all workspaces |
|
|
99
117
|
| `luv --clean --safe -f` | Force delete only workspaces older than 24h |
|
|
@@ -196,6 +214,20 @@ luv continue # reattach exactly where you left off
|
|
|
196
214
|
|
|
197
215
|
Use `--local` for a one-off local run, `-s HOST` to target a different machine, and `-i PATH` for a different SSH key.
|
|
198
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
|
+
|
|
199
231
|
### Handing a session to another machine
|
|
200
232
|
|
|
201
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:
|
|
@@ -247,9 +279,10 @@ registry as soon as its tmux session exits, but the clone it was working in
|
|
|
247
279
|
stays on the remote disk. Those orphans are invisible to `luv ls` and are
|
|
248
280
|
usually what is actually filling up the box.
|
|
249
281
|
|
|
250
|
-
Only folders ending in `-{N}` are ever eligible — `{repo}-{machine}-{N}
|
|
251
|
-
pre-slug `{repo}-{N}` alike.
|
|
252
|
-
|
|
282
|
+
Only folders ending in `-{N}` are ever eligible — `{repo}-{machine}-{N}`, a
|
|
283
|
+
`luv -l` copy `{repo}-{machine}-{N}_2`, and pre-slug `{repo}-{N}` alike.
|
|
284
|
+
Anything else in the workspace root is left alone, and a target that doesn't
|
|
285
|
+
resolve to one is an error rather than an `rm -rf`.
|
|
253
286
|
|
|
254
287
|
## Configuration
|
|
255
288
|
|
|
@@ -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 →
|
|
@@ -345,19 +424,27 @@ a laptop and a box would routinely pick the same one and push the same branch.
|
|
|
345
424
|
Folders and branches created before slugs existed keep working — luv looks for
|
|
346
425
|
its own name first, then any other machine's, then the plain `{repo}-{number}`.
|
|
347
426
|
|
|
427
|
+
`luv -l <PR URL>` clones into a folder of its own every time, so a second run on
|
|
428
|
+
the same PR gets `{repo}-{machine}-{number}_2`, a third `_3`. The suffix is part
|
|
429
|
+
of the number's segment, so those folders are still workspace `{number}`
|
|
430
|
+
everywhere it matters — `luv ls`, `luv rm`, `--clean`, handover. Where a number
|
|
431
|
+
has several copies, the newest wins: that is the one you just made.
|
|
432
|
+
|
|
348
433
|
Sessions are named after the workspace folder: `~/prs/myrepo-box-42` becomes
|
|
349
434
|
`luv-myrepo-box-42`. tmux forbids `.` and `:` in session names, so a repo like
|
|
350
435
|
`foo.js` becomes `luv-foo_js-box-42`.
|
|
351
436
|
|
|
352
|
-
When the workspace is already known — reopening by number, `-pr`, or `-l` —
|
|
353
|
-
laptop pins the session name down before dispatching, so `tmux new-session
|
|
354
|
-
doubles as "attach if it's already running". It can't compute that name,
|
|
355
|
-
since the slug belongs to the machine holding the folder: it takes it
|
|
356
|
-
session registry, or asks the host directly, and falls back to the
|
|
437
|
+
When the workspace is already known — reopening by number, `-pr`, or `-l -r` —
|
|
438
|
+
the laptop pins the session name down before dispatching, so `tmux new-session
|
|
439
|
+
-A` doubles as "attach if it's already running". It can't compute that name,
|
|
440
|
+
though, since the slug belongs to the machine holding the folder: it takes it
|
|
441
|
+
from the session registry, or asks the host directly, and falls back to the
|
|
357
442
|
`luv-pending-<id>` rename-on-arrival path if the host doesn't answer.
|
|
358
443
|
|
|
359
|
-
When it isn't known — a brand new workspace,
|
|
360
|
-
|
|
444
|
+
When it isn't known — a brand new workspace, plain `luv -l` (whose folder the
|
|
445
|
+
remote only picks once it sees what is already there), or bare `-n`/`-r` — the
|
|
446
|
+
laptop uses a placeholder `luv-pending-<id>` and the remote renames it once the
|
|
447
|
+
clone lands.
|
|
361
448
|
If the target name is already taken by another live session, luv keeps the
|
|
362
449
|
session under `luv-myrepo-box-42-2` and warns rather than failing.
|
|
363
450
|
|
|
@@ -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
|
}
|
|
@@ -63,8 +66,11 @@ the session (which luv does as soon as the clone lands) breaks nothing.
|
|
|
63
66
|
| `pr_number`, `pr_url` | The session's pull request; `null` until one exists |
|
|
64
67
|
| `pr_state` | `OPEN`, `CLOSED` or `MERGED` — what `luv rm --merged` selects on |
|
|
65
68
|
| `pr_checked` | Unix time GitHub was last asked about it |
|
|
66
|
-
| `pr_hint` | PR number known at dispatch (`-l` / `-pr` only); absent otherwise |
|
|
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.
|
|
@@ -230,10 +236,11 @@ case.
|
|
|
230
236
|
|
|
231
237
|
Two things bound the damage:
|
|
232
238
|
|
|
233
|
-
- Only names ending in `-{N}` are eligible — `{repo}-{machine}-{N}`
|
|
234
|
-
`{repo}-{N}` alike — checked again
|
|
235
|
-
only at selection time. A stray
|
|
236
|
-
|
|
239
|
+
- Only names ending in `-{N}` are eligible — `{repo}-{machine}-{N}`, a `luv -l`
|
|
240
|
+
copy `{repo}-{machine}-{N}_2`, and pre-slug `{repo}-{N}` alike — checked again
|
|
241
|
+
immediately before the `rm -rf` rather than only at selection time. A stray
|
|
242
|
+
directory in the workspace root is never a candidate, and a target that
|
|
243
|
+
resolves to one is an error, not a deletion.
|
|
237
244
|
- A named target is its own confirmation, but `--merged` and `--dead` print what
|
|
238
245
|
they matched and ask, because a selector can sweep up folders on a machine you
|
|
239
246
|
are not looking at. `-f` skips the prompt.
|