milo-cli 0.3.1__tar.gz → 0.4.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 (150) hide show
  1. {milo_cli-0.3.1 → milo_cli-0.4.0}/PKG-INFO +48 -28
  2. {milo_cli-0.3.1 → milo_cli-0.4.0}/README.md +47 -27
  3. {milo_cli-0.3.1 → milo_cli-0.4.0}/pyproject.toml +1 -1
  4. milo_cli-0.4.0/src/milo/__init__.py +320 -0
  5. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_cells.py +5 -0
  6. milo_cli-0.4.0/src/milo/_child.py +326 -0
  7. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_cli_help.py +15 -14
  8. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_command_defs.py +59 -1
  9. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_errors.py +16 -0
  10. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_jsonrpc.py +3 -0
  11. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_mcp_router.py +39 -0
  12. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_scaffold/default/README.md +18 -17
  13. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_scaffold/default/app.py +3 -3
  14. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_scaffold/default/tests/test_app.py +16 -3
  15. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/cli.py +3 -3
  16. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/commands.py +595 -144
  17. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/context.py +89 -21
  18. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/gateway.py +311 -33
  19. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/groups.py +19 -2
  20. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/llms.py +21 -5
  21. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/mcp.py +271 -19
  22. milo_cli-0.4.0/src/milo/mcp_apps.py +152 -0
  23. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/schema.py +388 -1
  24. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/__init__.py +2 -0
  25. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/components/_defs.kida +21 -18
  26. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/testing/_mcp.py +9 -4
  27. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/testing/_snapshot.py +22 -5
  28. milo_cli-0.4.0/src/milo/verify.py +1287 -0
  29. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo_cli.egg-info/PKG-INFO +48 -28
  30. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo_cli.egg-info/SOURCES.txt +9 -0
  31. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_ai_native.py +44 -8
  32. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_child.py +133 -26
  33. milo_cli-0.4.0/tests/test_chirp_adoption_contract.py +255 -0
  34. milo_cli-0.4.0/tests/test_command_contract.py +333 -0
  35. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_command_defs.py +10 -0
  36. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_commands_middleware.py +17 -0
  37. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_components.py +84 -0
  38. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_context.py +184 -1
  39. milo_cli-0.4.0/tests/test_ctxdemo_example.py +28 -0
  40. milo_cli-0.4.0/tests/test_docs_information_architecture.py +151 -0
  41. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_docs_snippets.py +16 -0
  42. milo_cli-0.4.0/tests/test_downloader_example.py +47 -0
  43. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_effects.py +28 -8
  44. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_effects_stress.py +64 -17
  45. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_gateway.py +39 -1
  46. milo_cli-0.4.0/tests/test_gateway_mcp_apps.py +360 -0
  47. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_groups.py +12 -0
  48. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_help.py +48 -0
  49. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_lazy.py +114 -0
  50. milo_cli-0.4.0/tests/test_mcp_app_example.py +164 -0
  51. milo_cli-0.4.0/tests/test_mcp_apps.py +472 -0
  52. milo_cli-0.4.0/tests/test_mcp_compat_docs.py +55 -0
  53. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_mcp_handler.py +65 -0
  54. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_mcp_router.py +15 -0
  55. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_mcp_transport.py +20 -0
  56. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_milo_init.py +5 -0
  57. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_readme_example_index.py +25 -0
  58. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_scaffold.py +26 -3
  59. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_schema_v2.py +222 -0
  60. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_templates.py +5 -0
  61. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_testing.py +26 -0
  62. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_testing_mcp.py +46 -1
  63. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_verify.py +14 -2
  64. milo_cli-0.4.0/tests/test_verify_mcp_apps.py +351 -0
  65. milo_cli-0.3.1/src/milo/__init__.py +0 -282
  66. milo_cli-0.3.1/src/milo/_child.py +0 -190
  67. milo_cli-0.3.1/src/milo/verify.py +0 -465
  68. milo_cli-0.3.1/tests/test_command_contract.py +0 -173
  69. milo_cli-0.3.1/tests/test_docs_information_architecture.py +0 -64
  70. {milo_cli-0.3.1 → milo_cli-0.4.0}/setup.cfg +0 -0
  71. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_compat.py +0 -0
  72. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_protocols.py +0 -0
  73. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_scaffold/__init__.py +0 -0
  74. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_scaffold/default/conftest.py +0 -0
  75. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_scaffold/default/tests/__init__.py +0 -0
  76. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/_types.py +0 -0
  77. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/app.py +0 -0
  78. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/completions.py +0 -0
  79. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/components_cli.py +0 -0
  80. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/config.py +0 -0
  81. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/dev.py +0 -0
  82. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/doctor.py +0 -0
  83. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/flow.py +0 -0
  84. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/form.py +0 -0
  85. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/help.py +0 -0
  86. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/input/__init__.py +0 -0
  87. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/input/_platform.py +0 -0
  88. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/input/_reader.py +0 -0
  89. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/input/_sequences.py +0 -0
  90. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/live.py +0 -0
  91. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/middleware.py +0 -0
  92. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/observability.py +0 -0
  93. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/output.py +0 -0
  94. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/pipeline.py +0 -0
  95. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/plugins.py +0 -0
  96. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/py.typed +0 -0
  97. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/reducers.py +0 -0
  98. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/registry.py +0 -0
  99. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/state.py +0 -0
  100. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/streaming.py +0 -0
  101. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/components/command_list.kida +0 -0
  102. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/components/help_page.kida +0 -0
  103. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/error.kida +0 -0
  104. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/field_confirm.kida +0 -0
  105. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/field_select.kida +0 -0
  106. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/field_text.kida +0 -0
  107. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/form.kida +0 -0
  108. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/help.kida +0 -0
  109. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/templates/progress.kida +0 -0
  110. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/testing/__init__.py +0 -0
  111. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/testing/_record.py +0 -0
  112. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/testing/_replay.py +0 -0
  113. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/theme.py +0 -0
  114. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo/version_check.py +0 -0
  115. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo_cli.egg-info/dependency_links.txt +0 -0
  116. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo_cli.egg-info/entry_points.txt +0 -0
  117. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo_cli.egg-info/requires.txt +0 -0
  118. {milo_cli-0.3.1 → milo_cli-0.4.0}/src/milo_cli.egg-info/top_level.txt +0 -0
  119. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_app.py +0 -0
  120. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_bubbletea_patterns.py +0 -0
  121. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_cli.py +0 -0
  122. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_commands_core.py +0 -0
  123. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_compat.py +0 -0
  124. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_completions.py +0 -0
  125. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_config.py +0 -0
  126. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_dev.py +0 -0
  127. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_doctor.py +0 -0
  128. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_errors.py +0 -0
  129. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_flow.py +0 -0
  130. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_form.py +0 -0
  131. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_input.py +0 -0
  132. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_mcp_prompts.py +0 -0
  133. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_mcp_resources.py +0 -0
  134. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_middleware.py +0 -0
  135. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_migration_docs.py +0 -0
  136. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_mount.py +0 -0
  137. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_observability.py +0 -0
  138. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_outputgallery_example.py +0 -0
  139. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_pipeline.py +0 -0
  140. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_plugins.py +0 -0
  141. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_protocols.py +0 -0
  142. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_reducers.py +0 -0
  143. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_registry_crud.py +0 -0
  144. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_registry_v2.py +0 -0
  145. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_release_status.py +0 -0
  146. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_state.py +0 -0
  147. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_streaming.py +0 -0
  148. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_theme.py +0 -0
  149. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_types.py +0 -0
  150. {milo_cli-0.3.1 → milo_cli-0.4.0}/tests/test_version_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: milo-cli
