Usage

Basic command

uv-start <project-name> [options]

The project name must not contain spaces or underscores.

Options

Flag

Description

-t, --type [lib|package]

Project type to create. lib (default) creates a simple library with a src/ layout. package creates an installable package with an entry-point script.

-p, --python [3.14|3.13|3.12|3.11|3.10]

Python version for the new project (default: 3.13).

-w, --workspace

Create a uv workspace (monorepo). You will be prompted to add a shared utilities library and additional sub-projects.

-g, --github

Initialise a Git repository and create a GitHub remote. Sets up CI/CD workflows automatically.

--private

Make the GitHub repository private. Requires --github.

--data

Create a data analysis project. Installs Jupyter, pandas, matplotlib, and seaborn. No src/ layout — just a flat project with a starter notebook, lab matplotlib style, and colour palette.

--config NAME EMAIL

Save author name and email for project templates. Stored in ~/.config/uv-start/config.toml.

Examples

Create a library (default)

uv-start my-lib

Creates a my-lib/ directory with a src/my_lib/ package, test directory, and all dev-tool configuration.

Create an installable package

uv-start my-cli -t package

Same as a library but also registers a console script entry-point so the package can be run from the command line.

Specify a Python version

uv-start my-project -p 3.12

Target Python 3.12 instead of the default 3.13.

Create a project with a GitHub repo

uv-start my-project -g

Initialises a local Git repo, creates a public GitHub remote, pushes an initial commit, and sets up CI workflows.

uv-start my-project -g --private

Same as above but the GitHub repository is private.

Create a workspace (monorepo)

uv-start my-workspace -w

Sets up a uv workspace. You will be interactively prompted to:

  1. Add a common-utils shared library

  2. Add additional sub-projects to the workspace

Combine workspace and GitHub:

uv-start my-workspace -w -g

Create a data analysis project

uv-start my-analysis --data

Creates a flat project (no src/ layout) pre-configured for interactive data analysis:

  • Jupyter, pandas, matplotlib, and seaborn installed in the .venv

  • Lab matplotlib style (hhlab_style01.mplstyle) at the project root

  • colors.py — a COLOR enum with the lab’s standard palette

  • sample.ipynb — starter notebook that applies the style and imports COLOR

  • CLAUDE.md — instructions for AI-assisted work that enforce consistent figure styling

Combine with --github to create a GitHub repository at the same time:

uv-start my-analysis --data -g

Generated project structure

Standard project

project-name/
├── src/
│   └── project_name/
│       └── __init__.py
├── tests/
├── pyproject.toml
├── README.md
├── LICENSE
├── .env.example
└── .pre-commit-config.yaml

Workspace

workspace-name/
├── packages/
│   ├── package1/
│   └── package2/
├── pyproject.toml
├── README.md
└── .pre-commit-config.yaml

Data analysis project

analysis-name/
├── hhlab_style01.mplstyle   ← lab matplotlib style, loaded by the notebook
├── colors.py                ← COLOR enum with the lab palette
├── sample.ipynb             ← starter notebook
├── CLAUDE.md                ← AI instructions for consistent figure style
├── pyproject.toml
├── README.md
├── .env.example
└── .gitignore

Development tools configured

Every generated project comes with:

  • Ruff — linter and formatter (line length 79, flake8 + isort rules)

  • Ty — static type checker (checks src/ and tests/)

  • pytest — test framework with automatic discovery in tests/

  • commitizen — conventional commits, automatic version bumping, and changelog generation (see Conventional commits and version bumps below)

  • pre-commit — Git hooks that run linting, formatting, and type checking before each commit

Data analysis project details

Matplotlib style

hhlab_style01.mplstyle is placed at the project root so it can be referenced by name from any notebook in the same directory:

import matplotlib.pyplot as plt
plt.style.use('hhlab_style01.mplstyle')

Always apply this style before creating any figure.

Lab colour palette

colors.py defines the COLOR enum with the lab’s standard palette. Use the enum values — never raw hex strings or default matplotlib colors:

from colors import COLOR

ax.scatter(x, y, color=COLOR.BLUE.value)
ax.bar(labels, heights, color=COLOR.PINK.value)

Available colours:

Name

Hex

Typical use

COLOR.BLUE

#526C94

Primary data series

COLOR.LIGHT_BLUE

#75B1CE

Secondary / paired condition

COLOR.PINK

#DC6B83

Contrast / highlight

COLOR.YELLOW

#D8C367

Third condition

COLOR.TURQUOISE

#00bfb2

Fourth condition

COLOR.LIGHT_GREEN

#CCDBA2

Fifth condition

COLOR.LAVENDER

#C6B2D1

