soothe-cli 0.1.0__py3-none-any.whl

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/__init__.py +5 -0
  2. soothe_cli/cli/__init__.py +1 -0
  3. soothe_cli/cli/commands/__init__.py +1 -0
  4. soothe_cli/cli/commands/autopilot_cmd.py +410 -0
  5. soothe_cli/cli/commands/config_cmd.py +277 -0
  6. soothe_cli/cli/commands/run_cmd.py +87 -0
  7. soothe_cli/cli/commands/status_cmd.py +121 -0
  8. soothe_cli/cli/commands/subagent_names.py +17 -0
  9. soothe_cli/cli/commands/thread_cmd.py +657 -0
  10. soothe_cli/cli/execution/__init__.py +6 -0
  11. soothe_cli/cli/execution/daemon.py +194 -0
  12. soothe_cli/cli/execution/headless.py +99 -0
  13. soothe_cli/cli/execution/launcher.py +31 -0
  14. soothe_cli/cli/main.py +509 -0
  15. soothe_cli/cli/renderer.py +444 -0
  16. soothe_cli/cli/stream/__init__.py +17 -0
  17. soothe_cli/cli/stream/context.py +138 -0
  18. soothe_cli/cli/stream/display_line.py +83 -0
  19. soothe_cli/cli/stream/formatter.py +412 -0
  20. soothe_cli/cli/stream/pipeline.py +521 -0
  21. soothe_cli/cli/utils.py +46 -0
  22. soothe_cli/config/__init__.py +5 -0
  23. soothe_cli/config/cli_config.py +155 -0
  24. soothe_cli/plan/__init__.py +5 -0
  25. soothe_cli/plan/rich_tree.py +54 -0
  26. soothe_cli/shared/__init__.py +107 -0
  27. soothe_cli/shared/command_router.py +246 -0
  28. soothe_cli/shared/config_loader.py +68 -0
  29. soothe_cli/shared/display_policy.py +413 -0
  30. soothe_cli/shared/essential_events.py +68 -0
  31. soothe_cli/shared/event_processor.py +823 -0
  32. soothe_cli/shared/message_processing.py +393 -0
  33. soothe_cli/shared/presentation_engine.py +173 -0
  34. soothe_cli/shared/processor_state.py +80 -0
  35. soothe_cli/shared/renderer_protocol.py +158 -0
  36. soothe_cli/shared/rendering.py +43 -0
  37. soothe_cli/shared/slash_commands.py +354 -0
  38. soothe_cli/shared/subagent_routing.py +63 -0
  39. soothe_cli/shared/suppression_state.py +188 -0
  40. soothe_cli/shared/tool_formatters/__init__.py +27 -0
  41. soothe_cli/shared/tool_formatters/base.py +109 -0
  42. soothe_cli/shared/tool_formatters/execution.py +297 -0
  43. soothe_cli/shared/tool_formatters/fallback.py +128 -0
  44. soothe_cli/shared/tool_formatters/file_ops.py +299 -0
  45. soothe_cli/shared/tool_formatters/goal_formatter.py +331 -0
  46. soothe_cli/shared/tool_formatters/media.py +291 -0
  47. soothe_cli/shared/tool_formatters/structured.py +202 -0
  48. soothe_cli/shared/tool_formatters/web.py +143 -0
  49. soothe_cli/shared/tool_output_formatter.py +227 -0
  50. soothe_cli/shared/tui_trace_log.py +40 -0
  51. soothe_cli/tui/__init__.py +5 -0
  52. soothe_cli/tui/_ask_user_types.py +50 -0
  53. soothe_cli/tui/_cli_context.py +27 -0
  54. soothe_cli/tui/_env_vars.py +56 -0
  55. soothe_cli/tui/_session_stats.py +114 -0
  56. soothe_cli/tui/_version.py +21 -0
  57. soothe_cli/tui/app.py +4992 -0
  58. soothe_cli/tui/app.tcss +302 -0
  59. soothe_cli/tui/command_registry.py +310 -0
  60. soothe_cli/tui/config.py +2381 -0
  61. soothe_cli/tui/daemon_session.py +233 -0
  62. soothe_cli/tui/file_ops.py +409 -0
  63. soothe_cli/tui/formatting.py +28 -0
  64. soothe_cli/tui/hooks.py +23 -0
  65. soothe_cli/tui/input.py +782 -0
  66. soothe_cli/tui/media_utils.py +471 -0
  67. soothe_cli/tui/model_config.py +518 -0
  68. soothe_cli/tui/output.py +69 -0
  69. soothe_cli/tui/project_utils.py +188 -0
  70. soothe_cli/tui/sessions.py +1248 -0
  71. soothe_cli/tui/skills/__init__.py +5 -0
  72. soothe_cli/tui/skills/invocation.py +74 -0
  73. soothe_cli/tui/skills/load.py +93 -0
  74. soothe_cli/tui/textual_adapter.py +1430 -0
  75. soothe_cli/tui/theme.py +838 -0
  76. soothe_cli/tui/tool_display.py +297 -0
  77. soothe_cli/tui/unicode_security.py +502 -0
  78. soothe_cli/tui/update_check.py +447 -0
  79. soothe_cli/tui/widgets/__init__.py +9 -0
  80. soothe_cli/tui/widgets/_links.py +63 -0
  81. soothe_cli/tui/widgets/approval.py +430 -0
  82. soothe_cli/tui/widgets/ask_user.py +392 -0
  83. soothe_cli/tui/widgets/autocomplete.py +666 -0
  84. soothe_cli/tui/widgets/autopilot_dashboard.py +308 -0
  85. soothe_cli/tui/widgets/autopilot_screen.py +64 -0
  86. soothe_cli/tui/widgets/chat_input.py +1834 -0
  87. soothe_cli/tui/widgets/clipboard.py +128 -0
  88. soothe_cli/tui/widgets/diff.py +240 -0
  89. soothe_cli/tui/widgets/editor.py +140 -0
  90. soothe_cli/tui/widgets/history.py +221 -0
  91. soothe_cli/tui/widgets/loading.py +194 -0
  92. soothe_cli/tui/widgets/mcp_viewer.py +352 -0
  93. soothe_cli/tui/widgets/message_store.py +693 -0
  94. soothe_cli/tui/widgets/messages.py +1720 -0
  95. soothe_cli/tui/widgets/model_selector.py +988 -0
  96. soothe_cli/tui/widgets/notification_settings.py +155 -0
  97. soothe_cli/tui/widgets/status.py +403 -0
  98. soothe_cli/tui/widgets/theme_selector.py +158 -0
  99. soothe_cli/tui/widgets/thread_selector.py +1865 -0
  100. soothe_cli/tui/widgets/tool_renderers.py +148 -0
  101. soothe_cli/tui/widgets/tool_widgets.py +254 -0
  102. soothe_cli/tui/widgets/tools.py +165 -0
  103. soothe_cli/tui/widgets/welcome.py +330 -0
  104. soothe_cli-0.1.0.dist-info/METADATA +100 -0
  105. soothe_cli-0.1.0.dist-info/RECORD +107 -0
  106. soothe_cli-0.1.0.dist-info/WHEEL +4 -0
  107. soothe_cli-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,56 @@
