soothe-cli 0.7.11__tar.gz → 0.7.12__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 (129) hide show
  1. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/PKG-INFO +1 -1
  2. soothe_cli-0.7.12/src/soothe_cli/__init__.py +5 -0
  3. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/main.py +2 -2
  4. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/presentation/duration_format.py +9 -2
  5. soothe_cli-0.7.12/src/soothe_cli/tui/_version.py +128 -0
  6. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/_execution.py +2 -29
  7. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/_startup.py +3 -0
  8. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/config.py +13 -86
  9. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/loading.py +3 -3
  10. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +2 -3
  11. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +2 -2
  12. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +1 -0
  13. soothe_cli-0.7.11/src/soothe_cli/__init__.py +0 -10
  14. soothe_cli-0.7.11/src/soothe_cli/tui/_version.py +0 -21
  15. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/.gitignore +0 -0
  16. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/README.md +0 -0
  17. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/pyproject.toml +0 -0
  18. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/__init__.py +0 -0
  19. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/commands/__init__.py +0 -0
  20. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
  21. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
  22. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
  23. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
  24. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
  25. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
  26. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/execution/__init__.py +0 -0
  27. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/execution/daemon.py +0 -0
  28. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
  29. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/execution/headless.py +0 -0
  30. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
  31. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/cli/execution/launcher.py +0 -0
  32. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/config/__init__.py +0 -0
  33. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/config/cli_config.py +0 -0
  34. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/config/loader.py +0 -0
  35. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/config/logging_setup.py +0 -0
  36. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/__init__.py +0 -0
  37. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/headless/processor.py +0 -0
  38. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
  39. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
  40. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
  41. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
  42. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
  43. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
  44. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
  45. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/presentation/engine.py +0 -0
  46. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
  47. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
  48. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
  49. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
  50. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/state/session_stats.py +0 -0
  51. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/state/step_router.py +0 -0
  52. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
  53. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/state/transcript.py +0 -0
  54. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/transport/session.py +0 -0
  55. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
  56. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/turn/prepare.py +0 -0
  57. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
  58. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/wire/message_text.py +0 -0
  59. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/runtime/wire/messages.py +0 -0
  60. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/__init__.py +0 -0
  61. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/_cli_context.py +0 -0
  62. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/_env_vars.py +0 -0
  63. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/__init__.py +0 -0
  64. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/_app.py +0 -0
  65. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/_history.py +0 -0
  66. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/_messages_mixin.py +0 -0
  67. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/_model.py +0 -0
  68. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/_module_init.py +0 -0
  69. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/_ui.py +0 -0
  70. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/app/app.tcss +0 -0
  71. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/binding.py +0 -0
  72. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/command_registry.py +0 -0
  73. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/commands/__init__.py +0 -0
  74. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/commands/command_router.py +0 -0
  75. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
  76. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
  77. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/file_change_notify.py +0 -0
  78. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/file_change_renderers.py +0 -0
  79. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/hooks.py +0 -0
  80. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/input.py +0 -0
  81. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/markdown_theme.py +0 -0
  82. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/media_utils.py +0 -0
  83. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/model_config.py +0 -0
  84. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/path_utils.py +0 -0
  85. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/preview_limits.py +0 -0
  86. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/project_utils.py +0 -0
  87. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/sessions.py +0 -0
  88. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/shell_color.py +0 -0
  89. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/skills/__init__.py +0 -0
  90. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/skills/invocation.py +0 -0
  91. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/skills/load.py +0 -0
  92. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/spinner_labels.py +0 -0
  93. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/textual_adapter.py +0 -0
  94. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/theme.py +0 -0
  95. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/tips.py +0 -0
  96. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/tool_display.py +0 -0
  97. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/unicode_security.py +0 -0
  98. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/update_check.py +0 -0
  99. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  100. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/_links.py +0 -0
  101. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  102. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  103. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  104. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/context_viewer.py +0 -0
  105. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/diff.py +0 -0
  106. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/editor.py +0 -0
  107. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
  108. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/help_screen.py +0 -0
  109. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/history.py +0 -0
  110. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  111. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  112. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  113. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
  114. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/_helpers.py +0 -0
  115. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
  116. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/assistant.py +0 -0
  117. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/clarification.py +0 -0
  118. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +0 -0
  119. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/cognition_step.py +0 -0
  120. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/cognition_subagent.py +0 -0
  121. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
  122. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
  123. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/skill.py +0 -0
  124. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
  125. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  126. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  127. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/status.py +0 -0
  128. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  129. {soothe_cli-0.7.11 → soothe_cli-0.7.12}/src/soothe_cli/tui/widgets/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.7.11
