soothe-cli 0.2.0__tar.gz → 0.3.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 (112) hide show
  1. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/PKG-INFO +3 -3
  2. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/pyproject.toml +8 -4
  3. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -1
  4. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/run_cmd.py +5 -10
  5. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/thread_cmd.py +6 -3
  6. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/execution/launcher.py +0 -1
  7. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/main.py +7 -12
  8. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/renderer.py +4 -6
  9. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/formatter.py +27 -1
  10. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/pipeline.py +37 -11
  11. soothe_cli-0.3.4/src/soothe_cli/config/__init__.py +5 -0
  12. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/config/cli_config.py +28 -4
  13. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/__init__.py +2 -0
  14. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/command_router.py +9 -3
  15. soothe_cli-0.3.4/src/soothe_cli/shared/config_loader.py +64 -0
  16. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/display_policy.py +19 -0
  17. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/event_processor.py +30 -29
  18. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/message_processing.py +87 -29
  19. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/suppression_state.py +5 -4
  20. soothe_cli-0.3.4/src/soothe_cli/shared/tool_call_resolution.py +279 -0
  21. soothe_cli-0.3.4/src/soothe_cli/shared/tool_card_payload.py +121 -0
  22. soothe_cli-0.3.4/src/soothe_cli/shared/tool_message_format.py +77 -0
  23. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/app.py +65 -10
  24. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/command_registry.py +21 -0
  25. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/daemon_session.py +45 -1
  26. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/textual_adapter.py +583 -86
  27. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/theme.py +28 -1
  28. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/tool_display.py +110 -99
  29. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/message_store.py +150 -0
  30. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/messages.py +650 -17
  31. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/thread_selector.py +3 -67
  32. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/welcome.py +5 -75
  33. soothe_cli-0.2.0/src/soothe_cli/config/__init__.py +0 -5
  34. soothe_cli-0.2.0/src/soothe_cli/shared/config_loader.py +0 -68
  35. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/.gitignore +0 -0
  36. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/README.md +0 -0
  37. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/__init__.py +0 -0
  38. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/__init__.py +0 -0
  39. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/__init__.py +0 -0
  40. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
  41. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
  42. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/subagent_names.py +0 -0
  43. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/execution/__init__.py +0 -0
  44. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/execution/daemon.py +0 -0
  45. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/execution/headless.py +0 -0
  46. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/__init__.py +0 -0
  47. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/context.py +0 -0
  48. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/display_line.py +0 -0
  49. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/utils.py +0 -0
  50. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/plan/__init__.py +0 -0
  51. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/plan/rich_tree.py +0 -0
  52. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/essential_events.py +0 -0
  53. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/presentation_engine.py +0 -0
  54. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/processor_state.py +0 -0
  55. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/renderer_protocol.py +0 -0
  56. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/rendering.py +0 -0
  57. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/slash_commands.py +0 -0
  58. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/subagent_routing.py +0 -0
  59. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/__init__.py +0 -0
  60. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/base.py +0 -0
  61. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/execution.py +0 -0
  62. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/fallback.py +0 -0
  63. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/file_ops.py +0 -0
  64. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/goal_formatter.py +0 -0
  65. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/media.py +0 -0
  66. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/structured.py +0 -0
  67. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/web.py +0 -0
  68. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_output_formatter.py +0 -0
  69. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tui_trace_log.py +0 -0
  70. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/__init__.py +0 -0
  71. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_ask_user_types.py +0 -0
  72. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_cli_context.py +0 -0
  73. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_env_vars.py +0 -0
  74. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_session_stats.py +0 -0
  75. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_version.py +0 -0
  76. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/app.tcss +0 -0
  77. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/config.py +0 -0
  78. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/file_ops.py +0 -0
  79. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/formatting.py +0 -0
  80. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/hooks.py +0 -0
  81. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/input.py +0 -0
  82. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/media_utils.py +0 -0
  83. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/model_config.py +0 -0
  84. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/output.py +0 -0
  85. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/project_utils.py +0 -0
  86. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/sessions.py +0 -0
  87. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/skills/__init__.py +0 -0
  88. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/skills/invocation.py +0 -0
  89. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/skills/load.py +0 -0
  90. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/unicode_security.py +0 -0
  91. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/update_check.py +0 -0
  92. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  93. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/_links.py +0 -0
  94. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/approval.py +0 -0
  95. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
  96. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  97. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
  98. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
  99. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  100. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  101. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/diff.py +0 -0
  102. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/editor.py +0 -0
  103. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/history.py +0 -0
  104. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/loading.py +0 -0
  105. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  106. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  107. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  108. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/status.py +0 -0
  109. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  110. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
  111. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
  112. {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/tools.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.2.0
4
- Summary: Soothe CLI client - communicates with daemon via WebSocket (updated for SDK v0.4.0)
3
+ Version: 0.3.4
4
+ Summary: Soothe CLI client - communicates with daemon via WebSocket
5
5
  Project-URL: Homepage, https://github.com/caesar0301/soothe
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
7
7
  Project-URL: Repository, https://github.com/caesar0301/soothe
@@ -21,7 +21,7 @@ Requires-Python: <3.15,>=3.11
21
21
  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
- Requires-Dist: soothe-sdk<1.0.0,>=0.4.0
24
+ Requires-Dist: soothe-sdk<1.0.0,>=0.3.3
25
25
  Requires-Dist: textual>=0.40.0
26
26
  Requires-Dist: typer<1.0.0,>=0.9.0
27
27
  Requires-Dist: websockets>=12.0
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "soothe-cli"
7
- version = "0.2.0"
8
- description = "Soothe CLI client - communicates with daemon via WebSocket (updated for SDK v0.4.0)"
7
+ dynamic = ["version"]
8
+ description = "Soothe CLI client - communicates with daemon via WebSocket"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
11
11
  requires-python = ">=3.11,<3.15"
@@ -23,7 +23,7 @@ classifiers = [
23
23
  "Topic :: Software Development :: Libraries :: Python Modules",
24
24
  ]
25
25
  dependencies = [
26
- "soothe-sdk>=0.4.0,<1.0.0", # WebSocket client, protocol, types (v0.4.0 breaking change)
26
+ "soothe-sdk>=0.3.3,<1.0.0", # WebSocket client, protocol, types
27
27
  "typer>=0.9.0,<1.0.0", # CLI framework
28
28
  "textual>=0.40.0", # TUI framework
29
29
  "rich>=13.0.0", # Console output
@@ -71,8 +71,12 @@ ignore = ["E501"]
71
71
  [tool.ruff.format]
72
72
  docstring-code-format = true
73
73
 
74
+ [tool.hatch.version]
75
+ path = "../../VERSION"
76
+ pattern = "^(?P<version>[0-9]+\\.[0-9]+\\.[0-9]+)$"
77
+
74
78
  [tool.mypy]
75
79
  python_version = "3.11"
76
80
  warn_return_any = true
77
81
  warn_unused_configs = true
78
- disallow_untyped_defs = true
82
+ disallow_untyped_defs = true
@@ -40,7 +40,6 @@ def run(
40
40
  autonomous=True,
41
41
  max_iterations=max_iterations,
42
42
  output_format=output_format,
43
- verbosity=None,
44
43
  )
45
44
 
46
45
 
@@ -4,11 +4,10 @@ import logging
4
4
  import sys
5
5
  import time
6
6
  from pathlib import Path
7
- from typing import Literal
8
7
 
9
8
  import typer
10
9
  from soothe_sdk.client.config import SOOTHE_HOME
11
- from soothe_sdk.utils.logging import VERBOSITY_TO_LOG_LEVEL
10
+ from soothe_sdk.utils.logging import resolve_cli_log_level
12
11
 
13
12
  from soothe_cli.cli.execution import run_headless, run_tui
14
13
  from soothe_cli.shared import load_config, setup_logging
@@ -24,28 +23,24 @@ def run_impl(
24
23
  autonomous: bool, # noqa: FBT001
25
24
  max_iterations: int | None,
26
25
  output_format: str,
27
- verbosity: Literal["quiet", "minimal", "normal", "detailed", "debug"] | None,
28
26
  ) -> None:
29
27
  """Core implementation for running Soothe agent.
30
28
 
31
29
  Args:
32
30
  prompt: Optional prompt for headless mode
33
- config: Path to config file
31
+ config: Deprecated; passed through for ``--config`` compatibility (ignored for
32
+ client settings; see ``load_config``).
34
33
  thread_id: Thread ID to resume
35
34
  no_tui: Force headless mode
36
35
  autonomous: Enable autonomous iteration mode
37
36
  max_iterations: Max iterations for autonomous mode
38
37
  output_format: Output format (text or jsonl)
39
- verbosity: Verbosity level
40
38
  """
41
39
  startup_start = time.perf_counter()
42
40
 
43
41
  try:
44
42
  cfg = load_config(config)
45
- if verbosity is not None:
46
- # CLIConfig is a dataclass, update verbosity directly
47
- cfg.verbosity = verbosity
48
- log_level = VERBOSITY_TO_LOG_LEVEL.get(cfg.verbosity, "INFO")
43
+ log_level = resolve_cli_log_level(cfg.verbosity, logging_level=cfg.logging_level)
49
44
  log_file = Path(SOOTHE_HOME) / "logs" / "soothe-cli.log"
50
45
  setup_logging(log_level, log_file=log_file)
51
46
 
@@ -72,7 +67,7 @@ def run_impl(
72
67
  )
73
68
  else:
74
69
  # TUI mode (with optional initial prompt)
75
- run_tui(cfg, thread_id=thread_id, config_path=config, initial_prompt=prompt)
70
+ run_tui(cfg, thread_id=thread_id, initial_prompt=prompt)
76
71
 
77
72
  run_elapsed_s = time.perf_counter() - run_start
78
73
  typer.echo(f"Total running time: {run_elapsed_s:.2f}s", err=True)
@@ -12,7 +12,7 @@ from typing import Annotated, Any
12
12
  import typer
13
13
  from soothe_sdk.client import WebSocketClient, is_daemon_live, websocket_url_from_config
14
14
  from soothe_sdk.client.config import SOOTHE_HOME
15
- from soothe_sdk.utils.logging import VERBOSITY_TO_LOG_LEVEL
15
+ from soothe_sdk.utils.logging import resolve_cli_log_level
16
16
 
17
17
  from soothe_cli.shared import load_config
18
18
 
@@ -209,7 +209,10 @@ def thread_continue(
209
209
  from soothe_cli.shared import setup_logging
210
210
 
211
211
  cfg = load_config(config)
212
- log_level = VERBOSITY_TO_LOG_LEVEL.get(cfg.logging.verbosity, "INFO")
212
+ log_level = resolve_cli_log_level(
213
+ cfg.logging.verbosity,
214
+ logging_level=cfg.logging.level,
215
+ )
213
216
  log_file = Path(SOOTHE_HOME) / "logs" / "soothe-cli.log"
214
217
  setup_logging(log_level, log_file=log_file)
215
218
  ws_url = websocket_url_from_config(cfg)
@@ -246,7 +249,7 @@ def thread_continue(
246
249
 
247
250
  thread_id = asyncio.run(get_last_thread_via_daemon())
248
251
 
249
- run_tui(cfg, thread_id=thread_id, config_path=config)
252
+ run_tui(cfg, thread_id=thread_id)
250
253
 
251
254
 
252
255
  def thread_archive(
@@ -11,7 +11,6 @@ def run_tui(
11
11
  cfg: CLIConfig,
12
12
  *,
13
13
  thread_id: str | None = None,
14
- config_path: str | None = None,
15
14
  initial_prompt: str | None = None,
16
15
  ) -> None:
17
16
  """Launch the Textual TUI (with daemon auto-start)."""
@@ -7,7 +7,7 @@ from dotenv import load_dotenv
7
7
  load_dotenv()
8
8
 
9
9
  from importlib.metadata import version # noqa: E402
10
- from typing import Annotated, Literal # noqa: E402
10
+ from typing import Annotated # noqa: E402
11
11
 
12
12
  import typer # noqa: E402
13
13
 
@@ -54,7 +54,11 @@ def main(
54
54
  ctx: typer.Context,
55
55
  config: Annotated[
56
56
  str | None,
57
- typer.Option("--config", "-c", help="Path to configuration file."),
57
+ typer.Option(
58
+ "--config",
59
+ "-c",
60
+ help="Ignored for client settings; edit ~/.soothe/config/cli_config.yml instead.",
61
+ ),
58
62
  ] = None,
59
63
  prompt: Annotated[
60
64
  str | None,
@@ -70,14 +74,6 @@ def main(
70
74
  str,
71
75
  typer.Option("--format", "-f", help="Output format for headless mode: text or jsonl."),
72
76
  ] = "text",
73
- verbosity: Annotated[
74
- Literal["quiet", "minimal", "normal", "detailed", "debug"] | None,
75
- typer.Option(
76
- "--verbosity",
77
- "-v",
78
- help="Verbosity level: quiet, normal, detailed, debug. 'minimal' is accepted as an alias.",
79
- ),
80
- ] = None,
81
77
  show_help: Annotated[ # noqa: FBT002
82
78
  bool,
83
79
  typer.Option("--help", "-h", is_flag=True, help="Show this message and exit."),
@@ -96,7 +92,7 @@ def main(
96
92
  Examples:
97
93
  soothe # Interactive TUI mode
98
94
  soothe -p "Research AI advances" # Headless single-prompt mode
99
- soothe --config custom.yml # Use custom CLI config
95
+ soothe --config custom.yml # Ignored for client settings; use ~/.soothe/config/cli_config.yml
100
96
  soothe thread list # List conversation threads
101
97
  """
102
98
  # Handle -h/--help flag
@@ -121,7 +117,6 @@ def main(
121
117
  autonomous=False,
122
118
  max_iterations=None,
123
119
  output_format=output_format,
124
- verbosity=verbosity,
125
120
  )
126
121
 
127
122
 
@@ -207,9 +207,9 @@ class CliRenderer:
207
207
  if not self._presentation.tier_visible(VerbosityTier.NORMAL, self._verbosity):
208
208
  return
209
209
 
210
- # HARD SUPPRESS during multi-step execution (IG-143)
211
- if self._state.suppression.should_suppress_output():
212
- return
210
+ # Multi-step / agentic suppression applies to assistant stdout only (IG-143).
211
+ # Tool calls and results still stream to stderr at normal+ verbosity so headless
212
+ # runs show the same tool activity as the TUI.
213
213
 
214
214
  self._stderr_begin_icon_block()
215
215
 
@@ -251,9 +251,7 @@ class CliRenderer:
251
251
  if not self._presentation.tier_visible(VerbosityTier.NORMAL, self._verbosity):
252
252
  return
253
253
 
254
- # HARD SUPPRESS during multi-step execution (IG-143)
255
- if self._state.suppression.should_suppress_output():
256
- return
254
+ # See on_tool_call: do not suppress stderr tool results during multi-step runs.
257
255
 
258
256
  self._stderr_begin_icon_block()
259
257
 
@@ -261,6 +261,25 @@ def format_subagent_done(
261
261
  )
262
262
 
263
263
 
264
+ def format_plan_phase_reasoning(
265
+ label: str,
266
+ text: str,
267
+ *,
268
+ namespace: tuple[str, ...] = (),
269
+ verbosity_tier: VerbosityTier = VerbosityTier.NORMAL,
270
+ ) -> DisplayLine:
271
+ """Format a labeled plan-phase reasoning line (assessment vs plan strategy)."""
272
+ content = f"💭 {label}: {text}"
273
+ return DisplayLine(
274
+ level=3,
275
+ content=content,
276
+ icon="•",
277
+ indent=indent_for_level(3),
278
+ source_prefix=_derive_source_prefix(namespace, verbosity_tier),
279
+ newline_before=True,
280
+ )
281
+
282
+
264
283
  def format_reasoning(
265
284
  reasoning: str,
266
285
  *,
@@ -296,6 +315,7 @@ def format_judgement(
296
315
  judgement: str,
297
316
  action: str,
298
317
  *,
318
+ plan_action: str | None = None,
299
319
  namespace: tuple[str, ...] = (),
300
320
  verbosity_tier: VerbosityTier = VerbosityTier.NORMAL,
301
321
  ) -> DisplayLine:
@@ -307,6 +327,7 @@ def format_judgement(
307
327
  Args:
308
328
  judgement: Human-readable summary of the decision.
309
329
  action: Action taken ("continue" or "complete").
330
+ plan_action: When set, show ``[keep]`` or ``[new]`` before the judgement text.
310
331
  namespace: Event namespace.
311
332
  verbosity_tier: Current verbosity tier.
312
333
 
@@ -315,8 +336,12 @@ def format_judgement(
315
336
  """
316
337
  action_icon = "→" if action == "continue" else "✓"
317
338
 
339
+ badge = ""
340
+ if plan_action in ("keep", "new"):
341
+ badge = f"[{plan_action}] "
342
+
318
343
  # Polish: Add "Reason:" prefix to make LLM reasoning prominent
319
- content = f"🌀 {judgement}"
344
+ content = f"🌀 {badge}{judgement}"
320
345
 
321
346
  return DisplayLine(
322
347
  level=2, # Use level 2 for more prominence (like step headers)
@@ -436,6 +461,7 @@ __all__ = [
436
461
  "format_goal_done",
437
462
  "format_goal_header",
438
463
  "format_judgement",
464
+ "format_plan_phase_reasoning",
439
465
  "format_reasoning",
440
466
  "format_step_done",
441
467
  "format_step_header",
@@ -15,6 +15,7 @@ from soothe_cli.cli.stream.formatter import (
15
15
  format_goal_done,
16
16
  format_goal_header,
17
17
  format_judgement,
18
+ format_plan_phase_reasoning,
18
19
  format_reasoning,
19
20
  format_step_done,
20
21
  format_step_header,
@@ -175,8 +176,8 @@ class StreamDisplayPipeline:
175
176
  if "judgement" in action:
176
177
  return self._on_subagent_judgement(event)
177
178
 
178
- # Step events (browser automation steps)
179
- if "step" in action and "running" in action:
179
+ # Step events (browser automation): type ends with .step.running
180
+ if len(parts) >= 5 and parts[3] == "step" and parts[4] == "running": # noqa: PLR2004
180
181
  return self._on_capability_step(event, subagent)
181
182
 
182
183
  # Completed events
@@ -572,25 +573,50 @@ class StreamDisplayPipeline:
572
573
  # Determine action type
573
574
  action = "complete" if status == "done" else "continue"
574
575
 
576
+ raw_plan_action = event.get("plan_action")
577
+ plan_action_kw: str | None = raw_plan_action if raw_plan_action in ("keep", "new") else None
578
+
575
579
  lines = [
576
580
  format_judgement(
577
581
  action_text,
578
582
  action,
583
+ plan_action=plan_action_kw,
579
584
  namespace=self._current_namespace,
580
585
  verbosity_tier=self._verbosity_tier,
581
586
  )
582
587
  ]
583
588
 
584
- # Add reasoning line if present (IG-XXX: Show internal technical analysis)
585
- reasoning = event.get("reasoning", "").strip()
586
- if reasoning:
587
- lines.append(
588
- format_reasoning(
589
- reasoning,
590
- namespace=self._current_namespace,
591
- verbosity_tier=self._verbosity_tier,
589
+ assessment = event.get("assessment_reasoning", "").strip()
590
+ plan_reasoning = event.get("plan_reasoning", "").strip()
591
+ if assessment or plan_reasoning:
592
+ if assessment:
593
+ lines.append(
594
+ format_plan_phase_reasoning(
595
+ "Assessment",
596
+ assessment,
597
+ namespace=self._current_namespace,
598
+ verbosity_tier=self._verbosity_tier,
599
+ )
600
+ )
601
+ if plan_reasoning:
602
+ lines.append(
603
+ format_plan_phase_reasoning(
604
+ "Plan",
605
+ plan_reasoning,
606
+ namespace=self._current_namespace,
607
+ verbosity_tier=self._verbosity_tier,
608
+ )
609
+ )
610
+ else:
611
+ reasoning = event.get("reasoning", "").strip()
612
+ if reasoning:
613
+ lines.append(
614
+ format_reasoning(
615
+ reasoning,
616
+ namespace=self._current_namespace,
617
+ verbosity_tier=self._verbosity_tier,
618
+ )
592
619
  )
593
- )
594
620
 
595
621
  return lines
596
622
 
@@ -0,0 +1,5 @@
1
+ """CLI configuration package."""
2
+
3
+ from soothe_cli.config.cli_config import CLI_CONFIG_FILE, CLIConfig
4
+
5
+ __all__ = ["CLI_CONFIG_FILE", "CLIConfig"]
@@ -6,6 +6,11 @@ from dataclasses import dataclass, field
6
6
  from pathlib import Path
7
7
  from typing import Any
8
8
 
9
+ from soothe_sdk.client.config import SOOTHE_HOME
10
+
11
+ # Sole on-disk location for CLI client settings (WebSocket address, progress verbosity, …).
12
+ CLI_CONFIG_FILE = Path(SOOTHE_HOME) / "config" / "cli_config.yml"
13
+
9
14
 
10
15
  @dataclass
11
16
  class CLIConfig:
@@ -19,8 +24,11 @@ class CLIConfig:
19
24
  daemon_host: str = "127.0.0.1"
20
25
  daemon_port: int = 8765
21
26
 
22
- # CLI behavior
27
+ # CLI behavior — verbosity: progress/event display (quiet … debug).
23
28
  verbosity: str = "normal"
29
+ # logging_level: DEBUG/INFO/… for ~/.soothe/logs/soothe-cli.log; None = derive from verbosity.
30
+ logging_level: str | None = None
31
+
24
32
  output_format: str = "text"
25
33
 
26
34
  # Paths
@@ -81,7 +89,7 @@ class CLIConfig:
81
89
  import yaml
82
90
 
83
91
  if config_path is None:
84
- config_path = Path.home() / ".soothe" / "config" / "cli_config.yml"
92
+ config_path = CLI_CONFIG_FILE
85
93
 
86
94
  if not config_path.exists():
87
95
  return cls() # Use defaults
@@ -94,10 +102,15 @@ class CLIConfig:
94
102
  transports = daemon_section.get("transports", {})
95
103
  websocket = transports.get("websocket", {})
96
104
 
105
+ raw_level = data.get("logging_level")
106
+ if raw_level is not None and not isinstance(raw_level, str):
107
+ raw_level = None
108
+
97
109
  return cls(
98
110
  daemon_host=websocket.get("host", "127.0.0.1"),
99
111
  daemon_port=websocket.get("port", 8765),
100
- verbosity=data.get("logging", {}).get("verbosity", "normal"),
112
+ verbosity=data.get("verbosity", "normal"),
113
+ logging_level=raw_level,
101
114
  soothe_home=Path(data.get("home", str(Path.home() / ".soothe"))),
102
115
  )
103
116
 
@@ -113,10 +126,17 @@ class CLIConfig:
113
126
  Returns:
114
127
  CLIConfig with WebSocket settings extracted.
115
128
  """
129
+ level_from_full = getattr(soothe_config.logging, "level", None)
130
+ if isinstance(level_from_full, str) and level_from_full.strip():
131
+ logging_level = level_from_full.strip()
132
+ else:
133
+ logging_level = None
134
+
116
135
  return cls(
117
136
  daemon_host=soothe_config.daemon.transports.websocket.host,
118
137
  daemon_port=soothe_config.daemon.transports.websocket.port,
119
138
  verbosity=soothe_config.logging.verbosity,
139
+ logging_level=logging_level,
120
140
  soothe_home=Path(soothe_config.home),
121
141
  )
122
142
 
@@ -147,7 +167,11 @@ class CLIConfig:
147
167
  @property
148
168
  def logging(self) -> Any:
149
169
  """Compatibility property: return logging config structure."""
150
- return type("LoggingConfig", (), {"verbosity": self.verbosity})()
170
+ return type(
171
+ "LoggingConfig",
172
+ (),
173
+ {"verbosity": self.verbosity, "level": self.logging_level},
174
+ )()
151
175
 
152
176
  @property
153
177
  def home(self) -> str:
@@ -35,6 +35,7 @@ from soothe_cli.shared.event_processor import EventProcessor
35
35
  from soothe_cli.shared.message_processing import (
36
36
  accumulate_tool_call_chunks,
37
37
  coerce_tool_call_args_to_dict,
38
+ extract_tool_args_dict,
38
39
  extract_tool_brief,
39
40
  finalize_pending_tool_call,
40
41
  format_tool_call_args,
@@ -75,6 +76,7 @@ __all__ = [
75
76
  "VerbosityLevel",
76
77
  "accumulate_tool_call_chunks",
77
78
  "coerce_tool_call_args_to_dict",
79
+ "extract_tool_args_dict",
78
80
  # Display Policy (unified filtering module)
79
81
  "create_display_policy",
80
82
  "extract_tool_brief",
@@ -12,6 +12,8 @@ import json
12
12
  import logging
13
13
  from typing import TYPE_CHECKING, Any
14
14
 
15
+ from soothe_cli.shared.subagent_routing import parse_subagent_from_input
16
+
15
17
  if TYPE_CHECKING:
16
18
  from rich.console import Console
17
19
  from soothe_sdk.client import WebSocketClient
@@ -224,15 +226,19 @@ async def handle_rpc_command(
224
226
 
225
227
 
226
228
  async def handle_routing_command(cmd_input: str, console: Console, client: WebSocketClient) -> None:
227
- """Handle daemon routing command by sending plain text input (RFC-404).
229
+ """Handle daemon routing command by sending input with optional subagent (RFC-404).
230
+
231
+ For ``/browser``, ``/claude``, and ``/research``, sets the WebSocket ``subagent``
232
+ field (same contract as headless ``-p``) so the daemon uses direct subagent routing.
233
+ Other routing commands (e.g. ``/plan``) are sent as plain text unchanged.
228
234
 
229
235
  Args:
230
236
  cmd_input: Full command input (e.g., "/browser AI trends")
231
237
  console: Rich console
232
238
  client: WebSocket client
233
239
  """
234
- # Send as plain text - daemon input parser will route
235
- await client.send_input(cmd_input)
240
+ subagent_name, text = parse_subagent_from_input(cmd_input.strip())
241
+ await client.send_input(text, subagent=subagent_name)
236
242
 
237
243
 
238
244
  __all__ = [
@@ -0,0 +1,64 @@
1
+ """Configuration loading utilities (IG-174 Phase 3)."""
2
+
3
+ import logging
4
+ import time
5
+ from pathlib import Path
6
+
7
+ from dotenv import load_dotenv
8
+
9
+ from soothe_cli.config.cli_config import CLI_CONFIG_FILE, CLIConfig
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Config cache for performance
14
+ _config_cache: dict[str, CLIConfig] = {}
15
+
16
+
17
+ def load_config(config_path: str | None = None) -> CLIConfig:
18
+ """Load CLI client configuration from ``cli_config.yml`` only.
19
+
20
+ Client settings (WebSocket endpoint, progress verbosity, etc.) always come from
21
+ :data:`~soothe_cli.config.cli_config.CLI_CONFIG_FILE`. The optional ``config_path``
22
+ argument is accepted for backward compatibility with existing ``--config`` flags
23
+ but is **ignored**; a warning is logged when it is non-``None``.
24
+
25
+ Args:
26
+ config_path: Deprecated. If set, ignored after logging a warning.
27
+
28
+ Returns:
29
+ A ``CLIConfig`` instance.
30
+ """
31
+ # Load environment variables from .env file
32
+ # This ensures LangSmith and other env vars are available
33
+ load_dotenv()
34
+
35
+ if config_path is not None:
36
+ logger.warning(
37
+ "Ignoring --config %s; CLI client settings are loaded only from %s",
38
+ config_path,
39
+ CLI_CONFIG_FILE,
40
+ )
41
+
42
+ cache_key = str(CLI_CONFIG_FILE)
43
+
44
+ # Check cache first
45
+ if cache_key in _config_cache:
46
+ logger.debug("Config loaded from cache: %s", cache_key)
47
+ return _config_cache[cache_key]
48
+
49
+ load_start = time.perf_counter()
50
+
51
+ config = CLIConfig.from_config_file(CLI_CONFIG_FILE)
52
+ _config_cache[cache_key] = config
53
+
54
+ elapsed_ms = (time.perf_counter() - load_start) * 1000
55
+ if Path(CLI_CONFIG_FILE).is_file():
56
+ logger.info("Loaded CLI config from '%s' in %.1fms", CLI_CONFIG_FILE, elapsed_ms)
57
+ else:
58
+ logger.debug(
59
+ "CLI config file missing at %s; using defaults (%.1fms)",
60
+ CLI_CONFIG_FILE,
61
+ elapsed_ms,
62
+ )
63
+
64
+ return config
@@ -58,6 +58,23 @@ def normalize_verbosity(verbosity: str) -> VerbosityLevel:
58
58
  return "normal"
59
59
 
60
60
 
61
+ def should_show_tool_call_ui(verbosity: str | VerbosityLevel) -> bool:
62
+ """Whether the TUI should mount tool-call rows (``ToolCallMessage`` / tool output).
63
+
64
+ Controlled only by ``logging.verbosity`` in the CLI client config
65
+ (``~/.soothe/config/cli_config.yml``), same scale as CLI progress — not by
66
+ LangGraph namespace or event type. ``quiet`` hides tool UI; other levels show it.
67
+
68
+ Args:
69
+ verbosity: Raw or normalized verbosity string (e.g. from ``cli_config.yml``).
70
+
71
+ Returns:
72
+ False when verbosity is ``quiet``; True for ``normal``, ``detailed``, and ``debug``.
73
+ """
74
+ v = normalize_verbosity(verbosity) if isinstance(verbosity, str) else verbosity
75
+ return v != "quiet"
76
+
77
+
61
78
  # =============================================================================
62
79
  # Policy Configuration Constants
63
80
  # =============================================================================
@@ -406,4 +423,6 @@ __all__ = [
406
423
  "VerbosityLevel",
407
424
  "VerbosityTier",
408
425
  "create_display_policy",
426
+ "normalize_verbosity",
427
+ "should_show_tool_call_ui",
409
428
  ]