luv-cli 0.0.17__tar.gz → 0.0.18__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.17
3
+ Version: 0.0.18
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
@@ -97,6 +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
101
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
101
102
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
102
103
  | `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
@@ -74,6 +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
78
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
78
79
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
79
80
  | `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
@@ -316,8 +316,10 @@ def navigate(clone_dir: Path, extra_env: dict[str, str] = {}) -> None:
316
316
  os.execv(shell, [shell])
317
317
 
318
318
 
319
- def resume(clone_dir: Path, extra_env: dict[str, str] = {}) -> None:
319
+ def resume(clone_dir: Path, extra_env: dict[str, str] | None = None,
320
+ model: str = "claude-opus-4-7") -> None:
320
321
  """Trust, chdir, and exec claude --resume — replacing this process."""
322
+ extra_env = extra_env or {}
321
323
  trust_project(clone_dir)
322
324
  os.chdir(str(clone_dir))
323
325
  settings = load_luv_settings(clone_dir)
@@ -330,7 +332,7 @@ def resume(clone_dir: Path, extra_env: dict[str, str] = {}) -> None:
330
332
  base = docker_compose_base(clone_dir, compose_file, project)
331
333
  r = subprocess.run(base + ["exec", "-it"] + docker_env_flags(extra_env) + ["dev-environment",
332
334
  "claude", "--dangerously-skip-permissions",
333
- "--model", "claude-opus-4-7",
335
+ "--model", model,
334
336
  "--effort", "max", "--resume",
335
337
  "--remote-control",
336
338
  "--remote-control-session-name-prefix", clone_dir.name])
@@ -343,21 +345,23 @@ def resume(clone_dir: Path, extra_env: dict[str, str] = {}) -> None:
343
345
  die("'claude' not found in PATH")
344
346
  os.environ.update(extra_env)
345
347
  os.execv(claude_bin, [claude_bin, "--dangerously-skip-permissions",
346
- "--model", "claude-opus-4-7", "--effort", "max", "--resume",
348
+ "--model", model, "--effort", "max", "--resume",
347
349
  "--remote-control",
348
350
  "--remote-control-session-name-prefix", clone_dir.name])
349
351
 
350
352
 
351
353
  def launch(clone_dir: Path, prompt: str | None, plan_mode: bool = False,
352
- non_interactive: bool = False, extra_env: dict[str, str] = {}) -> None:
354
+ non_interactive: bool = False, extra_env: dict[str, str] | None = None,
355
+ model: str = "claude-opus-4-7") -> None:
353
356
  """Trust, resolve claude, chdir, and exec — replacing this process."""
357
+ extra_env = extra_env or {}
354
358
  trust_project(clone_dir)
355
359
  os.chdir(str(clone_dir))
356
360
  settings = load_luv_settings(clone_dir)
357
361
  compose_file = (settings or {}).get("compose_file")
358
362
 
359
363
  common_flags = ["--dangerously-skip-permissions",
360
- "--model", "claude-opus-4-7",
364
+ "--model", model,
361
365
  "--effort", "max",
362
366
  "--remote-control",
363
367
  "--remote-control-session-name-prefix", clone_dir.name]
@@ -504,8 +508,9 @@ def find_latest_clone(repo: str) -> Path | None:
504
508
  return best
505
509
 
506
510
 
507
- 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:
511
+ 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:
508
512
  """Open an existing work folder or remote branch by number."""
513
+ extra_env = extra_env or {}
509
514
  clone_dir = PRS_DIR / f"{repo}-{number}"
510
515
 
511
516
  # 1. Local folder takes priority
@@ -515,9 +520,9 @@ def open_existing(org: str, repo: str, number: int, prompt: str | None, nav_mode
515
520
  if nav_mode:
516
521
  navigate(clone_dir, extra_env=extra_env)
517
522
  elif resume_mode:
518
- resume(clone_dir, extra_env=extra_env)
523
+ resume(clone_dir, extra_env=extra_env, model=model)
519
524
  else:
520
- launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
525
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env, model=model)
521
526
  return # unreachable
522
527
 
523
528
  # 2. Check remote branch luv-{number}
@@ -542,13 +547,14 @@ def open_existing(org: str, repo: str, number: int, prompt: str | None, nav_mode
542
547
  if nav_mode:
543
548
  navigate(clone_dir, extra_env=extra_env)
544
549
  elif resume_mode:
545
- resume(clone_dir, extra_env=extra_env)
550
+ resume(clone_dir, extra_env=extra_env, model=model)
546
551
  else:
547
- launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
552
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env, model=model)
548
553
 
549
554
 
550
- 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:
555
+ 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:
551
556
  """Open any GitHub PR by org/repo/number, cloning if needed."""
557
+ extra_env = extra_env or {}
552
558
  clone_dir = PRS_DIR / f"{repo}-{number}"
