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.
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/PKG-INFO +3 -3
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/pyproject.toml +8 -4
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -1
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/run_cmd.py +5 -10
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/thread_cmd.py +6 -3
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/execution/launcher.py +0 -1
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/main.py +7 -12
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/renderer.py +4 -6
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/formatter.py +27 -1
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/pipeline.py +37 -11
- soothe_cli-0.3.4/src/soothe_cli/config/__init__.py +5 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/config/cli_config.py +28 -4
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/__init__.py +2 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/command_router.py +9 -3
- soothe_cli-0.3.4/src/soothe_cli/shared/config_loader.py +64 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/display_policy.py +19 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/event_processor.py +30 -29
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/message_processing.py +87 -29
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/suppression_state.py +5 -4
- soothe_cli-0.3.4/src/soothe_cli/shared/tool_call_resolution.py +279 -0
- soothe_cli-0.3.4/src/soothe_cli/shared/tool_card_payload.py +121 -0
- soothe_cli-0.3.4/src/soothe_cli/shared/tool_message_format.py +77 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/app.py +65 -10
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/command_registry.py +21 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/daemon_session.py +45 -1
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/textual_adapter.py +583 -86
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/theme.py +28 -1
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/tool_display.py +110 -99
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/message_store.py +150 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/messages.py +650 -17
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/thread_selector.py +3 -67
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/welcome.py +5 -75
- soothe_cli-0.2.0/src/soothe_cli/config/__init__.py +0 -5
- soothe_cli-0.2.0/src/soothe_cli/shared/config_loader.py +0 -68
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/.gitignore +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/README.md +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/commands/subagent_names.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/context.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/stream/display_line.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/cli/utils.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/plan/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/plan/rich_tree.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/essential_events.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/presentation_engine.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/processor_state.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/renderer_protocol.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/rendering.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/slash_commands.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/subagent_routing.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/base.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/execution.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/fallback.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/file_ops.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/goal_formatter.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/media.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/structured.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_formatters/web.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tool_output_formatter.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/shared/tui_trace_log.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_ask_user_types.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_session_stats.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/app.tcss +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/config.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/file_ops.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/formatting.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/output.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/sessions.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/approval.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
- {soothe_cli-0.2.0 → soothe_cli-0.3.4}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
- {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.
|
|
4
|
-
Summary: Soothe CLI client - communicates with daemon via WebSocket
|
|
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.
|
|
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
|
-
|
|
8
|
-
description = "Soothe CLI client - communicates with daemon via WebSocket
|
|
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.
|
|
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
|
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
|
252
|
+
run_tui(cfg, thread_id=thread_id)
|
|
250
253
|
|
|
251
254
|
|
|
252
255
|
def thread_archive(
|
|
@@ -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
|
|
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(
|
|
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 #
|
|
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
|
-
#
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
#
|
|
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
|
|
179
|
-
if "step"
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
if
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
|
@@ -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 =
|
|
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("
|
|
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(
|
|
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
|
|
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
|
-
|
|
235
|
-
await client.send_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
|
]
|