runprompt: dotprompt files as shell commands
Run LLM prompts from your shell
At a glance
- What is it?
- A single-file Python script that turns .prompt files into command-line programs, with structured JSON output and shell hooks. It is a thin wrapper around the dotprompt spec, and the wrapper is the whole point.
- Who is it for?
- Adopt runprompt if you already keep prompts in version control and want them runnable as shell commands with structured output, especially on a team where the prompt file is the unit of review. Do not adopt it if you need a GUI, a hosted playground, or a provider abstraction layer with retry and failover logic, because runprompt does not claim to provide one.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem: prompts trapped in chat windows and notebooks
The README states the motivation directly: make prompts first-class artifacts, check them into your repo, and run them from the command line instead of making ad-hoc requests to AI chatbots. That is a workflow complaint, not a model-quality complaint. The target user is someone who already writes prompts repeatedly and wants them diffable, reviewable and scriptable. A .prompt file holds the prompt text and its metadata (model, schema, config) in one file, so the model choice and the output contract travel with the prompt rather than living in a shell history entry or a colleague's notes. If your prompts are one-off questions, this tool adds a file format and an install step for no benefit.
What actually executes: one Python file, dotprompt semantics
runprompt is distributed as a single-file Python script. The README's quick start downloads it with curl, chmod +x, and runs it. There is no daemon, no server, no config directory required. The file parses the frontmatter (YAML between --- markers) and the template body, then sends the rendered prompt to the provider named in the model field. Provider routing is expressed in that string: the examples use anthropic/claude-sonnet-4-20250514 and openai/gpt-4o, and the repository topics list anthropic, gemini, ollama, openai and openrouter. The template layer follows the dotprompt spec, and the README links to the Picoschema reference for output schemas. So runprompt is best understood as an executor for a format defined elsewhere. Its own contribution is the shell integration: stdin parsing, argument variables, pre-prompt commands, chaining and chat.
Input handling: STDIN, ARGS, INPUT and JSON variables
Three special variables are always available. {{STDIN}} holds the raw stdin as a string. {{ARGS}} holds everything after the prompt file path, joined with spaces. {{INPUT}} resolves to STDIN if present, otherwise ARGS. The README also notes that if the input is JSON it is parsed into individual variables, which is what makes chaining work: the JSON output of one prompt becomes the template variables of the next. That design has a sharp edge worth naming. Because JSON input is spread into top-level variables, a prompt that expects a variable called name will silently receive it from any upstream JSON that happens to contain that key. There is no namespacing shown in the README. For two-prompt pipelines this is convenient. For a longer chain, variable collisions become a real possibility and the failure mode is a wrong prompt, not an error.
Getting it running: three install paths and one prompt file
The README gives three ways in. Download the script and chmod it. Run it without installing via uvx --from git+https://github.com/chr15m/runprompt runprompt hello.prompt. Or install as a library with uv pip install git+https://github.com/chr15m/runprompt, or pip install "git+https://github.com/chr15m/runprompt.git". A minimal prompt file is frontmatter with a model line, then the template body. You supply the key through the environment, for example export ANTHROPIC_API_KEY, and pass data either as a JSON argument or on stdin. Frontmatter values can be overridden on the command line with dot notation, so --model and --output.format work against a file you did not edit. Inline prompts skip the file entirely with -p or --prompt plus --model. The --save-response flag writes the full API envelope, including token usage and finish reasons, to a path you choose. That last flag is the one I would use first on any new prompt, because the final text alone hides what the model actually did.
before: blocks run your shell before the model sees anything
Frontmatter can carry a before mapping of variable names to shell commands. Each command runs in $SHELL, defaulting to /bin/sh, with full shell features including pipes and redirects. On success stdout is captured; on failure stderr is captured and, notably, is still fed into the prompt as the variable value. All outputs are available individually plus a combined {{BEFORE}} variable. Template variables are exported as environment variables to those commands, so a command can read ${model} and branch on it. This is the most interesting part of the tool and also the part to think hardest about. The README's own example runs git log, date and a find piped into wc. The capture-on-failure behaviour means a broken command does not stop the run; the model receives an error string and may or may not notice. If you need a failing context command to abort, runprompt as documented does not give you that.
Chat mode, tools and the /edit escape hatch
Interactive mode is enabled by --chat, by RUNPROMPT_CHAT=1, or by chat: true in frontmatter. A prompt file supplies the initial persona or context, or you pass --model for a bare chat. History persistence is opt-in through RUNPROMPT_CHAT_HISTORY=1 or chat_history: true, stored at .runprompt.history unless RUNPROMPT_HISTORY_FILE overrides it. Inside a session, /read appends a file or URL to context, and /edit exposes a write_file tool for a named path while /drop removes it. This is a deliberately narrow tool surface compared with agent frameworks that expose a fixed toolset. The write_file tool is scoped per path and only exists while you have asked for it, which limits the blast radius of a confused model. It also means you cannot pre-declare a toolset in frontmatter and walk away; the README only documents enabling write_file interactively through /edit.
Where runprompt is the wrong tool
The README is explicit that the shell commands in before: run with your full environment and shell features. That is a deliberate choice, and it means a .prompt file checked into a repository is executable code in a way a plain text prompt is not. Reviewing a prompt file therefore means reviewing its frontmatter commands too. The second limitation is narrower: runprompt implements the dotprompt format rather than defining it, so anything the spec adds that the script has not caught up with is simply unavailable, and the README points to the external spec for template and Picoschema details rather than restating them. The third is the absence of releases. The repository metadata shows no releases retrieved, so installation is pinned to a git ref or to whatever the raw script URL returns at download time. If you need reproducible builds, you must pin the commit yourself. There is no evidence in the supplied material of retry logic, provider failover, or rate-limit handling, so treat it as a caller, not a gateway.
The alternative: an SDK plus your own wrapper
The obvious comparison is calling a provider SDK directly from a short Python script. The difference is where the contract lives. With an SDK call, the model name, the output schema and the prompt text sit in your Python source, and changing the model means editing and redeploying code. With runprompt, those live in a .prompt file that the same binary reads, and --model or --output.format can override them for a single run without touching the file. That is the trade: you accept a YAML frontmatter format and a Python dependency in exchange for prompts that are data rather than code. If your team already treats prompts as configuration, runprompt matches that. If your prompts are tightly coupled to application logic and change with it, the SDK route avoids a second file format and a second parser.
Editorial conclusion
Adopt runprompt if you already keep prompts in version control and want them runnable as shell commands with structured output, especially on a team where the prompt file is the unit of review. Do not adopt it if you need a GUI, a hosted playground, or a provider abstraction layer with retry and failover logic, because runprompt does not claim to provide one. Before committing, verify three things against the repository: which providers the current script actually wires up, whether the Picoschema subset you need is supported, and how the before: commands behave on your platform, since they run through $SHELL with your full environment.
Community notes