luv-cli 0.0.24__tar.gz → 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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: luv-cli
3
- Version: 0.0.24
3
+ Version: 0.1.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
- # Configure your default GitHub org (one-time setup)
46
- luv --init
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,10 @@ luv -r my-repo 42
74
74
 
75
75
  # Clean up fully-merged workspaces
76
76
  luv --clean
77
+
78
+ # See what's running on your remote machine, and reattach
79
+ luv ls
80
+ luv continue
77
81
  ```
78
82
 
79
83
  ## How it works
@@ -81,15 +85,22 @@ luv --clean
81
85
  1. Clones the repo into `~/prs/{repo}-{number}/`
82
86
  2. Creates a new branch `luv-{number}`
83
87
  3. Configures the selected agent with the workspace's PR conventions
84
- 4. Launches Claude with Opus 4.8 at max effort, or Codex in YOLO mode
88
+ 4. Launches Claude with Opus 5 at max effort, or Codex in YOLO mode
85
89
 
86
90
  All workspaces live under `~/prs/`. The number comes from the repo's GitHub issue counter to avoid collisions.
87
91
 
92
+ With a remote host configured, steps 1–4 happen on that machine inside a tmux session instead — see [Remote sessions](#remote-sessions).
93
+
88
94
  ## Commands
89
95
 
90
96
  | Command | Description |
91
97
  |---------|-------------|
92
- | `luv --init` | Configure default GitHub org |
98
+ | `luv config` | Interactive setup: remote host, SSH key, default org |
99
+ | `luv config set\|get\|unset <key> [value]` | Read or write a single setting |
100
+ | `luv config list` | Show all settings |
101
+ | `luv --init` | Configure default GitHub org only |
102
+ | `luv ls [--host H] [--prune]` | List live sessions across hosts |
103
+ | `luv continue [<repo> [number]]` | Attach to a live session |
93
104
  | `luv [org/]<repo> [prompt...]` | Create a new workspace and launch Claude (default) |
94
105
  | `luv --codex [org/]<repo> [prompt...]` | Create a workspace and launch Codex in YOLO mode |
95
106
  | `luv [org/]<repo> -b <branch> [prompt...]` | Create a workspace based off `<branch>` instead of the default |
@@ -110,9 +121,12 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
110
121
  | `-r` | Resume: resume the selected agent's last session |
111
122
  | `-p` | Launch Claude in plan permission mode (default: `bypassPermissions`) |
112
123
  | `-nit` | Non-interactive: run the selected agent and exit (no REPL); Claude streams `stream-json` events to stdout |
113
- | `-m MODEL` | Model to use; Claude defaults to `claude-opus-4-8`, while Codex uses its configured CLI default |
124
+ | `-m MODEL` | Model to use; Claude defaults to `claude-opus-5`, while Codex uses its configured CLI default |
114
125
  | `-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
126
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
127
+ | `-s HOST` | Run on `HOST` over SSH, overriding the configured remote host |
128
+ | `-i PATH` | SSH identity file to use for this invocation |
129
+ | `--local` | Force local execution even when a remote host is configured |
116
130
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
117
131
  | `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
118
132
 
@@ -166,6 +180,24 @@ The `dev-environment` service **must** have the selected agent CLI (`claude` or
166
180
 
167
181
  Docker mode works with all flags: `-n` opens a bash shell in the container, `-r` resumes a Claude session in the container.
168
182
 
183
+ ## Remote sessions
184
+
185
+ Point `luv` at another machine and every workspace command runs there, inside a tmux session that survives disconnects.
186
+
187
+ ```bash
188
+ luv config # set the remote host, SSH key, and org
189
+ luv myrepo "fix the flaky test" # clones and launches Claude on the remote
190
+ # ...close the laptop, go home...
191
+ luv ls # see what's still running
192
+ luv continue # reattach exactly where you left off
193
+ ```
194
+
195
+ `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`.
196
+
197
+ Use `--local` for a one-off local run, `-s HOST` to target a different machine, and `-i PATH` for a different SSH key.
198
+
199
+ Full guide, including how to prepare a fresh Ubuntu box and set up SSH keys: **[docs/remote-sessions.md](docs/remote-sessions.md)**.
200
+
169
201
  ## Workspace cleanup
170
202
 
171
203
  `luv --clean` scans `~/prs/` and safely removes workspaces that are fully pushed. It checks:
@@ -176,12 +208,28 @@ Docker mode works with all flags: `-n` opens a bash shell in the container, `-r`
176
208
 
177
209
  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
210
 
211
+ Workspaces with a live tmux session are skipped unless you pass `-f`.
212
+
179
213
  ## Configuration
180
214
 
181
- Run `luv --init` to set your default GitHub org. This saves to `~/.luv/config.json`.
215
+ Run `luv config` for interactive setup, or `luv --init` to set just your default GitHub org. Both save to `~/.luv/config.json`:
216
+
217
+ ```json
218
+ {
219
+ "org": "exosphere",
220
+ "prs_dir": "~/prs",
221
+ "remote": {
222
+ "host": "box",
223
+ "identity_file": "~/.ssh/id_ed25519",
224
+ "dir": "~/prs"
225
+ }
226
+ }
227
+ ```
182
228
 
183
229
  You can also pass `org/repo` inline to override the default for any command (e.g., `luv other-org/my-repo`).
184
230
 
231
+ 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)**.
232
+
185
233
  ## License
186
234
 
187
235
  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
- # Configure your default GitHub org (one-time setup)
23
- luv --init
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,10 @@ luv -r my-repo 42
51
51
 
52
52
  # Clean up fully-merged workspaces
53
53
  luv --clean
54
+
55
+ # See what's running on your remote machine, and reattach
56
+ luv ls
57
+ luv continue
54
58
  ```
