luv-cli 0.0.22__tar.gz → 0.0.23__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.22
3
+ Version: 0.0.23
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
@@ -48,6 +48,9 @@ luv --init
48
48
  # Create a new workspace and launch Claude
49
49
  luv my-repo "add user authentication"
50
50
 
51
+ # Base a new workspace off a non-default branch
52
+ luv my-repo -b develop "add user authentication"
53
+
51
54
  # Use a different org inline
52
55
  luv other-org/my-repo "fix the bug"
53
56
 
@@ -82,6 +85,7 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
82
85
  |---------|-------------|
83
86
  | `luv --init` | Configure default GitHub org |
84
87
  | `luv [org/]<repo> [prompt...]` | Create a new workspace and launch Claude |
88
+ | `luv [org/]<repo> -b <branch> [prompt...]` | Create a workspace based off `<branch>` instead of the default |
85
89
  | `luv [org/]<repo> <number> [prompt]` | Reopen an existing workspace |
86
90
  | `luv -l <PR URL> [prompt]` | Open any GitHub PR by URL |
87
91
  | `luv [org/]<repo> -pr <number> [prompt]` | Open a PR by repo + number |
@@ -98,6 +102,7 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
98
102
  | `-p` | Launch Claude in plan permission mode (default: `bypassPermissions`) |
99
103
  | `-nit` | Non-interactive: run `claude -p <prompt>` and exit (no REPL); streams `stream-json` events to stdout |
100
104
  | `-m MODEL` | Claude model to use (default: `claude-opus-4-8`); passed through to `claude --model`, so aliases like `opus`/`sonnet`/`haiku` work |
105
+ | `-b BRANCH` | Base a new workspace off `BRANCH` (clone + branch from it); recorded in `git config luv.base` so the PR can target it |
101
106
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
102
107
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
103
108
  | `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
@@ -25,6 +25,9 @@ luv --init
25
25
  # Create a new workspace and launch Claude
26
26
  luv my-repo "add user authentication"
27
27
 
28
+ # Base a new workspace off a non-default branch
29
+ luv my-repo -b develop "add user authentication"
30
+
28
31
  # Use a different org inline
29
32
  luv other-org/my-repo "fix the bug"
30
33
 
@@ -59,6 +62,7 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
59
62
  |---------|-------------|
60
63
  | `luv --init` | Configure default GitHub org |
61
64
  | `luv [org/]<repo> [prompt...]` | Create a new workspace and launch Claude |
65
+ | `luv [org/]<repo> -b <branch> [prompt...]` | Create a workspace based off `<branch>` instead of the default |
62
66
  | `luv [org/]<repo> <number> [prompt]` | Reopen an existing workspace |
63
67
  | `luv -l <PR URL> [prompt]` | Open any GitHub PR by URL |
64
68
  | `luv [org/]<repo> -pr <number> [prompt]` | Open a PR by repo + number |
@@ -75,6 +79,7 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
75
79
  | `-p` | Launch Claude in plan permission mode (default: `bypassPermissions`) |
76
80
  | `-nit` | Non-interactive: run `claude -p <prompt>` and exit (no REPL); streams `stream-json` events to stdout |
77
81
  | `-m MODEL` | Claude model to use (default: `claude-opus-4-8`); passed through to `claude --model`, so aliases like `opus`/`sonnet`/`haiku` work |
82
+ | `-b BRANCH` | Base a new workspace off `BRANCH` (clone + branch from it); recorded in `git config luv.base` so the PR can target it |
78
83
  | `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
79
84
  | `-f`, `--force` | Skip safety checks (with `--clean`) |
