milo-cli 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {milo_cli-0.2.0 → milo_cli-0.2.2}/PKG-INFO +5 -4
- {milo_cli-0.2.0 → milo_cli-0.2.2}/README.md +3 -2
- {milo_cli-0.2.0 → milo_cli-0.2.2}/pyproject.toml +7 -4
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/__init__.py +13 -1
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_command_defs.py +37 -6
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_jsonrpc.py +7 -2
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_types.py +10 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/app.py +16 -2
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/commands.py +445 -201
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/config.py +29 -4
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/context.py +37 -3
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/form.py +74 -22
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/gateway.py +56 -4
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/groups.py +48 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/help.py +20 -2
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/mcp.py +85 -4
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/output.py +11 -1
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/pipeline.py +458 -11
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/plugins.py +22 -5
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/registry.py +15 -3
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/schema.py +59 -12
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/state.py +73 -15
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/__init__.py +3 -2
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/components/_defs.kida +85 -0
- milo_cli-0.2.2/src/milo/templates/help.kida +30 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/testing/_record.py +6 -1
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/version_check.py +20 -13
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo_cli.egg-info/PKG-INFO +5 -4
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo_cli.egg-info/SOURCES.txt +4 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo_cli.egg-info/requires.txt +1 -1
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_ai_native.py +68 -3
- milo_cli-0.2.2/tests/test_command_defs.py +294 -0
- milo_cli-0.2.2/tests/test_commands_core.py +457 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_components.py +139 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_config.py +82 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_context.py +32 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_effects.py +13 -12
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_effects_stress.py +1 -1
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_form.py +58 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_gateway.py +47 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_groups.py +139 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_help.py +17 -1
- milo_cli-0.2.2/tests/test_lazy.py +678 -0
- milo_cli-0.2.2/tests/test_mcp_handler.py +400 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_mcp_resources.py +62 -2
- milo_cli-0.2.2/tests/test_pipeline.py +1318 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_plugins.py +47 -0
- milo_cli-0.2.2/tests/test_registry_crud.py +122 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_schema_v2.py +55 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_state.py +52 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_testing.py +14 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_types.py +2 -1
- milo_cli-0.2.0/src/milo/templates/help.kida +0 -13
- milo_cli-0.2.0/tests/test_lazy.py +0 -332
- milo_cli-0.2.0/tests/test_pipeline.py +0 -614
- {milo_cli-0.2.0 → milo_cli-0.2.2}/setup.cfg +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_child.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_cli_help.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_compat.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_errors.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_mcp_router.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/_protocols.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/cli.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/completions.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/dev.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/doctor.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/flow.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/input/__init__.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/input/_platform.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/input/_reader.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/input/_sequences.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/llms.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/middleware.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/observability.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/py.typed +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/reducers.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/streaming.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/components/command_list.kida +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/components/help_page.kida +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/error.kida +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/field_confirm.kida +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/field_select.kida +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/field_text.kida +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/form.kida +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/templates/progress.kida +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/testing/__init__.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/testing/_mcp.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/testing/_replay.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/testing/_snapshot.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo/theme.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo_cli.egg-info/dependency_links.txt +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo_cli.egg-info/entry_points.txt +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/src/milo_cli.egg-info/top_level.txt +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_app.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_bubbletea_patterns.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_child.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_cli.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_commands_middleware.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_compat.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_completions.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_dev.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_doctor.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_errors.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_flow.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_input.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_mcp_prompts.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_mcp_router.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_middleware.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_milo_init.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_mount.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_observability.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_protocols.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_reducers.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_registry_v2.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_streaming.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_templates.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_testing_mcp.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/tests/test_theme.py +0 -0
- {milo_cli-0.2.0 → milo_cli-0.2.2}/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.
|
|
3
|
+
Version: 0.2.2
|
|
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,7 +18,7 @@ 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.
|
|
21
|
+
Requires-Dist: kida-templates>=0.6.0
|
|
22
22
|
Provides-Extra: docs
|
|
23
23
|
Requires-Dist: bengal>=0.2.6; extra == "docs"
|
|
24
24
|
Provides-Extra: yaml
|
|
@@ -150,7 +150,7 @@ Requires Python 3.14+
|
|
|
150
150
|
| **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
|
|
151
151
|
| **State Management** | Redux-style `Store` with dispatch, listeners, middleware, and saga scheduling | [State →](https://lbliii.github.io/milo-cli/docs/usage/state/) |
|
|
152
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/) |
|
|
153
|
+
| **Sagas** | Generator-based side effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Race`, `All`, `Take`, and more | [Sagas →](https://lbliii.github.io/milo-cli/docs/usage/sagas/) |
|
|
154
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
155
|
| **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/usage/flows/) |
|
|
156
156
|
| **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/usage/forms/) |
|
|
@@ -159,6 +159,7 @@ Requires Python 3.14+
|
|
|
159
159
|
| **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/usage/dev/) |
|
|
160
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
161
|
| **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/usage/testing/) |
|
|
162
|
+
| **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/usage/pipeline/) |
|
|
162
163
|
| **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/usage/help/) |
|
|
163
164
|
| **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/usage/context/) |
|
|
164
165
|
| **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/usage/config/) |
|
|
@@ -343,7 +344,7 @@ def reducer(state, action):
|
|
|
343
344
|
return state
|
|
344
345
|
```
|
|
345
346
|
|
|
346
|
-
Saga effects: `Call
|
|
347
|
+
Saga effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Timeout`, `TryCall`, `Race`, `All`, `Take`, `Debounce`, `TakeEvery`, `TakeLatest`.
|
|
347
348
|
|
|
348
349
|
For one-shot effects, use `Cmd` instead — no generator needed:
|
|
349
350
|
|
|
@@ -122,7 +122,7 @@ Requires Python 3.14+
|
|
|
122
122
|
| **Observability** | Built-in request logging with latency stats (`milo://stats` resource) | [MCP →](https://lbliii.github.io/milo-cli/docs/usage/mcp/) |
|
|
123
123
|
| **State Management** | Redux-style `Store` with dispatch, listeners, middleware, and saga scheduling | [State →](https://lbliii.github.io/milo-cli/docs/usage/state/) |
|
|
124
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/) |
|
|
125
|
+
| **Sagas** | Generator-based side effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Race`, `All`, `Take`, and more | [Sagas →](https://lbliii.github.io/milo-cli/docs/usage/sagas/) |
|
|
126
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
127
|
| **Flows** | Multi-screen state machines with `>>` operator and custom transitions | [Flows →](https://lbliii.github.io/milo-cli/docs/usage/flows/) |
|
|
128
128
|
| **Forms** | Text, select, confirm, password fields with validation and TTY fallback | [Forms →](https://lbliii.github.io/milo-cli/docs/usage/forms/) |
|
|
@@ -131,6 +131,7 @@ Requires Python 3.14+
|
|
|
131
131
|
| **Dev Server** | `milo dev` with filesystem polling and `@@HOT_RELOAD` dispatch | [Dev →](https://lbliii.github.io/milo-cli/docs/usage/dev/) |
|
|
132
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
133
|
| **Snapshot Testing** | `assert_renders`, `assert_state`, `assert_saga` for deterministic test coverage | [Testing →](https://lbliii.github.io/milo-cli/docs/usage/testing/) |
|
|
134
|
+
| **Pipeline** | Declarative multi-phase workflows with dependency graphs, retry policies, and output capture | [Pipeline →](https://lbliii.github.io/milo-cli/docs/usage/pipeline/) |
|
|
134
135
|
| **Help Rendering** | `HelpRenderer` — drop-in `argparse.HelpFormatter` using Kida templates | [Help →](https://lbliii.github.io/milo-cli/docs/usage/help/) |
|
|
135
136
|
| **Context** | Execution context with verbosity, output format, global options, and `run_app()` bridge | [Context →](https://lbliii.github.io/milo-cli/docs/usage/context/) |
|
|
136
137
|
| **Configuration** | `Config` with validation, init scaffolding, and profile support | [Config →](https://lbliii.github.io/milo-cli/docs/usage/config/) |
|
|
@@ -315,7 +316,7 @@ def reducer(state, action):
|
|
|
315
316
|
return state
|
|
316
317
|
```
|
|
317
318
|
|
|
318
|
-
Saga effects: `Call
|
|
319
|
+
Saga effects: `Call`, `Put`, `Select`, `Fork`, `Delay`, `Retry`, `Timeout`, `TryCall`, `Race`, `All`, `Take`, `Debounce`, `TakeEvery`, `TakeLatest`.
|
|
319
320
|
|
|
320
321
|
For one-shot effects, use `Cmd` instead — no generator needed:
|
|
321
322
|
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "milo-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Template-driven CLI applications for free-threaded Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14"
|
|
11
11
|
license = "MIT"
|
|
12
|
-
dependencies = ["kida-templates>=0.
|
|
12
|
+
dependencies = ["kida-templates>=0.6.0"]
|
|
13
13
|
keywords = ["cli", "terminal", "forms", "free-threading", "template", "elm"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
@@ -123,7 +123,7 @@ ignore = [
|
|
|
123
123
|
|
|
124
124
|
[tool.ruff.lint.per-file-ignores]
|
|
125
125
|
"__init__.py" = ["F401"]
|
|
126
|
-
"tests/**/*.py" = ["S101", "S108", "S110", "S604", "SIM117", "N806", "ARG"]
|
|
126
|
+
"tests/**/*.py" = ["S101", "S108", "S110", "S604", "SIM117", "N806", "ARG", "T201"]
|
|
127
127
|
"src/milo/_child.py" = ["S101"] # asserts for type narrowing on subprocess pipes
|
|
128
128
|
"src/milo/testing/*.py" = ["S101", "ARG"] # test helpers use assert and may have unused args
|
|
129
129
|
"src/milo/version_check.py" = ["S110", "S310"] # best-effort cache reads + known PyPI URL
|
|
@@ -136,7 +136,7 @@ directory = "changelog.d"
|
|
|
136
136
|
filename = "CHANGELOG.md"
|
|
137
137
|
package = "milo"
|
|
138
138
|
package_dir = "src"
|
|
139
|
-
title_format = "##
|
|
139
|
+
title_format = "## {version} — {project_date}"
|
|
140
140
|
issue_format = "[#{issue}](https://github.com/lbliii/milo-cli/issues/{issue})"
|
|
141
141
|
underlines = ["", "", ""]
|
|
142
142
|
|
|
@@ -187,3 +187,6 @@ dev = [
|
|
|
187
187
|
# Changelog
|
|
188
188
|
"towncrier>=24.0",
|
|
189
189
|
]
|
|
190
|
+
docs = [
|
|
191
|
+
"bengal>=0.2.6",
|
|
192
|
+
]
|
|
@@ -93,8 +93,14 @@ def __getattr__(name: str):
|
|
|
93
93
|
"Pipeline": "pipeline",
|
|
94
94
|
"Phase": "pipeline",
|
|
95
95
|
"PhasePolicy": "pipeline",
|
|
96
|
+
"PhaseLog": "pipeline",
|
|
96
97
|
"PipelineState": "pipeline",
|
|
97
98
|
"PhaseStatus": "pipeline",
|
|
99
|
+
"PipelineViewState": "pipeline",
|
|
100
|
+
"make_detail_reducer": "pipeline",
|
|
101
|
+
"pipeline_to_timeline": "pipeline",
|
|
102
|
+
"set_active_pipeline": "pipeline",
|
|
103
|
+
"get_active_pipeline": "pipeline",
|
|
98
104
|
"CycleError": "pipeline",
|
|
99
105
|
# Plugins
|
|
100
106
|
"HookRegistry": "plugins",
|
|
@@ -154,7 +160,7 @@ def _Py_mod_gil() -> int: # noqa: N802
|
|
|
154
160
|
return 0
|
|
155
161
|
|
|
156
162
|
|
|
157
|
-
__version__ = "0.2.
|
|
163
|
+
__version__ = "0.2.2"
|
|
158
164
|
__all__ = [
|
|
159
165
|
"BUILTIN_ACTIONS",
|
|
160
166
|
"CLI",
|
|
@@ -211,11 +217,13 @@ __all__ = [
|
|
|
211
217
|
"MinLen",
|
|
212
218
|
"Pattern",
|
|
213
219
|
"Phase",
|
|
220
|
+
"PhaseLog",
|
|
214
221
|
"PhasePolicy",
|
|
215
222
|
"PhaseStatus",
|
|
216
223
|
"Pipeline",
|
|
217
224
|
"PipelineError",
|
|
218
225
|
"PipelineState",
|
|
226
|
+
"PipelineViewState",
|
|
219
227
|
"PluginError",
|
|
220
228
|
"Progress",
|
|
221
229
|
"PromptDef",
|
|
@@ -254,14 +262,18 @@ __all__ = [
|
|
|
254
262
|
"format_render_error",
|
|
255
263
|
"function_to_schema",
|
|
256
264
|
"generate_llms_txt",
|
|
265
|
+
"get_active_pipeline",
|
|
257
266
|
"get_context",
|
|
258
267
|
"install_completions",
|
|
268
|
+
"make_detail_reducer",
|
|
259
269
|
"make_form_reducer",
|
|
260
270
|
"make_style_filter",
|
|
271
|
+
"pipeline_to_timeline",
|
|
261
272
|
"quit_on",
|
|
262
273
|
"render_html",
|
|
263
274
|
"run",
|
|
264
275
|
"run_doctor",
|
|
276
|
+
"set_active_pipeline",
|
|
265
277
|
"with_confirm",
|
|
266
278
|
"with_cursor",
|
|
267
279
|
"write_output",
|
|
@@ -59,6 +59,8 @@ class CommandDef:
|
|
|
59
59
|
"""If non-empty, prompt for confirmation before running."""
|
|
60
60
|
annotations: dict[str, Any] = field(default_factory=dict)
|
|
61
61
|
"""MCP tool annotations (readOnlyHint, destructiveHint, etc.)."""
|
|
62
|
+
display_result: bool = True
|
|
63
|
+
"""If False, suppress plain-format output (return value still available for --format json)."""
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
class LazyCommandDef:
|
|
@@ -80,6 +82,7 @@ class LazyCommandDef:
|
|
|
80
82
|
"annotations",
|
|
81
83
|
"confirm",
|
|
82
84
|
"description",
|
|
85
|
+
"display_result",
|
|
83
86
|
"examples",
|
|
84
87
|
"hidden",
|
|
85
88
|
"import_path",
|
|
@@ -100,6 +103,7 @@ class LazyCommandDef:
|
|
|
100
103
|
examples: tuple[dict[str, Any], ...] | list[dict[str, Any]] = (),
|
|
101
104
|
confirm: str = "",
|
|
102
105
|
annotations: dict[str, Any] | None = None,
|
|
106
|
+
display_result: bool = True,
|
|
103
107
|
) -> None:
|
|
104
108
|
self.name = name
|
|
105
109
|
self.description = description
|
|
@@ -110,6 +114,7 @@ class LazyCommandDef:
|
|
|
110
114
|
self.examples = tuple(examples)
|
|
111
115
|
self.confirm = confirm
|
|
112
116
|
self.annotations = annotations or {}
|
|
117
|
+
self.display_result = display_result
|
|
113
118
|
self._schema = schema
|
|
114
119
|
self._resolved: CommandDef | None = None
|
|
115
120
|
self._lock = threading.Lock()
|
|
@@ -127,7 +132,11 @@ class LazyCommandDef:
|
|
|
127
132
|
return self.resolve().handler
|
|
128
133
|
|
|
129
134
|
def resolve(self) -> CommandDef:
|
|
130
|
-
"""Import the handler and cache as a full CommandDef. Thread-safe.
|
|
135
|
+
"""Import the handler and cache as a full CommandDef. Thread-safe.
|
|
136
|
+
|
|
137
|
+
Raises :class:`LazyImportError` if the module or attribute cannot
|
|
138
|
+
be imported, wrapping the original exception with a clear message.
|
|
139
|
+
"""
|
|
131
140
|
if self._resolved is not None:
|
|
132
141
|
return self._resolved
|
|
133
142
|
|
|
@@ -139,10 +148,13 @@ class LazyCommandDef:
|
|
|
139
148
|
module_path, _, attr_name = self.import_path.rpartition(":")
|
|
140
149
|
if not module_path or not attr_name:
|
|
141
150
|
msg = f"Invalid import_path {self.import_path!r}: expected 'module.path:attribute'"
|
|
142
|
-
raise ValueError(msg)
|
|
151
|
+
raise LazyImportError(self.name, self.import_path, ValueError(msg))
|
|
143
152
|
|
|
144
|
-
|
|
145
|
-
|
|
153
|
+
try:
|
|
154
|
+
module = importlib.import_module(module_path)
|
|
155
|
+
handler = getattr(module, attr_name)
|
|
156
|
+
except Exception as exc:
|
|
157
|
+
raise LazyImportError(self.name, self.import_path, exc) from exc
|
|
146
158
|
|
|
147
159
|
from milo.schema import function_to_schema
|
|
148
160
|
|
|
@@ -159,6 +171,7 @@ class LazyCommandDef:
|
|
|
159
171
|
examples=self.examples,
|
|
160
172
|
confirm=self.confirm,
|
|
161
173
|
annotations=self.annotations,
|
|
174
|
+
display_result=self.display_result,
|
|
162
175
|
)
|
|
163
176
|
return self._resolved
|
|
164
177
|
|
|
@@ -174,6 +187,20 @@ class InvokeResult:
|
|
|
174
187
|
stderr: str = ""
|
|
175
188
|
|
|
176
189
|
|
|
190
|
+
class LazyImportError(Exception):
|
|
191
|
+
"""Raised when a lazy command's import fails.
|
|
192
|
+
|
|
193
|
+
Wraps the original exception with the command name and import path
|
|
194
|
+
so callers can provide actionable error messages.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def __init__(self, command_name: str, import_path: str, cause: Exception) -> None:
|
|
198
|
+
self.command_name = command_name
|
|
199
|
+
self.import_path = import_path
|
|
200
|
+
self.cause = cause
|
|
201
|
+
super().__init__(f"Command {command_name!r} failed to import from {import_path!r}: {cause}")
|
|
202
|
+
|
|
203
|
+
|
|
177
204
|
def _make_command_def(
|
|
178
205
|
name: str,
|
|
179
206
|
func: Callable,
|
|
@@ -185,6 +212,7 @@ def _make_command_def(
|
|
|
185
212
|
examples: tuple[dict[str, Any], ...] = (),
|
|
186
213
|
confirm: str = "",
|
|
187
214
|
annotations: dict[str, Any] | None = None,
|
|
215
|
+
display_result: bool = True,
|
|
188
216
|
) -> CommandDef:
|
|
189
217
|
"""Build a CommandDef from a function and decorator kwargs."""
|
|
190
218
|
from milo.schema import function_to_schema
|
|
@@ -204,17 +232,20 @@ def _make_command_def(
|
|
|
204
232
|
examples=examples,
|
|
205
233
|
confirm=confirm,
|
|
206
234
|
annotations=annotations or {},
|
|
235
|
+
display_result=display_result,
|
|
207
236
|
)
|
|
208
237
|
|
|
209
238
|
|
|
210
239
|
def _is_context_param(param: inspect.Parameter) -> bool:
|
|
211
240
|
"""Check if a parameter is a Context injection point."""
|
|
241
|
+
from milo.context import Context as _MiloContext
|
|
242
|
+
|
|
212
243
|
annotation = param.annotation
|
|
213
244
|
if annotation is inspect.Parameter.empty:
|
|
214
245
|
return False
|
|
215
|
-
# Check for
|
|
246
|
+
# Check for exact type identity or subclass of milo.context.Context
|
|
216
247
|
if isinstance(annotation, type):
|
|
217
|
-
return annotation
|
|
248
|
+
return issubclass(annotation, _MiloContext)
|
|
218
249
|
if isinstance(annotation, str):
|
|
219
250
|
return annotation in ("Context", "milo.context.Context")
|
|
220
251
|
return False
|
|
@@ -15,11 +15,16 @@ def _write_result(req_id: Any, result: dict[str, Any]) -> None:
|
|
|
15
15
|
sys.stdout.flush()
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def _write_error(
|
|
18
|
+
def _write_error(
|
|
19
|
+
req_id: Any, code: int, message: str, *, data: dict[str, Any] | None = None
|
|
20
|
+
) -> None:
|
|
21
|
+
error_obj: dict[str, Any] = {"code": code, "message": message}
|
|
22
|
+
if data is not None:
|
|
23
|
+
error_obj["data"] = data
|
|
19
24
|
response = {
|
|
20
25
|
"jsonrpc": "2.0",
|
|
21
26
|
"id": req_id,
|
|
22
|
-
"error":
|
|
27
|
+
"error": error_obj,
|
|
23
28
|
}
|
|
24
29
|
sys.stdout.write(json.dumps(response) + "\n")
|
|
25
30
|
sys.stdout.flush()
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import warnings
|
|
5
6
|
from collections.abc import Callable, Generator
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
8
|
from enum import Enum, auto
|
|
@@ -82,6 +83,7 @@ BUILTIN_ACTIONS: frozenset[str] = frozenset(
|
|
|
82
83
|
"@@PHASE_FAILED",
|
|
83
84
|
"@@PHASE_SKIPPED",
|
|
84
85
|
"@@PHASE_RETRY",
|
|
86
|
+
"@@PHASE_LOG",
|
|
85
87
|
"@@SAGA_ERROR",
|
|
86
88
|
"@@SAGA_CANCELLED",
|
|
87
89
|
"@@CMD_ERROR",
|
|
@@ -326,6 +328,14 @@ class Take:
|
|
|
326
328
|
action_type: str
|
|
327
329
|
timeout: float | None = None
|
|
328
330
|
|
|
331
|
+
def __post_init__(self) -> None:
|
|
332
|
+
if self.timeout is None:
|
|
333
|
+
warnings.warn(
|
|
334
|
+
f"Take({self.action_type!r}) has no timeout and may block forever. "
|
|
335
|
+
f"Consider setting timeout= to prevent silent hangs.",
|
|
336
|
+
stacklevel=2,
|
|
337
|
+
)
|
|
338
|
+
|
|
329
339
|
|
|
330
340
|
@dataclass(frozen=True, slots=True)
|
|
331
341
|
class Debounce:
|
|
@@ -241,7 +241,14 @@ class App:
|
|
|
241
241
|
from milo.templates import get_env
|
|
242
242
|
|
|
243
243
|
env = get_env()
|
|
244
|
-
|
|
244
|
+
try:
|
|
245
|
+
tmpl = env.get_template(template)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
raise AppError(
|
|
248
|
+
ErrorCode.APP_RENDER,
|
|
249
|
+
f"Template not found: {template!r}",
|
|
250
|
+
suggestion="Ensure the template file exists and the kida environment is configured correctly.",
|
|
251
|
+
) from e
|
|
245
252
|
return tmpl.render(state=state)
|
|
246
253
|
|
|
247
254
|
def run(self) -> Any:
|
|
@@ -405,7 +412,14 @@ class App:
|
|
|
405
412
|
|
|
406
413
|
def _render_exit(self, state: Any, env: Any) -> None:
|
|
407
414
|
"""Render the exit template once to stdout."""
|
|
408
|
-
|
|
415
|
+
try:
|
|
416
|
+
template = env.get_template(self._exit_template)
|
|
417
|
+
except Exception as e:
|
|
418
|
+
raise AppError(
|
|
419
|
+
ErrorCode.APP_RENDER,
|
|
420
|
+
f"Exit template not found: {self._exit_template!r}",
|
|
421
|
+
suggestion="Ensure the exit template file exists and the kida environment is configured correctly.",
|
|
422
|
+
) from e
|
|
409
423
|
render_state = state
|
|
410
424
|
if isinstance(state, FlowState):
|
|
411
425
|
# For flows, pass all screen states so exit template can reference any data
|