Add system dependencies support for tools

Tools can now declare system-level package dependencies (apt, brew, pacman, dnf)
that get checked and optionally installed when the tool is installed or run.

Features:
- SystemDependency dataclass with short form (string) and long form (dict)
- New system_deps.py module with platform detection and installation
- `cmdforge system-deps <tool>` command to check/install system packages
- `cmdforge check` now shows both CmdForge and system dependencies
- `cmdforge registry install` prompts for system dep installation
- GUI: System Dependencies section in Tool Builder with add/edit dialog
- Runner warns about missing system deps before execution
- Integration with project install (manifest and lock-based)

Also includes:
- Quote paths in wrapper scripts for spaces support
- Tests for value type preservation in code steps
- Unskip invalid tool name test

Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
rob
2026-01-28 16:31:37 -04:00
co-authored by Claude Opus 4.5
parent 8eff55ed1e
commit 071ade0ffb
15 changed files with 1399 additions and 62 deletions
+56 -4
View File
@@ -92,6 +92,21 @@ Line 3: {var1} again"""
result = substitute_variables("Count: {n}", {"n": 42})
assert result == "Count: 42"
def test_zero_value_preserved(self):
"""Zero should be preserved, not converted to empty string."""
result = substitute_variables("Count: {n}", {"n": 0})
assert result == "Count: 0"
def test_false_value_preserved(self):
"""False should be preserved, not converted to empty string."""
result = substitute_variables("Enabled: {flag}", {"flag": False})
assert result == "Enabled: False"
def test_true_value_preserved(self):
"""True should be preserved as 'True'."""
result = substitute_variables("Enabled: {flag}", {"flag": True})
assert result == "Enabled: True"
class TestExecutePromptStep:
"""Tests for prompt step execution."""
@@ -179,9 +194,10 @@ class TestExecuteCodeStep:
outputs, success = execute_code_step(step, variables)
assert success is True
assert outputs["a"] == "1"
assert outputs["b"] == "2"
assert outputs["c"] == "3"
# Code step outputs preserve original types (integers here)
assert outputs["a"] == 1
assert outputs["b"] == 2
assert outputs["c"] == 3
def test_code_uses_variables(self):
step = CodeStep(
@@ -242,7 +258,43 @@ class TestExecuteCodeStep:
outputs, success = execute_code_step(step, variables)
assert success is True
assert outputs["result"] == "3"
# Code step outputs preserve original types (integer here)
assert outputs["result"] == 3
def test_code_preserves_complex_types(self):
"""Code step outputs preserve lists/dicts for code→code workflows."""
step = CodeStep(
code="data = [1, 2, 3]\ninfo = {'key': 'value'}",
output_var="data, info"
)
variables = {"input": ""}
outputs, success = execute_code_step(step, variables)
assert success is True
assert outputs["data"] == [1, 2, 3]
assert outputs["info"] == {"key": "value"}
def test_code_to_code_workflow(self):
"""Test that complex types can flow between code steps."""
# First step creates a list
step1 = CodeStep(
code="numbers = [1, 2, 3, 4, 5]",
output_var="numbers"
)
variables = {"input": ""}
outputs1, _ = execute_code_step(step1, variables)
variables.update(outputs1)
# Second step uses the list directly (no re-parsing needed)
step2 = CodeStep(
code="total = sum(numbers)",
output_var="total"
)
outputs2, success = execute_code_step(step2, variables)
assert success is True
assert outputs2["total"] == 15
class TestRunTool: