scc-cli 1.5.3__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 +311 -0
- scc_cli/cli_common.py +190 -0
- scc_cli/cli_helpers.py +244 -0
- scc_cli/commands/__init__.py +20 -0
- scc_cli/commands/admin.py +708 -0
- scc_cli/commands/audit.py +246 -0
- scc_cli/commands/config.py +528 -0
- scc_cli/commands/exceptions.py +696 -0
- scc_cli/commands/init.py +272 -0
- scc_cli/commands/launch/__init__.py +73 -0
- scc_cli/commands/launch/app.py +1247 -0
- scc_cli/commands/launch/render.py +309 -0
- scc_cli/commands/launch/sandbox.py +135 -0
- scc_cli/commands/launch/workspace.py +339 -0
- scc_cli/commands/org/__init__.py +49 -0
- scc_cli/commands/org/_builders.py +264 -0
- scc_cli/commands/org/app.py +41 -0
- scc_cli/commands/org/import_cmd.py +267 -0
- scc_cli/commands/org/init_cmd.py +269 -0
- scc_cli/commands/org/schema_cmd.py +76 -0
- scc_cli/commands/org/status_cmd.py +157 -0
- scc_cli/commands/org/update_cmd.py +330 -0
- scc_cli/commands/org/validate_cmd.py +138 -0
- scc_cli/commands/support.py +323 -0
- scc_cli/commands/team.py +910 -0
- scc_cli/commands/worktree/__init__.py +72 -0
- scc_cli/commands/worktree/_helpers.py +57 -0
- scc_cli/commands/worktree/app.py +170 -0
- scc_cli/commands/worktree/container_commands.py +385 -0
- scc_cli/commands/worktree/context_commands.py +61 -0
- scc_cli/commands/worktree/session_commands.py +128 -0
- scc_cli/commands/worktree/worktree_commands.py +734 -0
- scc_cli/config.py +647 -0
- scc_cli/confirm.py +20 -0
- scc_cli/console.py +562 -0
- scc_cli/contexts.py +394 -0
- scc_cli/core/__init__.py +68 -0
- scc_cli/core/constants.py +101 -0
- scc_cli/core/errors.py +297 -0
- scc_cli/core/exit_codes.py +91 -0
- scc_cli/core/workspace.py +57 -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 +467 -0
- scc_cli/docker/credentials.py +726 -0
- scc_cli/docker/launch.py +595 -0
- scc_cli/doctor/__init__.py +105 -0
- scc_cli/doctor/checks/__init__.py +166 -0
- scc_cli/doctor/checks/cache.py +314 -0
- scc_cli/doctor/checks/config.py +107 -0
- scc_cli/doctor/checks/environment.py +182 -0
- scc_cli/doctor/checks/json_helpers.py +157 -0
- scc_cli/doctor/checks/organization.py +264 -0
- scc_cli/doctor/checks/worktree.py +278 -0
- scc_cli/doctor/render.py +365 -0
- scc_cli/doctor/types.py +66 -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/git.py +84 -0
- scc_cli/json_command.py +166 -0
- scc_cli/json_output.py +159 -0
- scc_cli/kinds.py +65 -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 +846 -0
- scc_cli/marketplace/normalize.py +548 -0
- scc_cli/marketplace/render.py +281 -0
- scc_cli/marketplace/resolve.py +459 -0
- scc_cli/marketplace/schema.py +506 -0
- scc_cli/marketplace/sync.py +279 -0
- scc_cli/marketplace/team_cache.py +195 -0
- scc_cli/marketplace/team_fetch.py +689 -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/services/__init__.py +1 -0
- scc_cli/services/git/__init__.py +79 -0
- scc_cli/services/git/branch.py +151 -0
- scc_cli/services/git/core.py +216 -0
- scc_cli/services/git/hooks.py +108 -0
- scc_cli/services/git/worktree.py +444 -0
- scc_cli/services/workspace/__init__.py +36 -0
- scc_cli/services/workspace/resolver.py +223 -0
- scc_cli/services/workspace/suspicious.py +200 -0
- scc_cli/sessions.py +425 -0
- scc_cli/setup.py +589 -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 +383 -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 +154 -0
- scc_cli/ui/branding.py +68 -0
- scc_cli/ui/chrome.py +401 -0
- scc_cli/ui/dashboard/__init__.py +62 -0
- scc_cli/ui/dashboard/_dashboard.py +794 -0
- scc_cli/ui/dashboard/loaders.py +452 -0
- scc_cli/ui/dashboard/models.py +185 -0
- scc_cli/ui/dashboard/orchestrator.py +735 -0
- scc_cli/ui/formatters.py +444 -0
- scc_cli/ui/gate.py +350 -0
- scc_cli/ui/git_interactive.py +869 -0
- scc_cli/ui/git_render.py +176 -0
- scc_cli/ui/help.py +157 -0
- scc_cli/ui/keys.py +615 -0
- scc_cli/ui/list_screen.py +437 -0
- scc_cli/ui/picker.py +763 -0
- scc_cli/ui/prompts.py +201 -0
- scc_cli/ui/quick_resume.py +116 -0
- scc_cli/ui/wizard.py +576 -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 +114 -0
- scc_cli/utils/ttl.py +376 -0
- scc_cli/validate.py +455 -0
- scc_cli-1.5.3.dist-info/METADATA +401 -0
- scc_cli-1.5.3.dist-info/RECORD +153 -0
- scc_cli-1.5.3.dist-info/WHEEL +4 -0
- scc_cli-1.5.3.dist-info/entry_points.txt +2 -0
- scc_cli-1.5.3.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,154 @@
|
|
|
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
|
+
# Git Interactive: User-facing workflows with console output
|
|
55
|
+
from .git_interactive import (
|
|
56
|
+
check_branch_safety,
|
|
57
|
+
cleanup_worktree,
|
|
58
|
+
clone_repo,
|
|
59
|
+
create_worktree,
|
|
60
|
+
install_dependencies,
|
|
61
|
+
install_hooks,
|
|
62
|
+
list_worktrees,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Git Rendering: Pure display functions for git data
|
|
66
|
+
from .git_render import (
|
|
67
|
+
format_git_status,
|
|
68
|
+
render_worktrees,
|
|
69
|
+
render_worktrees_table,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Help: Mode-aware help overlay (user-facing)
|
|
73
|
+
from .help import (
|
|
74
|
+
HelpMode,
|
|
75
|
+
show_help_overlay,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# =============================================================================
|
|
79
|
+
# Tier 2: Advanced building blocks (supported, but may evolve)
|
|
80
|
+
# =============================================================================
|
|
81
|
+
# ListScreen: Core navigation engine
|
|
82
|
+
from .list_screen import (
|
|
83
|
+
ListItem,
|
|
84
|
+
ListMode,
|
|
85
|
+
ListScreen,
|
|
86
|
+
ListState,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Pickers: Domain-specific selection workflows
|
|
90
|
+
from .picker import (
|
|
91
|
+
TeamSwitchRequested,
|
|
92
|
+
pick_container,
|
|
93
|
+
pick_containers,
|
|
94
|
+
pick_context,
|
|
95
|
+
pick_session,
|
|
96
|
+
pick_team,
|
|
97
|
+
pick_worktree,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Prompts: Simple Rich-based user input utilities
|
|
101
|
+
from .prompts import (
|
|
102
|
+
prompt_custom_workspace,
|
|
103
|
+
prompt_repo_url,
|
|
104
|
+
render_error,
|
|
105
|
+
select_session,
|
|
106
|
+
select_team,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# =============================================================================
|
|
110
|
+
# Package metadata
|
|
111
|
+
# =============================================================================
|
|
112
|
+
|
|
113
|
+
__version__ = "0.1.0"
|
|
114
|
+
|
|
115
|
+
__all__ = [
|
|
116
|
+
# Tier 1: High-level API
|
|
117
|
+
"InteractivityContext",
|
|
118
|
+
"InteractivityMode",
|
|
119
|
+
"is_interactive_allowed",
|
|
120
|
+
"require_selection_or_prompt",
|
|
121
|
+
"TeamSwitchRequested",
|
|
122
|
+
"pick_container",
|
|
123
|
+
"pick_containers",
|
|
124
|
+
"pick_context",
|
|
125
|
+
"pick_session",
|
|
126
|
+
"pick_team",
|
|
127
|
+
"pick_worktree",
|
|
128
|
+
"run_dashboard",
|
|
129
|
+
"HelpMode",
|
|
130
|
+
"show_help_overlay",
|
|
131
|
+
# Tier 2: Advanced building blocks
|
|
132
|
+
"ListItem",
|
|
133
|
+
"ListMode",
|
|
134
|
+
"ListScreen",
|
|
135
|
+
"ListState",
|
|
136
|
+
# Prompts: Simple Rich-based user input utilities
|
|
137
|
+
"prompt_custom_workspace",
|
|
138
|
+
"prompt_repo_url",
|
|
139
|
+
"render_error",
|
|
140
|
+
"select_session",
|
|
141
|
+
"select_team",
|
|
142
|
+
# Git Rendering: Pure display functions for git data
|
|
143
|
+
"format_git_status",
|
|
144
|
+
"render_worktrees",
|
|
145
|
+
"render_worktrees_table",
|
|
146
|
+
# Git Interactive: User-facing workflows with console output
|
|
147
|
+
"check_branch_safety",
|
|
148
|
+
"cleanup_worktree",
|
|
149
|
+
"clone_repo",
|
|
150
|
+
"create_worktree",
|
|
151
|
+
"install_dependencies",
|
|
152
|
+
"install_hooks",
|
|
153
|
+
"list_worktrees",
|
|
154
|
+
]
|
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"
|