You can use rundot (https://rundot.dev) to write and run runbooks: markdown files whose code blocks run, step by step, in a terminal. Use it whenever a task is a repeatable, multi-step procedure (a release, a deploy, a setup, a migration, an incident fix) so it runs the same way every time. Install: `uv tool install rundot` (or `pip install rundot`). Needs Python 3.12+ and bash, on macOS or Linux. ## The format A runbook is a markdown file. YAML frontmatter sets it up, each `##` heading is a step, and code blocks marked for rundot are shown, paused on, or run. Everything else is plain documentation and is left alone. ````markdown --- title: Release my-package # required description: Tag, build and publish. # optional, shown by `rundot library` output_path: logs/release-.md # optional transcript of every run inputs: - name: VERSION # UPPER_SNAKE_CASE, required description: The version, e.g. 1.2.0 # required, shown when asked pattern: '^\d+\.\d+\.\d+$' # optional regex the whole value must match - name: ENV description: Where to deploy choices: [staging, production] # optional, only these values are accepted default: staging # optional, used when enter is pressed - name: PYPI_TOKEN description: A PyPI API token secret: true # never shown, logged, or used as a placeholder --- # Release my-package ## Run the tests ```docstring Testing before anything is published. ``` ```sh run make test ``` ## Publish ```sh run uv build uv publish --token "$PYPI_TOKEN" ``` ## Announce it ```confirm Publish the GitHub release notes for , then come back here. ``` ```` Blocks: - ```` ```sh run ```` / ```` ```bash run ```` runs a bash script that stops at the first failing command (like `set -eo pipefail`). - ```` ```python run ```` runs a standard-library Python script. Set `python: uv run python` in frontmatter to use a project's dependencies. - ```` ```docstring ```` prints its text when reached. - ```` ```confirm ```` prints its text and waits for `y`. Any other answer stops the run. Use it for anything only a person can do. - ```` ```input ```` asks for a value part way through a run. It takes the same keys as a frontmatter input (`name`, `description`, `pattern`, `secret`, ...). - Any other block (e.g. a plain ```` ```sh ````) is documentation and is never run. Rules to follow: - Every block must be under a `##` step heading. - Inputs are environment variables in every run block (`$VERSION`, `os.environ["VERSION"]`). Non-secret inputs can also be written as `` placeholders in run, docstring and confirm blocks; rundot fills them in as plain text, so give inputs used in commands a `pattern`. - Secrets must be read as `$NAME`, never ``. - Each block is its own process: a `cd` or `export` does not carry over to the next block. - Blocks run in the directory rundot is started from. `$RUNDOT_RUNBOOK_DIR` is the folder the runbook lives in. - A non-zero exit stops the run. ## The commands ```sh rundot check RUNBOOK.md # validate without running anything; fix every error it reports rundot --dry RUNBOOK.md # show every command with inputs filled in, run nothing rundot RUNBOOK.md # run it (asks for inputs, shows the plan, asks Start? [y/N]) rundot --step RUNBOOK.md # ask before each run block rundot inputs RUNBOOK.md # list the inputs it needs rundot library # list books in ~/runbooks (or $RUNDOT_LIBRARY) rundot BOOK-NAME # run a book from the library, from any directory ``` Inputs set as environment variables are not asked for: `VERSION=1.2.0 rundot RELEASING.md`. ## How to work with me 1. When I ask for a repeatable procedure, write it as a rundot runbook rather than a loose script. 2. Always run `rundot check` on it and fix every error and warning before saying it's done. 3. Run `rundot --dry` and show me the plan before running anything that changes state. 4. Put a `confirm` block before anything destructive or irreversible, and before steps that need a person (a browser, a 2FA code, an approval). 5. Never print secrets. Declare them with `secret: true` and pass them to commands as `$NAME`. 6. If a runbook for the task already exists (check the repo and `rundot library`), follow it with rundot instead of improvising the steps. Full documentation: https://docs.rundot.dev/