soothe-cli 0.1.0__tar.gz → 0.2.0__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 (107) hide show
  1. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/.gitignore +1 -0
  2. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/PKG-INFO +3 -3
  3. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/pyproject.toml +3 -3
  4. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/commands/autopilot_cmd.py +11 -11
  5. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/commands/config_cmd.py +3 -3
  6. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/commands/run_cmd.py +6 -5
  7. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/commands/status_cmd.py +2 -2
  8. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/commands/thread_cmd.py +2 -1
  9. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/execution/daemon.py +2 -2
  10. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/renderer.py +2 -2
  11. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/stream/display_line.py +8 -5
  12. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/stream/formatter.py +51 -17
  13. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/stream/pipeline.py +127 -32
  14. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/config/cli_config.py +2 -2
  15. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/__init__.py +1 -1
  16. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/config_loader.py +1 -1
  17. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/display_policy.py +3 -7
  18. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/event_processor.py +5 -4
  19. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/message_processing.py +4 -2
  20. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/presentation_engine.py +1 -1
  21. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/processor_state.py +1 -1
  22. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/renderer_protocol.py +1 -1
  23. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/rendering.py +1 -1
  24. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/fallback.py +2 -2
  25. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/structured.py +3 -3
  26. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_output_formatter.py +1 -1
  27. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tui_trace_log.py +1 -1
  28. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/app.py +1 -1
  29. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/config.py +1 -1
  30. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/model_config.py +1 -1
  31. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/sessions.py +1 -1
  32. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/textual_adapter.py +39 -15
  33. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/theme.py +1 -1
  34. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/tool_display.py +1 -1
  35. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/update_check.py +1 -1
  36. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +4 -4
  37. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/autopilot_screen.py +1 -1
  38. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/chat_input.py +1 -1
  39. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/history.py +1 -1
  40. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/messages.py +24 -0
  41. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/README.md +0 -0
  42. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/__init__.py +0 -0
  43. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/__init__.py +0 -0
  44. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/commands/__init__.py +0 -0
  45. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/commands/subagent_names.py +0 -0
  46. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/execution/__init__.py +0 -0
  47. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/execution/headless.py +0 -0
  48. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/execution/launcher.py +0 -0
  49. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/main.py +0 -0
  50. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/stream/__init__.py +0 -0
  51. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/stream/context.py +0 -0
  52. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/cli/utils.py +0 -0
  53. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/config/__init__.py +0 -0
  54. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/plan/__init__.py +0 -0
  55. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/plan/rich_tree.py +0 -0
  56. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/command_router.py +0 -0
  57. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/essential_events.py +0 -0
  58. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/slash_commands.py +0 -0
  59. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/subagent_routing.py +0 -0
  60. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/suppression_state.py +0 -0
  61. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/__init__.py +0 -0
  62. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/base.py +0 -0
  63. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/execution.py +0 -0
  64. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/file_ops.py +0 -0
  65. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/goal_formatter.py +0 -0
  66. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/media.py +0 -0
  67. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/shared/tool_formatters/web.py +0 -0
  68. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/__init__.py +0 -0
  69. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/_ask_user_types.py +0 -0
  70. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/_cli_context.py +0 -0
  71. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/_env_vars.py +0 -0
  72. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/_session_stats.py +0 -0
  73. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/_version.py +0 -0
  74. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/app.tcss +0 -0
  75. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/command_registry.py +0 -0
  76. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/daemon_session.py +0 -0
  77. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/file_ops.py +0 -0
  78. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/formatting.py +0 -0
  79. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/hooks.py +0 -0
  80. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/input.py +0 -0
  81. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/media_utils.py +0 -0
  82. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/output.py +0 -0
  83. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/project_utils.py +0 -0
  84. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/skills/__init__.py +0 -0
  85. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/skills/invocation.py +0 -0
  86. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/skills/load.py +0 -0
  87. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/unicode_security.py +0 -0
  88. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  89. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/_links.py +0 -0
  90. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/approval.py +0 -0
  91. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
  92. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  93. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  94. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/diff.py +0 -0
  95. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/editor.py +0 -0
  96. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/loading.py +0 -0
  97. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  98. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  99. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  100. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  101. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/status.py +0 -0
  102. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  103. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/thread_selector.py +0 -0
  104. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
  105. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
  106. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/tools.py +0 -0
  107. {soothe_cli-0.1.0 → soothe_cli-0.2.0}/src/soothe_cli/tui/widgets/welcome.py +0 -0
