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.

Files changed (153) 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 +311 -0
  8. scc_cli/cli_common.py +190 -0
  9. scc_cli/cli_helpers.py +244 -0
  10. scc_cli/commands/__init__.py +20 -0
  11. scc_cli/commands/admin.py +708 -0
  12. scc_cli/commands/audit.py +246 -0
  13. scc_cli/commands/config.py +528 -0
  14. scc_cli/commands/exceptions.py +696 -0
  15. scc_cli/commands/init.py +272 -0
  16. scc_cli/commands/launch/__init__.py +73 -0
  17. scc_cli/commands/launch/app.py +1247 -0
  18. scc_cli/commands/launch/render.py +309 -0
  19. scc_cli/commands/launch/sandbox.py +135 -0
  20. scc_cli/commands/launch/workspace.py +339 -0
  21. scc_cli/commands/org/__init__.py +49 -0
  22. scc_cli/commands/org/_builders.py +264 -0
  23. scc_cli/commands/org/app.py +41 -0
  24. scc_cli/commands/org/import_cmd.py +267 -0
  25. scc_cli/commands/org/init_cmd.py +269 -0
  26. scc_cli/commands/org/schema_cmd.py +76 -0
  27. scc_cli/commands/org/status_cmd.py +157 -0
  28. scc_cli/commands/org/update_cmd.py +330 -0
  29. scc_cli/commands/org/validate_cmd.py +138 -0
  30. scc_cli/commands/support.py +323 -0
  31. scc_cli/commands/team.py +910 -0
  32. scc_cli/commands/worktree/__init__.py +72 -0
  33. scc_cli/commands/worktree/_helpers.py +57 -0
  34. scc_cli/commands/worktree/app.py +170 -0
  35. scc_cli/commands/worktree/container_commands.py +385 -0
  36. scc_cli/commands/worktree/context_commands.py +61 -0
  37. scc_cli/commands/worktree/session_commands.py +128 -0
  38. scc_cli/commands/worktree/worktree_commands.py +734 -0
  39. scc_cli/config.py +647 -0
  40. scc_cli/confirm.py +20 -0
  41. scc_cli/console.py +562 -0
  42. scc_cli/contexts.py +394 -0
  43. scc_cli/core/__init__.py +68 -0
  44. scc_cli/core/constants.py +101 -0
  45. scc_cli/core/errors.py +297 -0
  46. scc_cli/core/exit_codes.py +91 -0
  47. scc_cli/core/workspace.py +57 -0
  48. scc_cli/deprecation.py +54 -0
  49. scc_cli/deps.py +189 -0
  50. scc_cli/docker/__init__.py +127 -0
  51. scc_cli/docker/core.py +467 -0
  52. scc_cli/docker/credentials.py +726 -0
  53. scc_cli/docker/launch.py +595 -0
  54. scc_cli/doctor/__init__.py +105 -0
  55. scc_cli/doctor/checks/__init__.py +166 -0
  56. scc_cli/doctor/checks/cache.py +314 -0
  57. scc_cli/doctor/checks/config.py +107 -0
  58. scc_cli/doctor/checks/environment.py +182 -0
  59. scc_cli/doctor/checks/json_helpers.py +157 -0
  60. scc_cli/doctor/checks/organization.py +264 -0
  61. scc_cli/doctor/checks/worktree.py +278 -0
  62. scc_cli/doctor/render.py +365 -0
  63. scc_cli/doctor/types.py +66 -0
  64. scc_cli/evaluation/__init__.py +27 -0
  65. scc_cli/evaluation/apply_exceptions.py +207 -0
  66. scc_cli/evaluation/evaluate.py +97 -0
  67. scc_cli/evaluation/models.py +80 -0
  68. scc_cli/git.py +84 -0
  69. scc_cli/json_command.py +166 -0
  70. scc_cli/json_output.py +159 -0
  71. scc_cli/kinds.py +65 -0
  72. scc_cli/marketplace/__init__.py +123 -0
  73. scc_cli/marketplace/adapter.py +74 -0
  74. scc_cli/marketplace/compute.py +377 -0
  75. scc_cli/marketplace/constants.py +87 -0
  76. scc_cli/marketplace/managed.py +135 -0
  77. scc_cli/marketplace/materialize.py +846 -0
  78. scc_cli/marketplace/normalize.py +548 -0
  79. scc_cli/marketplace/render.py +281 -0
  80. scc_cli/marketplace/resolve.py +459 -0
  81. scc_cli/marketplace/schema.py +506 -0
  82. scc_cli/marketplace/sync.py +279 -0
  83. scc_cli/marketplace/team_cache.py +195 -0
  84. scc_cli/marketplace/team_fetch.py +689 -0
  85. scc_cli/marketplace/trust.py +244 -0
  86. scc_cli/models/__init__.py +41 -0
  87. scc_cli/models/exceptions.py +273 -0
  88. scc_cli/models/plugin_audit.py +434 -0
  89. scc_cli/org_templates.py +269 -0
  90. scc_cli/output_mode.py +167 -0
  91. scc_cli/panels.py +113 -0
  92. scc_cli/platform.py +350 -0
  93. scc_cli/profiles.py +960 -0
  94. scc_cli/remote.py +443 -0
  95. scc_cli/schemas/__init__.py +1 -0
  96. scc_cli/schemas/org-v1.schema.json +456 -0
  97. scc_cli/schemas/team-config.v1.schema.json +163 -0
  98. scc_cli/services/__init__.py +1 -0
  99. scc_cli/services/git/__init__.py +79 -0
  100. scc_cli/services/git/branch.py +151 -0
  101. scc_cli/services/git/core.py +216 -0
  102. scc_cli/services/git/hooks.py +108 -0
  103. scc_cli/services/git/worktree.py +444 -0
  104. scc_cli/services/workspace/__init__.py +36 -0
  105. scc_cli/services/workspace/resolver.py +223 -0
  106. scc_cli/services/workspace/suspicious.py +200 -0
  107. scc_cli/sessions.py +425 -0
  108. scc_cli/setup.py +589 -0
  109. scc_cli/source_resolver.py +470 -0
  110. scc_cli/stats.py +378 -0
  111. scc_cli/stores/__init__.py +13 -0
  112. scc_cli/stores/exception_store.py +251 -0
  113. scc_cli/subprocess_utils.py +88 -0
  114. scc_cli/teams.py +383 -0
  115. scc_cli/templates/__init__.py +2 -0
  116. scc_cli/templates/org/__init__.py +0 -0
  117. scc_cli/templates/org/minimal.json +19 -0
  118. scc_cli/templates/org/reference.json +74 -0
  119. scc_cli/templates/org/strict.json +38 -0
  120. scc_cli/templates/org/teams.json +42 -0
  121. scc_cli/templates/statusline.sh +75 -0
  122. scc_cli/theme.py +348 -0
  123. scc_cli/ui/__init__.py +154 -0
  124. scc_cli/ui/branding.py +68 -0
  125. scc_cli/ui/chrome.py +401 -0
  126. scc_cli/ui/dashboard/__init__.py +62 -0
  127. scc_cli/ui/dashboard/_dashboard.py +794 -0
  128. scc_cli/ui/dashboard/loaders.py +452 -0
  129. scc_cli/ui/dashboard/models.py +185 -0
  130. scc_cli/ui/dashboard/orchestrator.py +735 -0
  131. scc_cli/ui/formatters.py +444 -0
  132. scc_cli/ui/gate.py +350 -0
  133. scc_cli/ui/git_interactive.py +869 -0
  134. scc_cli/ui/git_render.py +176 -0
  135. scc_cli/ui/help.py +157 -0
  136. scc_cli/ui/keys.py +615 -0
  137. scc_cli/ui/list_screen.py +437 -0
  138. scc_cli/ui/picker.py +763 -0
  139. scc_cli/ui/prompts.py +201 -0
  140. scc_cli/ui/quick_resume.py +116 -0
  141. scc_cli/ui/wizard.py +576 -0
  142. scc_cli/update.py +680 -0
  143. scc_cli/utils/__init__.py +39 -0
  144. scc_cli/utils/fixit.py +264 -0
  145. scc_cli/utils/fuzzy.py +124 -0
  146. scc_cli/utils/locks.py +114 -0
  147. scc_cli/utils/ttl.py +376 -0
  148. scc_cli/validate.py +455 -0
  149. scc_cli-1.5.3.dist-info/METADATA +401 -0
  150. scc_cli-1.5.3.dist-info/RECORD +153 -0
  151. scc_cli-1.5.3.dist-info/WHEEL +4 -0
  152. scc_cli-1.5.3.dist-info/entry_points.txt +2 -0
  153. scc_cli-1.5.3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,176 @@
