luv-cli 0.6.2__tar.gz → 0.6.3__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.
- {luv_cli-0.6.2 → luv_cli-0.6.3}/PKG-INFO +1 -1
- {luv_cli-0.6.2 → luv_cli-0.6.3}/luv/__init__.py +10 -5
- {luv_cli-0.6.2 → luv_cli-0.6.3}/pyproject.toml +1 -1
- {luv_cli-0.6.2 → luv_cli-0.6.3}/tests/test_agents.py +28 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/.claude/settings.json +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/.failproofai/policies/version-policies.mjs +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/.failproofai/policies-config.json +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/.github/workflows/publish.yml +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/.gitignore +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/LICENSE +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/README.md +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/claude-output.txt +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/docs/configuration.md +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/docs/remote-sessions.md +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/docs/sessions.md +0 -0
- {luv_cli-0.6.2 → luv_cli-0.6.3}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: luv-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: Launch Claude Code or Codex 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
|
|
@@ -59,12 +59,14 @@ def die(msg: str) -> None:
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def run(cmd: list[str], *, cwd: str | None = None, timeout: float | None = None,
|
|
62
|
-
stdin: int | None = None
|
|
62
|
+
stdin: int | None = None,
|
|
63
|
+
new_session: bool = False) -> subprocess.CompletedProcess:
|
|
63
64
|
"""Capture a subprocess. A timeout surfaces as a failure, not an exception,
|
|
64
65
|
so callers keep their plain returncode checks."""
|
|
65
66
|
try:
|
|
66
67
|
return subprocess.run(cmd, capture_output=True, text=True, cwd=cwd,
|
|
67
|
-
timeout=timeout, stdin=stdin
|
|
68
|
+
timeout=timeout, stdin=stdin,
|
|
69
|
+
start_new_session=new_session)
|
|
68
70
|
except subprocess.TimeoutExpired:
|
|
69
71
|
return subprocess.CompletedProcess(cmd, 124, "", f"timed out after {timeout}s")
|
|
70
72
|
|
|
@@ -773,10 +775,13 @@ def shell_env() -> dict[str, str]:
|
|
|
773
775
|
code = ("import json,os,sys;"
|
|
774
776
|
f"sys.stdout.write({marker!r} + json.dumps(dict(os.environ)))")
|
|
775
777
|
probe = f"{shlex.quote(sys.executable)} -c {shlex.quote(code)}"
|
|
776
|
-
#
|
|
777
|
-
#
|
|
778
|
+
# A session of its own, so the probe has no controlling tty to touch. An
|
|
779
|
+
# interactive zsh opens /dev/tty whatever stdin is and makes its own process
|
|
780
|
+
# group the foreground one, then execs the probe rather than forking it, so
|
|
781
|
+
# nothing is left to hand the terminal back. luv and the agent it execs would
|
|
782
|
+
# be in the background, and the agent's first tcsetattr gets SIGTTOU.
|
|
778
783
|
r = run([shell, "-lic", probe], timeout=SHELL_ENV_TIMEOUT,
|
|
779
|
-
stdin=subprocess.DEVNULL)
|
|
784
|
+
stdin=subprocess.DEVNULL, new_session=True)
|
|
780
785
|
# Match on the marker, not the exit status: rc files greet, warn, and fail
|
|
781
786
|
# in ways that say nothing about whether the environment came back. What
|
|
782
787
|
# precedes the marker is the greeting; raw_decode drops anything an exit
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "luv-cli"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.3"
|
|
8
8
|
description = "Launch Claude Code or Codex agents on GitHub repos with isolated workspaces and optional Docker dev environments"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
license = "MIT"
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import io
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
5
|
+
import pty
|
|
4
6
|
import re
|
|
7
|
+
import shutil
|
|
5
8
|
import subprocess
|
|
6
9
|
import tempfile
|
|
7
10
|
import time
|
|
@@ -880,8 +883,33 @@ class ShellEnvTests(unittest.TestCase):
|
|
|
880
883
|
self.assertEqual(env, {"API_BASE": "https://x"})
|
|
881
884
|
self.assertEqual(call.args[0][:2], ["/bin/zsh", "-lic"])
|
|
882
885
|
self.assertEqual(call.kwargs["stdin"], subprocess.DEVNULL)
|
|
886
|
+
self.assertTrue(call.kwargs["new_session"])
|
|
883
887
|
self.assertEqual(call.kwargs["timeout"], luv.SHELL_ENV_TIMEOUT)
|
|
884
888
|
|
|
889
|
+
@unittest.skipUnless(shutil.which("zsh"), "needs zsh")
|
|
890
|
+
def test_probe_leaves_the_terminal_in_the_foreground_of_the_caller(self):
|
|
891
|
+
# A real zsh on a real pty, because only that shows the failure: zsh
|
|
892
|
+
# takes the foreground and execs the probe, so the caller, and the agent
|
|
893
|
+
# it later execs, would be stopped by SIGTTOU on touching the terminal.
|
|
894
|
+
with tempfile.TemporaryDirectory() as zdotdir:
|
|
895
|
+
pid, fd = pty.fork()
|
|
896
|
+
if pid == 0:
|
|
897
|
+
status = 1
|
|
898
|
+
try:
|
|
899
|
+
with patch.dict(luv.os.environ,
|
|
900
|
+
{"SHELL": "zsh", "ZDOTDIR": zdotdir}):
|
|
901
|
+
luv.shell_env()
|
|
902
|
+
status = 0 if os.tcgetpgrp(0) == os.getpgrp() else 1
|
|
903
|
+
finally:
|
|
904
|
+
os._exit(status)
|
|
905
|
+
with contextlib.suppress(OSError):
|
|
906
|
+
while os.read(fd, 4096):
|
|
907
|
+
pass
|
|
908
|
+
os.close(fd)
|
|
909
|
+
_, status = os.waitpid(pid, 0)
|
|
910
|
+
|
|
911
|
+
self.assertEqual(os.waitstatus_to_exitcode(status), 0)
|
|
912
|
+
|
|
885
913
|
def test_greeting_before_the_marker_is_ignored(self):
|
|
886
914
|
env, _ = self._probe(_shell_env_output({"A": "1"}, before="welcome back!\n"))
|
|
887
915
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|