ducktap 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.
Files changed (72) hide show
  1. ducktap-0.1.0/.github/workflows/ci.yml +28 -0
  2. ducktap-0.1.0/.gitignore +41 -0
  3. ducktap-0.1.0/AGENTS.md +30 -0
  4. ducktap-0.1.0/CHANGELOG.md +13 -0
  5. ducktap-0.1.0/CONTRIBUTING.md +35 -0
  6. ducktap-0.1.0/LICENSE +21 -0
  7. ducktap-0.1.0/PKG-INFO +231 -0
  8. ducktap-0.1.0/README.md +171 -0
  9. ducktap-0.1.0/catalog/github.yaml +9 -0
  10. ducktap-0.1.0/catalog/petstore.yaml +9 -0
  11. ducktap-0.1.0/catalog/stripe.yaml +8 -0
  12. ducktap-0.1.0/docs/ARCHITECTURE.md +48 -0
  13. ducktap-0.1.0/docs/COMPARISON.md +63 -0
  14. ducktap-0.1.0/docs/PLUGINS.md +82 -0
  15. ducktap-0.1.0/docs/ROADMAP.md +70 -0
  16. ducktap-0.1.0/pyproject.toml +72 -0
  17. ducktap-0.1.0/skills/ducktap/SKILL.md +49 -0
  18. ducktap-0.1.0/skills/ducktap-publish/SKILL.md +22 -0
  19. ducktap-0.1.0/skills/ducktap-reprint/SKILL.md +24 -0
  20. ducktap-0.1.0/skills/ducktap-score/SKILL.md +16 -0
  21. ducktap-0.1.0/src/ducktap/__init__.py +6 -0
  22. ducktap-0.1.0/src/ducktap/__main__.py +4 -0
  23. ducktap-0.1.0/src/ducktap/catalog/__init__.py +4 -0
  24. ducktap-0.1.0/src/ducktap/catalog/registry.py +59 -0
  25. ducktap-0.1.0/src/ducktap/cli.py +213 -0
  26. ducktap-0.1.0/src/ducktap/core/__init__.py +1 -0
  27. ducktap-0.1.0/src/ducktap/core/naming.py +62 -0
  28. ducktap-0.1.0/src/ducktap/core/pipeline.py +59 -0
  29. ducktap-0.1.0/src/ducktap/core/plugins.py +104 -0
  30. ducktap-0.1.0/src/ducktap/core/spec.py +87 -0
  31. ducktap-0.1.0/src/ducktap/discovery/__init__.py +1 -0
  32. ducktap-0.1.0/src/ducktap/discovery/browser_sniff.py +71 -0
  33. ducktap-0.1.0/src/ducktap/discovery/har.py +126 -0
  34. ducktap-0.1.0/src/ducktap/discovery/openapi.py +247 -0
  35. ducktap-0.1.0/src/ducktap/generator/__init__.py +1 -0
  36. ducktap-0.1.0/src/ducktap/generator/mcp_server.py +111 -0
  37. ducktap-0.1.0/src/ducktap/generator/python_cli.py +121 -0
  38. ducktap-0.1.0/src/ducktap/generator/skill.py +60 -0
  39. ducktap-0.1.0/src/ducktap/generator/templates/cli/.gitignore.j2 +6 -0
  40. ducktap-0.1.0/src/ducktap/generator/templates/cli/README.md.j2 +47 -0
  41. ducktap-0.1.0/src/ducktap/generator/templates/cli/__init__.py.j2 +3 -0
  42. ducktap-0.1.0/src/ducktap/generator/templates/cli/__main__.py.j2 +4 -0
  43. ducktap-0.1.0/src/ducktap/generator/templates/cli/client.py.j2 +102 -0
  44. ducktap-0.1.0/src/ducktap/generator/templates/cli/commands.py.j2 +124 -0
  45. ducktap-0.1.0/src/ducktap/generator/templates/cli/main.py.j2 +41 -0
  46. ducktap-0.1.0/src/ducktap/generator/templates/cli/mirror.py.j2 +65 -0
  47. ducktap-0.1.0/src/ducktap/generator/templates/cli/pyproject.toml.j2 +21 -0
  48. ducktap-0.1.0/src/ducktap/generator/templates/cli/test_smoke.py.j2 +19 -0
  49. ducktap-0.1.0/src/ducktap/generator/templates/mcp/README.md.j2 +33 -0
  50. ducktap-0.1.0/src/ducktap/generator/templates/mcp/__init__.py.j2 +2 -0
  51. ducktap-0.1.0/src/ducktap/generator/templates/mcp/__main__.py.j2 +4 -0
  52. ducktap-0.1.0/src/ducktap/generator/templates/mcp/pyproject.toml.j2 +20 -0
  53. ducktap-0.1.0/src/ducktap/generator/templates/mcp/server.py.j2 +102 -0
  54. ducktap-0.1.0/src/ducktap/generator/templates/skill/SKILL.md.j2 +49 -0
  55. ducktap-0.1.0/src/ducktap/generator/templates/skill/cursor.mdc.j2 +14 -0
  56. ducktap-0.1.0/src/ducktap/generator/templates/skill/tools.json.j2 +27 -0
  57. ducktap-0.1.0/src/ducktap/llm/__init__.py +4 -0
  58. ducktap-0.1.0/src/ducktap/llm/base.py +66 -0
  59. ducktap-0.1.0/src/ducktap/plugins/__init__.py +1 -0
  60. ducktap-0.1.0/src/ducktap/plugins/builtin/__init__.py +1 -0
  61. ducktap-0.1.0/src/ducktap/plugins/builtin/graphql_intro.py +58 -0
  62. ducktap-0.1.0/src/ducktap/verify/__init__.py +1 -0
  63. ducktap-0.1.0/src/ducktap/verify/scorecard.py +96 -0
  64. ducktap-0.1.0/src/ducktap/verify/shipcheck.py +65 -0
  65. ducktap-0.1.0/src/ducktap/webui/__init__.py +1 -0
  66. ducktap-0.1.0/src/ducktap/webui/app.py +122 -0
  67. ducktap-0.1.0/tests/fixtures/petstore.yaml +830 -0
  68. ducktap-0.1.0/tests/test_catalog.py +15 -0
  69. ducktap-0.1.0/tests/test_e2e.py +61 -0
  70. ducktap-0.1.0/tests/test_naming.py +42 -0
  71. ducktap-0.1.0/tests/test_openapi.py +22 -0
  72. ducktap-0.1.0/tests/test_plugins.py +9 -0
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest, windows-latest, macos-latest]
15
+ python-version: ["3.11", "3.12"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - name: Install
22
+ run: |
23
+ python -m pip install --upgrade pip
24
+ pip install -e ".[dev]"
25
+ - name: Lint
26
+ run: ruff check src tests
27
+ - name: Tests
28
+ run: pytest -q
@@ -0,0 +1,41 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ venv/
7
+ .eggs/
8
+ dist/
9
+ build/
10
+ *.egg
11
+
12
+ # Tooling
13
+ .pytest_cache/
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .coverage
17
+ htmlcov/
18
+
19
+ # IDE
20
+ .vscode/
21
+ .idea/
22
+ *.swp
23
+
24
+ # OS
25
+ .DS_Store
26
+ Thumbs.db
27
+
28
+ # DuckTap working dirs
29
+ .ducktap/
30
+ output/
31
+ out/
32
+ generated/
33
+ *.duckdb
34
+ *.sqlite
35
+ *.sqlite3
36
+ *.har
37
+
38
+ # Secrets
39
+ .env
40
+ .env.local
41
+ *.pem
@@ -0,0 +1,30 @@
1
+ # Agent notes for DuckTap
2
+
3
+ ## Build / test commands
4
+
5
+ - Install dev deps: `pip install -e ".[dev]"`
6
+ - Run unit tests: `pytest -q`
7
+ - Lint: `ruff check src tests`
8
+ - Type check (best effort): `mypy src/ducktap`
9
+
10
+ ## End-to-end smoke
11
+
12
+ ```bash
13
+ ducktap press tests/fixtures/petstore.yaml --out ./out
14
+ ducktap shipcheck petstore --out-dir ./out
15
+ ```
16
+
17
+ ## Layout
18
+
19
+ - `src/ducktap/` — package
20
+ - `src/ducktap/generator/templates/` — Jinja2 templates (use `StrictUndefined`)
21
+ - `catalog/*.yaml` — recipe library
22
+ - `skills/` — Claude Code skills that drive DuckTap itself
23
+
24
+ ## Conventions
25
+
26
+ - Generated CLIs are named `<api>-dt-cli`; MCP servers `<api>-dt-mcp`;
27
+ Claude skills `ducktap-<api>`.
28
+ - Discoverers and generators are plugins (see `docs/PLUGINS.md`); register them
29
+ with `ducktap.core.plugins.register_*`.
30
+ - Don't break the `APISpec` schema casually — every generator depends on it.
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-05-11
4
+
5
+ Initial release. The lean loop end-to-end:
6
+
7
+ - OpenAPI / HAR / browser-sniff discoverers
8
+ - Python CLI + MCP server + skill generators
9
+ - Multi-LLM (LiteLLM)
10
+ - Plugin registry (entry points)
11
+ - Scorecard + shipcheck
12
+ - FastAPI dashboard
13
+ - Catalog (petstore, github, stripe)
@@ -0,0 +1,35 @@
1
+ # Contributing to DuckTap
2
+
3
+ Thanks for your interest! DuckTap is MIT-licensed and welcomes PRs.
4
+
5
+ ## Dev setup
6
+
7
+ ```bash
8
+ git clone https://github.com/zanni098/DuckTap
9
+ cd ducktap
10
+ python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
11
+ pip install -e ".[dev,sniff]"
12
+ playwright install chromium # optional, for browser-sniff
13
+ pytest -q
14
+ ```
15
+
16
+ ## Adding a catalog entry
17
+
18
+ 1. Create `catalog/<name>.yaml` matching `src/ducktap/catalog/registry.py:CatalogEntry`.
19
+ 2. Run `ducktap catalog print <name>` to verify it generates.
20
+ 3. PR with the YAML and a one-line summary in `CHANGELOG.md`.
21
+
22
+ ## Adding a plugin (better!)
23
+
24
+ See `docs/PLUGINS.md`. Publish as its own PyPI package and we'll list it in the
25
+ README.
26
+
27
+ ## Commit style
28
+
29
+ Conventional commits welcome but not required. PR titles like `feat(catalog): add Notion`, `fix(openapi): handle nullable enums`, `docs: …` work great.
30
+
31
+ ## Code style
32
+
33
+ - `ruff check src tests` clean.
34
+ - Type hints encouraged; not enforced by CI.
35
+ - New code paths need at least one test.
ducktap-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DuckTap Contributors
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.
ducktap-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.4
2
+ Name: ducktap
3
+ Version: 0.1.0
4
+ Summary: DuckTap — a CLI factory for AI agents. Print agent-native CLIs + MCP servers + skills from any API or website.
5
+ Project-URL: Homepage, https://github.com/zanni098/DuckTap
6
+ Project-URL: Repository, https://github.com/zanni098/DuckTap
7
+ Project-URL: Issues, https://github.com/zanni098/DuckTap/issues
8
+ Author: DuckTap Contributors
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 DuckTap Contributors
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: agents,ai,automation,cli,llm,mcp,openapi
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Topic :: Software Development :: Code Generators
37
+ Requires-Python: >=3.11
38
+ Requires-Dist: click>=8.1
39
+ Requires-Dist: fastapi>=0.110
40
+ Requires-Dist: httpx>=0.27
41
+ Requires-Dist: jinja2>=3.1
42
+ Requires-Dist: jsonref>=1.1
43
+ Requires-Dist: litellm>=1.40
44
+ Requires-Dist: mcp>=1.0
45
+ Requires-Dist: openapi-spec-validator>=0.7
46
+ Requires-Dist: pydantic>=2.7
47
+ Requires-Dist: pyyaml>=6.0
48
+ Requires-Dist: rich>=13.7
49
+ Requires-Dist: typer>=0.12
50
+ Requires-Dist: uvicorn[standard]>=0.30
51
+ Provides-Extra: dev
52
+ Requires-Dist: mypy>=1.10; extra == 'dev'
53
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
54
+ Requires-Dist: pytest>=8.0; extra == 'dev'
55
+ Requires-Dist: ruff>=0.5; extra == 'dev'
56
+ Provides-Extra: sniff
57
+ Requires-Dist: mitmproxy>=11.0; extra == 'sniff'
58
+ Requires-Dist: playwright>=1.45; extra == 'sniff'
59
+ Description-Content-Type: text/markdown
60
+
61
+ # DuckTap
62
+
63
+ > **Tape any API to your agent in one command.**
64
+ > DuckTap is a CLI factory for AI agents. Point it at an OpenAPI spec, a HAR
65
+ > file, or a plain website, and it *prints* a Python CLI, an MCP server, and a
66
+ > Claude/Cursor/Codex skill — wired up, cached, scored, ready to ship.
67
+
68
+ [![CI](https://github.com/zanni098/DuckTap/actions/workflows/ci.yml/badge.svg)](https://github.com/zanni098/DuckTap/actions/workflows/ci.yml)
69
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
70
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](pyproject.toml)
71
+
72
+ DuckTap is inspired by [Printing Press](https://printingpress.dev) by
73
+ [@mvanhorn](https://github.com/mvanhorn) — same north star (*muscle memory for
74
+ agents*) — rebuilt in Python with multi-LLM support, a web dashboard,
75
+ Playwright-powered browser sniffing, and a real plugin system.
76
+
77
+ ## Why a CLI factory?
78
+
79
+ In a world of AI agents, **a well-designed CLI is muscle memory**. No hunting
80
+ through docs, no wrong turns, no wasted tokens. DuckTap reads the spec, sniffs
81
+ the traffic when no spec exists, and prints:
82
+
83
+ - **A Python CLI** (`<api>-dt-cli`) — Click-based, auth from env vars, JSON by default, pretty mode for humans, local SQLite mirror for compound queries, retries on transient errors.
84
+ - **An MCP server** (`<api>-dt-mcp`) — every operation exposed as an MCP tool, stdio transport, drop into Claude Desktop or Cursor in 60 seconds.
85
+ - **A skill** for Claude Code, Cursor (`.mdc`), and a generic `tools.json` — so any agent harness can pick up where the others left off.
86
+ - **A scorecard** grading coverage, docs, auth clarity, typed params, artifacts, and naming.
87
+
88
+ ## Install
89
+
90
+ ```bash
91
+ git clone https://github.com/zanni098/DuckTap
92
+ cd ducktap
93
+ pip install -e ".[dev]"
94
+ ducktap --version
95
+ ```
96
+
97
+ For browser sniffing:
98
+
99
+ ```bash
100
+ pip install -e ".[dev,sniff]"
101
+ playwright install chromium
102
+ ```
103
+
104
+ ## Quick start
105
+
106
+ ```bash
107
+ # 1. From an OpenAPI spec (file or URL)
108
+ ducktap press https://petstore3.swagger.io/api/v3/openapi.yaml
109
+
110
+ # 2. From a HAR file (recorded browser traffic)
111
+ ducktap press ./capture.har --name myapi
112
+
113
+ # 3. From a website with no public spec
114
+ ducktap sniff https://example.com
115
+
116
+ # 4. From the curated catalog
117
+ ducktap catalog list
118
+ ducktap catalog print stripe
119
+
120
+ # 5. Browse + drive everything from the dashboard
121
+ ducktap ui # http://127.0.0.1:8765
122
+ ```
123
+
124
+ What you get under `./out/`:
125
+
126
+ ```
127
+ out/
128
+ ├── petstore-dt-cli/ # pip install -e . → petstore-dt-cli --help
129
+ │ ├── pyproject.toml
130
+ │ ├── README.md
131
+ │ ├── petstore_dt_cli/
132
+ │ │ ├── main.py
133
+ │ │ ├── commands.py # one click subcommand per API operation
134
+ │ │ ├── client.py # httpx + env-var auth + retries
135
+ │ │ └── mirror.py # local SQLite cache
136
+ │ └── tests/test_smoke.py
137
+ ├── petstore-dt-mcp/ # pip install -e . → add to Claude Desktop config
138
+ │ └── petstore_dt_mcp/server.py
139
+ └── skills/ducktap-petstore/
140
+ ├── SKILL.md # Claude Code skill
141
+ ├── ducktap-petstore.mdc # Cursor rule
142
+ └── tools.json # generic agent tool definitions
143
+ ```
144
+
145
+ ## How DuckTap improves on Printing Press
146
+
147
+ | | Printing Press | **DuckTap** |
148
+ |---|---|---|
149
+ | Language | Go | Python — easier to extend, richer LLM ecosystem |
150
+ | LLM | Claude only | **Multi-LLM via LiteLLM** (Anthropic, OpenAI, Gemini, Ollama, Groq, Azure) |
151
+ | Skills | Claude Code | **Claude Code + Cursor `.mdc` + generic `tools.json`** |
152
+ | UI | None | **Local FastAPI dashboard** (`ducktap ui`) |
153
+ | Plugins | Source fork | **Entry-point plugin system** — drop-in discoverers & generators |
154
+ | Browser sniff | Custom Go browser | **Playwright** — full HAR export, scriptable actions |
155
+ | Generated CLI runtime | Single Go binary | Python (pip-installable, hackable, single-file editable) |
156
+
157
+ See [`docs/COMPARISON.md`](docs/COMPARISON.md) for the full feature matrix.
158
+
159
+ ## Commands
160
+
161
+ ```text
162
+ ducktap press <source> # discover + generate (the default loop)
163
+ ducktap research <source> # discover only — emit normalized APISpec JSON
164
+ ducktap sniff <url> # browser-sniff a site (needs [sniff] extra)
165
+ ducktap scorecard <source> # quality scorecard
166
+ ducktap shipcheck <name> # structural & runtime sanity checks
167
+ ducktap catalog list|print # browse the recipe library
168
+ ducktap plugins list # show installed discoverers + generators
169
+ ducktap ui # local web dashboard
170
+ ```
171
+
172
+ ## Plugins
173
+
174
+ Add a discoverer or generator without forking. Register via Python entry points:
175
+
176
+ ```toml
177
+ # your_plugin/pyproject.toml
178
+ [project.entry-points."ducktap.plugins"]
179
+ mything = "your_plugin.module" # module just calls plugins.register_discoverer(...)
180
+ ```
181
+
182
+ See [`docs/PLUGINS.md`](docs/PLUGINS.md) and the sample at
183
+ `src/ducktap/plugins/builtin/graphql_intro.py`.
184
+
185
+ ## Architecture
186
+
187
+ ```
188
+ input (URL | spec | HAR)
189
+
190
+
191
+ ┌─────────────┐
192
+ │ Discovery │ openapi / har / browser-sniff / graphql (plugin) / …
193
+ └──────┬──────┘
194
+
195
+ APISpec (Pydantic) ──── intermediate normalized representation
196
+
197
+
198
+ ┌─────────────┐
199
+ │ Generator │ python-cli / mcp-server / skill / …
200
+ └──────┬──────┘
201
+
202
+ artifacts/ (CLI pkg + MCP pkg + SKILL.md + cursor.mdc + tools.json)
203
+
204
+
205
+ ┌─────────────┐
206
+ │ Verify │ scorecard + shipcheck + (optional) live smoke test
207
+ └─────────────┘
208
+ ```
209
+
210
+ See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
211
+
212
+ ## Roadmap
213
+
214
+ See [`docs/ROADMAP.md`](docs/ROADMAP.md). Highlights for v0.2+:
215
+
216
+ - LLM-assisted **polish** step (operation descriptions, command names, doc strings)
217
+ - **GraphQL** first-class (today: plugin, beta)
218
+ - **Auth doctor** — detect login flows during sniffing, emit accurate auth blocks
219
+ - **Compound query** macros (canonical "what's interesting about X" recipes)
220
+ - **CLI publish** to PyPI + GitHub in one command
221
+
222
+ ## License
223
+
224
+ MIT — see [`LICENSE`](LICENSE).
225
+
226
+ ## Acknowledgements
227
+
228
+ Inspired by [Printing Press](https://github.com/mvanhorn/cli-printing-press) by
229
+ Matt Van Horn and the agent-CLI playbook proved out by
230
+ [discrawl](https://github.com/steipete/discrawl) and
231
+ [gogcli](https://github.com/steipete/gogcli).
@@ -0,0 +1,171 @@
1
+ # DuckTap
2
+
3
+ > **Tape any API to your agent in one command.**
4
+ > DuckTap is a CLI factory for AI agents. Point it at an OpenAPI spec, a HAR
5
+ > file, or a plain website, and it *prints* a Python CLI, an MCP server, and a
6
+ > Claude/Cursor/Codex skill — wired up, cached, scored, ready to ship.
7
+
8
+ [![CI](https://github.com/zanni098/DuckTap/actions/workflows/ci.yml/badge.svg)](https://github.com/zanni098/DuckTap/actions/workflows/ci.yml)
9
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](pyproject.toml)
11
+
12
+ DuckTap is inspired by [Printing Press](https://printingpress.dev) by
13
+ [@mvanhorn](https://github.com/mvanhorn) — same north star (*muscle memory for
14
+ agents*) — rebuilt in Python with multi-LLM support, a web dashboard,
15
+ Playwright-powered browser sniffing, and a real plugin system.
16
+
17
+ ## Why a CLI factory?
18
+
19
+ In a world of AI agents, **a well-designed CLI is muscle memory**. No hunting
20
+ through docs, no wrong turns, no wasted tokens. DuckTap reads the spec, sniffs
21
+ the traffic when no spec exists, and prints:
22
+
23
+ - **A Python CLI** (`<api>-dt-cli`) — Click-based, auth from env vars, JSON by default, pretty mode for humans, local SQLite mirror for compound queries, retries on transient errors.
24
+ - **An MCP server** (`<api>-dt-mcp`) — every operation exposed as an MCP tool, stdio transport, drop into Claude Desktop or Cursor in 60 seconds.
25
+ - **A skill** for Claude Code, Cursor (`.mdc`), and a generic `tools.json` — so any agent harness can pick up where the others left off.
26
+ - **A scorecard** grading coverage, docs, auth clarity, typed params, artifacts, and naming.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ git clone https://github.com/zanni098/DuckTap
32
+ cd ducktap
33
+ pip install -e ".[dev]"
34
+ ducktap --version
35
+ ```
36
+
37
+ For browser sniffing:
38
+
39
+ ```bash
40
+ pip install -e ".[dev,sniff]"
41
+ playwright install chromium
42
+ ```
43
+
44
+ ## Quick start
45
+
46
+ ```bash
47
+ # 1. From an OpenAPI spec (file or URL)
48
+ ducktap press https://petstore3.swagger.io/api/v3/openapi.yaml
49
+
50
+ # 2. From a HAR file (recorded browser traffic)
51
+ ducktap press ./capture.har --name myapi
52
+
53
+ # 3. From a website with no public spec
54
+ ducktap sniff https://example.com
55
+
56
+ # 4. From the curated catalog
57
+ ducktap catalog list
58
+ ducktap catalog print stripe
59
+
60
+ # 5. Browse + drive everything from the dashboard
61
+ ducktap ui # http://127.0.0.1:8765
62
+ ```
63
+
64
+ What you get under `./out/`:
65
+
66
+ ```
67
+ out/
68
+ ├── petstore-dt-cli/ # pip install -e . → petstore-dt-cli --help
69
+ │ ├── pyproject.toml
70
+ │ ├── README.md
71
+ │ ├── petstore_dt_cli/
72
+ │ │ ├── main.py
73
+ │ │ ├── commands.py # one click subcommand per API operation
74
+ │ │ ├── client.py # httpx + env-var auth + retries
75
+ │ │ └── mirror.py # local SQLite cache
76
+ │ └── tests/test_smoke.py
77
+ ├── petstore-dt-mcp/ # pip install -e . → add to Claude Desktop config
78
+ │ └── petstore_dt_mcp/server.py
79
+ └── skills/ducktap-petstore/
80
+ ├── SKILL.md # Claude Code skill
81
+ ├── ducktap-petstore.mdc # Cursor rule
82
+ └── tools.json # generic agent tool definitions
83
+ ```
84
+
85
+ ## How DuckTap improves on Printing Press
86
+
87
+ | | Printing Press | **DuckTap** |
88
+ |---|---|---|
89
+ | Language | Go | Python — easier to extend, richer LLM ecosystem |
90
+ | LLM | Claude only | **Multi-LLM via LiteLLM** (Anthropic, OpenAI, Gemini, Ollama, Groq, Azure) |
91
+ | Skills | Claude Code | **Claude Code + Cursor `.mdc` + generic `tools.json`** |
92
+ | UI | None | **Local FastAPI dashboard** (`ducktap ui`) |
93
+ | Plugins | Source fork | **Entry-point plugin system** — drop-in discoverers & generators |
94
+ | Browser sniff | Custom Go browser | **Playwright** — full HAR export, scriptable actions |
95
+ | Generated CLI runtime | Single Go binary | Python (pip-installable, hackable, single-file editable) |
96
+
97
+ See [`docs/COMPARISON.md`](docs/COMPARISON.md) for the full feature matrix.
98
+
99
+ ## Commands
100
+
101
+ ```text
102
+ ducktap press <source> # discover + generate (the default loop)
103
+ ducktap research <source> # discover only — emit normalized APISpec JSON
104
+ ducktap sniff <url> # browser-sniff a site (needs [sniff] extra)
105
+ ducktap scorecard <source> # quality scorecard
106
+ ducktap shipcheck <name> # structural & runtime sanity checks
107
+ ducktap catalog list|print # browse the recipe library
108
+ ducktap plugins list # show installed discoverers + generators
109
+ ducktap ui # local web dashboard
110
+ ```
111
+
112
+ ## Plugins
113
+
114
+ Add a discoverer or generator without forking. Register via Python entry points:
115
+
116
+ ```toml
117
+ # your_plugin/pyproject.toml
118
+ [project.entry-points."ducktap.plugins"]
119
+ mything = "your_plugin.module" # module just calls plugins.register_discoverer(...)
120
+ ```
121
+
122
+ See [`docs/PLUGINS.md`](docs/PLUGINS.md) and the sample at
123
+ `src/ducktap/plugins/builtin/graphql_intro.py`.
124
+
125
+ ## Architecture
126
+
127
+ ```
128
+ input (URL | spec | HAR)
129
+
130
+
131
+ ┌─────────────┐
132
+ │ Discovery │ openapi / har / browser-sniff / graphql (plugin) / …
133
+ └──────┬──────┘
134
+
135
+ APISpec (Pydantic) ──── intermediate normalized representation
136
+
137
+
138
+ ┌─────────────┐
139
+ │ Generator │ python-cli / mcp-server / skill / …
140
+ └──────┬──────┘
141
+
142
+ artifacts/ (CLI pkg + MCP pkg + SKILL.md + cursor.mdc + tools.json)
143
+
144
+
145
+ ┌─────────────┐
146
+ │ Verify │ scorecard + shipcheck + (optional) live smoke test
147
+ └─────────────┘
148
+ ```
149
+
150
+ See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
151
+
152
+ ## Roadmap
153
+
154
+ See [`docs/ROADMAP.md`](docs/ROADMAP.md). Highlights for v0.2+:
155
+
156
+ - LLM-assisted **polish** step (operation descriptions, command names, doc strings)
157
+ - **GraphQL** first-class (today: plugin, beta)
158
+ - **Auth doctor** — detect login flows during sniffing, emit accurate auth blocks
159
+ - **Compound query** macros (canonical "what's interesting about X" recipes)
160
+ - **CLI publish** to PyPI + GitHub in one command
161
+
162
+ ## License
163
+
164
+ MIT — see [`LICENSE`](LICENSE).
165
+
166
+ ## Acknowledgements
167
+
168
+ Inspired by [Printing Press](https://github.com/mvanhorn/cli-printing-press) by
169
+ Matt Van Horn and the agent-CLI playbook proved out by
170
+ [discrawl](https://github.com/steipete/discrawl) and
171
+ [gogcli](https://github.com/steipete/gogcli).
@@ -0,0 +1,9 @@
1
+ name: github
2
+ display_name: GitHub REST
3
+ description: Full GitHub REST v3 API surface — repos, issues, PRs, actions, search.
4
+ category: developer
5
+ spec_url: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json
6
+ spec_format: json
7
+ tier: official
8
+ homepage: https://docs.github.com/rest
9
+ notes: Large spec (~1000 endpoints). Use `--targets python-cli` to skip MCP if tools list is too long for your harness.
@@ -0,0 +1,9 @@
1
+ name: petstore
2
+ display_name: Swagger Petstore
3
+ description: Canonical OpenAPI example — pet store management.
4
+ category: example
5
+ spec_url: https://petstore3.swagger.io/api/v3/openapi.yaml
6
+ spec_format: yaml
7
+ tier: official
8
+ homepage: https://petstore3.swagger.io
9
+ notes: Used as DuckTap's smoke test.
@@ -0,0 +1,8 @@
1
+ name: stripe
2
+ display_name: Stripe API
3
+ description: Payments, customers, subscriptions, invoices, products, prices.
4
+ category: payments
5
+ spec_url: https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json
6
+ spec_format: json
7
+ tier: official
8
+ homepage: https://stripe.com/docs/api
@@ -0,0 +1,48 @@
1
+ # DuckTap Architecture
2
+
3
+ DuckTap is organized around one normalized data model (`APISpec`) sitting
4
+ between two extensible plugin layers (Discoverers and Generators).
5
+
6
+ ## Modules
7
+
8
+ | Module | Responsibility |
9
+ |---|---|
10
+ | `ducktap.core.spec` | `APISpec` (pydantic) — the intermediate representation. |
11
+ | `ducktap.core.naming` | Slugify, snake/kebab case, flag/env-var conventions. |
12
+ | `ducktap.core.plugins` | Plugin registry + entry-point loader. |
13
+ | `ducktap.core.pipeline` | High-level `discover()` and `press()`. |
14
+ | `ducktap.discovery.openapi` | OpenAPI 2/3 → `APISpec`. |
15
+ | `ducktap.discovery.har` | HAR file → `APISpec` (clustered request grouping). |
16
+ | `ducktap.discovery.browser_sniff` | Playwright → HAR → `APISpec`. |
17
+ | `ducktap.generator.python_cli` | `APISpec` → Click-based Python CLI package. |
18
+ | `ducktap.generator.mcp_server` | `APISpec` → MCP server package (stdio). |
19
+ | `ducktap.generator.skill` | `APISpec` → `SKILL.md`, `.mdc`, `tools.json`. |
20
+ | `ducktap.llm.base` | LiteLLM wrapper — multi-provider chat. |
21
+ | `ducktap.verify.scorecard` | Quality grading (6 dimensions). |
22
+ | `ducktap.verify.shipcheck` | Structural + runtime sanity checks. |
23
+ | `ducktap.catalog.registry` | YAML recipe loader. |
24
+ | `ducktap.webui.app` | FastAPI dashboard. |
25
+ | `ducktap.cli` | Top-level `ducktap` Typer entry point. |
26
+
27
+ ## The pipeline
28
+
29
+ ```
30
+ press(source, out_dir)
31
+
32
+ ├── discover(source)
33
+ │ for d in [openapi, har, browser-sniff, …]:
34
+ │ if d.can_handle(source): return d.discover(source)
35
+
36
+ ├── for tgt in targets:
37
+ │ generators[tgt].generate(spec, out_dir)
38
+
39
+ └── PressResult(spec, out_dir, artifacts)
40
+ ```
41
+
42
+ ## Why this shape
43
+
44
+ - **One intermediate format** means new discoverers and new generators evolve independently.
45
+ - **Plugins via entry points** means improvements ship as PyPI packages, not forks.
46
+ - **Pydantic models** give us free JSON serialization (research command writes the spec to disk for inspection / debugging / LLM polish steps).
47
+ - **Click for generated CLIs** because every Python user already has it and Click subcommands have richer help output than argparse out of the box.
48
+ - **MCP SDK** rather than hand-rolled JSON-RPC, so we get protocol-version updates for free.