3
- Version: 0.3.1
3
+ Version: 0.4.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/
@@ -33,33 +33,43 @@ Requires-Dist: watchfiles>=1.0; extra == "watch"
33
33
  [![Python 3.14+](https://img.shields.io/badge/python-3.14+-blue.svg)](https://pypi.org/project/milo-cli/)
34
34
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
35
35
 
36
- **Build CLIs that humans and AI agents both use natively**
36
+ **One typed Python function becomes a human CLI command, an MCP tool, and an
37
+ agent-readable discovery entry.**
37
38
 
38
- ```python
39
- from milo import CLI
39
+ <!--
40
+ Launch video: replace this comment with the final 60–90 second recording.
41
+ The verified shot list and clean-machine commands live in docs/launch-demo-script.md.
42
+ -->
40
43
 
41
- cli = CLI(name="deployer", description="Deploy services to environments")
44
+ ## Prove It in 60 Seconds
42
45
 
43
- @cli.command("deploy", description="Deploy a service", annotations={"destructiveHint": True})
44
- def deploy(environment: str, service: str, version: str = "latest") -> dict:
45
- """Deploy a service to the specified environment."""
46
- return {"status": "deployed", "environment": environment, "service": service, "version": version}
46
+ With [`uv`](https://docs.astral.sh/uv/) installed, paste this into a clean
47
+ directory. `uv` downloads Python 3.14 and Milo when they are not already
48
+ available:
47
49
 
48
- cli.run()
50
+ ```bash milo-docs:skip reason=downloads-package-and-creates-project
51
+ uvx --python 3.14 --from milo-cli milo new hello_milo
52
+ uv run --python 3.14 --with milo-cli python hello_milo/app.py greet --name World
53
+ uv run --python 3.14 --with milo-cli milo verify hello_milo/app.py
49
54
  ```
50
55
 
51
- Three protocols from one decorator:
56
+ The second command prints `Hello, World!`. The verifier then exercises import,
57
+ schema generation, MCP discovery, MCP Apps tool/resource/gateway conformance,
58
+ and a real subprocess JSON-RPC handshake with resource reads. Do not register a
59
+ new tool until it reports zero failures.
52
60
 
53
- ```bash
54
- # Human CLI
55
- deployer deploy --environment production --service api
61
+ Now give Claude Code the same file as a local stdio MCP server:
62
+
63
+ ```bash milo-docs:skip reason=requires-claude-cli-and-user-registration
64
+ claude mcp add --transport stdio hello_milo -- \
65
+ uv run --python 3.14 --with milo-cli python "$PWD/hello_milo/app.py" --mcp
66
+ ```
56
67
 
57
- # MCP tool (AI agent calls this via JSON-RPC)
58
- echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"deploy","arguments":{"environment":"staging","service":"api"}}}' \
59
- | deployer --mcp
68
+ Ask Claude to “use the `greet` tool to greet Ada,” or call the same function by
69
+ hand:
60
70
 
