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.
From sandboxnpx claudepluginhub aaronbassett/agent-foundry --plugin sandboxThis skill uses the workspace's default tool permissions.
examples/Sandbox.toml.fullstackscripts/parse_config.pySearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
Read, write, and manage Sandbox.toml configuration files that define sandbox setup and preferences.
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]
name = "myproject"
location = "/Users/aaronbassett/Sandboxes/aaronbassett/myproject"
created = "2026-01-24T01:30:00Z"
base_image = "ubuntu:24.04"
[source]
type = "github" # or "local" or "new"
repository = "aaronbassett/myproject"
branch = "main" # optional
[languages]
rust = "1.93.0" # or "stable", "nightly"
python = "3.14.2" # or "current"
nodejs = "current" # or "lts", "nightly", "18.20.0"
[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] # specific ports
# or
port_range = "3000-3999" # range
[environment]
# Non-secret environment variables
NODE_ENV = "development"
RUST_BACKTRACE = "1"
[shell]
aliases = true # use standard eza/git aliases
starship_theme = "red_container"
oh_my_zsh_plugins = ["git", "docker", "rust", "python", "node"]
[tools]
# Additional CLI tools beyond defaults
extra = ["bat", "delta", "hyperfine"]
[sandbox] section:
name: Project name (required)location: Absolute path to sandbox directory (required)created: ISO 8601 timestamp of creationbase_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[languages.tools] section:
[claude] section:
marketplaces: List of marketplace reposplugins: List of plugins in "name@marketplace" format[network] section:
ports: Array of specific ports to forwardport_range: String range like "3000-3999"[environment] section:
[shell] section:
aliases: Boolean to include standard aliasesstarship_theme: Theme identifieroh_my_zsh_plugins: List of plugin names[tools] section:
extra: Additional tools beyond standard setUse scripts/parse_config.py:
python3 scripts/parse_config.py /path/to/sandbox/Sandbox.toml
Output: JSON representation of configuration
# In Python code
import tomli
with open("Sandbox.toml", "rb") as f:
config = tomli.load(f)
# Access fields
project_name = config["sandbox"]["name"]
languages = config.get("languages", {})
rust_version = languages.get("rust")
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 = []
# Required sections
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")
# Warn about unknown fields (lenient)
# Just log warnings, don't error
return errors
Use scripts/validate_config.py for validation.
When user references an existing sandbox:
User: "Base this on ~/Sandboxes/aaronbassett/project1"
Process:
Sandbox.toml in directory rootexisting_config_path = Path("~/Sandboxes/aaronbassett/project1/Sandbox.toml").expanduser()
if not existing_config_path.exists():
print("No Sandbox.toml found in that directory")
# Offer to create from scratch
else:
with open(existing_config_path, "rb") as f:
existing_config = tomli.load(f)
# Show user what's in existing config
print(f"Found sandbox config for: {existing_config['sandbox']['name']}")
print(f"Languages: {', '.join(existing_config.get('languages', {}).keys())}")
# etc.
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, # "github", "local", or "new"
},
}
# Add repository if GitHub source
if source_type == "github":
config["source"]["repository"] = repo_name
config["source"]["branch"] = branch or "main"
# Add detected/specified languages
if languages:
config["languages"] = languages
config["languages"]["tools"] = tools_by_language
# Add Claude configuration
if marketplaces or plugins:
config["claude"] = {}
if marketplaces:
config["claude"]["marketplaces"] = marketplaces
if plugins:
config["claude"]["plugins"] = plugins
# Add network config
if ports:
config["network"] = {"ports": ports}
elif port_range:
config["network"] = {"port_range": port_range}
# Add environment variables (non-secrets only)
if env_vars:
config["environment"] = env_vars
# Shell configuration
config["shell"] = {
"aliases": True,
"starship_theme": "red_container",
"oh_my_zsh_plugins": ["git", "docker", "rust", "python", "node"],
}
import tomli_w # or toml for writing
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.
When user wants to clone an existing sandbox for a new project:
User: "Create a duplicate of ~/Sandboxes/aaronbassett/project1 for myproject2"
Workflow:
Example:
# Read existing
existing = read_config("~/Sandboxes/aaronbassett/project1/Sandbox.toml")
# Detect new project (if applicable)
if new_project_repo:
detected_languages = detect_languages(new_project_repo)
# Compare
existing_langs = set(existing.get("languages", {}).keys())
detected_langs = set(detected_languages.keys())
if existing_langs != detected_langs:
# Ask user
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?")
# Wait for user response
# Then merge accordingly
# Clone base config
new_config = existing.copy()
# Update with new values
new_config["sandbox"]["name"] = "myproject2"
new_config["sandbox"]["location"] = new_location
new_config["sandbox"]["created"] = datetime.now(timezone.utc).isoformat()
# Update languages if changed
if user_wants_new_languages:
new_config["languages"] = detected_languages_with_versions
# Keep other settings (tools, plugins, etc.)
# unless user specifies changes
User may want only specific parts:
User: "Use the same Claude plugins as project1, but different languages"
new_config = {
"sandbox": {...}, # New sandbox details
"source": {...}, # New source
"languages": {...}, # New languages
"claude": existing_config["claude"], # Cloned from existing
"network": existing_config.get("network", default_network), # Cloned
"shell": existing_config.get("shell", default_shell), # Cloned
}
When conflicts occur, resolution order:
.claude/sandbox.local.md)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
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()
# Layer in each level (lowest to highest priority)
for source in [user_defaults, cloned, detected, user_requested]:
for key, value in source.items():
if value is not None: # Only override if value provided
result[key] = value
return result
Validate configuration before using:
Language version compatibility:
def check_language_compatibility(config: dict) -> List[str]:
warnings = []
# Check if versions are too old
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")
# Check for known incompatibilities
# e.g., certain tool combinations
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
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")
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.
./sandbox/shell.sh
./sandbox/run.sh <command>
# Examples:
./sandbox/run.sh cargo test
./sandbox/run.sh npm run dev
./sandbox/run.sh python -m pytest
./sandbox/stop.sh
On first run in the container:
Authenticate Claude Code:
./sandbox/shell.sh
cc auth
# Follow authentication flow
Verify plugins loaded:
cc plugin list
Exit shell:
exit
Authentication persists across container restarts.
Ports forwarded: {ports or port_range}
Access applications:
{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
See examples/ directory for:
Sandbox.toml.rust - Rust-only projectSandbox.toml.python - Python projectSandbox.toml.fullstack - Multi-language monorepoRead 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):
Validation: Lenient - warn on unknown fields, error only on critical issues