feat: add PyInstaller build for standalone binary distribution

- Add PyInstaller spec file and build script for creating standalone executables
- Add install.sh for curl | sh installation from GitHub releases
- Add GitHub Actions workflow for multi-platform builds (macOS, Linux, Windows)
- Move sandbox-only deps (playwright, ipython, libtmux, etc.) to optional extras
- Make google-cloud-aiplatform optional ([vertex] extra) to reduce binary size
- Use lazy imports in tool actions to avoid loading sandbox deps at startup
- Add -v/--version flag to CLI
- Add website and Discord links to completion message
- Binary size: ~97MB (down from ~120MB with all deps)
This commit is contained in:
0xallam
2025-12-07 14:35:08 +02:00
committed by Ahmed Allam
parent 2899021a21
commit eb0c52b720
13 changed files with 1138 additions and 149 deletions

View File

@@ -233,6 +233,15 @@ async def warm_up_llm() -> None:
sys.exit(1)
def get_version() -> str:
try:
from importlib.metadata import version
return version("strix-agent")
except Exception: # noqa: BLE001
return "unknown"
def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Strix Multi-Agent Cybersecurity Penetration Testing Tool",
@@ -268,6 +277,13 @@ Examples:
""",
)
parser.add_argument(
"-v",
"--version",
action="version",
version=f"strix {get_version()}",
)
parser.add_argument(
"-t",
"--target",
@@ -428,6 +444,9 @@ def display_completion_message(args: argparse.Namespace, results_path: Path) ->
console.print("\n")
console.print(panel)
console.print()
console.print("[dim]🌐 Website:[/] [cyan]https://usestrix.com[/]")
console.print("[dim]💬 Discord:[/] [cyan]https://discord.gg/YjKFvEZSdZ[/]")
console.print()
def pull_docker_image() -> None:

View File

@@ -1,8 +1,10 @@
from typing import Any, Literal, NoReturn
from typing import TYPE_CHECKING, Any, Literal, NoReturn
from strix.tools.registry import register_tool
from .tab_manager import BrowserTabManager, get_browser_tab_manager
if TYPE_CHECKING:
from .tab_manager import BrowserTabManager
BrowserAction = Literal[
@@ -71,7 +73,7 @@ def _validate_file_path(action_name: str, file_path: str | None) -> None:
def _handle_navigation_actions(
manager: BrowserTabManager,
manager: "BrowserTabManager",
action: str,
url: str | None = None,
tab_id: str | None = None,
@@ -90,7 +92,7 @@ def _handle_navigation_actions(
def _handle_interaction_actions(
manager: BrowserTabManager,
manager: "BrowserTabManager",
action: str,
coordinate: str | None = None,
text: str | None = None,
@@ -128,7 +130,7 @@ def _raise_unknown_action(action: str) -> NoReturn:
def _handle_tab_actions(
manager: BrowserTabManager,
manager: "BrowserTabManager",
action: str,
url: str | None = None,
tab_id: str | None = None,
@@ -149,7 +151,7 @@ def _handle_tab_actions(
def _handle_utility_actions(
manager: BrowserTabManager,
manager: "BrowserTabManager",
action: str,
duration: float | None = None,
js_code: str | None = None,
@@ -191,6 +193,8 @@ def browser_action(
file_path: str | None = None,
clear: bool = False,
) -> dict[str, Any]:
from .tab_manager import get_browser_tab_manager
manager = get_browser_tab_manager()
try:

View File

@@ -3,9 +3,6 @@ import re
from pathlib import Path
from typing import Any, cast
from openhands_aci import file_editor
from openhands_aci.utils.shell import run_shell_cmd
from strix.tools.registry import register_tool
@@ -33,6 +30,8 @@ def str_replace_editor(
new_str: str | None = None,
insert_line: int | None = None,
) -> dict[str, Any]:
from openhands_aci import file_editor
try:
path_obj = Path(path)
if not path_obj.is_absolute():
@@ -64,6 +63,8 @@ def list_files(
path: str,
recursive: bool = False,
) -> dict[str, Any]:
from openhands_aci.utils.shell import run_shell_cmd
try:
path_obj = Path(path)
if not path_obj.is_absolute():
@@ -116,6 +117,8 @@ def search_files(
regex: str,
file_pattern: str = "*",
) -> dict[str, Any]:
from openhands_aci.utils.shell import run_shell_cmd
try:
path_obj = Path(path)
if not path_obj.is_absolute():

View File

@@ -2,8 +2,6 @@ from typing import Any, Literal
from strix.tools.registry import register_tool
from .proxy_manager import get_proxy_manager
RequestPart = Literal["request", "response"]
@@ -27,6 +25,8 @@ def list_requests(
sort_order: Literal["asc", "desc"] = "desc",
scope_id: str | None = None,
) -> dict[str, Any]:
from .proxy_manager import get_proxy_manager
manager = get_proxy_manager()
return manager.list_requests(
httpql_filter, start_page, end_page, page_size, sort_by, sort_order, scope_id
@@ -41,6 +41,8 @@ def view_request(
page: int = 1,
page_size: int = 50,
) -> dict[str, Any]:
from .proxy_manager import get_proxy_manager
manager = get_proxy_manager()
return manager.view_request(request_id, part, search_pattern, page, page_size)
@@ -53,6 +55,8 @@ def send_request(
body: str = "",
timeout: int = 30,
) -> dict[str, Any]:
from .proxy_manager import get_proxy_manager
if headers is None:
headers = {}
manager = get_proxy_manager()
@@ -64,6 +68,8 @@ def repeat_request(
request_id: str,
modifications: dict[str, Any] | None = None,
) -> dict[str, Any]:
from .proxy_manager import get_proxy_manager
if modifications is None:
modifications = {}
manager = get_proxy_manager()
@@ -78,6 +84,8 @@ def scope_rules(
scope_id: str | None = None,
scope_name: str | None = None,
) -> dict[str, Any]:
from .proxy_manager import get_proxy_manager
manager = get_proxy_manager()
return manager.scope_rules(action, allowlist, denylist, scope_id, scope_name)
@@ -89,6 +97,8 @@ def list_sitemap(
depth: Literal["DIRECT", "ALL"] = "DIRECT",
page: int = 1,
) -> dict[str, Any]:
from .proxy_manager import get_proxy_manager
manager = get_proxy_manager()
return manager.list_sitemap(scope_id, parent_id, depth, page)
@@ -97,5 +107,7 @@ def list_sitemap(
def view_sitemap_entry(
entry_id: str,
) -> dict[str, Any]:
from .proxy_manager import get_proxy_manager
manager = get_proxy_manager()
return manager.view_sitemap_entry(entry_id)

View File

@@ -2,8 +2,6 @@ from typing import Any, Literal
from strix.tools.registry import register_tool
from .python_manager import get_python_session_manager
PythonAction = Literal["new_session", "execute", "close", "list_sessions"]
@@ -15,6 +13,8 @@ def python_action(
timeout: int = 30,
session_id: str | None = None,
) -> dict[str, Any]:
from .python_manager import get_python_session_manager
def _validate_code(action_name: str, code: str | None) -> None:
if not code:
raise ValueError(f"code parameter is required for {action_name} action")

View File

@@ -2,8 +2,6 @@ from typing import Any
from strix.tools.registry import register_tool
from .terminal_manager import get_terminal_manager
@register_tool
def terminal_execute(
@@ -13,6 +11,8 @@ def terminal_execute(
terminal_id: str | None = None,
no_enter: bool = False,
) -> dict[str, Any]:
from .terminal_manager import get_terminal_manager
manager = get_terminal_manager()
try: