diff --git a/scripts/check_default_images.py b/scripts/check_default_images.py index 6281576..969e939 100644 --- a/scripts/check_default_images.py +++ b/scripts/check_default_images.py @@ -29,10 +29,7 @@ def _check_image_exists(image: str) -> tuple[bool, str]: except subprocess.TimeoutExpired: return ( False, - ( - "timed out after " - f"{MANIFEST_CHECK_TIMEOUT_SECONDS}s while checking docker manifest" - ), + (f"timed out after {MANIFEST_CHECK_TIMEOUT_SECONDS}s while checking docker manifest"), ) if proc.returncode == 0: return True, "" diff --git a/src/vibepod/commands/attach.py b/src/vibepod/commands/attach.py index df061dd..f18d912 100644 --- a/src/vibepod/commands/attach.py +++ b/src/vibepod/commands/attach.py @@ -79,9 +79,7 @@ def attach( agent = (getattr(target, "labels", {}) or {}).get("vibepod.agent", "agent") info(f"Attaching to {target.name} ({agent})") - warning( - f"Close the terminal to leave it running, or stop it with `vp stop {target.name}`." - ) + warning(f"Close the terminal to leave it running, or stop it with `vp stop {target.name}`.") try: manager.attach_interactive(target) except DockerClientError as exc: diff --git a/src/vibepod/commands/doctor.py b/src/vibepod/commands/doctor.py index 5dd7bab..d65585a 100644 --- a/src/vibepod/commands/doctor.py +++ b/src/vibepod/commands/doctor.py @@ -167,9 +167,7 @@ def claude() -> None: console.print(f" {key}: {masked}") if not found_any: console.print(" none set on host") - console.print( - " [dim]note: these are host-side; the container sees its own env.[/dim]" - ) + console.print(" [dim]note: these are host-side; the container sees its own env.[/dim]") console.print() console.print("[bold]Effective auth mode on next `vp run claude`[/bold]") @@ -196,12 +194,8 @@ def claude() -> None: " • If `modified` on .credentials.json never updates past the original /login time," ) console.print(" the token is not being rotated. Re-run with:") - console.print( - " [cyan]vp run claude -e ANTHROPIC_LOG=debug -e DEBUG=1[/cyan]" - ) - console.print( - " and look for [dim][API:auth][/dim] entries near/after expiry to confirm." - ) + console.print(" [cyan]vp run claude -e ANTHROPIC_LOG=debug -e DEBUG=1[/cyan]") + console.print(" and look for [dim][API:auth][/dim] entries near/after expiry to confirm.") console.print( " • For headless/CI, consider `claude setup-token` + " "`-e CLAUDE_CODE_OAUTH_TOKEN=...` to bypass refresh entirely." diff --git a/src/vibepod/commands/run.py b/src/vibepod/commands/run.py index 9bae2fb..bbe0cb6 100644 --- a/src/vibepod/commands/run.py +++ b/src/vibepod/commands/run.py @@ -625,11 +625,7 @@ def run( finally: logger.close_session(exit_reason) - if ( - selected_agent == "claude" - and "setup-token" in passthrough_args - and exit_reason == "normal" - ): + if selected_agent == "claude" and "setup-token" in passthrough_args and exit_reason == "normal": _capture_claude_setup_token(config_dir) diff --git a/src/vibepod/commands/skills.py b/src/vibepod/commands/skills.py index 3f037aa..81712fc 100644 --- a/src/vibepod/commands/skills.py +++ b/src/vibepod/commands/skills.py @@ -78,8 +78,7 @@ def add_cmd( if record.get("bundle"): installed = record.get("installed", []) success( - f"Installed {len(installed)} skill(s) from bundle " - f"{record.get('locator', '')}" + f"Installed {len(installed)} skill(s) from bundle {record.get('locator', '')}" ) for item in installed: info(f" + {item.get('id', '?')} ({item.get('name', '')})") diff --git a/src/vibepod/compat.py b/src/vibepod/compat.py index 5e2e976..57964d0 100644 --- a/src/vibepod/compat.py +++ b/src/vibepod/compat.py @@ -9,9 +9,7 @@ _CLOSED_FILE_ERROR = "I/O operation on closed file." -def should_ignore_closed_http_response_flush_error( - response: object, exc: BaseException -) -> bool: +def should_ignore_closed_http_response_flush_error(response: object, exc: BaseException) -> bool: """Return True for Python 3.14's closed ``HTTPResponse.fp`` flush cleanup error. Python 3.14 can surface a ``ValueError`` while finalizing Docker SDK / urllib3 diff --git a/src/vibepod/core/docker.py b/src/vibepod/core/docker.py index b409edb..e2bf1b0 100644 --- a/src/vibepod/core/docker.py +++ b/src/vibepod/core/docker.py @@ -689,13 +689,9 @@ def stop_container(self, name_or_id: str, force: bool = False) -> Any: try: container.stop(timeout=0 if force else 10) except APIError as exc: - raise DockerClientError( - f"Failed to stop container '{name_or_id}': {exc}" - ) from exc + raise DockerClientError(f"Failed to stop container '{name_or_id}': {exc}") from exc except DockerException as exc: - raise DockerClientError( - f"Failed to stop container '{name_or_id}': {exc}" - ) from exc + raise DockerClientError(f"Failed to stop container '{name_or_id}': {exc}") from exc return container def stop_all(self, force: bool = False) -> int: diff --git a/src/vibepod/core/skills_engine.py b/src/vibepod/core/skills_engine.py index 57e1ccd..0c89917 100644 --- a/src/vibepod/core/skills_engine.py +++ b/src/vibepod/core/skills_engine.py @@ -158,10 +158,9 @@ def run_engine( config = get_config() auto_pull_enabled = bool(config.get("auto_pull", True)) - is_latest = ( - ":" not in SKILLS_ENGINE_IMAGE.split("/")[-1] - or SKILLS_ENGINE_IMAGE.endswith(":latest") - ) + is_latest = ":" not in SKILLS_ENGINE_IMAGE.split("/")[ + -1 + ] or SKILLS_ENGINE_IMAGE.endswith(":latest") if not image_exists: manager.pull_image( @@ -171,6 +170,7 @@ def run_engine( elif auto_pull_enabled and is_latest: try: from vibepod.utils.console import info + info("Checking for skills-engine image updates…") manager.pull_if_newer( SKILLS_ENGINE_IMAGE, diff --git a/tests/test_config.py b/tests/test_config.py index c473b3e..6b3a58b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -178,7 +178,6 @@ def test_llm_env_overrides(monkeypatch, tmp_path: Path) -> None: assert llm["model"] == "llama3" - # --------------------------------------------------------------------------- # allow-dir / remove-dir / list-allowed-dirs subcommand tests # --------------------------------------------------------------------------- diff --git a/tests/test_doctor.py b/tests/test_doctor.py index 13bc6f3..6b75497 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -23,9 +23,7 @@ def test_doctor_missing_dir(tmp_path: Path, monkeypatch) -> None: def test_doctor_valid_token(tmp_path: Path, monkeypatch) -> None: - monkeypatch.setattr( - "vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path - ) + monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path) future_ms = int((time.time() + 3600) * 1000) (tmp_path / ".credentials.json").write_text( json.dumps( @@ -46,9 +44,7 @@ def test_doctor_valid_token(tmp_path: Path, monkeypatch) -> None: def test_doctor_expired_token(tmp_path: Path, monkeypatch) -> None: - monkeypatch.setattr( - "vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path - ) + monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path) monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False) past_ms = int((time.time() - 3600) * 1000) @@ -70,16 +66,12 @@ def test_doctor_expired_token(tmp_path: Path, monkeypatch) -> None: def test_doctor_expired_creds_but_stored_token_is_ok(tmp_path: Path, monkeypatch) -> None: """Expired credentials.json should NOT exit 2 when a stored token covers auth.""" - monkeypatch.setattr( - "vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path - ) + monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path) monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False) past_ms = int((time.time() - 3600) * 1000) (tmp_path / ".credentials.json").write_text( - json.dumps( - {"claudeAiOauth": {"accessToken": "a", "expiresAt": past_ms}} - ) + json.dumps({"claudeAiOauth": {"accessToken": "a", "expiresAt": past_ms}}) ) (tmp_path / "oauth-token").write_text("sk-stored\n", encoding="utf-8") result = runner.invoke(app, ["doctor", "claude"]) @@ -88,9 +80,7 @@ def test_doctor_expired_creds_but_stored_token_is_ok(tmp_path: Path, monkeypatch def test_doctor_missing_refresh_token(tmp_path: Path, monkeypatch) -> None: - monkeypatch.setattr( - "vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path - ) + monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path) future_ms = int((time.time() + 3600) * 1000) (tmp_path / ".credentials.json").write_text( json.dumps( @@ -108,9 +98,7 @@ def test_doctor_missing_refresh_token(tmp_path: Path, monkeypatch) -> None: def test_doctor_reports_stored_token_mode(tmp_path: Path, monkeypatch) -> None: - monkeypatch.setattr( - "vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path - ) + monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path) monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False) (tmp_path / "oauth-token").write_text("sk-xyz\n", encoding="utf-8") @@ -120,9 +108,7 @@ def test_doctor_reports_stored_token_mode(tmp_path: Path, monkeypatch) -> None: def test_doctor_reports_host_env_mode(tmp_path: Path, monkeypatch) -> None: - monkeypatch.setattr( - "vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path - ) + monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path) monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", "host-token-abc") result = runner.invoke(app, ["doctor", "claude"]) diff --git a/tests/test_run.py b/tests/test_run.py index 927d854..b62b2fe 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -679,9 +679,9 @@ def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def] run_cmd.run(agent="claude", workspace=tmp_path, detach=True, paste_images=True) - assert ("/tmp/.X11-unix", "/tmp/.X11-unix", "rw") in captured.get( - "extra_volumes", [] - ), f"X11 socket not found in extra_volumes: {captured.get('extra_volumes')}" + assert ("/tmp/.X11-unix", "/tmp/.X11-unix", "rw") in captured.get("extra_volumes", []), ( + f"X11 socket not found in extra_volumes: {captured.get('extra_volumes')}" + ) def test_paste_images_flag_mounts_xauth_cookie(monkeypatch, tmp_path: Path) -> None: @@ -1900,7 +1900,6 @@ def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def] assert env["TERM"] == "xterm-256color" - # --------------------------------------------------------------------------- # Directory permission tests # --------------------------------------------------------------------------- @@ -1942,9 +1941,7 @@ def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def] return _CapturingDockerManager, captured -def test_run_aborts_when_dir_not_allowed_and_non_interactive( - monkeypatch, tmp_path: Path -) -> None: +def test_run_aborts_when_dir_not_allowed_and_non_interactive(monkeypatch, tmp_path: Path) -> None: """Non-interactive stdin + disallowed dir → Exit(1) with no prompt.""" monkeypatch.setattr(run_cmd, "is_dir_allowed", lambda p: False) monkeypatch.setattr(run_cmd, "is_protected_dir", lambda p: False) diff --git a/tests/test_skills_engine_driver.py b/tests/test_skills_engine_driver.py index 5a7a64f..f7a13d4 100644 --- a/tests/test_skills_engine_driver.py +++ b/tests/test_skills_engine_driver.py @@ -19,8 +19,10 @@ def mock_docker_manager(monkeypatch: pytest.MonkeyPatch) -> None: class FakeDockerManager: def __init__(self) -> None: self.client = MagicMock() + def pull_image(self, image: str, auto_clean: bool = False) -> None: pass + def pull_if_newer(self, image: str, auto_clean: bool = False) -> bool: return False @@ -80,9 +82,7 @@ def test_run_engine_explicit_local_scope_creates_local_skills_dir( skills_engine.list_skills("local", cwd=tmp_path) local = tmp_path / ".vibepod" / "skills" - mount_args = [ - arg for i, arg in enumerate(captured["cmd"]) if captured["cmd"][i - 1] == "-v" - ] + mount_args = [arg for i, arg in enumerate(captured["cmd"]) if captured["cmd"][i - 1] == "-v"] assert local.is_dir() assert f"{local}:/vibepod/local-skills" in mount_args @@ -150,8 +150,7 @@ def test_add_accepts_github_tree_url(monkeypatch: pytest.MonkeyPatch, tmp_path: monkeypatch.setattr(subprocess, "run", fake_run) url = ( - "https://github.com/alirezarezvani/claude-skills/tree/main/" - "product-team/skills/spec-to-repo" + "https://github.com/alirezarezvani/claude-skills/tree/main/product-team/skills/spec-to-repo" ) skills_engine.add(url, scope="user", cwd=tmp_path) @@ -232,9 +231,7 @@ def test_add_does_not_mount_remote_locators( assert "-w" not in cmd, locator -def test_add_expands_tilde_local_locator( - monkeypatch: pytest.MonkeyPatch, tmp_path: Path -) -> None: +def test_add_expands_tilde_local_locator(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: home = tmp_path / "home" source = home / "skills" / "foo" source.mkdir(parents=True) @@ -308,8 +305,10 @@ class FakeDockerManager: def __init__(self) -> None: self.client = MagicMock() self.client.images.get.side_effect = NotFound("not found") + def pull_image(self, image: str, auto_clean: bool = False) -> None: pulled_images.append((image, auto_clean)) + def pull_if_newer(self, image: str, auto_clean: bool = False) -> bool: checked_images.append(image) return False @@ -341,8 +340,10 @@ class FakeDockerManager: def __init__(self) -> None: self.client = MagicMock() self.client.images.get.return_value = MagicMock() + def pull_image(self, image: str, auto_clean: bool = False) -> None: pulled_images.append((image, auto_clean)) + def pull_if_newer(self, image: str, auto_clean: bool = False) -> bool: checked_images.append((image, auto_clean)) return False diff --git a/tests/test_task_cmd.py b/tests/test_task_cmd.py index 64a25a0..ae68714 100644 --- a/tests/test_task_cmd.py +++ b/tests/test_task_cmd.py @@ -1012,6 +1012,7 @@ def test_task_create_preserves_host_user_for_non_podman( monkeypatch, tmp_path, tmp_task_store ) -> None: import os + class _DockerDockerManager(_CapturingDockerManager): def is_rootless_podman(self) -> bool: return False @@ -1021,15 +1022,14 @@ def is_rootless_podman(self) -> bool: monkeypatch.setattr(task_cmd, "DockerManager", lambda: stub) import dataclasses + original_get_agent_spec = task_cmd.get_agent_spec spec = original_get_agent_spec("claude") modified_spec = dataclasses.replace(spec, run_as_host_user=True) monkeypatch.setattr( task_cmd, "get_agent_spec", - lambda agent: ( - modified_spec if agent == "claude" else original_get_agent_spec(agent) - ), + lambda agent: modified_spec if agent == "claude" else original_get_agent_spec(agent), ) monkeypatch.setattr(os, "getuid", lambda: 1234, raising=False) monkeypatch.setattr(os, "getgid", lambda: 5678, raising=False)