milo-cli 0.2.1__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 (139) hide show
  1. {milo_cli-0.2.1 → milo_cli-0.3.0}/PKG-INFO +88 -34
  2. {milo_cli-0.2.1 → milo_cli-0.3.0}/README.md +85 -31
  3. {milo_cli-0.2.1 → milo_cli-0.3.0}/pyproject.toml +16 -5
  4. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/__init__.py +15 -1
  5. milo_cli-0.3.0/src/milo/_cells.py +166 -0
  6. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/_child.py +31 -12
  7. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/_command_defs.py +29 -6
  8. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/_compat.py +1 -1
  9. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/_errors.py +14 -2
  10. milo_cli-0.3.0/src/milo/_jsonrpc.py +73 -0
  11. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/_mcp_router.py +5 -1
  12. milo_cli-0.3.0/src/milo/_scaffold/__init__.py +55 -0
  13. milo_cli-0.3.0/src/milo/_scaffold/default/README.md +115 -0
  14. milo_cli-0.3.0/src/milo/_scaffold/default/app.py +29 -0
  15. milo_cli-0.3.0/src/milo/_scaffold/default/conftest.py +8 -0
  16. milo_cli-0.3.0/src/milo/_scaffold/default/tests/test_app.py +76 -0
  17. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/_types.py +10 -0
  18. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/app.py +54 -11
  19. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/cli.py +100 -1
  20. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/commands.py +349 -180
  21. milo_cli-0.3.0/src/milo/components_cli.py +111 -0
  22. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/config.py +30 -5
  23. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/context.py +37 -3
  24. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/dev.py +1 -1
  25. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/flow.py +16 -4
  26. milo_cli-0.3.0/src/milo/form.py +423 -0
  27. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/gateway.py +71 -19
  28. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/groups.py +31 -3
  29. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/help.py +18 -2
  30. milo_cli-0.3.0/src/milo/live.py +15 -0
  31. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/llms.py +8 -3
  32. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/mcp.py +185 -20
  33. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/middleware.py +2 -2
  34. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/output.py +19 -5
  35. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/pipeline.py +458 -11
  36. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/plugins.py +22 -5
  37. milo_cli-0.3.0/src/milo/py.typed +0 -0
  38. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/registry.py +19 -5
  39. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/schema.py +105 -16
  40. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/state.py +127 -24
  41. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/__init__.py +51 -3
  42. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/components/_defs.kida +93 -3
  43. milo_cli-0.3.0/src/milo/templates/progress.kida +3 -0
  44. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/testing/_record.py +6 -1
  45. milo_cli-0.3.0/src/milo/verify.py +465 -0
  46. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/version_check.py +20 -13
  47. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo_cli.egg-info/PKG-INFO +88 -34
  48. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo_cli.egg-info/SOURCES.txt +23 -0
  49. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo_cli.egg-info/requires.txt +2 -2
  50. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_ai_native.py +150 -5
  51. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_app.py +17 -3
  52. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_child.py +17 -0
  53. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_cli.py +63 -0
  54. milo_cli-0.3.0/tests/test_command_contract.py +173 -0
  55. milo_cli-0.3.0/tests/test_command_defs.py +294 -0
  56. milo_cli-0.3.0/tests/test_commands_core.py +470 -0
  57. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_components.py +181 -0
  58. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_config.py +82 -0
  59. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_context.py +32 -0
  60. milo_cli-0.3.0/tests/test_docs_information_architecture.py +64 -0
  61. milo_cli-0.3.0/tests/test_docs_snippets.py +90 -0
  62. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_effects.py +16 -7
  63. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_effects_stress.py +1 -1
  64. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_flow.py +24 -1
  65. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_form.py +186 -1
  66. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_gateway.py +78 -0
  67. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_help.py +17 -1
  68. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_input.py +14 -0
  69. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_lazy.py +73 -7
  70. milo_cli-0.3.0/tests/test_mcp_handler.py +432 -0
  71. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_mcp_resources.py +62 -2
  72. milo_cli-0.3.0/tests/test_mcp_transport.py +37 -0
  73. milo_cli-0.3.0/tests/test_migration_docs.py +50 -0
  74. milo_cli-0.3.0/tests/test_outputgallery_example.py +258 -0
  75. milo_cli-0.3.0/tests/test_pipeline.py +1318 -0
  76. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_plugins.py +47 -0
  77. milo_cli-0.3.0/tests/test_readme_example_index.py +41 -0
  78. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_reducers.py +2 -2
  79. milo_cli-0.3.0/tests/test_registry_crud.py +122 -0
  80. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_registry_v2.py +29 -1
  81. milo_cli-0.3.0/tests/test_scaffold.py +144 -0
  82. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_schema_v2.py +134 -9
  83. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_state.py +149 -1
  84. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_templates.py +77 -0
  85. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_testing.py +14 -0
  86. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_types.py +2 -1
  87. milo_cli-0.3.0/tests/test_verify.py +241 -0
  88. milo_cli-0.2.1/src/milo/_jsonrpc.py +0 -37
  89. milo_cli-0.2.1/src/milo/form.py +0 -278
  90. milo_cli-0.2.1/src/milo/templates/progress.kida +0 -1
  91. milo_cli-0.2.1/tests/test_pipeline.py +0 -614
  92. {milo_cli-0.2.1 → milo_cli-0.3.0}/setup.cfg +0 -0
  93. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/_cli_help.py +0 -0
  94. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/_protocols.py +0 -0
  95. /milo_cli-0.2.1/src/milo/py.typed → /milo_cli-0.3.0/src/milo/_scaffold/default/tests/__init__.py +0 -0
  96. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/completions.py +0 -0
  97. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/doctor.py +0 -0
  98. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/input/__init__.py +0 -0
  99. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/input/_platform.py +0 -0
  100. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/input/_reader.py +0 -0
  101. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/input/_sequences.py +0 -0
  102. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/observability.py +0 -0
  103. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/reducers.py +0 -0
  104. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/streaming.py +0 -0
  105. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/components/command_list.kida +0 -0
  106. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/components/help_page.kida +0 -0
  107. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/error.kida +0 -0
  108. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/field_confirm.kida +0 -0
  109. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/field_select.kida +0 -0
  110. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/field_text.kida +0 -0
  111. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/form.kida +0 -0
  112. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/templates/help.kida +0 -0
  113. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/testing/__init__.py +0 -0
  114. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/testing/_mcp.py +0 -0
  115. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/testing/_replay.py +0 -0
  116. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/testing/_snapshot.py +0 -0
  117. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo/theme.py +0 -0
  118. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo_cli.egg-info/dependency_links.txt +0 -0
  119. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo_cli.egg-info/entry_points.txt +0 -0
  120. {milo_cli-0.2.1 → milo_cli-0.3.0}/src/milo_cli.egg-info/top_level.txt +0 -0
  121. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_bubbletea_patterns.py +0 -0
  122. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_commands_middleware.py +0 -0
  123. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_compat.py +0 -0
  124. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_completions.py +0 -0
  125. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_dev.py +0 -0
  126. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_doctor.py +0 -0
  127. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_errors.py +0 -0
  128. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_groups.py +0 -0
  129. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_mcp_prompts.py +0 -0
  130. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_mcp_router.py +0 -0
  131. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_middleware.py +0 -0
  132. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_milo_init.py +0 -0
  133. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_mount.py +0 -0
  134. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_observability.py +0 -0
  135. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_protocols.py +0 -0
  136. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_streaming.py +0 -0
  137. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_testing_mcp.py +0 -0
  138. {milo_cli-0.2.1 → milo_cli-0.3.0}/tests/test_theme.py +0 -0
  139. {milo_cli-0.2.1 → 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.1
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.4.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,30 +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` | [Sagas →](https://lbliii.github.io/milo-cli/docs/usage/sagas/) |
154
- | **ViewState** | Declarative terminal state (`cursor_visible`, `alt_screen`, `window_title`, `mouse_mode`) | [Commands →](https://lbliii.github.io/milo-cli/docs/usage/commands-effects/) |
155
- | **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/usage/flows/) |
156
- | **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/usage/forms/) |
157
- | **Input Handling** | Cross-platform key reader with full escape sequence support (arrows, F-keys, modifiers) | [Input →](https://lbliii.github.io/milo-cli/docs/usage/input/) |
158
- | **Templates** | Kida-powered terminal rendering with built-in form, field, help, and progress templates | [Templates →](https://lbliii.github.io/milo-cli/docs/usage/templates/) |
159
- | **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/usage/dev/) |
160
- | **Session Recording** | JSONL action log with state hashes for debugging and regression testing | [Testing →](https://lbliii.github.io/milo-cli/docs/usage/testing/) |
161
- | **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/usage/testing/) |
162
- | **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/usage/help/) |
163
- | **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/usage/context/) |
164
- | **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/usage/config/) |
165
- | **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
166
- | **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
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.
167
220
 
168
221
  ---
169
222
 
@@ -343,7 +396,7 @@ def reducer(state, action):
343
396
  return state
344
397
  ```
345
398
 
346
- Saga effects: `Call(fn, args)`, `Put(action)`, `Select(selector)`, `Fork(saga)`, `Delay(seconds)`, `Retry(fn, ...)`.
399
+ Saga effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Timeout`, `TryCall`, `Race`, `All`, `Take`, `Debounce`, `TakeEvery`, `TakeLatest`.
347
400
 
348
401
  For one-shot effects, use `Cmd` instead — no generator needed:
349
402
 
@@ -455,11 +508,12 @@ App.run()
455
508
 
456
509
  | Section | Description |
457
510
  |---------|-------------|
511
+ | [About](https://lbliii.github.io/milo-cli/docs/about/) | Philosophy, architecture, concepts, and lifecycle |
458
512
  | [Get Started](https://lbliii.github.io/milo-cli/docs/get-started/) | Installation and quickstart |
459
- | [MCP & AI](https://lbliii.github.io/milo-cli/docs/usage/mcp/) | MCP server, gateway, annotations, streaming, and llms.txt |
460
- | [Usage](https://lbliii.github.io/milo-cli/docs/usage/) | State, sagas, flows, forms, templates |
461
- | [Testing](https://lbliii.github.io/milo-cli/docs/usage/testing/) | Snapshots, recording, replay |
462
- | [Reference](https://lbliii.github.io/milo-cli/docs/reference/) | Complete API documentation |
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 |
463
517
 
464
518
  ---
465
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,30 +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` | [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
- | **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/usage/help/) |
135
- | **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/usage/context/) |
136
- | **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/usage/config/) |
137
- | **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/usage/cli/) |
138
- | **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.
139
192
 
