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.
- ducktap-0.1.0/.github/workflows/ci.yml +28 -0
- ducktap-0.1.0/.gitignore +41 -0
- ducktap-0.1.0/AGENTS.md +30 -0
- ducktap-0.1.0/CHANGELOG.md +13 -0
- ducktap-0.1.0/CONTRIBUTING.md +35 -0
- ducktap-0.1.0/LICENSE +21 -0
- ducktap-0.1.0/PKG-INFO +231 -0
- ducktap-0.1.0/README.md +171 -0
- ducktap-0.1.0/catalog/github.yaml +9 -0
- ducktap-0.1.0/catalog/petstore.yaml +9 -0
- ducktap-0.1.0/catalog/stripe.yaml +8 -0
- ducktap-0.1.0/docs/ARCHITECTURE.md +48 -0
- ducktap-0.1.0/docs/COMPARISON.md +63 -0
- ducktap-0.1.0/docs/PLUGINS.md +82 -0
- ducktap-0.1.0/docs/ROADMAP.md +70 -0
- ducktap-0.1.0/pyproject.toml +72 -0
- ducktap-0.1.0/skills/ducktap/SKILL.md +49 -0
- ducktap-0.1.0/skills/ducktap-publish/SKILL.md +22 -0
- ducktap-0.1.0/skills/ducktap-reprint/SKILL.md +24 -0
- ducktap-0.1.0/skills/ducktap-score/SKILL.md +16 -0
- ducktap-0.1.0/src/ducktap/__init__.py +6 -0
- ducktap-0.1.0/src/ducktap/__main__.py +4 -0
- ducktap-0.1.0/src/ducktap/catalog/__init__.py +4 -0
- ducktap-0.1.0/src/ducktap/catalog/registry.py +59 -0
- ducktap-0.1.0/src/ducktap/cli.py +213 -0
- ducktap-0.1.0/src/ducktap/core/__init__.py +1 -0
- ducktap-0.1.0/src/ducktap/core/naming.py +62 -0
- ducktap-0.1.0/src/ducktap/core/pipeline.py +59 -0
- ducktap-0.1.0/src/ducktap/core/plugins.py +104 -0
- ducktap-0.1.0/src/ducktap/core/spec.py +87 -0
- ducktap-0.1.0/src/ducktap/discovery/__init__.py +1 -0
- ducktap-0.1.0/src/ducktap/discovery/browser_sniff.py +71 -0
- ducktap-0.1.0/src/ducktap/discovery/har.py +126 -0
- ducktap-0.1.0/src/ducktap/discovery/openapi.py +247 -0
- ducktap-0.1.0/src/ducktap/generator/__init__.py +1 -0
- ducktap-0.1.0/src/ducktap/generator/mcp_server.py +111 -0
- ducktap-0.1.0/src/ducktap/generator/python_cli.py +121 -0
- ducktap-0.1.0/src/ducktap/generator/skill.py +60 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/.gitignore.j2 +6 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/README.md.j2 +47 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/__init__.py.j2 +3 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/__main__.py.j2 +4 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/client.py.j2 +102 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/commands.py.j2 +124 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/main.py.j2 +41 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/mirror.py.j2 +65 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/pyproject.toml.j2 +21 -0
- ducktap-0.1.0/src/ducktap/generator/templates/cli/test_smoke.py.j2 +19 -0
- ducktap-0.1.0/src/ducktap/generator/templates/mcp/README.md.j2 +33 -0
- ducktap-0.1.0/src/ducktap/generator/templates/mcp/__init__.py.j2 +2 -0
- ducktap-0.1.0/src/ducktap/generator/templates/mcp/__main__.py.j2 +4 -0
- ducktap-0.1.0/src/ducktap/generator/templates/mcp/pyproject.toml.j2 +20 -0
- ducktap-0.1.0/src/ducktap/generator/templates/mcp/server.py.j2 +102 -0
- ducktap-0.1.0/src/ducktap/generator/templates/skill/SKILL.md.j2 +49 -0
- ducktap-0.1.0/src/ducktap/generator/templates/skill/cursor.mdc.j2 +14 -0
- ducktap-0.1.0/src/ducktap/generator/templates/skill/tools.json.j2 +27 -0
- ducktap-0.1.0/src/ducktap/llm/__init__.py +4 -0
- ducktap-0.1.0/src/ducktap/llm/base.py +66 -0
- ducktap-0.1.0/src/ducktap/plugins/__init__.py +1 -0
- ducktap-0.1.0/src/ducktap/plugins/builtin/__init__.py +1 -0
- ducktap-0.1.0/src/ducktap/plugins/builtin/graphql_intro.py +58 -0
- ducktap-0.1.0/src/ducktap/verify/__init__.py +1 -0
- ducktap-0.1.0/src/ducktap/verify/scorecard.py +96 -0
- ducktap-0.1.0/src/ducktap/verify/shipcheck.py +65 -0
- ducktap-0.1.0/src/ducktap/webui/__init__.py +1 -0
- ducktap-0.1.0/src/ducktap/webui/app.py +122 -0
- ducktap-0.1.0/tests/fixtures/petstore.yaml +830 -0
- ducktap-0.1.0/tests/test_catalog.py +15 -0
- ducktap-0.1.0/tests/test_e2e.py +61 -0
- ducktap-0.1.0/tests/test_naming.py +42 -0
- ducktap-0.1.0/tests/test_openapi.py +22 -0
- 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
|
ducktap-0.1.0/.gitignore
ADDED
|
@@ -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
|
ducktap-0.1.0/AGENTS.md
ADDED
|
@@ -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
|
+
[](https://github.com/zanni098/DuckTap/actions/workflows/ci.yml)
|
|
69
|
+
[](LICENSE)
|
|
70
|
+
[](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).
|
ducktap-0.1.0/README.md
ADDED
|
@@ -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
|
+
[](https://github.com/zanni098/DuckTap/actions/workflows/ci.yml)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](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.
|