61
- # AI-readable discovery document
62
- deployer --llms-txt
71
+ ```bash milo-docs:skip reason=requires-prior-scaffold
72
+ uv run --python 3.14 --with milo-cli python hello_milo/app.py greet --name Ada
63
73
  ```
64
74
 
65
75
  ---
@@ -72,7 +82,7 @@ Milo is a Python framework where every CLI is simultaneously a terminal app, a c
72
82
 
73
83
  - **Every CLI is an MCP server** — `@cli.command` produces an argparse subcommand, MCP tool, and llms.txt entry from one function. AI agents discover and call your tools with zero extra code.
74
84
  - **Dual-mode commands** — The same command shows an interactive UI when a human runs it, and returns structured JSON when an AI calls it via MCP.
75
- - **Annotated schemas** — Type hints + `Annotated` constraints generate rich JSON Schema. Agents validate inputs before calling.
85
+ - **Annotated schemas** — Type hints + `Annotated` constraints generate rich JSON Schema, and Milo enforces it before handlers run.
76
86
  - **Streaming progress** — Commands that yield `Progress` objects stream notifications to MCP clients in real time.
77
87
  - **Elm Architecture** — Immutable state, pure reducers, declarative views. Every state transition is explicit and testable.
78
88
  - **Free-threading ready** — Built for Python 3.14t (PEP 703). Sagas run on `ThreadPoolExecutor` with no GIL contention.
@@ -121,7 +131,12 @@ The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The
121
131
  | `--llms-txt` | Generate AI discovery doc |
122
132
  | `--mcp-install` | Register in gateway |
123
133
  | `annotations={...}` | MCP behavioral hints |
134
+ | `ui=MCPAppToolMeta("ui://...")` | Link a tool to an MCP Apps UI resource |
124
135
  | `Annotated[str, MinLen(1)]` | Schema constraints |
136
+ | `Annotated[str, Positional("NAME")]` | Positional CLI presentation |
137
+ | `Option(aliases=("-n",))` | Compatible option aliases |
138
+ | `surfaces=("cli",)` | Keep long-running commands out of agent discovery |
139
+ | `terminal_renderer=...` | Human output over structured command results |
125
140
 
126
141
  ### Interactive Apps
127
142
 
@@ -148,7 +163,7 @@ The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The
148
163
  | **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
164
  | **Tool Annotations** | Declare `readOnlyHint`, `destructiveHint`, `idempotentHint` per MCP spec | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
150
165
  | **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/) |
166
+ | **Schema Constraints** | `Annotated[str, MinLen(1), MaxLen(100)]` generates and enforces rich JSON Schema | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
152
167
  | **llms.txt** | Generate AI-readable discovery documents from CLI command definitions | [llms.txt →](https://lbliii.github.io/milo-cli/docs/build-clis/llms/) |
153
168
  | **Middleware** | Intercept MCP calls and CLI commands for logging, auth, and transformation | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
154
169
  | **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
@@ -165,7 +180,7 @@ The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The
165
180
  | **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/quality/testing/) |
166
181
  | **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/quality/pipeline/) |
167
182
  | **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/) |
183
+ | **Context** | Injectable output, interaction, approvals, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/build-clis/context/) |
169
184
  | **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/about/concepts/configuration/) |
170
185
  | **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
171
186
  | **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
