luv-cli 0.0.19__tar.gz → 0.0.21__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.19
3
+ Version: 0.0.21
4
4
  Summary: Launch Claude Code 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
@@ -72,7 +72,7 @@ luv --clean
72
72
  1. Clones the repo into `~/prs/{repo}-{number}/`
73
73
  2. Creates a new branch `luv-{number}`
74
74
  3. Trusts the project in Claude Code config
75
- 4. Launches Claude with Opus and max effort
75
+ 4. Launches Claude with Opus 4.8 at max effort; prompt-less sessions auto-run `/effort ultracode` as their first input to enter ultracode mode
76
76
 
77
77
  All workspaces live under `~/prs/`. The number comes from the repo's GitHub issue counter to avoid collisions.
78
78
 
@@ -97,7 +97,7 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
97
97
  | `-r` | Resume: resume the last Claude session |
98
98
  | `-p` | Launch Claude in plan permission mode (default: `bypassPermissions`) |
99
99
  | `-nit` | Non-interactive: run `claude -p <prompt>` and exit (no REPL); streams `stream-json` events to stdout |
100
- | `-m MODEL` | Claude model to use (default: `claude-opus-4-7`); passed through to `claude --model`, so aliases like `opus`/`sonnet`/`haiku` work |
100
+ | `-m MODEL` | Claude model to use (default: `claude-opus-4-8`); passed through to `claude --model`, so aliases like `opus`/`sonnet`/`haiku` work |
101
101
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
102
102
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
103
103
  | `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
@@ -49,7 +49,7 @@ luv --clean
49
49
  1. Clones the repo into `~/prs/{repo}-{number}/`
50
50
  2. Creates a new branch `luv-{number}`
51
51
  3. Trusts the project in Claude Code config
52
- 4. Launches Claude with Opus and max effort
52
+ 4. Launches Claude with Opus 4.8 at max effort; prompt-less sessions auto-run `/effort ultracode` as their first input to enter ultracode mode
53
53
 
54
54
  All workspaces live under `~/prs/`. The number comes from the repo's GitHub issue counter to avoid collisions.
55
55
 
@@ -74,7 +74,7 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
74
74
  | `-r` | Resume: resume the last Claude session |
75
75
  | `-p` | Launch Claude in plan permission mode (default: `bypassPermissions`) |
76
76
  | `-nit` | Non-interactive: run `claude -p <prompt>` and exit (no REPL); streams `stream-json` events to stdout |
77
- | `-m MODEL` | Claude model to use (default: `claude-opus-4-7`); passed through to `claude --model`, so aliases like `opus`/`sonnet`/`haiku` work |
77
+ | `-m MODEL` | Claude model to use (default: `claude-opus-4-8`); passed through to `claude --model`, so aliases like `opus`/`sonnet`/`haiku` work |
78
78
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
79
79
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
80
80
  | `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  import os
3
- import random
4
3
  import re
5
4
  import shutil
6
5
  import stat
@@ -16,13 +15,6 @@ PRS_DIR = Path.home() / "prs"
16
15
  CLAUDE_JSON = Path.home() / ".claude.json"
17
16
  CLAUDE_SETTINGS_JSON = Path.home() / ".claude" / "settings.json"
18
17
 
