agenticli 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.
- agenticli-0.1.0/.gitignore +12 -0
- agenticli-0.1.0/CHANGELOG.md +68 -0
- agenticli-0.1.0/LICENSE +21 -0
- agenticli-0.1.0/PKG-INFO +264 -0
- agenticli-0.1.0/README.md +248 -0
- agenticli-0.1.0/docs/index.md +14 -0
- agenticli-0.1.0/docs/index_en.md +385 -0
- agenticli-0.1.0/docs/index_zh.md +385 -0
- agenticli-0.1.0/example/demo.py +364 -0
- agenticli-0.1.0/example/direct_demo.py +691 -0
- agenticli-0.1.0/example/minimal_demo.py +111 -0
- agenticli-0.1.0/pyproject.toml +31 -0
- agenticli-0.1.0/src/llmcli/__init__.py +75 -0
- agenticli-0.1.0/src/llmcli/builtin.py +90 -0
- agenticli-0.1.0/src/llmcli/core.py +904 -0
- agenticli-0.1.0/src/llmcli/decorators.py +138 -0
- agenticli-0.1.0/src/llmcli/parser.py +281 -0
- agenticli-0.1.0/src/llmcli/tooling.py +313 -0
- agenticli-0.1.0/src/llmcli/types.py +271 -0
- agenticli-0.1.0/src/llmcli/validation.py +1010 -0
- agenticli-0.1.0/tests/test_command_registry.py +768 -0
- agenticli-0.1.0/tests/test_examples.py +131 -0
- agenticli-0.1.0/tests/test_generated_matrix.py +318 -0
- agenticli-0.1.0/uv.lock +441 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Bilingual documentation (English and Chinese)
|
|
12
|
+
- Comprehensive docstrings for all modules and public APIs
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Refactored `pyproject.toml` dependencies: core library now has zero required dependencies
|
|
16
|
+
- Updated minimum Python version requirement to 3.10 (from 3.12)
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
- Created `docs/index_en.md` - English documentation
|
|
20
|
+
- Created `docs/index_zh.md` - Chinese documentation
|
|
21
|
+
- Updated `README.md` with new documentation links
|
|
22
|
+
|
|
23
|
+
## [0.1.0] - Initial Release
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `CommandRegistry` as the core execution and help system
|
|
27
|
+
- Multiple command registration patterns:
|
|
28
|
+
- Function decorator (`@command`)
|
|
29
|
+
- Command groups (`@command_group`)
|
|
30
|
+
- Class inheritance (`CliCommand`)
|
|
31
|
+
- Dataclass-based (`command_from_model`)
|
|
32
|
+
- Schema-based tool wrapping (`wrap_tool`)
|
|
33
|
+
- Command parsing:
|
|
34
|
+
- Positional arguments
|
|
35
|
+
- Long options (`--option value`)
|
|
36
|
+
- Short options (`-o value`)
|
|
37
|
+
- Boolean flags
|
|
38
|
+
- Inline values (`--option=value`)
|
|
39
|
+
- Help system:
|
|
40
|
+
- Local command help
|
|
41
|
+
- Group help with subcommands
|
|
42
|
+
- Structured execution API:
|
|
43
|
+
- `ExecutionResult` with ok/value/error
|
|
44
|
+
- `CommandError` with code, message, hint, and suggestion
|
|
45
|
+
- Command suggestions:
|
|
46
|
+
- Unknown command suggestions (did you mean)
|
|
47
|
+
- Unknown option suggestions
|
|
48
|
+
- Enum value suggestions
|
|
49
|
+
- Command metadata:
|
|
50
|
+
- Hidden commands (excluded from help)
|
|
51
|
+
- Deprecated commands with notice
|
|
52
|
+
- Parameter features:
|
|
53
|
+
- Ordering and positioning control
|
|
54
|
+
- Example values for documentation
|
|
55
|
+
- Hidden parameters
|
|
56
|
+
- Repeatable parameters
|
|
57
|
+
- List/dict/tuple parameter handling
|
|
58
|
+
- `requires` / `excludes` validation
|
|
59
|
+
- Lifecycle callbacks (`ExecutionCallbacks`):
|
|
60
|
+
- `before_execute`
|
|
61
|
+
- `after_execute`
|
|
62
|
+
- `on_error`
|
|
63
|
+
- Internal parameter injection (`Injected`, `Callback`, `State`)
|
|
64
|
+
- Chain execution with operators (`&&`, `||`, `;`)
|
|
65
|
+
- Built-in `ExecTool` for shell command execution
|
|
66
|
+
|
|
67
|
+
[Unreleased]: https://github.com/your-repo/agenticli/compare/v0.1.0...HEAD
|
|
68
|
+
[0.1.0]: https://github.com/your-repo/agenticli/releases/tag/v0.1.0
|
agenticli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 agenticli
|
|
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.
|
agenticli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agenticli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Expose tools as lightweight CLI commands for LLM agents
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Provides-Extra: dev
|
|
8
|
+
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
|
|
9
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
10
|
+
Provides-Extra: examples
|
|
11
|
+
Requires-Dist: anthropic>=0.34; extra == 'examples'
|
|
12
|
+
Requires-Dist: openai>=2.32.0; extra == 'examples'
|
|
13
|
+
Provides-Extra: pydantic
|
|
14
|
+
Requires-Dist: pydantic<3,>=2; extra == 'pydantic'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
<div align="center">
|
|
18
|
+
|
|
19
|
+
# ⚡ agenticli
|
|
20
|
+
|
|
21
|
+
**Expose tools as lightweight CLI commands for LLM agents** 🔧✨
|
|
22
|
+
|
|
23
|
+
[](https://python.org)
|
|
24
|
+
[](LICENSE)
|
|
25
|
+
[](https://pypi.org/project/agenticli/)
|
|
26
|
+
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ✨ What is this?
|
|
32
|
+
|
|
33
|
+
**llmcli** converts functions, classes, and schema-based tools into a stable CLI semantic layer. Instead of flooding prompts with large schemas, LLMs just output a single command string.
|
|
34
|
+
|
|
35
|
+
> 💡 **Philosophy: bash is everything.** The command string is the most stable, restrained, and observable intermediate representation between LLMs and tool systems.
|
|
36
|
+
|
|
37
|
+
## 🎯 When to use llmcli?
|
|
38
|
+
|
|
39
|
+
| Scenario | llmcli helps? |
|
|
40
|
+
|----------|---------------|
|
|
41
|
+
| You have many tools/functions and need a unified interface for LLMs | ✅ |
|
|
42
|
+
| You don't want massive schemas injected into prompts | ✅ |
|
|
43
|
+
| You want models to see minimal hints, expanding via `--help` | ✅ |
|
|
44
|
+
| You want validation, help, errors, and lifecycle in one place | ✅ |
|
|
45
|
+
|
|
46
|
+
## 🚀 Quick Start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install llmcli
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from typing import Annotated
|
|
54
|
+
from llmcli import CommandRegistry, Option, command, command_group
|
|
55
|
+
|
|
56
|
+
@command_group(name="calc", description="Calculator commands")
|
|
57
|
+
class Calc:
|
|
58
|
+
@command(name="add", description="Add numbers")
|
|
59
|
+
def add(self,
|
|
60
|
+
values: Annotated[list[float], Option(positional=True, value_name="n")]
|
|
61
|
+
) -> dict:
|
|
62
|
+
return {"result": sum(values)}
|
|
63
|
+
|
|
64
|
+
registry = CommandRegistry()
|
|
65
|
+
registry.register(Calc)
|
|
66
|
+
|
|
67
|
+
# LLM sees this minimal prompt:
|
|
68
|
+
print(registry.get_llm_prompt())
|
|
69
|
+
# -> You can use the following CLI commands:
|
|
70
|
+
# calc: Calculator commands
|
|
71
|
+
|
|
72
|
+
# Execute:
|
|
73
|
+
registry.parse_and_execute("calc add 10 20 30")
|
|
74
|
+
# -> {"result": 60.0}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 🏗️ Core Architecture
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
81
|
+
│ LLM Output │
|
|
82
|
+
│ "calc add 10 20 30" │
|
|
83
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
84
|
+
│
|
|
85
|
+
▼
|
|
86
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
87
|
+
│ CommandRegistry │
|
|
88
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
|
|
89
|
+
│ │ Parse │─▶│ Validate │─▶│ Execute │─▶│ Result │ │
|
|
90
|
+
│ └──────────┘ └──────────┘ └──────────┘ └────────────┘ │
|
|
91
|
+
│ │
|
|
92
|
+
│ • Command hit/matching • Lifecycle callbacks │
|
|
93
|
+
│ • Help generation • Error with suggestions │
|
|
94
|
+
│ • Argument injection • Chain execution (&&, ||, ;) │
|
|
95
|
+
└─────────────────────────────────────────────────────────────┘
|
|
96
|
+
│
|
|
97
|
+
▼
|
|
98
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
99
|
+
│ Your Functions / Tools │
|
|
100
|
+
└─────────────────────────────────────────────────────────────┘
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 📋 Key Features
|
|
104
|
+
|
|
105
|
+
| Feature | Description |
|
|
106
|
+
|---------|-------------|
|
|
107
|
+
| 🔌 **Multiple Registrations** | Decorators, class inheritance, dataclass, Pydantic, schema wrapping |
|
|
108
|
+
| ⚡ **CLI Parsing** | Positional args, `--option value`, `-o value`, `--opt=val`, flags |
|
|
109
|
+
| 📖 **Smart Help** | Auto-generated usage, help text, LLM prompts |
|
|
110
|
+
| ✅ **Validation** | Type coercion, `requires`/`excludes`, enums |
|
|
111
|
+
| 💡 **Suggestions** | "Did you mean X?" for unknown commands/options/enums |
|
|
112
|
+
| 🔄 **Lifecycle Hooks** | `before_execute`, `after_execute`, `on_error` |
|
|
113
|
+
| 🏃 **Internal Injection** | Hide callbacks/state from CLI, inject at runtime |
|
|
114
|
+
| 🔗 **Chain Execution** | `cmd1 && cmd2 || cmd3 ; cmd4` |
|
|
115
|
+
|
|
116
|
+
## 📝 Registration Patterns
|
|
117
|
+
|
|
118
|
+
### 1️⃣ Decorator (Most Common)
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from typing import Annotated
|
|
122
|
+
from llmcli import command, Option
|
|
123
|
+
|
|
124
|
+
@command(name="ls", description="List directory")
|
|
125
|
+
def ls(
|
|
126
|
+
path: Annotated[str, Option(short='p', description="Directory path")],
|
|
127
|
+
verbose: Annotated[bool, Option(short='v')] = False,
|
|
128
|
+
) -> list[str]:
|
|
129
|
+
import os
|
|
130
|
+
return os.listdir(path)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 2️⃣ Command Group
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from llmcli import command_group, command
|
|
137
|
+
|
|
138
|
+
@command_group(name="db", description="Database operations")
|
|
139
|
+
class Database:
|
|
140
|
+
@command(description="Create database")
|
|
141
|
+
def create(self, name: str) -> None: ...
|
|
142
|
+
|
|
143
|
+
@command(description="Drop database")
|
|
144
|
+
def drop(self, name: str) -> None: ...
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 3️⃣ Wrap Existing Tools
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from llmcli import wrap_tool
|
|
151
|
+
|
|
152
|
+
class MyTool:
|
|
153
|
+
name = "my_tool"
|
|
154
|
+
description = "Does something"
|
|
155
|
+
parameters = {"type": "object", "properties": {"x": {"type": "int"}}}
|
|
156
|
+
async def execute(self, **kwargs): return kwargs
|
|
157
|
+
|
|
158
|
+
registry.register_spec(wrap_tool(MyTool()))
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 4️⃣ Class Inheritance
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from llmcli import CliCommand, CommandRegistry
|
|
165
|
+
|
|
166
|
+
class AddCommand(CliCommand):
|
|
167
|
+
name = "add"
|
|
168
|
+
description = "Add numbers"
|
|
169
|
+
args_model = AddArgs
|
|
170
|
+
|
|
171
|
+
async def run(self, **kwargs) -> dict:
|
|
172
|
+
return {"result": sum(kwargs["values"])}
|
|
173
|
+
|
|
174
|
+
registry.register(AddCommand())
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 🤖 LLM Integration
|
|
178
|
+
|
|
179
|
+
### Minimal Tool Schema
|
|
180
|
+
|
|
181
|
+
Expose only one `exec` tool to the LLM:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from llmcli import ExecTool
|
|
185
|
+
|
|
186
|
+
exec_tool = ExecTool(callback=registry.parse_and_execute)
|
|
187
|
+
# Tool schema: {name: "exec", params: {command: string, timeout?: int}}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Lifecycle Callbacks
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from llmcli import ExecutionCallbacks
|
|
194
|
+
|
|
195
|
+
def on_error(ctx):
|
|
196
|
+
print(f"Error: {ctx.error.code} - {ctx.error.message}")
|
|
197
|
+
|
|
198
|
+
registry = CommandRegistry(
|
|
199
|
+
callbacks=ExecutionCallbacks(on_error=on_error)
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Internal Parameter Injection
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from typing import Annotated
|
|
207
|
+
from llmcli import Callback, State, command
|
|
208
|
+
|
|
209
|
+
@command(name="process")
|
|
210
|
+
def process(
|
|
211
|
+
data: list[str],
|
|
212
|
+
cache: Annotated[object, State(factory=lambda ctx: load_cache())] = None,
|
|
213
|
+
):
|
|
214
|
+
# cache is injected automatically, hidden from CLI
|
|
215
|
+
return cached_transform(data, cache)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 📦 Stable API
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# Core
|
|
222
|
+
CommandRegistry
|
|
223
|
+
CommandRegistry.register(target)
|
|
224
|
+
CommandRegistry.register_spec(spec)
|
|
225
|
+
CommandRegistry.execute(command_str)
|
|
226
|
+
CommandRegistry.parse_and_execute(command_str)
|
|
227
|
+
CommandRegistry.render_help(command)
|
|
228
|
+
CommandRegistry.get_llm_prompt(detailed=False)
|
|
229
|
+
|
|
230
|
+
# Decorators
|
|
231
|
+
command(name=None, description="", aliases=None, hidden=False, deprecated=None)
|
|
232
|
+
command_group(name, description)
|
|
233
|
+
|
|
234
|
+
# Helpers
|
|
235
|
+
CliCommand
|
|
236
|
+
wrap_tool(tool)
|
|
237
|
+
command_from_model(name, model, handler)
|
|
238
|
+
command_from_method(name, target, method_name)
|
|
239
|
+
Option
|
|
240
|
+
Injected / Callback / State
|
|
241
|
+
ExecutionCallbacks
|
|
242
|
+
ExecTool
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## 💡 Examples
|
|
246
|
+
|
|
247
|
+
See [example/demo.py](example/demo.py) for a complete calc system with OpenAI/Anthropic integration:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
pip install "llmcli[examples]"
|
|
251
|
+
python -m example.demo --provider openai
|
|
252
|
+
python -m example.demo --provider anthropic
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## 📚 Documentation
|
|
256
|
+
|
|
257
|
+
| Language | Link |
|
|
258
|
+
|----------|------|
|
|
259
|
+
| 🇺🇸 English | [docs/index_en.md](docs/index_en.md) |
|
|
260
|
+
| 🇨🇳 中文 | [docs/index_zh.md](docs/index_zh.md) |
|
|
261
|
+
|
|
262
|
+
## 📄 License
|
|
263
|
+
|
|
264
|
+
MIT
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ agenticli
|
|
4
|
+
|
|
5
|
+
**Expose tools as lightweight CLI commands for LLM agents** 🔧✨
|
|
6
|
+
|
|
7
|
+
[](https://python.org)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://pypi.org/project/agenticli/)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ What is this?
|
|
16
|
+
|
|
17
|
+
**llmcli** converts functions, classes, and schema-based tools into a stable CLI semantic layer. Instead of flooding prompts with large schemas, LLMs just output a single command string.
|
|
18
|
+
|
|
19
|
+
> 💡 **Philosophy: bash is everything.** The command string is the most stable, restrained, and observable intermediate representation between LLMs and tool systems.
|
|
20
|
+
|
|
21
|
+
## 🎯 When to use llmcli?
|
|
22
|
+
|
|
23
|
+
| Scenario | llmcli helps? |
|
|
24
|
+
|----------|---------------|
|
|
25
|
+
| You have many tools/functions and need a unified interface for LLMs | ✅ |
|
|
26
|
+
| You don't want massive schemas injected into prompts | ✅ |
|
|
27
|
+
| You want models to see minimal hints, expanding via `--help` | ✅ |
|
|
28
|
+
| You want validation, help, errors, and lifecycle in one place | ✅ |
|
|
29
|
+
|
|
30
|
+
## 🚀 Quick Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install llmcli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from typing import Annotated
|
|
38
|
+
from llmcli import CommandRegistry, Option, command, command_group
|
|
39
|
+
|
|
40
|
+
@command_group(name="calc", description="Calculator commands")
|
|
41
|
+
class Calc:
|
|
42
|
+
@command(name="add", description="Add numbers")
|
|
43
|
+
def add(self,
|
|
44
|
+
values: Annotated[list[float], Option(positional=True, value_name="n")]
|
|
45
|
+
) -> dict:
|
|
46
|
+
return {"result": sum(values)}
|
|
47
|
+
|
|
48
|
+
registry = CommandRegistry()
|
|
49
|
+
registry.register(Calc)
|
|
50
|
+
|
|
51
|
+
# LLM sees this minimal prompt:
|
|
52
|
+
print(registry.get_llm_prompt())
|
|
53
|
+
# -> You can use the following CLI commands:
|
|
54
|
+
# calc: Calculator commands
|
|
55
|
+
|
|
56
|
+
# Execute:
|
|
57
|
+
registry.parse_and_execute("calc add 10 20 30")
|
|
58
|
+
# -> {"result": 60.0}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🏗️ Core Architecture
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
65
|
+
│ LLM Output │
|
|
66
|
+
│ "calc add 10 20 30" │
|
|
67
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
68
|
+
│
|
|
69
|
+
▼
|
|
70
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
71
|
+
│ CommandRegistry │
|
|
72
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
|
|
73
|
+
│ │ Parse │─▶│ Validate │─▶│ Execute │─▶│ Result │ │
|
|
74
|
+
│ └──────────┘ └──────────┘ └──────────┘ └────────────┘ │
|
|
75
|
+
│ │
|
|
76
|
+
│ • Command hit/matching • Lifecycle callbacks │
|
|
77
|
+
│ • Help generation • Error with suggestions │
|
|
78
|
+
│ • Argument injection • Chain execution (&&, ||, ;) │
|
|
79
|
+
└─────────────────────────────────────────────────────────────┘
|
|
80
|
+
│
|
|
81
|
+
▼
|
|
82
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
83
|
+
│ Your Functions / Tools │
|
|
84
|
+
└─────────────────────────────────────────────────────────────┘
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 📋 Key Features
|
|
88
|
+
|
|
89
|
+
| Feature | Description |
|
|
90
|
+
|---------|-------------|
|
|
91
|
+
| 🔌 **Multiple Registrations** | Decorators, class inheritance, dataclass, Pydantic, schema wrapping |
|
|
92
|
+
| ⚡ **CLI Parsing** | Positional args, `--option value`, `-o value`, `--opt=val`, flags |
|
|
93
|
+
| 📖 **Smart Help** | Auto-generated usage, help text, LLM prompts |
|
|
94
|
+
| ✅ **Validation** | Type coercion, `requires`/`excludes`, enums |
|
|
95
|
+
| 💡 **Suggestions** | "Did you mean X?" for unknown commands/options/enums |
|
|
96
|
+
| 🔄 **Lifecycle Hooks** | `before_execute`, `after_execute`, `on_error` |
|
|
97
|
+
| 🏃 **Internal Injection** | Hide callbacks/state from CLI, inject at runtime |
|
|
98
|
+
| 🔗 **Chain Execution** | `cmd1 && cmd2 || cmd3 ; cmd4` |
|
|
99
|
+
|
|
100
|
+
## 📝 Registration Patterns
|
|
101
|
+
|
|
102
|
+
### 1️⃣ Decorator (Most Common)
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from typing import Annotated
|
|
106
|
+
from llmcli import command, Option
|
|
107
|
+
|
|
108
|
+
@command(name="ls", description="List directory")
|
|
109
|
+
def ls(
|
|
110
|
+
path: Annotated[str, Option(short='p', description="Directory path")],
|
|
111
|
+
verbose: Annotated[bool, Option(short='v')] = False,
|
|
112
|
+
) -> list[str]:
|
|
113
|
+
import os
|
|
114
|
+
return os.listdir(path)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 2️⃣ Command Group
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from llmcli import command_group, command
|
|
121
|
+
|
|
122
|
+
@command_group(name="db", description="Database operations")
|
|
123
|
+
class Database:
|
|
124
|
+
@command(description="Create database")
|
|
125
|
+
def create(self, name: str) -> None: ...
|
|
126
|
+
|
|
127
|
+
@command(description="Drop database")
|
|
128
|
+
def drop(self, name: str) -> None: ...
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 3️⃣ Wrap Existing Tools
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from llmcli import wrap_tool
|
|
135
|
+
|
|
136
|
+
class MyTool:
|
|
137
|
+
name = "my_tool"
|
|
138
|
+
description = "Does something"
|
|
139
|
+
parameters = {"type": "object", "properties": {"x": {"type": "int"}}}
|
|
140
|
+
async def execute(self, **kwargs): return kwargs
|
|
141
|
+
|
|
142
|
+
registry.register_spec(wrap_tool(MyTool()))
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 4️⃣ Class Inheritance
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from llmcli import CliCommand, CommandRegistry
|
|
149
|
+
|
|
150
|
+
class AddCommand(CliCommand):
|
|
151
|
+
name = "add"
|
|
152
|
+
description = "Add numbers"
|
|
153
|
+
args_model = AddArgs
|
|
154
|
+
|
|
155
|
+
async def run(self, **kwargs) -> dict:
|
|
156
|
+
return {"result": sum(kwargs["values"])}
|
|
157
|
+
|
|
158
|
+
registry.register(AddCommand())
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 🤖 LLM Integration
|
|
162
|
+
|
|
163
|
+
### Minimal Tool Schema
|
|
164
|
+
|
|
165
|
+
Expose only one `exec` tool to the LLM:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from llmcli import ExecTool
|
|
169
|
+
|
|
170
|
+
exec_tool = ExecTool(callback=registry.parse_and_execute)
|
|
171
|
+
# Tool schema: {name: "exec", params: {command: string, timeout?: int}}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Lifecycle Callbacks
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from llmcli import ExecutionCallbacks
|
|
178
|
+
|
|
179
|
+
def on_error(ctx):
|
|
180
|
+
print(f"Error: {ctx.error.code} - {ctx.error.message}")
|
|
181
|
+
|
|
182
|
+
registry = CommandRegistry(
|
|
183
|
+
callbacks=ExecutionCallbacks(on_error=on_error)
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Internal Parameter Injection
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from typing import Annotated
|
|
191
|
+
from llmcli import Callback, State, command
|
|
192
|
+
|
|
193
|
+
@command(name="process")
|
|
194
|
+
def process(
|
|
195
|
+
data: list[str],
|
|
196
|
+
cache: Annotated[object, State(factory=lambda ctx: load_cache())] = None,
|
|
197
|
+
):
|
|
198
|
+
# cache is injected automatically, hidden from CLI
|
|
199
|
+
return cached_transform(data, cache)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 📦 Stable API
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
# Core
|
|
206
|
+
CommandRegistry
|
|
207
|
+
CommandRegistry.register(target)
|
|
208
|
+
CommandRegistry.register_spec(spec)
|
|
209
|
+
CommandRegistry.execute(command_str)
|
|
210
|
+
CommandRegistry.parse_and_execute(command_str)
|
|
211
|
+
CommandRegistry.render_help(command)
|
|
212
|
+
CommandRegistry.get_llm_prompt(detailed=False)
|
|
213
|
+
|
|
214
|
+
# Decorators
|
|
215
|
+
command(name=None, description="", aliases=None, hidden=False, deprecated=None)
|
|
216
|
+
command_group(name, description)
|
|
217
|
+
|
|
218
|
+
# Helpers
|
|
219
|
+
CliCommand
|
|
220
|
+
wrap_tool(tool)
|
|
221
|
+
command_from_model(name, model, handler)
|
|
222
|
+
command_from_method(name, target, method_name)
|
|
223
|
+
Option
|
|
224
|
+
Injected / Callback / State
|
|
225
|
+
ExecutionCallbacks
|
|
226
|
+
ExecTool
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## 💡 Examples
|
|
230
|
+
|
|
231
|
+
See [example/demo.py](example/demo.py) for a complete calc system with OpenAI/Anthropic integration:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pip install "llmcli[examples]"
|
|
235
|
+
python -m example.demo --provider openai
|
|
236
|
+
python -m example.demo --provider anthropic
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## 📚 Documentation
|
|
240
|
+
|
|
241
|
+
| Language | Link |
|
|
242
|
+
|----------|------|
|
|
243
|
+
| 🇺🇸 English | [docs/index_en.md](docs/index_en.md) |
|
|
244
|
+
| 🇨🇳 中文 | [docs/index_zh.md](docs/index_zh.md) |
|
|
245
|
+
|
|
246
|
+
## 📄 License
|
|
247
|
+
|
|
248
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# agenticli Documentation
|
|
2
|
+
|
|
3
|
+
This directory contains the documentation for agenticli.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- [index_en.md](index_en.md) - English documentation
|
|
8
|
+
- [index_zh.md](index_zh.md) - Chinese documentation (中文文档)
|
|
9
|
+
|
|
10
|
+
## Quick Links
|
|
11
|
+
|
|
12
|
+
For English documentation, see [index_en.md](index_en.md).
|
|
13
|
+
|
|
14
|
+
For Chinese documentation, see [index_zh.md](index_zh.md).
|