soothe-cli 0.5.2__tar.gz → 0.5.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/.gitignore +1 -5
  2. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/PKG-INFO +1 -1
  3. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/commands/run_cmd.py +25 -7
  4. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/main.py +19 -4
  5. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/config/cli_config.py +1 -1
  6. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/_env_vars.py +4 -1
  7. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_history.py +4 -4
  8. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_messages_mixin.py +2 -2
  9. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_startup.py +19 -15
  10. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/config.py +3 -3
  11. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/textual_adapter/_turn.py +13 -4
  12. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/update_check.py +46 -15
  13. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/message_store.py +14 -14
  14. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/messages.py +33 -17
  15. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/welcome.py +23 -0
  16. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/README.md +0 -0
  17. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/pyproject.toml +0 -0
  18. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/__init__.py +0 -0
  19. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/__init__.py +0 -0
  20. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/commands/__init__.py +0 -0
  21. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
  22. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
  23. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/execution/__init__.py +0 -0
  24. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/execution/daemon.py +0 -0
  25. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/execution/headless.py +0 -0
  26. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
  27. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/execution/launcher.py +0 -0
  28. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/stream/__init__.py +0 -0
  29. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/stream/context.py +0 -0
  30. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/stream/display_line.py +0 -0
  31. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/stream/formatter.py +0 -0
  32. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/stream/pipeline.py +0 -0
  33. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/cli/stream/task_scope.py +0 -0
  34. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/config/__init__.py +0 -0
  35. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/plan/__init__.py +0 -0
  36. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/plan/rich_tree.py +0 -0
  37. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/__init__.py +0 -0
  38. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/commands/__init__.py +0 -0
  39. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/commands/command_router.py +0 -0
  40. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/commands/slash_commands.py +0 -0
  41. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/commands/subagent_routing.py +0 -0
  42. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/config_loader.py +0 -0
  43. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/core/__init__.py +0 -0
  44. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/core/event_processor.py +0 -0
  45. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/core/presentation_engine.py +0 -0
  46. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/core/processor_state.py +0 -0
  47. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/core/renderer_protocol.py +0 -0
  48. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/duration_format.py +0 -0
  49. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/events/__init__.py +0 -0
  50. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/events/display_policy.py +0 -0
  51. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/events/essential_events.py +0 -0
  52. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/events/explore_task_display.py +0 -0
  53. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/events/stream_accumulator.py +0 -0
  54. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/events/tui_trace_log.py +0 -0
  55. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/rendering/__init__.py +0 -0
  56. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/rendering/async_renderer_protocol.py +0 -0
  57. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/rendering/renderer_base.py +0 -0
  58. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/__init__.py +0 -0
  59. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/_utils.py +0 -0
  60. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/message_processing.py +0 -0
  61. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/rendering.py +0 -0
  62. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_call_resolution.py +0 -0
  63. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_card_payload.py +0 -0
  64. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_card_visibility.py +0 -0
  65. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/__init__.py +0 -0
  66. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/base.py +0 -0
  67. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/execution.py +0 -0
  68. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/fallback.py +0 -0
  69. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py +0 -0
  70. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/goal_formatter.py +0 -0
  71. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/media.py +0 -0
  72. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/structured.py +0 -0
  73. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/subagent.py +0 -0
  74. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_formatters/web.py +0 -0
  75. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_message_format.py +0 -0
  76. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/shared/tools/tool_output_formatter.py +0 -0
  77. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/__init__.py +0 -0
  78. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/_ask_user_types.py +0 -0
  79. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/_cli_context.py +0 -0
  80. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/_session_stats.py +0 -0
  81. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/_version.py +0 -0
  82. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/__init__.py +0 -0
  83. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_app.py +0 -0
  84. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_commands.py +0 -0
  85. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_execution.py +0 -0
  86. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_model.py +0 -0
  87. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_module_init.py +0 -0
  88. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/_ui.py +0 -0
  89. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/app/app.tcss +0 -0
  90. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/command_registry.py +0 -0
  91. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/daemon_session.py +0 -0
  92. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/file_ops.py +0 -0
  93. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/formatting.py +0 -0
  94. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/hooks.py +0 -0
  95. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/input.py +0 -0
  96. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/media_utils.py +0 -0
  97. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/message_display_filter.py +0 -0
  98. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/model_config.py +0 -0
  99. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/output.py +0 -0
  100. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/preview_limits.py +0 -0
  101. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/project_utils.py +0 -0
  102. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/sessions.py +0 -0
  103. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/skills/__init__.py +0 -0
  104. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/skills/invocation.py +0 -0
  105. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/skills/load.py +0 -0
  106. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/textual_adapter/__init__.py +0 -0
  107. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/textual_adapter/_adapter.py +0 -0
  108. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/textual_adapter/_stream_formatting.py +0 -0
  109. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/textual_adapter/_stream_messages.py +0 -0
  110. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/textual_adapter/_turn_helpers.py +0 -0
  111. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/theme.py +0 -0
  112. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/tool_display.py +0 -0
  113. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/unicode_security.py +0 -0
  114. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  115. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/_links.py +0 -0
  116. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/approval.py +0 -0
  117. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
  118. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  119. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
  120. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
  121. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  122. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  123. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/diff.py +0 -0
  124. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/editor.py +0 -0
  125. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/history.py +0 -0
  126. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/loading.py +0 -0
  127. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  128. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  129. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  130. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  131. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/status.py +0 -0
  132. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  133. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
  134. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
  135. {soothe_cli-0.5.2 → soothe_cli-0.5.4}/src/soothe_cli/tui/widgets/tools.py +0 -0
