luv-cli 0.0.4__tar.gz → 0.0.6__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.4
3
+ Version: 0.0.6
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
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import os
3
+ import random
3
4
  import re
4
5
  import shutil
5
6
  import subprocess
@@ -11,6 +12,14 @@ LUV_DIR = Path.home() / ".luv"
11
12
  CONFIG_FILE = LUV_DIR / "config.json"
12
13
  PRS_DIR = Path.home() / "prs"
13
14
  CLAUDE_JSON = Path.home() / ".claude.json"
15
+ CLAUDE_SETTINGS_JSON = Path.home() / ".claude" / "settings.json"
16
+
17
+ COLORS = ["red", "blue", "green", "yellow", "purple", "orange", "pink", "cyan", "default"]
18
+
19
+
20
+ def pick_color() -> str:
21
+ """Pick a random /color value so each luv session is visually distinct."""
22
+ return random.choice(COLORS)
14
23
 
15
24
  PR_RULES = """
16
25
  # Pull Request Management
@@ -146,6 +155,45 @@ def ensure_pr_rules() -> None:
146
155
  f.write(PR_RULES)
147
156
 
148
157
 
158
+ def ensure_default_permission_mode() -> None:
159
+ """Set permissions.defaultMode = bypassPermissions in ~/.claude/settings.json.
160
+
161
+ Merges into existing JSON without clobbering other keys. No-op if the file
162
+ exists but is unreadable/invalid, or if the value is already set.
163
+ """
164
+ data: dict[str, object] = {}
165
+ if CLAUDE_SETTINGS_JSON.exists():
166
+ try:
167
+ with CLAUDE_SETTINGS_JSON.open("r", encoding="utf-8") as f:
168
+ loaded = json.load(f)
169
+ if not isinstance(loaded, dict):
170
+ return
171
+ data = loaded
172
+ except (json.JSONDecodeError, OSError):
173
+ return
174
+
175
+ permissions = data.get("permissions")
176
+ if not isinstance(permissions, dict):
177
+ permissions = {}
178
+ data["permissions"] = permissions
179
+
180
+ if permissions.get("defaultMode") == "bypassPermissions":
181
+ return
182
+ permissions["defaultMode"] = "bypassPermissions"
183
+
184
+ CLAUDE_SETTINGS_JSON.parent.mkdir(parents=True, exist_ok=True)
185
+ with tempfile.NamedTemporaryFile(
186
+ "w",
187
+ encoding="utf-8",
188
+ dir=str(CLAUDE_SETTINGS_JSON.parent),
189
+ delete=False,
190
+ ) as tmp:
191
+ json.dump(data, tmp, indent=2)
192
+ tmp.write("\n")
193
+ tmp_path = Path(tmp.name)
194
+ os.replace(tmp_path, CLAUDE_SETTINGS_JSON)
195
+
196
+
149
197
  def cmd_init() -> None:
150
198
  """Interactive setup: choose a default GitHub org."""
151
199
  if not sys.stdin.isatty():
@@ -302,6 +350,9 @@ def launch(clone_dir: Path, prompt: str | None, extra_env: dict[str, str] = {})
302
350
  settings = load_luv_settings(clone_dir)
303
351
  compose_file = (settings or {}).get("compose_file")
304
352
 
353
+ color_cmd = f"/color {pick_color()}"
354
+ initial = f"{color_cmd}\n/plan {prompt}" if prompt else color_cmd
355
+
305
356
  if compose_file:
306
357
  project = docker_project_name(clone_dir)
307
358
  start_docker(clone_dir, compose_file, project)
@@ -309,9 +360,8 @@ def launch(clone_dir: Path, prompt: str | None, extra_env: dict[str, str] = {})
309
360
  base = docker_compose_base(clone_dir, compose_file, project)
310
361
  claude_cmd = ["claude", "--dangerously-skip-permissions",
311
362
  "--permission-mode", "bypassPermissions",
312
- "--model", "claude-opus-4-7", "--effort", "max"]
313
- if prompt:
314
- claude_cmd.append(f"/plan {prompt}")
363
+ "--model", "claude-opus-4-7", "--effort", "max",
364
+ initial]
315
365
  r = subprocess.run(base + ["exec", "-it"] + docker_env_flags(extra_env) + ["dev-environment"] + claude_cmd)
316
366
  sys.exit(r.returncode)
317
367
  finally:
@@ -324,10 +374,7 @@ def launch(clone_dir: Path, prompt: str | None, extra_env: dict[str, str] = {})
324
374
  "--permission-mode", "bypassPermissions",
325
375
  "--model", "claude-opus-4-7", "--effort", "max"]
326
376
  os.environ.update(extra_env)
327
- if prompt:
328
- os.execv(claude_bin, base_args + [f"/plan {prompt}"])
329
- else:
330
- os.execv(claude_bin, base_args)
377
+ os.execv(claude_bin, base_args + [initial])
331
378
 
332
379
 
333
380
  def cmd_clean(force: bool = False) -> None:
@@ -657,8 +704,9 @@ Docker:
657
704
  if r.returncode != 0:
658
705
  die(f"git checkout -b failed (exit {r.returncode})")
659
706
 
660
- # 6. Ensure PR rules in ~/.claude/CLAUDE.md
707
+ # 6. Ensure PR rules in ~/.claude/CLAUDE.md and bypass-permissions default
661
708
  ensure_pr_rules()
709
+ ensure_default_permission_mode()
662
710
 
663
711
  print(f"luv: ready — {clone_dir.name}, branch {branch}")
664
712
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "luv-cli"
7
- version = "0.0.4"
7
+ version = "0.0.6"
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
File without changes