soothe-cli 0.5.1__tar.gz → 0.5.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/PKG-INFO +1 -1
  2. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/commands/run_cmd.py +25 -7
  3. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/main.py +19 -4
  4. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/config/cli_config.py +1 -1
  5. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/_env_vars.py +4 -1
  6. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_app.py +16 -96
  7. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_commands.py +27 -30
  8. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_execution.py +13 -50
  9. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_history.py +144 -89
  10. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_messages_mixin.py +4 -4
  11. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_model.py +27 -79
  12. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_module_init.py +6 -46
  13. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_startup.py +33 -240
  14. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/config.py +10 -0
  15. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/daemon_session.py +9 -4
  16. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_turn.py +60 -68
  17. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_turn_helpers.py +56 -47
  18. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/update_check.py +46 -15
  19. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/message_store.py +14 -14
  20. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/messages.py +135 -118
  21. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/welcome.py +30 -39
  22. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/.gitignore +0 -0
  23. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/README.md +0 -0
  24. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/pyproject.toml +0 -0
  25. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/__init__.py +0 -0
  26. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/__init__.py +0 -0
  27. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/commands/__init__.py +0 -0
  28. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
  29. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
  30. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/__init__.py +0 -0
  31. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/daemon.py +0 -0
  32. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/headless.py +0 -0
  33. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
  34. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/execution/launcher.py +0 -0
  35. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/__init__.py +0 -0
  36. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/context.py +0 -0
  37. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/display_line.py +0 -0
  38. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/formatter.py +0 -0
  39. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/pipeline.py +0 -0
  40. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/cli/stream/task_scope.py +0 -0
  41. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/config/__init__.py +0 -0
  42. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/plan/__init__.py +0 -0
  43. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/plan/rich_tree.py +0 -0
  44. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/__init__.py +0 -0
  45. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/commands/__init__.py +0 -0
  46. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/commands/command_router.py +0 -0
  47. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/commands/slash_commands.py +0 -0
  48. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/commands/subagent_routing.py +0 -0
  49. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/config_loader.py +0 -0
  50. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/__init__.py +0 -0
  51. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/event_processor.py +0 -0
  52. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/presentation_engine.py +0 -0
  53. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/processor_state.py +0 -0
  54. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/core/renderer_protocol.py +0 -0
  55. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/duration_format.py +0 -0
  56. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/__init__.py +0 -0
  57. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/display_policy.py +0 -0
  58. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/essential_events.py +0 -0
  59. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/explore_task_display.py +0 -0
  60. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/stream_accumulator.py +0 -0
  61. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/events/tui_trace_log.py +0 -0
  62. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/rendering/__init__.py +0 -0
  63. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/rendering/async_renderer_protocol.py +0 -0
  64. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/rendering/renderer_base.py +0 -0
  65. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/__init__.py +0 -0
  66. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/_utils.py +0 -0
  67. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/message_processing.py +0 -0
  68. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/rendering.py +0 -0
  69. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_call_resolution.py +0 -0
  70. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_card_payload.py +0 -0
  71. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_card_visibility.py +0 -0
  72. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/__init__.py +0 -0
  73. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/base.py +0 -0
  74. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/execution.py +0 -0
  75. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/fallback.py +0 -0
  76. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py +0 -0
  77. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/goal_formatter.py +0 -0
  78. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/media.py +0 -0
  79. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/structured.py +0 -0
  80. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/subagent.py +0 -0
  81. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_formatters/web.py +0 -0
  82. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_message_format.py +0 -0
  83. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/shared/tools/tool_output_formatter.py +0 -0
  84. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/__init__.py +0 -0
  85. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/_ask_user_types.py +0 -0
  86. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/_cli_context.py +0 -0
  87. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/_session_stats.py +0 -0
  88. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/_version.py +0 -0
  89. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/__init__.py +0 -0
  90. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/_ui.py +0 -0
  91. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/app/app.tcss +0 -0
  92. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/command_registry.py +0 -0
  93. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/file_ops.py +0 -0
  94. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/formatting.py +0 -0
  95. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/hooks.py +0 -0
  96. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/input.py +0 -0
  97. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/media_utils.py +0 -0
  98. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/message_display_filter.py +0 -0
  99. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/model_config.py +0 -0
  100. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/output.py +0 -0
  101. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/preview_limits.py +0 -0
  102. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/project_utils.py +0 -0
  103. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/sessions.py +0 -0
  104. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/skills/__init__.py +0 -0
  105. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/skills/invocation.py +0 -0
  106. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/skills/load.py +0 -0
  107. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/__init__.py +0 -0
  108. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_adapter.py +0 -0
  109. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_stream_formatting.py +0 -0
  110. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/textual_adapter/_stream_messages.py +0 -0
  111. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/theme.py +0 -0
  112. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/tool_display.py +0 -0
  113. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/unicode_security.py +0 -0
  114. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  115. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/_links.py +0 -0
  116. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/approval.py +0 -0
  117. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
  118. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  119. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
  120. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
  121. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  122. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  123. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/diff.py +0 -0
  124. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/editor.py +0 -0
  125. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/history.py +0 -0
  126. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/loading.py +0 -0
  127. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  128. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  129. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  130. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  131. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/status.py +0 -0
  132. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  133. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
  134. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
  135. {soothe_cli-0.5.1 → soothe_cli-0.5.3}/src/soothe_cli/tui/widgets/tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.5.1
