Skip to content

Commit 12d2f9c

Browse files
authored
Re-add clai --help output to clai README (#3766)
1 parent ad7d03c commit 12d2f9c

File tree

6 files changed

+79
-14
lines changed

6 files changed

+79
-14
lines changed

.pre-commit-config.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ repos:
2727
args: ["--skip", "tests/models/cassettes/*"]
2828
additional_dependencies:
2929
- tomli
30-
3130
- repo: local
3231
hooks:
32+
- id: clai-help
33+
name: clai help output
34+
entry: uv
35+
args: [run, pytest, "clai/update_readme.py"]
36+
language: system
37+
types_or: [python, markdown]
38+
pass_filenames: false
3339
- id: format
3440
name: Format
3541
entry: make

clai/README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,34 @@ Either way, running `clai` will start an interactive session where you can chat
5151
- `/multiline`: Toggle multiline input mode (use Ctrl+D to submit)
5252
- `/cp`: Copy the last response to clipboard
5353

54-
For full CLI documentation, see the [CLI documentation](https://ai.pydantic.dev/cli/).
54+
## Help
55+
56+
```
57+
usage: clai [-h] [-l] [--version] [-m MODEL] [-a AGENT] [-t CODE_THEME] [--no-stream] [prompt]
58+
59+
Pydantic AI CLI v...
60+
61+
subcommands:
62+
web Start a web-based chat interface for an agent
63+
Run "clai web --help" for more information
64+
65+
positional arguments:
66+
prompt AI prompt for one-shot mode. If omitted, starts interactive mode.
67+
68+
options:
69+
-h, --help show this help message and exit
70+
-l, --list-models List all available models and exit
71+
--version Show version and exit
72+
-m MODEL, --model MODEL
73+
Model to use, in format "<provider>:<model>" e.g. "openai:gpt-5" or "anthropic:claude-sonnet-4-5". Defaults to "openai:gpt-5".
74+
-a AGENT, --agent AGENT
75+
Custom Agent to use, in format "module:variable", e.g. "mymodule.submodule:my_agent"
76+
-t CODE_THEME, --code-theme CODE_THEME
77+
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.
78+
--no-stream Disable streaming from the model
79+
```
80+
81+
For more information on how to use it, see the [CLI documentation](https://ai.pydantic.dev/cli/).
5582

5683
## Web Chat UI
5784

clai/update_readme.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
help_output = re.sub(r'(Pydantic AI CLI v).+', r'\1...', help_output)
20+
21+
this_dir = Path(__file__).parent
22+
readme = this_dir / 'README.md'
23+
content = readme.read_text(encoding='utf-8')
24+
25+
new_content, count = re.subn('^(## Help\n+```).+?```', rf'\1\n{help_output}\n```', content, flags=re.M | re.S)
26+
assert count, 'help section not found'
27+
if new_content != content:
28+
readme.write_text(new_content, encoding='utf-8')
29+
pytest.fail('`clai --help` output changed.')

pydantic_ai_slim/pydantic_ai/_cli/__init__.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ def cli_system_prompt() -> str:
122122
The user is running {sys.platform}."""
123123

124124

125-
def cli_exit(prog_name: str = 'pai'): # pragma: no cover
125+
def cli_exit(prog_name: str = 'clai'): # pragma: no cover
126126
"""Run the CLI and exit."""
127127
sys.exit(cli(prog_name=prog_name))
128128

129129

130-
def cli(args_list: Sequence[str] | None = None, *, prog_name: str = 'pai', default_model: str = 'openai:gpt-5') -> int:
130+
def cli(args_list: Sequence[str] | None = None, *, prog_name: str = 'clai', default_model: str = 'openai:gpt-5') -> int:
131131
"""Run the CLI and return the exit code for the process."""
132132
# we don't want to autocomplete or list models that don't include the provider,
133133
# e.g. we want to show `openai:gpt-4o` but not `gpt-4o`
@@ -180,7 +180,6 @@ def _cli_web(args_list: list[str], prog_name: str, default_model: str, qualified
180180
)
181181
parser.add_argument('--host', default='127.0.0.1', help='Host to bind server (default: 127.0.0.1)')
182182
parser.add_argument('--port', type=int, default=7932, help='Port to bind server (default: 7932)')
183-
184183
argcomplete.autocomplete(parser)
185184
args = parser.parse_args(args_list)
186185

@@ -201,7 +200,13 @@ def _cli_chat(args_list: list[str], prog_name: str, default_model: str, qualifie
201200
"""Handle the chat command (default)."""
202201
parser = argparse.ArgumentParser(
203202
prog=prog_name,
204-
description=f'Pydantic AI CLI v{__version__}',
203+
description=f"""\
204+
Pydantic AI CLI v{__version__}
205+
206+
subcommands:
207+
web Start a web-based chat interface for an agent
208+
Run "clai web --help" for more information
209+
""",
205210
formatter_class=argparse.RawTextHelpFormatter,
206211
)
207212

@@ -237,7 +242,6 @@ def _cli_chat(args_list: list[str], prog_name: str, default_model: str, qualifie
237242
default='dark',
238243
)
239244
parser.add_argument('--no-stream', action='store_true', help='Disable streaming from the model')
240-
241245
argcomplete.autocomplete(parser)
242246
args = parser.parse_args(args_list)
243247

tests/models/anthropic/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121
AnthropicModelFactory = Callable[..., AnthropicModel]
2222

2323

24-
# Model factory fixture for live API tests
2524
@pytest.fixture
2625
def anthropic_model(anthropic_api_key: str) -> AnthropicModelFactory:
27-
"""Factory to create Anthropic models with custom configuration."""
26+
"""Factory to create Anthropic models. Used by VCR-recorded integration tests."""
2827

2928
@cache
3029
def _create_model(

tests/test_cli.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
def test_cli_version(capfd: CaptureFixture[str]):
3333
assert cli(['--version']) == 0
34-
assert capfd.readouterr().out.startswith('pai - Pydantic AI CLI')
34+
assert capfd.readouterr().out.startswith('clai - Pydantic AI CLI')
3535

3636

3737
def test_invalid_model(capfd: CaptureFixture[str]):
@@ -141,7 +141,7 @@ def test_no_command_defaults_to_chat(mocker: MockerFixture):
141141
def test_list_models(capfd: CaptureFixture[str]):
142142
assert cli(['--list-models']) == 0
143143
output = capfd.readouterr().out.splitlines()
144-
assert output[:3] == snapshot([IsStr(regex='pai - Pydantic AI CLI .*'), '', 'Available models:'])
144+
assert output[:3] == snapshot([IsStr(regex='clai - Pydantic AI CLI .*'), '', 'Available models:'])
145145

146146
providers = (
147147
'openai',
@@ -274,21 +274,21 @@ def test_code_theme_unset(mocker: MockerFixture, env: TestEnv):
274274
env.set('OPENAI_API_KEY', 'test')
275275
mock_run_chat = mocker.patch('pydantic_ai._cli.run_chat')
276276
cli([])
277-
mock_run_chat.assert_awaited_once_with(True, IsInstance(Agent), IsInstance(Console), 'monokai', 'pai')
277+
mock_run_chat.assert_awaited_once_with(True, IsInstance(Agent), IsInstance(Console), 'monokai', 'clai')
278278

279279

280280
def test_code_theme_light(mocker: MockerFixture, env: TestEnv):
281281
env.set('OPENAI_API_KEY', 'test')
282282
mock_run_chat = mocker.patch('pydantic_ai._cli.run_chat')
283283
cli(['--code-theme=light'])
284-
mock_run_chat.assert_awaited_once_with(True, IsInstance(Agent), IsInstance(Console), 'default', 'pai')
284+
mock_run_chat.assert_awaited_once_with(True, IsInstance(Agent), IsInstance(Console), 'default', 'clai')
285285

286286

287287
def test_code_theme_dark(mocker: MockerFixture, env: TestEnv):
288288
env.set('OPENAI_API_KEY', 'test')
289289
mock_run_chat = mocker.patch('pydantic_ai._cli.run_chat')
290290
cli(['--code-theme=dark'])
291-
mock_run_chat.assert_awaited_once_with(True, IsInstance(Agent), IsInstance(Console), 'monokai', 'pai')
291+
mock_run_chat.assert_awaited_once_with(True, IsInstance(Agent), IsInstance(Console), 'monokai', 'clai')
292292

293293

294294
def test_agent_to_cli_sync(mocker: MockerFixture, env: TestEnv):

0 commit comments

Comments
 (0)