Skip to content

Development Setup

This guide walks you through setting up a local development environment for soong CLI.

Prerequisites

Required

  • Python 3.10 or higher

    python3 --version  # Should be 3.10+
    

  • Git

    git --version
    

  • pip (usually comes with Python)

    pip --version
    

Optional

  • Lambda Labs account (for testing against real API)
  • SSH key configured in Lambda Labs
  • Lambda filesystem named coding-stack

Initial Setup

1. Clone the Repository

git clone https://github.com/axiomantic/soong.git
cd soong/cli

Or if you're forking:

git clone https://github.com/YOUR_USERNAME/soong.git
cd soong/cli
git remote add upstream https://github.com/axiomantic/soong.git

Monorepo Structure

Soong is a monorepo with multiple components: the Python CLI (cli/), a Flask dashboard (dashboard/), and a Cloudflare worker (worker/). Development commands run from the cli/ subdirectory where pyproject.toml lives.

2. Create Virtual Environment

Using venv (recommended):

python3 -m venv venv
source venv/bin/activate  # On macOS/Linux

On Windows:

python -m venv venv
venv\Scripts\activate

On Windows PowerShell:

python -m venv venv
venv\Scripts\Activate.ps1

3. Install Dependencies

Install the package in editable mode with test dependencies:

pip install -e ".[test]"

This installs:

  • Runtime dependencies: typer, rich, requests, pyyaml, questionary
  • Test dependencies: pytest, pytest-cov, pytest-mock, responses

4. Verify Installation

# Check that CLI is available
soong --help

# Should show:
# Usage: soong [OPTIONS] COMMAND [ARGS]...
# Check Python imports
python -c "from soong import cli, models, config; print('OK')"

# Should print: OK

5. Configure for Testing (Optional)

If you want to test against the real Lambda API:

soong configure

Enter:

  • Lambda API key: Get from https://cloud.lambdalabs.com/api-keys
  • Status daemon token: Any secure random string (or generate one)
  • Default region: us-west-1 or your preferred region
  • Filesystem name: coding-stack (must exist in Lambda)
  • Default model: deepseek-r1-70b
  • Default GPU: gpu_1x_a100_sxm4_80gb
  • Lease hours: 4
  • SSH key path: ~/.ssh/id_rsa

For development, you can use dummy values since tests use mocks:

# ~/.config/gpu-dashboard/config.yaml
lambda:
  api_key: "test_key_12345"
  default_region: "us-west-1"
  filesystem_name: "test-fs"

status_daemon:
  token: "test_token_67890"
  port: 8080

defaults:
  model: "deepseek-r1-70b"
  gpu: "gpu_1x_a100_sxm4_80gb"
  lease_hours: 4

ssh:
  key_path: "~/.ssh/id_rsa"

Development Tools

Code Editor Setup

VS Code

Install recommended extensions:

  • Python (ms-python.python)
  • Pylance (ms-python.vscode-pylance)
  • Python Test Explorer (littlefoxteam.vscode-python-test-adapter)

Workspace settings (.vscode/settings.json):

{
  "python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python",
  "python.testing.pytestEnabled": true,
  "python.testing.unittestEnabled": false,
  "python.testing.pytestArgs": [
    "tests"
  ],
  "python.linting.enabled": true,
  "python.linting.pylintEnabled": false,
  "python.linting.flake8Enabled": true,
  "python.formatting.provider": "black",
  "editor.formatOnSave": true,
  "editor.rulers": [88]
}

PyCharm

  1. Open cli/ directory as project
  2. Configure interpreter: Settings → Project → Python Interpreter → Add → Existing environment
  3. Select cli/venv/bin/python
  4. Enable pytest: Settings → Tools → Python Integrated Tools → Testing → pytest

Linting and Formatting (Optional)

Install development tools:

pip install black flake8 mypy

Format code:

black src/soong tests

Check style:

flake8 src/soong tests --max-line-length=88 --extend-ignore=E203

Type checking:

mypy src/soong --ignore-missing-imports

Running the CLI in Development Mode

Since you installed with -e, any changes to the source code are immediately reflected:

# Edit src/soong/cli.py
# Then immediately test:
soong --help

Testing Changes

Method 1: Direct invocation

soong status
soong start --help

Method 2: Python module

python -m soong.cli status

Method 3: Testing individual functions

# In Python REPL
from soong.models import estimate_vram, Quantization

result = estimate_vram(70, Quantization.INT4, 8192)
print(result)

Project Structure

Understanding the codebase:

cli/
├── src/soong/          # Main package
│   ├── __init__.py           # Package initialization
│   ├── cli.py                # CLI commands (Typer app)
│   ├── config.py             # Configuration management
│   ├── instance.py           # Instance lifecycle
│   ├── lambda_api.py         # API client
│   ├── ssh.py                # SSH tunnels
│   ├── models.py             # Model definitions
│   └── history.py            # Termination history
├── tests/                    # Test suite
│   ├── conftest.py           # Pytest fixtures
│   ├── helpers/              # Test utilities
│   └── test_*.py             # Test modules
├── pyproject.toml            # Project metadata
└── README.md                 # Package documentation

