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.

Files changed (113) hide show
  1. scc_cli/__init__.py +15 -0
  2. scc_cli/audit/__init__.py +37 -0
  3. scc_cli/audit/parser.py +191 -0
  4. scc_cli/audit/reader.py +180 -0
  5. scc_cli/auth.py +145 -0
  6. scc_cli/claude_adapter.py +485 -0
  7. scc_cli/cli.py +259 -0
  8. scc_cli/cli_admin.py +706 -0
  9. scc_cli/cli_audit.py +245 -0
  10. scc_cli/cli_common.py +166 -0
  11. scc_cli/cli_config.py +527 -0
  12. scc_cli/cli_exceptions.py +705 -0
  13. scc_cli/cli_helpers.py +244 -0
  14. scc_cli/cli_init.py +272 -0
  15. scc_cli/cli_launch.py +1454 -0
  16. scc_cli/cli_org.py +1428 -0
  17. scc_cli/cli_support.py +322 -0
  18. scc_cli/cli_team.py +892 -0
  19. scc_cli/cli_worktree.py +865 -0
  20. scc_cli/config.py +583 -0
  21. scc_cli/console.py +562 -0
  22. scc_cli/constants.py +79 -0
  23. scc_cli/contexts.py +377 -0
  24. scc_cli/deprecation.py +54 -0
  25. scc_cli/deps.py +189 -0
  26. scc_cli/docker/__init__.py +127 -0
  27. scc_cli/docker/core.py +466 -0
  28. scc_cli/docker/credentials.py +726 -0
  29. scc_cli/docker/launch.py +604 -0
  30. scc_cli/doctor/__init__.py +99 -0
  31. scc_cli/doctor/checks.py +1074 -0
  32. scc_cli/doctor/render.py +346 -0
  33. scc_cli/doctor/types.py +66 -0
  34. scc_cli/errors.py +288 -0
  35. scc_cli/evaluation/__init__.py +27 -0
  36. scc_cli/evaluation/apply_exceptions.py +207 -0
  37. scc_cli/evaluation/evaluate.py +97 -0
  38. scc_cli/evaluation/models.py +80 -0
  39. scc_cli/exit_codes.py +55 -0
  40. scc_cli/git.py +1521 -0
  41. scc_cli/json_command.py +166 -0
  42. scc_cli/json_output.py +96 -0
  43. scc_cli/kinds.py +62 -0
  44. scc_cli/marketplace/__init__.py +123 -0
  45. scc_cli/marketplace/adapter.py +74 -0
  46. scc_cli/marketplace/compute.py +377 -0
  47. scc_cli/marketplace/constants.py +87 -0
  48. scc_cli/marketplace/managed.py +135 -0
  49. scc_cli/marketplace/materialize.py +723 -0
  50. scc_cli/marketplace/normalize.py +548 -0
  51. scc_cli/marketplace/render.py +257 -0
  52. scc_cli/marketplace/resolve.py +459 -0
  53. scc_cli/marketplace/schema.py +506 -0
  54. scc_cli/marketplace/sync.py +260 -0
  55. scc_cli/marketplace/team_cache.py +195 -0
  56. scc_cli/marketplace/team_fetch.py +688 -0
  57. scc_cli/marketplace/trust.py +244 -0
  58. scc_cli/models/__init__.py +41 -0
  59. scc_cli/models/exceptions.py +273 -0
  60. scc_cli/models/plugin_audit.py +434 -0
  61. scc_cli/org_templates.py +269 -0
  62. scc_cli/output_mode.py +167 -0
  63. scc_cli/panels.py +113 -0
  64. scc_cli/platform.py +350 -0
  65. scc_cli/profiles.py +960 -0
  66. scc_cli/remote.py +443 -0
  67. scc_cli/schemas/__init__.py +1 -0
  68. scc_cli/schemas/org-v1.schema.json +456 -0
  69. scc_cli/schemas/team-config.v1.schema.json +163 -0
  70. scc_cli/sessions.py +425 -0
  71. scc_cli/setup.py +588 -0
  72. scc_cli/source_resolver.py +470 -0
  73. scc_cli/stats.py +378 -0
  74. scc_cli/stores/__init__.py +13 -0
  75. scc_cli/stores/exception_store.py +251 -0
  76. scc_cli/subprocess_utils.py +88 -0
  77. scc_cli/teams.py +382 -0
  78. scc_cli/templates/__init__.py +2 -0
  79. scc_cli/templates/org/__init__.py +0 -0
  80. scc_cli/templates/org/minimal.json +19 -0
  81. scc_cli/templates/org/reference.json +74 -0
  82. scc_cli/templates/org/strict.json +38 -0
  83. scc_cli/templates/org/teams.json +42 -0
  84. scc_cli/templates/statusline.sh +75 -0
  85. scc_cli/theme.py +348 -0
  86. scc_cli/ui/__init__.py +124 -0
  87. scc_cli/ui/branding.py +68 -0
  88. scc_cli/ui/chrome.py +395 -0
  89. scc_cli/ui/dashboard/__init__.py +62 -0
  90. scc_cli/ui/dashboard/_dashboard.py +677 -0
  91. scc_cli/ui/dashboard/loaders.py +395 -0
  92. scc_cli/ui/dashboard/models.py +184 -0
  93. scc_cli/ui/dashboard/orchestrator.py +390 -0
  94. scc_cli/ui/formatters.py +443 -0
  95. scc_cli/ui/gate.py +350 -0
  96. scc_cli/ui/help.py +157 -0
  97. scc_cli/ui/keys.py +538 -0
  98. scc_cli/ui/list_screen.py +431 -0
  99. scc_cli/ui/picker.py +700 -0
  100. scc_cli/ui/prompts.py +200 -0
  101. scc_cli/ui/wizard.py +675 -0
  102. scc_cli/update.py +680 -0
  103. scc_cli/utils/__init__.py +39 -0
  104. scc_cli/utils/fixit.py +264 -0
  105. scc_cli/utils/fuzzy.py +124 -0
  106. scc_cli/utils/locks.py +101 -0
  107. scc_cli/utils/ttl.py +376 -0
  108. scc_cli/validate.py +455 -0
  109. scc_cli-1.4.1.dist-info/METADATA +369 -0
  110. scc_cli-1.4.1.dist-info/RECORD +113 -0
  111. scc_cli-1.4.1.dist-info/WHEEL +4 -0
  112. scc_cli-1.4.1.dist-info/entry_points.txt +2 -0
  113. scc_cli-1.4.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,75 @@
