Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help install dev-install format lint type-check security check-all clean pre-commit setup-dev dev
.PHONY: help install dev-install format lint type-check security test check-all clean pre-commit setup-dev dev

help:
@echo "Available commands:"
Expand All @@ -11,7 +11,8 @@ help:
@echo " lint - Lint code with ruff"
@echo " type-check - Run type checking with mypy and pyright"
@echo " security - Run security checks with bandit"
@echo " check-all - Run all code quality checks"
@echo " test - Run test suite"
@echo " check-all - Run all code quality checks + tests"
@echo ""
@echo "Development:"
@echo " pre-commit - Run pre-commit hooks on all files"
Expand Down Expand Up @@ -50,7 +51,12 @@ security:
uv run bandit -r strix/ -c pyproject.toml
@echo "✅ Security checks complete!"

check-all: format lint type-check security
test:
@echo "🧪 Running tests with pytest..."
uv run pytest tests/ -v
@echo "✅ All tests passed!"

check-all: format lint type-check security test
@echo "✅ All code quality checks passed!"

pre-commit:
Expand Down
26 changes: 25 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ dev = [
"bandit>=1.8.3",
"pre-commit>=4.2.0",
"pyinstaller>=6.17.0; python_version >= '3.12' and python_version < '3.15'",
"pytest>=9.0.3",
"pytest-mock>=3.15.1",
]

[build-system]
Expand Down Expand Up @@ -100,6 +102,7 @@ module = [
"docker.*",
"caido_sdk_client.*",
"pydantic_settings.*",
"pygments.*",
]
ignore_missing_imports = true
disable_error_code = ["import-untyped"]
Expand Down Expand Up @@ -197,7 +200,7 @@ ignore = [
# Custom Docker subclass duplicates parent body; some imports are for annotations.
# Backend factories import their backend's deps lazily so deployments
# that pick a different backend don't need every backend's libs installed.
"strix/runtime/backends.py" = ["PLC0415"]
"strix/runtime/backends.py" = ["PLC0415", "BLE001", "SIM105", "S607"]
"strix/runtime/docker_client.py" = [
"TC002", # Manifest, Container imported for annotations
"TC003", # uuid imported for annotation
Expand Down Expand Up @@ -228,6 +231,13 @@ ignore = [
"strix/interface/tui/app.py" = ["BLE001", "PLC0415", "PLR0912", "PLR0915", "SIM105"]
"strix/interface/main.py" = ["BLE001", "PLC0415", "PLR0912", "PLR0915"]
"strix/interface/tui/renderers/agent_message_renderer.py" = ["PLC0415"]
# Lazy litellm imports avoid startup penalty and circular imports.
"strix/config/models.py" = ["PLC0415"]
"strix/report/usage.py" = ["PLC0415"]
"strix/telemetry/logging.py" = ["PLC0415"]
# Test files: unused fixture args are intentional (side-effect fixtures like
# clean_env), and stubs have unused params for interface compatibility.
"tests/**/*.py" = ["ARG002", "S108", "PLC0415", "ARG001", "TC003"]

[tool.ruff.lint.isort]
force-single-line = false
Expand Down Expand Up @@ -278,6 +288,20 @@ reportDuplicateImport = true
# Black Configuration (Code Formatter)
# ============================================================================

# ============================================================================
# Pytest Configuration
# ============================================================================

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]

# ============================================================================
# Black Configuration (Code Formatter)
# ============================================================================

[tool.black]
line-length = 100
target-version = ['py312']
Expand Down
1 change: 1 addition & 0 deletions strix/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class RuntimeSettings(BaseSettings):
alias="STRIX_IMAGE",
)
backend: str = Field(default="docker", alias="STRIX_RUNTIME_BACKEND")
socket_path: str | None = Field(default=None, alias="STRIX_RUNTIME_SOCKET")


class TelemetrySettings(BaseSettings):
Expand Down
38 changes: 31 additions & 7 deletions strix/interface/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import argparse
import asyncio
import logging
import shutil
import sys
from datetime import UTC, datetime
Expand Down Expand Up @@ -44,16 +45,11 @@
)
from strix.report.state import get_global_report_state
from strix.report.writer import read_run_record, write_run_record
from strix.runtime.backends import get_host_gateway
from strix.telemetry import posthog, scarf
from strix.telemetry.logging import configure_dependency_logging