55
59
 
56
60
  ## How it works
@@ -58,15 +62,22 @@ luv --clean
58
62
  1. Clones the repo into `~/prs/{repo}-{number}/`
59
63
  2. Creates a new branch `luv-{number}`
60
64
  3. Configures the selected agent with the workspace's PR conventions
61
- 4. Launches Claude with Opus 4.8 at max effort, or Codex in YOLO mode
65
+ 4. Launches Claude with Opus 5 at max effort, or Codex in YOLO mode
62
66
 
63
67
  All workspaces live under `~/prs/`. The number comes from the repo's GitHub issue counter to avoid collisions.
64
68
 
69
+ With a remote host configured, steps 1–4 happen on that machine inside a tmux session instead — see [Remote sessions](#remote-sessions).
70
+
65
71
  ## Commands
66
72
 
67
73
  | Command | Description |
68
74
  |---------|-------------|
69
- | `luv --init` | Configure default GitHub org |
75
+ | `luv config` | Interactive setup: remote host, SSH key, default org |
76
+ | `luv config set\|get\|unset <key> [value]` | Read or write a single setting |
77
+ | `luv config list` | Show all settings |
78
+ | `luv --init` | Configure default GitHub org only |
79
+ | `luv ls [--host H] [--prune]` | List live sessions across hosts |
80
+ | `luv continue [<repo> [number]]` | Attach to a live session |
70
81
  | `luv [org/]<repo> [prompt...]` | Create a new workspace and launch Claude (default) |
71
82
  | `luv --codex [org/]<repo> [prompt...]` | Create a workspace and launch Codex in YOLO mode |
72
83
  | `luv [org/]<repo> -b <branch> [prompt...]` | Create a workspace based off `<branch>` instead of the default |
@@ -87,9 +98,12 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
87
98
  | `-r` | Resume: resume the selected agent's last session |
88
99
  | `-p` | Launch Claude in plan permission mode (default: `bypassPermissions`) |
89
100
  | `-nit` | Non-interactive: run the selected agent and exit (no REPL); Claude streams `stream-json` events to stdout |
90
- | `-m MODEL` | Model to use; Claude defaults to `claude-opus-4-8`, while Codex uses its configured CLI default |
101
+ | `-m MODEL` | Model to use; Claude defaults to `claude-opus-5`, while Codex uses its configured CLI default |
91
102
  | `-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
103
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
104
+ | `-s HOST` | Run on `HOST` over SSH, overriding the configured remote host |
105
+ | `-i PATH` | SSH identity file to use for this invocation |
106
+ | `--local` | Force local execution even when a remote host is configured |
93
107
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
94
108
  | `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
95
109
 
@@ -143,6 +157,24 @@ The `dev-environment` service **must** have the selected agent CLI (`claude` or
143
157
 
144
158
  Docker mode works with all flags: `-n` opens a bash shell in the container, `-r` resumes a Claude session in the container.
145
159
 
160
+ ## Remote sessions
161
+
162
+ Point `luv` at another machine and every workspace command runs there, inside a tmux session that survives disconnects.
163
+
164
+ ```bash
165
+ luv config # set the remote host, SSH key, and org
166
+ luv myrepo "fix the flaky test" # clones and launches Claude on the remote
167
+ # ...close the laptop, go home...
168
+ luv ls # see what's still running
169
+ luv continue # reattach exactly where you left off
170
+ ```
171
+
172
+ `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`.
173
+
174
+ Use `--local` for a one-off local run, `-s HOST` to target a different machine, and `-i PATH` for a different SSH key.
175
+
176
+ Full guide, including how to prepare a fresh Ubuntu box and set up SSH keys: **[docs/remote-sessions.md](docs/remote-sessions.md)**.
177
+
146
178
  ## Workspace cleanup
147
179
 
148
180
  `luv --clean` scans `~/prs/` and safely removes workspaces that are fully pushed. It checks:
@@ -153,12 +185,28 @@ Docker mode works with all flags: `-n` opens a bash shell in the container, `-r`
153
185
 
154
186
  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
187
 
188
+ Workspaces with a live tmux session are skipped unless you pass `-f`.
189
+
156
190
  ## Configuration
157
191
 
158
- Run `luv --init` to set your default GitHub org. This saves to `~/.luv/config.json`.
192
+ Run `luv config` for interactive setup, or `luv --init` to set just your default GitHub org. Both save to `~/.luv/config.json`:
193
+
194
+ ```json
195
+ {
196
+ "org": "exosphere",
197
+ "prs_dir": "~/prs",
198
+ "remote": {
199
+ "host": "box",
200
+ "identity_file": "~/.ssh/id_ed25519",
201
+ "dir": "~/prs"
202
+ }
203
+ }
204
+ ```
159
205
 
160
206
  You can also pass `org/repo` inline to override the default for any command (e.g., `luv other-org/my-repo`).
161
207
 
208
+ 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)**.
209
+
162
210
  ## License
163
211
 
164
212
  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`.