1
+ """Canonical registry of `SOOTHE_CLI_*` environment variables.
2
+
3
+ Every env var the CLI reads whose name starts with `SOOTHE_CLI_` must
4
+ be defined here as a module-level constant. A drift-detection test
5
+ (`tests/unit_tests/test_env_vars.py`) fails when a bare string literal
6
+ like `"SOOTHE_CLI_FOO"` appears in source code instead of a constant
7
+ imported from this module.
8
+
9
+ Import the short-name constants (e.g. `AUTO_UPDATE`, `DEBUG`) and pass them
10
+ to `os.environ.get()` instead of using raw string literals. If the env var is
11
+ ever renamed, only the value here changes.
12
+
13
+ !!! note
14
+
15
+ `resolve_env_var` also supports a dynamic prefix override for API keys
16
+ and provider credentials: setting `SOOTHE_CLI_{NAME}` takes priority
17
+ over `{NAME}`. For example, `SOOTHE_CLI_OPENAI_API_KEY` overrides
18
+ `OPENAI_API_KEY`. Only call sites that use `resolve_env_var` benefit from
19
+ this -- direct `os.environ.get` lookups (like the constants below) do not.
20
+ Dynamic overrides are not listed here because they mirror third-party
21
+ variable names.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Constants — import these instead of bare string literals.
28
+ # Keep alphabetically sorted by constant name.
29
+ # ---------------------------------------------------------------------------
30
+
31
+ AUTO_UPDATE = "SOOTHE_CLI_AUTO_UPDATE"
32
+ """Enable automatic CLI updates ('1', 'true', or 'yes')."""
33
+
34
+ DEBUG = "SOOTHE_CLI_DEBUG"
35
+ """Enable verbose debug logging to a file."""
36
+
37
+ DEBUG_FILE = "SOOTHE_CLI_DEBUG_FILE"
38
+ """Path for the debug log file (default: `/tmp/soothe_debug.log`)."""
39
+
40
+ EXTRA_SKILLS_DIRS = "SOOTHE_CLI_EXTRA_SKILLS_DIRS"
41
+ """Colon-separated paths added to the skill containment allowlist."""
42
+
43
+ LANGSMITH_PROJECT = "SOOTHE_CLI_LANGSMITH_PROJECT"
44
+ """Override LangSmith project name for agent traces."""
45
+
46
+ NO_UPDATE_CHECK = "SOOTHE_CLI_NO_UPDATE_CHECK"
47
+ """Disable automatic update checking when set."""
48
+
49
+ SERVER_ENV_PREFIX = "SOOTHE_CLI_SERVER_"
50
+ """Environment variable prefix used to pass CLI config to the server subprocess."""
51
+
52
+ SHELL_ALLOW_LIST = "SOOTHE_CLI_SHELL_ALLOW_LIST"
53
+ """Comma-separated shell commands to allow (or 'recommended'/'all')."""
54
+
55
+ USER_ID = "SOOTHE_CLI_USER_ID"
56
+ """Attach a user identifier to LangSmith trace metadata."""
@@ -0,0 +1,114 @@
1
+ """Lightweight session statistics and token formatting utilities.
2
+
3
+ This module is intentionally kept free of heavy dependencies (no pydantic, no
4
+ config, no widget imports) so that `app.py` can import `SessionStats` and
5
+ `format_token_count` at module level without pulling in the full
6
+ `textual_adapter` dependency tree.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Literal
13
+
14
+ SpinnerStatus = Literal["Thinking", "Offloading"] | None
15
+ """Valid spinner display states, or `None` to hide."""
16
+
17
+
18
+ @dataclass
19
+ class ModelStats:
20
+ """Token stats for a single model within a session.
21
+
22
+ Attributes:
23
+ request_count: Number of LLM API requests made to this model.
24
+ input_tokens: Cumulative input tokens sent to this model.
25
+ output_tokens: Cumulative output tokens received from this model.
26
+ """
27
+
28
+ request_count: int = 0
29
+ input_tokens: int = 0
30
+ output_tokens: int = 0
31
+
32
+
33
+ @dataclass
34
+ class SessionStats:
35
+ """Stats accumulated over a single agent turn (or full session).
36
+
37
+ Attributes:
38
+ request_count: Total LLM API requests made (each chunk with
39
+ usage_metadata counts as one completed request).
40
+ input_tokens: Cumulative input tokens across all LLM requests.
41
+ output_tokens: Cumulative output tokens across all LLM requests.
42
+ wall_time_seconds: Wall-clock duration from stream start to end.
43
+ per_model: Per-model breakdown keyed by model name.
44
+ Populated only when `record_request` receives a non-empty
45
+ `model_name`. Empty dict means no named-model requests were
46
+ recorded; `print_usage_table` omits the model table in that case and
47
+ shows only the wall-time line (if applicable).
48
+ """
49
+
50
+ request_count: int = 0
51
+ input_tokens: int = 0
52
+ output_tokens: int = 0
53
+ wall_time_seconds: float = 0.0
54
+ per_model: dict[str, ModelStats] = field(default_factory=dict)
55
+
56
+ def record_request(
57
+ self,
58
+ model_name: str,
59
+ input_toks: int,
60
+ output_toks: int,
61
+ ) -> None:
62
+ """Accumulate token counts for one completed LLM request.
63
+
64
+ Updates both the session totals and the per-model breakdown.
65
+
66
+ Args:
67
+ model_name: The model that served this request (used as the
68
+ per-model key). Pass an empty string to skip the per-model
69
+ breakdown for this request.
70
+ input_toks: Input tokens for this request.
71
+ output_toks: Output tokens for this request.
72
+ """
73
+ self.request_count += 1
74
+ self.input_tokens += input_toks
75
+ self.output_tokens += output_toks
76
+ if model_name:
77
+ entry = self.per_model.setdefault(model_name, ModelStats())
78
+ entry.request_count += 1
79
+ entry.input_tokens += input_toks
80
+ entry.output_tokens += output_toks
81
+
82
+ def merge(self, other: SessionStats) -> None:
83
+ """Merge another `SessionStats` into this one (mutates *self*).
84
+
85
+ Used to accumulate per-turn stats into a session-level total.
86
+
87
+ Args:
88
+ other: The stats to fold in.
89
+ """
90
+ self.request_count += other.request_count
91
+ self.input_tokens += other.input_tokens
92
+ self.output_tokens += other.output_tokens
93
+ self.wall_time_seconds += other.wall_time_seconds
94
+ for model, ms in other.per_model.items():
95
+ entry = self.per_model.setdefault(model, ModelStats())
96
+ entry.request_count += ms.request_count
97
+ entry.input_tokens += ms.input_tokens
98
+ entry.output_tokens += ms.output_tokens
99
+
100
+
101
+ def format_token_count(count: int) -> str:
102
+ """Format a token count into a human-readable short string.
103
+
104
+ Args:
105
+ count: Number of tokens.
106
+
107
+ Returns:
108
+ Formatted string like `'12.5K'`, `'1.2M'`, or `'500'`.
109
+ """
110
+ if count >= 1_000_000: # noqa: PLR2004
111
+ return f"{count / 1_000_000:.1f}M"
112
+ if count >= 1000: # noqa: PLR2004
113
+ return f"{count / 1000:.1f}K"
114
+ return str(count)
@@ -0,0 +1,21 @@
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/caesar0301/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/caesar0301/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."""