Skip to content

Commit 240b012

Browse files
authored
Switch CLI to clai (#1504)
1 parent 85a9cbc commit 240b012

File tree

14 files changed

+280
-95
lines changed

14 files changed

+280
-95
lines changed

.pre-commit-config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ repos:
4040
types_or: [javascript, ts, json]
4141
files: '^mcp-run-python/'
4242
pass_filenames: false
43+
- id: clai-help
44+
name: clai help output
45+
entry: uv
46+
args: [run, pytest, 'clai/update_readme.py']
47+
language: system
48+
types_or: [python, markdown]
49+
pass_filenames: false
4350
- id: typecheck
4451
name: Typecheck
4552
entry: make

clai/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# clai
2+
3+
[![CI](https://github.com/pydantic/pydantic-ai/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/pydantic/pydantic-ai/actions/workflows/ci.yml?query=branch%3Amain)
4+
[![Coverage](https://coverage-badge.samuelcolvin.workers.dev/pydantic/pydantic-ai.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/pydantic/pydantic-ai)
5+
[![PyPI](https://img.shields.io/pypi/v/clai.svg)](https://pypi.python.org/pypi/clai)
6+
[![versions](https://img.shields.io/pypi/pyversions/clai.svg)](https://github.com/pydantic/pydantic-ai)
7+
[![license](https://img.shields.io/github/license/pydantic/pydantic-ai.svg?v)](https://github.com/pydantic/pydantic-ai/blob/main/LICENSE)
8+
9+
(pronounced "clay")
10+
11+
Command line interface to chat to LLMs, part of the [PydanticAI project](https://github.com/pydantic/pydantic-ai).
12+
13+
## Usage
14+
15+
<!-- Keep this in sync with docs/cli.md -->
16+
17+
You'll need to set an environment variable depending on the provider you intend to use.
18+
19+
E.g. if you're using OpenAI, set the `OPENAI_API_KEY` environment variable:
20+
21+
```bash
22+
export OPENAI_API_KEY='your-api-key-here'
23+
```
24+
25+
Then with [`uvx`](https://docs.astral.sh/uv/guides/tools/), run:
26+
27+
```bash
28+
uvx clai
29+
```
30+
31+
Or to install `clai` globally [with `uv`](https://docs.astral.sh/uv/guides/tools/#installing-tools), run:
32+
33+
```bash
34+
uv tool install clai
35+
...
36+
clai
37+
```
38+
39+
Or with `pip`, run:
40+
41+
```bash
42+
pip install clai
43+
...
44+
clai
45+
```
46+
47+
Either way, running `clai` will start an interactive session where you can chat with the AI model. Special commands available in interactive mode:
48+
49+
- `/exit`: Exit the session
50+
- `/markdown`: Show the last response in markdown format
51+
- `/multiline`: Toggle multiline input mode (use Ctrl+D to submit)
52+
53+
## Help
54+
55+
```
56+
usage: clai [-h] [-m [MODEL]] [-l] [-t [CODE_THEME]] [--no-stream] [--version] [prompt]
57+
58+
PydanticAI CLI v...
59+
60+
Special prompts:
61+
* `/exit` - exit the interactive mode (ctrl-c and ctrl-d also work)
62+
* `/markdown` - show the last markdown output of the last question
63+
* `/multiline` - toggle multiline mode
64+
65+
positional arguments:
66+
prompt AI Prompt, if omitted fall into interactive mode
67+
68+
options:
69+
-h, --help show this help message and exit
70+
-m [MODEL], --model [MODEL]
71+
Model to use, in format "<provider>:<model>" e.g. "openai:gpt-4o" or "anthropic:claude-3-7-sonnet-latest". Defaults to "openai:gpt-4o".
72+
-l, --list-models List all available models and exit
73+
-t [CODE_THEME], --code-theme [CODE_THEME]
74+
Which colors to use for code, can be "dark", "light" or any theme from pygments.org/styles/. Defaults to "dark" which works well on dark terminals.
75+
--no-stream Disable streaming from the model
76+
--version Show version and exit
77+
```

clai/clai/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from importlib.metadata import version as _metadata_version
2+
3+
from pydantic_ai import _cli
4+
5+
__all__ = '__version__', 'cli'
6+
__version__ = _metadata_version('clai')
7+
8+
9+
def cli():
10+
"""Run the clai CLI and exit."""
11+
_cli.cli_exit('clai')

clai/clai/__main__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""This means `python -m clai` should run the CLI."""
2+
3+
from pydantic_ai import _cli
4+
5+
if __name__ == '__main__':
6+
_cli.cli_exit('clai')

clai/pyproject.toml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
[build-system]
2+
requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
3+
build-backend = "hatchling.build"
4+
5+
[tool.hatch.version]
6+
source = "uv-dynamic-versioning"
7+
8+
[tool.uv-dynamic-versioning]
9+
vcs = "git"
10+
style = "pep440"
11+
bump = true
12+
13+
[project]
14+
name = "clai"
15+
dynamic = ["version", "dependencies"]
16+
description = "PydanticAI CLI: command line interface to chat to LLMs"
17+
authors = [
18+
{ name = "Samuel Colvin", email = "samuel@pydantic.dev" },
19+
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" },
20+
{ name = "David Montague", email = "david@pydantic.dev" },
21+
{ name = "Alex Hall", email = "alex@pydantic.dev" },
22+
]
23+
license = "MIT"
24+
readme = "README.md"
25+
classifiers = [
26+
"Development Status :: 4 - Beta",
27+
"Programming Language :: Python",
28+
"Programming Language :: Python :: 3",
29+
"Programming Language :: Python :: 3 :: Only",
30+
"Programming Language :: Python :: 3.9",
31+
"Programming Language :: Python :: 3.10",
32+
"Programming Language :: Python :: 3.11",
33+
"Programming Language :: Python :: 3.12",
34+
"Programming Language :: Python :: 3.13",
35+
"Intended Audience :: Developers",
36+
"Intended Audience :: Information Technology",
37+
"Intended Audience :: System Administrators",
38+
"License :: OSI Approved :: MIT License",
39+
"Operating System :: Unix",
40+
"Operating System :: POSIX :: Linux",
41+
"Environment :: Console",
42+
"Environment :: MacOS X",
43+
"Topic :: Software Development :: Libraries :: Python Modules",
44+
"Topic :: Internet",
45+
]
46+
requires-python = ">=3.9"
47+
48+
[tool.hatch.metadata.hooks.uv-dynamic-versioning]
49+
dependencies = [
50+
"pydantic-ai=={{ version }}",
51+
]
52+
53+
[tool.hatch.metadata]
54+
allow-direct-references = true
55+
56+
[project.scripts]
57+
clai = "clai:cli"
58+
59+
[tool.hatch.build.targets.wheel]
60+
packages = ["clai"]
61+
62+
[tool.uv.sources]
63+
pydantic-ai = { workspace = true }

clai/update_readme.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import os
2+
import re
3+
import sys
4+
from pathlib import Path
5+
6+
import pytest
7+
8+
from pydantic_ai._cli import cli
9+
10+
11+
@pytest.mark.skipif(sys.version_info >= (3, 13), reason='slightly different output with 3.13')
12+
def test_cli_help(capfd: pytest.CaptureFixture[str]):
13+
"""Check README.md help output matches `clai --help`."""
14+
os.environ['COLUMNS'] = '150'
15+
with pytest.raises(SystemExit):
16+
cli(['--help'], prog_name='clai')
17+
18+
help_output = capfd.readouterr().out.strip()
19+
# TODO change when we reach v1
20+
help_output = re.sub(r'(PydanticAI CLI v).+', r'\1...', help_output)
21+
22+
this_dir = Path(__file__).parent
23+
readme = this_dir / 'README.md'
24+
content = readme.read_text()
25+
26+
new_content, count = re.subn('^(## Help\n+```).+?```', rf'\1\n{help_output}\n```', content, flags=re.M | re.S)
27+
assert count, 'help section not found'
28+
if new_content != content:
29+
readme.write_text(new_content)
30+
pytest.fail('`clai --help` output changed.')

docs/cli.md

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,66 @@
11
# Command Line Interface (CLI)
22

3-
**PydanticAI** comes with a simple reference CLI application which you can use to interact with various LLMs directly from the command line.
3+
**PydanticAI** comes with a CLI, `clai` (pronounced "clay") which you can use to interact with various LLMs from the command line.
44
It provides a convenient way to chat with language models and quickly get answers right in the terminal.
55

66
We originally developed this CLI for our own use, but found ourselves using it so frequently that we decided to share it as part of the PydanticAI package.
77

88
We plan to continue adding new features, such as interaction with MCP servers, access to tools, and more.
99

10-
## Installation
10+
## Usage
1111

12-
To use the CLI, you need to either install [`pydantic-ai`](install.md), or install
13-
[`pydantic-ai-slim`](install.md#slim-install) with the `cli` optional group:
12+
<!-- Keep this in sync with clai/README.md -->
1413

15-
```bash
16-
pip/uv-add "pydantic-ai[cli]"
17-
```
14+
You'll need to set an environment variable depending on the provider you intend to use.
1815

19-
To enable command-line argument autocompletion, run:
16+
E.g. if you're using OpenAI, set the `OPENAI_API_KEY` environment variable:
2017

2118
```bash
22-
register-python-argcomplete pai >> ~/.bashrc # for bash
23-
register-python-argcomplete pai >> ~/.zshrc # for zsh
19+
export OPENAI_API_KEY='your-api-key-here'
2420
```
2521

26-
## Usage
22+
Then with [`uvx`](https://docs.astral.sh/uv/guides/tools/), run:
2723

28-
You'll need to set an environment variable depending on the provider you intend to use.
24+
```bash
25+
uvx clai
26+
```
2927

30-
If using OpenAI, set the `OPENAI_API_KEY` environment variable:
28+
Or to install `clai` globally [with `uv`](https://docs.astral.sh/uv/guides/tools/#installing-tools), run:
3129

3230
```bash
33-
export OPENAI_API_KEY='your-api-key-here'
31+
uv tool install clai
32+
...
33+
clai
3434
```
3535

36-
Then simply run:
36+
Or with `pip`, run:
3737

3838
```bash
39-
pai
39+
pip install clai
40+
...
41+
clai
4042
```
4143

42-
This will start an interactive session where you can chat with the AI model. Special commands available in interactive mode:
44+
Either way, running `clai` will start an interactive session where you can chat with the AI model. Special commands available in interactive mode:
4345

4446
- `/exit`: Exit the session
4547
- `/markdown`: Show the last response in markdown format
4648
- `/multiline`: Toggle multiline input mode (use Ctrl+D to submit)
4749

48-
### Choose a model
50+
### Help
4951

50-
You can specify which model to use with the `--model` flag:
52+
To get help on the CLI, use the `--help` flag:
5153

5254
```bash
53-
$ pai --model=openai:gpt-4 "What's the capital of France?"
55+
uvx clai --help
5456
```
5557

56-
### Usage with `uvx`
58+
### Choose a model
5759

58-
If you have [uv](https://docs.astral.sh/uv/) installed, the quickest way to run the CLI is with `uvx`:
60+
You can specify which model to use with the `--model` flag:
5961

6062
```bash
61-
uvx --from pydantic-ai pai
63+
uvx clai --model anthropic:claude-3-7-sonnet-latest
6264
```
65+
66+
(a full list of models available can be printed with `uvx clai --list-models`)

pydantic_ai_slim/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
[![CI](https://github.com/pydantic/pydantic-ai/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/pydantic/pydantic-ai/actions/workflows/ci.yml?query=branch%3Amain)
44
[![Coverage](https://coverage-badge.samuelcolvin.workers.dev/pydantic/pydantic-ai.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/pydantic/pydantic-ai)
5-
[![PyPI](https://img.shields.io/pypi/v/pydantic-ai.svg)](https://pypi.python.org/pypi/pydantic-ai)
6-
[![versions](https://img.shields.io/pypi/pyversions/pydantic-ai.svg)](https://github.com/pydantic/pydantic-ai)
5+
[![PyPI](https://img.shields.io/pypi/v/pydantic-ai-slim.svg)](https://pypi.python.org/pypi/pydantic-ai-slim)
6+
[![versions](https://img.shields.io/pypi/pyversions/pydantic-ai-slim.svg)](https://github.com/pydantic/pydantic-ai)
77
[![license](https://img.shields.io/github/license/pydantic/pydantic-ai.svg?v)](https://github.com/pydantic/pydantic-ai/blob/main/LICENSE)
88

99
PydanticAI core logic with minimal required dependencies.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""This means `python -m pydantic_ai` should run the CLI."""
22

3-
from ._cli import app
3+
from ._cli import cli_exit
44

55
if __name__ == '__main__':
6-
app()
6+
cli_exit()

0 commit comments

Comments
 (0)