protean new
The protean new command initializes a new project with a given name.
Usage
protean new [OPTIONS] PROJECT_NAME
Arguments
| Argument | Description | Default | Required |
|---|---|---|---|
PROJECT_NAME |
Name of the new project | None | Yes |
The project name doubles as the name of the directory that gets created, so it
has to be a single directory segment: not empty, not . or .., and without
<>:"/\|?* or whitespace. Anything else is rejected before a single file is
touched.
Options
--output-dir,-o: Specifies the directory where the project should be created. If not provided, the current directory is used.
Note
Throws an error if the output directory is not found or not empty.
Combine with --force to overwrite existing directory.
-
--data,-d: Accepts one or more key-value pairs to be included in the project's configuration. Available configuration options include:author_name: Project author nameauthor_email: Project author emailshort_description: Brief project descriptiondatabase: Database choice (memory,postgresql,sqlite,elasticsearch)broker: Message broker choice (inline,redis,redis-pubsub)include_example: Include example domain code (true/false)
-
--defaults: Use default values for all prompts without interaction --skip-setup: Skip running setup commands (useful for testing)--from-model: Path to a text event-model file. Builds the whole project from the model and verifies it in one step (see Building from a model below). This is its own pipeline: it ignores--data,--dry-run, and--skip-setup, and always creates the project with the example slice off. It does honour--force, which clears an existing target the same way a plainprotean newdoes.--help: Shows the help message and exits.
Behavior Modifiers
--dry-run: Prints the project-relative path of every file the command would create, one per line, and writes nothing. The target directory is left alone whether or not it already has files in it, and--forcedoes not clear it under a dry run.--force,-f: Forces the command to run even if it would overwrite existing files. The target directory has to sit inside the output directory: if<output-dir>/<name>is a symlink pointing somewhere else, the command refuses to run, so the clear never reaches outside the output directory.
Generated Project Structure
The command creates a complete project structure with the following components:
Root Files
pyproject.toml: Python project configuration with uvREADME.md: Project documentationAGENTS.md: Guidance for an agent working on the project, inside a managed block. This is the dx-managed form, the same fileprotean dx installwrites, which composes the packaged pack guidance with the error-level Protean diagnostic rules. It is written and maintained byprotean dx; refresh it withprotean dx refreshafter upgrading Protean.CLAUDE.md: A one-line bridge (@AGENTS.md) that points Claude at theAGENTS.mdguidance. Also maintained byprotean dx.Makefile: Common development tasks.gitignore: Git ignore patterns.pre-commit-config.yaml: Pre-commit hooks configuration.env.example: Environment variables template.dockerignore: Docker ignore patterns.protean/dx-state.json: Records the managed filesprotean dxwrote (AGENTS.md,CLAUDE.md) and the pack version, soprotean dx checkandrefreshcan verify and update them.
Docker Configuration
Dockerfile: Production Docker imageDockerfile.dev: Development Docker imagedocker-compose.yml: Base docker-compose configurationdocker-compose.override.yml: Local development overridesdocker-compose.prod.yml: Production configurationnginx.conf: Nginx configuration for production
Activation Scripts
The scripts/ directory contains virtual environment activation scripts for different shells:
scripts/activate.sh: Bash/Zsh activationscripts/activate.fish: Fish shell activationscripts/activate.bat: Windows batch activation
Source Code Structure
The generated structure follows the Organize by Domain Concept pattern. The folder tree is organized around what the system does (domain concepts), not around technical layers. Protean's decorators carry architectural metadata (which layer, which side), so the folder structure doesn't need to repeat it.
src/
└── <package_name>/
├── __init__.py
├── domain.py # Domain initialization
├── domain.toml # Domain configuration
├── shared/ # Shared domain vocabulary and utilities
│ ├── __init__.py
│ ├── logging.py # Structured logging setup
│ ├── exceptions.py # Domain exceptions
│ └── value_objects.py
└── example/ # Optional example aggregate
├── __init__.py
├── aggregate.py # Example aggregate with a create() factory
├── commands.py # CreateExample
├── command_handlers.py
├── events.py # ExampleCreated
├── projection.py # ExampleSummary read model
└── projectors.py # Builds ExampleSummary from ExampleCreated
The example is a minimal walking skeleton: one command creates the aggregate,
which raises one event, which a projector turns into one read model. It shows
the write path and the read path end to end with nothing to trim before you
start. Delete the example/ folder (or pass --data include_example=false)
once you have your own aggregates.
Key structural decisions:
- Aggregates are top-level folders: Each aggregate (
example/) is a name someone in the business would recognize. - Projections live with the aggregate that sources them:
projection.py(the read model) andprojectors.py(the code that keeps it current) sit insideexample/. As projections grow to span aggregates, lift them into a domain-levelprojections/folder. - Shared vocabulary has its own folder: Cross-aggregate value objects
and utilities live in
shared/. domain.pyanddomain.tomlare front matter: A newcomer sees what this bounded context is and how it's configured immediately.
As your domain grows, add new aggregates as peer folders alongside
example/. See the
Organize by Domain Concept
pattern for guidance on evolving this structure, including colocating
commands with their handlers in capability files.
Test Structure
tests/
├── README.md
└── <package_name>/
├── conftest.py # Initialises the domain for the test session
├── test_smoke.py # Always generated: asserts the domain boots
├── domain/ # Domain logic tests
│ └── __init__.py
├── application/ # Application layer tests
│ ├── __init__.py
│ └── test_example.py # Write- and read-path tests for the example
└── integration/ # Integration tests
└── __init__.py
Every project gets test_smoke.py, which just asserts the domain boots, so
pytest collects and passes at least one test even when you opt out of the
example. With the example included, two more tests ship: one drives the
command/event write path, the other asserts the projection reflects the
created aggregate. pytest runs green on a fresh project, so you have a
working test to copy from on day one.
CI/CD Configuration
.github/workflows/ci.yml: GitHub Actions CI pipeline
Generated Modules
Logging Module (shared/logging.py)
The generated logging module provides structured logging with:
- JSON formatting for production environments
- Readable formatting for development
- Log rotation support with size-based rotation
- Environment-specific log levels (DEBUG, INFO, WARNING, ERROR)
Key Features:
get_logger(name): Get a configured structlog loggeradd_context(**kwargs): Add context variables to all subsequent logsclear_context(): Clear all context variableslog_method_call: Decorator for logging method callsconfigure_for_testing(): Reduce verbosity during tests
Environment Configuration:
The logging level is determined by PROTEAN_ENV, then ENV, then
ENVIRONMENT:
production/staging: INFO leveldevelopment: DEBUG leveltest: WARNING level- unset, or any other value: INFO level
Override with the PROTEAN_LOG_LEVEL environment variable.
Exceptions Module (shared/exceptions.py)
Domain-specific exception hierarchy:
DomainException: Base exception for all domain errorsInvalidStateException: Operation attempted in invalid stateNotFoundException: Requested resource not foundDuplicateException: Duplicate resource creation attemptValidationException: Domain validation failure
Examples
Creating a New Project
To create a new project named "authentication" in the current directory:
protean new authentication
Specifying an Output Directory
To create a new project in a specific directory:
protean new authentication -o /path/to/directory
Using Configuration Data
To create a project with PostgreSQL and Redis:
protean new authentication \
-d author_name="John Doe" \
-d author_email=john@example.com \
-d database=postgresql \
-d broker=redis
Creating a Project with Example Code
To include example domain code (aggregate, commands, events, handlers):
protean new authentication \
-d include_example=true \
--defaults
Quick Setup with Defaults
To quickly create a project with default options:
protean new my_project --defaults
Building from a model
--from-model builds a project from a text event model and verifies it in one
step. Write the model to a file:
aggregate Item:
field name: string(max_length=100)
field quantity: integer
command CreateItem:
field name: string(max_length=100)
field quantity: integer
event ItemCreated:
field item_id: string
field name: string(max_length=100)
field quantity: integer
Then point protean new at it:
protean new inventory --from-model model.txt
The command parses the model, creates the project (with the example slice off),
writes the slice the model describes, then runs the same checks as
protean verify (init, check, and the project's tests) and
reports the verdict. It parses the model first, so an invalid model exits with
the parser's error (and its line number) before any directory is created. If the
model parses but the slice cannot be generated or applied, the command prints the
error, leaves the created project directory in place, and exits non-zero.
Verification imports the new project's package into the running process, so the
project name cannot be one Protean itself already imports (protean, or a
standard-library name like json). Such a name would import the existing module
instead of the new code, so the command refuses to report a verdict and asks for
a different name.
Unlike a plain protean new, this path does not run the post-generation setup
(uv sync, git init, pre-commit). It composes create, generate, apply, and
verify only. See
ADR-0041 for the
model grammar.