553
559
 
554
560
  if clone_dir.exists():
@@ -557,9 +563,9 @@ def open_pr(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool
557
563
  if nav_mode:
558
564
  navigate(clone_dir, extra_env=extra_env)
559
565
  elif resume_mode:
560
- resume(clone_dir, extra_env=extra_env)
566
+ resume(clone_dir, extra_env=extra_env, model=model)
561
567
  else:
562
- launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
568
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env, model=model)
563
569
  return # unreachable
564
570
 
565
571
  # Resolve the actual branch name via GitHub API
@@ -584,9 +590,9 @@ def open_pr(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool
584
590
  if nav_mode:
585
591
  navigate(clone_dir, extra_env=extra_env)
586
592
  elif resume_mode:
587
- resume(clone_dir, extra_env=extra_env)
593
+ resume(clone_dir, extra_env=extra_env, model=model)
588
594
  else:
589
- launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
595
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env, model=model)
590
596
 
591
597
 
592
598
  def main() -> None:
@@ -599,6 +605,20 @@ def main() -> None:
599
605
  force = "-f" in args or "--force" in args
600
606
  safe = "--safe" in args
601
607
  env_mode = "-e" in args
608
+
609
+ # -m takes a value, so extract it before the boolean-flag strip below
610
+ model = "claude-opus-4-7"
611
+ if args.count("-m") > 1:
612
+ die("-m may only be provided once")
613
+ if "-m" in args:
614
+ idx = args.index("-m")
615
+ if idx + 1 >= len(args):
616
+ die("-m requires a model name")
617
+ model = args[idx + 1].strip()
618
+ if not model or model.startswith("-"):
619
+ die("-m requires a model name")
620
+ args = args[:idx] + args[idx + 2:]
621
+
602
622
  args = [a for a in args if a not in ("-n", "-r", "-e", "-f", "--force", "-p", "-nit", "--safe")]
603
623
  extra_env = collect_luv_env() if env_mode else {}
604
624
 
@@ -611,6 +631,7 @@ Flags:
611
631
  -r resume: resume the last Claude session in the work folder
612
632
  -p launch Claude in plan permission mode (default: bypassPermissions)
613
633
  -nit non-interactive: run claude -p <prompt> and exit (no REPL)
634
+ -m MODEL claude model to use (default: claude-opus-4-7)
614
635
  -e env: pass LUV_* environment variables (with prefix stripped) into the session
615
636
  -f, --force (with --clean) skip safety checks and delete all work folders
616
637
  --safe (with --clean -f) only delete folders older than 24h
@@ -656,7 +677,7 @@ Docker:
656
677
  die(f"cannot parse PR URL: {url}")
657
678
  org, repo, number = m.group(1), m.group(2), int(m.group(3))
658
679
  prompt = " ".join(args[2:]) or None
659
- open_pr(org, repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env)
680
+ open_pr(org, repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env, model=model)
660
681
  return
661
682
 
662
683
  raw = args[0].rstrip("/")
@@ -676,14 +697,14 @@ Docker:
676
697
  die(f"expected a PR number after -pr, got '{args[idx + 1]}'")
677
698
  prompt_parts = [a for i, a in enumerate(args) if i not in (0, idx, idx + 1)]
678
699
  prompt = " ".join(prompt_parts) or None
679
- open_pr(resolve_org(explicit_org), repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env)
700
+ open_pr(resolve_org(explicit_org), repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env, model=model)
680
701
  return
681
702
 
682
703
  # Detect optional numeric second argument
683
704
  if len(args) > 1 and args[1].isdigit():
684
705
  number = int(args[1])
685
706
  prompt = " ".join(args[2:]) or None
686
- open_existing(resolve_org(explicit_org), repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env)
707
+ open_existing(resolve_org(explicit_org), repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env, model=model)
687
708
  return
688
709
 
689
710
  org = resolve_org(explicit_org)
@@ -698,7 +719,7 @@ Docker:
698
719
  if nav_mode:
699
720
  navigate(clone_dir, extra_env=extra_env)
700
721
  else:
701
- resume(clone_dir, extra_env=extra_env)
722
+ resume(clone_dir, extra_env=extra_env, model=model)
702
723
  return
703
724
 
704
725
  # 1. Verify repo exists
@@ -750,6 +771,6 @@ Docker:
750
771
  if nav_mode:
751
772
  navigate(clone_dir, extra_env=extra_env)
752
773
  elif resume_mode:
753
- resume(clone_dir, extra_env=extra_env)
774
+ resume(clone_dir, extra_env=extra_env, model=model)
754
775
  else:
755
- launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
776
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env, model=model)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "luv-cli"
7
- version = "0.0.17"
7
+ version = "0.0.18"
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