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,158 @@
|
|
|
1
|
+
"""Interactive theme selector screen for /theme command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
7
|
+
|
|
8
|
+
from textual.binding import Binding, BindingType
|
|
9
|
+
from textual.containers import Vertical
|
|
10
|
+
from textual.screen import ModalScreen
|
|
11
|
+
from textual.widgets import OptionList, Static
|
|
12
|
+
from textual.widgets.option_list import Option
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from textual.app import ComposeResult
|
|
16
|
+
|
|
17
|
+
from soothe_cli.tui import theme
|
|
18
|
+
from soothe_cli.tui.config import get_glyphs, is_ascii_mode
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ThemeSelectorScreen(ModalScreen[str | None]):
|
|
24
|
+
"""Modal dialog for theme selection with live preview.
|
|
25
|
+
|
|
26
|
+
Displays available themes in an `OptionList`. Navigating the option list
|
|
27
|
+
applies a live preview by swapping the app theme. Returns the selected
|
|
28
|
+
theme name on Enter, or `None` on Esc (restoring the original theme).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
BINDINGS: ClassVar[list[BindingType]] = [
|
|
32
|
+
Binding("escape", "cancel", "Cancel", show=False),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
CSS = """
|
|
36
|
+
ThemeSelectorScreen {
|
|
37
|
+
align: center middle;
|
|
38
|
+
background: transparent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ThemeSelectorScreen > Vertical {
|
|
42
|
+
width: 50;
|
|
43
|
+
max-width: 90%;
|
|
44
|
+
height: auto;
|
|
45
|
+
max-height: 80%;
|
|
46
|
+
background: $surface;
|
|
47
|
+
border: solid $primary;
|
|
48
|
+
padding: 1 2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ThemeSelectorScreen .theme-selector-title {
|
|
52
|
+
text-style: bold;
|
|
53
|
+
color: $primary;
|
|
54
|
+
text-align: center;
|
|
55
|
+
margin-bottom: 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ThemeSelectorScreen OptionList {
|
|
59
|
+
height: auto;
|
|
60
|
+
max-height: 16;
|
|
61
|
+
background: $background;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ThemeSelectorScreen .theme-selector-help {
|
|
65
|
+
height: 1;
|
|
66
|
+
color: $text-muted;
|
|
67
|
+
text-style: italic;
|
|
68
|
+
margin-top: 1;
|
|
69
|
+
text-align: center;
|
|
70
|
+
}
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, current_theme: str) -> None:
|
|
74
|
+
"""Initialize the ThemeSelectorScreen.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
current_theme: The currently active theme name (to highlight).
|
|
78
|
+
"""
|
|
79
|
+
super().__init__()
|
|
80
|
+
self._current_theme = current_theme
|
|
81
|
+
self._original_theme = current_theme
|
|
82
|
+
|
|
83
|
+
def compose(self) -> ComposeResult:
|
|
84
|
+
"""Compose the screen layout.
|
|
85
|
+
|
|
86
|
+
Yields:
|
|
87
|
+
Widgets for the theme selector UI.
|
|
88
|
+
"""
|
|
89
|
+
glyphs = get_glyphs()
|
|
90
|
+
options: list[Option] = []
|
|
91
|
+
highlight_index = 0
|
|
92
|
+
|
|
93
|
+
for i, (name, entry) in enumerate(theme.ThemeEntry.REGISTRY.items()):
|
|
94
|
+
label = entry.label
|
|
95
|
+
if name == self._current_theme:
|
|
96
|
+
label = f"{label} (current)"
|
|
97
|
+
highlight_index = i
|
|
98
|
+
options.append(Option(label, id=name))
|
|
99
|
+
|
|
100
|
+
with Vertical():
|
|
101
|
+
yield Static("Select Theme", classes="theme-selector-title")
|
|
102
|
+
option_list = OptionList(*options, id="theme-options")
|
|
103
|
+
option_list.highlighted = highlight_index
|
|
104
|
+
yield option_list
|
|
105
|
+
help_text = f"{glyphs.arrow_up}/{glyphs.arrow_down} preview {glyphs.bullet} Enter select {glyphs.bullet} Esc cancel"
|
|
106
|
+
yield Static(help_text, classes="theme-selector-help")
|
|
107
|
+
|
|
108
|
+
def on_mount(self) -> None:
|
|
109
|
+
"""Apply ASCII border if needed."""
|
|
110
|
+
if is_ascii_mode():
|
|
111
|
+
container = self.query_one(Vertical)
|
|
112
|
+
colors = theme.get_theme_colors(self)
|
|
113
|
+
container.styles.border = ("ascii", colors.success)
|
|
114
|
+
|
|
115
|
+
def on_option_list_option_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
|
116
|
+
"""Live-preview the highlighted theme.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
event: The option highlighted event.
|
|
120
|
+
"""
|
|
121
|
+
name = event.option.id
|
|
122
|
+
if name is not None and name in theme.ThemeEntry.REGISTRY:
|
|
123
|
+
try:
|
|
124
|
+
self.app.theme = name
|
|
125
|
+
# refresh_css only repaints the active (modal) screen's layout;
|
|
126
|
+
# force the screen beneath us to repaint so the user sees the
|
|
127
|
+
# preview through the transparent scrim.
|
|
128
|
+
stack = self.app.screen_stack
|
|
129
|
+
if len(stack) > 1:
|
|
130
|
+
stack[-2].refresh(layout=True)
|
|
131
|
+
except Exception:
|
|
132
|
+
logger.warning("Failed to preview theme '%s'", name, exc_info=True)
|
|
133
|
+
try:
|
|
134
|
+
self.app.theme = self._original_theme
|
|
135
|
+
except Exception:
|
|
136
|
+
logger.warning(
|
|
137
|
+
"Failed to restore original theme '%s'",
|
|
138
|
+
self._original_theme,
|
|
139
|
+
exc_info=True,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
|
|
143
|
+
"""Commit the selected theme.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
event: The option selected event.
|
|
147
|
+
"""
|
|
148
|
+
name = event.option.id
|
|
149
|
+
if name is not None and name in theme.ThemeEntry.REGISTRY:
|
|
150
|
+
self.dismiss(name)
|
|
151
|
+
else:
|
|
152
|
+
logger.warning("Selected theme '%s' is no longer available", name)
|
|
153
|
+
self.dismiss(None)
|
|
154
|
+
|
|
155
|
+
def action_cancel(self) -> None:
|
|
156
|
+
"""Restore the original theme and dismiss."""
|
|
157
|
+
self.app.theme = self._original_theme
|
|
158
|
+
self.dismiss(None)
|