@@ -182,11 +197,12 @@ Pick the example closest to your use case, copy its `app.py`, and adapt. See [ex
182
197
  |---|---|---|
183
198
  | The simplest possible CLI | [examples/greet](examples/greet) | `CLI`, `@cli.command` |
184
199
  | 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` |
200
+ | MCP tool with a negotiated interactive UI resource | [examples/mcp_app](examples/mcp_app) | Dependency-free HTML, `ui_resource`, `MCPAppToolMeta`, structured fallback |
201
+ | Context injection, host-owned output, progress, confirms | [examples/ctxdemo](examples/ctxdemo) | `Context`, `OutputSink`, `ctx.progress`, `ctx.confirm` |
186
202
  | Nested command groups (`app repo list`) | [examples/groups](examples/groups) | `cli.group()`, `walk_commands` |
187
203
  | 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` |
204
+ | Production CLI with hooks, completions, doctor | [examples/devtool](examples/devtool) | `run_doctor`, `before_command`/`after_command`, did-you-mean, completions |
205
+ | AI-native CLI surfacing tools + resources | [examples/taskman](examples/taskman) | `@cli.command`, `@cli.resource`, `--format`, `--llms-txt`, `--mcp` |
190
206
  | Advanced terminal reports and diagnostics | [examples/outputgallery](examples/outputgallery) | `Context.render`, Kida templates, character maps, JSON output |
191
207
 
192
208
  **Configuration, plugins, pipelines**
@@ -267,7 +283,8 @@ Every Milo CLI is automatically an MCP server:
267
283
  myapp --mcp
268
284
 
269
285
  # Register with an AI host directly
270
- claude mcp add myapp -- uv run python examples/deploy/app.py --mcp
286
+ claude mcp add --transport stdio myapp -- \
287
+ uv run python "$PWD/examples/deploy/app.py" --mcp
271
288
  ```
272
289
 
273
290
  For multiple CLIs, register them and run a single gateway:
@@ -281,10 +298,13 @@ deployer --mcp-install
281
298
  uv run python -m milo.gateway --mcp
282
299
 
283
300
  # Or register the gateway with your AI host
284
- claude mcp add milo -- uv run python -m milo.gateway --mcp
301
+ claude mcp add --transport stdio milo -- uv run python -m milo.gateway --mcp
285
302
  ```
286
303
 
287
- The gateway namespaces tools automatically: `taskman.add`, `deployer.deploy`, etc. Implements MCP 2025-11-25 with `outputSchema`, `structuredContent`, tool `annotations`, and streaming `Progress` notifications.
304
+ The gateway namespaces tools automatically (`taskman.add`, `deployer.deploy`) and
305
+ rewrites negotiated MCP Apps `ui://` links without collisions. It preserves
306
+ `outputSchema`, `structuredContent`, tool annotations, resource metadata, and
307
+ streaming `Progress` notifications across child CLIs.
288
308
 
289
309
  Built-in `milo://stats` resource exposes request latency, error counts, and throughput.
290
310
 
@@ -5,33 +5,43 @@
5
5
  [![Python 3.14+](https://img.shields.io/badge/python-3.14+-blue.svg)](https://pypi.org/project/milo-cli/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- **Build CLIs that humans and AI agents both use natively**
8
+ **One typed Python function becomes a human CLI command, an MCP tool, and an
9
+ agent-readable discovery entry.**
9
10
 
10
- ```python
11
- from milo import CLI
11
+ <!--
12
+ Launch video: replace this comment with the final 60–90 second recording.
13
+ The verified shot list and clean-machine commands live in docs/launch-demo-script.md.
14
+ -->
12
15
 
13
- cli = CLI(name="deployer", description="Deploy services to environments")
16
+ ## Prove It in 60 Seconds
14
17
 
15
- @cli.command("deploy", description="Deploy a service", annotations={"destructiveHint": True})
16
- def deploy(environment: str, service: str, version: str = "latest") -> dict:
17
- """Deploy a service to the specified environment."""
18
- return {"status": "deployed", "environment": environment, "service": service, "version": version}
18
+ With [`uv`](https://docs.astral.sh/uv/) installed, paste this into a clean
19
+ directory. `uv` downloads Python 3.14 and Milo when they are not already
20
+ available:
19
21
 
20
- cli.run()
22
+ ```bash milo-docs:skip reason=downloads-package-and-creates-project
23
+ uvx --python 3.14 --from milo-cli milo new hello_milo
24
+ uv run --python 3.14 --with milo-cli python hello_milo/app.py greet --name World
25
+ uv run --python 3.14 --with milo-cli milo verify hello_milo/app.py
21
26
  ```
22
27
 
23
- Three protocols from one decorator:
28
+ The second command prints `Hello, World!`. The verifier then exercises import,
29
+ schema generation, MCP discovery, MCP Apps tool/resource/gateway conformance,
30
+ and a real subprocess JSON-RPC handshake with resource reads. Do not register a
31
+ new tool until it reports zero failures.
24
32
 
25
- ```bash
26
- # Human CLI
27
- deployer deploy --environment production --service api
33
+ Now give Claude Code the same file as a local stdio MCP server:
34
+
35
+ ```bash milo-docs:skip reason=requires-claude-cli-and-user-registration
36
+ claude mcp add --transport stdio hello_milo -- \
37
+ uv run --python 3.14 --with milo-cli python "$PWD/hello_milo/app.py" --mcp
38
+ ```
28
39
 
29
- # MCP tool (AI agent calls this via JSON-RPC)
30
- echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"deploy","arguments":{"environment":"staging","service":"api"}}}' \
31
- | deployer --mcp
40
+ Ask Claude to “use the `greet` tool to greet Ada,” or call the same function by
41
+ hand:
32
42
 
33
- # AI-readable discovery document
34
- deployer --llms-txt
43
+ ```bash milo-docs:skip reason=requires-prior-scaffold
44
+ uv run --python 3.14 --with milo-cli python hello_milo/app.py greet --name Ada
35
45
  ```
36
46
 
37
47
  ---
@@ -44,7 +54,7 @@ Milo is a Python framework where every CLI is simultaneously a terminal app, a c
44
54
 
45
55
  - **Every CLI is an MCP server** — `@cli.command` produces an argparse subcommand, MCP tool, and llms.txt entry from one function. AI agents discover and call your tools with zero extra code.
46
56
  - **Dual-mode commands** — The same command shows an interactive UI when a human runs it, and returns structured JSON when an AI calls it via MCP.
47
- - **Annotated schemas** — Type hints + `Annotated` constraints generate rich JSON Schema. Agents validate inputs before calling.
57
+ - **Annotated schemas** — Type hints + `Annotated` constraints generate rich JSON Schema, and Milo enforces it before handlers run.
48
58
  - **Streaming progress** — Commands that yield `Progress` objects stream notifications to MCP clients in real time.
49
59
  - **Elm Architecture** — Immutable state, pure reducers, declarative views. Every state transition is explicit and testable.
50
60
  - **Free-threading ready** — Built for Python 3.14t (PEP 703). Sagas run on `ThreadPoolExecutor` with no GIL contention.
@@ -93,7 +103,12 @@ The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The
93
103
  | `--llms-txt` | Generate AI discovery doc |
94
104
  | `--mcp-install` | Register in gateway |
95
105
  | `annotations={...}` | MCP behavioral hints |
106
+ | `ui=MCPAppToolMeta("ui://...")` | Link a tool to an MCP Apps UI resource |
96
107
  | `Annotated[str, MinLen(1)]` | Schema constraints |
108
+ | `Annotated[str, Positional("NAME")]` | Positional CLI presentation |
109
+ | `Option(aliases=("-n",))` | Compatible option aliases |
110
+ | `surfaces=("cli",)` | Keep long-running commands out of agent discovery |
111
+ | `terminal_renderer=...` | Human output over structured command results |
97
112
 
98
113
  ### Interactive Apps
99
114
 
@@ -120,7 +135,7 @@ The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The
120
135
  | **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
136
  | **Tool Annotations** | Declare `readOnlyHint`, `destructiveHint`, `idempotentHint` per MCP spec | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
122
137
  | **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/) |