@@ -94,11 +94,7 @@ ipython_config.py
94
94
  # install all needed dependencies.
95
95
  #Pipfile.lock
96
96
 
97
- # UV
98
- # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
- # This is especially recommended for binary packages to ensure reproducibility, and is more
100
- # commonly ignored for libraries.
101
- uv.lock
97
+ # UV — lockfile is committed for reproducible installs (Docker `uv sync --frozen`, CI).
102
98
  .uv/
103
99
 
104
100
  # poetry
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.5.2
3
+ Version: 0.5.4
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
@@ -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 for headless mode
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: Force headless mode
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" / "soothe-cli.log"
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
- if no_tui:
66
- # Headless mode (force no TUI)
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 or "",
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", "-p", help="Prompt to send as user message (headless single-shot mode)."
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("--no-tui", help="Disable TUI; run single prompt and exit."),
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 provide a prompt via --prompt/-p option.
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" # Headless single-prompt mode
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/soothe-cli.log; None = default INFO.
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
- """Enable automatic CLI updates ('1', 'true', or 'yes')."""
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 "new").strip()
442
- plan_action = plan_action_raw if plan_action_raw in {"keep", "new"} else "new"
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.COGNITION_PLAN,
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.COGNITION_PLAN,
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
- CognitionPlanReasonMessage,
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
- | CognitionPlanReasonMessage
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
- # (opt-out via env var or config.yml [update].check)
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 or notify (failures surfaced to user)
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 # vs [^] - shown when expanded (click to collapse)
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
- CognitionPlanReasonMessage,
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", "new")
1399
- plan_action = pa_raw if pa_raw in ("keep", "new") else "new"
1400
- plan_widget = CognitionPlanReasonMessage(
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
- Checks `SOOTHE_NO_UPDATE_CHECK` env var and the `[update].check` key
322
- in `config.yml`.
323
-
324
- Defaults to enabled.
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
- if os.environ.get("SOOTHE_NO_UPDATE_CHECK"):
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
- return _read_update_config().get("check", True)
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 is_auto_update_enabled() -> bool:
332
- """Return whether auto-update is enabled.
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
- Opt-in via `SOOTHE_AUTO_UPDATE=1` env var or
335
- `[update].auto_update = true` in `config.yml`.
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
- Defaults to `False`.
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
- if os.environ.get("SOOTHE_AUTO_UPDATE", "").lower() in {"1", "true", "yes"}:
346
- return True
347
- return _read_update_config().get("auto_update", False)
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
- COGNITION_PLAN = "cognition_plan"
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 (COGNITION_PLAN only)."""
166
+ """User-facing next step (COGNITION_REASON only)."""
167
167
 
168
168
  cognition_plan_status: str | None = None
169
- """Plan status: continue, replan, done (COGNITION_PLAN only)."""
169
+ """Plan status: continue, replan, done (COGNITION_REASON only)."""
170
170
 
171
171
  cognition_plan_iteration: int | None = None
172
- """Agent-loop iteration (COGNITION_PLAN only)."""
172
+ """Agent-loop iteration (COGNITION_REASON only)."""
173
173
 
174
174
  cognition_plan_action: str | None = None
175
- """``keep`` or ``new`` (COGNITION_PLAN only)."""
175
+ """``keep`` or ``new`` (COGNITION_REASON only)."""
176
176
 
177
177
  cognition_plan_assessment: str | None = None
178
- """Phase-1 assessment text (COGNITION_PLAN only)."""
178
+ """Phase-1 assessment text (COGNITION_REASON only)."""
179
179
 
180
180
  cognition_plan_strategy: str | None = None
181
- """Phase-2 plan reasoning (COGNITION_PLAN only)."""
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
- CognitionPlanReasonMessage,
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.COGNITION_PLAN:
333
- return CognitionPlanReasonMessage(
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 "new",
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
- CognitionPlanReasonMessage,
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, CognitionPlanReasonMessage):
417
+ if isinstance(widget, CognitionReasonMessage):
418
418
  return cls(
419
- type=MessageType.COGNITION_PLAN,
419
+ type=MessageType.COGNITION_REASON,
420
420
  content="",
421
421
  id=widget_id,
422
422
  cognition_plan_next_action=widget._next_action,