scc-cli 1.4.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.
Potentially problematic release.
This version of scc-cli might be problematic. Click here for more details.
- scc_cli/__init__.py +15 -0
- scc_cli/audit/__init__.py +37 -0
- scc_cli/audit/parser.py +191 -0
- scc_cli/audit/reader.py +180 -0
- scc_cli/auth.py +145 -0
- scc_cli/claude_adapter.py +485 -0
- scc_cli/cli.py +259 -0
- scc_cli/cli_admin.py +683 -0
- scc_cli/cli_audit.py +245 -0
- scc_cli/cli_common.py +166 -0
- scc_cli/cli_config.py +527 -0
- scc_cli/cli_exceptions.py +705 -0
- scc_cli/cli_helpers.py +244 -0
- scc_cli/cli_init.py +272 -0
- scc_cli/cli_launch.py +1400 -0
- scc_cli/cli_org.py +1433 -0
- scc_cli/cli_support.py +322 -0
- scc_cli/cli_team.py +858 -0
- scc_cli/cli_worktree.py +865 -0
- scc_cli/config.py +583 -0
- scc_cli/console.py +562 -0
- scc_cli/constants.py +79 -0
- scc_cli/contexts.py +377 -0
- scc_cli/deprecation.py +54 -0
- scc_cli/deps.py +189 -0
- scc_cli/docker/__init__.py +127 -0
- scc_cli/docker/core.py +466 -0
- scc_cli/docker/credentials.py +726 -0
- scc_cli/docker/launch.py +603 -0
- scc_cli/doctor/__init__.py +99 -0
- scc_cli/doctor/checks.py +1082 -0
- scc_cli/doctor/render.py +346 -0
- scc_cli/doctor/types.py +66 -0
- scc_cli/errors.py +288 -0
- scc_cli/evaluation/__init__.py +27 -0
- scc_cli/evaluation/apply_exceptions.py +207 -0
- scc_cli/evaluation/evaluate.py +97 -0
- scc_cli/evaluation/models.py +80 -0
- scc_cli/exit_codes.py +55 -0
- scc_cli/git.py +1405 -0
- scc_cli/json_command.py +166 -0
- scc_cli/json_output.py +96 -0
- scc_cli/kinds.py +62 -0
- scc_cli/marketplace/__init__.py +123 -0
- scc_cli/marketplace/compute.py +377 -0
- scc_cli/marketplace/constants.py +87 -0
- scc_cli/marketplace/managed.py +135 -0
- scc_cli/marketplace/materialize.py +723 -0
- scc_cli/marketplace/normalize.py +548 -0
- scc_cli/marketplace/render.py +238 -0
- scc_cli/marketplace/resolve.py +459 -0
- scc_cli/marketplace/schema.py +502 -0
- scc_cli/marketplace/sync.py +257 -0
- scc_cli/marketplace/team_cache.py +195 -0
- scc_cli/marketplace/team_fetch.py +688 -0
- scc_cli/marketplace/trust.py +244 -0
- scc_cli/models/__init__.py +41 -0
- scc_cli/models/exceptions.py +273 -0
- scc_cli/models/plugin_audit.py +434 -0
- scc_cli/org_templates.py +269 -0
- scc_cli/output_mode.py +167 -0
- scc_cli/panels.py +113 -0
- scc_cli/platform.py +350 -0
- scc_cli/profiles.py +1034 -0
- scc_cli/remote.py +443 -0
- scc_cli/schemas/__init__.py +1 -0
- scc_cli/schemas/org-v1.schema.json +456 -0
- scc_cli/schemas/team-config.v1.schema.json +163 -0
- scc_cli/sessions.py +425 -0
- scc_cli/setup.py +582 -0
- scc_cli/source_resolver.py +470 -0
- scc_cli/stats.py +378 -0
- scc_cli/stores/__init__.py +13 -0
- scc_cli/stores/exception_store.py +251 -0
- scc_cli/subprocess_utils.py +88 -0
- scc_cli/teams.py +339 -0
- scc_cli/templates/__init__.py +2 -0
- scc_cli/templates/org/__init__.py +0 -0
- scc_cli/templates/org/minimal.json +19 -0
- scc_cli/templates/org/reference.json +74 -0
- scc_cli/templates/org/strict.json +38 -0
- scc_cli/templates/org/teams.json +42 -0
- scc_cli/templates/statusline.sh +75 -0
- scc_cli/theme.py +348 -0
- scc_cli/ui/__init__.py +124 -0
- scc_cli/ui/branding.py +68 -0
- scc_cli/ui/chrome.py +395 -0
- scc_cli/ui/dashboard/__init__.py +62 -0
- scc_cli/ui/dashboard/_dashboard.py +669 -0
- scc_cli/ui/dashboard/loaders.py +369 -0
- scc_cli/ui/dashboard/models.py +184 -0
- scc_cli/ui/dashboard/orchestrator.py +337 -0
- scc_cli/ui/formatters.py +443 -0
- scc_cli/ui/gate.py +350 -0
- scc_cli/ui/help.py +157 -0
- scc_cli/ui/keys.py +521 -0
- scc_cli/ui/list_screen.py +431 -0
- scc_cli/ui/picker.py +700 -0
- scc_cli/ui/prompts.py +200 -0
- scc_cli/ui/wizard.py +490 -0
- scc_cli/update.py +680 -0
- scc_cli/utils/__init__.py +39 -0
- scc_cli/utils/fixit.py +264 -0
- scc_cli/utils/fuzzy.py +124 -0
- scc_cli/utils/locks.py +101 -0
- scc_cli/utils/ttl.py +376 -0
- scc_cli/validate.py +455 -0
- scc_cli-1.4.0.dist-info/METADATA +369 -0
- scc_cli-1.4.0.dist-info/RECORD +112 -0
- scc_cli-1.4.0.dist-info/WHEEL +4 -0
- scc_cli-1.4.0.dist-info/entry_points.txt +2 -0
- scc_cli-1.4.0.dist-info/licenses/LICENSE +21 -0
scc_cli/ui/chrome.py
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""Shared chrome layout rendering for interactive UI components.
|
|
2
|
+
|
|
3
|
+
This module provides the consistent visual wrapper (chrome) around all
|
|
4
|
+
list-based UI components. It handles:
|
|
5
|
+
- Title and subtitle rendering
|
|
6
|
+
- Tab row for dashboard views
|
|
7
|
+
- Search/filter query display
|
|
8
|
+
- Footer hints with keybindings
|
|
9
|
+
- Consistent spacing and styling
|
|
10
|
+
|
|
11
|
+
The chrome pattern ensures visual consistency across pickers, multi-select
|
|
12
|
+
lists, and the dashboard while keeping content rendering separate.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> config = ChromeConfig.for_picker("Select Team", 5)
|
|
16
|
+
>>> chrome = Chrome(config)
|
|
17
|
+
>>> rendered = chrome.render(body_content, search_query="dev")
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
from rich.console import Group
|
|
26
|
+
from rich.panel import Panel
|
|
27
|
+
from rich.text import Text
|
|
28
|
+
|
|
29
|
+
from ..theme import Borders, Indicators
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from rich.console import RenderableType
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class FooterHint:
|
|
37
|
+
"""Single hint displayed in the footer.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
key: The key or key combination (e.g., "↑↓", "Enter", "q").
|
|
41
|
+
action: Description of what the key does (e.g., "navigate", "select").
|
|
42
|
+
dimmed: Whether to show this hint as dimmed/disabled (e.g., standalone mode).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
key: str
|
|
46
|
+
action: str
|
|
47
|
+
dimmed: bool = False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ChromeConfig:
|
|
52
|
+
"""Configuration for the shared chrome layout.
|
|
53
|
+
|
|
54
|
+
Use the factory methods for common configurations:
|
|
55
|
+
- for_picker(): Standard single-select picker
|
|
56
|
+
- for_multi_select(): Multi-select list
|
|
57
|
+
- for_dashboard(): Tabbed dashboard view
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
title: Main title displayed at top.
|
|
61
|
+
subtitle: Secondary text (e.g., item count).
|
|
62
|
+
context_label: Current work context (e.g., "team · repo · worktree").
|
|
63
|
+
show_tabs: Whether to display tab row.
|
|
64
|
+
tabs: List of tab names when show_tabs=True.
|
|
65
|
+
active_tab_index: Index of currently active tab.
|
|
66
|
+
show_search: Whether to show search/filter row.
|
|
67
|
+
footer_hints: List of keybinding hints for footer.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
title: str
|
|
71
|
+
subtitle: str = ""
|
|
72
|
+
context_label: str = ""
|
|
73
|
+
show_tabs: bool = False
|
|
74
|
+
tabs: tuple[str, ...] = ()
|
|
75
|
+
active_tab_index: int = 0
|
|
76
|
+
show_search: bool = True
|
|
77
|
+
footer_hints: tuple[FooterHint, ...] = ()
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def for_picker(
|
|
81
|
+
cls,
|
|
82
|
+
title: str,
|
|
83
|
+
subtitle: str | None = None,
|
|
84
|
+
*,
|
|
85
|
+
item_count: int | None = None,
|
|
86
|
+
standalone: bool = False,
|
|
87
|
+
) -> ChromeConfig:
|
|
88
|
+
"""Create standard config for single-select pickers.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
title: Picker title (e.g., "Select Team").
|
|
92
|
+
subtitle: Optional subtitle text. If not provided and item_count is,
|
|
93
|
+
generates "{item_count} available".
|
|
94
|
+
item_count: Deprecated, use subtitle instead. Number of available items.
|
|
95
|
+
standalone: If True, dim the "t teams" hint (not available without org).
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
ChromeConfig with standard picker hints.
|
|
99
|
+
"""
|
|
100
|
+
if subtitle is None and item_count is not None:
|
|
101
|
+
subtitle = f"{item_count} available"
|
|
102
|
+
return cls(
|
|
103
|
+
title=title,
|
|
104
|
+
subtitle=subtitle or "",
|
|
105
|
+
show_tabs=False,
|
|
106
|
+
show_search=True,
|
|
107
|
+
footer_hints=(
|
|
108
|
+
FooterHint("↑↓", "navigate"),
|
|
109
|
+
FooterHint("Enter", "select"),
|
|
110
|
+
FooterHint("Esc", "back"),
|
|
111
|
+
FooterHint("q", "quit"),
|
|
112
|
+
FooterHint("t", "teams", dimmed=standalone),
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def for_multi_select(cls, title: str, selected: int, total: int) -> ChromeConfig:
|
|
118
|
+
"""Create standard config for multi-select lists.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
title: List title (e.g., "Stop Containers").
|
|
122
|
+
selected: Number of currently selected items.
|
|
123
|
+
total: Total number of items.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
ChromeConfig with multi-select hints.
|
|
127
|
+
"""
|
|
128
|
+
return cls(
|
|
129
|
+
title=title,
|
|
130
|
+
subtitle=f"{selected} of {total} selected",
|
|
131
|
+
show_tabs=False,
|
|
132
|
+
show_search=True,
|
|
133
|
+
footer_hints=(
|
|
134
|
+
FooterHint("↑↓", "navigate"),
|
|
135
|
+
FooterHint("Space", "toggle"),
|
|
136
|
+
FooterHint("a", "toggle all"),
|
|
137
|
+
FooterHint("Enter", "confirm"),
|
|
138
|
+
FooterHint("Esc", "back"),
|
|
139
|
+
FooterHint("q", "quit"),
|
|
140
|
+
FooterHint("t", "teams"),
|
|
141
|
+
),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def for_quick_resume(
|
|
146
|
+
cls, title: str, subtitle: str | None = None, *, standalone: bool = False
|
|
147
|
+
) -> ChromeConfig:
|
|
148
|
+
"""Create config for Quick Resume picker with consistent key hints.
|
|
149
|
+
|
|
150
|
+
The Quick Resume picker follows the standard TUI key contract:
|
|
151
|
+
- Enter: Resume the selected context
|
|
152
|
+
- n: Explicitly start a new session (skip resume)
|
|
153
|
+
- Esc: Back/dismiss (cancel wizard from this screen)
|
|
154
|
+
- q: Quit app
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
title: Picker title (typically "Quick Resume").
|
|
158
|
+
subtitle: Optional subtitle (defaults to hint about n/Esc).
|
|
159
|
+
standalone: If True, dim the "t teams" hint (not available without org).
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
ChromeConfig with Quick Resume-specific hints.
|
|
163
|
+
"""
|
|
164
|
+
return cls(
|
|
165
|
+
title=title,
|
|
166
|
+
subtitle=subtitle or "n for new session · Esc to go back",
|
|
167
|
+
show_tabs=False,
|
|
168
|
+
show_search=True,
|
|
169
|
+
footer_hints=(
|
|
170
|
+
FooterHint("↑↓", "navigate"),
|
|
171
|
+
FooterHint("Enter", "resume"),
|
|
172
|
+
FooterHint("n", "new session"),
|
|
173
|
+
FooterHint("Esc", "back"),
|
|
174
|
+
FooterHint("q", "quit"),
|
|
175
|
+
FooterHint("t", "teams", dimmed=standalone),
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def for_dashboard(
|
|
181
|
+
cls,
|
|
182
|
+
tabs: list[str],
|
|
183
|
+
active: int,
|
|
184
|
+
*,
|
|
185
|
+
standalone: bool = False,
|
|
186
|
+
details_open: bool = False,
|
|
187
|
+
custom_hints: tuple[FooterHint, ...] | None = None,
|
|
188
|
+
) -> ChromeConfig:
|
|
189
|
+
"""Create standard config for dashboard view.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
tabs: List of tab names.
|
|
193
|
+
active: Index of active tab (0-based).
|
|
194
|
+
standalone: If True, dim the "t teams" hint (not available without org).
|
|
195
|
+
details_open: If True, show "Esc close" instead of "Enter details".
|
|
196
|
+
custom_hints: Optional custom footer hints to override defaults.
|
|
197
|
+
When provided, these hints are used instead of the standard set.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
ChromeConfig with dashboard hints.
|
|
201
|
+
"""
|
|
202
|
+
# Use custom hints if provided
|
|
203
|
+
if custom_hints is not None:
|
|
204
|
+
footer_hints = custom_hints
|
|
205
|
+
# Otherwise fall back to standard hints based on details state
|
|
206
|
+
elif details_open:
|
|
207
|
+
footer_hints = (
|
|
208
|
+
FooterHint("↑↓", "navigate"),
|
|
209
|
+
FooterHint("Esc", "close"),
|
|
210
|
+
FooterHint("Tab", "switch tab"),
|
|
211
|
+
FooterHint("t", "teams", dimmed=standalone),
|
|
212
|
+
FooterHint("q", "quit"),
|
|
213
|
+
FooterHint("?", "help"),
|
|
214
|
+
)
|
|
215
|
+
else:
|
|
216
|
+
footer_hints = (
|
|
217
|
+
FooterHint("↑↓", "navigate"),
|
|
218
|
+
FooterHint("Tab", "switch tab"),
|
|
219
|
+
FooterHint("Enter", "details"),
|
|
220
|
+
FooterHint("t", "teams", dimmed=standalone),
|
|
221
|
+
FooterHint("q", "quit"),
|
|
222
|
+
FooterHint("?", "help"),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return cls(
|
|
226
|
+
title="SCC Dashboard",
|
|
227
|
+
show_tabs=True,
|
|
228
|
+
tabs=tuple(tabs),
|
|
229
|
+
active_tab_index=active,
|
|
230
|
+
show_search=True,
|
|
231
|
+
footer_hints=footer_hints,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
def with_context(self, context_label: str) -> ChromeConfig:
|
|
235
|
+
"""Create a new config with context label added.
|
|
236
|
+
|
|
237
|
+
This is useful for adding current work context (team/repo/worktree)
|
|
238
|
+
to any existing chrome configuration.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
context_label: The context label (e.g., "platform · api · main").
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
New ChromeConfig with context_label set.
|
|
245
|
+
"""
|
|
246
|
+
return ChromeConfig(
|
|
247
|
+
title=self.title,
|
|
248
|
+
subtitle=self.subtitle,
|
|
249
|
+
context_label=context_label,
|
|
250
|
+
show_tabs=self.show_tabs,
|
|
251
|
+
tabs=self.tabs,
|
|
252
|
+
active_tab_index=self.active_tab_index,
|
|
253
|
+
show_search=self.show_search,
|
|
254
|
+
footer_hints=self.footer_hints,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class Chrome:
|
|
259
|
+
"""Renderer for the shared chrome layout.
|
|
260
|
+
|
|
261
|
+
Chrome wraps content in a consistent visual frame with title,
|
|
262
|
+
tabs, search, and footer hints.
|
|
263
|
+
|
|
264
|
+
Attributes:
|
|
265
|
+
config: The ChromeConfig defining layout options.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
def __init__(self, config: ChromeConfig) -> None:
|
|
269
|
+
"""Initialize chrome renderer.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
config: Layout configuration.
|
|
273
|
+
"""
|
|
274
|
+
self.config = config
|
|
275
|
+
|
|
276
|
+
def render(
|
|
277
|
+
self,
|
|
278
|
+
body: RenderableType,
|
|
279
|
+
*,
|
|
280
|
+
search_query: str = "",
|
|
281
|
+
) -> RenderableType:
|
|
282
|
+
"""Render complete chrome with body content.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
body: The main content to display inside chrome.
|
|
286
|
+
search_query: Current filter/search query.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
A Rich renderable combining all chrome elements.
|
|
290
|
+
"""
|
|
291
|
+
elements: list[RenderableType] = []
|
|
292
|
+
|
|
293
|
+
# Tabs row (if enabled)
|
|
294
|
+
if self.config.show_tabs:
|
|
295
|
+
elements.append(self._render_tabs())
|
|
296
|
+
|
|
297
|
+
# Search row (if enabled and has query)
|
|
298
|
+
if self.config.show_search:
|
|
299
|
+
elements.append(self._render_search(search_query))
|
|
300
|
+
|
|
301
|
+
# Body content
|
|
302
|
+
elements.append(body)
|
|
303
|
+
|
|
304
|
+
# Footer hints
|
|
305
|
+
if self.config.footer_hints:
|
|
306
|
+
elements.append(self._render_footer())
|
|
307
|
+
|
|
308
|
+
# Combine into panel with title
|
|
309
|
+
title = self._build_title()
|
|
310
|
+
return Panel(
|
|
311
|
+
Group(*elements),
|
|
312
|
+
title=title,
|
|
313
|
+
title_align="left",
|
|
314
|
+
border_style="blue",
|
|
315
|
+
padding=(0, 1),
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def _build_title(self) -> str:
|
|
319
|
+
"""Build the panel title string.
|
|
320
|
+
|
|
321
|
+
Format: "Title │ context_label │ subtitle"
|
|
322
|
+
- If only subtitle: "Title │ subtitle"
|
|
323
|
+
- If only context: "Title │ context_label"
|
|
324
|
+
- If neither: "Title"
|
|
325
|
+
"""
|
|
326
|
+
parts = [self.config.title]
|
|
327
|
+
if self.config.context_label:
|
|
328
|
+
parts.append(self.config.context_label)
|
|
329
|
+
if self.config.subtitle:
|
|
330
|
+
parts.append(self.config.subtitle)
|
|
331
|
+
sep = Indicators.get("VERTICAL_LINE")
|
|
332
|
+
return f" {sep} ".join(parts)
|
|
333
|
+
|
|
334
|
+
def _render_tabs(self) -> Text:
|
|
335
|
+
"""Render the tab row."""
|
|
336
|
+
text = Text()
|
|
337
|
+
for i, tab in enumerate(self.config.tabs):
|
|
338
|
+
if i > 0:
|
|
339
|
+
text.append(" ")
|
|
340
|
+
if i == self.config.active_tab_index:
|
|
341
|
+
text.append(f"[{tab}]", style="bold cyan")
|
|
342
|
+
else:
|
|
343
|
+
text.append(f" {tab} ", style="dim")
|
|
344
|
+
text.append("\n")
|
|
345
|
+
return text
|
|
346
|
+
|
|
347
|
+
def _render_search(self, query: str) -> Text:
|
|
348
|
+
"""Render the search/filter row."""
|
|
349
|
+
text = Text()
|
|
350
|
+
if query:
|
|
351
|
+
text.append(f"{Indicators.get('SEARCH_ICON')} ", style="dim")
|
|
352
|
+
text.append(query, style="yellow")
|
|
353
|
+
text.append(Indicators.get("TEXT_CURSOR"), style="yellow bold")
|
|
354
|
+
else:
|
|
355
|
+
text.append("Type to filter...", style="dim italic")
|
|
356
|
+
text.append("\n")
|
|
357
|
+
return text
|
|
358
|
+
|
|
359
|
+
def _render_footer(self) -> Text:
|
|
360
|
+
"""Render the footer hints row."""
|
|
361
|
+
text = Text()
|
|
362
|
+
text.append(Borders.FOOTER_SEPARATOR * 40 + "\n", style="dim")
|
|
363
|
+
for i, hint in enumerate(self.config.footer_hints):
|
|
364
|
+
if i > 0:
|
|
365
|
+
text.append(f" {Indicators.get('VERTICAL_LINE')} ", style="dim")
|
|
366
|
+
# Dimmed hints (e.g., teams in standalone mode) show in strike-through dim
|
|
367
|
+
if hint.dimmed:
|
|
368
|
+
text.append(hint.key, style="dim strike")
|
|
369
|
+
text.append(" ", style="dim")
|
|
370
|
+
text.append(hint.action, style="dim strike")
|
|
371
|
+
else:
|
|
372
|
+
text.append(hint.key, style="cyan bold")
|
|
373
|
+
text.append(" ", style="dim")
|
|
374
|
+
text.append(hint.action, style="dim")
|
|
375
|
+
return text
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def render_chrome(
|
|
379
|
+
config: ChromeConfig,
|
|
380
|
+
body: RenderableType,
|
|
381
|
+
*,
|
|
382
|
+
search_query: str = "",
|
|
383
|
+
) -> RenderableType:
|
|
384
|
+
"""Convenience function to render chrome without instantiating Chrome class.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
config: Chrome configuration.
|
|
388
|
+
body: Body content to wrap.
|
|
389
|
+
search_query: Current search/filter query.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Complete rendered chrome with body.
|
|
393
|
+
"""
|
|
394
|
+
chrome = Chrome(config)
|
|
395
|
+
return chrome.render(body, search_query=search_query)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Dashboard package for SCC CLI interactive interface.
|
|
2
|
+
|
|
3
|
+
This package provides the tabbed dashboard interface shown when running
|
|
4
|
+
`scc` with no arguments in an interactive terminal.
|
|
5
|
+
|
|
6
|
+
Public API:
|
|
7
|
+
run_dashboard: Entry point for the dashboard UI
|
|
8
|
+
Dashboard: Main dashboard class for direct instantiation
|
|
9
|
+
DashboardTab: Enum of available tabs
|
|
10
|
+
DashboardState: State management for the dashboard
|
|
11
|
+
TabData: Data container for individual tabs
|
|
12
|
+
TAB_ORDER: Canonical ordering of tabs
|
|
13
|
+
|
|
14
|
+
Module structure:
|
|
15
|
+
models.py: Data models (DashboardTab, TabData, DashboardState)
|
|
16
|
+
loaders.py: Tab data loading functions
|
|
17
|
+
_dashboard.py: Dashboard class implementation
|
|
18
|
+
orchestrator.py: Entry point and flow handlers
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
>>> from scc_cli.ui.dashboard import run_dashboard
|
|
22
|
+
>>> run_dashboard() # Interactive dashboard
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from .loaders import (
|
|
26
|
+
_load_all_tab_data,
|
|
27
|
+
_load_containers_tab_data,
|
|
28
|
+
_load_sessions_tab_data,
|
|
29
|
+
_load_status_tab_data,
|
|
30
|
+
_load_worktrees_tab_data,
|
|
31
|
+
)
|
|
32
|
+
from .models import TAB_ORDER, DashboardState, DashboardTab, TabData
|
|
33
|
+
from .orchestrator import _prepare_for_nested_ui, run_dashboard
|
|
34
|
+
|
|
35
|
+
# Lazy import for Dashboard to avoid circular imports
|
|
36
|
+
# (Dashboard depends on models, but models doesn't depend on Dashboard)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def __getattr__(name: str) -> object:
|
|
40
|
+
"""Lazy import for Dashboard class."""
|
|
41
|
+
if name == "Dashboard":
|
|
42
|
+
from ._dashboard import Dashboard
|
|
43
|
+
|
|
44
|
+
return Dashboard
|
|
45
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
46
|
+
raise AttributeError(msg)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"Dashboard",
|
|
51
|
+
"DashboardState",
|
|
52
|
+
"DashboardTab",
|
|
53
|
+
"TAB_ORDER",
|
|
54
|
+
"TabData",
|
|
55
|
+
"_load_all_tab_data",
|
|
56
|
+
"_load_containers_tab_data",
|
|
57
|
+
"_load_sessions_tab_data",
|
|
58
|
+
"_load_status_tab_data",
|
|
59
|
+
"_load_worktrees_tab_data",
|
|
60
|
+
"_prepare_for_nested_ui",
|
|
61
|
+
"run_dashboard",
|
|
62
|
+
]
|