HOST_GATEWAY_HOSTNAME = "host.docker.internal"


import logging # noqa: E402


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -183,6 +179,33 @@ def validate_environment() -> None:


def check_docker_installed() -> None:
settings = load_settings()
backend = settings.runtime.backend

if backend == "podman":
if shutil.which("podman") is None:
logger.error("Podman CLI not found in PATH")
console = Console()
error_text = Text()
error_text.append("PODMAN NOT INSTALLED", style="bold red")
error_text.append("\n\n", style="white")
error_text.append("The 'podman' CLI was not found in your PATH.\n", style="white")
error_text.append(
"Please install Podman and ensure the 'podman' command is available.\n\n",
style="white",
)
panel = Panel(
error_text,
title="[bold white]STRIX",
title_align="left",
border_style="red",
padding=(1, 2),
)
console.print("\n", panel, "\n")
sys.exit(1)
logger.debug("Podman CLI present")
return

if shutil.which("docker") is None:
logger.error("Docker CLI not found in PATH")
console = Console()
Expand Down Expand Up @@ -456,7 +479,8 @@ def parse_arguments() -> argparse.Namespace:
parser.error(f"Invalid target '{target}'")

assign_workspace_subdirs(args.targets_info)
rewrite_localhost_targets(args.targets_info, HOST_GATEWAY_HOSTNAME)
host_gateway = get_host_gateway(load_settings().runtime.backend)
rewrite_localhost_targets(args.targets_info, host_gateway)

return args

Expand Down
43 changes: 33 additions & 10 deletions strix/interface/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from urllib.parse import urlparse
from urllib.request import Request, urlopen

import docker
from docker.errors import DockerException, ImageNotFound
from rich.console import Console
from rich.panel import Panel
Expand Down Expand Up @@ -1329,18 +1328,42 @@ def clone_repository(repo_url: str, run_name: str, dest_name: str | None = None)


def check_docker_connection() -> Any:
from strix.config import load_settings
from strix.runtime.backends import create_docker_client

settings = load_settings()
backend = settings.runtime.backend

try:
return docker.from_env()
except DockerException:
return create_docker_client(backend)
except DockerException as exc:
console = Console()
error_text = Text()
error_text.append("DOCKER NOT AVAILABLE", style="bold red")
error_text.append("\n\n", style="white")
error_text.append("Cannot connect to Docker daemon.\n", style="white")
error_text.append(
"Please ensure Docker Desktop is installed and running, and try running strix again.\n",
style="white",
)

if backend == "podman":
error_text.append("PODMAN NOT AVAILABLE", style="bold red")
error_text.append("\n\n", style="white")
error_text.append("Cannot connect to Podman daemon.\n", style="white")
error_text.append(
"Please ensure Podman is installed and running, and try running strix again.\n\n",
style="white",
)
error_text.append(f"Reason: {exc}\n\n", style="dim")
error_text.append(
"Tip: set STRIX_RUNTIME_SOCKET to your Podman socket path "
"if auto-detection fails.\n",
style="dim",
)
else:
error_text.append("DOCKER NOT AVAILABLE", style="bold red")
error_text.append("\n\n", style="white")
error_text.append("Cannot connect to Docker daemon.\n", style="white")
error_text.append(
"Please ensure Docker Desktop is installed and running, "
"and try running strix again.\n\n",
style="white",
)
error_text.append(f"Reason: {exc}\n", style="dim")

panel = Panel(
error_text,
Expand Down
2 changes: 1 addition & 1 deletion strix/report/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def read_run_record(run_dir: Path) -> dict[str, Any]:
except (OSError, json.JSONDecodeError) as exc:
raise RuntimeError(f"run.json at {path} is unreadable: {exc}") from exc
if not isinstance(data, dict):
raise RuntimeError(f"run.json at {path} is not an object")
raise TypeError(f"run.json at {path} is not an object")
return data


Expand Down
Loading