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.
- soothe_cli/__init__.py +5 -0
- soothe_cli/cli/__init__.py +1 -0
- soothe_cli/cli/commands/__init__.py +1 -0
- soothe_cli/cli/commands/autopilot_cmd.py +410 -0
- soothe_cli/cli/commands/config_cmd.py +277 -0
- soothe_cli/cli/commands/run_cmd.py +87 -0
- soothe_cli/cli/commands/status_cmd.py +121 -0
- soothe_cli/cli/commands/subagent_names.py +17 -0
- soothe_cli/cli/commands/thread_cmd.py +657 -0
- soothe_cli/cli/execution/__init__.py +6 -0
- soothe_cli/cli/execution/daemon.py +194 -0
- soothe_cli/cli/execution/headless.py +99 -0
- soothe_cli/cli/execution/launcher.py +31 -0
- soothe_cli/cli/main.py +509 -0
- soothe_cli/cli/renderer.py +444 -0
- soothe_cli/cli/stream/__init__.py +17 -0
- soothe_cli/cli/stream/context.py +138 -0
- soothe_cli/cli/stream/display_line.py +83 -0
- soothe_cli/cli/stream/formatter.py +412 -0
- soothe_cli/cli/stream/pipeline.py +521 -0
- soothe_cli/cli/utils.py +46 -0
- soothe_cli/config/__init__.py +5 -0
- soothe_cli/config/cli_config.py +155 -0
- soothe_cli/plan/__init__.py +5 -0
- soothe_cli/plan/rich_tree.py +54 -0
- soothe_cli/shared/__init__.py +107 -0
- soothe_cli/shared/command_router.py +246 -0
- soothe_cli/shared/config_loader.py +68 -0
- soothe_cli/shared/display_policy.py +413 -0
- soothe_cli/shared/essential_events.py +68 -0
- soothe_cli/shared/event_processor.py +823 -0
- soothe_cli/shared/message_processing.py +393 -0
- soothe_cli/shared/presentation_engine.py +173 -0
- soothe_cli/shared/processor_state.py +80 -0
- soothe_cli/shared/renderer_protocol.py +158 -0
- soothe_cli/shared/rendering.py +43 -0
- soothe_cli/shared/slash_commands.py +354 -0
- soothe_cli/shared/subagent_routing.py +63 -0
- soothe_cli/shared/suppression_state.py +188 -0
- soothe_cli/shared/tool_formatters/__init__.py +27 -0
- soothe_cli/shared/tool_formatters/base.py +109 -0
- soothe_cli/shared/tool_formatters/execution.py +297 -0
- soothe_cli/shared/tool_formatters/fallback.py +128 -0
- soothe_cli/shared/tool_formatters/file_ops.py +299 -0
- soothe_cli/shared/tool_formatters/goal_formatter.py +331 -0
- soothe_cli/shared/tool_formatters/media.py +291 -0
- soothe_cli/shared/tool_formatters/structured.py +202 -0
- soothe_cli/shared/tool_formatters/web.py +143 -0
- soothe_cli/shared/tool_output_formatter.py +227 -0
- soothe_cli/shared/tui_trace_log.py +40 -0
- soothe_cli/tui/__init__.py +5 -0
- soothe_cli/tui/_ask_user_types.py +50 -0
- soothe_cli/tui/_cli_context.py +27 -0
- soothe_cli/tui/_env_vars.py +56 -0
- soothe_cli/tui/_session_stats.py +114 -0
- soothe_cli/tui/_version.py +21 -0
- soothe_cli/tui/app.py +4992 -0
- soothe_cli/tui/app.tcss +302 -0
- soothe_cli/tui/command_registry.py +310 -0
- soothe_cli/tui/config.py +2381 -0
- soothe_cli/tui/daemon_session.py +233 -0
- soothe_cli/tui/file_ops.py +409 -0
- soothe_cli/tui/formatting.py +28 -0
- soothe_cli/tui/hooks.py +23 -0
- soothe_cli/tui/input.py +782 -0
- soothe_cli/tui/media_utils.py +471 -0
- soothe_cli/tui/model_config.py +518 -0
- soothe_cli/tui/output.py +69 -0
- soothe_cli/tui/project_utils.py +188 -0
- soothe_cli/tui/sessions.py +1248 -0
- soothe_cli/tui/skills/__init__.py +5 -0
- soothe_cli/tui/skills/invocation.py +74 -0
- soothe_cli/tui/skills/load.py +93 -0
- soothe_cli/tui/textual_adapter.py +1430 -0
- soothe_cli/tui/theme.py +838 -0
- soothe_cli/tui/tool_display.py +297 -0
- soothe_cli/tui/unicode_security.py +502 -0
- soothe_cli/tui/update_check.py +447 -0
- soothe_cli/tui/widgets/__init__.py +9 -0
- soothe_cli/tui/widgets/_links.py +63 -0
- soothe_cli/tui/widgets/approval.py +430 -0
- soothe_cli/tui/widgets/ask_user.py +392 -0
- soothe_cli/tui/widgets/autocomplete.py +666 -0
- soothe_cli/tui/widgets/autopilot_dashboard.py +308 -0
- soothe_cli/tui/widgets/autopilot_screen.py +64 -0
- soothe_cli/tui/widgets/chat_input.py +1834 -0
- soothe_cli/tui/widgets/clipboard.py +128 -0
- soothe_cli/tui/widgets/diff.py +240 -0
- soothe_cli/tui/widgets/editor.py +140 -0
- soothe_cli/tui/widgets/history.py +221 -0
- soothe_cli/tui/widgets/loading.py +194 -0
- soothe_cli/tui/widgets/mcp_viewer.py +352 -0
- soothe_cli/tui/widgets/message_store.py +693 -0
- soothe_cli/tui/widgets/messages.py +1720 -0
- soothe_cli/tui/widgets/model_selector.py +988 -0
- soothe_cli/tui/widgets/notification_settings.py +155 -0
- soothe_cli/tui/widgets/status.py +403 -0
- soothe_cli/tui/widgets/theme_selector.py +158 -0
- soothe_cli/tui/widgets/thread_selector.py +1865 -0
- soothe_cli/tui/widgets/tool_renderers.py +148 -0
- soothe_cli/tui/widgets/tool_widgets.py +254 -0
- soothe_cli/tui/widgets/tools.py +165 -0
- soothe_cli/tui/widgets/welcome.py +330 -0
- soothe_cli-0.1.0.dist-info/METADATA +100 -0
- soothe_cli-0.1.0.dist-info/RECORD +107 -0
- soothe_cli-0.1.0.dist-info/WHEEL +4 -0
- 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."""
|