@@ -217,3 +217,4 @@ plot_*
217
217
 
218
218
  checkpoint.json
219
219
  manifest.json
220
+ _bmad
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.1.0
4
- Summary: Soothe CLI client - communicates with daemon via WebSocket
3
+ Version: 0.2.0
4
+ Summary: Soothe CLI client - communicates with daemon via WebSocket (updated for SDK v0.4.0)
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.2.0
24
+ Requires-Dist: soothe-sdk<1.0.0,>=0.4.0
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.1.0"
8
- description = "Soothe CLI client - communicates with daemon via WebSocket"
7
+ version = "0.2.0"
8
+ description = "Soothe CLI client - communicates with daemon via WebSocket (updated for SDK v0.4.0)"
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.2.0,<1.0.0", # WebSocket client, protocol, types
26
+ "soothe-sdk>=0.4.0,<1.0.0", # WebSocket client, protocol, types (v0.4.0 breaking change)
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
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
  from pathlib import Path
10
10
 
11
11
  import typer
12
- from soothe_sdk.protocol import preview_first
12
+ from soothe_sdk.client.protocol import preview_first
13
13
 
14
14
  app = typer.Typer(help="Autopilot mode — long-running autonomous agent control.")
15
15
 
@@ -55,7 +55,7 @@ def submit(
55
55
  """
56
56
  from datetime import UTC, datetime
57
57
 
58
- from soothe_sdk import SOOTHE_HOME
58
+ from soothe_sdk.client.config import SOOTHE_HOME
59
59
 
60
60
  inbox_dir = SOOTHE_HOME / "autopilot" / "inbox"
61
61
  inbox_dir.mkdir(parents=True, exist_ok=True)
@@ -72,7 +72,7 @@ def submit(
72
72
  @app.command("status")
73
73
  def status() -> None:
74
74
  """Show overall autopilot state."""
75
- from soothe_sdk import SOOTHE_HOME
75
+ from soothe_sdk.client.config import SOOTHE_HOME
76
76
 
77
77
  autopilot_dir = SOOTHE_HOME / "autopilot"
78
78
  state_file = autopilot_dir / "status.json"
@@ -106,7 +106,7 @@ def list_goals(
106
106
  status_filter: str = typer.Option("", "--status", "-s", help="Filter by status."),
107
107
  ) -> None:
108
108
  """List all goals."""
109
- from soothe_sdk import SOOTHE_HOME
109
+ from soothe_sdk.client.config import SOOTHE_HOME
110
110
 
111
111
  autopilot_dir = SOOTHE_HOME / "autopilot"
112
112
  goals = _discover_goals(autopilot_dir)
@@ -130,7 +130,7 @@ def show_goal(
130
130
  goal_id: str = typer.Argument(..., help="Goal ID to show details for."),
131
131
  ) -> None:
132
132
  """Show details for a specific goal."""
133
- from soothe_sdk import SOOTHE_HOME
133
+ from soothe_sdk.client.config import SOOTHE_HOME
134
134
 
135
135
  autopilot_dir = SOOTHE_HOME / "autopilot"
136
136
  goals = _discover_goals(autopilot_dir)
@@ -160,7 +160,7 @@ def cancel_goal(
160
160
  goal_id: str = typer.Argument(..., help="Goal ID to cancel."),
161
161
  ) -> None:
162
162
  """Cancel a goal (remove from inbox if pending)."""
163
- from soothe_sdk import SOOTHE_HOME
163
+ from soothe_sdk.client.config import SOOTHE_HOME
164
164
 
165
165
  inbox_dir = SOOTHE_HOME / "autopilot" / "inbox"
166
166
  if not inbox_dir.exists():
@@ -190,7 +190,7 @@ def approve_goal(
190
190
  """Approve a MUST-confirmation goal."""
191
191
  import json
192
192
 
193
- from soothe_sdk import SOOTHE_HOME
193
+ from soothe_sdk.client.config import SOOTHE_HOME
194
194
 
195
195
  confirmations_file = SOOTHE_HOME / "autopilot" / "pending_confirmations.json"
196
196
  if not confirmations_file.exists():
@@ -223,7 +223,7 @@ def reject_goal(
223
223
  """Reject a proposed goal."""
224
224
  import json
225
225
 
226
- from soothe_sdk import SOOTHE_HOME
226
+ from soothe_sdk.client.config import SOOTHE_HOME
227
227
 
228
228
  confirmations_file = SOOTHE_HOME / "autopilot" / "pending_confirmations.json"
229
229
  if not confirmations_file.exists():
@@ -248,7 +248,7 @@ def reject_goal(
248
248
  @app.command("wake")
249
249
  def wake() -> None:
250
250
  """Exit dreaming mode — resume active execution."""
251
- from soothe_sdk import SOOTHE_HOME
251
+ from soothe_sdk.client.config import SOOTHE_HOME
252
252
 
253
253
  inbox_dir = SOOTHE_HOME / "autopilot" / "inbox"
254
254
  inbox_dir.mkdir(parents=True, exist_ok=True)
@@ -261,7 +261,7 @@ def wake() -> None:
261
261
  @app.command("dream")
262
262
  def dream() -> None:
263
263
  """Force enter dreaming mode."""
264
- from soothe_sdk import SOOTHE_HOME
264
+ from soothe_sdk.client.config import SOOTHE_HOME
265
265
 
266
266
  inbox_dir = SOOTHE_HOME / "autopilot" / "inbox"
267
267
  inbox_dir.mkdir(parents=True, exist_ok=True)
@@ -276,7 +276,7 @@ def view_inbox(
276
276
  limit: int = typer.Option(10, "--limit", "-n", help="Max tasks to show."),
277
277
  ) -> None:
278
278
  """View pending inbox tasks."""
279
- from soothe_sdk import SOOTHE_HOME
279
+ from soothe_sdk.client.config import SOOTHE_HOME
280
280
 
281
281
  inbox_dir = SOOTHE_HOME / "autopilot" / "inbox"
282
282
  if not inbox_dir.exists():
@@ -113,7 +113,7 @@ def config_show(
113
113
  sys.exit(0)
114
114
  except Exception as e:
115
115
  logger.exception("Config command error")
116
- from soothe_sdk import format_cli_error
116
+ from soothe_sdk.utils import format_cli_error
117
117
 
118
118
  typer.echo(f"Error: {format_cli_error(e)}", err=True)
119
119
  sys.exit(1)
@@ -137,7 +137,7 @@ def config_init(
137
137
  from importlib.resources import as_file, files
138
138
  from pathlib import Path
139
139
 
140
- from soothe_sdk import SOOTHE_HOME
140
+ from soothe_sdk.client.config import SOOTHE_HOME
141
141
 
142
142
  home = Path(SOOTHE_HOME).expanduser()
143
143
  target = home / "config" / "config.yml"
@@ -209,7 +209,7 @@ def config_validate(
209
209
  """
210
210
  from pathlib import Path
211
211
 
212
- from soothe_sdk import SOOTHE_HOME
212
+ from soothe_sdk.client.config import SOOTHE_HOME
213
213
 
214
214
  try:
215
215
  cfg = load_config(config)
@@ -7,7 +7,8 @@ from pathlib import Path
7
7
  from typing import Literal
8
8
 
9
9
  import typer
10
- from soothe_sdk import SOOTHE_HOME, VERBOSITY_TO_LOG_LEVEL
10
+ from soothe_sdk.client.config import SOOTHE_HOME
11
+ from soothe_sdk.utils.logging import VERBOSITY_TO_LOG_LEVEL
11
12
 
12
13
  from soothe_cli.cli.execution import run_headless, run_tui
13
14
  from soothe_cli.shared import load_config, setup_logging
@@ -42,9 +43,9 @@ def run_impl(
42
43
  try:
43
44
  cfg = load_config(config)
44
45
  if verbosity is not None:
45
- logging_config = cfg.logging.model_copy(update={"verbosity": verbosity})
46
- cfg = cfg.model_copy(update={"logging": logging_config})
47
- log_level = VERBOSITY_TO_LOG_LEVEL.get(cfg.logging.verbosity, "INFO")
46
+ # CLIConfig is a dataclass, update verbosity directly
47
+ cfg.verbosity = verbosity
48
+ log_level = VERBOSITY_TO_LOG_LEVEL.get(cfg.verbosity, "INFO")
48
49
  log_file = Path(SOOTHE_HOME) / "logs" / "soothe-cli.log"
49
50
  setup_logging(log_level, log_file=log_file)
50
51
 
@@ -81,7 +82,7 @@ def run_impl(
81
82
  sys.exit(0)
82
83
  except Exception as e:
83
84
  logger.exception("CLI run error")
84
- from soothe_sdk import format_cli_error
85
+ from soothe_sdk.utils import format_cli_error
85
86
 
86
87
  typer.echo(f"Error: {format_cli_error(e)}", err=True)
87
88
  sys.exit(1)
@@ -80,7 +80,7 @@ def agent_list(
80
80
  sys.exit(0)
81
81
  except Exception as e:
82
82
  logger.exception("Agent list error")
83
- from soothe_sdk import format_cli_error
83
+ from soothe_sdk.utils import format_cli_error
84
84
 
85
85
  typer.echo(f"Error: {format_cli_error(e)}", err=True)
86
86
  sys.exit(1)
@@ -115,7 +115,7 @@ def agent_status(
115
115
  typer.echo(f"\nTotal: {enabled_count}/{total_count} agents enabled")
116
116
  except Exception as e:
117
117
  logger.exception("Agent status error")
118
- from soothe_sdk import format_cli_error
118
+ from soothe_sdk.utils import format_cli_error
119
119
 
120
120
  typer.echo(f"Error: {format_cli_error(e)}", err=True)
121
121
  sys.exit(1)
@@ -10,8 +10,9 @@ from pathlib import Path
10
10
  from typing import Annotated, Any
11
11
 
12
12
  import typer
13
- from soothe_sdk import SOOTHE_HOME, VERBOSITY_TO_LOG_LEVEL
14
13
  from soothe_sdk.client import WebSocketClient, is_daemon_live, websocket_url_from_config
14
+ from soothe_sdk.client.config import SOOTHE_HOME
15
+ from soothe_sdk.utils.logging import VERBOSITY_TO_LOG_LEVEL
15
16
 
16
17
  from soothe_cli.shared import load_config
17
18
 
@@ -178,13 +178,13 @@ async def run_headless_via_daemon(
178
178
 
179
179
  except (ConnectionError, OSError, TimeoutError) as e:
180
180
  logger.exception("Daemon connection failed")
181
- from soothe_sdk import format_cli_error
181
+ from soothe_sdk.utils import format_cli_error
182
182
 
183
183
  typer.echo(f"Error: {format_cli_error(e, context='daemon connection')}", err=True)
184
184
  return _DAEMON_FALLBACK_EXIT_CODE
185
185
  except Exception as e:
186
186
  logger.exception("Failed to run via daemon")
187
- from soothe_sdk import format_cli_error
187
+ from soothe_sdk.utils import format_cli_error
188
188
 
189
189
  typer.echo(f"Error: {format_cli_error(e)}", err=True)
190
190
  return 1
@@ -12,7 +12,7 @@ import time
12
12
  from dataclasses import dataclass, field
13
13
  from typing import TYPE_CHECKING, Any
14
14
 
15
- from soothe_sdk import get_tool_display_name
15
+ from soothe_sdk.utils import get_tool_display_name
16
16
  from soothe_sdk.verbosity import VerbosityTier
17
17
 
18
18
  from soothe_cli.cli.stream import DisplayLine, StreamDisplayPipeline
@@ -23,7 +23,7 @@ from soothe_cli.shared.presentation_engine import PresentationEngine
23
23
  from soothe_cli.shared.suppression_state import SuppressionState
24
24
 
25
25
  if TYPE_CHECKING:
26
- from soothe_sdk import Plan
26
+ from soothe_sdk.client.schemas import Plan
27
27
 
28
28
 
29
29
  @dataclass
@@ -66,18 +66,21 @@ class DisplayLine:
66
66
  return "".join(parts)
67
67
 
68
68
 
69
- def indent_for_level(_level: int) -> str:
69
+ def indent_for_level(level: int) -> str:
70
70
  """Get indentation string for a display level.
71
71
 
72
- Headless CLI uses a flat information stream (no tree connectors).
72
+ IG-182: Headless CLI uses flat layout for levels 1-2, but tree indentation
73
+ for level-3 child nodes (step results with "|__" connector).
73
74
 
74
75
  Args:
75
- _level: Display level (1, 2, or 3); retained for API compatibility.
76
+ level: Display level (1=goal, 2=step/tool, 3=result child).
76
77
 
77
78
  Returns:
78
- Indentation string (always empty for stream layout).
79
+ Indentation string: "" for level 1-2, " " for level 3 (tree child).
79
80
  """
80
- return ""
81
+ if level >= 3: # noqa: PLR2004
82
+ return " " # 2-space indent for tree children (IG-182)
83
+ return "" # Flat layout for goal/step headers
81
84
 
82
85
 
83
86
  __all__ = ["DisplayLine", "indent_for_level"]
@@ -280,7 +280,7 @@ def format_reasoning(
280
280
  DisplayLine for reasoning.
281
281
  """
282
282
  # Polish: Add "Reasoning:" prefix to make internal analysis visible
283
- content = f"💭 Reasoning: {reasoning}"
283
+ content = f"💭 {reasoning}"
284
284
 
285
285
  return DisplayLine(
286
286
  level=3, # Use level 3 for less prominence (subordinate to next_action)
@@ -329,38 +329,72 @@ def format_judgement(
329
329
 
330
330
 
331
331
  def format_step_done(
332
- description: str,
333
332
  duration_s: float,
334
333
  *,
335
334
  tool_call_count: int = 0,
335
+ success: bool = True,
336
+ error_msg: str | None = None,
336
337
  namespace: tuple[str, ...] = (),
337
338
  verbosity_tier: VerbosityTier = VerbosityTier.NORMAL,
338
- ) -> DisplayLine:
339
- """Format a step completion line with solid checkbox.
339
+ ) -> list[DisplayLine]:
340
+ """Format step completion as level-3 child node (IG-182).
341
+
342
+ IG-159/IG-182: Shows brief "Done"/"Failed" with tree connector as child of step header.
343
+ No description repeat - user already saw it in the step header above.
340
344
 
341
345
  Args:
342
- description: Step description (same as header).
343
346
  duration_s: Duration in seconds.
344
347
  tool_call_count: Number of tool calls made during step execution.
348
+ success: Whether step succeeded.
349
+ error_msg: Error message if failed.
345
350
  namespace: Event namespace.
346
351
  verbosity_tier: Current verbosity tier.
347
352
 
348
353
  Returns:
349
- DisplayLine for step done with solid circle icon.
354
+ List of DisplayLine objects for step result tree (1-2 lines).
350
355
  """
351
356
  duration_ms = int(duration_s * 1000)
352
- # Abbreviate description for cleaner display
353
- abbreviated = abbreviate_text(description, max_length=50)
354
357
  tool_info = f" [{tool_call_count} tools]" if tool_call_count > 0 else ""
355
- content = f"✅ {abbreviated}{tool_info}"
356
- return DisplayLine(
357
- level=2,
358
- content=content,
359
- icon="●", # Solid circle for completed step
360
- indent=indent_for_level(2),
361
- duration_ms=duration_ms,
362
- source_prefix=_derive_source_prefix(namespace, verbosity_tier),
363
- )
358
+
359
+ # Success case: single line
360
+ if success:
361
+ content = f"Done{tool_info}"
362
+ return [
363
+ DisplayLine(
364
+ level=3, # Child node of step header (level 2)
365
+ content=content,
366
+ icon="|__", # Tree connector (IG-159)
367
+ indent=indent_for_level(3),
368
+ duration_ms=duration_ms,
369
+ source_prefix=_derive_source_prefix(namespace, verbosity_tier),
370
+ )
371
+ ]
372
+
373
+ # Error case: result line + optional error detail
374
+ lines = [
375
+ DisplayLine(
376
+ level=3,
377
+ content=f"Failed{tool_info}",
378
+ icon="|__",
379
+ indent=indent_for_level(3),
380
+ duration_ms=duration_ms,
381
+ source_prefix=_derive_source_prefix(namespace, verbosity_tier),
382
+ )
383
+ ]
384
+
385
+ # Show error message on level-4 line if present
386
+ if error_msg:
387
+ lines.append(
388
+ DisplayLine(
389
+ level=4, # Error detail as child of failed result
390
+ content=f"Error: {error_msg}",
391
+ icon="|__",
392
+ indent=indent_for_level(4),
393
+ source_prefix=_derive_source_prefix(namespace, verbosity_tier),
394
+ )
395
+ )
396
+
397
+ return lines
364
398
 
365
399
 
366
400
  def format_goal_done(
@@ -6,7 +6,7 @@ import logging
6
6
  import time
7
7
  from typing import Any
8
8
 
9
- from soothe_sdk.protocol import preview_first
9
+ from soothe_sdk.client.protocol import preview_first
10
10
  from soothe_sdk.verbosity import VerbosityTier
11
11
 
12
12
  from soothe_cli.cli.stream.context import PipelineContext
@@ -114,7 +114,7 @@ class StreamDisplayPipeline:
114
114
  Returns:
115
115
  VerbosityTier for the event.
116
116
  """
117
- from soothe_sdk.verbosity import classify_event_to_tier
117
+ from soothe_sdk.ux import classify_event_to_tier, is_subagent_progress_event
118
118
 
119
119
  # Goal events - NORMAL
120
120
  if is_goal_start_event_type(event_type):
@@ -128,8 +128,16 @@ class StreamDisplayPipeline:
128
128
  if event_type in GOAL_COMPLETE_EVENTS:
129
129
  return VerbosityTier.QUIET
130
130
 
131
+ # Subagent capability events - DETAILED by default, NORMAL for important progress
132
+ # IG-192: Use SDK helper to identify important progress events (started/completed/judgement)
133
+ if event_type.startswith("soothe.capability."):
134
+ if is_subagent_progress_event(event_type):
135
+ return VerbosityTier.NORMAL
136
+ # Other capability events (internal steps) - DETAILED
137
+ return VerbosityTier.DETAILED
138
+
131
139
  # soothe.* events: defer to SDK domain-based classification (RFC-0020)
132
- # Step completion, tool events, subagent events all use domain defaults
140
+ # Step completion, tool events use domain defaults
133
141
  if event_type.startswith("soothe."):
134
142
  return classify_event_to_tier(event_type)
135
143
 
@@ -150,12 +158,36 @@ class StreamDisplayPipeline:
150
158
  Returns:
151
159
  List of DisplayLine objects.
152
160
  """
153
- if is_goal_start_event_type(event_type):
154
- return self._on_goal_started(event)
155
-
156
- if is_step_start_event_type(event_type):
157
- return self._on_step_started(event)
158
-
161
+ # Capability events (soothe.capability.<subagent>.<action>)
162
+ # IG-192: Handle new unified naming scheme for subagent events
163
+ if event_type.startswith("soothe.capability."):
164
+ parts = event_type.split(".")
165
+ if len(parts) >= 4: # noqa: PLR2004
166
+ # soothe.capability.<subagent>.<action>
167
+ subagent = parts[2]
168
+ action = parts[3]
169
+
170
+ # Started/dispatched events
171
+ if action in ("started", "dispatching"):
172
+ return self._on_subagent_dispatched(event, subagent)
173
+
174
+ # Judgement events (research)
175
+ if "judgement" in action:
176
+ return self._on_subagent_judgement(event)
177
+
178
+ # Step events (browser automation steps)
179
+ if "step" in action and "running" in action:
180
+ return self._on_capability_step(event, subagent)
181
+
182
+ # Completed events
183
+ if action == "completed":
184
+ return self._on_subagent_completed(event, subagent)
185
+
186
+ # Text/tool events (claude) - DETAILED level
187
+ if action in ("text", "tool") and "running" in event_type:
188
+ return self._on_capability_activity(event, subagent, action)
189
+
190
+ # Legacy subagent events (.subagent.* format)
159
191
  if ".subagent." in event_type and ".dispatched" in event_type:
160
192
  return self._on_subagent_dispatched(event)
161
193
 
@@ -168,6 +200,13 @@ class StreamDisplayPipeline:
168
200
  if ".subagent." in event_type and ".completed" in event_type:
169
201
  return self._on_subagent_completed(event)
170
202
 
203
+ # Goal/step events
204
+ if is_goal_start_event_type(event_type):
205
+ return self._on_goal_started(event)
206
+
207
+ if is_step_start_event_type(event_type):
208
+ return self._on_step_started(event)
209
+
171
210
  if is_step_complete_event_type(event_type):
172
211
  return self._on_step_completed(event)
173
212
 
@@ -244,24 +283,27 @@ class StreamDisplayPipeline:
244
283
  )
245
284
  ]
246
285
 
247
- def _on_subagent_dispatched(self, event: dict[str, Any]) -> list[DisplayLine]:
286
+ def _on_subagent_dispatched(
287
+ self, event: dict[str, Any], subagent_name: str = ""
288
+ ) -> list[DisplayLine]:
248
289
  """Handle subagent dispatched event.
249
290
 
250
291
  Args:
251
292
  event: Event dictionary.
293
+ subagent_name: Subagent name (extracted from event type).
252
294
 
253
295
  Returns:
254
296
  Display lines (none for dispatch, just tracking).
255
297
  """
256
- # Extract name from event type: soothe.subagent.<name>.dispatched
298
+ # Extract name from event type: soothe.capability.<name>.started
257
299
  event_type = event.get("type", "")
258
300
  parts = event_type.split(".")
259
301
  name = ""
260
- # Pattern: soothe.subagent.<name>.dispatched -> parts[0]=soothe, parts[1]=subagent, parts[2]=name
261
- # Need at least 3 parts for valid subagent event type
262
- if len(parts) >= 3 and parts[1] == "subagent": # noqa: PLR2004
302
+ # Pattern: soothe.capability.<name>.started -> parts[0]=soothe, parts[1]=capability, parts[2]=name
303
+ # Need at least 3 parts for valid capability event type
304
+ if len(parts) >= 3 and parts[1] == "capability": # noqa: PLR2004
263
305
  name = parts[2]
264
- name = name or event.get("name", event.get("subagent_name", ""))
306
+ name = name or subagent_name or event.get("name", event.get("subagent_name", ""))
265
307
  self._context.subagent_name = name
266
308
  self._context.subagent_milestones.clear()
267
309
 
@@ -272,6 +314,7 @@ class StreamDisplayPipeline:
272
314
  format_tool_call(
273
315
  f"{name}_subagent",
274
316
  args_summary,
317
+ running=True,
275
318
  namespace=self._current_namespace,
276
319
  verbosity_tier=self._verbosity_tier,
277
320
  )
@@ -334,11 +377,14 @@ class StreamDisplayPipeline:
334
377
  )
335
378
  ]
336
379
 
337
- def _on_subagent_completed(self, event: dict[str, Any]) -> list[DisplayLine]:
380
+ def _on_subagent_completed(
381
+ self, event: dict[str, Any], subagent_name: str = ""
382
+ ) -> list[DisplayLine]:
338
383
  """Handle subagent completed event.
339
384
 
340
385
  Args:
341
386
  event: Event dictionary.
387
+ subagent_name: Subagent name (extracted from event type).
342
388
 
343
389
  Returns:
344
390
  Display lines for completion.
@@ -371,6 +417,57 @@ class StreamDisplayPipeline:
371
417
  )
372
418
  ]
373
419
 
420
+ def _on_capability_step(self, event: dict[str, Any], subagent_name: str) -> list[DisplayLine]:
421
+ """Handle capability step event (e.g., browser automation steps).
422
+
423
+ Args:
424
+ event: Event dictionary.
425
+ subagent_name: Subagent name (browser, etc.).
426
+
427
+ Returns:
428
+ Display lines for step milestone.
429
+ """
430
+ # Extract step info from different event schemas
431
+ step = event.get("step", "")
432
+ url = event.get("url", "")
433
+ action = event.get("action", "")
434
+ title = event.get("title", "")
435
+
436
+ # Build brief description
437
+ if url:
438
+ brief = f"Step {step}: {action} on {url}"
439
+ elif action:
440
+ brief = f"Step {step}: {action}"
441
+ elif title:
442
+ brief = f"Step {step}: {title}"
443
+ else:
444
+ brief = f"Step {step}"
445
+
446
+ return [
447
+ format_subagent_milestone(
448
+ preview_first(brief, 60),
449
+ namespace=self._current_namespace,
450
+ verbosity_tier=self._verbosity_tier,
451
+ )
452
+ ]
453
+
454
+ def _on_capability_activity(
455
+ self, event: dict[str, Any], subagent_name: str, action_type: str
456
+ ) -> list[DisplayLine]:
457
+ """Handle capability activity event (e.g., claude text/tool).
458
+
459
+ Args:
460
+ event: Event dictionary.
461
+ subagent_name: Subagent name (claude, etc.).
462
+ action_type: Activity type (text, tool).
463
+
464
+ Returns:
465
+ Display lines for activity milestone (empty for DETAILED events).
466
+ """
467
+ # Claude text/tool events are DETAILED level - not shown at normal verbosity
468
+ # Just return empty list, classification already filters them
469
+ return []
470
+
374
471
  def _on_step_completed(self, event: dict[str, Any]) -> list[DisplayLine]:
375
472
  """Handle step completed event.
376
473
 
@@ -390,13 +487,11 @@ class StreamDisplayPipeline:
390
487
  if duration_s == 0 and self._context.step_start_time:
391
488
  duration_s = time.time() - self._context.step_start_time
392
489
 
393
- # Resolve description robustly for parallel/async step completions
394
- description = (
395
- self._context.step_descriptions.get(step_id, "")
396
- or self._context.current_step_description
397
- or event.get("description", "")
398
- or "Completed action"
399
- )
490
+ # Get success/error status (IG-182)
491
+ success = event.get("success", True)
492
+ error_msg = None
493
+ if not success:
494
+ error_msg = event.get("error", event.get("error_message", ""))
400
495
 
401
496
  # Get tool call count from event
402
497
  tool_call_count = event.get("tool_call_count", 0)
@@ -411,15 +506,15 @@ class StreamDisplayPipeline:
411
506
  self._context.current_step_description = None
412
507
  self._context.step_start_time = None
413
508
 
414
- return [
415
- format_step_done(
416
- description,
417
- duration_s,
418
- tool_call_count=tool_call_count,
419
- namespace=self._current_namespace,
420
- verbosity_tier=self._verbosity_tier,
421
- )
422
- ]
509
+ # IG-182: Return list directly (formatter returns list now)
510
+ return format_step_done(
511
+ duration_s,
512
+ tool_call_count=tool_call_count,
513
+ success=success,
514
+ error_msg=error_msg,
515
+ namespace=self._current_namespace,
516
+ verbosity_tier=self._verbosity_tier,
517
+ )
423
518
 
424
519
  def _on_goal_completed(self, event: dict[str, Any]) -> list[DisplayLine]:
425
520
  """Handle goal completed event.
@@ -73,7 +73,7 @@ class CLIConfig:
73
73
  Full config available via daemon RPC.
74
74
 
75
75
  Args:
76
- config_path: Path to config file. Defaults to ~/.soothe/config.yml.
76
+ config_path: Path to config file. Defaults to ~/.soothe/config/cli_config.yml.
77
77
 
78
78
  Returns:
79
79
  CLIConfig instance with minimal settings.
@@ -81,7 +81,7 @@ class CLIConfig:
81
81
  import yaml
82
82
 
83
83
  if config_path is None:
84
- config_path = Path.home() / ".soothe" / "config.yml"
84
+ config_path = Path.home() / ".soothe" / "config" / "cli_config.yml"
85
85
 
86
86
  if not config_path.exists():
87
87
  return cls() # Use defaults