luv-cli 0.0.13__tar.gz → 0.0.16__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.13
3
+ Version: 0.0.16
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
@@ -94,6 +94,8 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
94
94
  |------|-------------|
95
95
  | `-n` | Navigate: open a shell instead of Claude |
96
96
  | `-r` | Resume: resume the last Claude session |
97
+ | `-p` | Launch Claude in plan permission mode (default: `bypassPermissions`) |
98
+ | `-nit` | Non-interactive: run `claude -p <prompt>` and exit (no REPL); streams `stream-json` events to stdout |
97
99
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
98
100
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
99
101
 
@@ -71,6 +71,8 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
71
71
  |------|-------------|
72
72
  | `-n` | Navigate: open a shell instead of Claude |
73
73
  | `-r` | Resume: resume the last Claude session |
74
+ | `-p` | Launch Claude in plan permission mode (default: `bypassPermissions`) |
75
+ | `-nit` | Non-interactive: run `claude -p <prompt>` and exit (no REPL); streams `stream-json` events to stdout |
74
76
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
75
77
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
76
78
 
@@ -347,7 +347,8 @@ def resume(clone_dir: Path, extra_env: dict[str, str] = {}) -> None:
347
347
  "--remote-control-session-name-prefix", clone_dir.name])
348
348
 
349
349
 
350
- def launch(clone_dir: Path, prompt: str | None, extra_env: dict[str, str] = {}) -> None:
350
+ def launch(clone_dir: Path, prompt: str | None, plan_mode: bool = False,
351
+ non_interactive: bool = False, extra_env: dict[str, str] = {}) -> None:
351
352
  """Trust, resolve claude, chdir, and exec — replacing this process."""
352
353
  trust_project(clone_dir)
353
354
  os.chdir(str(clone_dir))
@@ -359,12 +360,18 @@ def launch(clone_dir: Path, prompt: str | None, extra_env: dict[str, str] = {})
359
360
  "--effort", "max",
360
361
  "--remote-control",
361
362
  "--remote-control-session-name-prefix", clone_dir.name]
362
- if prompt:
363
+ if non_interactive:
364
+ if not prompt:
365
+ die("-nit requires a prompt")
366
+ mode_flags = ["--output-format", "stream-json",
367
+ "--verbose", "--include-partial-messages"]
368
+ initial_args = ["-p", prompt]
369
+ elif plan_mode:
363
370
  mode_flags = ["--permission-mode", "plan"]
364
- initial_args = [prompt]
371
+ initial_args = [prompt] if prompt else [f"/color {pick_color()}"]
365
372
  else:
366
373
  mode_flags = ["--permission-mode", "bypassPermissions"]
367
- initial_args = [f"/color {pick_color()}"]
374
+ initial_args = [prompt] if prompt else [f"/color {pick_color()}"]
368
375
 
369
376
  if compose_file:
370
377
  project = docker_project_name(clone_dir)
@@ -489,7 +496,7 @@ def find_latest_clone(repo: str) -> Path | None:
489
496
  return best
490
497
 
491
498
 
492
- def open_existing(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool = False, resume_mode: bool = False, extra_env: dict[str, str] = {}) -> None:
499
+ 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:
493
500
  """Open an existing work folder or remote branch by number."""
494
501
  clone_dir = PRS_DIR / f"{repo}-{number}"
495
502
 