3
+ Version: 0.5.3
4
4
  Summary: Soothe CLI client - communicates with daemon via WebSocket
5
5
  Project-URL: Homepage, https://github.com/OpenSoothe/soothe
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
@@ -24,25 +24,29 @@ def run_impl(
24
24
  streaming_enabled: bool | None = None,
25
25
  streaming_mode: str | None = None,
26
26
  *,
27
+ tui_with_prompt: bool = False,
27
28
  config_path: str | None = None,
28
29
  ) -> None:
29
30
  """Core implementation for running Soothe agent.
30
31
 
31
32
  Args:
32
- prompt: Optional prompt for headless mode
33
+ prompt: Optional user message; non-empty prompt defaults to a headless
34
+ one-shot run unless ``tui_with_prompt`` is set or a loop is being
35
+ resumed (``resume_loop_id``).
33
36
  resume_loop_id: Existing loop id to attach to (optional)
34
- no_tui: Force headless mode
37
+ no_tui: Require headless mode (must include a non-empty prompt)
35
38
  autonomous: Enable autonomous iteration mode
36
39
  max_iterations: Max iterations for autonomous mode
37
40
  streaming_enabled: Override daemon streaming enabled setting (RFC-614)
38
41
  streaming_mode: Override daemon streaming mode ('streaming' or 'batch')
42
+ tui_with_prompt: When True with a prompt, open the TUI instead of headless.
39
43
  """
40
44
  startup_start = time.perf_counter()
41
45
 
42
46
  try:
43
47
  cfg = load_config(config_path)
44
48
  log_level = resolve_cli_log_level(logging_level=cfg.logging_level)
45
- log_file = Path(SOOTHE_HOME) / "logs" / "soothe-cli.log"
49
+ log_file = Path(SOOTHE_HOME) / "logs" / "cli.log"
46
50
  setup_logging(log_level, log_file=log_file)
47
51
 
48
52
  # PostgreSQL availability check (requires daemon-side config)