80
85
  | `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
@@ -663,6 +663,19 @@ def main() -> None:
663
663
  die("-m requires a model name")
664
664
  args = args[:idx] + args[idx + 2:]
665
665
 
666
+ # -b takes a value (base branch); extract before the boolean-flag strip below.
667
+ base = None
668
+ if args.count("-b") > 1:
669
+ die("-b may only be provided once")
670
+ if "-b" in args:
671
+ idx = args.index("-b")
672
+ if idx + 1 >= len(args):
673
+ die("-b requires a branch name")
674
+ base = args[idx + 1].strip()
675
+ if not base or base.startswith("-"):
676
+ die("-b requires a branch name")
677
+ args = args[:idx] + args[idx + 2:]
678
+
666
679
  args = [a for a in args if a not in ("-n", "-r", "-e", "-f", "--force", "-p", "-nit", "--safe")]
667
680
  extra_env = collect_luv_env() if env_mode else {}
668
681
 
@@ -676,6 +689,7 @@ Flags:
676
689
  -p launch Claude in plan permission mode (default: bypassPermissions)
677
690
  -nit non-interactive: run claude -p <prompt> and exit (no REPL)
678
691
  -m MODEL claude model to use (default: claude-opus-4-8)
692
+ -b BRANCH base a new workspace off BRANCH (clone + branch from it; recorded in git config luv.base)
679
693
  -e env: pass LUV_* environment variables (with prefix stripped) into the session
680
694
  -f, --force (with --clean) skip safety checks and delete all work folders
681
695
  --safe (with --clean -f) only delete folders older than 24h
@@ -683,6 +697,7 @@ Flags:
683
697
  Commands:
684
698
  luv --init configure default GitHub org
685
699
  luv [org/]<repo> [prompt...] create a new PR workspace
700
+ luv [org/]<repo> -b <branch> [prompt] create a workspace based off <branch>
686
701
  luv [org/]<repo> <number> [prompt] reopen an existing work folder by number
687
702
  luv -l <PR URL> [prompt] open any GitHub PR by URL
688
703
  luv [org/]<repo> -pr <number> [prompt] open a GitHub PR by repo + number
@@ -703,6 +718,19 @@ Docker:
703
718
  if safe and (args[0] != "--clean" or not force):
704
719
  die("--safe only works with --clean -f")
705
720
 
721
+ # -b only makes sense when creating a NEW workspace; reject it on every path
722
+ # that early-returns without cloning+branching from scratch.
723
+ if base is not None:
724
+ is_reopen_by_number = len(args) > 1 and args[1].isdigit()
725
+ opens_latest_local = (nav_mode or resume_mode) and len(args) == 1
726
+ if (args[0] in ("--clean", "--init", "-l")
727
+ or "-pr" in args
728
+ or is_reopen_by_number
729
+ or opens_latest_local):
730
+ die("-b only applies when creating a new workspace "
731
+ "(luv [org/]<repo> [prompt...]); it cannot be combined with "
732
+ "--clean, --init, -l, -pr, reopen-by-number, or bare -n/-r")
733
+
706
734
  if args[0] == "--clean":
707
735
  cmd_clean(force=force, safe=safe)
708
736
  return
@@ -771,6 +799,14 @@ Docker:
771
799
  if r.returncode != 0:
772
800
  die(f"repo '{org}/{repo}' not found or gh auth failed.\n{r.stderr.strip()}")
773
801
 
802
+ clone_url = f"https://github.com/{org}/{repo}"
803
+
804
+ # 1b. If a base branch was requested, verify it exists before cloning.
805
+ if base is not None:
806
+ r = run(["git", "ls-remote", "--heads", clone_url, base])
807
+ if r.returncode != 0 or f"refs/heads/{base}" not in r.stdout:
808
+ die(f"base branch '{base}' not found on {org}/{repo}")
809
+
774
810
  # 2. Get latest issue/PR number (shared counter on GitHub).
775
811
  # /issues is documented to include PRs but in practice returns [] for repos
776
812
  # with no plain issues, so query both endpoints and take the max.
@@ -791,20 +827,30 @@ Docker:
791
827
  candidate += 1
792
828
  clone_dir = PRS_DIR / f"{repo}-{candidate}"
793
829
 
794
- # 4. Clone
795
- clone_url = f"https://github.com/{org}/{repo}"
796
- print(f"luv: cloning {clone_url} -> {clone_dir}")
797
- r = subprocess.run(["git", "clone", clone_url, str(clone_dir)])
830
+ # 4. Clone (off the base branch when -b was given)
831
+ print(f"luv: cloning {clone_url} -> {clone_dir}" + (f" (base {base})" if base else ""))
832
+ clone_cmd = ["git", "clone"]
833
+ if base is not None:
834
+ clone_cmd += ["--branch", base]
835
+ clone_cmd += [clone_url, str(clone_dir)]
836
+ r = subprocess.run(clone_cmd)
798
837
  if r.returncode != 0:
799
838
  die(f"git clone failed (exit {r.returncode})")
800
839
 
801
- # 5. Create branch
840
+ # 5. Create branch off the cloned HEAD (= base when -b was given, else default)
802
841
  branch = f"luv-{candidate}"
803
842
  print(f"luv: creating branch {branch}")
804
843
  r = subprocess.run(["git", "checkout", "-b", branch], cwd=str(clone_dir))
805
844
  if r.returncode != 0:
806
845
  die(f"git checkout -b failed (exit {r.returncode})")
807
846
 
847
+ # 5b. Record the base so the eventual PR can target it (local .git/config only).
848
+ if base is not None:
849
+ r = run(["git", "config", "luv.base", base], cwd=str(clone_dir))
850
+ if r.returncode != 0:
851
+ print(f"luv: warning: could not record base branch ({r.stderr.strip()})",
852
+ file=sys.stderr)
853
+
808
854
  # 6. Ensure PR rules in ~/.claude/CLAUDE.md and bypass-permissions default
809
855
  ensure_pr_rules()
810
856
  ensure_default_permission_mode()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "luv-cli"
7
- version = "0.0.22"
7
+ version = "0.0.23"
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