prompt2: AI CLI

Transform your terminal into an AI powerhouse ⚡ Craft dynamic prompts, automate workflows, and generate code at lightspeed - all through CLI sorcery 🧙♂️

Why prompt2?

  • 🎮 Prompt Engineering Playground: Iterate faster than ChatGPT can say “hallucination”

  • 🖥️ Terminal Native: Feels like home for vim-wielding keyboard warriors

  • Jinja2 Turbocharged: Magic variables like {{reads}}, {{writes}}, and custom filters

  • 💸 Cache Commander: Automatic response caching slashes API costs by 40%+

  • 🔌 Plugin Ecosystem: Extend with Python simplicity (backends, parsers, custom filters)

🚀 Blazing Start

# litellm pulls a lot of dependencies I don't need in my air-gapped networks
# as such, you need to pull it manually to use the litellm plugin:
pip install prompt2 litellm
export OPENROUTER_API_KEY=sk_...
prompt2 edit hello
prompt2 send hello

prompt2 is the AI CLI where YOU control the narrative.

Note

🔌 LiteLLM pros: Use any supported ENV vars instead of OPENROUTER_API_KEY

🕹️ Tutorial Time!

  • run prompt2 edit hello, this will open your $EDITOR, there, type something like create a hello world in python. This created a prompt in ~/.prompt2/prompts/hello.txt and you can see it with prompt2 list.

  • run prompt2 send hello, you will see the AI responded with a tutorial to create a python script.

  • run prompte2 send hello wholefile to use the wholefile parser with that prompt, you will see that you only get the python source code as output

  • run prompt2 send hello wholefile > hello.py, you will see that prompt2 caches responses to save costs, so the second time running that command is very fast

  • run prompt2 edit hello, change your text with:

    update this script to also print any sys arg with the hello string {{read('hello.py')}}
    

    Hell yes, we’re using jinja2 inside the prompt and telling it to actually read the content of the file, you’ll be able to register your own jinja functions and template paths too!

  • run prompt2 send hello wholefile > hello.py

  • profit

I know prompt-fu

Troubleshooting

DEBUG=1 prompt2 send hello  # See the magic behind the curtain

Models

Juggle between multiple models by defining environment variables.

MODEL

An environment variable to configure the default LLM plugin, as far as the base plugin is concerned, it must be in the following format:

[<code2 backend plugin>] [<args>...] [<kwarg=value>...]

When the backend plugin is omited, or not found, litellm is used by default which should be fine in most cases, leveraging the env vars you already have say for aider or anything using litellm, such as $OPENAI_API_KEY, $OPENROUTER_API_KEY, etc. For the litellm backend, the first arg is the model name and kwargs are optional.

The default value for MODEL env var is:

MODEL=openrouter/google/gemini-2.5-pro-exp-03-25:free

You can add litellm kwargs as such:

MODEL='openrouter/deepseek/deepseek-chat:free max_tokens=16384'
MODEL_name

MODEL_name is not an actual variable, it represents other models configurations that you can have, ie:

MODEL_EDITOR='deepseek/deepseek-chat:free max_tokens=16384'
MODEL_ARCHITECT=xai/grok-2-latest

While you will be able to use the model names you want in your own code2 workflow plugins, code2 core prompt plugins will try to use the best one that is defined for a purpose based on standard names, “editor” and “architect” at this time. https://aider.chat/2024/09/26/architect.html

Python API

Script

You could script like this:

from prompt2 import Model, Prompt


async def ai_wizard():
    # get the model defined in $MODEL_ARCHITECT, or $MODEL, or the default
    model = Model('architect')

    # load one of your prompts by name
    prompt = Prompt('hello')

    # call the model with the prompt, with a parser by name
    result = await model(prompt, 'wholefile')

    # there you go
    with open('hello.py', 'w') as f:
        f.write(result)

import asyncio
asyncio.run(ai_wizard())

Test

The pytest plugin was pretty easy to write, given that prompt2 already does response caching. Just add the prompt2_env fixture in your pytest script and it will:

  • store the cache in your repo instead of home dir, so that it can find it again

  • store prompts in temporary directories, so that you can mess with them

Plugins

prompt2 supports a bunch of plugin types, all registered by Python’s standard entry points.

  • parser: those define 2 functions:
    • one allowing to change the messages before sending, such as to add something like “reply with a structured list”

    • another to convert the string reply into a python variable, ie. to parse a list, file, diff …

  • jinja2: register custom functions to have in your templates

  • backend: custom APIS backend: you probably won’t need that, the default litellm backend works great, but if you’re using prompt2 behind an air-gapped network with a custom AI API, at least you’re able to register another backend.

Jinja functions

Functions that will be exposed in Jinja2.

Add yours over the prompt2_jinja2 entry point plugin!

Note that all prompt paths are added to the Jinja2 loader, so, you can already include your prompts in your prompts:

{% include('your_prompt.txt') %}

Or even go crazy with {% extend %} and {% macro %}!

prompt2.jinja2.dirs(path=None)[source]

Show the list of directories within a path.

Renders:

Directories:
- path/to/directory1
- path/to/directory2
Parameters:

path – Path to walk

prompt2.jinja2.exec(*command, **env)[source]

Execute a command and return the full output.

Parameters:

command – String or args list.

prompt2.jinja2.file(path)[source]

Show a file path content with context markers.

It will render:

path/to/file source code:
```
<source code here>
```
Parameters:

path – File path

prompt2.jinja2.files(path=None)[source]

Show the list of files within a path.

Renders:

Files:
- path/to/file1
- path/to/file2
Parameters:

path – Path to walk

prompt2.jinja2.read(path)[source]