@@ -502,7 +509,7 @@ def open_existing(org: str, repo: str, number: int, prompt: str | None, nav_mode
502
509
  elif resume_mode:
503
510
  resume(clone_dir, extra_env=extra_env)
504
511
  else:
505
- launch(clone_dir, prompt, extra_env=extra_env)
512
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
506
513
  return # unreachable
507
514
 
508
515
  # 2. Check remote branch luv-{number}
@@ -529,10 +536,10 @@ def open_existing(org: str, repo: str, number: int, prompt: str | None, nav_mode
529
536
  elif resume_mode:
530
537
  resume(clone_dir, extra_env=extra_env)
531
538
  else:
532
- launch(clone_dir, prompt, extra_env=extra_env)
539
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
533
540
 
534
541
 
535
- def open_pr(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool = False, resume_mode: bool = False, extra_env: dict[str, str] = {}) -> None:
542
+ 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:
536
543
  """Open any GitHub PR by org/repo/number, cloning if needed."""
537
544
  clone_dir = PRS_DIR / f"{repo}-{number}"
538
545
 
@@ -544,7 +551,7 @@ def open_pr(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool
544
551
  elif resume_mode:
545
552
  resume(clone_dir, extra_env=extra_env)
546
553
  else:
547
- launch(clone_dir, prompt, extra_env=extra_env)
554
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
548
555
  return # unreachable
549
556
 
550
557
  # Resolve the actual branch name via GitHub API
@@ -571,7 +578,7 @@ def open_pr(org: str, repo: str, number: int, prompt: str | None, nav_mode: bool
571
578
  elif resume_mode:
572
579
  resume(clone_dir, extra_env=extra_env)
573
580
  else:
574
- launch(clone_dir, prompt, extra_env=extra_env)
581
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
575
582
 
576
583
 
577
584
  def main() -> None:
@@ -579,9 +586,11 @@ def main() -> None:
579
586
 
580
587
  nav_mode = "-n" in args
581
588
  resume_mode = "-r" in args
589
+ plan_mode = "-p" in args
590
+ non_interactive = "-nit" in args
582
591
  force = "-f" in args or "--force" in args
583
592
  env_mode = "-e" in args
584
- args = [a for a in args if a not in ("-n", "-r", "-e", "-f", "--force")]
593
+ args = [a for a in args if a not in ("-n", "-r", "-e", "-f", "--force", "-p", "-nit")]
585
594
  extra_env = collect_luv_env() if env_mode else {}
586
595
 
587
596
  if not args or args[0] in ("-h", "--help"):
@@ -591,6 +600,8 @@ Usage: luv [flags] <command>
591
600
  Flags:
592
601
  -n navigate: open a shell in the work folder instead of launching Claude
593
602
  -r resume: resume the last Claude session in the work folder
603
+ -p launch Claude in plan permission mode (default: bypassPermissions)
604
+ -nit non-interactive: run claude -p <prompt> and exit (no REPL)
594
605
  -e env: pass LUV_* environment variables (with prefix stripped) into the session
595
606
  -f, --force (with --clean) skip safety checks and delete all work folders
596
607
 
@@ -632,7 +643,7 @@ Docker:
632
643
  die(f"cannot parse PR URL: {url}")
633
644
  org, repo, number = m.group(1), m.group(2), int(m.group(3))
634
645
  prompt = " ".join(args[2:]) or None
635
- open_pr(org, repo, number, prompt, nav_mode, resume_mode, extra_env=extra_env)
646
+ open_pr(org, repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env)
636
647
  return
637
648
 
638
649
  raw = args[0].rstrip("/")
@@ -652,14 +663,14 @@ Docker:
652
663
  die(f"expected a PR number after -pr, got '{args[idx + 1]}'")
653
664
  prompt_parts = [a for i, a in enumerate(args) if i not in (0, idx, idx + 1)]
654
665
  prompt = " ".join(prompt_parts) or None
655
- open_pr(resolve_org(explicit_org), repo, number, prompt, nav_mode, resume_mode, extra_env=extra_env)
666
+ open_pr(resolve_org(explicit_org), repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env)
656
667
  return
657
668
 
658
669
  # Detect optional numeric second argument
659
670
  if len(args) > 1 and args[1].isdigit():
660
671
  number = int(args[1])
661
672
  prompt = " ".join(args[2:]) or None
662
- open_existing(resolve_org(explicit_org), repo, number, prompt, nav_mode, resume_mode, extra_env=extra_env)
673
+ open_existing(resolve_org(explicit_org), repo, number, prompt, nav_mode, resume_mode, plan_mode, non_interactive, extra_env=extra_env)
663
674
  return
664
675
 
665
676
  org = resolve_org(explicit_org)
@@ -728,4 +739,4 @@ Docker:
728
739
  elif resume_mode:
729
740
  resume(clone_dir, extra_env=extra_env)
730
741
  else:
731
- launch(clone_dir, prompt, extra_env=extra_env)
742
+ launch(clone_dir, prompt, plan_mode=plan_mode, non_interactive=non_interactive, extra_env=extra_env)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "luv-cli"
7
- version = "0.0.13"
7
+ version = "0.0.16"
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