19
- COLORS = ["red", "blue", "green", "yellow", "purple", "orange", "pink", "cyan", "default"]
20
-
21
-
22
- def pick_color() -> str:
23
- """Pick a random /color value so each luv session is visually distinct."""
24
- return random.choice(COLORS)
25
-
26
18
  PR_RULES = """
27
19
  # Pull Request Management
28
20
 
@@ -318,7 +310,7 @@ def navigate(clone_dir: Path, extra_env: dict[str, str] = {}) -> None:
318
310
 
319
311
 
320
312
  def resume(clone_dir: Path, extra_env: dict[str, str] | None = None,
321
- model: str = "claude-opus-4-7") -> None:
313
+ model: str = "claude-opus-4-8") -> None:
322
314
  """Trust, chdir, and exec claude --resume — replacing this process."""
323
315
  extra_env = extra_env or {}
324
316
  trust_project(clone_dir)
@@ -353,7 +345,7 @@ def resume(clone_dir: Path, extra_env: dict[str, str] | None = None,
353
345
 
354
346
  def launch(clone_dir: Path, prompt: str | None, plan_mode: bool = False,
355
347
  non_interactive: bool = False, extra_env: dict[str, str] | None = None,
356
- model: str = "claude-opus-4-7") -> None:
348
+ model: str = "claude-opus-4-8") -> None:
357
349
  """Trust, resolve claude, chdir, and exec — replacing this process."""
358
350
  extra_env = extra_env or {}
359
351
  trust_project(clone_dir)
@@ -374,10 +366,10 @@ def launch(clone_dir: Path, prompt: str | None, plan_mode: bool = False,
374
366
  initial_args = ["-p", prompt]
375
367
  elif plan_mode:
376
368
  mode_flags = ["--permission-mode", "plan"]
377
- initial_args = [prompt] if prompt else [f"/color {pick_color()}"]
369
+ initial_args = [prompt] if prompt else ["/effort ultracode"]
378
370
  else:
379
371
  mode_flags = ["--permission-mode", "bypassPermissions"]
380
- initial_args = [prompt] if prompt else [f"/color {pick_color()}"]
372
+ initial_args = [prompt] if prompt else ["/effort ultracode"]
381
373
 
382
374
  if compose_file:
383
375
  project = docker_project_name(clone_dir)
@@ -411,10 +403,36 @@ def _on_rm_error(func, path, _exc):
411
403
  func(path)
412
404
 
413
405
 
406
+ def _docker_wipe(path: Path) -> bool:
407
+ """rm -rf `path` from inside a busybox container so the container's root can
408
+ delete files that a previous container bind-mounted in as root (e.g. Rust
409
+ target dirs built inside the workspace's dev-environment). Returns True on
410
+ success. No-op if docker isn't available."""
411
+ if shutil.which("docker") is None:
412
+ return False
413
+ parent = path.resolve().parent
414
+ name = path.name
415
+ r = subprocess.run(
416
+ ["docker", "run", "--rm",
417
+ "-v", f"{parent}:/p",
418
+ "busybox", "rm", "-rf", f"/p/{name}"],
419
+ capture_output=True, text=True,
420
+ )
421
+ if r.returncode != 0:
422
+ sys.stderr.write(f"luv: docker rm fallback failed for {path}: {r.stderr.strip()}\n")
423
+ return False
424
+ return not path.exists()
425
+
426
+
414
427
  def _force_rmtree(path: Path) -> None:
415
- """rmtree that chmods read-only files (e.g. uv's CACHEDIR.TAG) instead of crashing."""
428
+ """rmtree that survives read-only files (chmod-and-retry) and root-owned
429
+ files left behind by Docker bind-mounts (containerized rm -rf fallback)."""
416
430
  kwargs = {"onexc": _on_rm_error} if sys.version_info >= (3, 12) else {"onerror": _on_rm_error}
417
- shutil.rmtree(path, **kwargs)
431
+ try:
432
+ shutil.rmtree(path, **kwargs)
433
+ except PermissionError:
434
+ if not _docker_wipe(path):
435
+ raise
418
436
 
419
437
 
420
438
  def cmd_clean(force: bool = False, safe: bool = False) -> None:
@@ -526,7 +544,7 @@ def find_latest_clone(repo: str) -> Path | None:
526
544
  return best
527
545
 
528
546
 
529
- def open_existing(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool = False, resume_mode: bool = False, plan_mode: bool = False, non_interactive: bool = False, extra_env: dict[str, str] | None = None, model: str = "claude-opus-4-7") -> None:
547
+ def open_existing(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool = False, resume_mode: bool = False, plan_mode: bool = False, non_interactive: bool = False, extra_env: dict[str, str] | None = None, model: str = "claude-opus-4-8") -> None:
530
548
  """Open an existing work folder or remote branch by number."""
531
549
  extra_env = extra_env or {}
532
550
  clone_dir = PRS_DIR / f"{repo}-{number}"
@@ -570,7 +588,7 @@ def open_existing(org: str, repo: str, number: int, prompt: str | None, nav_mode
570
588
  launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env, model=model)
571
589
 
572
590
 
573
- def open_pr(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool = False, resume_mode: bool = False, plan_mode: bool = False, non_interactive: bool = False, extra_env: dict[str, str] | None = None, model: str = "claude-opus-4-7") -> None:
591
+ def open_pr(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool = False, resume_mode: bool = False, plan_mode: bool = False, non_interactive: bool = False, extra_env: dict[str, str] | None = None, model: str = "claude-opus-4-8") -> None:
574
592
  """Open any GitHub PR by org/repo/number, cloning if needed."""
575
593
  extra_env = extra_env or {}
576
594
  clone_dir = PRS_DIR / f"{repo}-{number}"
@@ -625,7 +643,7 @@ def main() -> None:
625
643
  env_mode = "-e" in args
626
644
 
627
645
  # -m takes a value, so extract it before the boolean-flag strip below
628
- model = "claude-opus-4-7"
646
+ model = "claude-opus-4-8"
629
647
  if args.count("-m") > 1:
630
648
  die("-m may only be provided once")
631
649
  if "-m" in args:
@@ -649,7 +667,7 @@ Flags:
649
667
  -r resume: resume the last Claude session in the work folder
650
668
  -p launch Claude in plan permission mode (default: bypassPermissions)
651
669
  -nit non-interactive: run claude -p <prompt> and exit (no REPL)
652
- -m MODEL claude model to use (default: claude-opus-4-7)
670
+ -m MODEL claude model to use (default: claude-opus-4-8)
653
671
  -e env: pass LUV_* environment variables (with prefix stripped) into the session
654
672
  -f, --force (with --clean) skip safety checks and delete all work folders
655
673
  --safe (with --clean -f) only delete folders older than 24h
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "luv-cli"
7
- version = "0.0.19"
7
+ version = "0.0.21"
8
8
  description = "Launch Claude Code agents on GitHub repos with isolated workspaces and optional Docker dev environments"
9
9
  requires-python = ">=3.10"
10
10
  license = "MIT"
File without changes
File without changes
File without changes