scc-cli 1.4.1__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 +706 -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 +1454 -0
- scc_cli/cli_org.py +1428 -0
- scc_cli/cli_support.py +322 -0
- scc_cli/cli_team.py +892 -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 +604 -0
- scc_cli/doctor/__init__.py +99 -0
- scc_cli/doctor/checks.py +1074 -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 +1521 -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/adapter.py +74 -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 +257 -0
- scc_cli/marketplace/resolve.py +459 -0
- scc_cli/marketplace/schema.py +506 -0
- scc_cli/marketplace/sync.py +260 -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 +960 -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 +588 -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 +382 -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 +677 -0
- scc_cli/ui/dashboard/loaders.py +395 -0
- scc_cli/ui/dashboard/models.py +184 -0
- scc_cli/ui/dashboard/orchestrator.py +390 -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 +538 -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 +675 -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.1.dist-info/METADATA +369 -0
- scc_cli-1.4.1.dist-info/RECORD +113 -0
- scc_cli-1.4.1.dist-info/WHEEL +4 -0
- scc_cli-1.4.1.dist-info/entry_points.txt +2 -0
- scc_cli-1.4.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""Orchestration functions for the dashboard module.
|
|
2
|
+
|
|
3
|
+
This module contains the entry point and flow handlers:
|
|
4
|
+
- run_dashboard: Main entry point for `scc` with no arguments
|
|
5
|
+
- _handle_team_switch: Team picker integration
|
|
6
|
+
- _handle_start_flow: Start wizard integration
|
|
7
|
+
- _handle_session_resume: Session resume logic
|
|
8
|
+
|
|
9
|
+
The orchestrator manages the dashboard lifecycle including intent exceptions
|
|
10
|
+
that exit the Rich Live context before handling nested UI components.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from ...console import get_err_console
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
|
|
22
|
+
from ..keys import (
|
|
23
|
+
RefreshRequested,
|
|
24
|
+
SessionResumeRequested,
|
|
25
|
+
StartRequested,
|
|
26
|
+
StatuslineInstallRequested,
|
|
27
|
+
TeamSwitchRequested,
|
|
28
|
+
)
|
|
29
|
+
from ..list_screen import ListState
|
|
30
|
+
from ._dashboard import Dashboard
|
|
31
|
+
from .loaders import _load_all_tab_data
|
|
32
|
+
from .models import DashboardState, DashboardTab
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_dashboard() -> None:
|
|
36
|
+
"""Run the main SCC dashboard.
|
|
37
|
+
|
|
38
|
+
This is the entry point for `scc` with no arguments in a TTY.
|
|
39
|
+
It loads current resource data and displays the interactive dashboard.
|
|
40
|
+
|
|
41
|
+
Handles intent exceptions by executing the requested flow outside the
|
|
42
|
+
Rich Live context (critical to avoid nested Live conflicts), then
|
|
43
|
+
reloading the dashboard with restored tab state.
|
|
44
|
+
|
|
45
|
+
Intent Exceptions:
|
|
46
|
+
- TeamSwitchRequested: Show team picker, reload with new team
|
|
47
|
+
- StartRequested: Run start wizard, return to source tab with fresh data
|
|
48
|
+
- RefreshRequested: Reload tab data, return to source tab
|
|
49
|
+
"""
|
|
50
|
+
# Track which tab to restore after flow (uses .name for stability)
|
|
51
|
+
restore_tab: str | None = None
|
|
52
|
+
# Toast message to show on next dashboard iteration (e.g., "Start cancelled")
|
|
53
|
+
toast_message: str | None = None
|
|
54
|
+
|
|
55
|
+
while True:
|
|
56
|
+
# Load real data for all tabs
|
|
57
|
+
tabs = _load_all_tab_data()
|
|
58
|
+
|
|
59
|
+
# Determine initial tab (restore previous or default to STATUS)
|
|
60
|
+
initial_tab = DashboardTab.STATUS
|
|
61
|
+
if restore_tab:
|
|
62
|
+
# Find tab by name (stable identifier)
|
|
63
|
+
for tab in DashboardTab:
|
|
64
|
+
if tab.name == restore_tab:
|
|
65
|
+
initial_tab = tab
|
|
66
|
+
break
|
|
67
|
+
restore_tab = None # Clear after use
|
|
68
|
+
|
|
69
|
+
state = DashboardState(
|
|
70
|
+
active_tab=initial_tab,
|
|
71
|
+
tabs=tabs,
|
|
72
|
+
list_state=ListState(items=tabs[initial_tab].items),
|
|
73
|
+
status_message=toast_message, # Show any pending toast
|
|
74
|
+
)
|
|
75
|
+
toast_message = None # Clear after use
|
|
76
|
+
|
|
77
|
+
dashboard = Dashboard(state)
|
|
78
|
+
try:
|
|
79
|
+
dashboard.run()
|
|
80
|
+
break # Normal exit (q or Esc)
|
|
81
|
+
except TeamSwitchRequested:
|
|
82
|
+
# User pressed 't' - show team picker then reload dashboard
|
|
83
|
+
_handle_team_switch()
|
|
84
|
+
# Loop continues to reload dashboard with new team
|
|
85
|
+
|
|
86
|
+
except StartRequested as start_req:
|
|
87
|
+
# User pressed Enter on startable placeholder
|
|
88
|
+
# Execute start flow OUTSIDE Rich Live (critical: avoids nested Live)
|
|
89
|
+
restore_tab = start_req.return_to
|
|
90
|
+
result = _handle_start_flow(start_req.reason)
|
|
91
|
+
|
|
92
|
+
if result is None:
|
|
93
|
+
# User pressed q: quit app entirely
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
if result is False:
|
|
97
|
+
# User pressed Esc: go back to dashboard, show toast
|
|
98
|
+
toast_message = "Start cancelled"
|
|
99
|
+
# Loop continues to reload dashboard with fresh data
|
|
100
|
+
|
|
101
|
+
except RefreshRequested as refresh_req:
|
|
102
|
+
# User pressed 'r' - just reload data
|
|
103
|
+
restore_tab = refresh_req.return_to
|
|
104
|
+
# Loop continues with fresh data (no additional action needed)
|
|
105
|
+
|
|
106
|
+
except SessionResumeRequested as resume_req:
|
|
107
|
+
# User pressed Enter on a session item → resume it
|
|
108
|
+
restore_tab = resume_req.return_to
|
|
109
|
+
success = _handle_session_resume(resume_req.session)
|
|
110
|
+
|
|
111
|
+
if not success:
|
|
112
|
+
# Resume failed (e.g., missing workspace) - show toast
|
|
113
|
+
toast_message = "Session resume failed"
|
|
114
|
+
else:
|
|
115
|
+
# Successfully launched - exit dashboard
|
|
116
|
+
# (container is running, user is now in Claude)
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
except StatuslineInstallRequested as statusline_req:
|
|
120
|
+
# User pressed 'y' on statusline row - install statusline
|
|
121
|
+
restore_tab = statusline_req.return_to
|
|
122
|
+
success = _handle_statusline_install()
|
|
123
|
+
|
|
124
|
+
if success:
|
|
125
|
+
toast_message = "Statusline installed successfully"
|
|
126
|
+
else:
|
|
127
|
+
toast_message = "Statusline installation failed"
|
|
128
|
+
# Loop continues to reload dashboard with fresh data
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _prepare_for_nested_ui(console: Console) -> None:
|
|
132
|
+
"""Prepare terminal state for launching nested UI components.
|
|
133
|
+
|
|
134
|
+
Restores cursor visibility, ensures clean newline, and flushes
|
|
135
|
+
any buffered input to prevent ghost keypresses from Rich Live context.
|
|
136
|
+
|
|
137
|
+
This should be called before launching any interactive picker or wizard
|
|
138
|
+
from the dashboard to ensure clean terminal state.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
console: Rich Console instance for terminal operations.
|
|
142
|
+
"""
|
|
143
|
+
import io
|
|
144
|
+
import sys
|
|
145
|
+
|
|
146
|
+
# Restore cursor (Rich Live may hide it)
|
|
147
|
+
console.show_cursor(True)
|
|
148
|
+
console.print() # Ensure clean newline
|
|
149
|
+
|
|
150
|
+
# Flush buffered input (best-effort, Unix only)
|
|
151
|
+
try:
|
|
152
|
+
import termios
|
|
153
|
+
|
|
154
|
+
termios.tcflush(sys.stdin.fileno(), termios.TCIFLUSH)
|
|
155
|
+
except (
|
|
156
|
+
ModuleNotFoundError, # Windows - no termios module
|
|
157
|
+
OSError, # Redirected stdin, no TTY
|
|
158
|
+
ValueError, # Invalid file descriptor
|
|
159
|
+
TypeError, # Mock stdin without fileno
|
|
160
|
+
io.UnsupportedOperation, # Stdin without fileno support
|
|
161
|
+
):
|
|
162
|
+
pass # Non-Unix or non-TTY environment - safe to ignore
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _handle_team_switch() -> None:
|
|
166
|
+
"""Handle team switch request from dashboard.
|
|
167
|
+
|
|
168
|
+
Shows the team picker and switches team if user selects one.
|
|
169
|
+
"""
|
|
170
|
+
from ... import config, teams
|
|
171
|
+
from ..picker import pick_team
|
|
172
|
+
|
|
173
|
+
console = get_err_console()
|
|
174
|
+
_prepare_for_nested_ui(console)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
# Load config and org config for team list
|
|
178
|
+
cfg = config.load_user_config()
|
|
179
|
+
org_config = config.load_cached_org_config()
|
|
180
|
+
|
|
181
|
+
available_teams = teams.list_teams(cfg, org_config=org_config)
|
|
182
|
+
if not available_teams:
|
|
183
|
+
console.print("[yellow]No teams available[/yellow]")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# Get current team for marking
|
|
187
|
+
current_team = cfg.get("selected_profile")
|
|
188
|
+
|
|
189
|
+
selected = pick_team(
|
|
190
|
+
available_teams,
|
|
191
|
+
current_team=str(current_team) if current_team else None,
|
|
192
|
+
title="Switch Team",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if selected:
|
|
196
|
+
# Update team selection
|
|
197
|
+
team_name = selected.get("name", "")
|
|
198
|
+
cfg["selected_profile"] = team_name
|
|
199
|
+
config.save_user_config(cfg)
|
|
200
|
+
console.print(f"[green]Switched to team: {team_name}[/green]")
|
|
201
|
+
# If cancelled, just return to dashboard
|
|
202
|
+
|
|
203
|
+
except TeamSwitchRequested:
|
|
204
|
+
# Nested team switch (shouldn't happen, but handle gracefully)
|
|
205
|
+
pass
|
|
206
|
+
except Exception as e:
|
|
207
|
+
console.print(f"[red]Error switching team: {e}[/red]")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _handle_start_flow(reason: str) -> bool | None:
|
|
211
|
+
"""Handle start flow request from dashboard.
|
|
212
|
+
|
|
213
|
+
Runs the interactive start wizard and launches a sandbox if user completes it.
|
|
214
|
+
Executes OUTSIDE Rich Live context (the dashboard has already exited
|
|
215
|
+
via the exception unwind before this is called).
|
|
216
|
+
|
|
217
|
+
Three-state return contract:
|
|
218
|
+
- True: Sandbox launched successfully
|
|
219
|
+
- False: User pressed Esc (back to dashboard)
|
|
220
|
+
- None: User pressed q (quit app entirely)
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
reason: Why the start flow was triggered (e.g., "no_containers", "no_sessions").
|
|
224
|
+
Used for logging/analytics and to determine skip_quick_resume.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
True if wizard completed successfully, False if user wants to go back,
|
|
228
|
+
None if user wants to quit entirely.
|
|
229
|
+
"""
|
|
230
|
+
from ...cli_launch import run_start_wizard_flow
|
|
231
|
+
|
|
232
|
+
console = get_err_console()
|
|
233
|
+
_prepare_for_nested_ui(console)
|
|
234
|
+
|
|
235
|
+
# For empty-state starts, skip Quick Resume (user intent is "create new")
|
|
236
|
+
skip_quick_resume = reason in ("no_containers", "no_sessions")
|
|
237
|
+
|
|
238
|
+
# Show contextual message based on reason
|
|
239
|
+
if reason == "no_containers":
|
|
240
|
+
console.print("[dim]Starting a new session...[/dim]")
|
|
241
|
+
elif reason == "no_sessions":
|
|
242
|
+
console.print("[dim]Starting your first session...[/dim]")
|
|
243
|
+
console.print()
|
|
244
|
+
|
|
245
|
+
# Run the wizard with allow_back=True for dashboard context
|
|
246
|
+
# Returns: True (success), False (Esc/back), None (q/quit)
|
|
247
|
+
return run_start_wizard_flow(skip_quick_resume=skip_quick_resume, allow_back=True)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _handle_session_resume(session: dict[str, Any]) -> bool:
|
|
251
|
+
"""Handle session resume request from dashboard.
|
|
252
|
+
|
|
253
|
+
Resumes an existing session by launching the Docker container with
|
|
254
|
+
the stored workspace, team, and branch configuration.
|
|
255
|
+
|
|
256
|
+
This function executes OUTSIDE Rich Live context (the dashboard has
|
|
257
|
+
already exited via the exception unwind before this is called).
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
session: Session dict containing workspace, team, branch, container_name, etc.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
True if session was resumed successfully, False if resume failed
|
|
264
|
+
(e.g., workspace no longer exists).
|
|
265
|
+
"""
|
|
266
|
+
from pathlib import Path
|
|
267
|
+
|
|
268
|
+
from rich.status import Status
|
|
269
|
+
|
|
270
|
+
from ... import config, docker
|
|
271
|
+
from ...cli_launch import (
|
|
272
|
+
_configure_team_settings,
|
|
273
|
+
_launch_sandbox,
|
|
274
|
+
_resolve_mount_and_branch,
|
|
275
|
+
_sync_marketplace_settings,
|
|
276
|
+
_validate_and_resolve_workspace,
|
|
277
|
+
)
|
|
278
|
+
from ...theme import Spinners
|
|
279
|
+
|
|
280
|
+
console = get_err_console()
|
|
281
|
+
_prepare_for_nested_ui(console)
|
|
282
|
+
|
|
283
|
+
# Extract session info
|
|
284
|
+
workspace = session.get("workspace", "")
|
|
285
|
+
team = session.get("team") # May be None for standalone
|
|
286
|
+
session_name = session.get("name")
|
|
287
|
+
branch = session.get("branch")
|
|
288
|
+
|
|
289
|
+
if not workspace:
|
|
290
|
+
console.print("[red]Session has no workspace path[/red]")
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
# Validate workspace still exists
|
|
294
|
+
workspace_path = Path(workspace)
|
|
295
|
+
if not workspace_path.exists():
|
|
296
|
+
console.print(f"[red]Workspace no longer exists: {workspace}[/red]")
|
|
297
|
+
console.print("[dim]The session may have been deleted or moved.[/dim]")
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
# Docker availability check
|
|
302
|
+
with Status("[cyan]Checking Docker...[/cyan]", console=console, spinner=Spinners.DOCKER):
|
|
303
|
+
docker.check_docker_available()
|
|
304
|
+
|
|
305
|
+
# Validate and resolve workspace (we know it exists from earlier check)
|
|
306
|
+
resolved_path = _validate_and_resolve_workspace(str(workspace_path))
|
|
307
|
+
if resolved_path is None:
|
|
308
|
+
console.print("[red]Workspace validation failed[/red]")
|
|
309
|
+
return False
|
|
310
|
+
workspace_path = resolved_path
|
|
311
|
+
|
|
312
|
+
# Configure team settings
|
|
313
|
+
cfg = config.load_config()
|
|
314
|
+
_configure_team_settings(team, cfg)
|
|
315
|
+
|
|
316
|
+
# Sync marketplace settings
|
|
317
|
+
_sync_marketplace_settings(workspace_path, team)
|
|
318
|
+
|
|
319
|
+
# Resolve mount path and branch
|
|
320
|
+
mount_path, current_branch = _resolve_mount_and_branch(workspace_path)
|
|
321
|
+
|
|
322
|
+
# Use session's stored branch if available (more accurate than detected)
|
|
323
|
+
if branch:
|
|
324
|
+
current_branch = branch
|
|
325
|
+
|
|
326
|
+
# Show resume info
|
|
327
|
+
workspace_name = workspace_path.name
|
|
328
|
+
console.print(f"[cyan]Resuming session:[/cyan] {workspace_name}")
|
|
329
|
+
if team:
|
|
330
|
+
console.print(f"[dim]Team: {team}[/dim]")
|
|
331
|
+
if current_branch:
|
|
332
|
+
console.print(f"[dim]Branch: {current_branch}[/dim]")
|
|
333
|
+
console.print()
|
|
334
|
+
|
|
335
|
+
# Launch sandbox with resume flag
|
|
336
|
+
_launch_sandbox(
|
|
337
|
+
workspace_path=workspace_path,
|
|
338
|
+
mount_path=mount_path,
|
|
339
|
+
team=team,
|
|
340
|
+
session_name=session_name,
|
|
341
|
+
current_branch=current_branch,
|
|
342
|
+
should_continue_session=True, # Resume existing container
|
|
343
|
+
fresh=False,
|
|
344
|
+
)
|
|
345
|
+
return True
|
|
346
|
+
|
|
347
|
+
except Exception as e:
|
|
348
|
+
console.print(f"[red]Error resuming session: {e}[/red]")
|
|
349
|
+
return False
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _handle_statusline_install() -> bool:
|
|
353
|
+
"""Handle statusline installation request from dashboard.
|
|
354
|
+
|
|
355
|
+
Installs the Claude Code statusline enhancement using the same logic
|
|
356
|
+
as `scc statusline`. Works cross-platform (Windows, macOS, Linux).
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
True if statusline was installed successfully, False otherwise.
|
|
360
|
+
"""
|
|
361
|
+
from rich.status import Status
|
|
362
|
+
|
|
363
|
+
from ...cli_admin import install_statusline
|
|
364
|
+
from ...theme import Spinners
|
|
365
|
+
|
|
366
|
+
console = get_err_console()
|
|
367
|
+
_prepare_for_nested_ui(console)
|
|
368
|
+
|
|
369
|
+
console.print("[cyan]Installing statusline...[/cyan]")
|
|
370
|
+
console.print()
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
with Status(
|
|
374
|
+
"[cyan]Configuring statusline...[/cyan]",
|
|
375
|
+
console=console,
|
|
376
|
+
spinner=Spinners.DOCKER,
|
|
377
|
+
):
|
|
378
|
+
result = install_statusline()
|
|
379
|
+
|
|
380
|
+
if result:
|
|
381
|
+
console.print("[green]✓ Statusline installed successfully![/green]")
|
|
382
|
+
console.print("[dim]Press any key to continue...[/dim]")
|
|
383
|
+
else:
|
|
384
|
+
console.print("[yellow]Statusline installation completed with warnings[/yellow]")
|
|
385
|
+
|
|
386
|
+
return result
|
|
387
|
+
|
|
388
|
+
except Exception as e:
|
|
389
|
+
console.print(f"[red]Error installing statusline: {e}[/red]")
|
|
390
|
+
return False
|