untaped-recipe
Install it as part of untaped: uv tool install 'untaped[recipe]' or pip install 'untaped[recipe]'.
To add it to an existing install, see Getting started.
untaped recipe applies the same file changes to many directories: add a
config file, bump a version in YAML, remove a stale workflow. A recipe is a
list of steps; packs bundle recipes with Python hooks. Every change is
planned in memory, previewed, and written only after you confirm, with a
backup of every file it touches.
Recipes work on plain directories and never commit, push or open pull requests. No recipe step runs a shell command, but pack hooks are Python code that runs on your machine (see Install and manage packs).
Recipe verbs sit directly under untaped recipe; packs, hooks and backups
have their own nouns (recipe packs …, recipe hooks …, recipe backups …).
The packaged skill is the full reference:
applying,
packs, tests and backups
and authoring.
Set up
Recipes need no setup beyond installing a pack (below). uv must be on your
PATH to run pack hooks. Settings such as recipe.library_root (where packs
are installed) and hook timeouts are in the
configuration reference.
Apply a recipe
untaped recipe apply acme/editorconfig ~/work/api ~/work/web --dry-run
untaped recipe apply acme/editorconfig ~/work/api ~/work/web --preview diff
untaped recipe apply acme/editorconfig ~/work/api ~/work/web
The recipe argument is a unique recipe name, a pack/recipe ref, or a path.
A value is a path only when it starts with ./, ../, / or ~, is . or
.., or ends in .yml/.yaml. For a local pack directory, pass its path and
--recipe NAME.
--dry-run previews without writing; pack hooks still run, because they
compute the plan. A target that fails to plan or write changes nothing and
is reported; the other targets still run. Within a target, writes are one
transaction.
Check for drift in CI
untaped recipe apply acme/editorconfig ~/work/api --check
--check writes nothing, asks nothing and makes no backup, and exits 3
when any target would change.
Apply to every repo of a workspace
--stdin reads target paths, or records with a target_path:
untaped workspace status NAME --format pipe \
| untaped recipe apply acme/editorconfig --stdin --dry-run
With --stdin the confirmation reads the terminal. Without one, pass
--yes, --dry-run or --check.
Inputs
Recipes declare inputs. Give them values with --var KEY=VALUE or YAML
files, the same flags awx test uses for suite variables:
untaped recipe apply acme/codeowners ~/work/api --vars-file base.yml --var owner=@acme/platform
untaped recipe apply acme/readme --stdin --input-from 'service={{ target.name }}' < dirs.txt
--var wins over every file, and a later file over an earlier one.
--vars-file values are YAML, so quote version-like strings
(python_version: "3.10"). --input-from derives a value per target. A
required input still missing is prompted for at a terminal; otherwise the
run fails naming it. Sensitive inputs show as *** in rows, previews and
backups, and their targets show no diff. The full precedence and prompt
rules are in the apply reference.
Install and manage packs
Installing a pack installs code: its hooks run on your machine with no
sandbox, including during apply --dry-run and --check, since hooks compute
the planned changes. Inspect a pack before you trust it (recipe packs get,
recipe validate, recipe test). Hooks get a reduced environment without
your tokens, but they run as you with full file access; the
environment list
says exactly what they can see.
untaped recipe packs add https://git.example.com/acme/untaped-recipes.git --rev v1.2.0
untaped recipe validate acme
untaped recipe packs sync --all --dry-run
- A pack must contain a
uv.lockand no symlinks. Reinstalling needs--force; local edits to the installed copy also need--discard-edits. packs syncre-fetches packs from the source and--revrecorded at install, so a branch or tag moves forward. It lists the packs whose files would change, with the hook code that changes, and confirms first: review that list as you would a dependency upgrade.recipe validatechecks the whole library, or one pack, recipe or path, without importing hook code; any failing check exits 1.
Write a pack
untaped recipe packs init acme
untaped recipe init ./acme/editorconfig
untaped recipe hooks init ./acme/pin_python --kind transform
Each init refreshes the pack's uv.lock, which needs access to a package index.
--no-lock skips that, but hooks cannot run until uv lock succeeds.
A pack is a Python project. Its pyproject.toml lists recipes and hooks under
[tool.untaped_recipe.recipes] and [tool.untaped_recipe.hooks].
Recipe file
version: 1
description: Add a standard .editorconfig and pin the Python version
inputs:
python_version:
type: str
default: "3.14"
steps:
- type: template
template: templates/editorconfig
dest: .editorconfig
if_absent: true
- type: transform
hook: yaml_edit
file: .pre-commit-config.yaml
optional: true
args:
edits:
- op: set
path: [default_language_version, python]
value: "python{{ python_version }}"
- type: remove
file: .travis.yml
A validate step runs a hook that passes, fails or skips the target;
transform rewrites file content through a hook; template, copy and
remove render, copy and delete files. Step fields are in the
authoring reference.
globs has no implicit excludes: add exclude: [".git/**"] when the
targets are Git clones.
Hooks
A hook module exports transform(), validate(), or both. hooks init
writes a typed stub and a pytest for it. For YAML files, use the built-in
yaml_edit hook shown above. Debug one hook without a recipe:
untaped recipe hooks run acme/pin_python --target ~/work/api --file pyproject.toml \
--var python_version=3.14 --diff
Hooks compute the plan, so they must only read the target tree and their own pack: no writes, no network.
Golden tests
recipe init also creates a test case under tests/RECIPE/CASE/: given/
is the starting directory, expected/ the full expected result (omit it to
assert no change), and an optional case.yml sets inputs and expectations.
untaped recipe test ./acme
untaped recipe test acme/editorconfig --update
--update rewrites expected/ from the current plan.
Backups
Each apply writes one backup bundle unless you pass --no-backup.
untaped recipe backups restore latest --dry-run
untaped recipe backups restore latest
untaped recipe backups prune --keep 20 --dry-run
restore refuses to overwrite a file that changed after the backup unless
you pass --force. Backups hold file content only, not modes or times.
Output
apply prints one recipe.apply_outcome row per target, with action
planned, applied, unchanged, skipped, cancelled or failed; the
preview goes to stderr. See Scripting
and Exit codes.
See also
Metadata
Release files for untaped-recipe 10.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| untaped_recipe-10.0.0.tar.gz | 96.4 kB | With provenance | Details for untaped_recipe-10.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| untaped_recipe-10.0.0-py3-none-any.whl | Python 3 | none | any | Details for untaped_recipe-10.0.0-py3-none-any.whl |
Total release size: 224.8 kB
Release files / untaped_recipe-10.0.0.tar.gz
| Download URL | untaped_recipe-10.0.0.tar.gz |
|---|---|
| Size | 96.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e5dda6dbdd7f0dae3c548076f037d3d518661efb237d791f984cd344543d3a2c
|
|
BLAKE2b-256 checksum How to use checksums |
c444a4c02407bad4c5f92d72e089de103d74830466b5853f6e6710ea968e4b29
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / untaped_recipe-10.0.0-py3-none-any.whl
| Download URL | untaped_recipe-10.0.0-py3-none-any.whl |
|---|---|
| Size | 128.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b8c88abd72b9186e7d7a8cfb73b2dae123f55e1bfd8c709290debf115e7a7f77
|
|
BLAKE2b-256 checksum How to use checksums |
cb2a1448955d8c43fd1fd7068693b1e042f0a922a6c690c310c7163cf1bee4ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logSecurity for untaped-recipe 10.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for untaped_recipe-10.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.