1
+ """Git rendering functions - pure UI components for git data display.
2
+
3
+ Pure functions with no side effects beyond console output. These take
4
+ data structures (like WorktreeInfo) and render them to Rich consoles.
5
+
6
+ Extracted from git.py to separate rendering from domain logic.
7
+ """
8
+
9
+ from rich import box
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+ from rich.text import Text
13
+
14
+ from ..panels import create_warning_panel
15
+ from ..services.git.branch import PROTECTED_BRANCHES, get_display_branch
16
+ from ..services.git.worktree import WorktreeInfo
17
+
18
+
19
+ def format_git_status(wt: WorktreeInfo) -> Text:
20
+ """Format git status as compact symbols: +N!N?N, . for clean, or ... for timeout.
21
+
22
+ Args:
23
+ wt: WorktreeInfo object with status fields populated.
24
+
25
+ Returns:
26
+ Rich Text with styled git status symbols.
27
+ """
28
+ # Show ellipsis if status timed out
29
+ if wt.status_timed_out:
30
+ return Text("...", style="dim")
31
+
32
+ if wt.staged_count == 0 and wt.modified_count == 0 and wt.untracked_count == 0:
33
+ return Text(".", style="green")
34
+
35
+ parts = Text()
36
+ if wt.staged_count > 0:
37
+ parts.append(f"+{wt.staged_count}", style="green")
38
+ if wt.modified_count > 0:
39
+ parts.append(f"!{wt.modified_count}", style="yellow")
40
+ if wt.untracked_count > 0:
41
+ parts.append(f"?{wt.untracked_count}", style="dim")
42
+ return parts
43
+
44
+
45
+ def render_worktrees_table(
46
+ worktrees: list[WorktreeInfo],
47
+ console: Console,
48
+ *,
49
+ verbose: bool = False,
50
+ ) -> None:
51
+ """Render worktrees in a responsive table.
52
+
53
+ Args:
54
+ worktrees: List of WorktreeInfo objects to display.
55
+ console: Rich console for output.
56
+ verbose: If True, show git status symbols.
57
+ """
58
+ if not worktrees:
59
+ console.print()
60
+ console.print(
61
+ create_warning_panel(
62
+ "No Worktrees",
63
+ "No git worktrees found for this repository.",
64
+ "Create one with: scc worktree <repo> <feature-name>",
65
+ )
66
+ )
67
+ return
68
+
69
+ console.print()
70
+
71
+ # Responsive: check terminal width
72
+ width = console.width
73
+ wide_mode = width >= 110
74
+
75
+ # Create table with adaptive columns
76
+ table = Table(
77
+ title="[bold cyan]Git Worktrees[/bold cyan]",
78
+ box=box.ROUNDED,
79
+ header_style="bold cyan",
80
+ show_lines=False,
81
+ expand=True,
82
+ padding=(0, 1),
83
+ )
84
+
85
+ table.add_column("#", style="dim", width=3, justify="right")
86
+ table.add_column("Branch", style="cyan", no_wrap=True)
87
+
88
+ if verbose:
89
+ table.add_column("Status", no_wrap=True, width=10)
90
+
91
+ if wide_mode:
92
+ table.add_column("Path", style="dim", overflow="ellipsis", ratio=2)
93
+ if not verbose:
94
+ table.add_column("Status", style="dim", no_wrap=True, width=12)
95
+ else:
96
+ table.add_column("Path", style="dim", overflow="ellipsis", max_width=40)
97
+
98
+ for idx, wt in enumerate(worktrees, 1):
99
+ # Style the branch name with @ prefix for current
100
+ is_detached = not wt.branch
101
+ is_protected = wt.branch in PROTECTED_BRANCHES if wt.branch else False
102
+ # Use display-friendly name (strip SCC prefix)
103
+ branch_value = get_display_branch(wt.branch) if wt.branch else "detached"
104
+
105
+ # Add @ prefix for current worktree
106
+ if wt.is_current:
107
+ branch_display = Text("@ ", style="green bold")
108
+ branch_display.append(branch_value, style="cyan bold")
109
+ elif is_protected or is_detached:
110
+ branch_display = Text(branch_value, style="yellow")
111
+ else:
112
+ branch_display = Text(branch_value, style="cyan")
113
+
114
+ # Determine text status (for non-verbose wide mode)
115
+ text_status = wt.status or ("detached" if is_detached else "active")
116
+ if is_protected:
117
+ text_status = "protected"
118
+
119
+ status_style = {
120
+ "active": "green",
121
+ "protected": "yellow",
122
+ "detached": "yellow",
123
+ "bare": "dim",
124
+ }.get(text_status, "dim")
125
+
126
+ if verbose:
127
+ # Verbose mode: show git status symbols
128
+ git_status = format_git_status(wt)
129
+ if wide_mode:
130
+ table.add_row(
131
+ str(idx),
132
+ branch_display,
133
+ git_status,
134
+ wt.path,
135
+ )
136
+ else:
137
+ table.add_row(
138
+ str(idx),
139
+ branch_display,
140
+ git_status,
141
+ wt.path,
142
+ )
143
+ elif wide_mode:
144
+ table.add_row(
145
+ str(idx),
146
+ branch_display,
147
+ wt.path,
148
+ Text(text_status, style=status_style),
149
+ )
150
+ else:
151
+ table.add_row(
152
+ str(idx),
153
+ branch_display,
154
+ wt.path,
155
+ )
156
+
157
+ console.print(table)
158
+ console.print()
159
+
160
+
161
+ def render_worktrees(
162
+ worktrees: list[WorktreeInfo],
163
+ console: Console,
164
+ *,
165
+ verbose: bool = False,
166
+ ) -> None:
167
+ """Render worktrees with beautiful formatting.
168
+
169
+ Public interface used by cli.py for consistent styling across the application.
170
+
171
+ Args:
172
+ worktrees: List of WorktreeInfo objects to display.
173
+ console: Rich console for output.
174
+ verbose: If True, show git status symbols.
175
+ """
176
+ render_worktrees_table(worktrees, console, verbose=verbose)
scc_cli/ui/help.py ADDED
@@ -0,0 +1,157 @@
1
+ """Help overlay for interactive UI screens.
2
+
3
+ Provides mode-aware help that shows only keys relevant to the current screen.
4
+ The overlay is triggered by pressing '?' and dismissed by any key.
5
+
6
+ Key categories shown per mode:
7
+ - ALL: Navigation (↑↓/j/k), typing to filter, backspace, t for teams
8
+ - PICKER: Enter to select, Esc to cancel
9
+ - MULTI_SELECT: Space to toggle, a to toggle all, Enter to confirm, Esc to cancel
10
+ - DASHBOARD: Tab/Shift+Tab for tabs, Enter for details, q to quit
11
+
12
+ Example:
13
+ >>> from scc_cli.ui.help import show_help_overlay
14
+ >>> from scc_cli.ui.list_screen import ListMode
15
+ >>> show_help_overlay(ListMode.SINGLE_SELECT)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from enum import Enum, auto
21
+ from typing import TYPE_CHECKING
22
+
23
+ from rich.panel import Panel
24
+ from rich.table import Table
25
+ from rich.text import Text
26
+
27
+ from ..theme import Indicators
28
+
29
+ if TYPE_CHECKING:
30
+ from rich.console import Console, RenderableType
31
+
32
+
33
+ class HelpMode(Enum):
34
+ """Screen mode for help overlay customization."""
35
+
36
+ PICKER = auto() # Single-select picker (team, worktree, etc.)
37
+ MULTI_SELECT = auto() # Multi-select list (containers, etc.)
38
+ DASHBOARD = auto() # Tabbed dashboard view
39
+
40
+
41
+ # Mapping from HelpMode enum to string mode names used in KEYBINDING_DOCS
42
+ _MODE_NAMES: dict[HelpMode, str] = {
43
+ HelpMode.PICKER: "PICKER",
44
+ HelpMode.MULTI_SELECT: "MULTI_SELECT",
45
+ HelpMode.DASHBOARD: "DASHBOARD",
46
+ }
47
+
48
+
49
+ def get_help_entries(mode: HelpMode) -> list[tuple[str, str]]:
50
+ """Get help entries filtered for a specific mode.
51
+
52
+ This function uses KEYBINDING_DOCS from keys.py as the single source
53
+ of truth for keybinding documentation.
54
+
55
+ Args:
56
+ mode: The current screen mode.
57
+
58
+ Returns:
59
+ List of (key, description) tuples for the given mode.
60
+ """
61
+ from .keys import get_keybindings_for_mode
62
+
63
+ mode_name = _MODE_NAMES[mode]
64
+ return get_keybindings_for_mode(mode_name)
65
+
66
+
67
+ def get_help_entries_grouped(mode: HelpMode) -> dict[str, list[tuple[str, str]]]:
68
+ """Get help entries grouped by section for a specific mode.
69
+
70
+ This function uses KEYBINDING_DOCS from keys.py as the single source
71
+ of truth for keybinding documentation.
72
+
73
+ Args:
74
+ mode: The current screen mode.
75
+
76
+ Returns:
77
+ Dict mapping section names to lists of (key, description) tuples.
78
+ """
79
+ from .keys import get_keybindings_grouped_by_section
80
+
81
+ mode_name = _MODE_NAMES[mode]
82
+ return get_keybindings_grouped_by_section(mode_name)
83
+
84
+
85
+ def render_help_content(mode: HelpMode) -> RenderableType:
86
+ """Render help content for a given mode with section headers.
87
+
88
+ Args:
89
+ mode: The current screen mode.
90
+
91
+ Returns:
92
+ A Rich renderable with the help content organized by section.
93
+ """
94
+ from rich.console import Group
95
+
96
+ grouped = get_help_entries_grouped(mode)
97
+
98
+ renderables: list[RenderableType] = []
99
+
100
+ for section_name, entries in grouped.items():
101
+ # Section header
102
+ section_header = Text()
103
+ sep = Indicators.get("HORIZONTAL_LINE")
104
+ section_header.append(f"{sep}{sep}{sep} {section_name} ", style="dim")
105
+ section_header.append(sep * max(0, 30 - len(section_name)), style="dim")
106
+ renderables.append(section_header)
107
+
108
+ # Section table
109
+ table = Table(show_header=False, box=None, padding=(0, 2, 0, 0))
110
+ table.add_column("Key", style="cyan bold", width=12)
111
+ table.add_column("Action", style="dim")
112
+
113
+ for key, desc in entries:
114
+ table.add_row(key, desc)
115
+
116
+ renderables.append(table)
117
+ renderables.append(Text("")) # Spacing between sections
118
+
119
+ # Mode indicator
120
+ mode_display = {
121
+ HelpMode.PICKER: "Picker",
122
+ HelpMode.MULTI_SELECT: "Multi-Select",
123
+ HelpMode.DASHBOARD: "Dashboard",
124
+ }.get(mode, "Unknown")
125
+
126
+ footer = Text()
127
+ footer.append("Press any key to dismiss", style="dim italic")
128
+ renderables.append(footer)
129
+
130
+ return Panel(
131
+ Group(*renderables),
132
+ title=f"[bold]Keyboard Shortcuts[/bold] {Indicators.get('VERTICAL_LINE')} {mode_display}",
133
+ title_align="left",
134
+ border_style="blue",
135
+ padding=(1, 2),
136
+ )
137
+
138
+
139
+ def show_help_overlay(mode: HelpMode, console: Console | None = None) -> None:
140
+ """Display help overlay and wait for any key to dismiss.
141
+
142
+ Args:
143
+ mode: The current screen mode (affects which keys are shown).
144
+ console: Optional console to use. If None, creates a new one.
145
+ """
146
+ if console is None:
147
+ from ..console import get_err_console
148
+
149
+ console = get_err_console()
150
+
151
+ content = render_help_content(mode)
152
+ console.print(content)
153
+
154
+ # Wait for any key to dismiss
155
+ from .keys import read_key
156
+
157
+ read_key()