From angreal
Guides creating reusable angreal project templates for `angreal init`, covering angreal.toml config, Tera templating, variables, prompts, validation, and publishing.
npx claudepluginhub angreal/angreal --plugin angrealThis skill uses the workspace's default tool permissions.
Create reusable project templates that others can consume via `angreal init <template>`.
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.
Create reusable project templates that others can consume via angreal init <template>.
Templates let you create reusable project scaffolds. Users initialize new projects with:
angreal init https://github.com/user/my-template
angreal init /path/to/local/template
This is different from angreal-init (adding angreal to existing projects) - templates create entirely new projects from scratch.
Every angreal template follows this structure:
my-template/
├── angreal.toml # Template configuration (required)
├── {{ project_name }}/ # Templated directories
│ ├── src/
│ └── tests/
├── README.md # Templated files
├── static_file.txt # Static files (copied as-is)
└── .angreal/ # Optional: post-init tasks
├── init.py # Post-initialization script
└── task_*.py # Template-specific tasks
The angreal.toml file defines template variables at the root level:
# Variables with defaults - users will be prompted for these
project_name = "my-project"
author_name = "Anonymous"
description = "A new project"
license = "MIT"
use_docker = false
python_version = "3.11"
# Optional: Custom prompts for better UX
[prompt]
project_name = "Enter your project name"
author_name = "Enter your name"
license = "Choose a license (MIT, Apache-2.0, GPL-3.0)"
# Optional: Validation rules
[validation]
project_name.not_empty = true
project_name.length_min = 3
license.allowed_values = ["MIT", "Apache-2.0", "GPL-3.0"]
# String variables
project_name = "default-name"
author = "Your Name"
# Boolean variables
use_docker = false
include_tests = true
# Numeric variables
port = 8080
# List variables
dependencies = ["requests", "click", "pydantic"]
Customize what users see when providing values:
[prompt]
project_name = "Enter your project name (lowercase, no spaces)"
author_email = "Your email address"
use_docker = "Include Docker support? (y/n)"
Validate user input with these options:
[validation]
# Required field
project_name.not_empty = true
# String length
username.length_min = 3
username.length_max = 20
# Numeric range
port.type = "integer"
port.min = 1024
port.max = 65535
# Allowed values
license.allowed_values = ["MIT", "Apache-2.0", "GPL-3.0"]
# Regex pattern
email.regex_match = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
Angreal uses Tera (similar to Jinja2) for templating.
# {{ project_name }}
{{ description }}
Created by {{ author_name }}
# {{ project_name }}
{% if use_docker %}
## Docker
Build and run with Docker:
```bash
docker build -t {{ project_name }} .
docker run {{ project_name }}
{% endif %}
{% if include_tests %}
pytest tests/
{% endif %}
### Loops
```python
# requirements.txt
{% for dep in dependencies %}
{{ dep }}
{% endfor %}
# {{ project_name | title }}
Package: {{ project_name | lower | replace(from=" ", to="-") }}
Class: {{ project_name | title | replace(from=" ", to="") }}
Common filters:
upper / lower - Change casetitle - Title casereplace(from="x", to="y") - Replace texttrim - Remove whitespacemy-template/
└── {{ project_name }}/
├── {{ project_name }}/
│ └── __init__.py
└── tests/
With project_name = "my-app" creates:
my-app/
├── my-app/
│ └── __init__.py
└── tests/
{{ project_name }}.py
test_{{ project_name }}.py
Use {% raw %} to preserve template syntax in output files:
{% raw %}
{
"name": "{{ package_name }}",
"scripts": {
"test": "jest"
}
}
{% endraw %}
This outputs the literal {{ package_name }} without substitution. Use for:
Control which files and directories are created based on template variables.
Create files that are empty (and thus effectively skipped) when conditions aren't met:
{# Dockerfile - only has content if use_docker is true #}
{% if use_docker %}
FROM python:{{ python_version }}
WORKDIR /app
COPY . .
RUN pip install -e .
CMD ["python", "-m", "{{ project_name }}"]
{% endif %}
Use .angreal/init.py to create or remove directories based on variables:
# .angreal/init.py
import shutil
import os
import angreal
def init():
context = angreal.get_context()
project = context.get('project_name', 'project')
# Conditionally create directories
if context.get('include_docs', False):
os.makedirs(f"{project}/docs", exist_ok=True)
# Remove directories if feature not selected
if not context.get('use_docker', False):
docker_path = f"{project}/docker"
if os.path.exists(docker_path):
shutil.rmtree(docker_path)
# Remove optional files
if not context.get('include_ci', False):
ci_file = f"{project}/.github/workflows/ci.yml"
if os.path.exists(ci_file):
os.remove(ci_file)
Structure your template with optional feature directories:
my-template/
├── angreal.toml
├── {{ project_name }}/
│ ├── src/ # Always included
│ ├── tests/ # Always included
│ ├── docker/ # Removed if use_docker=false
│ │ ├── Dockerfile
│ │ └── docker-compose.yml
│ ├── docs/ # Removed if include_docs=false
│ │ └── index.md
│ └── .github/ # Removed if include_ci=false
│ └── workflows/
│ └── ci.yml
└── .angreal/
└── init.py # Handles conditional removal
# angreal.toml
project_name = "my-project"
use_docker = false
include_docs = true
include_ci = true
[prompt]
use_docker = "Include Docker support? (y/n)"
include_docs = "Include documentation? (y/n)"
include_ci = "Include CI/CD config? (y/n)"
Include config files only when needed:
# .angreal/init.py
import os
import angreal
def init():
context = angreal.get_context()
project = context.get('project_name')
# Create pytest.ini only if using pytest
if context.get('use_pytest', False):
with open(f"{project}/pytest.ini", 'w') as f:
f.write("[pytest]\ntestpaths = tests\n")
# Create .env.example only if using env vars
if context.get('use_env_vars', False):
env_vars = context.get('env_vars', ['DEBUG', 'API_KEY'])
with open(f"{project}/.env.example", 'w') as f:
for var in env_vars:
f.write(f"{var}=\n")
Let users choose between mutually exclusive options:
# angreal.toml
project_name = "my-app"
web_framework = "flask"
[prompt]
web_framework = "Choose web framework (flask, fastapi, django)"
[validation]
web_framework.allowed_values = ["flask", "fastapi", "django"]
# .angreal/init.py
import shutil
import os
import angreal
def init():
context = angreal.get_context()
project = context.get('project_name')
framework = context.get('web_framework', 'flask')
# Template includes all framework options
frameworks = ['flask', 'fastapi', 'django']
# Remove non-selected framework files
for fw in frameworks:
if fw != framework:
fw_dir = f"{project}/templates/{fw}"
if os.path.exists(fw_dir):
shutil.rmtree(fw_dir)
# Rename selected framework dir
selected = f"{project}/templates/{framework}"
if os.path.exists(selected):
# Move contents up and remove framework dir
for item in os.listdir(selected):
shutil.move(f"{selected}/{item}", f"{project}/{item}")
os.rmdir(selected)
os.rmdir(f"{project}/templates")
Create .angreal/init.py to run code after template rendering:
"""Post-initialization script."""
import subprocess
import angreal
def init():
"""Run after template is rendered."""
context = angreal.get_context()
project_name = context.get('project_name', 'project')
print(f"Initializing {project_name}...")
# Initialize git
subprocess.run(['git', 'init'], cwd=project_name)
# Install dependencies if requested
if context.get('install_dependencies', False):
subprocess.run(
['pip', 'install', '-r', 'requirements.txt'],
cwd=project_name
)
print(f"Done! Next steps:")
print(f" cd {project_name}")
print(f" angreal tree")
Add tasks useful for projects created from your template:
# .angreal/task_setup.py
import angreal
import subprocess
@angreal.command(name="setup", about="Set up development environment")
def setup():
"""Initialize the development environment."""
project_root = angreal.get_root().parent
print("Creating virtual environment...")
subprocess.run(['python', '-m', 'venv', '.venv'], cwd=project_root)
print("Installing dependencies...")
subprocess.run(
['.venv/bin/pip', 'install', '-r', 'requirements.txt'],
cwd=project_root
)
print("Setup complete!")
return 0
python-template/
├── angreal.toml
├── {{ project_name }}/
│ ├── {{ project_name }}/
│ │ └── __init__.py
│ ├── tests/
│ │ └── test_main.py
│ ├── README.md
│ ├── pyproject.toml
│ └── .gitignore
└── .angreal/
├── init.py
└── task_dev.py
# Variables at root level
project_name = "my-project"
author = "Your Name"
email = "you@example.com"
description = "A Python project"
python_version = "3.11"
use_pytest = true
# Custom prompts
[prompt]
project_name = "Enter your project name"
author = "Your full name"
email = "Your email address"
# Validation
[validation]
project_name.not_empty = true
project_name.length_min = 3
email.regex_match = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
# {{ project_name }}
{{ description }}
## Installation
```bash
pip install -e .
{% if use_pytest %}
pytest
{% endif %}
{{ author }} <{{ email }}>
### {{ project_name }}/pyproject.toml
```toml
[project]
name = "{{ project_name }}"
version = "0.1.0"
description = "{{ description }}"
authors = [
{name = "{{ author }}", email = "{{ email }}"}
]
requires-python = ">={{ python_version }}"
{% if use_pytest %}
[project.optional-dependencies]
dev = ["pytest"]
{% endif %}
angreal init https://github.com/user/templateangreal init /path/to/template.angreal/ with dev setup commandsangreal init on your own templateFor advanced use in tasks:
import angreal
# Render a template STRING (not a file!)
# Takes a template string and returns the rendered result
template = "Hello {{ name }}!"
result = angreal.render_template(template, {"name": "World"})
# result == "Hello World!"
# Render entire directory (positional args: src, dst, force, context)
rendered_files = angreal.render_directory(
"templates/project",
"my-project",
False,
{"project_name": "My Project"}
)
# Returns list of created file paths
# Generate context from angreal.toml
# Parameters: path, take_input
context = angreal.generate_context(
path="angreal.toml",
take_input=True # Prompt user for values
)
# Get current template context (during template initialization)
context = angreal.get_context()
# Render a template string
angreal.render_template(template: str, context: dict) -> str
# Render a templated directory (context is optional)
angreal.render_directory(src: str, dst: str, force: bool, context: dict | None) -> list[str]
# Generate context from TOML file
angreal.generate_context(path: str, take_input: bool) -> dict