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