3
+ Version: 0.7.12
4
4
  Summary: Soothe CLI client - communicates with daemon via WebSocket
5
5
  Project-URL: Homepage, https://github.com/mirasoth/soothe
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
@@ -0,0 +1,5 @@
1
+ """Soothe CLI client - communicates with daemon via WebSocket."""
2
+
3
+ from soothe_cli.tui._version import __version__
4
+
5
+ __all__ = ["__version__"]
@@ -6,7 +6,6 @@ from dotenv import load_dotenv
6
6
 
7
7
  load_dotenv()
8
8
 
9
- from importlib.metadata import version # noqa: E402
10
9
  from pathlib import Path # noqa: E402
11
10
  from typing import Annotated # noqa: E402
12
11
 
@@ -15,6 +14,7 @@ from soothe_sdk.client.config import SOOTHE_HOME # noqa: E402
15
14
 
16
15
  from soothe_cli.config.cli_config import CLIConfig # noqa: E402
17
16
  from soothe_cli.config.loader import set_runtime_config # noqa: E402
17
+ from soothe_cli.tui._version import __version__ # noqa: E402
18
18
  from soothe_cli.tui.markdown_theme import ( # noqa: E402
19
19
  DEFAULT_MARKDOWN_THEME,
20
20
  REGISTRY,
@@ -177,7 +177,7 @@ def main(
177
177
 
178
178
  # Handle --version flag
179
179
  if show_version:
180
- typer.echo(f"soothe {version('soothe-cli')}")
180
+ typer.echo(f"soothe {__version__}")
181
181
  raise typer.Exit
182
182
 
183
183
  home_path = Path(soothe_home).expanduser() if soothe_home else Path(SOOTHE_HOME)
@@ -4,13 +4,15 @@ from __future__ import annotations
4
4
 
5
5
 
6
6
  def format_duration(seconds: float) -> str:
7
- """Format a duration in seconds into a human-readable string.
7
+ """Format a completed duration in seconds into a human-readable string.
8
8
 
9
9
  Args:
10
10
  seconds: Duration in seconds.
11
11
 
12
12
  Returns:
13
- Formatted string like `"5s"`, `"2.3s"`, `"5m 12s"`, or `"1h 23m 4s"`.
13
+ Formatted string like ``5s``, ``2.3s``, ``5m 12s``, or ``1h 23m 4s``.
14
+ For live running timers that tick in whole seconds, use
15
+ :func:`format_running_elapsed` instead.
14
16
  """
15
17
  rounded = round(seconds, 1)
16
18
  if rounded < 60: # noqa: PLR2004
@@ -24,6 +26,11 @@ def format_duration(seconds: float) -> str:
24
26
  return f"{hours}h {minutes}m {secs}s"
25
27
 
26
28
 
29
+ def format_running_elapsed(seconds: float) -> str:
30
+ """Format a live running timer using whole-second ticks (e.g. ``20s``, ``1m 5s``)."""
31
+ return format_duration(float(max(0, int(seconds))))
32
+
33
+
27
34
  def format_duration_ms(milliseconds: int) -> str:
28
35
  """Format a wall-clock duration in milliseconds for status lines and cards.
29
36
 
@@ -0,0 +1,128 @@
1
+ """Version information and lightweight constants for `soothe`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import re
8
+ from importlib.metadata import PackageNotFoundError, distribution, version
9
+ from pathlib import Path
10
+ from urllib.parse import unquote, urlparse
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ _VERSION_PATTERN = re.compile(r"^(\d+\.\d+\.\d+)")
15
+
16
+
17
+ def _parse_version_file_text(text: str) -> str | None:
18
+ """Extract the semver from a repo ``VERSION`` file."""
19
+ for line in text.splitlines():
20
+ stripped = line.strip()
21
+ if not stripped or stripped.startswith("#"):
22
+ continue
23
+ match = _VERSION_PATTERN.match(stripped)
24
+ if match:
25
+ return match.group(1)
26
+ return None
27
+
28
+
29
+ def _editable_source_roots() -> list[Path]:
30
+ """Return resolved source roots for editable Soothe packages."""
31
+ roots: list[Path] = []
32
+ seen: set[Path] = set()
33
+ for dist_name in ("soothe-cli", "soothe", "Soothe"):
34
+ try:
35
+ dist = distribution(dist_name)
36
+ except PackageNotFoundError:
37
+ continue
38
+ try:
39
+ raw = dist.read_text("direct_url.json")
40
+ except (FileNotFoundError, OSError):
41
+ continue
42
+ if not raw:
43
+ continue
44
+ try:
45
+ data = json.loads(raw)
46
+ except json.JSONDecodeError:
47
+ logger.debug("Invalid direct_url.json for %s", dist_name, exc_info=True)
48
+ continue
49
+ if not data.get("dir_info", {}).get("editable"):
50
+ continue
51
+ url = data.get("url", "")
52
+ if not url.startswith("file://"):
53
+ continue
54
+ root = Path(unquote(urlparse(url).path)).resolve()
55
+ if root not in seen:
56
+ seen.add(root)
57
+ roots.append(root)
58
+ return roots
59
+
60
+
61
+ def _read_repo_version_from_source(root: Path) -> str | None:
62
+ """Read the monorepo ``VERSION`` file for an editable install root."""
63
+ candidates = [root.parent.parent / "VERSION", *(parent / "VERSION" for parent in root.parents)]
64
+ for path in candidates:
65
+ if not path.is_file():
66
+ continue
67
+ try:
68
+ parsed = _parse_version_file_text(path.read_text(encoding="utf-8"))
69
+ except OSError:
70
+ logger.debug("Failed to read VERSION file at %s", path, exc_info=True)
71
+ continue
72
+ if parsed:
73
+ return parsed
74
+ return None
75
+
76
+
77
+ def _resolve_version() -> str:
78
+ """Resolve the CLI version, preferring repo ``VERSION`` for editable installs."""
79
+ for root in _editable_source_roots():
80
+ repo_version = _read_repo_version_from_source(root)
81
+ if repo_version:
82
+ return repo_version
83
+
84
+ for pkg in ("soothe-cli", "soothe"):
85
+ try:
86
+ return version(pkg)
87
+ except PackageNotFoundError:
88
+ continue
89
+ return "0.0.0"
90
+
91
+
92
+ def is_editable_install() -> bool:
93
+ """Return whether Soothe is installed in editable mode."""
94
+ return bool(_editable_source_roots())
95
+
96
+
97
+ def editable_install_display_path() -> str | None:
98
+ """Return a ``~``-contracted source path for editable installs."""
99
+ roots = _editable_source_roots()
100
+ if not roots:
101
+ return None
102
+
103
+ install_root = roots[0]
104
+ for root in roots:
105
+ if root.name == "soothe-cli":
106
+ install_root = root
107
+ break
108
+
109
+ path = str(install_root)
110
+ home = str(Path.home())
111
+ if path.startswith(home):
112
+ return "~" + path[len(home) :]
113
+ return path
114
+
115
+
116
+ __version__ = _resolve_version()
117
+
118
+ DOCS_URL = "https://github.com/mirasoth/soothe/docs"
119
+ """URL for Soothe documentation."""
120
+
121
+ PYPI_URL = "https://pypi.org/pypi/soothe/json"
122
+ """PyPI JSON API endpoint for version checks."""
123
+
124
+ CHANGELOG_URL = "https://github.com/mirasoth/soothe/blob/main/CHANGELOG.md"
125
+ """URL for the full changelog."""
126
+
127
+ USER_AGENT = f"soothe/{__version__} update-check"
128
+ """User-Agent header sent with PyPI requests."""
@@ -533,36 +533,9 @@ class _ExecutionMixin:
533
533
  await self._open_url_command(command, cmd)
534
534
  elif cmd == "/version":
535
535
  await self._mount_message(UserMessage(command))
536
- # Show CLI and SDK package versions
537
- try:
538
- from soothe_cli.tui._version import (
539
- __version__ as cli_version,
540
- )
541
-
542
- cli_line = f"Soothe version: {cli_version}"
543
- except ImportError:
544
- logger.debug("soothe._version module not found")
545
- cli_line = "Soothe version: unknown"
546
- except Exception:
547
- logger.warning("Unexpected error looking up CLI version", exc_info=True)
548
- cli_line = "Soothe version: unknown"
549
- try:
550
- from importlib.metadata import (
551
- PackageNotFoundError,
552
- )
553
- from importlib.metadata import (
554
- version as _pkg_version,
555
- )
536
+ from soothe_cli.tui._version import __version__ as cli_version
556
537
 
557
- sdk_version = _pkg_version("Soothe")
558
- sdk_line = f"Soothe (SDK) version: {sdk_version}"
559
- except PackageNotFoundError:
560
- logger.debug("Soothe SDK package not found in environment")
561
- sdk_line = "Soothe (SDK) version: unknown"
562
- except Exception:
563
- logger.warning("Unexpected error looking up SDK version", exc_info=True)
564
- sdk_line = "Soothe (SDK) version: unknown"
565
- await self._mount_message(AppMessage(f"{cli_line}\n{sdk_line}"))
538
+ await self._mount_message(AppMessage(f"Soothe version: {cli_version}"))
566
539
  elif cmd == "/clear":
567
540
  self._pending_messages.clear()
568
541
  self._queued_widgets.clear()
@@ -119,6 +119,9 @@ class _StartupMixin:
119
119
  gc.freeze()
120
120
 
121
121
  chat = self.query_one("#chat", VerticalScroll)
122
+ # Transcript scrolling is mouse-driven; keeping the region unfocusable
123
+ # prevents clicks in the message area from stealing the chat prompt caret.
124
+ chat.can_focus = False
122
125
  chat.anchor()
123
126
 
124
127
  self._status_bar = self.query_one("#status-bar", StatusBar)
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import importlib
6
- import json
7
6
  import logging
8
7
  import os
9
8
  import re
@@ -11,14 +10,19 @@ import sys
11
10
  import threading
12
11
  from dataclasses import dataclass
13
12
  from enum import StrEnum
14
- from importlib.metadata import PackageNotFoundError, distribution
15
13
  from pathlib import Path
16
14
  from typing import TYPE_CHECKING, Any
17
- from urllib.parse import unquote, urlparse
18
15
 
19
16
  from soothe_sdk.client.config import SOOTHE_HOME
20
17
 
21
- from soothe_cli.tui._version import __version__
18
+ from soothe_cli.tui._version import (
19
+ __version__,
20
+ )
21
+ from soothe_cli.tui._version import (
22
+ is_editable_install as _is_editable_install,
23
+ )
24
+
25
+ __all__ = ["_is_editable_install"]
22
26
 
23
27
  logger = logging.getLogger(__name__)
24
28
 
@@ -330,67 +334,6 @@ ASCII_GLYPHS = Glyphs(
330
334
  _glyphs_cache: Glyphs | None = None
331
335
  """Module-level cache for detected glyphs."""
332
336
 
333
- _editable_cache: tuple[bool, str | None] | None = None
334
- """Module-level cache for editable install info: (is_editable, source_path)."""
335
-
336
-
337
- def _resolve_editable_info() -> tuple[bool, str | None]:
338
- """Parse PEP 610 `direct_url.json` once and cache both results.
339
-
340
- Returns:
341
- Tuple of (is_editable, contracted_source_path). The path is
342
- `~`-contracted when it falls under the user's home directory, or
343
- `None` when the install is non-editable or the path is unavailable.
344
- """
345
- global _editable_cache # noqa: PLW0603 # Module-level cache requires global statement
346
- if _editable_cache is not None:
347
- return _editable_cache
348
-
349
- editable = False
350
- path: str | None = None
351
-
352
- try:
353
- dist = distribution("Soothe")
354
- raw = dist.read_text("direct_url.json")
355
- if raw:
356
- data = json.loads(raw)
357
- editable = data.get("dir_info", {}).get("editable", False)
358
- if editable:
359
- url = data.get("url", "")
360
- if url.startswith("file://"):
361
- path = unquote(urlparse(url).path)
362
- home = str(Path.home())
363
- if path.startswith(home):
364
- path = "~" + path[len(home) :]
365
- except (PackageNotFoundError, FileNotFoundError, json.JSONDecodeError, TypeError):
366
- logger.debug(
367
- "Failed to read editable install info from PEP 610 metadata",
368
- exc_info=True,
369
- )
370
-
371
- _editable_cache = (editable, path)
372
- return _editable_cache
373
-
374
-
375
- def _is_editable_install() -> bool:
376
- """Check if Soothe is installed in editable mode.
377
-
378
- Uses PEP 610 `direct_url.json` metadata to detect editable installs.
379
-
380
- Returns:
381
- `True` if installed in editable mode, `False` otherwise.
382
- """
383
- return _resolve_editable_info()[0]
384
-
385
-
386
- def _get_editable_install_path() -> str | None:
387
- """Return the `~`-contracted source directory for an editable install.
388
-
389
- Returns `None` for non-editable installs or when the path cannot be
390
- determined.
391
- """
392
- return _resolve_editable_info()[1]
393
-
394
337
 
395
338
  def _detect_charset_mode() -> CharsetMode:
396
339
  """Auto-detect terminal charset capabilities.
@@ -544,19 +487,10 @@ def build_stream_config(
544
487
  ) -> RunnableConfig:
545
488
  """Build the LangGraph stream config dict.
546
489
 
547
- Injects CLI and SDK versions into `metadata["versions"]` so runs can be
548
- correlated with specific releases.
549
-
550
- Why the CLI sets *both* versions:
551
-
552
- * `create_deep_agent` bakes `versions: {"Soothe": "X.Y.Z"}` into the
553
- compiled graph via `with_config`. At stream time, LangGraph merges
554
- the graph config with the runtime config passed here. Because the
555
- metadata merge is shallow (effectively `{**graph_meta, **runtime_meta}`
556
- for top-level keys), both configs containing a `versions` key means
557
- the runtime dict **replaces** the graph dict entirely — the SDK
558
- version would be lost.
559
- * Including the SDK version here ensures it survives the merge.
490
+ Injects the resolved Soothe version into ``metadata["versions"]`` so runs
491
+ can be correlated with specific releases. The runtime config replaces the
492
+ graph config's ``versions`` key at stream time, so this must carry the
493
+ canonical release string.
560
494
 
561
495
  Args:
562
496
  loop_id: Active StrangeLoop id (stored under LangGraph ``configurable.thread_id``).
@@ -571,8 +505,6 @@ def build_stream_config(
571
505
  Returns:
572
506
  Config dict with `configurable` and `metadata` keys.
573
507
  """
574
- import contextlib
575
- import importlib.metadata as importlib_metadata
576
508
  from datetime import UTC, datetime
577
509
 
578
510
  try:
@@ -581,13 +513,8 @@ def build_stream_config(
581
513
  logger.warning("Could not determine working directory", exc_info=True)
582
514
  cwd = ""
583
515
 
584
- # Include SDK version alongside CLI version — see docstring for why.
585
- versions: dict[str, str] = {"Soothe": __version__}
586
- with contextlib.suppress(importlib_metadata.PackageNotFoundError):
587
- versions["Soothe"] = importlib_metadata.version("Soothe")
588
-
589
516
  metadata: dict[str, Any] = {
590
- "versions": versions,
517
+ "versions": {"Soothe": __version__},
591
518
  }
592
519
  from soothe_cli.tui._env_vars import USER_ID
593
520
 
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
8
8
  from textual.content import Content
9
9
  from textual.widgets import Static
10
10
 
11
- from soothe_cli.runtime.presentation.duration_format import format_duration
11
+ from soothe_cli.runtime.presentation.duration_format import format_running_elapsed
12
12
  from soothe_cli.tui import theme
13
13
  from soothe_cli.tui.config import get_glyphs
14
14
 
@@ -101,7 +101,7 @@ class LoadingWidget(Static):
101
101
  return f" {status}... "
102
102
 
103
103
  def _format_hint_line(self, elapsed_secs: float, *, include_interrupt: bool = False) -> str:
104
- duration = format_duration(elapsed_secs)
104
+ duration = format_running_elapsed(elapsed_secs)
105
105
  segments: list[str] = []
106
106
  if self._hint_extra:
107
107
  segments.append(self._hint_extra)
@@ -121,7 +121,7 @@ class LoadingWidget(Static):
121
121
  status_part = Content.styled(self._format_status_line(self._status), colors.primary)
122
122
  if self._paused:
123
123
  spinner_part = Content.styled(get_glyphs().pause, "dim")
124
- paused_duration = format_duration(float(self._paused_total_elapsed))
124
+ paused_duration = format_running_elapsed(float(self._paused_total_elapsed))
125
125
  if self._show_interrupt_hint:
126
126
  paused_hint = f" (paused at {paused_duration} · esc to interrupt)"
127
127
  else:
@@ -11,7 +11,7 @@ from textual.containers import Vertical
11
11
  from textual.content import Content
12
12
  from textual.widgets import Static
13
13
 
14
- from soothe_cli.runtime.presentation.duration_format import format_duration
14
+ from soothe_cli.runtime.presentation.duration_format import format_duration, format_running_elapsed
15
15
  from soothe_cli.tui import theme
16
16
  from soothe_cli.tui.config import get_glyphs
17
17
  from soothe_cli.tui.preview_limits import PLAN_QUICK_VIEW_STEP_LINE_MAX_CHARS
@@ -253,8 +253,7 @@ class CognitionGoalTreeMessage(Vertical):
253
253
  if st.phase == "running":
254
254
  parts: list[str] = []
255
255
  if st.started_at is not None:
256
- elapsed_s = max(0.001, time() - st.started_at)
257
- parts.append(format_duration(elapsed_s))
256
+ parts.append(format_running_elapsed(time() - st.started_at))
258
257
  if st.tool_call_count > 0:
259
258
  parts.append(f"{st.tool_call_count} tools")
260
259
  if not parts:
@@ -19,7 +19,7 @@ from soothe_sdk.ux.task_namespace import (
19
19
  )
20
20
  from textual.content import Content
21
21
 
22
- from soothe_cli.runtime.presentation.duration_format import format_duration
22
+ from soothe_cli.runtime.presentation.duration_format import format_running_elapsed
23
23
  from soothe_cli.tui import theme
24
24
  from soothe_cli.tui.commands.subagent_routing import get_subagent_display_name
25
25
  from soothe_cli.tui.preview_limits import STEP_CARD_TOOL_ACTIVITY_PREVIEW_COUNT
@@ -433,7 +433,7 @@ class StepCardStatusLine:
433
433
  """Step card footer while executing."""
434
434
  elapsed = ""
435
435
  if elapsed_secs is not None:
436
- elapsed = f" ({format_duration(float(elapsed_secs))})"
436
+ elapsed = f" ({format_running_elapsed(float(elapsed_secs))})"
437
437
  head = f"{gutter}{spinner_frame} Running{retry_suffix}...{elapsed}"
438
438
  tail = f"{stats_suffix}{token_suffix}"
439
439
  parts: list[object] = [Content.styled(head, colors.warning)]
@@ -108,6 +108,7 @@ class PlanQuickViewOverlay(Vertical):
108
108
 
109
109
  def on_mount(self) -> None:
110
110
  self._content = self.query_one("#plan-quick-view-content", Static)
111
+ self.query_one("#plan-quick-view-body", VerticalScroll).can_focus = False
111
112
  self.display = False
112
113
 
113
114
  @property
@@ -1,10 +0,0 @@
1
- """Soothe CLI client - communicates with daemon via WebSocket."""
2
-
3
- import importlib.metadata
4
-
5
- try:
6
- __version__ = importlib.metadata.version("soothe-cli")
7
- except importlib.metadata.PackageNotFoundError:
8
- __version__ = "0.0.0"
9
-
10
- __all__ = []
@@ -1,21 +0,0 @@
1
- """Version information and lightweight constants for `soothe`."""
2
-
3
- from importlib.metadata import PackageNotFoundError, version
4
-
5
- try:
6
- __version__ = version("soothe")
7
- except PackageNotFoundError:
8
- # Fallback for development/editable installs
9
- __version__ = "0.0.0"
10
-
11
- DOCS_URL = "https://github.com/mirasoth/soothe/docs"
12
- """URL for Soothe documentation."""
13
-
14
- PYPI_URL = "https://pypi.org/pypi/soothe/json"
15
- """PyPI JSON API endpoint for version checks."""
16
-
17
- CHANGELOG_URL = "https://github.com/mirasoth/soothe/blob/main/CHANGELOG.md"
18
- """URL for the full changelog."""
19
-
20
- USER_AGENT = f"soothe/{__version__} update-check"
21
- """User-Agent header sent with PyPI requests."""
File without changes
File without changes
File without changes