soothe-cli 0.5.2__tar.gz → 0.5.3__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.5.2 → soothe_cli-0.5.3}/PKG-INFO +1 -1
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/commands/run_cmd.py +25 -7
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/main.py +19 -4
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/config/cli_config.py +1 -1
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/_env_vars.py +4 -1
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_history.py +4 -4
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_messages_mixin.py +2 -2
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_startup.py +19 -15
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/config.py +3 -3
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_turn.py +13 -4
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/update_check.py +46 -15
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/message_store.py +14 -14
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/messages.py +35 -17
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/welcome.py +23 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/.gitignore +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/README.md +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/pyproject.toml +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/context.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/display_line.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/formatter.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/pipeline.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/task_scope.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/plan/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/plan/rich_tree.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/commands/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/commands/command_router.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/commands/slash_commands.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/commands/subagent_routing.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/config_loader.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/event_processor.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/presentation_engine.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/processor_state.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/renderer_protocol.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/duration_format.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/display_policy.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/essential_events.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/explore_task_display.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/stream_accumulator.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/tui_trace_log.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/rendering/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/rendering/async_renderer_protocol.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/rendering/renderer_base.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/_utils.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/message_processing.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/rendering.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_call_resolution.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_card_payload.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_card_visibility.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/base.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/execution.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/fallback.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/goal_formatter.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/media.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/structured.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/subagent.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/web.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_message_format.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_output_formatter.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/_ask_user_types.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/_session_stats.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_app.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_commands.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_execution.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_model.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_module_init.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_ui.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/command_registry.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/daemon_session.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/file_ops.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/formatting.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/message_display_filter.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/output.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/preview_limits.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/sessions.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_adapter.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_stream_formatting.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_stream_messages.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_turn_helpers.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/approval.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
- {soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/tools.py +0 -0
|
@@ -24,25 +24,29 @@ def run_impl(
|
|
|
24
24
|
streaming_enabled: bool | None = None,
|
|
25
25
|
streaming_mode: str | None = None,
|
|
26
26
|
*,
|
|
27
|
+
tui_with_prompt: bool = False,
|
|
27
28
|
config_path: str | None = None,
|
|
28
29
|
) -> None:
|
|
29
30
|
"""Core implementation for running Soothe agent.
|
|
30
31
|
|
|
31
32
|
Args:
|
|
32
|
-
prompt: Optional prompt
|
|
33
|
+
prompt: Optional user message; non-empty prompt defaults to a headless
|
|
34
|
+
one-shot run unless ``tui_with_prompt`` is set or a loop is being
|
|
35
|
+
resumed (``resume_loop_id``).
|
|
33
36
|
resume_loop_id: Existing loop id to attach to (optional)
|
|
34
|
-
no_tui:
|
|
37
|
+
no_tui: Require headless mode (must include a non-empty prompt)
|
|
35
38
|
autonomous: Enable autonomous iteration mode
|
|
36
39
|
max_iterations: Max iterations for autonomous mode
|
|
37
40
|
streaming_enabled: Override daemon streaming enabled setting (RFC-614)
|
|
38
41
|
streaming_mode: Override daemon streaming mode ('streaming' or 'batch')
|
|
42
|
+
tui_with_prompt: When True with a prompt, open the TUI instead of headless.
|
|
39
43
|
"""
|
|
40
44
|
startup_start = time.perf_counter()
|
|
41
45
|
|
|
42
46
|
try:
|
|
43
47
|
cfg = load_config(config_path)
|
|
44
48
|
log_level = resolve_cli_log_level(logging_level=cfg.logging_level)
|
|
45
|
-
log_file = Path(SOOTHE_HOME) / "logs" / "
|
|
49
|
+
log_file = Path(SOOTHE_HOME) / "logs" / "cli.log"
|
|
46
50
|
setup_logging(log_level, log_file=log_file)
|
|
47
51
|
|
|
48
52
|
# PostgreSQL availability check (requires daemon-side config)
|
|
@@ -62,17 +66,31 @@ def run_impl(
|
|
|
62
66
|
|
|
63
67
|
run_start = time.perf_counter()
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
has_prompt = bool(prompt and str(prompt).strip())
|
|
70
|
+
attaching_loop = bool(resume_loop_id and str(resume_loop_id).strip())
|
|
71
|
+
|
|
72
|
+
if tui_with_prompt and has_prompt:
|
|
73
|
+
use_headless = False
|
|
74
|
+
elif no_tui and not has_prompt:
|
|
75
|
+
typer.echo(
|
|
76
|
+
"Error: --no-tui requires a non-empty --prompt (-p).",
|
|
77
|
+
err=True,
|
|
78
|
+
)
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
elif no_tui:
|
|
81
|
+
use_headless = True
|
|
82
|
+
else:
|
|
83
|
+
use_headless = has_prompt and not attaching_loop
|
|
84
|
+
|
|
85
|
+
if use_headless:
|
|
67
86
|
run_headless(
|
|
68
87
|
cfg,
|
|
69
|
-
prompt
|
|
88
|
+
str(prompt).strip(),
|
|
70
89
|
resume_loop_id=resume_loop_id,
|
|
71
90
|
autonomous=autonomous,
|
|
72
91
|
max_iterations=max_iterations,
|
|
73
92
|
)
|
|
74
93
|
else:
|
|
75
|
-
# TUI mode (with optional initial prompt)
|
|
76
94
|
run_tui(cfg, resume_loop_id=resume_loop_id, initial_prompt=prompt)
|
|
77
95
|
|
|
78
96
|
run_elapsed_s = time.perf_counter() - run_start
|
|
@@ -55,12 +55,24 @@ def main(
|
|
|
55
55
|
prompt: Annotated[
|
|
56
56
|
str | None,
|
|
57
57
|
typer.Option(
|
|
58
|
-
"--prompt",
|
|
58
|
+
"--prompt",
|
|
59
|
+
"-p",
|
|
60
|
+
help="User message; runs a one-shot headless query by default (use --tui for TUI).",
|
|
59
61
|
),
|
|
60
62
|
] = None,
|
|
61
63
|
no_tui: Annotated[ # noqa: FBT002
|
|
62
64
|
bool,
|
|
63
|
-
typer.Option(
|
|
65
|
+
typer.Option(
|
|
66
|
+
"--no-tui",
|
|
67
|
+
help="Headless mode (requires --prompt). Same as default when -p is set.",
|
|
68
|
+
),
|
|
69
|
+
] = False,
|
|
70
|
+
tui_with_prompt: Annotated[ # noqa: FBT002
|
|
71
|
+
bool,
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--tui",
|
|
74
|
+
help="With --prompt/-p, open the interactive TUI and auto-submit the prompt.",
|
|
75
|
+
),
|
|
64
76
|
] = False,
|
|
65
77
|
streaming: Annotated[
|
|
66
78
|
bool | None,
|
|
@@ -81,13 +93,15 @@ def main(
|
|
|
81
93
|
) -> None:
|
|
82
94
|
"""Soothe CLI - Intelligent AI assistant client.
|
|
83
95
|
|
|
84
|
-
Run without arguments for interactive TUI mode, or
|
|
96
|
+
Run without arguments for interactive TUI mode, or pass --prompt for a one-shot
|
|
97
|
+
headless query (stdout, then exit).
|
|
85
98
|
|
|
86
99
|
Note: This is the CLI client. Use 'soothed' command to manage the daemon server.
|
|
87
100
|
|
|
88
101
|
Examples:
|
|
89
102
|
soothe # Interactive TUI mode
|
|
90
|
-
soothe -p "Research AI advances" #
|
|
103
|
+
soothe -p "Research AI advances" # One-shot headless (non-TUI) query
|
|
104
|
+
soothe -p "Hello" --tui # TUI with an auto-submitted prompt
|
|
91
105
|
soothe loop list # List AgentLoop instances
|
|
92
106
|
"""
|
|
93
107
|
# Handle -h/--help flag
|
|
@@ -112,6 +126,7 @@ def main(
|
|
|
112
126
|
max_iterations=None,
|
|
113
127
|
streaming_enabled=streaming,
|
|
114
128
|
streaming_mode=streaming_mode,
|
|
129
|
+
tui_with_prompt=tui_with_prompt,
|
|
115
130
|
)
|
|
116
131
|
|
|
117
132
|
|
|
@@ -24,7 +24,7 @@ class CLIConfig:
|
|
|
24
24
|
daemon_host: str = "127.0.0.1"
|
|
25
25
|
daemon_port: int = 8765
|
|
26
26
|
|
|
27
|
-
# logging_level: DEBUG/INFO/… for ~/.soothe/logs/
|
|
27
|
+
# logging_level: DEBUG/INFO/… for ~/.soothe/logs/cli.log; None = default INFO.
|
|
28
28
|
logging_level: str | None = None
|
|
29
29
|
|
|
30
30
|
# Output streaming overrides (RFC-614)
|
|
@@ -29,7 +29,7 @@ from __future__ import annotations
|
|
|
29
29
|
# ---------------------------------------------------------------------------
|
|
30
30
|
|
|
31
31
|
AUTO_UPDATE = "SOOTHE_CLI_AUTO_UPDATE"
|
|
32
|
-
"""
|
|
32
|
+
"""Override automatic CLI updates: '1'/'true'/'yes' on, '0'/'false'/'no' off. On by default when unset."""
|
|
33
33
|
|
|
34
34
|
DEBUG = "SOOTHE_CLI_DEBUG"
|
|
35
35
|
"""Enable verbose debug logging to a file."""
|
|
@@ -43,6 +43,9 @@ EXTRA_SKILLS_DIRS = "SOOTHE_CLI_EXTRA_SKILLS_DIRS"
|
|
|
43
43
|
NO_UPDATE_CHECK = "SOOTHE_CLI_NO_UPDATE_CHECK"
|
|
44
44
|
"""Disable automatic update checking when set."""
|
|
45
45
|
|
|
46
|
+
UPDATE_CHECK = "SOOTHE_CLI_UPDATE_CHECK"
|
|
47
|
+
"""Force-enable startup PyPI update checks ('1', 'true', or 'yes'). On by default."""
|
|
48
|
+
|
|
46
49
|
SERVER_ENV_PREFIX = "SOOTHE_CLI_SERVER_"
|
|
47
50
|
"""Environment variable prefix used to pass CLI config to the server subprocess."""
|
|
48
51
|
|
|
@@ -438,10 +438,10 @@ class _HistoryMixin:
|
|
|
438
438
|
# (``phase=goal_completion``); avoid duplicate app-line noise.
|
|
439
439
|
return None
|
|
440
440
|
if event_type == "soothe.cognition.agent_loop.reasoned":
|
|
441
|
-
plan_action_raw = str(event_data.get("plan_action") or "
|
|
442
|
-
plan_action = plan_action_raw if plan_action_raw in {"keep", "new"} else "
|
|
441
|
+
plan_action_raw = str(event_data.get("plan_action") or "").strip()
|
|
442
|
+
plan_action = plan_action_raw if plan_action_raw in {"keep", "new"} else ""
|
|
443
443
|
return MessageData(
|
|
444
|
-
type=MessageType.
|
|
444
|
+
type=MessageType.COGNITION_REASON,
|
|
445
445
|
content="",
|
|
446
446
|
timestamp=event_timestamp,
|
|
447
447
|
cognition_plan_next_action=str(event_data.get("next_action") or ""),
|
|
@@ -541,7 +541,7 @@ class _HistoryMixin:
|
|
|
541
541
|
if msg_data is None:
|
|
542
542
|
continue
|
|
543
543
|
if msg_data.type not in (
|
|
544
|
-
MessageType.
|
|
544
|
+
MessageType.COGNITION_REASON,
|
|
545
545
|
MessageType.COGNITION_GOAL_TREE,
|
|
546
546
|
MessageType.STEP_PROGRESS,
|
|
547
547
|
):
|
|
@@ -28,7 +28,7 @@ from soothe_cli.tui.widgets.messages import (
|
|
|
28
28
|
AppMessage,
|
|
29
29
|
AssistantMessage,
|
|
30
30
|
CognitionGoalTreeMessage,
|
|
31
|
-
|
|
31
|
+
CognitionReasonMessage,
|
|
32
32
|
CognitionStepMessage,
|
|
33
33
|
ErrorMessage,
|
|
34
34
|
QueuedUserMessage,
|
|
@@ -153,7 +153,7 @@ class _MessagesMixin:
|
|
|
153
153
|
| ToolCallMessage
|
|
154
154
|
| SkillMessage
|
|
155
155
|
| CognitionStepMessage
|
|
156
|
-
|
|
|
156
|
+
| CognitionReasonMessage
|
|
157
157
|
| CognitionGoalTreeMessage,
|
|
158
158
|
) -> None:
|
|
159
159
|
"""Mount a message widget to the messages area.
|
|
@@ -153,8 +153,9 @@ class _StartupMixin:
|
|
|
153
153
|
group="daemon-connect",
|
|
154
154
|
)
|
|
155
155
|
|
|
156
|
-
# Background update check and what's-new banner
|
|
157
|
-
#
|
|
156
|
+
# Background update check and what's-new banner (on by default; opt-out via
|
|
157
|
+
# SOOTHE_CLI_NO_UPDATE_CHECK or [update].check: false; SOOTHE_CLI_UPDATE_CHECK
|
|
158
|
+
# forces on if config disables)
|
|
158
159
|
from soothe_cli.tui.update_check import is_update_check_enabled
|
|
159
160
|
|
|
160
161
|
if is_update_check_enabled():
|
|
@@ -603,14 +604,13 @@ class _StartupMixin:
|
|
|
603
604
|
return
|
|
604
605
|
|
|
605
606
|
self._update_available = (True, latest)
|
|
607
|
+
self.call_after_refresh(lambda v=latest: self._apply_welcome_update_notice(v))
|
|
606
608
|
except Exception:
|
|
607
609
|
logger.debug("Background update check failed", exc_info=True)
|
|
608
610
|
return
|
|
609
611
|
|
|
610
|
-
# Phase 2: auto-update
|
|
612
|
+
# Phase 2: optional auto-update (version notice lives on the welcome banner only)
|
|
611
613
|
try:
|
|
612
|
-
from soothe_cli.tui._version import __version__ as cli_version
|
|
613
|
-
|
|
614
614
|
if is_auto_update_enabled():
|
|
615
615
|
from soothe_cli.tui.update_check import perform_upgrade
|
|
616
616
|
|
|
@@ -626,6 +626,7 @@ class _StartupMixin:
|
|
|
626
626
|
severity="information",
|
|
627
627
|
timeout=10,
|
|
628
628
|
)
|
|
629
|
+
self.call_after_refresh(lambda: self._apply_welcome_update_notice(None))
|
|
629
630
|
else:
|
|
630
631
|
cmd = upgrade_command()
|
|
631
632
|
self.notify(
|
|
@@ -634,16 +635,6 @@ class _StartupMixin:
|
|
|
634
635
|
timeout=15,
|
|
635
636
|
markup=False,
|
|
636
637
|
)
|
|
637
|
-
else:
|
|
638
|
-
cmd = upgrade_command()
|
|
639
|
-
self.notify(
|
|
640
|
-
f"Update available: v{latest} (current: v{cli_version}). "
|
|
641
|
-
f"Run: {cmd}\n\n"
|
|
642
|
-
f"Enable auto-updates: /auto-update",
|
|
643
|
-
severity="information",
|
|
644
|
-
timeout=15,
|
|
645
|
-
markup=False,
|
|
646
|
-
)
|
|
647
638
|
except Exception:
|
|
648
639
|
logger.warning("Auto-update failed unexpectedly", exc_info=True)
|
|
649
640
|
self.notify(
|
|
@@ -652,6 +643,14 @@ class _StartupMixin:
|
|
|
652
643
|
timeout=10,
|
|
653
644
|
)
|
|
654
645
|
|
|
646
|
+
def _apply_welcome_update_notice(self, latest: str | None) -> None:
|
|
647
|
+
"""Show or hide the welcome-banner update line (must run on the UI thread)."""
|
|
648
|
+
try:
|
|
649
|
+
banner = self.query_one("#welcome-banner", WelcomeBanner)
|
|
650
|
+
banner.set_update_notice(latest)
|
|
651
|
+
except NoMatches:
|
|
652
|
+
logger.debug("Welcome banner not found while applying update notice")
|
|
653
|
+
|
|
655
654
|
async def _show_whats_new(self) -> None:
|
|
656
655
|
"""Show a 'what's new' banner on the first launch after an upgrade."""
|
|
657
656
|
try:
|
|
@@ -698,17 +697,22 @@ class _StartupMixin:
|
|
|
698
697
|
await self._mount_message(AppMessage("Checking for updates..."))
|
|
699
698
|
available, latest = await asyncio.to_thread(is_update_available, bypass_cache=True)
|
|
700
699
|
if not available:
|
|
700
|
+
self._update_available = (False, None)
|
|
701
|
+
self._apply_welcome_update_notice(None)
|
|
701
702
|
await self._mount_message(AppMessage("Already on the latest version."))
|
|
702
703
|
return
|
|
703
704
|
|
|
704
705
|
from soothe_cli.tui._version import __version__ as cli_version
|
|
705
706
|
|
|
707
|
+
self._update_available = (True, latest)
|
|
708
|
+
self._apply_welcome_update_notice(latest)
|
|
706
709
|
await self._mount_message(
|
|
707
710
|
AppMessage(f"Update available: v{latest} (current: v{cli_version}). Upgrading...")
|
|
708
711
|
)
|
|
709
712
|
success, output = await perform_upgrade()
|
|
710
713
|
if success:
|
|
711
714
|
self._update_available = (False, None)
|
|
715
|
+
self._apply_welcome_update_notice(None)
|
|
712
716
|
await self._mount_message(
|
|
713
717
|
AppMessage(f"Updated to v{latest}. Restart to use the new version.")
|
|
714
718
|
)
|
|
@@ -252,7 +252,7 @@ class Glyphs:
|
|
|
252
252
|
|
|
253
253
|
# Expand/collapse icons
|
|
254
254
|
expand: str # ▶ vs [+] - shown when collapsed (click to expand)
|
|
255
|
-
collapse: str #
|
|
255
|
+
collapse: str # ▼ vs [v] - shown when expanded (click to collapse)
|
|
256
256
|
|
|
257
257
|
# Box-drawing characters
|
|
258
258
|
box_vertical: str # │ vs |
|
|
@@ -287,7 +287,7 @@ UNICODE_GLYPHS = Glyphs(
|
|
|
287
287
|
assistant="🤖", # AI/assistant icon
|
|
288
288
|
# Expand/collapse icons
|
|
289
289
|
expand="▶",
|
|
290
|
-
collapse="
|
|
290
|
+
collapse="▼",
|
|
291
291
|
# Box-drawing characters
|
|
292
292
|
box_vertical="│",
|
|
293
293
|
box_horizontal="─",
|
|
@@ -318,7 +318,7 @@ ASCII_GLYPHS = Glyphs(
|
|
|
318
318
|
assistant="[A]", # AI/assistant icon (ASCII)
|
|
319
319
|
# Expand/collapse icons
|
|
320
320
|
expand="[+]",
|
|
321
|
-
collapse="[
|
|
321
|
+
collapse="[v]",
|
|
322
322
|
# Box-drawing characters
|
|
323
323
|
box_vertical="|",
|
|
324
324
|
box_horizontal="-",
|
|
@@ -91,7 +91,7 @@ from soothe_cli.tui.textual_adapter._turn_helpers import (
|
|
|
91
91
|
from soothe_cli.tui.widgets.messages import (
|
|
92
92
|
AppMessage,
|
|
93
93
|
AssistantMessage,
|
|
94
|
-
|
|
94
|
+
CognitionReasonMessage,
|
|
95
95
|
CognitionStepMessage,
|
|
96
96
|
DiffMessage,
|
|
97
97
|
SummarizationMessage,
|
|
@@ -1249,6 +1249,8 @@ async def execute_task_textual(
|
|
|
1249
1249
|
error_text = str(
|
|
1250
1250
|
data.get("error") or data.get("message") or "Agent error"
|
|
1251
1251
|
)
|
|
1252
|
+
adapter.finalize_pending_tools_with_error(error_text)
|
|
1253
|
+
adapter.finalize_pending_steps_with_error(error_text)
|
|
1252
1254
|
await adapter._mount_message(AppMessage(error_text))
|
|
1253
1255
|
if adapter._set_spinner:
|
|
1254
1256
|
await adapter._set_spinner(None)
|
|
@@ -1395,9 +1397,9 @@ async def execute_task_textual(
|
|
|
1395
1397
|
)
|
|
1396
1398
|
pending_text_by_namespace[ns_key] = ""
|
|
1397
1399
|
assistant_message_by_namespace.pop(ns_key, None)
|
|
1398
|
-
pa_raw = data.get("plan_action", "
|
|
1399
|
-
plan_action = pa_raw if pa_raw in ("keep", "new") else "
|
|
1400
|
-
plan_widget =
|
|
1400
|
+
pa_raw = data.get("plan_action", "")
|
|
1401
|
+
plan_action = pa_raw if pa_raw in ("keep", "new") else ""
|
|
1402
|
+
plan_widget = CognitionReasonMessage(
|
|
1401
1403
|
next_action=str(data.get("next_action", "")),
|
|
1402
1404
|
status=str(data.get("status", "")),
|
|
1403
1405
|
iteration=int(data.get("iteration", 0)),
|
|
@@ -1509,6 +1511,13 @@ async def execute_task_textual(
|
|
|
1509
1511
|
)
|
|
1510
1512
|
adapter._pending_main_tools.clear()
|
|
1511
1513
|
|
|
1514
|
+
# Safety net: finalize any steps/tools still in-flight (e.g. worker
|
|
1515
|
+
# crash sent a soothe.error.* event but step_completed was never
|
|
1516
|
+
# emitted, or stream ended before matching results arrived).
|
|
1517
|
+
if adapter._current_step_messages or adapter._current_tool_messages:
|
|
1518
|
+
adapter.finalize_pending_tools_with_error("Stream ended unexpectedly")
|
|
1519
|
+
adapter.finalize_pending_steps_with_error("Stream ended unexpectedly")
|
|
1520
|
+
|
|
1512
1521
|
# Handle HITL after stream completes
|
|
1513
1522
|
if interrupt_occurred:
|
|
1514
1523
|
any_rejected = False
|
|
@@ -316,35 +316,66 @@ async def perform_upgrade() -> tuple[bool, str]:
|
|
|
316
316
|
|
|
317
317
|
|
|
318
318
|
def is_update_check_enabled() -> bool:
|
|
319
|
-
"""Return whether update checks are enabled.
|
|
319
|
+
"""Return whether startup update checks are enabled.
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
321
|
+
Disabled when `SOOTHE_CLI_NO_UPDATE_CHECK` or legacy `SOOTHE_NO_UPDATE_CHECK`
|
|
322
|
+
is set. When `SOOTHE_CLI_UPDATE_CHECK` is ``1``/``true``/``yes``, checks are
|
|
323
|
+
enabled (including when ``[update].check: false`` would otherwise turn them
|
|
324
|
+
off). Otherwise, respects ``[update].check`` in ``config.yml`` when
|
|
325
|
+
present; defaults to on. Use ``/update`` to check manually any time.
|
|
325
326
|
"""
|
|
326
|
-
|
|
327
|
+
from soothe_cli.tui._env_vars import NO_UPDATE_CHECK, UPDATE_CHECK
|
|
328
|
+
|
|
329
|
+
if os.environ.get("SOOTHE_NO_UPDATE_CHECK") or os.environ.get(NO_UPDATE_CHECK):
|
|
327
330
|
return False
|
|
328
|
-
|
|
331
|
+
if os.environ.get(UPDATE_CHECK, "").lower() in {"1", "true", "yes"}:
|
|
332
|
+
return True
|
|
333
|
+
cfg = _read_update_config()
|
|
334
|
+
if "check" in cfg:
|
|
335
|
+
return bool(cfg["check"])
|
|
336
|
+
return True
|
|
329
337
|
|
|
330
338
|
|
|
331
|
-
def
|
|
332
|
-
"""Return
|
|
339
|
+
def _auto_update_env_override() -> bool | None:
|
|
340
|
+
"""Return env-forced auto-update flag, or ``None`` if unset.
|
|
341
|
+
|
|
342
|
+
``SOOTHE_CLI_AUTO_UPDATE`` (and legacy ``SOOTHE_AUTO_UPDATE``) may be
|
|
343
|
+
``1``/``true``/``yes`` to force on or ``0``/``false``/``no`` to force off.
|
|
344
|
+
"""
|
|
345
|
+
from soothe_cli.tui._env_vars import AUTO_UPDATE
|
|
333
346
|
|
|
334
|
-
|
|
335
|
-
|
|
347
|
+
for key in (AUTO_UPDATE, "SOOTHE_AUTO_UPDATE"):
|
|
348
|
+
raw = os.environ.get(key)
|
|
349
|
+
if raw is None or not str(raw).strip():
|
|
350
|
+
continue
|
|
351
|
+
lv = str(raw).strip().lower()
|
|
352
|
+
if lv in {"1", "true", "yes"}:
|
|
353
|
+
return True
|
|
354
|
+
if lv in {"0", "false", "no"}:
|
|
355
|
+
return False
|
|
356
|
+
return None
|
|
336
357
|
|
|
337
|
-
|
|
358
|
+
|
|
359
|
+
def is_auto_update_enabled() -> bool:
|
|
360
|
+
"""Return whether auto-update is enabled.
|
|
338
361
|
|
|
339
362
|
Always disabled for editable installs.
|
|
363
|
+
|
|
364
|
+
Otherwise, ``SOOTHE_CLI_AUTO_UPDATE`` (or legacy ``SOOTHE_AUTO_UPDATE``)
|
|
365
|
+
forces on or off when set. When unset, ``[update].auto_update`` in
|
|
366
|
+
``config.yml`` is used if present; defaults to on.
|
|
340
367
|
"""
|
|
341
368
|
from soothe_cli.tui.config import _is_editable_install
|
|
342
369
|
|
|
343
370
|
if _is_editable_install():
|
|
344
371
|
return False
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
372
|
+
env_val = _auto_update_env_override()
|
|
373
|
+
if env_val is not None:
|
|
374
|
+
return env_val
|
|
375
|
+
cfg = _read_update_config()
|
|
376
|
+
if "auto_update" in cfg:
|
|
377
|
+
return bool(cfg["auto_update"])
|
|
378
|
+
return True
|
|
348
379
|
|
|
349
380
|
|
|
350
381
|
def set_auto_update(enabled: bool) -> None:
|
|
@@ -53,7 +53,7 @@ class MessageType(StrEnum):
|
|
|
53
53
|
APP = "app"
|
|
54
54
|
SUMMARIZATION = "summarization"
|
|
55
55
|
STEP_PROGRESS = "step_progress"
|
|
56
|
-
|
|
56
|
+
COGNITION_REASON = "cognition_reason"
|
|
57
57
|
COGNITION_GOAL_TREE = "cognition_goal_tree"
|
|
58
58
|
DIFF = "diff"
|
|
59
59
|
|
|
@@ -163,22 +163,22 @@ class MessageData:
|
|
|
163
163
|
"""JSON list of tool rows from ``CognitionStepMessage.snapshot_tool_rows()`` (IG-402)."""
|
|
164
164
|
|
|
165
165
|
cognition_plan_next_action: str | None = None
|
|
166
|
-
"""User-facing next step (
|
|
166
|
+
"""User-facing next step (COGNITION_REASON only)."""
|
|
167
167
|
|
|
168
168
|
cognition_plan_status: str | None = None
|
|
169
|
-
"""Plan status: continue, replan, done (
|
|
169
|
+
"""Plan status: continue, replan, done (COGNITION_REASON only)."""
|
|
170
170
|
|
|
171
171
|
cognition_plan_iteration: int | None = None
|
|
172
|
-
"""Agent-loop iteration (
|
|
172
|
+
"""Agent-loop iteration (COGNITION_REASON only)."""
|
|
173
173
|
|
|
174
174
|
cognition_plan_action: str | None = None
|
|
175
|
-
"""``keep`` or ``new`` (
|
|
175
|
+
"""``keep`` or ``new`` (COGNITION_REASON only)."""
|
|
176
176
|
|
|
177
177
|
cognition_plan_assessment: str | None = None
|
|
178
|
-
"""Phase-1 assessment text (
|
|
178
|
+
"""Phase-1 assessment text (COGNITION_REASON only)."""
|
|
179
179
|
|
|
180
180
|
cognition_plan_strategy: str | None = None
|
|
181
|
-
"""Phase-2 plan reasoning (
|
|
181
|
+
"""Phase-2 plan reasoning (COGNITION_REASON only)."""
|
|
182
182
|
|
|
183
183
|
cognition_goal_snapshot_json: str | None = None
|
|
184
184
|
"""JSON blob from ``CognitionGoalTreeMessage.snapshot_dict()`` (COGNITION_GOAL_TREE only)."""
|
|
@@ -227,7 +227,7 @@ class MessageData:
|
|
|
227
227
|
AppMessage,
|
|
228
228
|
AssistantMessage,
|
|
229
229
|
CognitionGoalTreeMessage,
|
|
230
|
-
|
|
230
|
+
CognitionReasonMessage,
|
|
231
231
|
CognitionStepMessage,
|
|
232
232
|
DiffMessage,
|
|
233
233
|
ErrorMessage,
|
|
@@ -329,12 +329,12 @@ class MessageData:
|
|
|
329
329
|
)
|
|
330
330
|
return w
|
|
331
331
|
|
|
332
|
-
case MessageType.
|
|
333
|
-
return
|
|
332
|
+
case MessageType.COGNITION_REASON:
|
|
333
|
+
return CognitionReasonMessage(
|
|
334
334
|
next_action=self.cognition_plan_next_action or "",
|
|
335
335
|
status=self.cognition_plan_status or "",
|
|
336
336
|
iteration=int(self.cognition_plan_iteration or 0),
|
|
337
|
-
plan_action=self.cognition_plan_action or "
|
|
337
|
+
plan_action=self.cognition_plan_action or "",
|
|
338
338
|
assessment_reasoning=self.cognition_plan_assessment or "",
|
|
339
339
|
plan_reasoning=self.cognition_plan_strategy or "",
|
|
340
340
|
id=self.id,
|
|
@@ -386,7 +386,7 @@ class MessageData:
|
|
|
386
386
|
AppMessage,
|
|
387
387
|
AssistantMessage,
|
|
388
388
|
CognitionGoalTreeMessage,
|
|
389
|
-
|
|
389
|
+
CognitionReasonMessage,
|
|
390
390
|
CognitionStepMessage,
|
|
391
391
|
DiffMessage,
|
|
392
392
|
ErrorMessage,
|
|
@@ -414,9 +414,9 @@ class MessageData:
|
|
|
414
414
|
cognition_goal_snapshot_json=json.dumps(widget.snapshot_dict()),
|
|
415
415
|
)
|
|
416
416
|
|
|
417
|
-
if isinstance(widget,
|
|
417
|
+
if isinstance(widget, CognitionReasonMessage):
|
|
418
418
|
return cls(
|
|
419
|
-
type=MessageType.
|
|
419
|
+
type=MessageType.COGNITION_REASON,
|
|
420
420
|
content="",
|
|
421
421
|
id=widget_id,
|
|
422
422
|
cognition_plan_next_action=widget._next_action,
|
|
@@ -1074,6 +1074,8 @@ class ToolCallMessage(Vertical):
|
|
|
1074
1074
|
"""Whether the entire card body is collapsed (header remains visible)."""
|
|
1075
1075
|
self._collapse_hint_widget: Static | None = None
|
|
1076
1076
|
"""Widget showing expand/collapse hint text."""
|
|
1077
|
+
self._final_duration_ms: int | None = None
|
|
1078
|
+
"""Frozen duration at completion; prevents drift when re-rendering."""
|
|
1077
1079
|
self._task_card_user_expanded: bool = False
|
|
1078
1080
|
"""If True, do not auto-collapse the task card (user expanded the body)."""
|
|
1079
1081
|
self._task_activity_list_user_expanded: bool = False
|
|
@@ -1082,6 +1084,17 @@ class ToolCallMessage(Vertical):
|
|
|
1082
1084
|
def _is_task_tool_card(self) -> bool:
|
|
1083
1085
|
return _normalize_tool_name_for_arg_map(self._tool_name) == "task"
|
|
1084
1086
|
|
|
1087
|
+
def _has_whole_card_collapse_affordance(self) -> bool:
|
|
1088
|
+
"""True when the card body can be collapsed/expanded.
|
|
1089
|
+
|
|
1090
|
+
Must stay aligned with :meth:`on_click` (whole-card toggle branch).
|
|
1091
|
+
"""
|
|
1092
|
+
return bool(
|
|
1093
|
+
self._activity
|
|
1094
|
+
or (self._output or "").strip()
|
|
1095
|
+
or self._status in ("success", "error")
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1085
1098
|
def _maybe_auto_collapse_task_card(self) -> None:
|
|
1086
1099
|
"""Collapse task cards when activity rows exceed the shared threshold."""
|
|
1087
1100
|
if not self._is_task_tool_card():
|
|
@@ -1584,10 +1597,9 @@ class ToolCallMessage(Vertical):
|
|
|
1584
1597
|
colors = theme.get_theme_colors(self)
|
|
1585
1598
|
gutter = f"{get_glyphs().output_prefix} "
|
|
1586
1599
|
# Expand/collapse affordance: collapsed → show right arrow (can expand); expanded → show down arrow (can collapse).
|
|
1587
|
-
has_collapsible = self._activity or (self._output or "").strip()
|
|
1588
1600
|
g = get_glyphs()
|
|
1589
1601
|
toggle_icon = ""
|
|
1590
|
-
if
|
|
1602
|
+
if self._has_whole_card_collapse_affordance():
|
|
1591
1603
|
toggle_icon = f" {g.expand if self._card_collapsed else g.collapse}"
|
|
1592
1604
|
line = f"{gutter}{frame} Running...{elapsed}{toggle_icon}"
|
|
1593
1605
|
self._status_widget.update(Content.styled(line, colors.cognition))
|
|
@@ -1622,8 +1634,7 @@ class ToolCallMessage(Vertical):
|
|
|
1622
1634
|
gutter = f"{get_glyphs().output_prefix} "
|
|
1623
1635
|
line = f"{gutter}{line}"
|
|
1624
1636
|
# Add expand/collapse icon at the end of status line
|
|
1625
|
-
|
|
1626
|
-
if has_collapsible:
|
|
1637
|
+
if self._has_whole_card_collapse_affordance():
|
|
1627
1638
|
icon = get_glyphs().expand if self._card_collapsed else get_glyphs().collapse
|
|
1628
1639
|
line = f"{line} {icon}"
|
|
1629
1640
|
w.update(Content(line))
|
|
@@ -1641,6 +1652,7 @@ class ToolCallMessage(Vertical):
|
|
|
1641
1652
|
self._invalidate_output_render_cache()
|
|
1642
1653
|
self._expanded = False
|
|
1643
1654
|
duration_ms = self._duration_ms_since_start()
|
|
1655
|
+
self._final_duration_ms = duration_ms
|
|
1644
1656
|
line = _TOOL_CARD_PRESENTATION.format_tool_result_status_line(
|
|
1645
1657
|
self._tool_name,
|
|
1646
1658
|
self._output,
|
|
@@ -1671,6 +1683,7 @@ class ToolCallMessage(Vertical):
|
|
|
1671
1683
|
self._invalidate_output_render_cache()
|
|
1672
1684
|
self._expanded = False
|
|
1673
1685
|
duration_ms = self._duration_ms_since_start()
|
|
1686
|
+
self._final_duration_ms = duration_ms
|
|
1674
1687
|
line = _TOOL_CARD_PRESENTATION.format_tool_result_status_line(
|
|
1675
1688
|
self._tool_name,
|
|
1676
1689
|
error,
|
|
@@ -1760,10 +1773,7 @@ class ToolCallMessage(Vertical):
|
|
|
1760
1773
|
self._refresh_activity_display()
|
|
1761
1774
|
return
|
|
1762
1775
|
# Priority 2: Card-level collapse when there's content to collapse
|
|
1763
|
-
|
|
1764
|
-
self._activity or (self._output or "").strip() or self._status in ("success", "error")
|
|
1765
|
-
)
|
|
1766
|
-
if has_collapsible_content:
|
|
1776
|
+
if self._has_whole_card_collapse_affordance():
|
|
1767
1777
|
self.toggle_collapse()
|
|
1768
1778
|
return
|
|
1769
1779
|
# Priority 3: Toggle output expansion
|
|
@@ -1804,8 +1814,12 @@ class ToolCallMessage(Vertical):
|
|
|
1804
1814
|
if self._status == "running":
|
|
1805
1815
|
self._update_running_animation()
|
|
1806
1816
|
elif self._status in ("success", "error") and self._result_summary_widget:
|
|
1807
|
-
# Re-apply result summary with updated icon
|
|
1808
|
-
duration_ms =
|
|
1817
|
+
# Re-apply result summary with updated icon using frozen duration
|
|
1818
|
+
duration_ms = (
|
|
1819
|
+
self._final_duration_ms
|
|
1820
|
+
if self._final_duration_ms is not None
|
|
1821
|
+
else self._duration_ms_since_start()
|
|
1822
|
+
)
|
|
1809
1823
|
line = _TOOL_CARD_PRESENTATION.format_tool_result_status_line(
|
|
1810
1824
|
self._tool_name,
|
|
1811
1825
|
self._output,
|
|
@@ -3301,7 +3315,7 @@ class CognitionStepMessage(Vertical):
|
|
|
3301
3315
|
self._detail_widget.display = False
|
|
3302
3316
|
|
|
3303
3317
|
|
|
3304
|
-
class
|
|
3318
|
+
class CognitionReasonMessage(_TimestampClickMixin, Vertical):
|
|
3305
3319
|
"""Single card for plan assessment, plan reasoning, and next action (keep/new).
|
|
3306
3320
|
|
|
3307
3321
|
Header uses the same cognition-colored label plus foreground body as ``CognitionStepMessage``.
|
|
@@ -3310,7 +3324,7 @@ class CognitionPlanReasonMessage(_TimestampClickMixin, Vertical):
|
|
|
3310
3324
|
can_select = True
|
|
3311
3325
|
|
|
3312
3326
|
DEFAULT_CSS = """
|
|
3313
|
-
|
|
3327
|
+
CognitionReasonMessage {
|
|
3314
3328
|
height: auto;
|
|
3315
3329
|
padding: 0 1;
|
|
3316
3330
|
margin: 0 0 1 0;
|
|
@@ -3318,19 +3332,19 @@ class CognitionPlanReasonMessage(_TimestampClickMixin, Vertical):
|
|
|
3318
3332
|
border-left: wide $cognition;
|
|
3319
3333
|
}
|
|
3320
3334
|
|
|
3321
|
-
|
|
3335
|
+
CognitionReasonMessage .cognition-plan-header {
|
|
3322
3336
|
height: auto;
|
|
3323
3337
|
margin: 0;
|
|
3324
3338
|
color: $foreground;
|
|
3325
3339
|
}
|
|
3326
3340
|
|
|
3327
|
-
|
|
3341
|
+
CognitionReasonMessage .plan-section-line {
|
|
3328
3342
|
height: auto;
|
|
3329
3343
|
margin-left: 3;
|
|
3330
3344
|
color: $text-muted;
|
|
3331
3345
|
}
|
|
3332
3346
|
|
|
3333
|
-
|
|
3347
|
+
CognitionReasonMessage:hover {
|
|
3334
3348
|
border-left: wide $cognition-hover;
|
|
3335
3349
|
}
|
|
3336
3350
|
"""
|
|
@@ -3361,11 +3375,15 @@ class CognitionPlanReasonMessage(_TimestampClickMixin, Vertical):
|
|
|
3361
3375
|
self._next_action = next_action.strip()
|
|
3362
3376
|
self._status = status
|
|
3363
3377
|
self._iteration = iteration
|
|
3364
|
-
self._plan_action = plan_action if plan_action in ("keep", "new") else "
|
|
3378
|
+
self._plan_action = plan_action if plan_action in ("keep", "new") else ""
|
|
3365
3379
|
self._assessment_reasoning = assessment_reasoning.strip()
|
|
3366
3380
|
self._plan_reasoning = plan_reasoning.strip()
|
|
3367
3381
|
|
|
3368
3382
|
def _plan_header_content(self) -> Content:
|
|
3383
|
+
# Assess-only card: only assessment_reasoning populated
|
|
3384
|
+
if self._assessment_reasoning and not self._plan_reasoning and not self._next_action:
|
|
3385
|
+
return _assemble_card_header(self, "💭 ", self._assessment_reasoning)
|
|
3386
|
+
|
|
3369
3387
|
# Concatenate plan_reasoning and next_action with proper separation
|
|
3370
3388
|
parts: list[str] = []
|
|
3371
3389
|
if self._plan_reasoning:
|
|
@@ -3437,7 +3455,7 @@ class _StepLineState:
|
|
|
3437
3455
|
class CognitionGoalTreeMessage(_TimestampClickMixin, Vertical):
|
|
3438
3456
|
"""Two-level Goal → steps tree; one aggregate block updates in place.
|
|
3439
3457
|
|
|
3440
|
-
Title line matches ``CognitionStepMessage`` / ``
|
|
3458
|
+
Title line matches ``CognitionStepMessage`` / ``CognitionReasonMessage``:
|
|
3441
3459
|
``{prefix} 📍 …`` with optional ``· iter<=N`` when ``max_iterations`` is set.
|
|
3442
3460
|
"""
|
|
3443
3461
|
|
|
@@ -90,6 +90,8 @@ class WelcomeBanner(Static):
|
|
|
90
90
|
self._failed = False
|
|
91
91
|
self._failure_error: str = ""
|
|
92
92
|
self._tip: str = random.choice(_TIPS) # noqa: S311
|
|
93
|
+
self._update_latest: str | None = None
|
|
94
|
+
"""PyPI version string when an update is available; drives banner line only."""
|
|
93
95
|
|
|
94
96
|
super().__init__(self._build_banner(), **kwargs)
|
|
95
97
|
|
|
@@ -124,6 +126,16 @@ class WelcomeBanner(Static):
|
|
|
124
126
|
self._failure_error = error
|
|
125
127
|
self.update(self._build_banner())
|
|
126
128
|
|
|
129
|
+
def set_update_notice(self, latest: str | None) -> None:
|
|
130
|
+
"""Show or hide the \"update available\" line in the welcome area.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
latest: Newer version from PyPI, or ``None`` to remove the line.
|
|
134
|
+
"""
|
|
135
|
+
cleaned = str(latest).strip() if latest else ""
|
|
136
|
+
self._update_latest = cleaned or None
|
|
137
|
+
self.update(self._build_banner())
|
|
138
|
+
|
|
127
139
|
def on_click(self, event: Click) -> None: # noqa: PLR6301 # Textual event handler
|
|
128
140
|
"""Open style-embedded hyperlinks on single click."""
|
|
129
141
|
open_style_link(event)
|
|
@@ -186,6 +198,17 @@ class WelcomeBanner(Static):
|
|
|
186
198
|
label = "MCP tool" if self._mcp_tool_count == 1 else "MCP tools"
|
|
187
199
|
parts.append(f"Loaded {self._mcp_tool_count} {label}\n")
|
|
188
200
|
|
|
201
|
+
if self._update_latest and not self._failed:
|
|
202
|
+
from soothe_cli.tui.update_check import upgrade_command
|
|
203
|
+
|
|
204
|
+
cmd = upgrade_command()
|
|
205
|
+
update_line = (
|
|
206
|
+
f"Update available: v{self._update_latest} (current: v{__version__}). "
|
|
207
|
+
f"Run: {cmd} — or /auto-update\n"
|
|
208
|
+
)
|
|
209
|
+
update_style = "yellow" if ansi else colors.warning
|
|
210
|
+
parts.append((update_line, update_style))
|
|
211
|
+
|
|
189
212
|
if tip_line is not None:
|
|
190
213
|
parts.append((tip_line, "dim"))
|
|
191
214
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/rendering/async_renderer_protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/execution.py
RENAMED
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/fallback.py
RENAMED
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py
RENAMED
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/goal_formatter.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/structured.py
RENAMED
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/subagent.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_stream_formatting.py
RENAMED
|
File without changes
|
{soothe_cli-0.5.2 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_stream_messages.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|