milo-cli 0.2.2__tar.gz → 0.3.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.2.2 → milo_cli-0.3.0}/PKG-INFO +87 -34
- {milo_cli-0.2.2 → milo_cli-0.3.0}/README.md +84 -31
- {milo_cli-0.2.2 → milo_cli-0.3.0}/pyproject.toml +13 -5
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/__init__.py +3 -1
- milo_cli-0.3.0/src/milo/_cells.py +166 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_child.py +31 -12
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_compat.py +1 -1
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_errors.py +14 -2
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_jsonrpc.py +31 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_mcp_router.py +5 -1
- milo_cli-0.3.0/src/milo/_scaffold/__init__.py +55 -0
- milo_cli-0.3.0/src/milo/_scaffold/default/README.md +115 -0
- milo_cli-0.3.0/src/milo/_scaffold/default/app.py +29 -0
- milo_cli-0.3.0/src/milo/_scaffold/default/conftest.py +8 -0
- milo_cli-0.3.0/src/milo/_scaffold/default/tests/test_app.py +76 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/app.py +38 -9
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/cli.py +100 -1
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/commands.py +41 -8
- milo_cli-0.3.0/src/milo/components_cli.py +111 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/config.py +1 -1
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/dev.py +1 -1
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/flow.py +16 -4
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/form.py +134 -41
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/gateway.py +15 -15
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/groups.py +23 -1
- milo_cli-0.3.0/src/milo/live.py +15 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/llms.py +8 -3
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/mcp.py +117 -33
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/middleware.py +2 -2
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/output.py +8 -4
- milo_cli-0.3.0/src/milo/py.typed +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/registry.py +4 -2
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/schema.py +57 -5
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/state.py +62 -17
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/__init__.py +49 -2
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/components/_defs.kida +17 -12
- milo_cli-0.3.0/src/milo/templates/progress.kida +3 -0
- milo_cli-0.3.0/src/milo/verify.py +465 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/PKG-INFO +87 -34
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/SOURCES.txt +19 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/requires.txt +2 -2
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_ai_native.py +82 -2
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_app.py +17 -3
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_child.py +17 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_cli.py +63 -0
- milo_cli-0.3.0/tests/test_command_contract.py +173 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_commands_core.py +13 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_components.py +42 -0
- milo_cli-0.3.0/tests/test_docs_information_architecture.py +64 -0
- milo_cli-0.3.0/tests/test_docs_snippets.py +90 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_effects.py +12 -4
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_flow.py +24 -1
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_form.py +128 -1
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_gateway.py +31 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_input.py +14 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mcp_handler.py +32 -0
- milo_cli-0.3.0/tests/test_mcp_transport.py +37 -0
- milo_cli-0.3.0/tests/test_migration_docs.py +50 -0
- milo_cli-0.3.0/tests/test_outputgallery_example.py +258 -0
- milo_cli-0.3.0/tests/test_readme_example_index.py +41 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_reducers.py +2 -2
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_registry_v2.py +29 -1
- milo_cli-0.3.0/tests/test_scaffold.py +144 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_schema_v2.py +79 -9
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_state.py +97 -1
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_templates.py +77 -0
- milo_cli-0.3.0/tests/test_verify.py +241 -0
- milo_cli-0.2.2/src/milo/templates/progress.kida +0 -1
- {milo_cli-0.2.2 → milo_cli-0.3.0}/setup.cfg +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_cli_help.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_command_defs.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_protocols.py +0 -0
- /milo_cli-0.2.2/src/milo/py.typed → /milo_cli-0.3.0/src/milo/_scaffold/default/tests/__init__.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_types.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/completions.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/context.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/doctor.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/help.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/input/__init__.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/input/_platform.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/input/_reader.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/input/_sequences.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/observability.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/pipeline.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/plugins.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/reducers.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/streaming.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/components/command_list.kida +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/components/help_page.kida +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/error.kida +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/field_confirm.kida +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/field_select.kida +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/field_text.kida +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/form.kida +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/help.kida +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/__init__.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/_mcp.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/_record.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/_replay.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/_snapshot.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/theme.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/version_check.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/dependency_links.txt +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/entry_points.txt +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/top_level.txt +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_bubbletea_patterns.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_command_defs.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_commands_middleware.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_compat.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_completions.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_config.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_context.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_dev.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_doctor.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_effects_stress.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_errors.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_groups.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_help.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_lazy.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mcp_prompts.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mcp_resources.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mcp_router.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_middleware.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_milo_init.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mount.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_observability.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_pipeline.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_plugins.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_protocols.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_registry_crud.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_streaming.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_testing.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_testing_mcp.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_theme.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_types.py +0 -0
- {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_version_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: milo-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Template-driven CLI applications for free-threaded Python
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://lbliii.github.io/milo-cli/
|
|
@@ -18,9 +18,9 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
-
Requires-Dist: kida-templates
|
|
21
|
+
Requires-Dist: kida-templates<0.8.0,>=0.7.0
|
|
22
22
|
Provides-Extra: docs
|
|
23
|
-
Requires-Dist: bengal>=0.2
|
|
23
|
+
Requires-Dist: bengal>=0.3.2; extra == "docs"
|
|
24
24
|
Provides-Extra: yaml
|
|
25
25
|
Requires-Dist: pyyaml>=6.0; extra == "yaml"
|
|
26
26
|
Provides-Extra: watch
|
|
@@ -92,18 +92,22 @@ Milo is a Python framework where every CLI is simultaneously a terminal app, a c
|
|
|
92
92
|
|
|
93
93
|
## Installation
|
|
94
94
|
|
|
95
|
+
**Requires Python 3.14+.** If you don't have it: `uv python install 3.14`.
|
|
96
|
+
|
|
95
97
|
```bash
|
|
96
98
|
pip install milo-cli
|
|
97
99
|
```
|
|
98
100
|
|
|
99
101
|
The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The `milo` console command is installed with the package.
|
|
100
102
|
|
|
101
|
-
Requires Python 3.14+
|
|
102
|
-
|
|
103
103
|
---
|
|
104
104
|
|
|
105
105
|
## Quick Start
|
|
106
106
|
|
|
107
|
+
> **Coding agents**: jump to [`docs/agent-quickstart.md`](./docs/agent-quickstart.md)
|
|
108
|
+
> for a 5-minute walkthrough from `@cli.command` to a verified Claude MCP tool call.
|
|
109
|
+
> See also [`docs/testing.md`](./docs/testing.md) for the test template.
|
|
110
|
+
|
|
107
111
|
### AI-Native CLI
|
|
108
112
|
|
|
109
113
|
| Function | Description |
|
|
@@ -140,31 +144,79 @@ Requires Python 3.14+
|
|
|
140
144
|
|
|
141
145
|
| Feature | Description | Docs |
|
|
142
146
|
|---------|-------------|------|
|
|
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/
|
|
144
|
-
| **MCP Gateway** | Single gateway aggregates all registered Milo CLIs for unified AI agent access | [MCP →](https://lbliii.github.io/milo-cli/docs/
|
|
145
|
-
| **Tool Annotations** | Declare `readOnlyHint`, `destructiveHint`, `idempotentHint` per MCP spec | [MCP →](https://lbliii.github.io/milo-cli/docs/
|
|
146
|
-
| **Streaming Progress** | Commands yield `Progress` objects; MCP clients receive real-time notifications | [MCP →](https://lbliii.github.io/milo-cli/docs/
|
|
147
|
-
| **Schema Constraints** | `Annotated[str, MinLen(1), MaxLen(100)]` generates rich JSON Schema | [CLI →](https://lbliii.github.io/milo-cli/docs/
|
|
148
|
-
| **llms.txt** | Generate AI-readable discovery documents from CLI command definitions | [llms.txt →](https://lbliii.github.io/milo-cli/docs/
|
|
149
|
-
| **Middleware** | Intercept MCP calls and CLI commands for logging, auth, and transformation | [CLI →](https://lbliii.github.io/milo-cli/docs/
|
|
150
|
-
| **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/
|
|
151
|
-
| **State Management** | Redux-style `Store` with dispatch, listeners, middleware, and saga scheduling | [State →](https://lbliii.github.io/milo-cli/docs/
|
|
152
|
-
| **Commands** | Lightweight `Cmd` thunks, `Batch`, `Sequence`, `TickCmd` for one-shot effects | [Commands →](https://lbliii.github.io/milo-cli/docs/
|
|
153
|
-
| **Sagas** | Generator-based side effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Race`, `All`, `Take`, and more | [Sagas →](https://lbliii.github.io/milo-cli/docs/
|
|
154
|
-
| **ViewState** | Declarative terminal state (`cursor_visible`, `alt_screen`, `window_title`, `mouse_mode`) | [Commands →](https://lbliii.github.io/milo-cli/docs/
|
|
155
|
-
| **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/
|
|
156
|
-
| **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/
|
|
157
|
-
| **Input Handling** | Cross-platform key reader with
|
|
158
|
-
| **Templates** | Kida-powered terminal rendering with built-in form, field, help, and progress templates | [Templates →](https://lbliii.github.io/milo-cli/docs/
|
|
159
|
-
| **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/
|
|
160
|
-
| **Session Recording** | JSONL action log with state hashes for debugging and regression testing | [Testing →](https://lbliii.github.io/milo-cli/docs/
|
|
161
|
-
| **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/
|
|
162
|
-
| **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/
|
|
163
|
-
| **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/
|
|
164
|
-
| **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/
|
|
165
|
-
| **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/
|
|
166
|
-
| **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/
|
|
167
|
-
| **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/
|
|
147
|
+
| **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/build-clis/mcp/) |
|
|
148
|
+
| **MCP Gateway** | Single gateway aggregates all registered Milo CLIs for unified AI agent access | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
|
|
149
|
+
| **Tool Annotations** | Declare `readOnlyHint`, `destructiveHint`, `idempotentHint` per MCP spec | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
|
|
150
|
+
| **Streaming Progress** | Commands yield `Progress` objects; MCP clients receive real-time notifications | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
|
|
151
|
+
| **Schema Constraints** | `Annotated[str, MinLen(1), MaxLen(100)]` generates rich JSON Schema | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
|
|
152
|
+
| **llms.txt** | Generate AI-readable discovery documents from CLI command definitions | [llms.txt →](https://lbliii.github.io/milo-cli/docs/build-clis/llms/) |
|
|
153
|
+
| **Middleware** | Intercept MCP calls and CLI commands for logging, auth, and transformation | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
|
|
154
|
+
| **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
|
|
155
|
+
| **State Management** | Redux-style `Store` with dispatch, listeners, middleware, and saga scheduling | [State →](https://lbliii.github.io/milo-cli/docs/build-apps/state/) |
|
|
156
|
+
| **Commands** | Lightweight `Cmd` thunks, `Batch`, `Sequence`, `TickCmd` for one-shot effects | [Commands →](https://lbliii.github.io/milo-cli/docs/build-apps/commands-effects/) |
|
|
157
|
+
| **Sagas** | Generator-based side effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Race`, `All`, `Take`, and more | [Sagas →](https://lbliii.github.io/milo-cli/docs/build-apps/sagas/) |
|
|
158
|
+
| **ViewState** | Declarative terminal state (`cursor_visible`, `alt_screen`, `window_title`, `mouse_mode`) | [Commands →](https://lbliii.github.io/milo-cli/docs/build-apps/commands-effects/) |
|
|
159
|
+
| **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/build-apps/flows/) |
|
|
160
|
+
| **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/build-apps/forms/) |
|
|
161
|
+
| **Input Handling** | Cross-platform key reader with VT100/xterm escape sequence support (arrows, F-keys, modifiers) | [Input →](https://lbliii.github.io/milo-cli/docs/build-apps/input/) |
|
|
162
|
+
| **Templates** | Kida-powered terminal rendering with built-in form, field, help, and progress templates | [Templates →](https://lbliii.github.io/milo-cli/docs/build-apps/templates/) |
|
|
163
|
+
| **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/build-apps/dev/) |
|
|
164
|
+
| **Session Recording** | JSONL action log with state hashes for debugging and regression testing | [Testing →](https://lbliii.github.io/milo-cli/docs/quality/testing/) |
|
|
165
|
+
| **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/quality/testing/) |
|
|
166
|
+
| **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/quality/pipeline/) |
|
|
167
|
+
| **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/build-clis/help/) |
|
|
168
|
+
| **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/build-clis/context/) |
|
|
169
|
+
| **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/about/concepts/configuration/) |
|
|
170
|
+
| **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
|
|
171
|
+
| **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Examples Index
|
|
176
|
+
|
|
177
|
+
Pick the example closest to your use case, copy its `app.py`, and adapt. See [examples/README.md](examples/README.md) for run commands, copy paths, and tested starting points.
|
|
178
|
+
|
|
179
|
+
**CLIs (typed function → CLI + MCP + llms.txt)**
|
|
180
|
+
|
|
181
|
+
| What you want to build | Example | Key APIs |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| The simplest possible CLI | [examples/greet](examples/greet) | `CLI`, `@cli.command` |
|
|
184
|
+
| Dual-mode CLI ↔ MCP server (flagship) | [examples/deploy](examples/deploy) | `Annotated`, `MinLen`, `Context`, `Progress`, `--mcp` |
|
|
185
|
+
| Context injection, logging, progress, confirms | [examples/ctxdemo](examples/ctxdemo) | `Context`, `ctx.info`, `ctx.progress`, `ctx.confirm` |
|
|
186
|
+
| Nested command groups (`app repo list`) | [examples/groups](examples/groups) | `cli.group()`, `walk_commands` |
|
|
187
|
+
| Fast startup via deferred imports | [examples/lazyapp](examples/lazyapp) | `cli.lazy_command()` |
|
|
188
|
+
| Production CLI with hooks, completions, doctor | [examples/devtool](examples/devtool) | `run_doctor`, `before_run`/`after_run`, did-you-mean, completions |
|
|
189
|
+
| AI-native CLI surfacing tools + resources | [examples/taskman](examples/taskman) | `@command`, `@resource`, `--format`, `--llms-txt`, `--mcp` |
|
|
190
|
+
| Advanced terminal reports and diagnostics | [examples/outputgallery](examples/outputgallery) | `Context.render`, Kida templates, character maps, JSON output |
|
|
191
|
+
|
|
192
|
+
**Configuration, plugins, pipelines**
|
|
193
|
+
|
|
194
|
+
| What you want to build | Example | Key APIs |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| TOML config with profiles + overlays | [examples/configapp](examples/configapp) | `Config`, `ConfigSpec`, `Config.load`, `Config.validate` |
|
|
197
|
+
| Plugin system with hooks + listeners | [examples/pluggable](examples/pluggable) | `HookRegistry`, `define`, `on`, `invoke` |
|
|
198
|
+
| Multi-phase pipeline with deps + retries | [examples/buildpipe](examples/buildpipe) | `Pipeline`, `Phase`, `PhasePolicy`, `>>` |
|
|
199
|
+
|
|
200
|
+
**Interactive TUIs (`App` + reducer)**
|
|
201
|
+
|
|
202
|
+
| What you want to build | Example | Key APIs |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| The simplest TUI | [examples/counter](examples/counter) | `App.from_dir`, reducer combinators |
|
|
205
|
+
| Modal input with derived filtering | [examples/todo](examples/todo) | tuple state, `quit_on`, derived views |
|
|
206
|
+
| Tick-driven animation | [examples/stopwatch](examples/stopwatch) | `tick_rate`, `@@TICK`, `quit_on` |
|
|
207
|
+
| Scrollable viewport with saga I/O | [examples/filepicker](examples/filepicker) | viewport, sagas, frozen tuples |
|
|
208
|
+
| Multi-screen flow with forms | [examples/wizard](examples/wizard) | `Flow`, `FlowScreen`, `make_form_reducer`, `FieldSpec` |
|
|
209
|
+
|
|
210
|
+
**Async work (sagas + Cmd pattern)**
|
|
211
|
+
|
|
212
|
+
| What you want to build | Example | Key APIs |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| Sagas for async side effects | [examples/fetcher](examples/fetcher) | `Call`, `Put`, `Select`, `Retry` |
|
|
215
|
+
| Parallel concurrent work | [examples/downloader](examples/downloader) | `Fork`, `Call`, `Delay`, `Timeout` |
|
|
216
|
+
| Bubbletea-style Cmd thunks | [examples/spinner](examples/spinner) | `Cmd`, `Batch`, `TickCmd`, `ViewState` |
|
|
217
|
+
| Live rendering outside an App | [examples/liverender](examples/liverender) | `milo.live.LiveRenderer`, `Spinner`, `terminal_env` |
|
|
218
|
+
|
|
219
|
+
> Don't see your use case? Run `milo new <name>` to scaffold a fresh CLI with tests, then `milo verify app.py` to confirm it works.
|
|
168
220
|
|
|
169
221
|
---
|
|
170
222
|
|
|
@@ -456,11 +508,12 @@ App.run()
|
|
|
456
508
|
|
|
457
509
|
| Section | Description |
|
|
458
510
|
|---------|-------------|
|
|
511
|
+
| [About](https://lbliii.github.io/milo-cli/docs/about/) | Philosophy, architecture, concepts, and lifecycle |
|
|
459
512
|
| [Get Started](https://lbliii.github.io/milo-cli/docs/get-started/) | Installation and quickstart |
|
|
460
|
-
| [
|
|
461
|
-
| [
|
|
462
|
-
| [
|
|
463
|
-
| [Reference](https://lbliii.github.io/milo-cli/docs/reference/) |
|
|
513
|
+
| [Build CLIs](https://lbliii.github.io/milo-cli/docs/build-clis/) | Commands, groups, MCP, llms.txt, context, output, and help |
|
|
514
|
+
| [Build Apps](https://lbliii.github.io/milo-cli/docs/build-apps/) | State, reducers, templates, input, forms, flows, sagas, and live rendering |
|
|
515
|
+
| [Quality](https://lbliii.github.io/milo-cli/docs/quality/) | Testing, verification, debugging, and pipelines |
|
|
516
|
+
| [Reference](https://lbliii.github.io/milo-cli/docs/reference/) | Schema, dispatch, error codes, actions, and types |
|
|
464
517
|
|
|
465
518
|
---
|
|
466
519
|
|
|
@@ -64,18 +64,22 @@ Milo is a Python framework where every CLI is simultaneously a terminal app, a c
|
|
|
64
64
|
|
|
65
65
|
## Installation
|
|
66
66
|
|
|
67
|
+
**Requires Python 3.14+.** If you don't have it: `uv python install 3.14`.
|
|
68
|
+
|
|
67
69
|
```bash
|
|
68
70
|
pip install milo-cli
|
|
69
71
|
```
|
|
70
72
|
|
|
71
73
|
The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The `milo` console command is installed with the package.
|
|
72
74
|
|
|
73
|
-
Requires Python 3.14+
|
|
74
|
-
|
|
75
75
|
---
|
|
76
76
|
|
|
77
77
|
## Quick Start
|
|
78
78
|
|
|
79
|
+
> **Coding agents**: jump to [`docs/agent-quickstart.md`](./docs/agent-quickstart.md)
|
|
80
|
+
> for a 5-minute walkthrough from `@cli.command` to a verified Claude MCP tool call.
|
|
81
|
+
> See also [`docs/testing.md`](./docs/testing.md) for the test template.
|
|
82
|
+
|
|
79
83
|
### AI-Native CLI
|
|
80
84
|
|
|
81
85
|
| Function | Description |
|
|
@@ -112,31 +116,79 @@ Requires Python 3.14+
|
|
|
112
116
|
|
|
113
117
|
| Feature | Description | Docs |
|
|
114
118
|
|---------|-------------|------|
|
|
115
|
-
| **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/
|
|
116
|
-
| **MCP Gateway** | Single gateway aggregates all registered Milo CLIs for unified AI agent access | [MCP →](https://lbliii.github.io/milo-cli/docs/
|
|
117
|
-
| **Tool Annotations** | Declare `readOnlyHint`, `destructiveHint`, `idempotentHint` per MCP spec | [MCP →](https://lbliii.github.io/milo-cli/docs/
|
|
118
|
-
| **Streaming Progress** | Commands yield `Progress` objects; MCP clients receive real-time notifications | [MCP →](https://lbliii.github.io/milo-cli/docs/
|
|
119
|
-
| **Schema Constraints** | `Annotated[str, MinLen(1), MaxLen(100)]` generates rich JSON Schema | [CLI →](https://lbliii.github.io/milo-cli/docs/
|
|
120
|
-
| **llms.txt** | Generate AI-readable discovery documents from CLI command definitions | [llms.txt →](https://lbliii.github.io/milo-cli/docs/
|
|
121
|
-
| **Middleware** | Intercept MCP calls and CLI commands for logging, auth, and transformation | [CLI →](https://lbliii.github.io/milo-cli/docs/
|
|
122
|
-
| **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/
|
|
123
|
-
| **State Management** | Redux-style `Store` with dispatch, listeners, middleware, and saga scheduling | [State →](https://lbliii.github.io/milo-cli/docs/
|
|
124
|
-
| **Commands** | Lightweight `Cmd` thunks, `Batch`, `Sequence`, `TickCmd` for one-shot effects | [Commands →](https://lbliii.github.io/milo-cli/docs/
|
|
125
|
-
| **Sagas** | Generator-based side effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Race`, `All`, `Take`, and more | [Sagas →](https://lbliii.github.io/milo-cli/docs/
|
|
126
|
-
| **ViewState** | Declarative terminal state (`cursor_visible`, `alt_screen`, `window_title`, `mouse_mode`) | [Commands →](https://lbliii.github.io/milo-cli/docs/
|
|
127
|
-
| **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/
|
|
128
|
-
| **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/
|
|
129
|
-
| **Input Handling** | Cross-platform key reader with
|
|
130
|
-
| **Templates** | Kida-powered terminal rendering with built-in form, field, help, and progress templates | [Templates →](https://lbliii.github.io/milo-cli/docs/
|
|
131
|
-
| **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/
|
|
132
|
-
| **Session Recording** | JSONL action log with state hashes for debugging and regression testing | [Testing →](https://lbliii.github.io/milo-cli/docs/
|
|
133
|
-
| **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/
|
|
134
|
-
| **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/
|
|
135
|
-
| **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/
|
|
136
|
-
| **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/
|
|
137
|
-
| **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/
|
|
138
|
-
| **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/
|
|
139
|
-
| **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/
|
|
119
|
+
| **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/build-clis/mcp/) |
|
|
120
|
+
| **MCP Gateway** | Single gateway aggregates all registered Milo CLIs for unified AI agent access | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
|
|
121
|
+
| **Tool Annotations** | Declare `readOnlyHint`, `destructiveHint`, `idempotentHint` per MCP spec | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
|
|
122
|
+
| **Streaming Progress** | Commands yield `Progress` objects; MCP clients receive real-time notifications | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
|
|
123
|
+
| **Schema Constraints** | `Annotated[str, MinLen(1), MaxLen(100)]` generates rich JSON Schema | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
|
|
124
|
+
| **llms.txt** | Generate AI-readable discovery documents from CLI command definitions | [llms.txt →](https://lbliii.github.io/milo-cli/docs/build-clis/llms/) |
|
|
125
|
+
| **Middleware** | Intercept MCP calls and CLI commands for logging, auth, and transformation | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
|
|
126
|
+
| **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
|
|
127
|
+
| **State Management** | Redux-style `Store` with dispatch, listeners, middleware, and saga scheduling | [State →](https://lbliii.github.io/milo-cli/docs/build-apps/state/) |
|
|
128
|
+
| **Commands** | Lightweight `Cmd` thunks, `Batch`, `Sequence`, `TickCmd` for one-shot effects | [Commands →](https://lbliii.github.io/milo-cli/docs/build-apps/commands-effects/) |
|
|
129
|
+
| **Sagas** | Generator-based side effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Race`, `All`, `Take`, and more | [Sagas →](https://lbliii.github.io/milo-cli/docs/build-apps/sagas/) |
|
|
130
|
+
| **ViewState** | Declarative terminal state (`cursor_visible`, `alt_screen`, `window_title`, `mouse_mode`) | [Commands →](https://lbliii.github.io/milo-cli/docs/build-apps/commands-effects/) |
|
|
131
|
+
| **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/build-apps/flows/) |
|
|
132
|
+
| **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/build-apps/forms/) |
|
|
133
|
+
| **Input Handling** | Cross-platform key reader with VT100/xterm escape sequence support (arrows, F-keys, modifiers) | [Input →](https://lbliii.github.io/milo-cli/docs/build-apps/input/) |
|
|
134
|
+
| **Templates** | Kida-powered terminal rendering with built-in form, field, help, and progress templates | [Templates →](https://lbliii.github.io/milo-cli/docs/build-apps/templates/) |
|
|
135
|
+
| **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/build-apps/dev/) |
|
|
136
|
+
| **Session Recording** | JSONL action log with state hashes for debugging and regression testing | [Testing →](https://lbliii.github.io/milo-cli/docs/quality/testing/) |
|
|
137
|
+
| **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/quality/testing/) |
|
|
138
|
+
| **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/quality/pipeline/) |
|
|
139
|
+
| **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/build-clis/help/) |
|
|
140
|
+
| **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/build-clis/context/) |
|
|
141
|
+
| **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/about/concepts/configuration/) |
|
|
142
|
+
| **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
|
|
143
|
+
| **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Examples Index
|
|
148
|
+
|
|
149
|
+
Pick the example closest to your use case, copy its `app.py`, and adapt. See [examples/README.md](examples/README.md) for run commands, copy paths, and tested starting points.
|
|
150
|
+
|
|
151
|
+
**CLIs (typed function → CLI + MCP + llms.txt)**
|
|
152
|
+
|
|
153
|
+
| What you want to build | Example | Key APIs |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| The simplest possible CLI | [examples/greet](examples/greet) | `CLI`, `@cli.command` |
|
|
156
|
+
| Dual-mode CLI ↔ MCP server (flagship) | [examples/deploy](examples/deploy) | `Annotated`, `MinLen`, `Context`, `Progress`, `--mcp` |
|
|
157
|
+
| Context injection, logging, progress, confirms | [examples/ctxdemo](examples/ctxdemo) | `Context`, `ctx.info`, `ctx.progress`, `ctx.confirm` |
|
|
158
|
+
| Nested command groups (`app repo list`) | [examples/groups](examples/groups) | `cli.group()`, `walk_commands` |
|
|
159
|
+
| Fast startup via deferred imports | [examples/lazyapp](examples/lazyapp) | `cli.lazy_command()` |
|
|
160
|
+
| Production CLI with hooks, completions, doctor | [examples/devtool](examples/devtool) | `run_doctor`, `before_run`/`after_run`, did-you-mean, completions |
|
|
161
|
+
| AI-native CLI surfacing tools + resources | [examples/taskman](examples/taskman) | `@command`, `@resource`, `--format`, `--llms-txt`, `--mcp` |
|
|
162
|
+
| Advanced terminal reports and diagnostics | [examples/outputgallery](examples/outputgallery) | `Context.render`, Kida templates, character maps, JSON output |
|
|
163
|
+
|
|
164
|
+
**Configuration, plugins, pipelines**
|
|
165
|
+
|
|
166
|
+
| What you want to build | Example | Key APIs |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| TOML config with profiles + overlays | [examples/configapp](examples/configapp) | `Config`, `ConfigSpec`, `Config.load`, `Config.validate` |
|
|
169
|
+
| Plugin system with hooks + listeners | [examples/pluggable](examples/pluggable) | `HookRegistry`, `define`, `on`, `invoke` |
|
|
170
|
+
| Multi-phase pipeline with deps + retries | [examples/buildpipe](examples/buildpipe) | `Pipeline`, `Phase`, `PhasePolicy`, `>>` |
|
|
171
|
+
|
|
172
|
+
**Interactive TUIs (`App` + reducer)**
|
|
173
|
+
|
|
174
|
+
| What you want to build | Example | Key APIs |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| The simplest TUI | [examples/counter](examples/counter) | `App.from_dir`, reducer combinators |
|
|
177
|
+
| Modal input with derived filtering | [examples/todo](examples/todo) | tuple state, `quit_on`, derived views |
|
|
178
|
+
| Tick-driven animation | [examples/stopwatch](examples/stopwatch) | `tick_rate`, `@@TICK`, `quit_on` |
|
|
179
|
+
| Scrollable viewport with saga I/O | [examples/filepicker](examples/filepicker) | viewport, sagas, frozen tuples |
|
|
180
|
+
| Multi-screen flow with forms | [examples/wizard](examples/wizard) | `Flow`, `FlowScreen`, `make_form_reducer`, `FieldSpec` |
|
|
181
|
+
|
|
182
|
+
**Async work (sagas + Cmd pattern)**
|
|
183
|
+
|
|
184
|
+
| What you want to build | Example | Key APIs |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| Sagas for async side effects | [examples/fetcher](examples/fetcher) | `Call`, `Put`, `Select`, `Retry` |
|
|
187
|
+
| Parallel concurrent work | [examples/downloader](examples/downloader) | `Fork`, `Call`, `Delay`, `Timeout` |
|
|
188
|
+
| Bubbletea-style Cmd thunks | [examples/spinner](examples/spinner) | `Cmd`, `Batch`, `TickCmd`, `ViewState` |
|
|
189
|
+
| Live rendering outside an App | [examples/liverender](examples/liverender) | `milo.live.LiveRenderer`, `Spinner`, `terminal_env` |
|
|
190
|
+
|
|
191
|
+
> Don't see your use case? Run `milo new <name>` to scaffold a fresh CLI with tests, then `milo verify app.py` to confirm it works.
|
|
140
192
|
|
|
141
193
|
---
|
|
142
194
|
|
|
@@ -428,11 +480,12 @@ App.run()
|
|
|
428
480
|
|
|
429
481
|
| Section | Description |
|
|
430
482
|
|---------|-------------|
|
|
483
|
+
| [About](https://lbliii.github.io/milo-cli/docs/about/) | Philosophy, architecture, concepts, and lifecycle |
|
|
431
484
|
| [Get Started](https://lbliii.github.io/milo-cli/docs/get-started/) | Installation and quickstart |
|
|
432
|
-
| [
|
|
433
|
-
| [
|
|
434
|
-
| [
|
|
435
|
-
| [Reference](https://lbliii.github.io/milo-cli/docs/reference/) |
|
|
485
|
+
| [Build CLIs](https://lbliii.github.io/milo-cli/docs/build-clis/) | Commands, groups, MCP, llms.txt, context, output, and help |
|
|
486
|
+
| [Build Apps](https://lbliii.github.io/milo-cli/docs/build-apps/) | State, reducers, templates, input, forms, flows, sagas, and live rendering |
|
|
487
|
+
| [Quality](https://lbliii.github.io/milo-cli/docs/quality/) | Testing, verification, debugging, and pipelines |
|
|
488
|
+
| [Reference](https://lbliii.github.io/milo-cli/docs/reference/) | Schema, dispatch, error codes, actions, and types |
|
|
436
489
|
|
|
437
490
|
---
|
|
438
491
|
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "milo-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Template-driven CLI applications for free-threaded Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14"
|
|
11
11
|
license = "MIT"
|
|
12
|
-
dependencies = ["kida-templates>=0.
|
|
12
|
+
dependencies = ["kida-templates>=0.7.0,<0.8.0"]
|
|
13
13
|
keywords = ["cli", "terminal", "forms", "free-threading", "template", "elm"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
milo = "milo.cli:main"
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
|
-
docs = ["bengal>=0.2
|
|
28
|
+
docs = ["bengal>=0.3.2"]
|
|
29
29
|
yaml = ["pyyaml>=6.0"]
|
|
30
30
|
watch = ["watchfiles>=1.0"]
|
|
31
31
|
|
|
@@ -40,7 +40,13 @@ Issues = "https://github.com/lbliii/milo-cli/issues"
|
|
|
40
40
|
where = ["src"]
|
|
41
41
|
|
|
42
42
|
[tool.setuptools.package-data]
|
|
43
|
-
milo = [
|
|
43
|
+
milo = [
|
|
44
|
+
"templates/*.kida",
|
|
45
|
+
"templates/components/*.kida",
|
|
46
|
+
"_scaffold/default/*",
|
|
47
|
+
"_scaffold/default/**/*",
|
|
48
|
+
"py.typed",
|
|
49
|
+
]
|
|
44
50
|
|
|
45
51
|
[tool.pytest.ini_options]
|
|
46
52
|
testpaths = ["tests"]
|
|
@@ -124,6 +130,8 @@ ignore = [
|
|
|
124
130
|
[tool.ruff.lint.per-file-ignores]
|
|
125
131
|
"__init__.py" = ["F401"]
|
|
126
132
|
"tests/**/*.py" = ["S101", "S108", "S110", "S604", "SIM117", "N806", "ARG", "T201"]
|
|
133
|
+
"examples/**/*.py" = ["S101", "S108", "S110", "S604", "SIM117", "N806", "ARG", "T201", "E402"]
|
|
134
|
+
"src/milo/_scaffold/default/**/*.py" = ["S101", "I001", "F401", "ARG", "T201", "E402"]
|
|
127
135
|
"src/milo/_child.py" = ["S101"] # asserts for type narrowing on subprocess pipes
|
|
128
136
|
"src/milo/testing/*.py" = ["S101", "ARG"] # test helpers use assert and may have unused args
|
|
129
137
|
"src/milo/version_check.py" = ["S110", "S310"] # best-effort cache reads + known PyPI URL
|
|
@@ -188,5 +196,5 @@ dev = [
|
|
|
188
196
|
"towncrier>=24.0",
|
|
189
197
|
]
|
|
190
198
|
docs = [
|
|
191
|
-
"bengal>=0.2
|
|
199
|
+
"bengal>=0.3.2",
|
|
192
200
|
]
|
|
@@ -68,6 +68,7 @@ def __getattr__(name: str):
|
|
|
68
68
|
# Form
|
|
69
69
|
"form": "form",
|
|
70
70
|
"form_reducer": "form",
|
|
71
|
+
"form_schema": "form",
|
|
71
72
|
"make_form_reducer": "form",
|
|
72
73
|
# Help
|
|
73
74
|
"HelpRenderer": "help",
|
|
@@ -160,7 +161,7 @@ def _Py_mod_gil() -> int: # noqa: N802
|
|
|
160
161
|
return 0
|
|
161
162
|
|
|
162
163
|
|
|
163
|
-
__version__ = "0.
|
|
164
|
+
__version__ = "0.3.0"
|
|
164
165
|
__all__ = [
|
|
165
166
|
"BUILTIN_ACTIONS",
|
|
166
167
|
"CLI",
|
|
@@ -256,6 +257,7 @@ __all__ = [
|
|
|
256
257
|
"compact_cmds",
|
|
257
258
|
"form",
|
|
258
259
|
"form_reducer",
|
|
260
|
+
"form_schema",
|
|
259
261
|
"format_doctor_report",
|
|
260
262
|
"format_error",
|
|
261
263
|
"format_output",
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Terminal display-cell width helpers.
|
|
2
|
+
|
|
3
|
+
Python string length counts code points, but terminals lay text out in display
|
|
4
|
+
cells. Box drawing, CJK characters, combining marks, and ANSI escapes all make
|
|
5
|
+
``len(text)`` the wrong primitive for fixed-width terminal UI.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import unicodedata
|
|
12
|
+
|
|
13
|
+
_ANSI_RE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def strip_ansi(value: object) -> str:
|
|
17
|
+
"""Return *value* as text with ANSI SGR/control sequences removed."""
|
|
18
|
+
return _ANSI_RE.sub("", str(value))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cell_width(value: object) -> int:
|
|
22
|
+
"""Return the terminal display-cell width of *value*.
|
|
23
|
+
|
|
24
|
+
This intentionally implements the common wcwidth behavior we need without
|
|
25
|
+
adding a runtime dependency: combining/control characters are zero-width,
|
|
26
|
+
fullwidth/wide East Asian characters are two cells, and ambiguous-width
|
|
27
|
+
symbols are treated as one cell for Western terminal defaults.
|
|
28
|
+
"""
|
|
29
|
+
width = 0
|
|
30
|
+
for ch in strip_ansi(value):
|
|
31
|
+
category = unicodedata.category(ch)
|
|
32
|
+
if category.startswith("C") or unicodedata.combining(ch):
|
|
33
|
+
continue
|
|
34
|
+
width += 2 if unicodedata.east_asian_width(ch) in {"F", "W"} else 1
|
|
35
|
+
return width
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cell_ljust(value: object, width: int, fill: str = " ") -> str:
|
|
39
|
+
"""Left-justify *value* to display-cell *width*."""
|
|
40
|
+
text = str(value)
|
|
41
|
+
return text + fill * max(0, width - cell_width(text))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def cell_rjust(value: object, width: int, fill: str = " ") -> str:
|
|
45
|
+
"""Right-justify *value* to display-cell *width*."""
|
|
46
|
+
text = str(value)
|
|
47
|
+
return fill * max(0, width - cell_width(text)) + text
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def cell_truncate(value: object, width: int, marker: str = "…") -> str:
|
|
51
|
+
"""Truncate *value* to display-cell *width*, preserving ANSI-free text."""
|
|
52
|
+
text = strip_ansi(value)
|
|
53
|
+
if cell_width(text) <= width:
|
|
54
|
+
return text
|
|
55
|
+
if width <= 0:
|
|
56
|
+
return ""
|
|
57
|
+
marker_width = cell_width(marker)
|
|
58
|
+
if width <= marker_width:
|
|
59
|
+
return marker[:width]
|
|
60
|
+
|
|
61
|
+
budget = width - marker_width
|
|
62
|
+
out: list[str] = []
|
|
63
|
+
used = 0
|
|
64
|
+
for ch in text:
|
|
65
|
+
ch_width = cell_width(ch)
|
|
66
|
+
if used + ch_width > budget:
|
|
67
|
+
break
|
|
68
|
+
out.append(ch)
|
|
69
|
+
used += ch_width
|
|
70
|
+
return "".join(out) + marker
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def cell_fit(value: object, width: int, fill: str = " ", marker: str = "…") -> str:
|
|
74
|
+
"""Truncate then left-pad *value* so it occupies exactly *width* cells."""
|
|
75
|
+
return cell_ljust(cell_truncate(value, width, marker=marker), width, fill=fill)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def cell_fill(width: int, fill: str = " ") -> str:
|
|
79
|
+
"""Return *fill* repeated/truncated to exactly *width* display cells."""
|
|
80
|
+
if width <= 0:
|
|
81
|
+
return ""
|
|
82
|
+
fill_width = cell_width(fill)
|
|
83
|
+
if fill_width <= 0:
|
|
84
|
+
return " " * width
|
|
85
|
+
repeated = str(fill) * ((width // fill_width) + 1)
|
|
86
|
+
return cell_truncate(repeated, width, marker="")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _rule_tail(width: int, fill: str = "─", tail: str = "╌┄") -> str:
|
|
90
|
+
"""Return a fixed-width rule that fades instead of ending abruptly."""
|
|
91
|
+
tail_width = cell_width(tail)
|
|
92
|
+
if width > tail_width + 8:
|
|
93
|
+
return cell_fill(width - tail_width, fill) + tail
|
|
94
|
+
return cell_fill(width, fill)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def rule_line(
|
|
98
|
+
value: object = "",
|
|
99
|
+
width: int = 78,
|
|
100
|
+
left: str = "╭",
|
|
101
|
+
right: str = "╮",
|
|
102
|
+
fill: str = "─",
|
|
103
|
+
tail: str = "",
|
|
104
|
+
) -> str:
|
|
105
|
+
"""Return a display-cell exact horizontal rule."""
|
|
106
|
+
label = str(value)
|
|
107
|
+
prefix = f"{left}─ {label} " if label else left
|
|
108
|
+
suffix_width = cell_width(right)
|
|
109
|
+
body_width = width - suffix_width
|
|
110
|
+
prefix_width = cell_width(prefix)
|
|
111
|
+
if body_width <= 0:
|
|
112
|
+
return cell_truncate(right, width, marker="")
|
|
113
|
+
if prefix_width > body_width:
|
|
114
|
+
return cell_fit(cell_truncate(prefix, body_width, marker="…"), body_width) + right
|
|
115
|
+
if tail:
|
|
116
|
+
return prefix + _rule_tail(body_width - prefix_width, fill=fill, tail=tail) + right
|
|
117
|
+
return prefix + cell_fill(body_width - prefix_width, fill) + right
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def divider_line(value: object = "", width: int = 78) -> str:
|
|
121
|
+
"""Return a display-cell exact closed-box divider."""
|
|
122
|
+
return rule_line(value, width=width, left="├", right="┤")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def bottom_rule(value: object = "", width: int = 78) -> str:
|
|
126
|
+
"""Return a display-cell exact closed-box bottom rule."""
|
|
127
|
+
return rule_line(value, width=width, left="╰", right="╯")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def frame_line(value: object = "", width: int = 78, left: str = "│", right: str = "│") -> str:
|
|
131
|
+
"""Return a display-cell exact framed content row."""
|
|
132
|
+
content_width = width - cell_width(left) - cell_width(right) - 2
|
|
133
|
+
if content_width <= 0:
|
|
134
|
+
return cell_truncate(left + right, width, marker="")
|
|
135
|
+
return f"{left} {cell_fit(value, content_width)} {right}"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def rail_line(value: object = "", width: int = 78, rail: str = "│") -> str:
|
|
139
|
+
"""Return a display-cell exact open-card row with one left rail."""
|
|
140
|
+
content_width = width - cell_width(rail) - 1
|
|
141
|
+
if content_width <= 0:
|
|
142
|
+
return cell_truncate(rail, width, marker="")
|
|
143
|
+
return f"{rail} {cell_fit(value, content_width)}"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def open_rule(value: object = "", width: int = 78, corner: str = "╭") -> str:
|
|
147
|
+
"""Return a cell-width exact open-card rule with a fading right edge."""
|
|
148
|
+
return rule_line(value, width=width, left=corner, right="", tail="╌┄")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def open_rule_divider(value: object = "", width: int = 78) -> str:
|
|
152
|
+
"""Return a cell-width exact open-card section divider."""
|
|
153
|
+
return open_rule(value, width=width, corner="├")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def open_rule_end(value: object = "", width: int = 78) -> str:
|
|
157
|
+
"""Return a cell-width exact open-card bottom rule."""
|
|
158
|
+
return open_rule(value, width=width, corner="╰")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def cell_meter(value: int, total: int, width: int = 10, filled: str = "█", empty: str = "░") -> str:
|
|
162
|
+
"""Return a display-cell exact meter."""
|
|
163
|
+
if width <= 0:
|
|
164
|
+
return ""
|
|
165
|
+
fill_cells = 0 if total <= 0 else max(0, min(width, round((value / total) * width)))
|
|
166
|
+
return cell_fill(fill_cells, filled) + cell_fill(width - fill_cells, empty)
|