tooli 1.0.1__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.
- tooli-1.0.1/LICENSE +21 -0
- tooli-1.0.1/PKG-INFO +34 -0
- tooli-1.0.1/README.md +317 -0
- tooli-1.0.1/pyproject.toml +71 -0
- tooli-1.0.1/setup.cfg +4 -0
- tooli-1.0.1/tests/test_api.py +27 -0
- tooli-1.0.1/tests/test_app.py +649 -0
- tooli-1.0.1/tests/test_architecture.py +113 -0
- tooli-1.0.1/tests/test_auth.py +63 -0
- tooli-1.0.1/tests/test_context.py +79 -0
- tooli-1.0.1/tests/test_csvkit_t.py +106 -0
- tooli-1.0.1/tests/test_docq.py +134 -0
- tooli-1.0.1/tests/test_docs.py +59 -0
- tooli-1.0.1/tests/test_dry_run.py +42 -0
- tooli-1.0.1/tests/test_envar.py +115 -0
- tooli-1.0.1/tests/test_eval.py +89 -0
- tooli-1.0.1/tests/test_gitsum.py +112 -0
- tooli-1.0.1/tests/test_idempotency.py +60 -0
- tooli-1.0.1/tests/test_imgsort.py +140 -0
- tooli-1.0.1/tests/test_mcp.py +70 -0
- tooli-1.0.1/tests/test_note_indexer.py +159 -0
- tooli-1.0.1/tests/test_otel.py +101 -0
- tooli-1.0.1/tests/test_proj.py +100 -0
- tooli-1.0.1/tests/test_secret_input.py +104 -0
- tooli-1.0.1/tests/test_security.py +106 -0
- tooli-1.0.1/tests/test_skill.py +24 -0
- tooli-1.0.1/tests/test_syswatch.py +61 -0
- tooli-1.0.1/tests/test_taskr.py +119 -0
- tooli-1.0.1/tests/test_telemetry.py +87 -0
- tooli-1.0.1/tests/test_versioning.py +101 -0
- tooli-1.0.1/tooli/__init__.py +27 -0
- tooli-1.0.1/tooli/annotations.py +29 -0
- tooli-1.0.1/tooli/api/openapi.py +94 -0
- tooli-1.0.1/tooli/api/server.py +112 -0
- tooli-1.0.1/tooli/app.py +378 -0
- tooli-1.0.1/tooli/auth.py +32 -0
- tooli-1.0.1/tooli/command.py +1226 -0
- tooli-1.0.1/tooli/command_meta.py +55 -0
- tooli-1.0.1/tooli/config.py +63 -0
- tooli-1.0.1/tooli/context.py +118 -0
- tooli-1.0.1/tooli/docs/llms_txt.py +40 -0
- tooli-1.0.1/tooli/docs/man.py +67 -0
- tooli-1.0.1/tooli/docs/skill.py +105 -0
- tooli-1.0.1/tooli/dry_run.py +98 -0
- tooli-1.0.1/tooli/envelope.py +25 -0
- tooli-1.0.1/tooli/errors.py +175 -0
- tooli-1.0.1/tooli/eval/__init__.py +23 -0
- tooli-1.0.1/tooli/eval/analyzer.py +119 -0
- tooli-1.0.1/tooli/eval/recorder.py +139 -0
- tooli-1.0.1/tooli/exit_codes.py +25 -0
- tooli-1.0.1/tooli/idempotency.py +74 -0
- tooli-1.0.1/tooli/input.py +225 -0
- tooli-1.0.1/tooli/mcp/__init__.py +6 -0
- tooli-1.0.1/tooli/mcp/export.py +69 -0
- tooli-1.0.1/tooli/mcp/server.py +59 -0
- tooli-1.0.1/tooli/output.py +91 -0
- tooli-1.0.1/tooli/pagination.py +75 -0
- tooli-1.0.1/tooli/providers/__init__.py +7 -0
- tooli-1.0.1/tooli/providers/base.py +18 -0
- tooli-1.0.1/tooli/providers/filesystem.py +86 -0
- tooli-1.0.1/tooli/providers/local.py +32 -0
- tooli-1.0.1/tooli/py.typed +0 -0
- tooli-1.0.1/tooli/schema.py +101 -0
- tooli-1.0.1/tooli/security/__init__.py +7 -0
- tooli-1.0.1/tooli/security/policy.py +24 -0
- tooli-1.0.1/tooli/security/sanitizer.py +35 -0
- tooli-1.0.1/tooli/telemetry.py +94 -0
- tooli-1.0.1/tooli/telemetry_pipeline.py +257 -0
- tooli-1.0.1/tooli/testing.py +39 -0
- tooli-1.0.1/tooli/transforms.py +80 -0
- tooli-1.0.1/tooli/versioning.py +103 -0
- tooli-1.0.1/tooli.egg-info/PKG-INFO +34 -0
- tooli-1.0.1/tooli.egg-info/SOURCES.txt +74 -0
- tooli-1.0.1/tooli.egg-info/dependency_links.txt +1 -0
- tooli-1.0.1/tooli.egg-info/requires.txt +19 -0
- tooli-1.0.1/tooli.egg-info/top_level.txt +1 -0
tooli-1.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tooli developers
|
|
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.
|
tooli-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tooli
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: The agent-native CLI framework for Python
|
|
5
|
+
Author: tooli developers
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/weisberg/tooli
|
|
8
|
+
Project-URL: Repository, https://github.com/weisberg/tooli
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: typer>=0.9
|
|
21
|
+
Requires-Dist: pydantic>=2.0
|
|
22
|
+
Requires-Dist: rich>=13.0
|
|
23
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
29
|
+
Provides-Extra: mcp
|
|
30
|
+
Requires-Dist: fastmcp>=2.0; extra == "mcp"
|
|
31
|
+
Provides-Extra: api
|
|
32
|
+
Requires-Dist: starlette>=0.27; extra == "api"
|
|
33
|
+
Requires-Dist: uvicorn>=0.20; extra == "api"
|
|
34
|
+
Dynamic: license-file
|
tooli-1.0.1/README.md
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Tooli
|
|
2
|
+
|
|
3
|
+
[](https://github.com/weisberg/tooli/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/tooli/)
|
|
5
|
+
[](https://pypi.org/project/tooli/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
The agent-native CLI framework for Python. Write one function, get a CLI, an MCP tool, and a self-documenting schema.
|
|
9
|
+
|
|
10
|
+
The name comes from "tool" + "CLI" = "tooli".
|
|
11
|
+
|
|
12
|
+
Tooli turns typed Python functions into CLI commands that are simultaneously human-friendly (Rich output, shell completions) and machine-consumable (JSON schemas, structured output, MCP compatibility). No separate "agent version" of your tool required.
|
|
13
|
+
|
|
14
|
+
## Why Tooli?
|
|
15
|
+
|
|
16
|
+
AI agents invoke thousands of CLI commands daily, but standard CLIs were designed for humans:
|
|
17
|
+
|
|
18
|
+
- **Interactive prompts hang agents** that can't navigate pagers or password dialogs
|
|
19
|
+
- **Unstructured output wastes tokens** — agents parse text with regex instead of reading JSON
|
|
20
|
+
- **Vague errors prevent self-correction** — "Error: invalid input" gives agents nothing to work with
|
|
21
|
+
- **No discoverability** — agents hallucinate flags for undocumented tools
|
|
22
|
+
|
|
23
|
+
Tooli fixes all of this. One decorated function produces a CLI, a JSON Schema, an MCP tool definition, structured errors with recovery suggestions, and auto-generated documentation — from a single source of truth.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **Dual-mode output** — Rich tables for humans, JSON/JSONL for agents, auto-detected via TTY
|
|
28
|
+
- **Structured errors** — actionable error objects with suggestion fields that guide agent self-correction
|
|
29
|
+
- **Schema generation** — JSON Schema from type hints, compatible with MCP `inputSchema` and OpenAI function-calling
|
|
30
|
+
- **MCP server mode** — serve your CLI as an MCP tool server over stdio or HTTP with zero extra code
|
|
31
|
+
- **SKILL.md generation** — auto-generated agent-readable documentation, always in sync with code
|
|
32
|
+
- **stdin/file parity** — `StdinOr[T]` type makes files, URLs, and piped data interchangeable
|
|
33
|
+
- **Standard global flags** — `--json`, `--jsonl`, `--plain`, `--quiet`, `--dry-run`, `--schema` injected automatically
|
|
34
|
+
- **Agent-safe by default** — no interactive prompts in non-TTY mode, no stdout pollution, strict exit codes
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install tooli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Optional extras:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install tooli[mcp] # MCP server support (fastmcp)
|
|
46
|
+
pip install tooli[api] # HTTP API server (starlette, uvicorn)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from tooli import Tooli, Annotated, Option, Argument
|
|
53
|
+
from tooli.annotations import ReadOnly, Idempotent
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
app = Tooli(
|
|
57
|
+
name="file-tools",
|
|
58
|
+
description="File manipulation utilities",
|
|
59
|
+
version="1.0.0",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@app.command(
|
|
63
|
+
annotations=ReadOnly | Idempotent,
|
|
64
|
+
examples=[
|
|
65
|
+
{"args": ["--pattern", "*.py", "--root", "/project"],
|
|
66
|
+
"description": "Find all Python files in a project"},
|
|
67
|
+
],
|
|
68
|
+
)
|
|
69
|
+
def find_files(
|
|
70
|
+
pattern: Annotated[str, Argument(help="Glob pattern to match files")],
|
|
71
|
+
root: Annotated[Path, Option(help="Root directory to search from")] = Path("."),
|
|
72
|
+
max_depth: Annotated[int, Option(help="Maximum directory depth")] = 10,
|
|
73
|
+
) -> list[dict]:
|
|
74
|
+
"""Find files matching a glob pattern in a directory tree."""
|
|
75
|
+
results = []
|
|
76
|
+
for path in root.rglob(pattern):
|
|
77
|
+
results.append({"path": str(path), "size": path.stat().st_size})
|
|
78
|
+
return results
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
app()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Human usage
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
$ file-tools find-files "*.py" --root ./src
|
|
88
|
+
┌──────────────────────┬───────┐
|
|
89
|
+
│ Path │ Size │
|
|
90
|
+
├──────────────────────┼───────┤
|
|
91
|
+
│ src/main.py │ 1,204 │
|
|
92
|
+
│ src/utils.py │ 892 │
|
|
93
|
+
└──────────────────────┴───────┘
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Agent usage
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
$ file-tools find-files "*.py" --root ./src --json
|
|
100
|
+
{
|
|
101
|
+
"ok": true,
|
|
102
|
+
"result": [
|
|
103
|
+
{"path": "src/main.py", "size": 1204},
|
|
104
|
+
{"path": "src/utils.py", "size": 892}
|
|
105
|
+
],
|
|
106
|
+
"meta": {"tool": "file-tools.find-files", "version": "1.0.0", "duration_ms": 34}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Schema export
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
$ file-tools find-files --schema
|
|
114
|
+
{
|
|
115
|
+
"name": "find-files",
|
|
116
|
+
"description": "Find files matching a glob pattern in a directory tree.",
|
|
117
|
+
"inputSchema": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"properties": {
|
|
120
|
+
"pattern": {"type": "string", "description": "Glob pattern to match files"},
|
|
121
|
+
"root": {"type": "string", "default": ".", "description": "Root directory to search from"},
|
|
122
|
+
"max_depth": {"type": "integer", "default": 10, "description": "Maximum directory depth"}
|
|
123
|
+
},
|
|
124
|
+
"required": ["pattern"]
|
|
125
|
+
},
|
|
126
|
+
"annotations": {"readOnlyHint": true, "idempotentHint": true}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### MCP server mode
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
$ file-tools mcp serve --transport stdio
|
|
134
|
+
$ file-tools mcp serve --transport http --host 127.0.0.1 --port 8080
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Add to your MCP client config and every command becomes a tool.
|
|
138
|
+
|
|
139
|
+
For SSE-enabled clients, use:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
$ file-tools mcp serve --transport sse --host 127.0.0.1 --port 8080
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Structured Errors
|
|
146
|
+
|
|
147
|
+
When something goes wrong, agents get actionable recovery guidance instead of opaque messages:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
$ file-tools find-files "*.rs" --root ./src --json
|
|
151
|
+
{
|
|
152
|
+
"ok": false,
|
|
153
|
+
"error": {
|
|
154
|
+
"code": "E3001",
|
|
155
|
+
"category": "state",
|
|
156
|
+
"message": "No files matched pattern '*.rs' in ./src",
|
|
157
|
+
"suggestion": {
|
|
158
|
+
"action": "retry_with_modified_input",
|
|
159
|
+
"fix": "The directory contains .py files. Try pattern '*.py' instead.",
|
|
160
|
+
"example": "find-files '*.py' --root ./src"
|
|
161
|
+
},
|
|
162
|
+
"is_retryable": true
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Output Modes
|
|
168
|
+
|
|
169
|
+
Tooli auto-detects the right output format, or you can be explicit:
|
|
170
|
+
|
|
171
|
+
| Flag | Behavior |
|
|
172
|
+
|---|---|
|
|
173
|
+
| *(TTY, no flag)* | Rich formatted output for humans |
|
|
174
|
+
| `--json` | Single JSON envelope to stdout |
|
|
175
|
+
| `--jsonl` | Newline-delimited JSON for streaming |
|
|
176
|
+
| `--plain` | Unformatted text for grep/awk pipelines |
|
|
177
|
+
| `--quiet` | Suppress non-essential output |
|
|
178
|
+
|
|
179
|
+
## Optional Telemetry
|
|
180
|
+
|
|
181
|
+
Tooli supports anonymous, opt-in usage telemetry for tool authors.
|
|
182
|
+
|
|
183
|
+
- Disabled by default.
|
|
184
|
+
- Enable with `TOOLI_TELEMETRY=true` or `Tooli(telemetry=True)`.
|
|
185
|
+
- Records only command identifiers, duration (ms), success/failure outcome, and exit/error codes.
|
|
186
|
+
- No command arguments or command output payloads are recorded.
|
|
187
|
+
- Local-first by default to `~/.config/tooli/telemetry/events.jsonl`.
|
|
188
|
+
- Remote forwarding is opt-in via `TOOLI_TELEMETRY_ENDPOINT` or `Tooli(telemetry_endpoint=...)`.
|
|
189
|
+
- Retention policy is local-only by default: entries older than 30 days are pruned.
|
|
190
|
+
|
|
191
|
+
## Dry-Run Planning
|
|
192
|
+
|
|
193
|
+
Tooli supports opt-in dry-run planning via `@dry_run_support`.
|
|
194
|
+
|
|
195
|
+
- Decorate command functions with `@dry_run_support`.
|
|
196
|
+
- Use `record_dry_action(action, target, details={...})` to add plan steps.
|
|
197
|
+
- Invoke with `--dry-run` to return the action plan instead of executing side effects.
|
|
198
|
+
- In JSON output, dry-run responses set `meta.dry_run` to `true`.
|
|
199
|
+
|
|
200
|
+
## Auto-Generated Documentation
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Agent-readable skill documentation
|
|
204
|
+
$ file-tools generate-skill > SKILL.md
|
|
205
|
+
|
|
206
|
+
# LLM-friendly docs (llms.txt standard)
|
|
207
|
+
$ file-tools docs llms
|
|
208
|
+
|
|
209
|
+
# Unix man page
|
|
210
|
+
$ file-tools docs man
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Input Unification
|
|
214
|
+
|
|
215
|
+
The `StdinOr[T]` type makes files, URLs, and piped data interchangeable:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from tooli import StdinOr
|
|
219
|
+
|
|
220
|
+
@app.command()
|
|
221
|
+
def process(
|
|
222
|
+
input_data: Annotated[StdinOr[Path], Argument(help="Input file, URL, or stdin")],
|
|
223
|
+
) -> dict:
|
|
224
|
+
"""Process data from any input source."""
|
|
225
|
+
...
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# All equivalent:
|
|
230
|
+
$ file-tools process data.csv
|
|
231
|
+
$ file-tools process https://example.com/data.csv
|
|
232
|
+
$ cat data.csv | file-tools process -
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Global Flags
|
|
236
|
+
|
|
237
|
+
Every Tooli command automatically gets:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
--output, -o auto|json|jsonl|text|plain
|
|
241
|
+
--json/--jsonl Convenience aliases
|
|
242
|
+
--quiet, -q Suppress non-essential output
|
|
243
|
+
--verbose, -v Increase verbosity (-vvv)
|
|
244
|
+
--dry-run Preview without executing
|
|
245
|
+
--yes Skip confirmation prompts (for automation/agents)
|
|
246
|
+
--no-color Disable colors (also respects NO_COLOR)
|
|
247
|
+
--print0 Emit NUL-separated output for list types in text/plain modes
|
|
248
|
+
--timeout Max execution time in seconds
|
|
249
|
+
--null Parse NUL-delimited list input from stdin (list-processing)
|
|
250
|
+
--schema Print JSON Schema and exit
|
|
251
|
+
--response-format concise|detailed
|
|
252
|
+
--help-agent Token-optimized help for agents
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Architecture
|
|
256
|
+
|
|
257
|
+
Tooli builds on the Python typing + decorator pipeline, adding a parallel schema generation path:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
@app.command()
|
|
261
|
+
Python function + type hints
|
|
262
|
+
│ │
|
|
263
|
+
▼ ▼
|
|
264
|
+
CLI Pipeline Schema Pipeline
|
|
265
|
+
→ CLI params → Pydantic model
|
|
266
|
+
→ CLI parser → JSON Schema
|
|
267
|
+
│ │
|
|
268
|
+
▼ ▼
|
|
269
|
+
CLI Output Agent Output
|
|
270
|
+
Rich tables MCP tool schema
|
|
271
|
+
Completions SKILL.md / JSON
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Key design decisions:
|
|
275
|
+
- **Library-first API** — the public surface is Tooli-native (no framework objects leaked into user code)
|
|
276
|
+
- **Pydantic schemas** — same pipeline as FastAPI and FastMCP
|
|
277
|
+
- **Functions stay callable** — no mutation; test with `CliRunner` or call directly as Python
|
|
278
|
+
|
|
279
|
+
## Examples
|
|
280
|
+
|
|
281
|
+
The [`examples/`](examples/) directory contains 18 complete CLI apps built with Tooli, each demonstrating different features:
|
|
282
|
+
|
|
283
|
+
| App | Features |
|
|
284
|
+
|---|---|
|
|
285
|
+
| **[docq](examples/docq/)** | ReadOnly, paginated, stdin input, output formats |
|
|
286
|
+
| **[gitsum](examples/gitsum/)** | ReadOnly, subprocess, StdinOr for diffs |
|
|
287
|
+
| **[csvkit_t](examples/csvkit_t/)** | StdinOr, JSONL output, paginated, OpenWorld |
|
|
288
|
+
| **[syswatch](examples/syswatch/)** | ReadOnly, paginated, structured errors |
|
|
289
|
+
| **[taskr](examples/taskr/)** | Idempotent, Destructive, paginated CRUD |
|
|
290
|
+
| **[proj](examples/proj/)** | Destructive, DryRunRecorder, Idempotent |
|
|
291
|
+
| **[envar](examples/envar/)** | SecretInput, AuthContext scopes |
|
|
292
|
+
| **[imgsort](examples/imgsort/)** | Destructive+Idempotent, DryRunRecorder, batch ops |
|
|
293
|
+
| **[note_indexer](examples/note_indexer/)** | ReadOnly, paginated, JSON index, error handling |
|
|
294
|
+
|
|
295
|
+
See the [examples README](examples/README.md) for the full list and usage guide.
|
|
296
|
+
|
|
297
|
+
## Development
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
# Clone and install for development
|
|
301
|
+
git clone https://github.com/weisberg/tooli.git
|
|
302
|
+
cd tooli
|
|
303
|
+
pip install -e ".[dev]"
|
|
304
|
+
|
|
305
|
+
# Run tests
|
|
306
|
+
pytest
|
|
307
|
+
|
|
308
|
+
# Lint and type check
|
|
309
|
+
ruff check .
|
|
310
|
+
mypy tooli
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tooli"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "The agent-native CLI framework for Python"
|
|
9
|
+
readme = "pypi/pypi_project.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "tooli developers"}
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"typer>=0.9",
|
|
27
|
+
"pydantic>=2.0",
|
|
28
|
+
"rich>=13.0",
|
|
29
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.0",
|
|
35
|
+
"pytest-cov>=4.0",
|
|
36
|
+
"ruff>=0.4",
|
|
37
|
+
"mypy>=1.0",
|
|
38
|
+
]
|
|
39
|
+
mcp = [
|
|
40
|
+
"fastmcp>=2.0",
|
|
41
|
+
]
|
|
42
|
+
api = [
|
|
43
|
+
"starlette>=0.27",
|
|
44
|
+
"uvicorn>=0.20",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
include = ["tooli*"]
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/weisberg/tooli"
|
|
52
|
+
Repository = "https://github.com/weisberg/tooli"
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 88
|
|
56
|
+
target-version = "py310"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = ["E", "W", "F", "I", "UP", "B", "SIM", "TCH"]
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
python_version = "3.10"
|
|
63
|
+
strict = true
|
|
64
|
+
warn_return_any = true
|
|
65
|
+
warn_unused_configs = true
|
|
66
|
+
warn_unused_ignores = false
|
|
67
|
+
disallow_untyped_defs = true
|
|
68
|
+
|
|
69
|
+
[tool.pytest.ini_options]
|
|
70
|
+
testpaths = ["tests"]
|
|
71
|
+
addopts = "-v --tb=short"
|
tooli-1.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Tests for HTTP API integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from tooli import Tooli
|
|
8
|
+
from tooli.testing import TooliTestClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_api_export_openapi() -> None:
|
|
12
|
+
"""api export-openapi should output valid OpenAPI schema."""
|
|
13
|
+
app = Tooli(name="test-api")
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def compute(x: int, y: int) -> int:
|
|
17
|
+
"""Add two numbers."""
|
|
18
|
+
return x + y
|
|
19
|
+
|
|
20
|
+
client = TooliTestClient(app)
|
|
21
|
+
result = client.invoke(["api", "export-openapi"])
|
|
22
|
+
assert result.exit_code == 0
|
|
23
|
+
|
|
24
|
+
schema = json.loads(result.output)
|
|
25
|
+
assert schema["openapi"] == "3.1.0"
|
|
26
|
+
assert "/compute" in schema["paths"]
|
|
27
|
+
assert "x" in schema["paths"]["/compute"]["post"]["requestBody"]["content"]["application/json"]["schema"]["properties"]
|