From angreal
Guides use of angreal's built-in integrations for Git repo tasks, VirtualEnv/Docker/Flox environments, and Tera templating to scaffold files and directories in tasks.
npx claudepluginhub angreal/angreal --plugin angrealThis skill uses the workspace's default tool permissions.
Angreal provides built-in integrations for common development tools: Git, virtual environments, Docker Compose, Flox environments, and Tera templating.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Angreal provides built-in integrations for common development tools: Git, virtual environments, Docker Compose, Flox environments, and Tera templating.
Generate files and directories using the Tera template engine. Useful for scaffolding code, configs, or any structured files within tasks.
import angreal
Render a template string with variable substitution:
import angreal
# Simple variable substitution
template = "Hello {{ name }}!"
result = angreal.render_template(template, {"name": "World"})
# result == "Hello World!"
# With conditionals and loops
template = """
# {{ project_name }}
{% if use_docker %}
## Docker Setup
Run `docker-compose up` to start.
{% endif %}
## Dependencies
{% for dep in dependencies %}
- {{ dep }}
{% endfor %}
"""
result = angreal.render_template(template, {
"project_name": "My App",
"use_docker": True,
"dependencies": ["requests", "click"]
})
Signature: angreal.render_template(template: str, context: dict) -> str
Render an entire directory tree, processing both file contents and file/directory names:
import angreal
# Source directory structure:
# templates/
# {{ module_name }}/
# __init__.py
# {{ module_name }}.py
rendered_files = angreal.render_directory(
"templates", # src: source directory
"output", # dst: destination directory
False, # force: overwrite existing files?
{"module_name": "mymodule"} # context: template variables
)
# Creates:
# output/
# mymodule/
# __init__.py
# mymodule.py
print(f"Created {len(rendered_files)} files")
# Context is optional - copy without templating
angreal.render_directory("static_files", "output", False, None)
Signature: angreal.render_directory(src: str, dst: str, force: bool, context: dict | None) -> list[str]
Returns a list of created file paths.
Load variables from a TOML file, optionally prompting the user:
import angreal
# Load from TOML without prompting
context = angreal.generate_context("config.toml", take_input=False)
# Load and prompt user for values
context = angreal.generate_context("config.toml", take_input=True)
Signature: angreal.generate_context(path: str, take_input: bool) -> dict
Get the context from the current project's .angreal/angreal.toml:
import angreal
# Returns dict from .angreal/angreal.toml or empty dict if not found
context = angreal.get_context()
project_name = context.get("project_name", "unknown")
Signature: angreal.get_context() -> dict
{# Comments #}
{{ variable }} {# Variable substitution #}
{{ name | upper }} {# Filters: upper, lower, title, trim #}
{{ items | length }} {# Get length #}
{{ value | default("fallback") }} {# Default value #}
{% if condition %} {# Conditionals #}
{% elif other %}
{% else %}
{% endif %}
{% for item in items %} {# Loops #}
{{ loop.index }}: {{ item }}
{% endfor %}
{% raw %}{{ not processed }}{% endraw %} {# Escape template syntax #}
import angreal
import os
@angreal.command(name="scaffold", about="Generate a new module")
@angreal.argument(name="name", long="name", required=True, help="Module name")
@angreal.argument(name="with_tests", long="with-tests", is_flag=True, takes_value=False)
def scaffold(name, with_tests=False):
project_root = angreal.get_root().parent
# Template for module file
module_template = '''"""{{ name }} module."""
class {{ name | title }}:
"""{{ description }}"""
def __init__(self):
pass
'''
# Template for test file
test_template = '''"""Tests for {{ name }}."""
import pytest
from {{ name }} import {{ name | title }}
def test_{{ name }}_init():
instance = {{ name | title }}()
assert instance is not None
'''
context = {
"name": name,
"description": f"The {name} module"
}
# Create module
module_content = angreal.render_template(module_template, context)
module_path = os.path.join(project_root, "src", f"{name}.py")
os.makedirs(os.path.dirname(module_path), exist_ok=True)
with open(module_path, "w") as f:
f.write(module_content)
print(f"Created {module_path}")
# Create test if requested
if with_tests:
test_content = angreal.render_template(test_template, context)
test_path = os.path.join(project_root, "tests", f"test_{name}.py")
os.makedirs(os.path.dirname(test_path), exist_ok=True)
with open(test_path, "w") as f:
f.write(test_content)
print(f"Created {test_path}")
return 0
Full-featured Git wrapper for repository operations.
from angreal.integrations.git import Git, clone
from angreal.integrations.git import Git
# Initialize with working directory (default: current directory)
git = Git()
git = Git("/path/to/repo")
# All methods return (exit_code, stderr, stdout) tuples
exit_code, stderr, stdout = git.status()
| Method | Signature | Description |
|---|---|---|
init | init(bare=False) | Initialize a new repository |
add | add(*paths) | Stage files for commit |
commit | commit(message, all=False) | Create a commit |
push | push(remote=None, branch=None) | Push to remote |
pull | pull(remote=None, branch=None) | Pull from remote |
status | status(short=False) | Show working tree status |
branch | branch(name=None, delete=False) | List or manage branches |
checkout | checkout(branch, create=False) | Switch branches |
tag | tag(name, message=None) | Create a tag |
The Git object is callable for arbitrary git commands:
git = Git()
# Call any git command
exit_code, stderr, stdout = git("log", "--oneline", "-5")
exit_code, stderr, stdout = git("diff", "HEAD~1")
exit_code, stderr, stdout = git("stash", "pop")
from angreal.integrations.git import clone
# Clone a repository
path = clone("https://github.com/user/repo.git")
path = clone("https://github.com/user/repo.git", "/custom/destination")
import angreal
from angreal.integrations.git import Git
@angreal.command(name="release", about="Create a release")
@angreal.argument(name="version", long="version", required=True, help="Version tag")
def release(version):
git = Git()
# Check for clean working tree
exit_code, stderr, stdout = git.status(short=True)
if stdout.strip():
print("Error: Working tree not clean")
return 1
# Create and push tag
git.tag(version, message=f"Release {version}")
git.push("origin", version)
print(f"Released {version}")
return 0
Manage Python virtual environments with automatic activation.
from angreal.integrations.venv import VirtualEnv, venv_required
from angreal.integrations.venv import VirtualEnv
# Create venv (default path: .venv, created immediately)
venv = VirtualEnv()
# Custom path
venv = VirtualEnv("/path/to/venv")
# Defer creation
venv = VirtualEnv(".venv", now=False)
venv.create() # Create manually later
# With requirements
venv = VirtualEnv(".venv", requirements="requirements.txt")
venv = VirtualEnv(".venv", requirements=["requests", "click"])
VirtualEnv(
path=".venv", # Path to virtual environment
python=None, # Python version (e.g., "3.11")
requirements=None, # Requirements file or package list
now=True, # Create immediately if True
)
| Method | Description |
|---|---|
create() | Create the virtual environment |
activate() | Activate the venv in current process |
deactivate() | Deactivate the venv |
install(packages) | Install packages (string, list, or requirements file) |
install_requirements() | Install requirements passed to constructor |
remove() | Delete the virtual environment |
| Property | Type | Description |
|---|---|---|
path | Path | Absolute path to venv directory |
name | str | Name of the venv |
python_executable | Path | Path to Python interpreter |
exists | bool | Whether the venv exists |
from angreal.integrations.venv import VirtualEnv
# Automatically activate and deactivate
with VirtualEnv(".venv") as venv:
# venv is activated here
import subprocess
subprocess.run(["python", "-c", "import sys; print(sys.prefix)"])
# venv is deactivated here
Automatically manage venv lifecycle for a task:
import angreal
from angreal.integrations.venv import venv_required
@angreal.command(name="test", about="Run tests in venv")
@venv_required(".venv", requirements=["pytest"])
def test():
import subprocess
# Runs inside activated venv
subprocess.run(["pytest", "tests/"])
import angreal
from angreal.integrations.venv import VirtualEnv
@angreal.command(name="setup", about="Setup development environment")
def setup():
venv = VirtualEnv(".venv", requirements="requirements-dev.txt")
venv.install_requirements()
print(f"Virtual environment created at {venv.path}")
print(f"Activate with: source {venv.path}/bin/activate")
return 0
Manage Docker Compose services from tasks.
from angreal.integrations.docker import DockerCompose, compose
from angreal.integrations.docker import DockerCompose
# Create instance from compose file
dc = DockerCompose("docker-compose.yml")
dc = DockerCompose("docker-compose.yml", project_name="myproject")
# Or use the convenience function
from angreal.integrations.docker import compose
dc = compose("docker-compose.yml")
All methods return a ComposeResult object with: success, exit_code, stdout, stderr.
# Start services
result = dc.up(detach=True)
result = dc.up(detach=True, build=True, services=["web", "db"])
# Stop and remove
result = dc.down(volumes=True, remove_orphans=True)
# Start/stop without removing
result = dc.start(services=["web"])
result = dc.stop(services=["web"], timeout="30s")
# Restart
result = dc.restart(services=["web"])
# Build images
result = dc.build(no_cache=True, parallel=True)
result = dc.build(services=["web"])
# Pull images
result = dc.pull(services=["db"])
# List services
result = dc.ps(all=True)
# View logs
result = dc.logs(services=["web"], follow=False, tail="100")
# Validate config
result = dc.config(quiet=True)
# Run command in service container
result = dc.exec(
"web",
["python", "manage.py", "migrate"],
user="app",
workdir="/app"
)
dc.up(
detach=True, # Run in background
build=False, # Build images before starting
remove_orphans=False, # Remove containers for undefined services
force_recreate=False, # Recreate even if unchanged
no_recreate=False, # Don't recreate existing containers
services=None, # List of services to start
)
dc.down(
volumes=False, # Remove named volumes
remove_orphans=False, # Remove undefined service containers
timeout=None, # Shutdown timeout (e.g., "30s")
)
dc.logs(
services=None, # Services to show logs for
follow=False, # Follow log output
timestamps=False, # Show timestamps
tail=None, # Number of lines (e.g., "100")
since=None, # Show logs since timestamp
)
dc.exec(
service, # Service name
command, # Command as list of strings
detach=False, # Run in background
tty=True, # Allocate pseudo-TTY
user=None, # Run as user
workdir=None, # Working directory
env=None, # Environment variables dict
)
# Check if Docker Compose is available
if DockerCompose.is_available():
dc = DockerCompose("docker-compose.yml")
| Property | Type | Description |
|---|---|---|
compose_file | str | Path to compose file |
working_dir | str | Working directory |
project_name | str | Project name (if set) |
import angreal
from angreal.integrations.docker import DockerCompose
@angreal.command(name="dev", about="Start development environment")
@angreal.argument(name="build", long="build", is_flag=True, takes_value=False)
def dev(build=False):
dc = DockerCompose("docker-compose.yml", project_name="myapp")
if not DockerCompose.is_available():
print("Error: Docker Compose not found")
return 1
result = dc.up(detach=True, build=build)
if not result.success:
print(f"Failed: {result.stderr}")
return 1
print("Services started:")
dc.ps()
return 0
@angreal.command(name="logs", about="View service logs")
@angreal.argument(name="service", long="service", help="Service name")
@angreal.argument(name="follow", short="f", long="follow", is_flag=True, takes_value=False)
def logs(service=None, follow=False):
dc = DockerCompose("docker-compose.yml")
services = [service] if service else None
result = dc.logs(services=services, follow=follow, tail="100")
print(result.stdout)
return 0
Cross-language development environment and services management using Flox (Nix-based).
| Use Case | Best Integration |
|---|---|
| Python-only project | VirtualEnv (simplest) |
| Multi-language project | Flox (cross-language) |
| Need containerized services | Docker Compose |
| Need lightweight services | Flox (process-based) |
| Team-wide consistency | Flox (manifest.toml) |
from angreal.integrations.flox import Flox, FloxServices, flox_required
Automatically manage Flox environment and services for a task:
import angreal
from angreal.integrations.flox import flox_required
@angreal.command(name="test", about="Run tests with database")
@flox_required(".", services=["postgres"])
def run_tests():
import subprocess
subprocess.run(["pytest", "-v"])
# Without services
@angreal.command(name="build", about="Build in Flox environment")
@flox_required(".")
def build():
import subprocess
subprocess.run(["npm", "run", "build"])
from angreal.integrations.flox import Flox
# Create instance
flox = Flox(".")
# Check availability
if not Flox.is_available():
print("Install Flox: curl -fsSL https://flox.dev/install | bash")
# Check environment
print(f"Exists: {flox.exists}")
print(f"Has manifest: {flox.has_manifest}")
print(f"Version: {Flox.version()}")
| Method | Description |
|---|---|
activate() | Activate environment in current process |
deactivate() | Restore original environment |
run(cmd, args) | Execute command in Flox environment |
from angreal.integrations.flox import Flox
with Flox(".") as flox:
# Environment is activated
exit_code, stdout, stderr = flox.run("node", ["--version"])
print(f"Node version: {stdout}")
# Environment is automatically deactivated
from angreal.integrations.flox import Flox
flox = Flox(".")
# Start services
handle = flox.services.start("postgres", "redis")
# Check status
for svc in flox.services.status():
print(f"{svc.name}: {svc.status} (PID: {svc.pid})")
# View logs
logs = flox.services.logs("postgres", tail=50)
# Stop services
handle.stop()
import angreal
from angreal.integrations.flox import Flox, FloxServiceHandle
@angreal.command(name="dev-start", about="Start dev services")
def start_dev():
flox = Flox(".")
handle = flox.services.start("postgres", "redis")
handle.save() # Persists to .flox-services.json
print("Development services started")
@angreal.command(name="dev-stop", about="Stop dev services")
def stop_dev():
handle = FloxServiceHandle.load()
handle.stop()
print("Development services stopped")
import angreal
from angreal.integrations.flox import Flox, flox_required
@angreal.command(name="status", about="Show environment status")
def status():
flox = Flox(".")
print(f"Flox version: {Flox.version()}")
print(f"Environment: {flox.path}")
print(f"Exists: {flox.exists}")
print("\nServices:")
for svc in flox.services.status():
pid_info = f"PID {svc.pid}" if svc.pid else "stopped"
print(f" {svc.name}: {svc.status} ({pid_info})")
@angreal.command(name="test", about="Run tests")
@flox_required(".", services=["postgres"])
def test():
import subprocess
return subprocess.run(["pytest", "-v"]).returncode
import angreal
from angreal.integrations.git import Git
from angreal.integrations.docker import DockerCompose
from angreal.integrations.venv import VirtualEnv
@angreal.command(name="ci", about="Run CI pipeline locally")
def ci():
git = Git()
# Ensure clean state
_, _, status = git.status(short=True)
if status.strip():
print("Commit or stash changes first")
return 1
# Start services
dc = DockerCompose("docker-compose.test.yml")
dc.up(detach=True, build=True)
try:
# Run tests in venv
with VirtualEnv(".venv", requirements="requirements-test.txt") as venv:
import subprocess
result = subprocess.run(["pytest", "-v"])
return result.returncode
finally:
dc.down(volumes=True)