138
+ | **Schema Constraints** | `Annotated[str, MinLen(1), MaxLen(100)]` generates and enforces rich JSON Schema | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
124
139
  | **llms.txt** | Generate AI-readable discovery documents from CLI command definitions | [llms.txt →](https://lbliii.github.io/milo-cli/docs/build-clis/llms/) |
125
140
  | **Middleware** | Intercept MCP calls and CLI commands for logging, auth, and transformation | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
126
141
  | **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/build-clis/mcp/) |
@@ -137,7 +152,7 @@ The PyPI package is **milo-cli**; import the **`milo`** namespace in Python. The
137
152
  | **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/quality/testing/) |
138
153
  | **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/quality/pipeline/) |
139
154
  | **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/) |
155
+ | **Context** | Injectable output, interaction, approvals, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/build-clis/context/) |
141
156
  | **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/about/concepts/configuration/) |
142
157
  | **Shell Completions** | Generate bash/zsh/fish completions from CLI definitions | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
143
158
  | **Doctor Diagnostics** | `run_doctor()` validates environment, dependencies, and config health | [CLI →](https://lbliii.github.io/milo-cli/docs/build-clis/commands/) |
@@ -154,11 +169,12 @@ Pick the example closest to your use case, copy its `app.py`, and adapt. See [ex
154
169
  |---|---|---|
155
170
  | The simplest possible CLI | [examples/greet](examples/greet) | `CLI`, `@cli.command` |
156
171
  | 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` |
172
+ | MCP tool with a negotiated interactive UI resource | [examples/mcp_app](examples/mcp_app) | Dependency-free HTML, `ui_resource`, `MCPAppToolMeta`, structured fallback |
173
+ | Context injection, host-owned output, progress, confirms | [examples/ctxdemo](examples/ctxdemo) | `Context`, `OutputSink`, `ctx.progress`, `ctx.confirm` |
158
174
  | Nested command groups (`app repo list`) | [examples/groups](examples/groups) | `cli.group()`, `walk_commands` |
159
175
  | 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` |
176
+ | Production CLI with hooks, completions, doctor | [examples/devtool](examples/devtool) | `run_doctor`, `before_command`/`after_command`, did-you-mean, completions |
177
+ | AI-native CLI surfacing tools + resources | [examples/taskman](examples/taskman) | `@cli.command`, `@cli.resource`, `--format`, `--llms-txt`, `--mcp` |
162
178
  | Advanced terminal reports and diagnostics | [examples/outputgallery](examples/outputgallery) | `Context.render`, Kida templates, character maps, JSON output |
163
179
 
164
180
  **Configuration, plugins, pipelines**
@@ -239,7 +255,8 @@ Every Milo CLI is automatically an MCP server:
239
255
  myapp --mcp
240
256
 
241
257
  # Register with an AI host directly
242
- claude mcp add myapp -- uv run python examples/deploy/app.py --mcp
258
+ claude mcp add --transport stdio myapp -- \
259
+ uv run python "$PWD/examples/deploy/app.py" --mcp
243
260
  ```
244
261
 
245
262
  For multiple CLIs, register them and run a single gateway:
@@ -253,10 +270,13 @@ deployer --mcp-install
253
270
  uv run python -m milo.gateway --mcp
254
271
 
255
272
  # Or register the gateway with your AI host
256
- claude mcp add milo -- uv run python -m milo.gateway --mcp
273
+ claude mcp add --transport stdio milo -- uv run python -m milo.gateway --mcp
257
274
  ```
258
275
 
259
- The gateway namespaces tools automatically: `taskman.add`, `deployer.deploy`, etc. Implements MCP 2025-11-25 with `outputSchema`, `structuredContent`, tool `annotations`, and streaming `Progress` notifications.
276
+ The gateway namespaces tools automatically (`taskman.add`, `deployer.deploy`) and
277
+ rewrites negotiated MCP Apps `ui://` links without collisions. It preserves
278
+ `outputSchema`, `structuredContent`, tool annotations, resource metadata, and
279
+ streaming `Progress` notifications across child CLIs.
260
280
 
261
281
  Built-in `milo://stats` resource exposes request latency, error counts, and throughput.
262
282
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "milo-cli"
7
- version = "0.3.1"
7
+ version = "0.4.0"
8
8
  description = "Template-driven CLI applications for free-threaded Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
@@ -0,0 +1,320 @@
1
+ """Milo — Template-driven CLI applications for free-threaded Python."""
2
+
3
+ from __future__ import annotations
4
+
5
+ _LAZY_IMPORTS = {
6
+ # Types
7
+ "Action": "_types",
8
+ "Key": "_types",
9
+ "SpecialKey": "_types",
10
+ "AppStatus": "_types",
11
+ "RenderTarget": "_types",
12
+ "FieldType": "_types",
13
+ "FieldSpec": "_types",
14
+ "FieldState": "_types",
15
+ "FormState": "_types",
16
+ "Screen": "_types",
17
+ "Transition": "_types",
18
+ "ReducerResult": "_types",
19
+ "Quit": "_types",
20
+ "Call": "_types",
21
+ "Put": "_types",
22
+ "Select": "_types",
23
+ "Fork": "_types",
24
+ "Delay": "_types",
25
+ "Retry": "_types",
26
+ "Timeout": "_types",
27
+ "TryCall": "_types",
28
+ "Race": "_types",
29
+ "All": "_types",
30
+ "Take": "_types",
31
+ "Debounce": "_types",
32
+ "Cmd": "_types",
33
+ "Batch": "_types",
34
+ "Sequence": "_types",
35
+ "TickCmd": "_types",
36
+ "ViewState": "_types",
37
+ "compact_cmds": "_types",
38
+ "BUILTIN_ACTIONS": "_types",
39
+ # Errors
40
+ "MiloError": "_errors",
41
+ "InputError": "_errors",
42
+ "StateError": "_errors",
43
+ "FormError": "_errors",
44
+ "AppError": "_errors",
45
+ "FlowError": "_errors",
46
+ "ConfigError": "_errors",
47
+ "PipelineError": "_errors",
48
+ "PluginError": "_errors",
49
+ "MCPAppError": "_errors",
50
+ "ErrorCode": "_errors",
51
+ "format_error": "_errors",
52
+ "format_render_error": "_errors",
53
+ # State
54
+ "Store": "state",
55
+ "SagaContext": "state",
56
+ "EffectResult": "state",
57
+ "combine_reducers": "state",
58
+ # App
59
+ "App": "app",
60
+ "run": "app",
61
+ "render_html": "app",
62
+ # Flow
63
+ "FlowScreen": "flow",
64
+ "Flow": "flow",
65
+ "FlowState": "flow",
66
+ # Form
67
+ "form": "form",
68
+ "form_reducer": "form",
69
+ "form_schema": "form",
70
+ "make_form_reducer": "form",
71
+ # Help
72
+ "HelpRenderer": "help",
73
+ # Dev
74
+ "DevServer": "dev",
75
+ # Commands (AI-native)
76
+ "CLI": "commands",
77
+ "CommandDef": "commands",
78
+ "CommandSurface": "commands",
79
+ "LazyCommandDef": "commands",
80
+ "InvokeResult": "commands",
81
+ # Groups
82
+ "Group": "groups",
83
+ "GroupDef": "groups",
84
+ "GlobalOption": "commands",
85
+ "RootOptionSpec": "commands",
86
+ # Context
87
+ "ConfirmStrategy": "context",
88
+ "Context": "context",
89
+ "NullOutputSink": "context",
90
+ "OutputSink": "context",
91
+ "get_context": "context",
92
+ "CLIProgress": "context",
93
+ # Config
94
+ "Config": "config",
95
+ "ConfigSpec": "config",
96
+ # Pipeline
97
+ "Pipeline": "pipeline",
98
+ "Phase": "pipeline",
99
+ "PhasePolicy": "pipeline",
100
+ "PhaseLog": "pipeline",
101
+ "PipelineState": "pipeline",
102
+ "PhaseStatus": "pipeline",
103
+ "PipelineViewState": "pipeline",
104
+ "make_detail_reducer": "pipeline",
105
+ "pipeline_to_timeline": "pipeline",
106
+ "set_active_pipeline": "pipeline",
107
+ "get_active_pipeline": "pipeline",
108
+ "CycleError": "pipeline",
109
+ # Plugins
110
+ "HookRegistry": "plugins",
111
+ "function_to_schema": "schema",
112
+ "validate_arguments": "schema",
113
+ "MinLen": "schema",
114
+ "MaxLen": "schema",
115
+ "Gt": "schema",
116
+ "Lt": "schema",
117
+ "Ge": "schema",
118
+ "Le": "schema",
119
+ "Pattern": "schema",
120
+ "Description": "schema",
121
+ "Positional": "schema",
122
+ "Option": "schema",
123
+ # Theme
124
+ "ThemeStyle": "theme",
125
+ "ThemeProxy": "theme",
126
+ "DEFAULT_THEME": "theme",
127
+ "make_style_filter": "theme",
128
+ "format_output": "output",
129
+ "write_output": "output",
130
+ "generate_llms_txt": "llms",
131
+ # Commands v2 (resources, prompts)
132
+ "ResourceDef": "commands",
133
+ "PromptDef": "commands",
134
+ # MCP Apps
135
+ "MCP_APPS_EXTENSION_ID": "mcp_apps",
136
+ "MCP_APPS_MIME_TYPE": "mcp_apps",
137
+ "MCPAppCSP": "mcp_apps",
138
+ "MCPAppPermissions": "mcp_apps",
139
+ "MCPAppResourceDef": "mcp_apps",
140
+ "MCPAppResourceMeta": "mcp_apps",
141
+ "MCPAppToolMeta": "mcp_apps",
142
+ "MCPAppVisibility": "mcp_apps",
143
+ # Middleware
144
+ "MCPCall": "middleware",
145
+ "MiddlewareStack": "middleware",
146
+ # Streaming
147
+ "Progress": "streaming",
148
+ # Observability
149
+ "RequestLog": "observability",
150
+ "RequestLogger": "observability",
151
+ # Completions
152
+ "install_completions": "completions",
153
+ # Doctor
154
+ "Check": "doctor",
155
+ "DoctorReport": "doctor",
156
+ "run_doctor": "doctor",
157
+ "format_doctor_report": "doctor",
158
+ # Reducer combinators
159
+ "quit_on": "reducers",
160
+ "with_cursor": "reducers",
161
+ "with_confirm": "reducers",
162
+ # Version check
163
+ "VersionInfo": "version_check",
164
+ "check_version": "version_check",
165
+ }
166
+
167
+
168
+ def __getattr__(name: str):
169
+ """Lazy imports for public API."""
170
+ if name in _LAZY_IMPORTS:
171
+ import importlib
172
+
173
+ module = importlib.import_module(f"milo.{_LAZY_IMPORTS[name]}")
174
+ return getattr(module, name)
175
+ raise AttributeError(f"module 'milo' has no attribute {name!r}")
176
+
177
+
178
+ # Free-threaded Python marker (PEP 703)
179
+ def _Py_mod_gil() -> int: # noqa: N802
180
+ return 0
181
+
182
+
183
+ __version__ = "0.4.0"
184
+ __all__ = [
185
+ "BUILTIN_ACTIONS",
186
+ "CLI",
187
+ "DEFAULT_THEME",
188
+ "MCP_APPS_EXTENSION_ID",
189
+ "MCP_APPS_MIME_TYPE",
190
+ "Action",
191
+ "All",
192
+ "App",
193
+ "AppError",
194
+ "AppStatus",
195
+ "Batch",
196
+ "CLIProgress",
197
+ "Call",
198
+ "Check",
199
+ "Cmd",
200
+ "CommandDef",
201
+ "CommandSurface",
202
+ "Config",
203
+ "ConfigError",
204
+ "ConfigSpec",
205
+ "ConfirmStrategy",
206
+ "Context",
207
+ "CycleError",
208
+ "Debounce",
209
+ "Delay",
210
+ "Description",
211
+ "DevServer",
212
+ "DoctorReport",
213
+ "EffectResult",
214
+ "ErrorCode",
215
+ "FieldSpec",
216
+ "FieldState",
217
+ "FieldType",
218
+ "Flow",
219
+ "FlowError",
220
+ "FlowScreen",
221
+ "FlowState",
222
+ "Fork",
223
+ "FormError",
224
+ "FormState",
225
+ "Ge",
226
+ "GlobalOption",
227
+ "Group",
228
+ "GroupDef",
229
+ "Gt",
230
+ "HelpRenderer",
231
+ "HookRegistry",
232
+ "InputError",
233
+ "InvokeResult",
234
+ "Key",
235
+ "LazyCommandDef",
236
+ "Le",
237
+ "Lt",
238
+ "MCPAppCSP",
239
+ "MCPAppError",
240
+ "MCPAppPermissions",
241
+ "MCPAppResourceDef",
242
+ "MCPAppResourceMeta",
243
+ "MCPAppToolMeta",
244
+ "MCPAppVisibility",
245
+ "MCPCall",
246
+ "MaxLen",
247
+ "MiddlewareStack",
248
+ "MiloError",
249
+ "MinLen",
250
+ "NullOutputSink",
251
+ "Option",
252
+ "OutputSink",
253
+ "Pattern",
254
+ "Phase",
255
+ "PhaseLog",
256
+ "PhasePolicy",
257
+ "PhaseStatus",
258
+ "Pipeline",
259
+ "PipelineError",
260
+ "PipelineState",
261
+ "PipelineViewState",
262
+ "PluginError",
263
+ "Positional",
264
+ "Progress",
265
+ "PromptDef",
266
+ "Put",
267
+ "Quit",
268
+ "Race",
269
+ "ReducerResult",
270
+ "RenderTarget",
271
+ "RequestLog",
272
+ "RequestLogger",
273
+ "ResourceDef",
274
+ "Retry",
275
+ "RootOptionSpec",
276
+ "SagaContext",
277
+ "Screen",
278
+ "Select",
279
+ "Sequence",
280
+ "SpecialKey",
281
+ "StateError",
282
+ "Store",
283
+ "Take",
284
+ "ThemeProxy",
285
+ "ThemeStyle",
286
+ "TickCmd",
287
+ "Timeout",
288
+ "Transition",
289
+ "TryCall",
290
+ "VersionInfo",
291
+ "ViewState",
292
+ "check_version",
293
+ "combine_reducers",
294
+ "compact_cmds",
295
+ "form",
296
+ "form_reducer",
297
+ "form_schema",
298
+ "format_doctor_report",
299
+ "format_error",
300
+ "format_output",
301
+ "format_render_error",
302
+ "function_to_schema",
303
+ "generate_llms_txt",
304
+ "get_active_pipeline",
305
+ "get_context",
306
+ "install_completions",
307
+ "make_detail_reducer",
308
+ "make_form_reducer",
309
+ "make_style_filter",
310
+ "pipeline_to_timeline",
311
+ "quit_on",
312
+ "render_html",
313
+ "run",
314
+ "run_doctor",
315
+ "set_active_pipeline",
316
+ "validate_arguments",
317
+ "with_confirm",
318
+ "with_cursor",
319
+ "write_output",
320
+ ]
@@ -35,6 +35,11 @@ def cell_width(value: object) -> int:
35
35
  return width
36
36
 
37
37
 
38
+ def max_cell_width(value: object) -> int:
39
+ """Return the widest display-cell width among the text's lines."""
40
+ return max((cell_width(line) for line in str(value).splitlines()), default=0)
41
+
42
+
38
43
  def cell_ljust(value: object, width: int, fill: str = " ") -> str:
39
44
  """Left-justify *value* to display-cell *width*."""
40
45
  text = str(value)