@@ -62,17 +66,31 @@ def run_impl(
62
66
 
63
67
  run_start = time.perf_counter()
64
68
 
65
- if no_tui:
66
- # Headless mode (force no TUI)
69
+ has_prompt = bool(prompt and str(prompt).strip())
70
+ attaching_loop = bool(resume_loop_id and str(resume_loop_id).strip())
71
+
72
+ if tui_with_prompt and has_prompt:
73
+ use_headless = False
74
+ elif no_tui and not has_prompt:
75
+ typer.echo(
76
+ "Error: --no-tui requires a non-empty --prompt (-p).",
77
+ err=True,
78
+ )
79
+ sys.exit(1)
80
+ elif no_tui:
81
+ use_headless = True
82
+ else:
83
+ use_headless = has_prompt and not attaching_loop
84
+
85
+ if use_headless:
67
86
  run_headless(
68
87
  cfg,
69
- prompt or "",
88
+ str(prompt).strip(),
70
89
  resume_loop_id=resume_loop_id,
71
90
  autonomous=autonomous,
72
91
  max_iterations=max_iterations,
73
92
  )
74
93
  else:
75
- # TUI mode (with optional initial prompt)
76
94
  run_tui(cfg, resume_loop_id=resume_loop_id, initial_prompt=prompt)
77
95
 
78
96
  run_elapsed_s = time.perf_counter() - run_start
@@ -55,12 +55,24 @@ def main(
55
55
  prompt: Annotated[
56
56
  str | None,
57
57
  typer.Option(
58
- "--prompt", "-p", help="Prompt to send as user message (headless single-shot mode)."
58
+ "--prompt",
59
+ "-p",
60
+ help="User message; runs a one-shot headless query by default (use --tui for TUI).",
59
61
  ),
60
62
  ] = None,
61
63
  no_tui: Annotated[ # noqa: FBT002
62
64
  bool,
63
- typer.Option("--no-tui", help="Disable TUI; run single prompt and exit."),
65
+ typer.Option(
66
+ "--no-tui",
67
+ help="Headless mode (requires --prompt). Same as default when -p is set.",
68
+ ),
69
+ ] = False,
70
+ tui_with_prompt: Annotated[ # noqa: FBT002
71
+ bool,
72
+ typer.Option(
73
+ "--tui",
74
+ help="With --prompt/-p, open the interactive TUI and auto-submit the prompt.",
75
+ ),
64
76
  ] = False,
65
77
  streaming: Annotated[
66
78
  bool | None,
@@ -81,13 +93,15 @@ def main(
81
93
  ) -> None:
82
94
  """Soothe CLI - Intelligent AI assistant client.
83
95
 
84
- Run without arguments for interactive TUI mode, or provide a prompt via --prompt/-p option.
96
+ Run without arguments for interactive TUI mode, or pass --prompt for a one-shot
97
+ headless query (stdout, then exit).
85
98
 
86
99
  Note: This is the CLI client. Use 'soothed' command to manage the daemon server.
87
100
 
88
101
  Examples:
89
102
  soothe # Interactive TUI mode
90
- soothe -p "Research AI advances" # Headless single-prompt mode
103
+ soothe -p "Research AI advances" # One-shot headless (non-TUI) query
104
+ soothe -p "Hello" --tui # TUI with an auto-submitted prompt
91
105
  soothe loop list # List AgentLoop instances
92
106
  """
93
107
  # Handle -h/--help flag
@@ -112,6 +126,7 @@ def main(
112
126
  max_iterations=None,
113
127
  streaming_enabled=streaming,
114
128
  streaming_mode=streaming_mode,
129
+ tui_with_prompt=tui_with_prompt,
115
130
  )
116
131
 
117
132
 
@@ -24,7 +24,7 @@ class CLIConfig:
24
24
  daemon_host: str = "127.0.0.1"
25
25
  daemon_port: int = 8765
26
26
 
27
- # logging_level: DEBUG/INFO/… for ~/.soothe/logs/soothe-cli.log; None = default INFO.
27
+ # logging_level: DEBUG/INFO/… for ~/.soothe/logs/cli.log; None = default INFO.
28
28
  logging_level: str | None = None
29
29
 
30
30
  # Output streaming overrides (RFC-614)
@@ -29,7 +29,7 @@ from __future__ import annotations
29
29
  # ---------------------------------------------------------------------------
30
30
 
31
31
  AUTO_UPDATE = "SOOTHE_CLI_AUTO_UPDATE"
32
- """Enable automatic CLI updates ('1', 'true', or 'yes')."""
32
+ """Override automatic CLI updates: '1'/'true'/'yes' on, '0'/'false'/'no' off. On by default when unset."""
33
33
 
34
34
  DEBUG = "SOOTHE_CLI_DEBUG"
35
35
  """Enable verbose debug logging to a file."""
@@ -43,6 +43,9 @@ EXTRA_SKILLS_DIRS = "SOOTHE_CLI_EXTRA_SKILLS_DIRS"
43
43
  NO_UPDATE_CHECK = "SOOTHE_CLI_NO_UPDATE_CHECK"
44
44
  """Disable automatic update checking when set."""
45
45
 
46
+ UPDATE_CHECK = "SOOTHE_CLI_UPDATE_CHECK"
47
+ """Force-enable startup PyPI update checks ('1', 'true', or 'yes'). On by default."""
48
+
46
49
  SERVER_ENV_PREFIX = "SOOTHE_CLI_SERVER_"
47
50
  """Environment variable prefix used to pass CLI config to the server subprocess."""
48
51
 
@@ -10,7 +10,6 @@ from pathlib import Path
10
10
  from typing import TYPE_CHECKING, Any, ClassVar
11
11
 
12
12
  if TYPE_CHECKING:
13
- from langgraph.pregel import Pregel
14
13
  from textual.app import ComposeResult
15
14
  from textual.worker import Worker
16
15
 
@@ -133,22 +132,8 @@ class SootheApp(
133
132
  """App-level keybindings for interrupt, quit, toggles, and approval menu
134
133
  navigation."""
135
134
 
136
- class ServerReady(Message):
137
- """Posted by the background server-startup worker on success."""
138
-
139
- def __init__( # noqa: D107
140
- self,
141
- agent: Any, # noqa: ANN401
142
- server_proc: Any, # noqa: ANN401
143
- mcp_server_info: list[Any] | None,
144
- ) -> None:
145
- super().__init__()
146
- self.agent = agent
147
- self.server_proc = server_proc
148
- self.mcp_server_info = mcp_server_info
149
-
150
135
  class ServerStartFailed(Message):
151
- """Posted by the background server-startup worker on failure."""
136
+ """Posted when daemon bootstrap or background connection fails."""
152
137
 
153
138
  def __init__(self, error: Exception) -> None: # noqa: D107
154
139
  super().__init__()
@@ -165,63 +150,30 @@ class SootheApp(
165
150
  def __init__(
166
151
  self,
167
152
  *,
168
- agent: Pregel | None = None,
153
+ daemon_config: Any,
169
154
  assistant_id: str | None = None,
170
155
  auto_approve: bool = False,
171
156
  cwd: str | Path | None = None,
172
157
  resume_loop_id: str | None = None,
173
- resume_loop_intent: str | None = None,
174
158
  initial_prompt: str | None = None,
175
159
  initial_skill: str | None = None,
176
160
  mcp_server_info: list[dict[str, Any]] | None = None,
177
161
  profile_override: dict[str, Any] | None = None,
178
- server_proc: Any | None = None,
179
- server_kwargs: dict[str, Any] | None = None,
180
- mcp_preload_kwargs: dict[str, Any] | None = None,
181
- model_kwargs: dict[str, Any] | None = None,
182
- daemon_config: Any | None = None,
183
162
  **kwargs: Any,
184
163
  ) -> None:
185
- """Initialize the Deep Agents application.
164
+ """Initialize the Textual application (daemon-backed execution only).
186
165
 
187
166
  Args:
188
- agent: Pre-configured LangGraph agent, or `None` when server
189
- startup is deferred via `server_kwargs`.
190
- assistant_id: Agent identifier for memory storage
191
- auto_approve: Whether to start with auto-approve enabled
192
- cwd: Current working directory to display
193
- resume_loop_id: Initial AgentLoop id (daemon-backed).
194
-
195
- `None` when `resume_loop_intent` is provided (resolved asynchronously).
196
- resume_loop_intent: Raw resume intent from `-r` flag.
197
-
198
- `'__MOST_RECENT__'` for bare `-r`, a loop id for
199
- `-r <id>`, or `None` for new sessions.
200
-
201
- Resolved via `_resolve_resume_loop_intent`
202
- during `_start_server_background`.
203
-
204
- Requires `server_kwargs` to be set; ignored otherwise.
205
- initial_prompt: Optional prompt to auto-submit when session starts
167
+ daemon_config: Loaded Soothe configuration (WebSocket URL, etc.).
168
+ assistant_id: Agent identifier for memory storage.
169
+ auto_approve: Whether to start with auto-approve enabled.
170
+ cwd: Current working directory to display.
171
+ resume_loop_id: Initial AgentLoop id when attaching to an existing loop.
172
+ initial_prompt: Optional prompt to auto-submit when session starts.
206
173
  initial_skill: Optional skill name to invoke when session starts.
207
174
  mcp_server_info: MCP server metadata for the `/mcp` viewer.
208
- profile_override: Extra profile fields from `--profile-override`,
209
- retained so later profile-aware behavior stays consistent with
210
- the CLI override, including model selection details and
211
- on-demand `create_model()` calls.
212
- server_proc: LangGraph server process for the interactive session.
213
- server_kwargs: When provided, server startup is deferred.
214
-
215
- The app shows a "Connecting..." state and starts the server in
216
- the background using these kwargs
217
- for `start_server_and_get_agent`.
218
- mcp_preload_kwargs: Kwargs for `_preload_session_mcp_server_info`,
219
- run concurrently with server startup when `server_kwargs` is set.
220
- model_kwargs: Kwargs for deferred `create_model()`.
221
-
222
- When provided, model creation runs in a background worker after
223
- first paint instead of blocking startup.
224
- **kwargs: Additional arguments passed to parent
175
+ profile_override: Extra profile fields from ``--profile-override``.
176
+ **kwargs: Additional arguments passed to the Textual ``App``.
225
177
  """
226
178
  super().__init__(**kwargs)
227
179
 
@@ -230,8 +182,6 @@ class SootheApp(
230
182
  # Apply saved theme preference (or default)
231
183
  self.theme = _load_theme_preference()
232
184
 
233
- self._agent = agent
234
-
235
185
  self._assistant_id = assistant_id
236
186
 
237
187
  self._auto_approve = auto_approve
@@ -242,8 +192,6 @@ class SootheApp(
242
192
  # Named `_lc_loop_id` to avoid colliding with Textual's App._thread_id.
243
193
  self._lc_loop_id = resume_loop_id
244
194
 
245
- self._resume_loop_intent = resume_loop_intent
246
-
247
195
  self._initial_prompt = initial_prompt
248
196
 
249
197
  self._initial_skill = (
@@ -254,14 +202,6 @@ class SootheApp(
254
202
 
255
203
  self._profile_override = profile_override
256
204
 
257
- self._server_proc = server_proc
258
-
259
- self._server_kwargs = server_kwargs
260
-
261
- self._mcp_preload_kwargs = mcp_preload_kwargs
262
-
263
- self._model_kwargs = model_kwargs
264
-
265
205
  self._daemon_config = daemon_config
266
206
 
267
207
  self._daemon_session: Any | None = None
@@ -269,14 +209,9 @@ class SootheApp(
269
209
  self._daemon_skills_wire: list[dict[str, Any]] = []
270
210
  """Cached ``skills_list_response`` rows when the TUI uses ``TuiDaemonSession``."""
271
211
 
272
- self._connecting = server_kwargs is not None or daemon_config is not None
273
- # Extract sandbox type from server kwargs for trace metadata.
274
- # ServerConfig.__post_init__ normalizes "none" → None, but server_kwargs carries
275
- # the raw argparse value, so guard against both.
212
+ self._connecting = True
276
213
 
277
- raw = (server_kwargs or {}).get("sandbox_type")
278
-
279
- self._sandbox_type: str | None = raw if raw and raw != "none" else None
214
+ self._sandbox_type: str | None = None
280
215
 
281
216
  self._model_override: str | None = None
282
217
 
@@ -304,11 +239,7 @@ class SootheApp(
304
239
  self._agent_running = False
305
240
 
306
241
  self._server_startup_error: str | None = None
307
- """Set when the background server fails to start; persists for the
308
- session lifetime (server failure is terminal).
309
-
310
- Shown in place of the generic 'Agent not configured' message.
311
- """
242
+ """Set when daemon bootstrap fails; persists for the session lifetime."""
312
243
 
313
244
  self._shell_process: asyncio.subprocess.Process | None = None
314
245
  """Shell command process tracking for interruption (! commands)."""
@@ -391,18 +322,9 @@ class SootheApp(
391
322
 
392
323
  self._image_tracker = MediaTracker()
393
324
 
394
- def _remote_agent(self) -> Any: # noqa: ANN401
395
- """Return the agent if it appears to be a remote agent, or `None`.
396
-
397
- Returns `None` when no agent is configured or the agent is a local graph.
398
- """
399
- # RemoteAgent module doesn't exist in this package; always return None.
400
- # When the SDK provides a RemoteAgent class, this can be re-implemented.
401
- return None
402
-
403
325
  def _runtime_backend_ready(self) -> bool:
404
- """Return whether the app has a usable execution backend."""
405
- return self._daemon_session is not None or self._agent is not None
326
+ """Return whether the app has a connected daemon session."""
327
+ return self._daemon_session is not None
406
328
 
407
329
  def get_theme_variable_defaults(self) -> dict[str, str]:
408
330
  """Return custom CSS variable defaults for the current theme.
@@ -434,8 +356,6 @@ class SootheApp(
434
356
  mcp_tool_count=self._mcp_tool_count,
435
357
  workspace_path=self._cwd,
436
358
  connecting=self._connecting,
437
- resuming=self._resume_loop_intent is not None,
438
- local_server=self._server_kwargs is not None,
439
359
  id="welcome-banner",
440
360
  )
441
361
  yield Container(id="messages")
@@ -9,7 +9,6 @@ from contextlib import suppress
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
11
  if TYPE_CHECKING:
12
- from langchain_core.runnables import RunnableConfig
13
12
  from textual.content import Content
14
13
 
15
14
  from textual.app import ScreenStackError
@@ -191,24 +190,25 @@ class _CommandsMixin:
191
190
  self._update_tokens(0)
192
191
  # Clear status message (e.g., "Interrupted" from previous session)
193
192
  self._update_status("")
194
- # New AgentLoop (daemon) or new local loop id
195
193
  if self._session_state:
196
- if self._daemon_session is not None:
194
+ if self._daemon_session is None:
195
+ await self._mount_message(
196
+ AppMessage("Not connected to the daemon; cannot start a new loop.")
197
+ )
198
+ else:
197
199
  status_event = await self._daemon_session.new_loop()
198
200
  new_loop_id = (
199
201
  str(status_event.get("loop_id", "")) or self._session_state.reset_loop()
200
202
  )
201
203
  self._session_state.loop_id = new_loop_id
202
204
  self._lc_loop_id = new_loop_id
203
- else:
204
- new_loop_id = self._session_state.reset_loop()
205
- try:
206
- banner = self.query_one("#welcome-banner", WelcomeBanner)
207
- banner.update_loop_id(new_loop_id)
208
- except NoMatches:
209
- pass
210
- self._clear_loop_model_override()
211
- await self._mount_message(AppMessage(f"Started new loop: {new_loop_id}"))
205
+ try:
206
+ banner = self.query_one("#welcome-banner", WelcomeBanner)
207
+ banner.update_loop_id(new_loop_id)
208
+ except NoMatches:
209
+ pass
210
+ self._clear_loop_model_override()
211
+ await self._mount_message(AppMessage(f"Started new loop: {new_loop_id}"))
212
212
  elif cmd == "/editor":
213
213
  await self.action_open_editor()
214
214
  elif cmd == "/loops":
@@ -367,13 +367,6 @@ class _CommandsMixin:
367
367
  report += "\nTheme registry reload failed. Check config.yml for errors."
368
368
  await self._mount_message(AppMessage(report))
369
369
 
370
- # Re-discover skills so autocomplete reflects any new/removed skills
371
- if self._daemon_config is None:
372
- self.run_worker(
373
- self._discover_skills(),
374
- exclusive=True,
375
- group="startup-skill-discovery",
376
- )
377
370
  if self._daemon_session is not None:
378
371
  self.run_worker(
379
372
  self._refresh_daemon_skills_catalog(),
@@ -424,22 +417,26 @@ class _CommandsMixin:
424
417
  Returns:
425
418
  Token count as an integer, or `None` if state is unavailable.
426
419
  """
427
- if not self._agent:
420
+ if not self._lc_loop_id:
428
421
  return None
429
422
  try:
430
- from langchain_core.messages.utils import (
431
- count_tokens_approximately,
432
- )
423
+ from langchain_core.messages import messages_from_dict
424
+ from langchain_core.messages.utils import count_tokens_approximately
433
425
 
434
- config: RunnableConfig = {
435
- "configurable": {"thread_id": self._lc_loop_id},
436
- }
437
- state = await self._agent.aget_state(config)
438
- if not state or not state.values:
426
+ if self._daemon_session is None:
439
427
  return None
440
- messages = state.values.get("messages", [])
441
- if not messages:
428
+ snap = await self._daemon_session.aget_loop_state(self._lc_loop_id)
429
+ vals = getattr(snap, "values", None)
430
+ if not isinstance(vals, dict):
442
431
  return None
432
+ raw = vals.get("messages")
433
+ if not isinstance(raw, list) or not raw:
434
+ return None
435
+ if isinstance(raw[0], dict):
436
+ messages = messages_from_dict(raw)
437
+ else:
438
+ messages = raw
439
+
443
440
  return count_tokens_approximately(messages)
444
441
  except Exception: # best-effort for /tokens display
445
442
  logger.debug("Failed to retrieve conversation token count", exc_info=True)
@@ -10,10 +10,7 @@ import sys
10
10
  import time
11
11
  import webbrowser
12
12
  from contextlib import suppress
13
- from typing import TYPE_CHECKING, Any, Literal
14
-
15
- if TYPE_CHECKING:
16
- from langchain_core.runnables import RunnableConfig
13
+ from typing import Any, Literal
17
14
 
18
15
  from textual.app import ScreenStackError
19
16
  from textual.containers import VerticalScroll
@@ -532,24 +529,25 @@ class _ExecutionMixin:
532
529
  self._update_tokens(0)
533
530
  # Clear status message (e.g., "Interrupted" from previous session)
534
531
  self._update_status("")
535
- # New AgentLoop (daemon) or new local loop id
536
532
  if self._session_state:
537
- if self._daemon_session is not None:
533
+ if self._daemon_session is None:
534
+ await self._mount_message(
535
+ AppMessage("Not connected to the daemon; cannot start a new loop.")
536
+ )
537
+ else:
538
538
  status_event = await self._daemon_session.new_loop()
539
539
  new_loop_id = (
540
540
  str(status_event.get("loop_id", "")) or self._session_state.reset_loop()
541
541
  )
542
542
  self._session_state.loop_id = new_loop_id
543
543
  self._lc_loop_id = new_loop_id
544
- else:
545
- new_loop_id = self._session_state.reset_loop()
546
- try:
547
- banner = self.query_one("#welcome-banner", WelcomeBanner)
548
- banner.update_loop_id(new_loop_id)
549
- except NoMatches:
550
- pass
551
- self._clear_loop_model_override()
552
- await self._mount_message(AppMessage(f"Started new loop: {new_loop_id}"))
544
+ try:
545
+ banner = self.query_one("#welcome-banner", WelcomeBanner)
546
+ banner.update_loop_id(new_loop_id)
547
+ except NoMatches:
548
+ pass
549
+ self._clear_loop_model_override()
550
+ await self._mount_message(AppMessage(f"Started new loop: {new_loop_id}"))
553
551
  elif cmd == "/editor":
554
552
  await self.action_open_editor()
555
553
  elif cmd == "/loops":
@@ -708,13 +706,6 @@ class _ExecutionMixin:
708
706
  report += "\nTheme registry reload failed. Check config.yml for errors."
709
707
  await self._mount_message(AppMessage(report))
710
708
 
711
- # Re-discover skills so autocomplete reflects any new/removed skills
712
- if self._daemon_config is None:
713
- self.run_worker(
714
- self._discover_skills(),
715
- exclusive=True,
716
- group="startup-skill-discovery",
717
- )
718
709
  if self._daemon_session is not None:
719
710
  self.run_worker(
720
711
  self._refresh_daemon_skills_catalog(),
@@ -759,33 +750,6 @@ class _ExecutionMixin:
759
750
  AppMessage("Skills require a daemon connection. Connect to a daemon first.")
760
751
  )
761
752
 
762
- async def _get_conversation_token_count(self) -> int | None:
763
- """Return the approximate conversation-only token count.
764
-
765
- Returns:
766
- Token count as an integer, or `None` if state is unavailable.
767
- """
768
- if not self._agent:
769
- return None
770
- try:
771
- from langchain_core.messages.utils import (
772
- count_tokens_approximately,
773
- )
774
-
775
- config: RunnableConfig = {
776
- "configurable": {"thread_id": self._lc_loop_id},
777
- }
778
- state = await self._agent.aget_state(config)
779
- if not state or not state.values:
780
- return None
781
- messages = state.values.get("messages", [])
782
- if not messages:
783
- return None
784
- return count_tokens_approximately(messages)
785
- except Exception: # best-effort for /tokens display
786
- logger.debug("Failed to retrieve conversation token count", exc_info=True)
787
- return None
788
-
789
753
  async def _handle_user_message(self, message: str) -> None:
790
754
  """Handle a user message to send to the agent.
791
755
 
@@ -878,7 +842,6 @@ class _ExecutionMixin:
878
842
  try:
879
843
  await execute_task_textual(
880
844
  user_input=message,
881
- agent=self._agent,
882
845
  daemon_session=self._daemon_session,
883
846
  assistant_id=self._assistant_id,
884
847
  session_state=self._session_state,