milo-cli 0.1.0__tar.gz → 0.2.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.
- {milo_cli-0.1.0 → milo_cli-0.2.0}/PKG-INFO +198 -139
- {milo_cli-0.1.0 → milo_cli-0.2.0}/README.md +191 -133
- milo_cli-0.2.0/pyproject.toml +189 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/__init__.py +86 -1
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/_child.py +50 -20
- milo_cli-0.2.0/src/milo/_cli_help.py +110 -0
- milo_cli-0.2.0/src/milo/_command_defs.py +220 -0
- milo_cli-0.2.0/src/milo/_compat.py +162 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/_errors.py +4 -3
- milo_cli-0.2.0/src/milo/_jsonrpc.py +37 -0
- milo_cli-0.2.0/src/milo/_mcp_router.py +55 -0
- milo_cli-0.2.0/src/milo/_types.py +510 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/app.py +149 -26
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/cli.py +10 -8
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/commands.py +312 -182
- milo_cli-0.2.0/src/milo/completions.py +203 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/config.py +163 -0
- milo_cli-0.2.0/src/milo/context.py +300 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/dev.py +2 -2
- milo_cli-0.2.0/src/milo/doctor.py +208 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/flow.py +3 -1
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/form.py +3 -2
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/gateway.py +143 -145
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/groups.py +13 -17
- milo_cli-0.2.0/src/milo/help.py +138 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/input/_platform.py +10 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/llms.py +27 -10
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/mcp.py +174 -65
- milo_cli-0.2.0/src/milo/pipeline.py +511 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/plugins.py +1 -1
- milo_cli-0.2.0/src/milo/reducers.py +147 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/registry.py +36 -14
- milo_cli-0.2.0/src/milo/schema.py +435 -0
- milo_cli-0.2.0/src/milo/state.py +1010 -0
- milo_cli-0.2.0/src/milo/templates/__init__.py +70 -0
- milo_cli-0.2.0/src/milo/templates/components/_defs.kida +224 -0
- milo_cli-0.2.0/src/milo/templates/components/command_list.kida +13 -0
- milo_cli-0.2.0/src/milo/templates/components/help_page.kida +51 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/templates/error.kida +4 -0
- milo_cli-0.2.0/src/milo/templates/form.kida +10 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/templates/help.kida +6 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/testing/_mcp.py +9 -7
- milo_cli-0.2.0/src/milo/theme.py +214 -0
- milo_cli-0.2.0/src/milo/version_check.py +126 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo_cli.egg-info/PKG-INFO +198 -139
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo_cli.egg-info/SOURCES.txt +25 -1
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo_cli.egg-info/requires.txt +1 -1
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_ai_native.py +610 -22
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_app.py +89 -0
- milo_cli-0.2.0/tests/test_bubbletea_patterns.py +634 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_child.py +96 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_cli.py +3 -1
- milo_cli-0.2.0/tests/test_commands_middleware.py +99 -0
- milo_cli-0.2.0/tests/test_compat.py +227 -0
- milo_cli-0.2.0/tests/test_completions.py +78 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_components.py +114 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_config.py +124 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_context.py +192 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_dev.py +2 -2
- milo_cli-0.2.0/tests/test_doctor.py +76 -0
- milo_cli-0.2.0/tests/test_effects.py +1819 -0
- milo_cli-0.2.0/tests/test_effects_stress.py +369 -0
- milo_cli-0.2.0/tests/test_gateway.py +467 -0
- milo_cli-0.2.0/tests/test_help.py +227 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_mcp_resources.py +3 -1
- milo_cli-0.2.0/tests/test_mcp_router.py +89 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_milo_init.py +50 -1
- milo_cli-0.2.0/tests/test_pipeline.py +614 -0
- milo_cli-0.2.0/tests/test_reducers.py +380 -0
- milo_cli-0.2.0/tests/test_schema_v2.py +546 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_templates.py +36 -0
- milo_cli-0.2.0/tests/test_theme.py +252 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_types.py +6 -1
- milo_cli-0.2.0/tests/test_version_check.py +103 -0
- milo_cli-0.1.0/pyproject.toml +0 -116
- milo_cli-0.1.0/src/milo/_types.py +0 -234
- milo_cli-0.1.0/src/milo/context.py +0 -81
- milo_cli-0.1.0/src/milo/help.py +0 -84
- milo_cli-0.1.0/src/milo/pipeline.py +0 -276
- milo_cli-0.1.0/src/milo/schema.py +0 -214
- milo_cli-0.1.0/src/milo/state.py +0 -229
- milo_cli-0.1.0/src/milo/templates/__init__.py +0 -38
- milo_cli-0.1.0/src/milo/templates/form.kida +0 -8
- milo_cli-0.1.0/tests/test_effects.py +0 -106
- milo_cli-0.1.0/tests/test_help.py +0 -99
- milo_cli-0.1.0/tests/test_pipeline.py +0 -299
- milo_cli-0.1.0/tests/test_schema_v2.py +0 -207
- {milo_cli-0.1.0 → milo_cli-0.2.0}/setup.cfg +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/_protocols.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/input/__init__.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/input/_reader.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/input/_sequences.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/middleware.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/observability.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/output.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/py.typed +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/streaming.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/templates/field_confirm.kida +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/templates/field_select.kida +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/templates/field_text.kida +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/templates/progress.kida +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/testing/__init__.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/testing/_record.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/testing/_replay.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo/testing/_snapshot.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo_cli.egg-info/dependency_links.txt +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo_cli.egg-info/entry_points.txt +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/src/milo_cli.egg-info/top_level.txt +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_errors.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_flow.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_form.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_groups.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_input.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_lazy.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_mcp_prompts.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_middleware.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_mount.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_observability.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_plugins.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_protocols.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_registry_v2.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_state.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_streaming.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_testing.py +0 -0
- {milo_cli-0.1.0 → milo_cli-0.2.0}/tests/test_testing_mcp.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: milo-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Template-driven CLI applications for free-threaded Python
|
|
5
5
|
License-Expression: MIT
|
|
6
|
-
Project-URL: Homepage, https://lbliii.github.io/milo/
|
|
7
|
-
Project-URL: Documentation, https://lbliii.github.io/milo/
|
|
8
|
-
Project-URL: Repository, https://github.com/lbliii/milo
|
|
9
|
-
Project-URL: Changelog, https://github.com/lbliii/milo/blob/main/CHANGELOG.md
|
|
6
|
+
Project-URL: Homepage, https://lbliii.github.io/milo-cli/
|
|
7
|
+
Project-URL: Documentation, https://lbliii.github.io/milo-cli/
|
|
8
|
+
Project-URL: Repository, https://github.com/lbliii/milo-cli
|
|
9
|
+
Project-URL: Changelog, https://github.com/lbliii/milo-cli/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/lbliii/milo-cli/issues
|
|
10
11
|
Keywords: cli,terminal,forms,free-threading,template,elm
|
|
11
12
|
Classifier: Development Status :: 3 - Alpha
|
|
12
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -17,7 +18,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
17
18
|
Classifier: Typing :: Typed
|
|
18
19
|
Requires-Python: >=3.14
|
|
19
20
|
Description-Content-Type: text/markdown
|
|
20
|
-
Requires-Dist: kida-templates>=0.
|
|
21
|
+
Requires-Dist: kida-templates>=0.4.0
|
|
21
22
|
Provides-Extra: docs
|
|
22
23
|
Requires-Dist: bengal>=0.2.6; extra == "docs"
|
|
23
24
|
Provides-Extra: yaml
|
|
@@ -27,77 +28,110 @@ Requires-Dist: watchfiles>=1.0; extra == "watch"
|
|
|
27
28
|
|
|
28
29
|
# ᗣᗣ Milo
|
|
29
30
|
|
|
30
|
-
[](https://pypi.org/project/milo/)
|
|
31
|
-
[](https://pypi.org/project/milo/)
|
|
31
|
+
[](https://pypi.org/project/milo-cli/)
|
|
32
|
+
[](https://github.com/lbliii/milo-cli/actions/workflows/ci.yml)
|
|
33
|
+
[](https://pypi.org/project/milo-cli/)
|
|
33
34
|
[](https://opensource.org/licenses/MIT)
|
|
34
35
|
|
|
35
|
-
**
|
|
36
|
+
**Build CLIs that humans and AI agents both use natively**
|
|
36
37
|
|
|
37
38
|
```python
|
|
38
|
-
from milo import
|
|
39
|
+
from milo import CLI
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
if state is None:
|
|
42
|
-
return {"count": 0}
|
|
43
|
-
if action.type == "@@KEY" and action.payload.char == " ":
|
|
44
|
-
return {**state, "count": state["count"] + 1}
|
|
45
|
-
return state
|
|
41
|
+
cli = CLI(name="deployer", description="Deploy services to environments")
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
@cli.command("deploy", description="Deploy a service", annotations={"destructiveHint": True})
|
|
44
|
+
def deploy(environment: str, service: str, version: str = "latest") -> dict:
|
|
45
|
+
"""Deploy a service to the specified environment."""
|
|
46
|
+
return {"status": "deployed", "environment": environment, "service": service, "version": version}
|
|
47
|
+
|
|
48
|
+
cli.run()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Three protocols from one decorator:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Human CLI
|
|
55
|
+
deployer deploy --environment production --service api
|
|
56
|
+
|
|
57
|
+
# MCP tool (AI agent calls this via JSON-RPC)
|
|
58
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"deploy","arguments":{"environment":"staging","service":"api"}}}' \
|
|
59
|
+
| deployer --mcp
|
|
60
|
+
|
|
61
|
+
# AI-readable discovery document
|
|
62
|
+
deployer --llms-txt
|
|
49
63
|
```
|
|
50
64
|
|
|
51
65
|
---
|
|
52
66
|
|
|
53
67
|
## What is Milo?
|
|
54
68
|
|
|
55
|
-
Milo is a framework
|
|
69
|
+
Milo is a Python framework where every CLI is simultaneously a terminal app, a command-line tool, and an MCP server. Write one function with type annotations and a docstring — Milo generates the argparse subcommand, the MCP tool schema, and the llms.txt entry automatically.
|
|
56
70
|
|
|
57
71
|
**Why people pick it:**
|
|
58
72
|
|
|
73
|
+
- **Every CLI is an MCP server** — `@cli.command` produces an argparse subcommand, MCP tool, and llms.txt entry from one function. AI agents discover and call your tools with zero extra code.
|
|
74
|
+
- **Dual-mode commands** — The same command shows an interactive UI when a human runs it, and returns structured JSON when an AI calls it via MCP.
|
|
75
|
+
- **Annotated schemas** — Type hints + `Annotated` constraints generate rich JSON Schema. Agents validate inputs before calling.
|
|
76
|
+
- **Streaming progress** — Commands that yield `Progress` objects stream notifications to MCP clients in real time.
|
|
59
77
|
- **Elm Architecture** — Immutable state, pure reducers, declarative views. Every state transition is explicit and testable.
|
|
60
|
-
- **Template-driven UI** — Render terminal output with Kida templates. Same syntax you use for HTML, now for CLI.
|
|
61
78
|
- **Free-threading ready** — Built for Python 3.14t (PEP 703). Sagas run on `ThreadPoolExecutor` with no GIL contention.
|
|
62
|
-
- **Declarative flows** — Chain multi-screen state machines with the `>>` operator. No manual navigation plumbing.
|
|
63
|
-
- **Built-in forms** — Text, select, confirm, and password fields with validation, keyboard navigation, and TTY fallback.
|
|
64
79
|
- **One runtime dependency** — Just `kida-templates`. No click, no rich, no curses.
|
|
65
80
|
|
|
66
81
|
## Use Milo For
|
|
67
82
|
|
|
83
|
+
- **AI agent toolchains** — Every CLI doubles as an MCP server; register multiple CLIs behind a single gateway
|
|
68
84
|
- **Interactive CLI tools** — Wizards, installers, configuration prompts, and guided workflows
|
|
85
|
+
- **Dual-mode commands** — Interactive when a human runs them, structured when an AI calls them
|
|
69
86
|
- **Multi-screen terminal apps** — Declarative flows with `>>` operator for screen-to-screen navigation
|
|
70
87
|
- **Forms and data collection** — Text, select, confirm, and password fields with validation
|
|
71
88
|
- **Dev tools with hot reload** — `milo dev` watches templates and live-reloads on change
|
|
72
89
|
- **Session recording and replay** — Record user sessions to JSONL, replay for debugging or CI regression tests
|
|
73
|
-
- **Styled terminal output** — Kida terminal templates with ANSI colors, progress bars, and live rendering
|
|
74
|
-
- **AI agent integration** — Every CLI is an MCP server; register multiple CLIs behind a single gateway
|
|
75
90
|
|
|
76
91
|
---
|
|
77
92
|
|
|
78
93
|
## Installation
|
|
79
94
|
|
|
80
95
|
```bash
|
|
81
|
-
pip install milo
|
|
96
|
+
pip install milo-cli
|
|
82
97
|
```
|
|
83
98
|
|
|
99
|
+
The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The `milo` console command is installed with the package.
|
|
100
|
+
|
|
84
101
|
Requires Python 3.14+
|
|
85
102
|
|
|
86
103
|
---
|
|
87
104
|
|
|
88
105
|
## Quick Start
|
|
89
106
|
|
|
107
|
+
### AI-Native CLI
|
|
108
|
+
|
|
109
|
+
| Function | Description |
|
|
110
|
+
|----------|-------------|
|
|
111
|
+
| `CLI(name, description, version)` | Create a CLI application |
|
|
112
|
+
| `@cli.command(name, description)` | Register a typed command |
|
|
113
|
+
| `cli.group(name, description)` | Create a command group |
|
|
114
|
+
| `cli.run()` | Parse args and dispatch |
|
|
115
|
+
| `cli.call("cmd", **kwargs)` | Programmatic invocation |
|
|
116
|
+
| `--mcp` | Run as MCP server |
|
|
117
|
+
| `--llms-txt` | Generate AI discovery doc |
|
|
118
|
+
| `--mcp-install` | Register in gateway |
|
|
119
|
+
| `annotations={...}` | MCP behavioral hints |
|
|
120
|
+
| `Annotated[str, MinLen(1)]` | Schema constraints |
|
|
121
|
+
|
|
122
|
+
### Interactive Apps
|
|
123
|
+
|
|
90
124
|
| Function | Description |
|
|
91
125
|
|----------|-------------|
|
|
92
126
|
| `App(template, reducer, initial_state)` | Create a single-screen app |
|
|
93
127
|
| `App.from_flow(flow)` | Create a multi-screen app from a `Flow` |
|
|
94
|
-
| `app.run()` | Run the event loop, return final state |
|
|
95
|
-
| `Store(reducer, initial_state)` | Standalone state container |
|
|
96
|
-
| `combine_reducers(**reducers)` | Compose slice-based reducers |
|
|
97
128
|
| `form(*specs)` | Run an interactive form, return `{field: value}` |
|
|
98
129
|
| `FlowScreen(name, template, reducer)` | Define a named screen |
|
|
99
130
|
| `flow = screen_a >> screen_b` | Chain screens into a flow |
|
|
100
|
-
| `
|
|
131
|
+
| `ctx.run_app(reducer, template, state)` | Bridge CLI commands to interactive apps |
|
|
132
|
+
| `quit_on`, `with_cursor`, `with_confirm` | Reducer combinator decorators |
|
|
133
|
+
| `Cmd(fn)`, `Batch(cmds)`, `Sequence(cmds)` | Side effects on thread pool |
|
|
134
|
+
| `ViewState(cursor_visible=True, ...)` | Declarative terminal state |
|
|
101
135
|
| `DevServer(app, watch_dirs)` | Hot-reload dev server |
|
|
102
136
|
|
|
103
137
|
---
|
|
@@ -106,26 +140,125 @@ Requires Python 3.14+
|
|
|
106
140
|
|
|
107
141
|
| Feature | Description | Docs |
|
|
108
142
|
|---------|-------------|------|
|
|
109
|
-
| **
|
|
110
|
-
| **
|
|
111
|
-
| **
|
|
112
|
-
| **
|
|
113
|
-
| **
|
|
114
|
-
| **
|
|
115
|
-
| **
|
|
116
|
-
| **
|
|
117
|
-
| **
|
|
118
|
-
| **
|
|
119
|
-
| **
|
|
120
|
-
| **
|
|
121
|
-
| **
|
|
122
|
-
| **
|
|
123
|
-
| **
|
|
143
|
+
| **MCP Server** | Every CLI doubles as an MCP server — AI agents discover and call commands via JSON-RPC | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
|
|
144
|
+
| **MCP Gateway** | Single gateway aggregates all registered Milo CLIs for unified AI agent access | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
|
|
145
|
+
| **Tool Annotations** | Declare `readOnlyHint`, `destructiveHint`, `idempotentHint` per MCP spec | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
|
|
146
|
+
| **Streaming Progress** | Commands yield `Progress` objects; MCP clients receive real-time notifications | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
|
|
147
|
+
| **Schema Constraints** | `Annotated[str, MinLen(1), MaxLen(100)]` generates rich JSON Schema | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
|
|
148
|
+
| **llms.txt** | Generate AI-readable discovery documents from CLI command definitions | [llms.txt →](https://lbliii.github.io/milo-cli/docs/usage/llms/) |
|
|
149
|
+
| **Middleware** | Intercept MCP calls and CLI commands for logging, auth, and transformation | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
|
|
150
|
+
| **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
|
|
151
|
+
| **State Management** | Redux-style `Store` with dispatch, listeners, middleware, and saga scheduling | [State →](https://lbliii.github.io/milo-cli/docs/usage/state/) |
|
|
152
|
+
| **Commands** | Lightweight `Cmd` thunks, `Batch`, `Sequence`, `TickCmd` for one-shot effects | [Commands →](https://lbliii.github.io/milo-cli/docs/usage/commands-effects/) |
|
|
153
|
+
| **Sagas** | Generator-based side effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry` | [Sagas →](https://lbliii.github.io/milo-cli/docs/usage/sagas/) |
|
|
154
|
+
| **ViewState** | Declarative terminal state (`cursor_visible`, `alt_screen`, `window_title`, `mouse_mode`) | [Commands →](https://lbliii.github.io/milo-cli/docs/usage/commands-effects/) |
|
|
155
|
+
| **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/usage/flows/) |
|
|
156
|
+
| **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/usage/forms/) |
|
|
157
|
+
| **Input Handling** | Cross-platform key reader with full escape sequence support (arrows, F-keys, modifiers) | [Input →](https://lbliii.github.io/milo-cli/docs/usage/input/) |
|
|
158
|
+
| **Templates** | Kida-powered terminal rendering with built-in form, field, help, and progress templates | [Templates →](https://lbliii.github.io/milo-cli/docs/usage/templates/) |
|
|
159
|
+
| **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/usage/dev/) |
|
|
160
|
+
| **Session Recording** | JSONL action log with state hashes for debugging and regression testing | [Testing →](https://lbliii.github.io/milo-cli/docs/usage/testing/) |
|
|
161
|
+
| **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/usage/testing/) |
|
|
162
|
+
| **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/usage/help/) |
|
|
163
|
+
| **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/usage/context/) |
|
|
164
|
+
| **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/usage/config/) |
|
|
165
|
+
| **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
|
|
166
|
+
| **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
|
|
124
167
|
|
|
125
168
|
---
|
|
126
169
|
|
|
127
170
|
## Usage
|
|
128
171
|
|
|
172
|
+
<details>
|
|
173
|
+
<summary><strong>Dual-Mode Commands</strong> — Interactive for humans, structured for AI</summary>
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from milo import CLI, Context, Action, Quit, SpecialKey
|
|
177
|
+
from milo.streaming import Progress
|
|
178
|
+
from typing import Annotated
|
|
179
|
+
from milo import MinLen
|
|
180
|
+
|
|
181
|
+
cli = CLI(name="deployer", description="Deploy services")
|
|
182
|
+
|
|
183
|
+
@cli.command("deploy", description="Deploy a service", annotations={"destructiveHint": True})
|
|
184
|
+
def deploy(
|
|
185
|
+
environment: Annotated[str, MinLen(1)],
|
|
186
|
+
service: Annotated[str, MinLen(1)],
|
|
187
|
+
ctx: Context = None,
|
|
188
|
+
) -> dict:
|
|
189
|
+
"""Deploy a service to an environment."""
|
|
190
|
+
# Interactive mode: show confirmation UI
|
|
191
|
+
if ctx and ctx.is_interactive:
|
|
192
|
+
if not ctx.confirm(f"Deploy {service} to {environment}?"):
|
|
193
|
+
return {"status": "cancelled"}
|
|
194
|
+
|
|
195
|
+
# Stream progress (MCP clients see real-time notifications)
|
|
196
|
+
yield Progress(status=f"Deploying {service}", step=0, total=2)
|
|
197
|
+
yield Progress(status="Verifying health", step=1, total=2)
|
|
198
|
+
|
|
199
|
+
return {"status": "deployed", "environment": environment, "service": service}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Run by a human: interactive confirmation, then progress output.
|
|
203
|
+
Called via MCP: progress notifications stream, then structured JSON result.
|
|
204
|
+
|
|
205
|
+
</details>
|
|
206
|
+
|
|
207
|
+
<details>
|
|
208
|
+
<summary><strong>MCP Server & Gateway</strong> — AI agent integration</summary>
|
|
209
|
+
|
|
210
|
+
Every Milo CLI is automatically an MCP server:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Run as MCP server (stdin/stdout JSON-RPC)
|
|
214
|
+
myapp --mcp
|
|
215
|
+
|
|
216
|
+
# Register with an AI host directly
|
|
217
|
+
claude mcp add myapp -- uv run python examples/deploy/app.py --mcp
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
For multiple CLIs, register them and run a single gateway:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Register CLIs
|
|
224
|
+
taskman --mcp-install
|
|
225
|
+
deployer --mcp-install
|
|
226
|
+
|
|
227
|
+
# Run the unified gateway
|
|
228
|
+
uv run python -m milo.gateway --mcp
|
|
229
|
+
|
|
230
|
+
# Or register the gateway with your AI host
|
|
231
|
+
claude mcp add milo -- uv run python -m milo.gateway --mcp
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The gateway namespaces tools automatically: `taskman.add`, `deployer.deploy`, etc. Implements MCP 2025-11-25 with `outputSchema`, `structuredContent`, tool `annotations`, and streaming `Progress` notifications.
|
|
235
|
+
|
|
236
|
+
Built-in `milo://stats` resource exposes request latency, error counts, and throughput.
|
|
237
|
+
|
|
238
|
+
</details>
|
|
239
|
+
|
|
240
|
+
<details>
|
|
241
|
+
<summary><strong>Schema Constraints</strong> — Rich validation from type hints</summary>
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from typing import Annotated
|
|
245
|
+
from milo import CLI, MinLen, MaxLen, Gt, Lt, Pattern, Description
|
|
246
|
+
|
|
247
|
+
cli = CLI(name="app")
|
|
248
|
+
|
|
249
|
+
@cli.command("create-user", description="Create a user account")
|
|
250
|
+
def create_user(
|
|
251
|
+
name: Annotated[str, MinLen(1), MaxLen(100), Description("Full name")],
|
|
252
|
+
age: Annotated[int, Gt(0), Lt(200)],
|
|
253
|
+
email: Annotated[str, Pattern(r"^[^@]+@[^@]+$")],
|
|
254
|
+
) -> dict:
|
|
255
|
+
return {"name": name, "age": age, "email": email}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Generates JSON Schema with `minLength`, `maxLength`, `exclusiveMinimum`, `exclusiveMaximum`, `pattern`, and `description` — AI agents validate inputs before calling.
|
|
259
|
+
|
|
260
|
+
</details>
|
|
261
|
+
|
|
129
262
|
<details>
|
|
130
263
|
<summary><strong>Single-Screen App</strong> — Counter with keyboard input</summary>
|
|
131
264
|
|
|
@@ -210,64 +343,20 @@ def reducer(state, action):
|
|
|
210
343
|
return state
|
|
211
344
|
```
|
|
212
345
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
</details>
|
|
216
|
-
|
|
217
|
-
<details>
|
|
218
|
-
<summary><strong>Middleware</strong> — Intercept and transform dispatches</summary>
|
|
219
|
-
|
|
220
|
-
```python
|
|
221
|
-
def logging_middleware(dispatch, get_state):
|
|
222
|
-
def wrapper(action):
|
|
223
|
-
print(f"Action: {action.type}")
|
|
224
|
-
return dispatch(action)
|
|
225
|
-
return wrapper
|
|
226
|
-
|
|
227
|
-
app = App(
|
|
228
|
-
template="app.kida",
|
|
229
|
-
reducer=reducer,
|
|
230
|
-
initial_state=None,
|
|
231
|
-
middleware=[logging_middleware],
|
|
232
|
-
)
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
</details>
|
|
236
|
-
|
|
237
|
-
<details>
|
|
238
|
-
<summary><strong>Dev Server</strong> — Hot reload templates</summary>
|
|
346
|
+
Saga effects: `Call(fn, args)`, `Put(action)`, `Select(selector)`, `Fork(saga)`, `Delay(seconds)`, `Retry(fn, ...)`.
|
|
239
347
|
|
|
240
|
-
|
|
241
|
-
# Watch templates and reload on change
|
|
242
|
-
milo dev myapp:app --watch ./templates --poll 0.25
|
|
243
|
-
```
|
|
348
|
+
For one-shot effects, use `Cmd` instead — no generator needed:
|
|
244
349
|
|
|
245
350
|
```python
|
|
246
|
-
from milo import
|
|
247
|
-
|
|
248
|
-
app = App(template="dashboard.kida", reducer=reducer, initial_state=None)
|
|
249
|
-
server = DevServer(app, watch_dirs=("./templates",), poll_interval=0.5)
|
|
250
|
-
server.run()
|
|
251
|
-
```
|
|
351
|
+
from milo import Cmd, ReducerResult
|
|
252
352
|
|
|
253
|
-
|
|
353
|
+
def fetch_status():
|
|
354
|
+
return Action("STATUS", payload=urllib.request.urlopen(url).status)
|
|
254
355
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
# Record a session
|
|
260
|
-
app = App(template="app.kida", reducer=reducer, initial_state=None, record=True)
|
|
261
|
-
app.run() # Writes to session.jsonl
|
|
262
|
-
|
|
263
|
-
# Replay for debugging
|
|
264
|
-
milo replay session.jsonl --speed 2.0 --diff
|
|
265
|
-
|
|
266
|
-
# CI regression: assert state hashes match
|
|
267
|
-
milo replay session.jsonl --assert --reducer myapp:reducer
|
|
268
|
-
|
|
269
|
-
# Step-by-step interactive replay
|
|
270
|
-
milo replay session.jsonl --step
|
|
356
|
+
def reducer(state, action):
|
|
357
|
+
if action.type == "CHECK":
|
|
358
|
+
return ReducerResult(state, cmds=(Cmd(fetch_status),))
|
|
359
|
+
return state
|
|
271
360
|
```
|
|
272
361
|
|
|
273
362
|
</details>
|
|
@@ -293,37 +382,6 @@ Set `MILO_UPDATE_SNAPSHOTS=1` to regenerate snapshot files.
|
|
|
293
382
|
|
|
294
383
|
</details>
|
|
295
384
|
|
|
296
|
-
<details>
|
|
297
|
-
<summary><strong>MCP Server & Gateway</strong> — AI agent integration</summary>
|
|
298
|
-
|
|
299
|
-
Every Milo CLI is automatically an MCP server:
|
|
300
|
-
|
|
301
|
-
```bash
|
|
302
|
-
# Run as MCP server (stdin/stdout JSON-RPC)
|
|
303
|
-
myapp --mcp
|
|
304
|
-
|
|
305
|
-
# Register with an AI host directly
|
|
306
|
-
claude mcp add myapp -- uv run python examples/taskman/app.py --mcp
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
For multiple CLIs, register them and run a single gateway:
|
|
310
|
-
|
|
311
|
-
```bash
|
|
312
|
-
# Register CLIs
|
|
313
|
-
taskman --mcp-install
|
|
314
|
-
ghub --mcp-install
|
|
315
|
-
|
|
316
|
-
# Run the unified gateway
|
|
317
|
-
uv run python -m milo.gateway --mcp
|
|
318
|
-
|
|
319
|
-
# Or register the gateway with your AI host
|
|
320
|
-
claude mcp add milo -- uv run python -m milo.gateway --mcp
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
The gateway namespaces tools automatically: `taskman.add`, `ghub.repo.list`, etc. Implements MCP 2025-11-25 with `outputSchema`, `structuredContent`, and tool `title` fields.
|
|
324
|
-
|
|
325
|
-
</details>
|
|
326
|
-
|
|
327
385
|
---
|
|
328
386
|
|
|
329
387
|
## Architecture
|
|
@@ -354,7 +412,7 @@ The gateway namespaces tools automatically: `taskman.add`, `ghub.repo.list`, etc
|
|
|
354
412
|
1. **Model** — Immutable state (plain dicts or frozen dataclasses)
|
|
355
413
|
2. **View** — Kida templates render state to terminal output
|
|
356
414
|
3. **Update** — Pure `reducer(state, action) -> state` functions
|
|
357
|
-
4. **Effects** —
|
|
415
|
+
4. **Effects** — `Cmd` thunks (one-shot) or generator-based sagas (multi-step) on `ThreadPoolExecutor`
|
|
358
416
|
|
|
359
417
|
</details>
|
|
360
418
|
|
|
@@ -397,22 +455,23 @@ App.run()
|
|
|
397
455
|
|
|
398
456
|
| Section | Description |
|
|
399
457
|
|---------|-------------|
|
|
400
|
-
| [Get Started](https://lbliii.github.io/milo/docs/get-started/) | Installation and quickstart |
|
|
401
|
-
| [
|
|
402
|
-
| [
|
|
403
|
-
| [
|
|
404
|
-
| [Reference](https://lbliii.github.io/milo/docs/reference/) | Complete API documentation |
|
|
458
|
+
| [Get Started](https://lbliii.github.io/milo-cli/docs/get-started/) | Installation and quickstart |
|
|
459
|
+
| [MCP & AI](https://lbliii.github.io/milo-cli/docs/usage/mcp/) | MCP server, gateway, annotations, streaming, and llms.txt |
|
|
460
|
+
| [Usage](https://lbliii.github.io/milo-cli/docs/usage/) | State, sagas, flows, forms, templates |
|
|
461
|
+
| [Testing](https://lbliii.github.io/milo-cli/docs/usage/testing/) | Snapshots, recording, replay |
|
|
462
|
+
| [Reference](https://lbliii.github.io/milo-cli/docs/reference/) | Complete API documentation |
|
|
405
463
|
|
|
406
464
|
---
|
|
407
465
|
|
|
408
466
|
## Development
|
|
409
467
|
|
|
410
468
|
```bash
|
|
411
|
-
git clone https://github.com/lbliii/milo.git
|
|
412
|
-
cd milo
|
|
469
|
+
git clone https://github.com/lbliii/milo-cli.git
|
|
470
|
+
cd milo-cli
|
|
413
471
|
# Uses Python 3.14t by default (.python-version)
|
|
414
472
|
uv sync --group dev --python 3.14t
|
|
415
|
-
PYTHON_GIL=0 uv run --python 3.14t pytest
|
|
473
|
+
PYTHON_GIL=0 uv run --python 3.14t pytest tests/
|
|
474
|
+
make ci # optional: ruff + ty + tests with coverage
|
|
416
475
|
```
|
|
417
476
|
|
|
418
477
|
---
|
|
@@ -430,7 +489,7 @@ A structured reactive stack — every layer written in pure Python for 3.14t fre
|
|
|
430
489
|
| **)彡** | [Kida](https://github.com/lbliii/kida) | Template engine | [Docs](https://lbliii.github.io/kida/) |
|
|
431
490
|
| **ฅᨐฅ** | [Patitas](https://github.com/lbliii/patitas) | Markdown parser | [Docs](https://lbliii.github.io/patitas/) |
|
|
432
491
|
| **⌾⌾⌾** | [Rosettes](https://github.com/lbliii/rosettes) | Syntax highlighter | [Docs](https://lbliii.github.io/rosettes/) |
|
|
433
|
-
| **ᗣᗣ** | **Milo** | CLI framework ← You are here | [Docs](https://lbliii.github.io/milo/) |
|
|
492
|
+
| **ᗣᗣ** | **Milo** (PyPI: `milo-cli`) | CLI framework ← You are here | [Docs](https://lbliii.github.io/milo-cli/) |
|
|
434
493
|
|
|
435
494
|
Python-native. Free-threading ready. No npm required.
|
|
436
495
|
|