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.
Files changed (136) hide show
  1. {milo_cli-0.2.2 → milo_cli-0.3.0}/PKG-INFO +87 -34
  2. {milo_cli-0.2.2 → milo_cli-0.3.0}/README.md +84 -31
  3. {milo_cli-0.2.2 → milo_cli-0.3.0}/pyproject.toml +13 -5
  4. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/__init__.py +3 -1
  5. milo_cli-0.3.0/src/milo/_cells.py +166 -0
  6. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_child.py +31 -12
  7. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_compat.py +1 -1
  8. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_errors.py +14 -2
  9. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_jsonrpc.py +31 -0
  10. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_mcp_router.py +5 -1
  11. milo_cli-0.3.0/src/milo/_scaffold/__init__.py +55 -0
  12. milo_cli-0.3.0/src/milo/_scaffold/default/README.md +115 -0
  13. milo_cli-0.3.0/src/milo/_scaffold/default/app.py +29 -0
  14. milo_cli-0.3.0/src/milo/_scaffold/default/conftest.py +8 -0
  15. milo_cli-0.3.0/src/milo/_scaffold/default/tests/test_app.py +76 -0
  16. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/app.py +38 -9
  17. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/cli.py +100 -1
  18. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/commands.py +41 -8
  19. milo_cli-0.3.0/src/milo/components_cli.py +111 -0
  20. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/config.py +1 -1
  21. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/dev.py +1 -1
  22. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/flow.py +16 -4
  23. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/form.py +134 -41
  24. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/gateway.py +15 -15
  25. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/groups.py +23 -1
  26. milo_cli-0.3.0/src/milo/live.py +15 -0
  27. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/llms.py +8 -3
  28. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/mcp.py +117 -33
  29. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/middleware.py +2 -2
  30. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/output.py +8 -4
  31. milo_cli-0.3.0/src/milo/py.typed +0 -0
  32. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/registry.py +4 -2
  33. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/schema.py +57 -5
  34. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/state.py +62 -17
  35. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/__init__.py +49 -2
  36. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/components/_defs.kida +17 -12
  37. milo_cli-0.3.0/src/milo/templates/progress.kida +3 -0
  38. milo_cli-0.3.0/src/milo/verify.py +465 -0
  39. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/PKG-INFO +87 -34
  40. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/SOURCES.txt +19 -0
  41. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/requires.txt +2 -2
  42. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_ai_native.py +82 -2
  43. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_app.py +17 -3
  44. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_child.py +17 -0
  45. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_cli.py +63 -0
  46. milo_cli-0.3.0/tests/test_command_contract.py +173 -0
  47. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_commands_core.py +13 -0
  48. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_components.py +42 -0
  49. milo_cli-0.3.0/tests/test_docs_information_architecture.py +64 -0
  50. milo_cli-0.3.0/tests/test_docs_snippets.py +90 -0
  51. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_effects.py +12 -4
  52. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_flow.py +24 -1
  53. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_form.py +128 -1
  54. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_gateway.py +31 -0
  55. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_input.py +14 -0
  56. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mcp_handler.py +32 -0
  57. milo_cli-0.3.0/tests/test_mcp_transport.py +37 -0
  58. milo_cli-0.3.0/tests/test_migration_docs.py +50 -0
  59. milo_cli-0.3.0/tests/test_outputgallery_example.py +258 -0
  60. milo_cli-0.3.0/tests/test_readme_example_index.py +41 -0
  61. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_reducers.py +2 -2
  62. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_registry_v2.py +29 -1
  63. milo_cli-0.3.0/tests/test_scaffold.py +144 -0
  64. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_schema_v2.py +79 -9
  65. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_state.py +97 -1
  66. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_templates.py +77 -0
  67. milo_cli-0.3.0/tests/test_verify.py +241 -0
  68. milo_cli-0.2.2/src/milo/templates/progress.kida +0 -1
  69. {milo_cli-0.2.2 → milo_cli-0.3.0}/setup.cfg +0 -0
  70. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_cli_help.py +0 -0
  71. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_command_defs.py +0 -0
  72. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_protocols.py +0 -0
  73. /milo_cli-0.2.2/src/milo/py.typed → /milo_cli-0.3.0/src/milo/_scaffold/default/tests/__init__.py +0 -0
  74. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/_types.py +0 -0
  75. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/completions.py +0 -0
  76. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/context.py +0 -0
  77. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/doctor.py +0 -0
  78. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/help.py +0 -0
  79. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/input/__init__.py +0 -0
  80. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/input/_platform.py +0 -0
  81. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/input/_reader.py +0 -0
  82. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/input/_sequences.py +0 -0
  83. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/observability.py +0 -0
  84. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/pipeline.py +0 -0
  85. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/plugins.py +0 -0
  86. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/reducers.py +0 -0
  87. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/streaming.py +0 -0
  88. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/components/command_list.kida +0 -0
  89. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/components/help_page.kida +0 -0
  90. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/error.kida +0 -0
  91. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/field_confirm.kida +0 -0
  92. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/field_select.kida +0 -0
  93. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/field_text.kida +0 -0
  94. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/form.kida +0 -0
  95. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/templates/help.kida +0 -0
  96. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/__init__.py +0 -0
  97. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/_mcp.py +0 -0
  98. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/_record.py +0 -0
  99. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/_replay.py +0 -0
  100. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/testing/_snapshot.py +0 -0
  101. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/theme.py +0 -0
  102. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo/version_check.py +0 -0
  103. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/dependency_links.txt +0 -0
  104. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/entry_points.txt +0 -0
  105. {milo_cli-0.2.2 → milo_cli-0.3.0}/src/milo_cli.egg-info/top_level.txt +0 -0
  106. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_bubbletea_patterns.py +0 -0
  107. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_command_defs.py +0 -0
  108. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_commands_middleware.py +0 -0
  109. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_compat.py +0 -0
  110. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_completions.py +0 -0
  111. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_config.py +0 -0
  112. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_context.py +0 -0
  113. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_dev.py +0 -0
  114. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_doctor.py +0 -0
  115. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_effects_stress.py +0 -0
  116. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_errors.py +0 -0
  117. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_groups.py +0 -0
  118. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_help.py +0 -0
  119. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_lazy.py +0 -0
  120. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mcp_prompts.py +0 -0
  121. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mcp_resources.py +0 -0
  122. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mcp_router.py +0 -0
  123. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_middleware.py +0 -0
  124. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_milo_init.py +0 -0
  125. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_mount.py +0 -0
  126. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_observability.py +0 -0
  127. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_pipeline.py +0 -0
  128. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_plugins.py +0 -0
  129. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_protocols.py +0 -0
  130. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_registry_crud.py +0 -0
  131. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_streaming.py +0 -0
  132. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_testing.py +0 -0
  133. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_testing_mcp.py +0 -0
  134. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_theme.py +0 -0
  135. {milo_cli-0.2.2 → milo_cli-0.3.0}/tests/test_types.py +0 -0
  136. {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.2.2
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>=0.6.0
21
+ Requires-Dist: kida-templates<0.8.0,>=0.7.0
22
22
  Provides-Extra: docs
23
- Requires-Dist: bengal>=0.2.6; extra == "docs"
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/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`, `Race`, `All`, `Take`, and more | [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
- | **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/usage/pipeline/) |
163
- | **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/usage/help/) |
164
- | **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/usage/context/) |
165
- | **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/usage/config/) |
166
- | **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
167
- | **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
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
- | [MCP & AI](https://lbliii.github.io/milo-cli/docs/usage/mcp/) | MCP server, gateway, annotations, streaming, and llms.txt |
461
- | [Usage](https://lbliii.github.io/milo-cli/docs/usage/) | State, sagas, flows, forms, templates |
462
- | [Testing](https://lbliii.github.io/milo-cli/docs/usage/testing/) | Snapshots, recording, replay |
463
- | [Reference](https://lbliii.github.io/milo-cli/docs/reference/) | Complete API documentation |
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/usage/mcp/) |
116
- | **MCP Gateway** | Single gateway aggregates all registered Milo CLIs for unified AI agent access | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
117
- | **Tool Annotations** | Declare `readOnlyHint`, `destructiveHint`, `idempotentHint` per MCP spec | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
118
- | **Streaming Progress** | Commands yield `Progress` objects; MCP clients receive real-time notifications | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
119
- | **Schema Constraints** | `Annotated[str, MinLen(1), MaxLen(100)]` generates rich JSON Schema | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
120
- | **llms.txt** | Generate AI-readable discovery documents from CLI command definitions | [llms.txt →](https://lbliii.github.io/milo-cli/docs/usage/llms/) |
121
- | **Middleware** | Intercept MCP calls and CLI commands for logging, auth, and transformation | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
122
- | **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
123
- | **State Management** | Redux-style `Store` with dispatch, listeners, middleware, and saga scheduling | [State →](https://lbliii.github.io/milo-cli/docs/usage/state/) |
124
- | **Commands** | Lightweight `Cmd` thunks, `Batch`, `Sequence`, `TickCmd` for one-shot effects | [Commands →](https://lbliii.github.io/milo-cli/docs/usage/commands-effects/) |
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/usage/sagas/) |
126
- | **ViewState** | Declarative terminal state (`cursor_visible`, `alt_screen`, `window_title`, `mouse_mode`) | [Commands →](https://lbliii.github.io/milo-cli/docs/usage/commands-effects/) |
127
- | **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/usage/flows/) |
128
- | **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/usage/forms/) |
129
- | **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/) |
130
- | **Templates** | Kida-powered terminal rendering with built-in form, field, help, and progress templates | [Templates →](https://lbliii.github.io/milo-cli/docs/usage/templates/) |
131
- | **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/usage/dev/) |
132
- | **Session Recording** | JSONL action log with state hashes for debugging and regression testing | [Testing →](https://lbliii.github.io/milo-cli/docs/usage/testing/) |
133
- | **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/usage/testing/) |
134
- | **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/usage/pipeline/) |
135
- | **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/usage/help/) |
136
- | **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/usage/context/) |
137
- | **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/usage/config/) |
138
- | **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
139
- | **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
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
- | [MCP & AI](https://lbliii.github.io/milo-cli/docs/usage/mcp/) | MCP server, gateway, annotations, streaming, and llms.txt |
433
- | [Usage](https://lbliii.github.io/milo-cli/docs/usage/) | State, sagas, flows, forms, templates |
434
- | [Testing](https://lbliii.github.io/milo-cli/docs/usage/testing/) | Snapshots, recording, replay |
435
- | [Reference](https://lbliii.github.io/milo-cli/docs/reference/) | Complete API documentation |
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.2.2"
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.6.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.6"]
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 = ["templates/*.kida", "templates/components/*.kida", "py.typed"]
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.6",
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.2.2"
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)