140
193
  ---
141
194
 
@@ -315,7 +368,7 @@ def reducer(state, action):
315
368
  return state
316
369
  ```
317
370
 
318
- Saga effects: `Call(fn, args)`, `Put(action)`, `Select(selector)`, `Fork(saga)`, `Delay(seconds)`, `Retry(fn, ...)`.
371
+ Saga effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Timeout`, `TryCall`, `Race`, `All`, `Take`, `Debounce`, `TakeEvery`, `TakeLatest`.
319
372
 
320
373
  For one-shot effects, use `Cmd` instead — no generator needed:
321
374
 
@@ -427,11 +480,12 @@ App.run()
427
480
 
428
481
  | Section | Description |
429
482
  |---------|-------------|
483
+ | [About](https://lbliii.github.io/milo-cli/docs/about/) | Philosophy, architecture, concepts, and lifecycle |
430
484
  | [Get Started](https://lbliii.github.io/milo-cli/docs/get-started/) | Installation and quickstart |
431
- | [MCP & AI](https://lbliii.github.io/milo-cli/docs/usage/mcp/) | MCP server, gateway, annotations, streaming, and llms.txt |
432
- | [Usage](https://lbliii.github.io/milo-cli/docs/usage/) | State, sagas, flows, forms, templates |
433
- | [Testing](https://lbliii.github.io/milo-cli/docs/usage/testing/) | Snapshots, recording, replay |
434
- | [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 |
435
489
 
436
490
  ---
437
491
 
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "milo-cli"
7
- version = "0.2.1"
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.4.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"]
@@ -123,7 +129,9 @@ ignore = [
123
129
 
124
130
  [tool.ruff.lint.per-file-ignores]
125
131
  "__init__.py" = ["F401"]
126
- "tests/**/*.py" = ["S101", "S108", "S110", "S604", "SIM117", "N806", "ARG"]
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
@@ -187,3 +195,6 @@ dev = [
187
195
  # Changelog
188
196
  "towncrier>=24.0",
189
197
  ]
198
+ docs = [
199
+ "bengal>=0.3.2",
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",
@@ -93,8 +94,14 @@ def __getattr__(name: str):
93
94
  "Pipeline": "pipeline",
94
95
  "Phase": "pipeline",
95
96
  "PhasePolicy": "pipeline",
97
+ "PhaseLog": "pipeline",
96
98
  "PipelineState": "pipeline",
97
99
  "PhaseStatus": "pipeline",
100
+ "PipelineViewState": "pipeline",
101
+ "make_detail_reducer": "pipeline",
102
+ "pipeline_to_timeline": "pipeline",
103
+ "set_active_pipeline": "pipeline",
104
+ "get_active_pipeline": "pipeline",
98
105
  "CycleError": "pipeline",
99
106
  # Plugins
100
107
  "HookRegistry": "plugins",
@@ -154,7 +161,7 @@ def _Py_mod_gil() -> int: # noqa: N802
154
161
  return 0
155
162
 
156
163
 
157
- __version__ = "0.2.1"
164
+ __version__ = "0.3.0"
158
165
  __all__ = [
159
166
  "BUILTIN_ACTIONS",
160
167
  "CLI",
@@ -211,11 +218,13 @@ __all__ = [
211
218
  "MinLen",
212
219
  "Pattern",
213
220
  "Phase",
221
+ "PhaseLog",
214
222
  "PhasePolicy",
215
223
  "PhaseStatus",
216
224
  "Pipeline",
217
225
  "PipelineError",
218
226
  "PipelineState",
227
+ "PipelineViewState",
219
228
  "PluginError",
220
229
  "Progress",
221
230
  "PromptDef",
@@ -248,20 +257,25 @@ __all__ = [
248
257
  "compact_cmds",
249
258
  "form",
250
259
  "form_reducer",
260
+ "form_schema",
251
261
  "format_doctor_report",
252
262
  "format_error",
253
263
  "format_output",
254
264
  "format_render_error",
255
265
  "function_to_schema",
256
266
  "generate_llms_txt",
267
+ "get_active_pipeline",
257
268
  "get_context",
258
269
  "install_completions",
270
+ "make_detail_reducer",
259
271
  "make_form_reducer",
260
272
  "make_style_filter",
273
+ "pipeline_to_timeline",
261
274
  "quit_on",
262
275
  "render_html",
263
276
  "run",
264
277
  "run_doctor",
278
+ "set_active_pipeline",
265
279
  "with_confirm",
266
280
  "with_cursor",
267
281
  "write_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)