| name | sandbox:sandbox-config-management |
| description | This skill should be used when reading or writing Sandbox.toml configuration files, cloning sandbox configurations from existing projects, merging user preferences with detected settings, validating sandbox configuration compatibility, or managing sandbox metadata and settings. Provides knowledge for Sandbox.toml structure and configuration management. |
| version | 0.1.0 |
sandbox-config-management
Read, write, and manage Sandbox.toml configuration files that define sandbox setup and preferences.
Purpose
This skill provides knowledge for working with Sandbox.toml files - the configuration format used to define and share sandbox setups. Handle reading existing configurations, creating new ones, cloning and customizing configurations, and validating compatibility.
Sandbox.toml Structure
Complete Example
[sandbox]
name = "myproject"
location = "/Users/aaronbassett/Sandboxes/aaronbassett/myproject"
created = "2026-01-24T01:30:00Z"
base_image = "ubuntu:24.04"
[source]
type = "github"
repository = "aaronbassett/myproject"
branch = "main"
[languages]
rust = "1.93.0"
python = "3.14.2"
nodejs = "current"
[languages.tools]
rust = ["clippy", "rustfmt", "cargo-dist", "cargo-deny", "cargo-release", "cocogitto"]
python = ["uv", "ruff", "mypy", "black", "pytest"]
nodejs = ["pnpm", "typescript", "ts-node"]
[claude]
marketplaces = [
"anthropics/claude-plugins-official",
"aaronbassett/agent-foundry"
]
[claude.plugins]
plugins = [
"rust-analyzer-lsp@claude-plugins-official",
"pyright-lsp@claude-plugins-official",
"typescript-lsp@claude-plugins-official",
"devs@agent-foundry",
"git-lovely@agent-foundry",
"settings-presets@agent-foundry"
]
[network]
ports = [3000, 3001, 3002]
port_range = "3000-3999"
[environment]
NODE_ENV = "development"
RUST_BACKTRACE = "1"
[shell]
aliases = true
starship_theme = "red_container"
oh_my_zsh_plugins = ["git", "docker", "rust", "python", "node"]
[tools]
extra = ["bat", "delta", "hyperfine"]
Field Descriptions
[sandbox] section:
name: Project name (required)
location: Absolute path to sandbox directory (required)
created: ISO 8601 timestamp of creation
base_image: Docker base image (default: "ubuntu:24.04")
[source] section:
type: "github", "local", or "new" (required)
repository: GitHub repo (owner/name) if type="github"
branch: Git branch (default: "main")
[languages] section:
rust, python, nodejs: Version strings or keywords
- Keywords: "stable"/"current", "lts", "nightly"
- Specific versions: "1.93.0", "3.14.2", "18.20.0"
[languages.tools] section:
- Arrays of tool names to install for each language
- Empty array = no additional tools
[claude] section:
marketplaces: List of marketplace repos
plugins: List of plugins in "name@marketplace" format
[network] section:
ports: Array of specific ports to forward
port_range: String range like "3000-3999"
- Use one or the other, not both
[environment] section:
- Key-value pairs for environment variables
- Do not include secrets (use .env file instead)
[shell] section:
aliases: Boolean to include standard aliases
starship_theme: Theme identifier
oh_my_zsh_plugins: List of plugin names
[tools] section:
extra: Additional tools beyond standard set
Reading Configurations
Parse Sandbox.toml
Use scripts/parse_config.py:
python3 scripts/parse_config.py /path/to/sandbox/Sandbox.toml
Output: JSON representation of configuration
import tomli
with open("Sandbox.toml", "rb") as f:
config = tomli.load(f)
project_name = config["sandbox"]["name"]
languages = config.get("languages", {})
rust_version = languages.get("rust")
Validate Configuration
Check for required fields and valid values:
def validate_config(config: dict) -> List[str]:
"""
Validate Sandbox.toml structure.
Returns list of validation errors (empty if valid).
"""
errors = []
if "sandbox" not in config:
errors.append("Missing [sandbox] section")
else:
sandbox = config["sandbox"]
if "name" not in sandbox:
errors.append("Missing sandbox.name")
if "location" not in sandbox:
errors.append("Missing sandbox.location")
if "source" not in config:
errors.append("Missing [source] section")
else:
source = config["source"]
if "type" not in source:
errors.append("Missing source.type")
elif source["type"] not in ["github", "local", "new"]:
errors.append(f"Invalid source.type: {source['type']}")
if source.get("type") == "github" and "repository" not in source:
errors.append("GitHub source requires repository field")
return errors
Use scripts/validate_config.py for validation.
Read from Existing Sandbox
When user references an existing sandbox:
User: "Base this on ~/Sandboxes/aaronbassett/project1"
Process:
- Check if directory exists
- Look for
Sandbox.toml in directory root
- Parse configuration
- Present configuration to user for customization
existing_config_path = Path("~/Sandboxes/aaronbassett/project1/Sandbox.toml").expanduser()
if not existing_config_path.exists():
print("No Sandbox.toml found in that directory")
else:
with open(existing_config_path, "rb") as f:
existing_config = tomli.load(f)
print(f"Found sandbox config for: {existing_config['sandbox']['name']}")
print(f"Languages: {', '.join(existing_config.get('languages', {}).keys())}")
Writing Configurations
Create New Sandbox.toml
Build configuration from user answers and detected settings:
from datetime import datetime, timezone
config = {
"sandbox": {
"name": project_name,
"location": str(sandbox_location),
"created": datetime.now(timezone.utc).isoformat(),
"base_image": base_image or "ubuntu:24.04",
},
"source": {
"type": source_type,
},
}
if source_type == "github":
config["source"]["repository"] = repo_name
config["source"]["branch"] = branch or "main"
if languages:
config["languages"] = languages
config["languages"]["tools"] = tools_by_language
if marketplaces or plugins:
config["claude"] = {}
if marketplaces:
config["claude"]["marketplaces"] = marketplaces
if plugins:
config["claude"]["plugins"] = plugins
if ports:
config["network"] = {"ports": ports}
elif port_range:
config["network"] = {"port_range": port_range}
if env_vars:
config["environment"] = env_vars
config["shell"] = {
"aliases": True,
"starship_theme": "red_container",
"oh_my_zsh_plugins": ["git", "docker", "rust", "python", "node"],
}
Write to File
import tomli_w
output_path = Path(sandbox_location) / "Sandbox.toml"
with open(output_path, "wb") as f:
tomli_w.dump(config, f)
print(f"Configuration saved to {output_path}")
Use scripts/write_config.py helper script.
Cloning Configurations
Clone and Customize Workflow
When user wants to clone an existing sandbox for a new project:
User: "Create a duplicate of ~/Sandboxes/aaronbassett/project1 for myproject2"
Workflow:
- Read existing config
- Detect new project languages (if repository provided)
- Compare and identify differences
- Ask user about discrepancies
- Merge preferences
- Write new config
Example:
existing = read_config("~/Sandboxes/aaronbassett/project1/Sandbox.toml")
if new_project_repo:
detected_languages = detect_languages(new_project_repo)
existing_langs = set(existing.get("languages", {}).keys())
detected_langs = set(detected_languages.keys())
if existing_langs != detected_langs:
print(f"Original sandbox configured for: {', '.join(existing_langs)}")
print(f"New project uses: {', '.join(detected_langs)}")
print("Should I configure for new project languages, or keep original?")
new_config = existing.copy()
new_config["sandbox"]["name"] = "myproject2"
new_config["sandbox"]["location"] = new_location
new_config["sandbox"]["created"] = datetime.now(timezone.utc).isoformat()
if user_wants_new_languages:
new_config["languages"] = detected_languages_with_versions
Partial Cloning
User may want only specific parts:
User: "Use the same Claude plugins as project1, but different languages"
new_config = {
"sandbox": {...},
"source": {...},
"languages": {...},
"claude": existing_config["claude"],
"network": existing_config.get("network", default_network),
"shell": existing_config.get("shell", default_shell),
}
User Preference Merging
Override Priority
When conflicts occur, resolution order:
- Explicit user request (highest priority)
- Detected from project files
- Cloned from existing sandbox
- User's default preferences (from
.claude/sandbox.local.md)
- Plugin defaults (lowest priority)
Example:
Detected: Python 3.12 (from pyproject.toml)
User says: "use current stable" (3.14.2)
Resolution: Use 3.14.2 (user request overrides detection)
Action: Warn user about mismatch, suggest updating pyproject.toml
Merge Algorithm
def merge_preferences(
detected: dict,
user_requested: dict,
cloned: dict,
user_defaults: dict,
plugin_defaults: dict,
) -> dict:
"""Merge preferences with priority."""
result = plugin_defaults.copy()
for source in [user_defaults, cloned, detected, user_requested]:
for key, value in source.items():
if value is not None:
result[key] = value
return result
Validation
Compatibility Checks
Validate configuration before using:
Language version compatibility:
def check_language_compatibility(config: dict) -> List[str]:
warnings = []
rust_version = config.get("languages", {}).get("rust")
if rust_version and rust_version < "1.70":
warnings.append(f"Rust {rust_version} is quite old, consider updating")
return warnings
Port conflicts:
def check_port_availability(ports: List[int]) -> List[int]:
"""Return list of ports that are already in use."""
import socket
in_use = []
for port in ports:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
if s.connect_ex(('localhost', port)) == 0:
in_use.append(port)
return in_use
Lenient Validation
Be lenient with unknown fields (warn, don't error):
KNOWN_SECTIONS = {"sandbox", "source", "languages", "claude", "network", "environment", "shell", "tools"}
def check_unknown_fields(config: dict):
unknown = set(config.keys()) - KNOWN_SECTIONS
if unknown:
print(f"Warning: Unknown sections in config: {', '.join(unknown)}")
print("These will be ignored but won't cause errors")
SANDBOX.md Generation
Create Documentation File
Alongside Sandbox.toml, create SANDBOX.md describing the setup:
# Sandbox: {project_name}
Created: {created_timestamp}
Location: {sandbox_location}
## Configuration
**Languages:**
- Rust: {rust_version}
- Python: {python_version}
- Node.js: {nodejs_version}
**Source:**
- Type: {source_type}
- Repository: {repository} (if applicable)
**Claude Code:**
- Marketplaces: {marketplaces}
- Plugins: {plugins}
## Setup
This sandbox was automatically configured based on:
{what influenced the config - detection, cloning, user preferences}
{Any version mismatches or warnings}
## Usage
### Starting the Sandbox
```bash
./sandbox/up.sh
The sandbox will build and start. First run may take 10-15 minutes.
Interactive Shell
./sandbox/shell.sh
Running Commands
./sandbox/run.sh <command>
./sandbox/run.sh cargo test
./sandbox/run.sh npm run dev
./sandbox/run.sh python -m pytest
Stopping
./sandbox/stop.sh
First Run
On first run in the container:
-
Authenticate Claude Code:
./sandbox/shell.sh
cc auth
-
Verify plugins loaded:
cc plugin list
-
Exit shell:
exit
Authentication persists across container restarts.
Network
Ports forwarded: {ports or port_range}
Access applications:
Notes
{Any special notes, warnings, or instructions}
Generate this file automatically when creating sandbox.
## Integration with Other Skills
**With docker-sandbox-setup:**
- This skill defines WHAT to configure
- docker-sandbox-setup implements HOW (Dockerfile, scripts)
**With language-environment-config:**
- This skill stores language preferences
- language-environment-config implements installation
## Scripts
### Available Utilities
- **`scripts/parse_config.py`** - Parse Sandbox.toml to JSON
- **`scripts/validate_config.py`** - Validate configuration
- **`scripts/write_config.py`** - Create Sandbox.toml from dict
- **`scripts/merge_configs.py`** - Merge multiple configs with priority
### Example Usage
```bash
# Parse existing config
python3 scripts/parse_config.py ~/sandbox/Sandbox.toml
# Validate
python3 scripts/validate_config.py ~/sandbox/Sandbox.toml
# Create new config
python3 scripts/write_config.py \
--name myproject \
--location ~/Sandboxes/myproject \
--rust stable \
--python current \
--output ~/Sandboxes/myproject/Sandbox.toml
Examples
See examples/ directory for:
Sandbox.toml.rust - Rust-only project
Sandbox.toml.python - Python project
Sandbox.toml.fullstack - Multi-language monorepo
Quick Reference
Read config:
import tomli
with open("Sandbox.toml", "rb") as f:
config = tomli.load(f)
Write config:
import tomli_w
with open("Sandbox.toml", "wb") as f:
tomli_w.dump(config, f)
Priority order (highest first):
- User explicit request
- Project detection
- Cloned config
- User defaults
- Plugin defaults
Validation: Lenient - warn on unknown fields, error only on critical issues