1
+ #!/bin/bash
2
+ # SCC Status Line for Claude Code
3
+ # Shows: Model | Git branch/worktree | Lines changed
4
+ #
5
+ # Install: scc statusline --install
6
+ # This script receives JSON from Claude Code via stdin
7
+
8
+ # Read JSON input from stdin
9
+ input=$(cat)
10
+
11
+ # Extract values using jq
12
+ MODEL=$(echo "$input" | jq -r '.model.display_name // "Unknown"')
13
+ CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir // "."')
14
+ PROJECT_DIR=$(echo "$input" | jq -r '.workspace.project_dir // "."')
15
+ LINES_ADDED=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
16
+ LINES_REMOVED=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
17
+
18
+ # Colors
19
+ CYAN="\033[36m"
20
+ GREEN="\033[32m"
21
+ RED="\033[31m"
22
+ MAGENTA="\033[35m"
23
+ YELLOW="\033[33m"
24
+ WHITE="\033[1;37m"
25
+ DIM="\033[2m"
26
+ RESET="\033[0m"
27
+
28
+ # Git information
29
+ GIT_INFO=""
30
+ cd "$CURRENT_DIR" 2>/dev/null || cd "$PROJECT_DIR" 2>/dev/null
31
+
32
+ if git rev-parse --git-dir > /dev/null 2>&1; then
33
+ # Get branch name
34
+ BRANCH=$(git branch --show-current 2>/dev/null)
35
+ if [ -z "$BRANCH" ]; then
36
+ # Detached HEAD - show short SHA
37
+ BRANCH=$(git rev-parse --short HEAD 2>/dev/null || echo "detached")
38
+ fi
39
+
40
+ # Check if we're in a worktree
41
+ GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
42
+
43
+ if [[ "$GIT_DIR" == *".git/worktrees/"* ]]; then
44
+ # In a worktree - show ⎇ icon
45
+ WORKTREE_NAME=$(basename "$(dirname "$GIT_DIR")" 2>/dev/null)
46
+ GIT_INFO="${MAGENTA}⎇ ${WORKTREE_NAME}${RESET}:${CYAN}${BRANCH}${RESET}"
47
+ else
48
+ # Regular repo - show 🌿 icon
49
+ GIT_INFO="${CYAN}🌿 ${BRANCH}${RESET}"
50
+ fi
51
+
52
+ # Check for uncommitted changes
53
+ if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
54
+ GIT_INFO="${GIT_INFO}${YELLOW}*${RESET}"
55
+ fi
56
+ fi
57
+
58
+ # Lines changed (only show if any changes made)
59
+ LINES_INFO=""
60
+ if [ "$LINES_ADDED" -gt 0 ] || [ "$LINES_REMOVED" -gt 0 ]; then
61
+ LINES_INFO=" ${DIM}|${RESET} ${GREEN}+${LINES_ADDED}${RESET} ${RED}-${LINES_REMOVED}${RESET}"
62
+ fi
63
+
64
+ # Build the status line
65
+ # Format: [Model] 🌿 branch* | +156 -23
66
+ OUTPUT="${WHITE}[${MODEL}]${RESET}"
67
+
68
+ if [ -n "$GIT_INFO" ]; then
69
+ OUTPUT="${OUTPUT} ${GIT_INFO}"
70
+ fi
71
+
72
+ OUTPUT="${OUTPUT}${LINES_INFO}"
73
+
74
+ # Output (printf handles escape codes)
75
+ printf "%b\n" "$OUTPUT"
scc_cli/theme.py ADDED
@@ -0,0 +1,348 @@
1
+ """Design tokens and theme configuration for SCC CLI.
2
+
3
+ This module is the SINGLE SOURCE OF TRUTH for all visual constants.
4
+ It must remain a LEAF MODULE with no imports from dashboard, list_screen,
5
+ or other UI modules to prevent circular dependencies.
6
+
7
+ Contains:
8
+ - Colors: Semantic color names for panels, text, and borders
9
+ - Borders: Panel border style mappings (derived from Colors)
10
+ - Indicators: Status symbols with ASCII fallbacks
11
+ - Spinners: Contextual spinner names for different operations
12
+ - get_scc_theme(): Lazy-loaded Rich Theme for semantic style names
13
+
14
+ Usage:
15
+ from scc_cli.theme import Colors, Borders, Indicators, get_scc_theme
16
+
17
+ # Use semantic names instead of hardcoded colors:
18
+ border_style=Borders.PANEL_SUCCESS # instead of "green"
19
+
20
+ # Use indicators with fallbacks:
21
+ symbol = Indicators.get("PASS") # returns "✓" or "OK"
22
+
23
+ # Apply theme to console:
24
+ console = Console(theme=get_scc_theme())
25
+ console.print("[scc.success]✓ Passed[/scc.success]")
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import os
31
+ import sys
32
+ from typing import TYPE_CHECKING
33
+
34
+ if TYPE_CHECKING:
35
+ from typing import TextIO
36
+
37
+ from rich.theme import Theme
38
+
39
+
40
+ # ═══════════════════════════════════════════════════════════════════════════════
41
+ # Unicode Detection (internal, no external dependencies)
42
+ # ═══════════════════════════════════════════════════════════════════════════════
43
+ # NOTE: This logic is kept in sync with console.py's _supports_unicode_for_stream.
44
+ # Duplicated here to keep theme.py a leaf module with no UI imports.
45
+
46
+
47
+ def _supports_unicode_for_stream(stream: "TextIO") -> bool:
48
+ """Check if a stream supports Unicode characters.
49
+
50
+ This is stream-aware (matching _supports_colors_for_stream pattern in console.py)
51
+ to handle cases where different streams have different encodings.
52
+
53
+ Args:
54
+ stream: A file-like object with an 'encoding' attribute.
55
+
56
+ Returns:
57
+ True if UTF-8 encoding is available on the stream.
58
+ """
59
+ encoding = getattr(stream, "encoding", None) or ""
60
+ if encoding.lower() in ("utf-8", "utf8"):
61
+ return True
62
+
63
+ # Check locale environment variables as fallback (LC_ALL > LC_CTYPE > LANG)
64
+ locale_var = (
65
+ os.environ.get("LC_ALL") or os.environ.get("LC_CTYPE") or os.environ.get("LANG", "")
66
+ )
67
+ return "utf-8" in locale_var.lower() or "utf8" in locale_var.lower()
68
+
69
+
70
+ def _supports_unicode() -> bool:
71
+ """Check if stderr supports Unicode characters.
72
+
73
+ IMPORTANT: Defaults to stderr since that's where Rich UI renders.
74
+ This avoids false negatives when stdout is piped (e.g., `scc ... | jq`).
75
+
76
+ For explicit stream checking, use _supports_unicode_for_stream().
77
+
78
+ Returns:
79
+ True if UTF-8 encoding is available on stderr.
80
+ """
81
+ return _supports_unicode_for_stream(sys.stderr)
82
+
83
+
84
+ # Cached at module load time for consistent behavior.
85
+ # IMPORTANT: In UI code, prefer passing `unicode=caps.unicode` explicitly
86
+ # to Indicators.get() and get_brand_header() rather than relying on this default.
87
+ _UNICODE_SUPPORTED: bool = _supports_unicode()
88
+
89
+
90
+ # ═══════════════════════════════════════════════════════════════════════════════
91
+ # Color Definitions
92
+ # ═══════════════════════════════════════════════════════════════════════════════
93
+
94
+
95
+ class Colors:
96
+ """Semantic color names for the SCC CLI.
97
+
98
+ All color values are Rich-compatible color strings.
99
+ Use these instead of hardcoded color names for consistency.
100
+
101
+ Example:
102
+ panel = Panel(content, border_style=Colors.SUCCESS)
103
+ """
104
+
105
+ # Brand colors
106
+ BRAND = "cyan"
107
+ BRAND_BOLD = "bold cyan"
108
+
109
+ # Semantic colors
110
+ SUCCESS = "green"
111
+ SUCCESS_BOLD = "bold green"
112
+ WARNING = "yellow"
113
+ WARNING_BOLD = "bold yellow"
114
+ ERROR = "red"
115
+ ERROR_BOLD = "bold red"
116
+ INFO = "blue"
117
+ INFO_BOLD = "bold blue"
118
+
119
+ # Text colors
120
+ PRIMARY = "white"
121
+ SECONDARY = "dim"
122
+ MUTED = "dim white"
123
+
124
+ # State colors
125
+ RUNNING = "green"
126
+ STOPPED = "dim"
127
+ PAUSED = "yellow"
128
+
129
+
130
+ # ═══════════════════════════════════════════════════════════════════════════════
131
+ # Panel Border Styles
132
+ # ═══════════════════════════════════════════════════════════════════════════════
133
+
134
+
135
+ class Borders:
136
+ """Panel border styles for consistent UI.
137
+
138
+ Maps semantic panel types to their border colors.
139
+ All values derive from Colors to maintain single source of truth.
140
+ Use with Panel(..., border_style=Borders.PANEL_SUCCESS).
141
+
142
+ Example:
143
+ panel = Panel(content, border_style=Borders.PANEL_INFO)
144
+ """
145
+
146
+ # Panel borders (derived from Colors - single source of truth)
147
+ PANEL_INFO = Colors.BRAND # cyan
148
+ PANEL_SUCCESS = Colors.SUCCESS # green
149
+ PANEL_WARNING = Colors.WARNING # yellow
150
+ PANEL_ERROR = Colors.ERROR # red
151
+ PANEL_NEUTRAL = Colors.INFO # blue
152
+ PANEL_MUTED = Colors.SECONDARY # dim
153
+
154
+ # Footer separator character (width computed at render time via console.width)
155
+ FOOTER_SEPARATOR = "─" if _UNICODE_SUPPORTED else "-"
156
+
157
+
158
+ # ═══════════════════════════════════════════════════════════════════════════════
159
+ # Status Indicators
160
+ # ═══════════════════════════════════════════════════════════════════════════════
161
+
162
+
163
+ class Indicators:
164
+ """Status indicator symbols with ASCII fallbacks.
165
+
166
+ Use get() method to automatically select Unicode or ASCII based on
167
+ terminal capabilities detected at module load.
168
+
169
+ Example:
170
+ symbol = Indicators.get("PASS") # "✓" or "OK"
171
+ print(f"{Indicators.get('RUNNING')} Container active")
172
+ """
173
+
174
+ # Mapping of indicator name -> (unicode, ascii)
175
+ _SYMBOLS: dict[str, tuple[str, str]] = {
176
+ # Success/failure
177
+ "PASS": ("✓", "OK"),
178
+ "FAIL": ("✗", "FAIL"),
179
+ "CHECK": ("✓", "[x]"),
180
+ "CROSS": ("✗", "[!]"),
181
+ # Warning/info
182
+ "WARNING": ("!", "!"),
183
+ "INFO": ("i", "i"),
184
+ # Status
185
+ "RUNNING": ("●", "[*]"),
186
+ "STOPPED": ("○", "[ ]"),
187
+ "PAUSED": ("◐", "[~]"),
188
+ # Navigation
189
+ "CURSOR": ("❯", ">"),
190
+ "TEXT_CURSOR": ("▏", "|"),
191
+ "ARROW": ("→", "->"),
192
+ "BULLET": ("•", "*"),
193
+ "SCROLL_UP": ("↑", "^"),
194
+ "SCROLL_DOWN": ("↓", "v"),
195
+ # Progress
196
+ "PENDING": ("⏳", "..."),
197
+ "SPINNER": ("◌", "o"),
198
+ # Layout elements
199
+ "INFO_ICON": ("ℹ", "i"), # Circled info icon for hints
200
+ "SEARCH_ICON": ("🔍", "[?]"), # Search/filter indicator
201
+ "VERTICAL_LINE": ("│", "|"), # Table column separator
202
+ "HORIZONTAL_LINE": ("─", "-"), # Section separator
203
+ }
204
+
205
+ @classmethod
206
+ def get(cls, name: str, *, unicode: bool | None = None) -> str:
207
+ """Get indicator symbol with automatic fallback.
208
+
209
+ Args:
210
+ name: Indicator name (e.g., "PASS", "RUNNING").
211
+ unicode: Override unicode detection. If None, uses module default.
212
+
213
+ Returns:
214
+ Unicode or ASCII symbol based on terminal capabilities.
215
+
216
+ Raises:
217
+ KeyError: If indicator name is not found.
218
+ """
219
+ if name not in cls._SYMBOLS:
220
+ raise KeyError(f"Unknown indicator: {name}")
221
+
222
+ use_unicode = unicode if unicode is not None else _UNICODE_SUPPORTED
223
+ unicode_char, ascii_char = cls._SYMBOLS[name]
224
+ return unicode_char if use_unicode else ascii_char
225
+
226
+ @classmethod
227
+ def has(cls, name: str) -> bool:
228
+ """Check if an indicator name exists.
229
+
230
+ Use this to safely check before calling get() if you want to
231
+ avoid KeyError exceptions.
232
+
233
+ Args:
234
+ name: Indicator name to check.
235
+
236
+ Returns:
237
+ True if the indicator exists, False otherwise.
238
+ """
239
+ return name in cls._SYMBOLS
240
+
241
+ @classmethod
242
+ def all_names(cls) -> list[str]:
243
+ """Get all available indicator names."""
244
+ return list(cls._SYMBOLS.keys())
245
+
246
+
247
+ # ═══════════════════════════════════════════════════════════════════════════════
248
+ # Spinner Names
249
+ # ═══════════════════════════════════════════════════════════════════════════════
250
+
251
+
252
+ class Spinners:
253
+ """Contextual spinner names for different operation types.
254
+
255
+ All values are valid Rich spinner names.
256
+ See: https://rich.readthedocs.io/en/stable/reference/spinner.html
257
+
258
+ Example:
259
+ with console.status("Building...", spinner=Spinners.BUILD):
260
+ build_project()
261
+ """
262
+
263
+ # Default spinner
264
+ DEFAULT = "dots"
265
+
266
+ # Context-specific spinners
267
+ DOCKER = "dots12" # Docker/container operations
268
+ NETWORK = "dots8Bit" # Network/fetch operations
269
+ BUILD = "bouncingBar" # Build/compile operations
270
+ SEARCH = "point" # Search/scan operations
271
+ SETUP = "arc" # Setup/initialization
272
+
273
+
274
+ # ═══════════════════════════════════════════════════════════════════════════════
275
+ # Rich Theme Definition
276
+ # ═══════════════════════════════════════════════════════════════════════════════
277
+
278
+ # Delayed import to keep module load fast when Theme isn't needed
279
+ _theme_instance: Theme | None = None
280
+
281
+
282
+ def get_scc_theme() -> Theme:
283
+ """Get the SCC Rich Theme instance (lazy-loaded).
284
+
285
+ Returns:
286
+ Rich Theme with semantic style names.
287
+
288
+ Example:
289
+ console = Console(theme=get_scc_theme())
290
+ console.print("[scc.success]✓ Passed[/scc.success]")
291
+ """
292
+ global _theme_instance
293
+ if _theme_instance is None:
294
+ from rich.theme import Theme
295
+
296
+ _theme_instance = Theme(
297
+ {
298
+ # Brand
299
+ "scc.brand": Colors.BRAND,
300
+ "scc.brand.bold": Colors.BRAND_BOLD,
301
+ # Semantic
302
+ "scc.success": Colors.SUCCESS,
303
+ "scc.success.bold": Colors.SUCCESS_BOLD,
304
+ "scc.warning": Colors.WARNING,
305
+ "scc.warning.bold": Colors.WARNING_BOLD,
306
+ "scc.error": Colors.ERROR,
307
+ "scc.error.bold": Colors.ERROR_BOLD,
308
+ "scc.info": Colors.INFO,
309
+ "scc.info.bold": Colors.INFO_BOLD,
310
+ # Text
311
+ "scc.primary": Colors.PRIMARY,
312
+ "scc.secondary": Colors.SECONDARY,
313
+ "scc.muted": Colors.MUTED,
314
+ # Status
315
+ "scc.running": Colors.RUNNING,
316
+ "scc.stopped": Colors.STOPPED,
317
+ "scc.paused": Colors.PAUSED,
318
+ }
319
+ )
320
+ return _theme_instance
321
+
322
+
323
+ # ═══════════════════════════════════════════════════════════════════════════════
324
+ # ASCII Art Branding (Optional)
325
+ # ═══════════════════════════════════════════════════════════════════════════════
326
+
327
+
328
+ def get_brand_header(*, unicode: bool | None = None) -> str:
329
+ """Get minimal brand header for --version and doctor output.
330
+
331
+ Args:
332
+ unicode: Override unicode detection. If None, uses module default.
333
+
334
+ Returns:
335
+ Brand header string with proper box-drawing characters.
336
+ """
337
+ use_unicode = unicode if unicode is not None else _UNICODE_SUPPORTED
338
+
339
+ if use_unicode:
340
+ return """\
341
+ ╭───────────────────────────────────────╮
342
+ │ SCC Sandboxed Claude CLI │
343
+ ╰───────────────────────────────────────╯"""
344
+ else:
345
+ return """\
346
+ +---------------------------------------+
347
+ | SCC Sandboxed Claude CLI |
348
+ +---------------------------------------+"""
scc_cli/ui/__init__.py ADDED
@@ -0,0 +1,124 @@
1
+ """SCC Interactive UI Package.
2
+
3
+ Public API for building interactive terminal experiences that share
4
+ consistent chrome, keybindings, and behavior patterns.
5
+
6
+ This package provides:
7
+ - Gate: Interactivity policy enforcement (JSON mode, CI detection, TTY checks)
8
+ - Pickers: High-level selection wrappers for domain objects
9
+ - ListScreen: Core navigation engine for building custom screens
10
+ - Dashboard: Tabbed navigation for the main SCC view
11
+
12
+ Usage Tiers:
13
+
14
+ Tier 1 - High-level (recommended for CLI commands):
15
+ >>> from scc_cli.ui import InteractivityContext, pick_team, TeamSwitchRequested
16
+ >>> ctx = InteractivityContext.create(json_mode=False)
17
+ >>> if ctx.allows_prompt():
18
+ ... try:
19
+ ... team = pick_team(teams)
20
+ ... except TeamSwitchRequested:
21
+ ... # Handle team switch request
22
+ ... pass
23
+
24
+ Tier 2 - Advanced building blocks (supported, but may evolve):
25
+ >>> from scc_cli.ui import ListScreen, ListItem, ListMode
26
+ >>> items = [ListItem(value=x, label=x.name) for x in data]
27
+ >>> screen = ListScreen(items, title="Custom Picker")
28
+ >>> selected = screen.run()
29
+
30
+ Internal modules (not part of public API, may change without notice):
31
+ - chrome: Layout rendering primitives (Chrome, ChromeConfig, FooterHint)
32
+ - keys: Key mapping internals (Action, ActionType, KeyReader)
33
+ Import directly from submodules if needed:
34
+ >>> from scc_cli.ui.keys import ActionType # Internal use only
35
+ >>> from scc_cli.ui.chrome import Chrome # Internal use only
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ # Dashboard: Main tabbed navigation view
41
+ from .dashboard import run_dashboard
42
+
43
+ # =============================================================================
44
+ # Tier 1: High-level API (recommended for most uses)
45
+ # =============================================================================
46
+ # Gate: Interactivity policy enforcement
47
+ from .gate import (
48
+ InteractivityContext,
49
+ InteractivityMode,
50
+ is_interactive_allowed,
51
+ require_selection_or_prompt,
52
+ )
53
+
54
+ # Help: Mode-aware help overlay (user-facing)
55
+ from .help import (
56
+ HelpMode,
57
+ show_help_overlay,
58
+ )
59
+
60
+ # =============================================================================
61
+ # Tier 2: Advanced building blocks (supported, but may evolve)
62
+ # =============================================================================
63
+ # ListScreen: Core navigation engine
64
+ from .list_screen import (
65
+ ListItem,
66
+ ListMode,
67
+ ListScreen,
68
+ ListState,
69
+ )
70
+
71
+ # Pickers: Domain-specific selection workflows
72
+ from .picker import (
73
+ TeamSwitchRequested,
74
+ pick_container,
75
+ pick_containers,
76
+ pick_context,
77
+ pick_session,
78
+ pick_team,
79
+ pick_worktree,
80
+ )
81
+
82
+ # Prompts: Simple Rich-based user input utilities
83
+ from .prompts import (
84
+ prompt_custom_workspace,
85
+ prompt_repo_url,
86
+ render_error,
87
+ select_session,
88
+ select_team,
89
+ )
90
+
91
+ # =============================================================================
92
+ # Package metadata
93
+ # =============================================================================
94
+
95
+ __version__ = "0.1.0"
96
+
97
+ __all__ = [
98
+ # Tier 1: High-level API
99
+ "InteractivityContext",
100
+ "InteractivityMode",
101
+ "is_interactive_allowed",
102
+ "require_selection_or_prompt",
103
+ "TeamSwitchRequested",
104
+ "pick_container",
105
+ "pick_containers",
106
+ "pick_context",
107
+ "pick_session",
108
+ "pick_team",
109
+ "pick_worktree",
110
+ "run_dashboard",
111
+ "HelpMode",
112
+ "show_help_overlay",
113
+ # Tier 2: Advanced building blocks
114
+ "ListItem",
115
+ "ListMode",
116
+ "ListScreen",
117
+ "ListState",
118
+ # Prompts: Simple Rich-based user input utilities
119
+ "prompt_custom_workspace",
120
+ "prompt_repo_url",
121
+ "render_error",
122
+ "select_session",
123
+ "select_team",
124
+ ]
scc_cli/ui/branding.py ADDED
@@ -0,0 +1,68 @@
1
+ """Branding utilities for SCC CLI.
2
+
3
+ This module provides minimal, professional branding elements for CLI output:
4
+ - Version display headers
5
+ - Doctor command headers
6
+ - Unicode-safe fallbacks for all terminals
7
+
8
+ The aesthetic is professional/minimal to suit enterprise team adoption.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from ..platform import supports_unicode
14
+
15
+
16
+ def get_version_header(version: str) -> str:
17
+ """Generate version display header with unicode/ASCII fallback.
18
+
19
+ Args:
20
+ version: The version string to display (e.g., "1.4.0").
21
+
22
+ Returns:
23
+ Formatted header string with box-drawing or ASCII characters.
24
+ """
25
+ # Pad version to ensure consistent width (assumes version <= 10 chars)
26
+ v_padded = f"v{version}".ljust(10)
27
+
28
+ if supports_unicode():
29
+ return (
30
+ "╭───────────────────────────────────────╮\n"
31
+ f"│ [cyan bold]SCC[/cyan bold] Sandboxed Claude CLI [dim]{v_padded}[/dim] │\n"
32
+ "╰───────────────────────────────────────╯"
33
+ )
34
+ else:
35
+ return (
36
+ "+---------------------------------------+\n"
37
+ f"| [cyan bold]SCC[/cyan bold] Sandboxed Claude CLI [dim]{v_padded}[/dim] |\n"
38
+ "+---------------------------------------+"
39
+ )
40
+
41
+
42
+ def get_doctor_header() -> str:
43
+ """Generate doctor command header with unicode/ASCII fallback.
44
+
45
+ Returns:
46
+ Formatted header string for doctor output.
47
+ """
48
+ if supports_unicode():
49
+ return (
50
+ "╭───────────────────────────────────────╮\n"
51
+ "│ [cyan bold]SCC Doctor[/cyan bold] System Health Check │\n"
52
+ "╰───────────────────────────────────────╯"
53
+ )
54
+ else:
55
+ return (
56
+ "+---------------------------------------+\n"
57
+ "| [cyan bold]SCC Doctor[/cyan bold] System Health Check |\n"
58
+ "+---------------------------------------+"
59
+ )
60
+
61
+
62
+ def get_brand_tagline() -> str:
63
+ """Get the brand tagline for SCC.
64
+
65
+ Returns:
66
+ The official tagline string.
67
+ """
68
+ return "Safe development environment manager for Claude Code"