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,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)