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
scc_cli/cli_helpers.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Provide centralized CLI helpers for confirmation and safety patterns.
|
|
2
|
+
|
|
3
|
+
Provide standardized helpers for:
|
|
4
|
+
- Destructive operation confirmation (prune, worktree remove, etc.)
|
|
5
|
+
- Governance command validation (unblock, exceptions)
|
|
6
|
+
- Non-interactive mode detection (CI environments)
|
|
7
|
+
- JSON mode compatibility
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
|
|
20
|
+
from .core.exit_codes import EXIT_USAGE
|
|
21
|
+
from .output_mode import is_json_mode
|
|
22
|
+
|
|
23
|
+
console = Console()
|
|
24
|
+
stderr_console = Console(stderr=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class ConfirmItems:
|
|
29
|
+
"""Hold items to display before a confirmation prompt.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
title: Header text shown above the item list.
|
|
33
|
+
items: List of item descriptions to display.
|
|
34
|
+
max_display: Maximum items to show before truncating with count.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
title: str
|
|
38
|
+
items: list[str]
|
|
39
|
+
max_display: int = 10
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_interactive() -> bool:
|
|
43
|
+
"""Check if running in interactive mode.
|
|
44
|
+
|
|
45
|
+
Return False if:
|
|
46
|
+
- stdin is not a TTY (piped input, redirected)
|
|
47
|
+
- CI environment variable is set to truthy value
|
|
48
|
+
- JSON output mode is active
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
True if interactive prompts are safe to use.
|
|
52
|
+
"""
|
|
53
|
+
if is_json_mode():
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
is_tty = sys.stdin.isatty()
|
|
57
|
+
is_ci = os.getenv("CI", "").lower() in ("1", "true", "yes")
|
|
58
|
+
|
|
59
|
+
return is_tty and not is_ci
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def confirm_action(
|
|
63
|
+
*,
|
|
64
|
+
yes: bool,
|
|
65
|
+
prompt: str,
|
|
66
|
+
dry_run: bool = False,
|
|
67
|
+
items: ConfirmItems | None = None,
|
|
68
|
+
non_interactive_requires_yes: bool = True,
|
|
69
|
+
) -> bool:
|
|
70
|
+
"""Standard confirmation behavior for destructive operations.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
yes: If True, skip confirmation prompt.
|
|
74
|
+
prompt: The confirmation question to ask.
|
|
75
|
+
dry_run: If True, return False (caller should not mutate state).
|
|
76
|
+
items: Optional list of affected items to display before prompting.
|
|
77
|
+
non_interactive_requires_yes: If True, exit with EXIT_USAGE when
|
|
78
|
+
running non-interactively without --yes.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
True if action should proceed, False if dry-run mode.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
typer.Exit(EXIT_USAGE): If non-interactive and would prompt without --yes.
|
|
85
|
+
typer.Abort: If user declines confirmation.
|
|
86
|
+
|
|
87
|
+
Behavior:
|
|
88
|
+
- If dry_run: return False (caller should not mutate state)
|
|
89
|
+
- If yes: return True (skip prompt)
|
|
90
|
+
- If JSON mode: exit EXIT_USAGE (never prompt in JSON mode)
|
|
91
|
+
- If non-interactive and would prompt: exit EXIT_USAGE
|
|
92
|
+
- Otherwise: print affected resources (if provided) and prompt
|
|
93
|
+
"""
|
|
94
|
+
if dry_run:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
if yes:
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
# JSON mode must never prompt
|
|
101
|
+
if is_json_mode():
|
|
102
|
+
stderr_console.print(
|
|
103
|
+
"[red]Error:[/red] Cannot prompt in JSON mode. Use --yes to confirm.",
|
|
104
|
+
style="bold",
|
|
105
|
+
)
|
|
106
|
+
raise typer.Exit(EXIT_USAGE)
|
|
107
|
+
|
|
108
|
+
# Non-interactive mode detection
|
|
109
|
+
if not is_interactive() and non_interactive_requires_yes:
|
|
110
|
+
console.print(
|
|
111
|
+
"[red]Error:[/red] This operation requires confirmation. "
|
|
112
|
+
"Use --yes to skip in non-interactive mode.",
|
|
113
|
+
style="bold",
|
|
114
|
+
)
|
|
115
|
+
raise typer.Exit(EXIT_USAGE)
|
|
116
|
+
|
|
117
|
+
# Display affected items
|
|
118
|
+
if items:
|
|
119
|
+
console.print(f"\n[bold]{items.title}[/bold]")
|
|
120
|
+
shown = items.items[: items.max_display]
|
|
121
|
+
for item in shown:
|
|
122
|
+
console.print(f" [dim]•[/dim] {item}")
|
|
123
|
+
if len(items.items) > items.max_display:
|
|
124
|
+
remaining = len(items.items) - items.max_display
|
|
125
|
+
console.print(f" [dim](+ {remaining} more)[/dim]")
|
|
126
|
+
console.print()
|
|
127
|
+
|
|
128
|
+
# Prompt for confirmation
|
|
129
|
+
return typer.confirm(prompt, abort=True)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def require_reason_for_governance(
|
|
133
|
+
*,
|
|
134
|
+
yes: bool,
|
|
135
|
+
reason: str | None,
|
|
136
|
+
command_name: str = "unblock",
|
|
137
|
+
) -> str:
|
|
138
|
+
"""Require --reason when --yes is used for governance commands.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
yes: Whether --yes flag was provided.
|
|
142
|
+
reason: The reason string if provided via --reason.
|
|
143
|
+
command_name: Name of the command for error messages.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
The reason string (either provided or collected interactively).
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
typer.Exit(EXIT_USAGE): If --yes used without --reason.
|
|
150
|
+
"""
|
|
151
|
+
if yes and not reason:
|
|
152
|
+
console.print(
|
|
153
|
+
f"[red]Error:[/red] --reason is required when using --yes with {command_name}.",
|
|
154
|
+
style="bold",
|
|
155
|
+
)
|
|
156
|
+
raise typer.Exit(EXIT_USAGE)
|
|
157
|
+
|
|
158
|
+
if reason:
|
|
159
|
+
return reason
|
|
160
|
+
|
|
161
|
+
# Interactive mode: prompt for reason
|
|
162
|
+
if is_json_mode():
|
|
163
|
+
stderr_console.print(
|
|
164
|
+
"[red]Error:[/red] Cannot prompt for reason in JSON mode. Use --reason.",
|
|
165
|
+
style="bold",
|
|
166
|
+
)
|
|
167
|
+
raise typer.Exit(EXIT_USAGE)
|
|
168
|
+
|
|
169
|
+
if not is_interactive():
|
|
170
|
+
console.print(
|
|
171
|
+
"[red]Error:[/red] Cannot prompt for reason in non-interactive mode. Use --reason.",
|
|
172
|
+
style="bold",
|
|
173
|
+
)
|
|
174
|
+
raise typer.Exit(EXIT_USAGE)
|
|
175
|
+
|
|
176
|
+
prompted_reason: str = typer.prompt("Reason for this exception")
|
|
177
|
+
return prompted_reason
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(frozen=True)
|
|
181
|
+
class AuditRecord:
|
|
182
|
+
"""Audit record for governance operations."""
|
|
183
|
+
|
|
184
|
+
timestamp: datetime
|
|
185
|
+
command: str
|
|
186
|
+
actor: str
|
|
187
|
+
target: str
|
|
188
|
+
reason: str
|
|
189
|
+
ticket: str | None = None
|
|
190
|
+
expires_in: str | None = None
|
|
191
|
+
|
|
192
|
+
def to_dict(self) -> dict[str, str | None]:
|
|
193
|
+
"""Convert to dictionary for JSON serialization."""
|
|
194
|
+
result: dict[str, str | None] = {
|
|
195
|
+
"timestamp": self.timestamp.isoformat(),
|
|
196
|
+
"command": self.command,
|
|
197
|
+
"actor": self.actor,
|
|
198
|
+
"target": self.target,
|
|
199
|
+
"reason": self.reason,
|
|
200
|
+
}
|
|
201
|
+
if self.ticket:
|
|
202
|
+
result["ticket"] = self.ticket
|
|
203
|
+
if self.expires_in:
|
|
204
|
+
result["expires_in"] = self.expires_in
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_current_user() -> str:
|
|
209
|
+
"""Get the current user for audit purposes."""
|
|
210
|
+
return os.getenv("USER", os.getenv("USERNAME", "unknown"))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def create_audit_record(
|
|
214
|
+
*,
|
|
215
|
+
command: str,
|
|
216
|
+
target: str,
|
|
217
|
+
reason: str,
|
|
218
|
+
ticket: str | None = None,
|
|
219
|
+
expires_in: str | None = None,
|
|
220
|
+
) -> AuditRecord:
|
|
221
|
+
"""Create an audit record for governance operations.
|
|
222
|
+
|
|
223
|
+
This is mandatory for governance commands (unblock, exception creation).
|
|
224
|
+
For other destructive operations, use only if SCC_AUDIT_LOG is enabled.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
command: The governance command being executed.
|
|
228
|
+
target: The resource being affected (e.g., plugin name, policy).
|
|
229
|
+
reason: Justification for the operation.
|
|
230
|
+
ticket: Optional issue/ticket reference for traceability.
|
|
231
|
+
expires_in: Optional duration string for temporary exceptions.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
A timestamped audit record with actor information.
|
|
235
|
+
"""
|
|
236
|
+
return AuditRecord(
|
|
237
|
+
timestamp=datetime.now(timezone.utc),
|
|
238
|
+
command=command,
|
|
239
|
+
actor=get_current_user(),
|
|
240
|
+
target=target,
|
|
241
|
+
reason=reason,
|
|
242
|
+
ticket=ticket,
|
|
243
|
+
expires_in=expires_in,
|
|
244
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""CLI command modules.
|
|
2
|
+
|
|
3
|
+
This package contains all CLI subcommand implementations.
|
|
4
|
+
Each module exports a Typer app that is registered in cli.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"admin",
|
|
11
|
+
"audit",
|
|
12
|
+
"config",
|
|
13
|
+
"exceptions",
|
|
14
|
+
"init",
|
|
15
|
+
"launch",
|
|
16
|
+
"org",
|
|
17
|
+
"support",
|
|
18
|
+
"team",
|
|
19
|
+
"worktree",
|
|
20
|
+
]
|