Key Files

cli.py - CLI interface

  • Typer commands (@app.command())
  • User interaction (prompts, confirmations)
  • Output formatting (tables, panels)

lambda_api.py - Lambda Labs API

  • HTTP client with retry logic
  • Dataclasses for API responses (Instance, InstanceType)
  • Error handling (LambdaAPIError)

models.py - Model registry

  • Model configurations (ModelConfig)
  • VRAM estimation
  • GPU recommendations
  • Known GPUs and models

config.py - Configuration

  • YAML config loading/saving
  • Validation for custom models
  • Secure file permissions

instance.py - Instance management

  • Waiting for instances to become ready
  • Status polling
  • Active instance detection

ssh.py - SSH tunnels

  • Background SSH tunnel creation
  • Port forwarding
  • PID tracking for cleanup

Common Development Tasks

Running Tests

# All tests
pytest

# With coverage
pytest --cov=soong --cov-report=html

# Specific test file
pytest tests/test_models.py

# Specific test
pytest tests/test_models.py::test_estimate_vram_llama_70b_int4

# Verbose output
pytest -v

# Show print statements
pytest -s

Adding a New Command

  1. Add command function in cli.py:
@app.command()
def my_command(
    arg: str = typer.Argument(..., help="Required argument"),
    option: bool = typer.Option(False, help="Optional flag"),
):
    """Brief description of the command."""
    config = get_config()
    # Implementation
    console.print("[green]Success![/green]")
  1. Add tests in tests/test_cli_commands.py:
def test_my_command_success(cli_runner, sample_config, mocker):
    """Test my_command with valid input."""
    mocker.patch("soong.cli.get_config", return_value=sample_config)
    result = cli_runner.invoke(app, ["my-command", "test-arg"])
    assert result.exit_code == 0
    assert "Success!" in result.output
  1. Update documentation in docs/

Debugging Tests

Run test with debugger:

pytest tests/test_models.py::test_estimate_vram_llama_70b_int4 --pdb

Add breakpoint in code:

def estimate_vram(params_billions, quantization):
    import pdb; pdb.set_trace()  # Debugger will stop here
    base = params_billions * quantization.bytes_per_param
    ...

See test output:

# Show print statements and logging
pytest -s --log-cli-level=DEBUG

Working with Mocks

Tests use pytest-mock and responses for mocking:

def test_api_call(mock_http, lambda_api_base_url):
    """Test API call with mocked HTTP."""
    # Mock HTTP response
    mock_http.add(
        responses.GET,
        f"{lambda_api_base_url}/instances",
        json={"data": []},
        status=200,
    )

    # Call code that makes request
    api = LambdaAPI("test_key")
    instances = api.list_instances()

    # Verify
    assert len(instances) == 0
    assert len(mock_http.calls) == 1

Recording the Demo

The project includes a VHS script for recording demo videos.

Prerequisites

Install VHS (terminal recording tool):

brew install charmbracelet/tap/vhs

Also requires ffmpeg and ttyd (installed automatically by VHS on first run).

Recording

make demo

This runs vhs scripts/demo.tape which:

  1. Shows soong --help
  2. Shows soong available (GPU/model availability)
  3. Runs soong start -y (launches instance, provisions, waits for health)
  4. Shows soong status
  5. Runs a curl command to chat with the model via OpenAI-compatible API
  6. Runs soong stop -y to tear down
  7. Shows final soong status

Requirements:

  • Valid Lambda API credentials configured (soong configure)
  • Sufficient Lambda credits (instance runs for ~10 minutes)
  • jq installed for JSON parsing in the curl demo

Output:

  • docs/assets/demo.gif - Animated GIF for documentation
  • docs/assets/demo.mp4 - Video file

The recording uses PlaybackSpeed 2 to keep the video reasonable length despite the ~10 minute real-time duration.

Customizing

Edit scripts/demo.tape to modify the demo. See VHS documentation for syntax.

Troubleshooting

Import Errors

Problem: ModuleNotFoundError: No module named 'soong'

Solution:

# Make sure you're in the venv
source venv/bin/activate

# Reinstall in editable mode
pip install -e ".[test]"

Test Failures

Problem: Tests fail with import errors

Solution:

# Install test dependencies
pip install -e ".[test]"

# Or explicitly
pip install pytest pytest-cov pytest-mock responses

Virtual Environment Issues

Problem: Can't activate venv

Solution:

# Delete and recreate
rm -rf venv
python3 -m venv venv
source venv/bin/activate
pip install -e ".[test]"

Config File Permission Errors

Problem: PermissionError when running tests

Solution:

# Fix permissions on config dir
chmod 755 ~/.config/gpu-dashboard
chmod 600 ~/.config/gpu-dashboard/config.yaml

Next Steps

Getting Help

If you encounter issues:

  1. Check GitHub Issues
  2. Search Discussions
  3. Ask in project chat/Slack (if available)
  4. Create a new issue with:
  5. Python version (python --version)
  6. OS and version
  7. Steps to reproduce
  8. Error messages