talk-python-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- talk_python_cli-0.1.0/.claude/settings.local.json +7 -0
- talk_python_cli-0.1.0/.gitignore +205 -0
- talk_python_cli-0.1.0/LICENSE +21 -0
- talk_python_cli-0.1.0/PKG-INFO +25 -0
- talk_python_cli-0.1.0/README.md +132 -0
- talk_python_cli-0.1.0/plans/001-talk-python-cli.plan.md +197 -0
- talk_python_cli-0.1.0/pyproject.toml +48 -0
- talk_python_cli-0.1.0/pyrefly.toml +45 -0
- talk_python_cli-0.1.0/ruff.toml +43 -0
- talk_python_cli-0.1.0/src/talk_python_cli/__init__.py +3 -0
- talk_python_cli-0.1.0/src/talk_python_cli/__main__.py +5 -0
- talk_python_cli-0.1.0/src/talk_python_cli/app.py +91 -0
- talk_python_cli-0.1.0/src/talk_python_cli/client.py +144 -0
- talk_python_cli-0.1.0/src/talk_python_cli/courses.py +59 -0
- talk_python_cli-0.1.0/src/talk_python_cli/episodes.py +99 -0
- talk_python_cli-0.1.0/src/talk_python_cli/formatting.py +94 -0
- talk_python_cli-0.1.0/src/talk_python_cli/guests.py +56 -0
- talk_python_cli-0.1.0/tests/__init__.py +0 -0
- talk_python_cli-0.1.0/tests/conftest.py +144 -0
- talk_python_cli-0.1.0/tests/test_client.py +214 -0
- talk_python_cli-0.1.0/tests/test_courses.py +120 -0
- talk_python_cli-0.1.0/tests/test_episodes.py +142 -0
- talk_python_cli-0.1.0/tests/test_guests.py +95 -0
- talk_python_cli-0.1.0/uv.lock +272 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# uv.lock is committed to version control for reproducible installs.
|
|
99
|
+
# uv.lock
|
|
100
|
+
|
|
101
|
+
# poetry
|
|
102
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
103
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
104
|
+
# commonly ignored for libraries.
|
|
105
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
106
|
+
#poetry.lock
|
|
107
|
+
#poetry.toml
|
|
108
|
+
|
|
109
|
+
# pdm
|
|
110
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
111
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
112
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
113
|
+
#pdm.lock
|
|
114
|
+
#pdm.toml
|
|
115
|
+
.pdm-python
|
|
116
|
+
.pdm-build/
|
|
117
|
+
|
|
118
|
+
# pixi
|
|
119
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
120
|
+
#pixi.lock
|
|
121
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
122
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
123
|
+
.pixi
|
|
124
|
+
|
|
125
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
126
|
+
__pypackages__/
|
|
127
|
+
|
|
128
|
+
# Celery stuff
|
|
129
|
+
celerybeat-schedule
|
|
130
|
+
celerybeat.pid
|
|
131
|
+
|
|
132
|
+
# SageMath parsed files
|
|
133
|
+
*.sage.py
|
|
134
|
+
|
|
135
|
+
# Environments
|
|
136
|
+
.env
|
|
137
|
+
.envrc
|
|
138
|
+
.venv
|
|
139
|
+
env/
|
|
140
|
+
venv/
|
|
141
|
+
ENV/
|
|
142
|
+
env.bak/
|
|
143
|
+
venv.bak/
|
|
144
|
+
|
|
145
|
+
# Spyder project settings
|
|
146
|
+
.spyderproject
|
|
147
|
+
.spyproject
|
|
148
|
+
|
|
149
|
+
# Rope project settings
|
|
150
|
+
.ropeproject
|
|
151
|
+
|
|
152
|
+
# mkdocs documentation
|
|
153
|
+
/site
|
|
154
|
+
|
|
155
|
+
# mypy
|
|
156
|
+
.mypy_cache/
|
|
157
|
+
.dmypy.json
|
|
158
|
+
dmypy.json
|
|
159
|
+
|
|
160
|
+
# Pyre type checker
|
|
161
|
+
.pyre/
|
|
162
|
+
|
|
163
|
+
# pytype static type analyzer
|
|
164
|
+
.pytype/
|
|
165
|
+
|
|
166
|
+
# Cython debug symbols
|
|
167
|
+
cython_debug/
|
|
168
|
+
|
|
169
|
+
# PyCharm
|
|
170
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
171
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
172
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
173
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
174
|
+
#.idea/
|
|
175
|
+
|
|
176
|
+
# Abstra
|
|
177
|
+
# Abstra is an AI-powered process automation framework.
|
|
178
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
179
|
+
# Learn more at https://abstra.io/docs
|
|
180
|
+
.abstra/
|
|
181
|
+
|
|
182
|
+
# Visual Studio Code
|
|
183
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
184
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
185
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
186
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
187
|
+
# .vscode/
|
|
188
|
+
|
|
189
|
+
# Ruff stuff:
|
|
190
|
+
.ruff_cache/
|
|
191
|
+
|
|
192
|
+
# PyPI configuration file
|
|
193
|
+
.pypirc
|
|
194
|
+
|
|
195
|
+
# Cursor
|
|
196
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
197
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
198
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
199
|
+
.cursorignore
|
|
200
|
+
.cursorindexingignore
|
|
201
|
+
|
|
202
|
+
# Marimo
|
|
203
|
+
marimo/_static/
|
|
204
|
+
marimo/_lsp/
|
|
205
|
+
__marimo__/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Talk Python
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: talk-python-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI for the Talk Python to Me podcast and courses
|
|
5
|
+
Project-URL: Homepage, https://github.com/talkpython/talk-python-cli
|
|
6
|
+
Project-URL: Source, https://github.com/talkpython/talk-python-cli
|
|
7
|
+
Project-URL: Documentation, https://github.com/talkpython/talk-python-cli
|
|
8
|
+
Author-email: Michael Kennedy <michael@talkpython.fm>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
20
|
+
Classifier: Topic :: Education
|
|
21
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Requires-Dist: cyclopts>=3.0
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: rich>=13.0
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Talk Python CLI
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/talk-python-cli/)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Unlock 500+ episodes of [Talk Python to Me](https://talkpython.fm), full transcripts, guest profiles, and 50+ [Talk Python Training](https://training.talkpython.fm) courses — all from your terminal. Search, browse, and pipe structured data into your scripts, AI agents, or automation workflows.
|
|
8
|
+
|
|
9
|
+
## Why use this?
|
|
10
|
+
|
|
11
|
+
- **Automation** — Query episode data, guest info, and course catalogs from scripts and pipelines.
|
|
12
|
+
- **LLM & AI integration** — Pipe JSON or Markdown output directly into AI agents, RAG systems, or chat workflows. Feed transcripts into RAG pipelines, build podcast assistants, or enrich your AI tools with real Python community knowledge.
|
|
13
|
+
- **Quick lookups** — Search episodes, pull transcripts, and browse courses without leaving the terminal.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Requires Python 3.12+.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Try it instantly with uvx (no install needed)
|
|
21
|
+
uvx --from talk-python-cli talkpython episodes recent
|
|
22
|
+
|
|
23
|
+
# Or install it permanently with uv
|
|
24
|
+
uv tool install talk-python-cli
|
|
25
|
+
|
|
26
|
+
# Or with pip
|
|
27
|
+
pip install talk-python-cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This installs the `talkpython` command.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Search for episodes about FastAPI
|
|
36
|
+
talkpython episodes search "FastAPI"
|
|
37
|
+
|
|
38
|
+
# Get full details for a specific episode
|
|
39
|
+
talkpython episodes get 535
|
|
40
|
+
|
|
41
|
+
# Pull the transcript for an episode
|
|
42
|
+
talkpython episodes transcript 535
|
|
43
|
+
|
|
44
|
+
# List recent episodes
|
|
45
|
+
talkpython episodes recent --limit 5
|
|
46
|
+
|
|
47
|
+
# Search for a guest
|
|
48
|
+
talkpython guests search "Hynek"
|
|
49
|
+
|
|
50
|
+
# Browse all training courses
|
|
51
|
+
talkpython courses list
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Commands
|
|
55
|
+
|
|
56
|
+
### Episodes
|
|
57
|
+
|
|
58
|
+
| Command | Description |
|
|
59
|
+
|---------|-------------|
|
|
60
|
+
| `talkpython episodes search <query> [--limit N]` | Search episodes by keyword (default limit: 10) |
|
|
61
|
+
| `talkpython episodes get <show_id>` | Get full details for an episode |
|
|
62
|
+
| `talkpython episodes list` | List all episodes |
|
|
63
|
+
| `talkpython episodes recent [--limit N]` | Get the most recent episodes |
|
|
64
|
+
| `talkpython episodes transcript <show_id>` | Get the plain-text transcript |
|
|
65
|
+
| `talkpython episodes transcript-vtt <show_id>` | Get the WebVTT transcript (with timestamps) |
|
|
66
|
+
|
|
67
|
+
### Guests
|
|
68
|
+
|
|
69
|
+
| Command | Description |
|
|
70
|
+
|---------|-------------|
|
|
71
|
+
| `talkpython guests search <query> [--limit N]` | Search guests by name |
|
|
72
|
+
| `talkpython guests get <guest_id>` | Get details for a specific guest |
|
|
73
|
+
| `talkpython guests list` | List all guests, sorted by number of appearances |
|
|
74
|
+
|
|
75
|
+
### Courses
|
|
76
|
+
|
|
77
|
+
| Command | Description |
|
|
78
|
+
|---------|-------------|
|
|
79
|
+
| `talkpython courses search <query> [--course_id N]` | Search courses, chapters, and lectures |
|
|
80
|
+
| `talkpython courses get <course_id>` | Get full course details including chapters and lectures |
|
|
81
|
+
| `talkpython courses list` | List all available training courses |
|
|
82
|
+
|
|
83
|
+
## Output formats
|
|
84
|
+
|
|
85
|
+
The CLI auto-detects the best output format:
|
|
86
|
+
|
|
87
|
+
- **Interactive terminal** — Rich-formatted Markdown with styled panels and color.
|
|
88
|
+
- **Piped / redirected** — Compact JSON, ready for processing.
|
|
89
|
+
|
|
90
|
+
Override the default with `--format`:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Force JSON output in the terminal
|
|
94
|
+
talkpython --format json episodes search "async"
|
|
95
|
+
|
|
96
|
+
# Force rich text output even when piping
|
|
97
|
+
talkpython --format text episodes recent | less -R
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Piping JSON to other tools
|
|
101
|
+
|
|
102
|
+
Because the CLI outputs JSON automatically when piped, it integrates naturally with tools like `jq`, `llm`, or your own scripts:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Extract episode titles with jq
|
|
106
|
+
talkpython episodes search "testing" | jq '.title'
|
|
107
|
+
|
|
108
|
+
# Feed episode data into an LLM
|
|
109
|
+
talkpython episodes get 535 | llm "Summarize this podcast episode"
|
|
110
|
+
|
|
111
|
+
# Grab a transcript for RAG ingestion
|
|
112
|
+
talkpython episodes transcript 535 | your-rag-pipeline ingest
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Global options
|
|
116
|
+
|
|
117
|
+
| Option | Description |
|
|
118
|
+
|--------|-------------|
|
|
119
|
+
| `--format text\|json` | Force output format (auto-detected by default) |
|
|
120
|
+
| `--url <mcp-url>` | Override the MCP server URL (default: `https://talkpython.fm/api/mcp`) |
|
|
121
|
+
| `--version`, `-V` | Show version |
|
|
122
|
+
|
|
123
|
+
## Part of the Talk Python ecosystem
|
|
124
|
+
|
|
125
|
+
Talk Python CLI is one way to tap into the data behind the [Talk Python to Me](https://talkpython.fm) podcast and [Talk Python Training](https://training.talkpython.fm) courses. It connects to the same public [MCP server](https://talkpython.fm/api/mcp) that powers Talk Python's AI integrations — so whether you're building an agent, a search tool, or just want quick answers from the terminal, you're working with the real data.
|
|
126
|
+
|
|
127
|
+
- [Talk Python to Me Podcast](https://talkpython.fm)
|
|
128
|
+
- [Talk Python Training](https://training.talkpython.fm)
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Plan 001: Talk Python CLI — Standalone Package
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
The Talk Python MCP server at `https://talkpython.fm/api/mcp` exposes 12 tools for querying
|
|
6
|
+
podcast episodes, guests, transcripts, and courses via JSON-RPC 2.0. This project is a
|
|
7
|
+
**standalone, open-source CLI tool** (`talkpython`) that wraps those MCP tools as terminal
|
|
8
|
+
commands.
|
|
9
|
+
|
|
10
|
+
Full server documentation (tool names, parameters, descriptions):
|
|
11
|
+
**https://talkpython.fm/api/mcp/docs**
|
|
12
|
+
|
|
13
|
+
The package will be published to PyPI as `talk-python-cli` with the command name `talkpython`.
|
|
14
|
+
|
|
15
|
+
### MCP server tools (reference)
|
|
16
|
+
|
|
17
|
+
The server (v1.3.0) is public, read-only, no authentication required. Transport: Streamable HTTP.
|
|
18
|
+
|
|
19
|
+
| CLI command | MCP tool name | Parameters |
|
|
20
|
+
|----------------------------------|----------------------------|-----------------------------------------|
|
|
21
|
+
| `episodes search` | `search_episodes` | `query` (str), `limit` (int, optional) |
|
|
22
|
+
| `episodes get` | `get_episode` | `show_id` (int) |
|
|
23
|
+
| `episodes list` | `get_episodes` | *(none)* |
|
|
24
|
+
| `episodes recent` | `get_recent_episodes` | `limit` (int, optional) |
|
|
25
|
+
| `episodes transcript` | `get_transcript_for_episode` | `show_id` (int) |
|
|
26
|
+
| `episodes transcript-vtt` | `get_transcript_vtt` | `show_id` (int) |
|
|
27
|
+
| `guests search` | `search_guests` | `query` (str), `limit` (int, optional) |
|
|
28
|
+
| `guests get` | `get_guest_by_id` | `guest_id` (int) |
|
|
29
|
+
| `guests list` | `get_guests` | *(none)* |
|
|
30
|
+
| `courses search` | `search_courses` | `query` (str), `course_id` (int, opt.) |
|
|
31
|
+
| `courses get` | `get_course_details` | `course_id` (int) |
|
|
32
|
+
| `courses list` | `get_courses` | *(none)* |
|
|
33
|
+
|
|
34
|
+
### Server-side prerequisite
|
|
35
|
+
|
|
36
|
+
The MCP server needs `?format=json` query parameter support so the CLI can receive structured
|
|
37
|
+
data instead of pre-formatted Markdown. That change lives in the main Talk Python web app repo
|
|
38
|
+
(not this one) and must be deployed before the CLI's `--format json` and auto-JSON-on-pipe
|
|
39
|
+
features work. The CLI should:
|
|
40
|
+
|
|
41
|
+
- Default to requesting `format=text` (works with the server today)
|
|
42
|
+
- Support `--format json` for when the server-side change is deployed
|
|
43
|
+
- Degrade gracefully if the server ignores the format parameter
|
|
44
|
+
|
|
45
|
+
## Project structure
|
|
46
|
+
|
|
47
|
+
Package lives at the repo root (not nested in a subdirectory):
|
|
48
|
+
|
|
49
|
+
Managed with **uv** — no manual venv creation, use `uv run`, `uv sync`, `uv lock`, etc.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
talk-python-cli/
|
|
53
|
+
├── pyproject.toml
|
|
54
|
+
├── uv.lock # Committed to version control
|
|
55
|
+
├── LICENSE
|
|
56
|
+
├── README.md
|
|
57
|
+
├── .gitignore
|
|
58
|
+
├── src/
|
|
59
|
+
│ └── talk_python_cli/
|
|
60
|
+
│ ├── __init__.py # Version string
|
|
61
|
+
│ ├── __main__.py # python -m talk_python_cli support
|
|
62
|
+
│ ├── app.py # Root Cyclopts app + global options
|
|
63
|
+
│ ├── client.py # MCP HTTP client (httpx JSON-RPC wrapper)
|
|
64
|
+
│ ├── formatting.py # Output formatting (rich Markdown or JSON)
|
|
65
|
+
│ ├── episodes.py # Episode commands sub-app
|
|
66
|
+
│ ├── guests.py # Guest commands sub-app
|
|
67
|
+
│ └── courses.py # Course commands sub-app
|
|
68
|
+
└── tests/
|
|
69
|
+
├── __init__.py
|
|
70
|
+
├── conftest.py # Shared fixtures (mock MCP responses)
|
|
71
|
+
├── test_client.py # MCPClient unit tests
|
|
72
|
+
├── test_episodes.py # Episode command tests
|
|
73
|
+
├── test_guests.py # Guest command tests
|
|
74
|
+
└── test_courses.py # Course command tests
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Dependencies (pyproject.toml)
|
|
78
|
+
|
|
79
|
+
Created via `uv init`, then `uv add cyclopts httpx rich` and `uv add --dev pytest pytest-httpx`.
|
|
80
|
+
|
|
81
|
+
```toml
|
|
82
|
+
[project]
|
|
83
|
+
name = "talk-python-cli"
|
|
84
|
+
version = "0.1.0"
|
|
85
|
+
description = "CLI for the Talk Python to Me podcast and courses"
|
|
86
|
+
requires-python = ">=3.12"
|
|
87
|
+
license = "MIT"
|
|
88
|
+
authors = [
|
|
89
|
+
{ name = "Michael Kennedy", email = "michael@talkpython.fm" },
|
|
90
|
+
]
|
|
91
|
+
dependencies = [
|
|
92
|
+
"cyclopts>=3.0",
|
|
93
|
+
"httpx>=0.27",
|
|
94
|
+
"rich>=13.0",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
[project.scripts]
|
|
98
|
+
talkpython = "talk_python_cli.app:main"
|
|
99
|
+
|
|
100
|
+
[dependency-groups]
|
|
101
|
+
dev = [
|
|
102
|
+
"pytest>=8.0",
|
|
103
|
+
"pytest-httpx>=0.34",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[build-system]
|
|
107
|
+
requires = ["hatchling"]
|
|
108
|
+
build-backend = "hatchling.build"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `uv.lock` file is committed for reproducible installs.
|
|
112
|
+
|
|
113
|
+
## CLI commands
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
talkpython [--format text|json] [--url URL]
|
|
117
|
+
|
|
118
|
+
talkpython episodes search QUERY [--limit N]
|
|
119
|
+
talkpython episodes get SHOW_ID
|
|
120
|
+
talkpython episodes list
|
|
121
|
+
talkpython episodes recent [--limit N]
|
|
122
|
+
talkpython episodes transcript SHOW_ID
|
|
123
|
+
talkpython episodes transcript-vtt SHOW_ID
|
|
124
|
+
|
|
125
|
+
talkpython guests search QUERY [--limit N]
|
|
126
|
+
talkpython guests get GUEST_ID
|
|
127
|
+
talkpython guests list
|
|
128
|
+
|
|
129
|
+
talkpython courses search QUERY [--course-id ID]
|
|
130
|
+
talkpython courses get COURSE_ID
|
|
131
|
+
talkpython courses list
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Global option `--format` defaults to `text` (rendered Markdown) but can be set to `json`
|
|
135
|
+
for machine-readable output. `--url` defaults to `https://talkpython.fm/api/mcp`.
|
|
136
|
+
|
|
137
|
+
### Auto-detection for piped output
|
|
138
|
+
|
|
139
|
+
When stdout is not a TTY (piped to another command), default to JSON format
|
|
140
|
+
for scripting convenience: `talkpython episodes recent | jq '.[]'`
|
|
141
|
+
|
|
142
|
+
## Key module designs
|
|
143
|
+
|
|
144
|
+
**`client.py`** — Thin wrapper around httpx making JSON-RPC calls:
|
|
145
|
+
```python
|
|
146
|
+
class MCPClient:
|
|
147
|
+
def __init__(self, base_url: str, output_format: str = 'text'):
|
|
148
|
+
self.base_url = base_url
|
|
149
|
+
self.output_format = output_format
|
|
150
|
+
self._msg_id = 0
|
|
151
|
+
|
|
152
|
+
def call_tool(self, tool_name: str, arguments: dict) -> dict:
|
|
153
|
+
# POST to base_url?format={output_format}
|
|
154
|
+
# JSON-RPC 2.0 envelope: {"jsonrpc":"2.0","id":N,"method":"tools/call","params":{...}}
|
|
155
|
+
# Returns the result content text
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**`formatting.py`** — Handles display:
|
|
159
|
+
- `text` format: render Markdown from server using rich
|
|
160
|
+
- `json` format: print with optional pretty-printing
|
|
161
|
+
|
|
162
|
+
**`app.py`** — Root app with global params:
|
|
163
|
+
```python
|
|
164
|
+
app = cyclopts.App(name='talkpython', help='Talk Python to Me CLI')
|
|
165
|
+
# Register sub-apps
|
|
166
|
+
app.command(episodes_app)
|
|
167
|
+
app.command(guests_app)
|
|
168
|
+
app.command(courses_app)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**`episodes.py`**, **`guests.py`**, **`courses.py`** — Each defines a sub-app:
|
|
172
|
+
```python
|
|
173
|
+
episodes_app = cyclopts.App(name='episodes', help='Podcast episode commands')
|
|
174
|
+
|
|
175
|
+
@episodes_app.command
|
|
176
|
+
def search(query: str, *, limit: int = 10):
|
|
177
|
+
...
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Implementation order
|
|
181
|
+
|
|
182
|
+
1. `uv init` — create `pyproject.toml`, then add dependencies with `uv add`
|
|
183
|
+
2. Implement `client.py` (HTTP JSON-RPC client)
|
|
184
|
+
3. Implement `formatting.py` (output rendering)
|
|
185
|
+
4. Implement `app.py` + command modules (`episodes.py`, `guests.py`, `courses.py`)
|
|
186
|
+
5. Add `__main__.py` for `python -m` support
|
|
187
|
+
6. Add tests with mocked HTTP responses (`uv add --dev pytest pytest-httpx`)
|
|
188
|
+
7. Verify against live server
|
|
189
|
+
|
|
190
|
+
## Verification
|
|
191
|
+
|
|
192
|
+
1. **Sync**: `uv sync` (installs all deps including dev group)
|
|
193
|
+
2. **Unit tests**: `uv run pytest tests/ -v`
|
|
194
|
+
3. **Smoke test**: `uv run talkpython episodes recent --limit 3`
|
|
195
|
+
4. **JSON output**: `uv run talkpython --format json episodes recent --limit 3`
|
|
196
|
+
5. **Piped output**: `uv run talkpython episodes recent | head` — should auto-detect JSON format
|
|
197
|
+
6. **Module entry**: `uv run python -m talk_python_cli episodes recent --limit 3`
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "talk-python-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CLI for the Talk Python to Me podcast and courses"
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Michael Kennedy", email = "michael@talkpython.fm" },
|
|
9
|
+
]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Programming Language :: Python :: 3.15",
|
|
20
|
+
"Topic :: Multimedia :: Sound/Audio",
|
|
21
|
+
"Topic :: Education",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"cyclopts>=3.0",
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"rich>=13.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/talkpython/talk-python-cli"
|
|
31
|
+
Source = "https://github.com/talkpython/talk-python-cli"
|
|
32
|
+
Documentation = "https://github.com/talkpython/talk-python-cli"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
talkpython = "talk_python_cli.app:main"
|
|
36
|
+
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=8.0",
|
|
40
|
+
"pytest-httpx>=0.34",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/talk_python_cli"]
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["hatchling"]
|
|
48
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
###### configuring what to type check and where to import from
|
|
2
|
+
|
|
3
|
+
# check all files in "."
|
|
4
|
+
project-includes = ["."]
|
|
5
|
+
# exclude dotfiles
|
|
6
|
+
project-excludes = ["**/.[!/.]*", "**/*venv/**"]
|
|
7
|
+
# perform an upward search for `.gitignore`, `.ignore`, and `.git/info/exclude`, and
|
|
8
|
+
# add those to `project-excludes` automatically
|
|
9
|
+
use-ignore-files = true
|
|
10
|
+
# import project files from "src" (main package) and "." (tests)
|
|
11
|
+
search-path = ["src", "."]
|
|
12
|
+
# let Pyrefly try to guess your search path
|
|
13
|
+
disable-search-path-heuristics = false
|
|
14
|
+
# do not include any third-party packages (except those provided by an interpreter)
|
|
15
|
+
site-package-path = []
|
|
16
|
+
|
|
17
|
+
###### configuring your python environment
|
|
18
|
+
|
|
19
|
+
python-platform = "darwin"
|
|
20
|
+
# assume the Python version we're using is 3.14, without querying an interpreter
|
|
21
|
+
python-version = "3.14"
|
|
22
|
+
# is Pyrefly disallowed from querying for an interpreter to automatically determine your
|
|
23
|
+
# `python-platform`, `python-version`, and extra entries to `site-package-path`?
|
|
24
|
+
skip-interpreter-query = false
|
|
25
|
+
# query the default Python interpreter on your system, if installed and `python_platform`,
|
|
26
|
+
# `python-version`, or `site-package-path` are unset.
|
|
27
|
+
# python-interpreter = null # this is commented out because there are no `null` values in TOML
|
|
28
|
+
|
|
29
|
+
#### configuring your type check settings
|
|
30
|
+
|
|
31
|
+
# wildcards for which Pyrefly will unconditionally replace the import with `typing.Any`
|
|
32
|
+
replace-imports-with-any = []
|
|
33
|
+
# wildcards for which Pyrefly will replace the import with `typing.Any` if it can't be found
|
|
34
|
+
ignore-missing-imports = []
|
|
35
|
+
# should Pyrefly skip type checking if we find a generated file?
|
|
36
|
+
ignore-errors-in-generated-code = false
|
|
37
|
+
# what should Pyrefly do when it encounters a function that is untyped?
|
|
38
|
+
untyped-def-behavior = "check-and-infer-return-type"
|
|
39
|
+
# can Pyrefly recognize ignore directives other than `# pyrefly: ignore` and `# type: ignore`
|
|
40
|
+
permissive-ignores = false
|
|
41
|
+
|
|
42
|
+
[errors]
|
|
43
|
+
# this is an empty table, meaning all errors are enabled by default
|
|
44
|
+
|
|
45
|
+
# no `[[sub-config]]` entries are included, since there are none by default
|