Sixth condition

COLOR.PURPLE

#654875

Seventh condition

COLOR.OLIVE

#889466

Eighth condition

COLOR.GREY

#D4D3CF

Background / negative control

COLOR.DARKGREY

#4A4A4A

Text / annotations

To add a new colour, extend the COLOR enum in colors.py rather than scattering raw hex values through notebooks.

Starter notebook

sample.ipynb opens with the standard imports already in place:

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from colors import COLOR

plt.style.use('hhlab_style01.mplstyle')

It also includes a commented example scatter plot to illustrate the expected usage pattern.

CLAUDE.md

The generated CLAUDE.md tells Claude Code (and other AI tools) to always apply the lab style and use the COLOR enum. This keeps AI-generated figure code consistent with hand-written code across the project.

Conventional commits and version bumps

Generated projects use commitizen to enforce conventional commits and automate semantic versioning.

Use cz commit instead of git commit to get an interactive prompt that builds a correctly formatted message:

cz commit

To bump the version based on your commit history:

cz bump

Commitizen inspects all commits since the last tag and determines the version bump automatically:

Commit prefix

Version bump

Example

fix:

PATCH (0.0.X)

fix: handle missing config file

feat:

MINOR (0.X.0)

feat: add --config flag

BREAKING CHANGE: in footer, or ! after type

MAJOR (X.0.0)

feat!: replace .env with config system

Other commit types — chore:, docs:, refactor:, ci:, test:, style:, perf:, build: — are recorded in the changelog but do not trigger a version bump.

Note

While major_version_zero is enabled in the commitizen configuration (the default for generated projects), breaking changes bump the minor version instead of major, following the SemVer spec for 0.x releases.

Workspace versioning

When a workspace is created with -w, all packages share a single synchronized version. The commitizen configuration lives only in the root pyproject.toml and its version_files list covers every sub-package:

  • src/<module>/__init__.py__version__ string

  • pyproject.tomlversion field

  • README.md — version badge

Running cz bump at the workspace root updates all of these files across the root project and every sub-package in one step. The release CI workflow does the same automatically when a conventional commit lands on main.

GitHub CI/CD workflows

When --github is used, two GitHub Actions workflows are created:

CI pipeline — runs on every push and pull request:

  • Ruff linting and format checking

  • Ty type checking

  • pytest test suite

Release pipeline — runs on pushes to main:

  • Automatic version bumping based on conventional commits

  • GitHub release creation with changelog

  • For workspaces, bumps the single synchronized version across all packages

Logging system

Every generated project includes a ready-to-use logging module at src/<package_name>/config.py. It provides environment-variable-driven configuration with sensible defaults, console and rotating file handlers, and environment-specific overrides.

Getting a logger

Import get_logger from your package’s config module:

from my_package.config import get_logger

logger = get_logger(__name__)

logger.debug("Detailed diagnostic info")
logger.info("General operational messages")
logger.warning("Something unexpected happened")
logger.error("Something failed")

The first call to get_logger configures the root logger based on environment variables. Subsequent calls return child loggers that inherit this configuration.

Environment variables

Control logging behaviour via the .env file or shell environment. Each generated project includes a .env.example with sensible defaults. Copy it to activate:

cp .env.example .env

Warning

The .env file is gitignored by default. Never commit it — it is for local configuration and secrets only.

Available variables:

Variable

Default

Description

LOG_LEVEL

INFO

Minimum level to capture (DEBUG, INFO, WARNING, ERROR, CRITICAL)

ENABLE_CONSOLE_LOGGING

true

Print log messages to the terminal

ENABLE_FILE_LOGGING

true

Write log messages to a rotating file

LOG_FILE_PATH

logs/app.log

Path to the log file

LOG_MAX_BYTES

1048576

Max size per log file before rotation (1 MB)

LOG_BACKUP_COUNT

5

Number of rotated log files to keep

LOG_FORMAT

see below

Python logging format string

ENV

development

Active environment; loads .env.<ENV> overrides if present

Default log format:

%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s

Environment-specific configuration

Create environment-specific .env files to override defaults:

  • .env.example — template with defaults (checked into version control)

  • .env — your local copy of .env.example (gitignored, never commit)

  • .env.development — local development overrides (gitignored)

  • .env.production — production settings (gitignored)

Example .env.production:

LOG_LEVEL=WARNING
ENABLE_CONSOLE_LOGGING=false
LOG_FILE_PATH=/var/log/myapp/app.log

Activate an environment:

export ENV=production

The configuration strategy is:

  1. Load hard-coded defaults

  2. Load the base .env file (if present)

  3. Load .env.<ENV> (if present), overriding previous values