Read a file from the filesystem

Parameters:

path – Path of the file to read.

prompt2.jinja2.shell(*command, **env)[source]

Show a command output with context markers.

It will render:

Output of `command line`:
```
<output here>
```
Parameters:
  • command – Command

  • env – Environment variables

CLI Reference

prompt2

prompt2

Template based prompts on the CLI

Sub-Command

Help

help

Get help for a command or group

ask

Ask a question from the CLI

paths

Return prompt paths

list

List available prompts

edit

Edit a prompt

show

Show a prompt

render

Render a prompt with a given template context

parser

Show a parser

parsers

List registered parsers

messages

Render prompt messages with a given template context

send

Send a prompt, rendered with a context, on a model with a parser

prompt2 ask

prompt2 ask [ARGS]... [parser=PARSER] [model=MODEL]

Function: prompt2.cli.ask()

Ask a question from the CLI

Example:

prompt2 ask write a hello world in python

Argument

Help

[ARGS]...

Question to ask

  • Required: True

  • usage: Any un-named arguments, ie.: something other

[parser=PARSER]

Parser name if any

  • Required: False

  • Default: None

[model=MODEL]

Model name to use, if any

  • Required: False

  • Default: None

prompt2 paths

prompt2 paths

Function: prompt2.cli.paths()

Return prompt paths

prompt2 list

prompt2 list

Function: prompt2.cli.prompts()

List available prompts

prompt2 edit

prompt2 edit NAME [local]

Function: prompt2.cli.edit()

Edit a prompt.

$HOME/.prompt2

Argument

Help

NAME

Prompt name.

  • Required: True

[local]

Enable this to store in $CWD/.prompt2 instead of $HOME/.prompt2

  • Required: False

  • Type: bool

  • Default: False

  • Accepted: yes, 1, true, no, 0, false

prompt2 show

prompt2 show PROMPT

Function: prompt2.cli.show()

Show a prompt

Argument

Help

PROMPT

Prompt name

  • Required: True

prompt2 render

prompt2 render PROMPT [CONTEXT=VALUE]...

Function: prompt2.cli.render()

Render a prompt with a given template context.

Argument

Help

PROMPT

Prompt name

  • Required: True

[CONTEXT=VALUE]...

Context variables.

  • Required: True

  • Usage: Any number of named self.arguments, ie.: something=somevalue other=foo

prompt2 parser

prompt2 parser NAME

Function: prompt2.cli.parser()

Show a parser

Argument

Help

NAME

Parser name to display

  • Required: True

prompt2 parsers

prompt2 parsers

Function: prompt2.cli.parsers()

List registered parsers

prompt2 messages

prompt2 messages PROMPT [parser=PARSER] [model=MODEL] [CONTEXT=VALUE]...

Function: prompt2.cli.messages()

Render prompt messages with a given template context.

Argument

Help

PROMPT

  • Required: True

[parser=PARSER]

Parser name if any

  • Required: False

  • Default: None

[model=MODEL]

Model name to use, if any

  • Required: False

  • Default: None

[CONTEXT=VALUE]...

Context variables.

  • Required: True

  • Usage: Any number of named self.arguments, ie.: something=somevalue other=foo

prompt2 send

prompt2 send PROMPT [parser=PARSER] [model=MODEL] [CONTEXT=VALUE]...

Function: prompt2.cli.send()

Send a prompt, rendered with a context, on a model with a parser.

Argument

Help

PROMPT

Prompt name

  • Required: True

[parser=PARSER]

Parser name if any

  • Required: False

  • Default: None

[model=MODEL]

Model name to use, if any

  • Required: False

  • Default: None

[CONTEXT=VALUE]...

Context variables

  • Required: True

  • Usage: Any number of named self.arguments, ie.: something=somevalue other=foo

Why I Built prompt2 Instead of Sticking with aider-chat

Let’s face it: someone is going to wonder. When I first explored aider-chat, I saw its potential, but over time, I found it didn’t align with my workflow or goals. Here’s why I decided to forge my own path with prompt2:

  • Flexibility with APIs: aider-chat leaned heavily on litellm, which made it tricky to integrate custom APIs—especially in air-gapped networks. I searched GitHub for plugin support and found an issue where users requested it, only to be told it wasn’t a priority. Plugins are a simple, powerful feature in Python, and I wanted a tool that embraces them fully.

  • CLI Over Prompt Toolkits: I grew tired of prompt-toolkit. I’m a fan of bash and the classic CLI experience—why reinvent something that already works well?

  • Control Over Commits: I didn’t like how aider-chat committed changes behind my back. I prefer to stay in charge of my version control flow.

  • Customizable Style: The fixed style of aider-chat wasn’t my taste. With cli2.theme, I’ve made theming a core feature—because who doesn’t love a tool that looks the way they want? Heck, you can even import prompt2 and do your python-click CLI if you really dislike cli2.

  • Light Configuration: aider-chat relied on heavy configuration, which felt overkill. I love environment variables (hello, 12-factor apps!), but I also want the freedom to tweak settings from the CLI and only save them to a file when I choose to, in export VAR= format.

  • Owning My Tools: I’ve always preferred building my own CLI tools. Sure, they have quirks, but they’re mine. With cli2, extending and adapting them is a breeze, and I can make them as dynamic as I need.

  • Project Independence: I wanted a tool that wasn’t tied to a specific git project. prompt2 lets you work with code anywhere, whether it’s part of a repository or not.

In short, prompt2 is my take on a lightweight, flexible, and CLI-driven alternative. It’s built for people like me who value simplicity, control, and the ability to make a tool their own. If that resonates with you, give it a try!