soothe-cli 0.4.7__tar.gz → 0.4.9__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.
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/PKG-INFO +3 -3
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/README.md +1 -1
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/pyproject.toml +1 -1
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/commands/loop_cmd.py +63 -55
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/execution/daemon.py +0 -2
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/config/cli_config.py +0 -13
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/core/event_processor.py +2 -8
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/message_processing.py +1 -1
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py +47 -20
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/_session_stats.py +6 -3
- soothe_cli-0.4.9/src/soothe_cli/tui/app/__init__.py +19 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_app.py +453 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_commands.py +454 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_execution.py +978 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_history.py +860 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_messages_mixin.py +827 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_model.py +731 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_module_init.py +507 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_startup.py +987 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/app/_ui.py +606 -0
- {soothe_cli-0.4.7/src/soothe_cli/tui → soothe_cli-0.4.9/src/soothe_cli/tui/app}/app.tcss +15 -5
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/model_config.py +3 -3
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/preview_limits.py +1 -1
- soothe_cli-0.4.9/src/soothe_cli/tui/textual_adapter/__init__.py +29 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/textual_adapter/_adapter.py +266 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/textual_adapter/_stream_formatting.py +285 -0
- soothe_cli-0.4.9/src/soothe_cli/tui/textual_adapter/_stream_messages.py +256 -0
- soothe_cli-0.4.7/src/soothe_cli/tui/textual_adapter.py → soothe_cli-0.4.9/src/soothe_cli/tui/textual_adapter/_turn.py +514 -1093
- soothe_cli-0.4.9/src/soothe_cli/tui/textual_adapter/_turn_helpers.py +379 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/tool_display.py +96 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/_links.py +6 -1
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/clipboard.py +67 -33
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/loading.py +51 -18
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/message_store.py +31 -1
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/messages.py +1115 -123
- soothe_cli-0.4.7/src/soothe_cli/tui/app.py +0 -5632
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/.gitignore +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/commands/thread_cmd.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/main.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/stream/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/stream/context.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/stream/display_line.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/stream/formatter.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/stream/pipeline.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/cli/stream/task_scope.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/plan/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/plan/rich_tree.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/commands/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/commands/command_router.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/commands/slash_commands.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/commands/subagent_routing.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/config_loader.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/core/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/core/presentation_engine.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/core/processor_state.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/core/renderer_protocol.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/events/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/events/display_policy.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/events/essential_events.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/events/explore_task_display.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/events/stream_accumulator.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/events/tui_trace_log.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/rendering/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/rendering/async_renderer_protocol.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/rendering/renderer_base.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/_utils.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/rendering.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_call_resolution.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_card_payload.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_card_visibility.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/base.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/execution.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/fallback.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/goal_formatter.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/media.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/structured.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/subagent.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/web.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_message_format.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_output_formatter.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/_ask_user_types.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/command_registry.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/config.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/daemon_session.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/file_ops.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/formatting.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/message_display_filter.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/output.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/sessions.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/approval.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/thread_selector.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/tools.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/tui/widgets/welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soothe-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.9
|
|
4
4
|
Summary: Soothe CLI client - communicates with daemon via WebSocket
|
|
5
5
|
Project-URL: Homepage, https://github.com/OpenSoothe/soothe
|
|
6
6
|
Project-URL: Documentation, https://soothe.readthedocs.io
|
|
@@ -22,7 +22,7 @@ Requires-Dist: python-dotenv<2.0.0,>=1.0.0
|
|
|
22
22
|
Requires-Dist: pyyaml<7.0.0,>=6.0.0
|
|
23
23
|
Requires-Dist: rich>=13.0.0
|
|
24
24
|
Requires-Dist: soothe-sdk<1.0.0,>=0.4.0
|
|
25
|
-
Requires-Dist: textual>=0.
|
|
25
|
+
Requires-Dist: textual>=8.0.0
|
|
26
26
|
Requires-Dist: typer<1.0.0,>=0.9.0
|
|
27
27
|
Requires-Dist: websockets>=12.0
|
|
28
28
|
Provides-Extra: dev
|
|
@@ -71,7 +71,7 @@ This package is the **client** component that communicates with the Soothe daemo
|
|
|
71
71
|
|
|
72
72
|
- `soothe-sdk>=0.2.0` - WebSocket client, protocol, types
|
|
73
73
|
- `typer>=0.9.0` - CLI framework
|
|
74
|
-
- `textual>=0.
|
|
74
|
+
- `textual>=8.0.0` - TUI framework
|
|
75
75
|
- `rich>=13.0.0` - Console output
|
|
76
76
|
|
|
77
77
|
## Configuration
|
|
@@ -36,7 +36,7 @@ This package is the **client** component that communicates with the Soothe daemo
|
|
|
36
36
|
|
|
37
37
|
- `soothe-sdk>=0.2.0` - WebSocket client, protocol, types
|
|
38
38
|
- `typer>=0.9.0` - CLI framework
|
|
39
|
-
- `textual>=0.
|
|
39
|
+
- `textual>=8.0.0` - TUI framework
|
|
40
40
|
- `rich>=13.0.0` - Console output
|
|
41
41
|
|
|
42
42
|
## Configuration
|
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
dependencies = [
|
|
26
26
|
"soothe-sdk>=0.4.0,<1.0.0", # WebSocket client, protocol, types
|
|
27
27
|
"typer>=0.9.0,<1.0.0", # CLI framework
|
|
28
|
-
"textual>=0.
|
|
28
|
+
"textual>=8.0.0", # TUI framework
|
|
29
29
|
"rich>=13.0.0", # Console output
|
|
30
30
|
"pyyaml>=6.0.0,<7.0.0", # Config loading
|
|
31
31
|
"python-dotenv>=1.0.0,<2.0.0", # .env loading
|
|
@@ -83,6 +83,55 @@ async def _rpc(
|
|
|
83
83
|
await client.close()
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def _resolve_continue_loop_id(ws_url: str, loop_id: str | None) -> str:
|
|
87
|
+
"""Resolve target loop ID for `loop continue`.
|
|
88
|
+
|
|
89
|
+
If `loop_id` is omitted, chooses the most recent loop, preferring active
|
|
90
|
+
statuses such as `running` and `detached`.
|
|
91
|
+
"""
|
|
92
|
+
if loop_id:
|
|
93
|
+
return loop_id
|
|
94
|
+
|
|
95
|
+
response = asyncio.run(
|
|
96
|
+
_rpc(
|
|
97
|
+
ws_url,
|
|
98
|
+
"send_loop_list",
|
|
99
|
+
{"filter_dict": None, "limit": 20},
|
|
100
|
+
"loop_list_response",
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
if "error" in response:
|
|
104
|
+
typer.echo(f"Error: {response['error']}", err=True)
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
|
|
107
|
+
loops = response.get("loops", [])
|
|
108
|
+
if not loops:
|
|
109
|
+
typer.echo(
|
|
110
|
+
"Error: No loops found. Start one first with `soothe loop new`.",
|
|
111
|
+
err=True,
|
|
112
|
+
)
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
preferred_statuses = {"running", "detached"}
|
|
116
|
+
selected = next(
|
|
117
|
+
(loop for loop in loops if loop.get("status") in preferred_statuses),
|
|
118
|
+
loops[0],
|
|
119
|
+
)
|
|
120
|
+
selected_loop_id = str(selected.get("loop_id", "")).strip()
|
|
121
|
+
if not selected_loop_id:
|
|
122
|
+
typer.echo(
|
|
123
|
+
"Error: Unable to resolve loop ID from loop list response.",
|
|
124
|
+
err=True,
|
|
125
|
+
)
|
|
126
|
+
sys.exit(1)
|
|
127
|
+
|
|
128
|
+
console.print(
|
|
129
|
+
"[info]No LOOP_ID provided; using most recent loop: "
|
|
130
|
+
f"{selected_loop_id} ({selected.get('status', 'unknown')})[/info]"
|
|
131
|
+
)
|
|
132
|
+
return selected_loop_id
|
|
133
|
+
|
|
134
|
+
|
|
86
135
|
@loop_app.command("list")
|
|
87
136
|
def list_loops(
|
|
88
137
|
status: Annotated[
|
|
@@ -632,7 +681,7 @@ def render_dot_tree(tree: dict[str, Any]) -> None:
|
|
|
632
681
|
|
|
633
682
|
@loop_app.command("continue")
|
|
634
683
|
def continue_loop(
|
|
635
|
-
loop_id: Annotated[str, typer.Argument(help="Loop identifier to continue")],
|
|
684
|
+
loop_id: Annotated[str | None, typer.Argument(help="Loop identifier to continue")] = None,
|
|
636
685
|
prompt: Annotated[
|
|
637
686
|
str | None,
|
|
638
687
|
typer.Option("--prompt", "-p", help="Optional prompt to send after continuing."),
|
|
@@ -643,70 +692,29 @@ def continue_loop(
|
|
|
643
692
|
Replaces: soothe thread continue <thread_id>
|
|
644
693
|
|
|
645
694
|
Behavior:
|
|
646
|
-
-
|
|
647
|
-
-
|
|
648
|
-
-
|
|
649
|
-
- Display loop status
|
|
695
|
+
- Resolve target loop (explicit `LOOP_ID` or most-recent loop)
|
|
696
|
+
- Launch TUI on that loop
|
|
697
|
+
- Optionally submit initial prompt in the resumed session
|
|
650
698
|
|
|
651
699
|
Example:
|
|
700
|
+
soothe loop continue
|
|
652
701
|
soothe loop continue loop_abc123
|
|
653
702
|
soothe loop continue loop_abc123 --prompt "translate to chinese"
|
|
654
703
|
"""
|
|
655
704
|
config = load_config()
|
|
656
705
|
ws_url = websocket_url_from_config(config)
|
|
657
706
|
_require_daemon(ws_url)
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
707
|
+
resolved_loop_id = _resolve_continue_loop_id(ws_url, loop_id)
|
|
708
|
+
from soothe_cli.cli.commands.run_cmd import run_impl
|
|
709
|
+
|
|
710
|
+
run_impl(
|
|
711
|
+
prompt=prompt,
|
|
712
|
+
thread_id=resolved_loop_id,
|
|
713
|
+
no_tui=False,
|
|
714
|
+
autonomous=False,
|
|
715
|
+
max_iterations=None,
|
|
667
716
|
)
|
|
668
717
|
|
|
669
|
-
if "error" in response:
|
|
670
|
-
typer.echo(f"Error: {response['error']}", err=True)
|
|
671
|
-
sys.exit(1)
|
|
672
|
-
|
|
673
|
-
console.print(f"[success]Attached to loop {loop_id}[/success]")
|
|
674
|
-
|
|
675
|
-
# Show loop status
|
|
676
|
-
status_response = asyncio.run(
|
|
677
|
-
_rpc(
|
|
678
|
-
ws_url,
|
|
679
|
-
"send_loop_get",
|
|
680
|
-
{"loop_id": loop_id, "verbose": False},
|
|
681
|
-
"loop_get_response",
|
|
682
|
-
)
|
|
683
|
-
)
|
|
684
|
-
|
|
685
|
-
loop = status_response.get("loop", {})
|
|
686
|
-
console.print(
|
|
687
|
-
Panel(
|
|
688
|
-
f"Status: {loop.get('status', 'unknown')}\n"
|
|
689
|
-
f"Goals: {loop.get('total_goals_completed', 0)} completed\n"
|
|
690
|
-
f"Internal Threads: {len(loop.get('thread_ids', []))}",
|
|
691
|
-
title=f"Loop: {loop_id}",
|
|
692
|
-
)
|
|
693
|
-
)
|
|
694
|
-
|
|
695
|
-
# Execute prompt if provided
|
|
696
|
-
if prompt:
|
|
697
|
-
input_response = asyncio.run(
|
|
698
|
-
_rpc(
|
|
699
|
-
ws_url,
|
|
700
|
-
"send_loop_input",
|
|
701
|
-
{"loop_id": loop_id, "content": prompt},
|
|
702
|
-
"loop_input_response",
|
|
703
|
-
)
|
|
704
|
-
)
|
|
705
|
-
if "error" in input_response:
|
|
706
|
-
typer.echo(f"Error: {input_response['error']}", err=True)
|
|
707
|
-
sys.exit(1)
|
|
708
|
-
console.print("[info]Prompt sent to loop[/info]")
|
|
709
|
-
|
|
710
718
|
|
|
711
719
|
@loop_app.command("detach")
|
|
712
720
|
def detach_loop(
|
|
@@ -47,7 +47,6 @@ async def run_headless_via_daemon(
|
|
|
47
47
|
|
|
48
48
|
ws_url = websocket_url_from_config(cfg)
|
|
49
49
|
client = WebSocketClient(url=ws_url)
|
|
50
|
-
final_output_mode = getattr(cfg, "final_output_mode", "streaming")
|
|
51
50
|
|
|
52
51
|
try:
|
|
53
52
|
await connect_websocket_with_retries(client)
|
|
@@ -85,7 +84,6 @@ async def run_headless_via_daemon(
|
|
|
85
84
|
renderer = HeadlessCliRenderer()
|
|
86
85
|
processor = EventProcessor(
|
|
87
86
|
renderer,
|
|
88
|
-
final_output_mode=final_output_mode,
|
|
89
87
|
presentation_engine=presentation,
|
|
90
88
|
headless_output=True,
|
|
91
89
|
)
|
|
@@ -27,8 +27,6 @@ class CLIConfig:
|
|
|
27
27
|
# logging_level: DEBUG/INFO/… for ~/.soothe/logs/soothe-cli.log; None = default INFO.
|
|
28
28
|
logging_level: str | None = None
|
|
29
29
|
|
|
30
|
-
final_output_mode: str = "streaming"
|
|
31
|
-
|
|
32
30
|
# Output streaming overrides (RFC-614)
|
|
33
31
|
output_streaming_enabled: bool | None = None
|
|
34
32
|
"""Override daemon streaming enabled setting."""
|
|
@@ -107,26 +105,15 @@ class CLIConfig:
|
|
|
107
105
|
transports = daemon_section.get("transports", {})
|
|
108
106
|
websocket = transports.get("websocket", {})
|
|
109
107
|
websocket_legacy = data.get("websocket", {})
|
|
110
|
-
ui_section = data.get("ui", {})
|
|
111
108
|
|
|
112
109
|
raw_level = data.get("logging_level")
|
|
113
110
|
if raw_level is not None and not isinstance(raw_level, str):
|
|
114
111
|
raw_level = None
|
|
115
112
|
|
|
116
|
-
raw_final_output_mode = data.get("final_output_mode")
|
|
117
|
-
if raw_final_output_mode is None and isinstance(ui_section, dict):
|
|
118
|
-
raw_final_output_mode = ui_section.get("final_output_mode")
|
|
119
|
-
if not isinstance(raw_final_output_mode, str):
|
|
120
|
-
raw_final_output_mode = "streaming"
|
|
121
|
-
final_output_mode = raw_final_output_mode.strip().lower()
|
|
122
|
-
if final_output_mode not in {"streaming", "batch"}:
|
|
123
|
-
final_output_mode = "streaming"
|
|
124
|
-
|
|
125
113
|
return cls(
|
|
126
114
|
daemon_host=websocket.get("host", websocket_legacy.get("host", "127.0.0.1")),
|
|
127
115
|
daemon_port=websocket.get("port", websocket_legacy.get("port", 8765)),
|
|
128
116
|
logging_level=raw_level,
|
|
129
|
-
final_output_mode=final_output_mode,
|
|
130
117
|
soothe_home=Path(data.get("home", str(Path.home() / ".soothe"))),
|
|
131
118
|
)
|
|
132
119
|
|
|
@@ -82,7 +82,6 @@ class EventProcessor:
|
|
|
82
82
|
self,
|
|
83
83
|
renderer: RendererProtocol,
|
|
84
84
|
*,
|
|
85
|
-
final_output_mode: str = "streaming",
|
|
86
85
|
presentation_engine: PresentationEngine | None = None,
|
|
87
86
|
tui_debug: bool = False,
|
|
88
87
|
headless_output: bool = False,
|
|
@@ -98,9 +97,6 @@ class EventProcessor:
|
|
|
98
97
|
"""
|
|
99
98
|
self._renderer = renderer
|
|
100
99
|
self._headless_output = headless_output
|
|
101
|
-
self._final_output_mode = (
|
|
102
|
-
final_output_mode if final_output_mode in {"streaming", "batch"} else "streaming"
|
|
103
|
-
)
|
|
104
100
|
self._tui_debug = tui_debug
|
|
105
101
|
|
|
106
102
|
rebind = getattr(renderer, "_rebind_presentation", None)
|
|
@@ -1103,12 +1099,10 @@ class EventProcessor:
|
|
|
1103
1099
|
Dict with enabled, mode, and synthesis_streaming fields.
|
|
1104
1100
|
"""
|
|
1105
1101
|
# Use defaults - streaming is enabled by default per RFC-614
|
|
1106
|
-
#
|
|
1102
|
+
# Always use streaming mode
|
|
1107
1103
|
config = {
|
|
1108
1104
|
"enabled": True,
|
|
1109
|
-
"mode":
|
|
1110
|
-
if self._final_output_mode in {"streaming", "batch"}
|
|
1111
|
-
else "streaming",
|
|
1105
|
+
"mode": "streaming",
|
|
1112
1106
|
"synthesis_streaming": True,
|
|
1113
1107
|
}
|
|
1114
1108
|
|
|
@@ -392,7 +392,7 @@ def format_tool_call_args(tool_name: str, tool_call: dict[str, Any]) -> str:
|
|
|
392
392
|
def _is_path_arg_name(key: str) -> bool:
|
|
393
393
|
return _PATH_ARG_PATTERN.match(key) is not None
|
|
394
394
|
|
|
395
|
-
max_value_length =
|
|
395
|
+
max_value_length = 50 # Max length for displayed values
|
|
396
396
|
|
|
397
397
|
def _display_path_value(raw: str) -> str:
|
|
398
398
|
out = convert_and_abbreviate_path(raw)
|
{soothe_cli-0.4.7 → soothe_cli-0.4.9}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py
RENAMED
|
@@ -13,8 +13,8 @@ from soothe_cli.shared.tools.tool_output_formatter import ToolBrief
|
|
|
13
13
|
class FileOpsFormatter(BaseFormatter):
|
|
14
14
|
"""Formatter for file operation tools.
|
|
15
15
|
|
|
16
|
-
Handles: read_file, write_file, delete_file, list_files, search_files,
|
|
17
|
-
grep, ls, file_info
|
|
16
|
+
Handles: read_file, write_file, edit_file, delete_file, list_files, search_files,
|
|
17
|
+
glob, grep, ls, file_info, edit_file_lines, insert_lines, delete_lines, apply_diff
|
|
18
18
|
|
|
19
19
|
Provides semantic summaries with size, line count, and item count metrics.
|
|
20
20
|
"""
|
|
@@ -46,6 +46,26 @@ class FileOpsFormatter(BaseFormatter):
|
|
|
46
46
|
return self._format_read_file(result)
|
|
47
47
|
if normalized == "write_file":
|
|
48
48
|
return self._format_write_file(result)
|
|
49
|
+
if normalized == "edit_file":
|
|
50
|
+
return self._format_file_mutation(
|
|
51
|
+
result, success_summary="Edited file", failure_summary="Edit failed"
|
|
52
|
+
)
|
|
53
|
+
if normalized == "edit_file_lines":
|
|
54
|
+
return self._format_file_mutation(
|
|
55
|
+
result, success_summary="Updated file", failure_summary="Line edit failed"
|
|
56
|
+
)
|
|
57
|
+
if normalized == "insert_lines":
|
|
58
|
+
return self._format_file_mutation(
|
|
59
|
+
result, success_summary="Inserted lines", failure_summary="Insert failed"
|
|
60
|
+
)
|
|
61
|
+
if normalized == "delete_lines":
|
|
62
|
+
return self._format_file_mutation(
|
|
63
|
+
result, success_summary="Deleted lines", failure_summary="Delete lines failed"
|
|
64
|
+
)
|
|
65
|
+
if normalized == "apply_diff":
|
|
66
|
+
return self._format_file_mutation(
|
|
67
|
+
result, success_summary="Applied patch", failure_summary="Patch failed"
|
|
68
|
+
)
|
|
49
69
|
if normalized == "delete_file":
|
|
50
70
|
return self._format_delete_file(result)
|
|
51
71
|
if normalized in ("list_files", "ls"):
|
|
@@ -143,6 +163,28 @@ class FileOpsFormatter(BaseFormatter):
|
|
|
143
163
|
metrics={"size_bytes": size_bytes, "lines": lines},
|
|
144
164
|
)
|
|
145
165
|
|
|
166
|
+
def _format_file_mutation(
|
|
167
|
+
self,
|
|
168
|
+
result: str,
|
|
169
|
+
*,
|
|
170
|
+
success_summary: str,
|
|
171
|
+
failure_summary: str,
|
|
172
|
+
) -> ToolBrief:
|
|
173
|
+
"""Format success/error for tools that mutate file content (write, edit, patch, …)."""
|
|
174
|
+
if text_looks_like_error(result):
|
|
175
|
+
return ToolBrief(
|
|
176
|
+
icon="✗",
|
|
177
|
+
summary=failure_summary,
|
|
178
|
+
detail=self._truncate_text(result, 80),
|
|
179
|
+
metrics={"error": True},
|
|
180
|
+
)
|
|
181
|
+
return ToolBrief(
|
|
182
|
+
icon="✓",
|
|
183
|
+
summary=success_summary,
|
|
184
|
+
detail=None,
|
|
185
|
+
metrics={},
|
|
186
|
+
)
|
|
187
|
+
|
|
146
188
|
def _format_write_file(self, result: str) -> ToolBrief:
|
|
147
189
|
"""Format write_file result.
|
|
148
190
|
|
|
@@ -157,25 +199,10 @@ class FileOpsFormatter(BaseFormatter):
|
|
|
157
199
|
Example:
|
|
158
200
|
>>> brief = formatter._format_write_file("Successfully wrote to file")
|
|
159
201
|
>>> brief.summary
|
|
160
|
-
'Wrote
|
|
202
|
+
'Wrote file'
|
|
161
203
|
"""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return ToolBrief(
|
|
165
|
-
icon="✗",
|
|
166
|
-
summary="Write failed",
|
|
167
|
-
detail=self._truncate_text(result, 80),
|
|
168
|
-
metrics={"error": True},
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
# Try to extract size from result (if available)
|
|
172
|
-
# Common patterns: "Wrote X bytes", "Successfully wrote X"
|
|
173
|
-
# For now, show simple success
|
|
174
|
-
return ToolBrief(
|
|
175
|
-
icon="✓",
|
|
176
|
-
summary="Wrote file",
|
|
177
|
-
detail=None,
|
|
178
|
-
metrics={},
|
|
204
|
+
return self._format_file_mutation(
|
|
205
|
+
result, success_summary="Wrote file", failure_summary="Write failed"
|
|
179
206
|
)
|
|
180
207
|
|
|
181
208
|
def _format_delete_file(self, result: str) -> ToolBrief:
|
|
@@ -9,10 +9,13 @@ config, no widget imports) so that `app.py` can import `SessionStats` and
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
|
-
from typing import Literal
|
|
13
12
|
|
|
14
|
-
SpinnerStatus =
|
|
15
|
-
"""
|
|
13
|
+
SpinnerStatus = str | None
|
|
14
|
+
"""Spinner line label, or `None` to hide.
|
|
15
|
+
|
|
16
|
+
Common values include ``Thinking``, ``Offloading``, ``Writing`` (assistant streaming),
|
|
17
|
+
``Tools`` (tool execution), and ``Synthesizing`` (goal-completion stream).
|
|
18
|
+
"""
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
@dataclass
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Public API for the app sub-package."""
|
|
2
|
+
|
|
3
|
+
from soothe_cli.tui.app._app import SootheApp
|
|
4
|
+
from soothe_cli.tui.app._module_init import (
|
|
5
|
+
AppResult,
|
|
6
|
+
TextualSessionState,
|
|
7
|
+
run_textual_app,
|
|
8
|
+
run_textual_tui,
|
|
9
|
+
save_theme_preference,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"SootheApp",
|
|
14
|
+
"AppResult",
|
|
15
|
+
"TextualSessionState",
|
|
16
|
+
"run_textual_app",
|
|
17
|
+
"run_textual_tui",
|
|
18
|
+
"save_theme_preference",
|
|
19
|
+
]
|