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,246 @@
|
|
|
1
|
+
"""Provide CLI commands for plugin audit functionality.
|
|
2
|
+
|
|
3
|
+
Audit installed Claude Code plugins via the `scc audit plugins` command,
|
|
4
|
+
including manifest validation and MCP server/hooks discovery.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich import box
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
from rich.text import Text
|
|
18
|
+
|
|
19
|
+
from scc_cli.audit.reader import audit_all_plugins
|
|
20
|
+
from scc_cli.core.constants import AGENT_CONFIG_DIR
|
|
21
|
+
from scc_cli.models.plugin_audit import (
|
|
22
|
+
AuditOutput,
|
|
23
|
+
ManifestStatus,
|
|
24
|
+
PluginAuditResult,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
29
|
+
# Create the audit sub-app
|
|
30
|
+
audit_app = typer.Typer(
|
|
31
|
+
name="audit",
|
|
32
|
+
help="Audit installed plugins and configurations.",
|
|
33
|
+
no_args_is_help=True,
|
|
34
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_claude_dir() -> Path:
|
|
39
|
+
"""Get the Claude Code directory path."""
|
|
40
|
+
return Path.home() / AGENT_CONFIG_DIR
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def format_status(status: str) -> str:
|
|
44
|
+
"""Format status with color for Rich output."""
|
|
45
|
+
if status == "clean":
|
|
46
|
+
return "[dim]clean[/dim]"
|
|
47
|
+
elif status == "parsed":
|
|
48
|
+
return "[green]parsed[/green]"
|
|
49
|
+
elif status == "malformed":
|
|
50
|
+
return "[red]malformed[/red]"
|
|
51
|
+
elif status == "unreadable":
|
|
52
|
+
return "[yellow]unreadable[/yellow]"
|
|
53
|
+
elif status == "not installed":
|
|
54
|
+
return "[dim]not installed[/dim]"
|
|
55
|
+
return status
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def render_human_output(output: AuditOutput) -> None:
|
|
59
|
+
"""Render audit output in human-readable format."""
|
|
60
|
+
# Header
|
|
61
|
+
console.print()
|
|
62
|
+
header_body = Text()
|
|
63
|
+
header_body.append("Plugin Audit Report", style="bold")
|
|
64
|
+
header_body.append("\n")
|
|
65
|
+
header_body.append(f"Discovered {output.total_plugins} plugin(s)", style="dim")
|
|
66
|
+
console.print(
|
|
67
|
+
Panel(
|
|
68
|
+
header_body,
|
|
69
|
+
border_style="cyan",
|
|
70
|
+
padding=(0, 1),
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
console.print()
|
|
74
|
+
|
|
75
|
+
if output.total_plugins == 0:
|
|
76
|
+
console.print("[dim]No plugins installed.[/dim]")
|
|
77
|
+
console.print()
|
|
78
|
+
_print_disclaimer()
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
# Create table for plugins with rounded borders
|
|
82
|
+
table = Table(
|
|
83
|
+
show_header=True,
|
|
84
|
+
header_style="bold",
|
|
85
|
+
box=box.ROUNDED,
|
|
86
|
+
border_style="dim",
|
|
87
|
+
padding=(0, 1),
|
|
88
|
+
)
|
|
89
|
+
table.add_column("Plugin", style="cyan", no_wrap=True)
|
|
90
|
+
table.add_column("Version", style="dim")
|
|
91
|
+
table.add_column("Status", justify="center")
|
|
92
|
+
table.add_column("MCP", justify="right", style="green")
|
|
93
|
+
table.add_column("Hooks", justify="right", style="yellow")
|
|
94
|
+
|
|
95
|
+
for plugin in output.plugins:
|
|
96
|
+
# Format MCP server count
|
|
97
|
+
mcp_count = 0
|
|
98
|
+
if plugin.manifests and plugin.manifests.mcp.status == ManifestStatus.PARSED:
|
|
99
|
+
mcp_count = len(plugin.manifests.mcp_servers)
|
|
100
|
+
mcp_display = str(mcp_count) if mcp_count > 0 else "[dim]-[/dim]"
|
|
101
|
+
|
|
102
|
+
# Format hooks count
|
|
103
|
+
hooks_count = 0
|
|
104
|
+
if plugin.manifests and plugin.manifests.hooks.status == ManifestStatus.PARSED:
|
|
105
|
+
hooks_count = len(plugin.manifests.hooks_info)
|
|
106
|
+
hooks_display = str(hooks_count) if hooks_count > 0 else "[dim]-[/dim]"
|
|
107
|
+
|
|
108
|
+
table.add_row(
|
|
109
|
+
plugin.plugin_name,
|
|
110
|
+
plugin.version,
|
|
111
|
+
format_status(plugin.status_summary),
|
|
112
|
+
mcp_display,
|
|
113
|
+
hooks_display,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
console.print(table)
|
|
117
|
+
console.print()
|
|
118
|
+
|
|
119
|
+
# Show details for plugins with declarations
|
|
120
|
+
for plugin in output.plugins:
|
|
121
|
+
if plugin.manifests and plugin.manifests.has_declarations:
|
|
122
|
+
_render_plugin_details(plugin)
|
|
123
|
+
|
|
124
|
+
# Show problems in a warning panel
|
|
125
|
+
problem_plugins = [p for p in output.plugins if p.has_ci_failures]
|
|
126
|
+
if problem_plugins:
|
|
127
|
+
_render_problems_panel(problem_plugins)
|
|
128
|
+
|
|
129
|
+
# Disclaimer
|
|
130
|
+
_print_disclaimer()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _render_plugin_details(plugin: PluginAuditResult) -> None:
|
|
134
|
+
"""Render details for a plugin with declarations."""
|
|
135
|
+
if not plugin.manifests:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# Create a details grid
|
|
139
|
+
details = Text()
|
|
140
|
+
details.append(f" {plugin.plugin_name}", style="bold cyan")
|
|
141
|
+
details.append("\n")
|
|
142
|
+
|
|
143
|
+
# Show MCP servers
|
|
144
|
+
if plugin.manifests.mcp_servers:
|
|
145
|
+
details.append(" MCP Servers: ", style="dim")
|
|
146
|
+
server_names = [s.name for s in plugin.manifests.mcp_servers]
|
|
147
|
+
details.append(", ".join(server_names), style="green")
|
|
148
|
+
details.append("\n")
|
|
149
|
+
|
|
150
|
+
# Show hooks
|
|
151
|
+
if plugin.manifests.hooks_info:
|
|
152
|
+
details.append(" Hooks: ", style="dim")
|
|
153
|
+
hook_events = [h.event for h in plugin.manifests.hooks_info]
|
|
154
|
+
details.append(", ".join(hook_events), style="yellow")
|
|
155
|
+
details.append("\n")
|
|
156
|
+
|
|
157
|
+
console.print(details)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _render_problems_panel(plugins: list[PluginAuditResult]) -> None:
|
|
161
|
+
"""Render a warning panel for all plugins with CI failures."""
|
|
162
|
+
problems_text = Text()
|
|
163
|
+
|
|
164
|
+
for i, plugin in enumerate(plugins):
|
|
165
|
+
if not plugin.manifests:
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
if i > 0:
|
|
169
|
+
problems_text.append("\n")
|
|
170
|
+
|
|
171
|
+
problems_text.append(f"⚠ {plugin.plugin_name}", style="bold red")
|
|
172
|
+
problems_text.append("\n")
|
|
173
|
+
|
|
174
|
+
# Check MCP manifest
|
|
175
|
+
if plugin.manifests.mcp.has_problems:
|
|
176
|
+
mcp = plugin.manifests.mcp
|
|
177
|
+
if mcp.status == ManifestStatus.MALFORMED and mcp.error:
|
|
178
|
+
problems_text.append(" .mcp.json: ", style="dim")
|
|
179
|
+
problems_text.append(f"malformed ({mcp.error.format()})", style="red")
|
|
180
|
+
problems_text.append("\n")
|
|
181
|
+
elif mcp.status == ManifestStatus.UNREADABLE:
|
|
182
|
+
problems_text.append(" .mcp.json: ", style="dim")
|
|
183
|
+
problems_text.append(f"unreadable ({mcp.error_message})", style="yellow")
|
|
184
|
+
problems_text.append("\n")
|
|
185
|
+
|
|
186
|
+
# Check hooks manifest
|
|
187
|
+
if plugin.manifests.hooks.has_problems:
|
|
188
|
+
hooks = plugin.manifests.hooks
|
|
189
|
+
if hooks.status == ManifestStatus.MALFORMED and hooks.error:
|
|
190
|
+
problems_text.append(" hooks/hooks.json: ", style="dim")
|
|
191
|
+
problems_text.append(f"malformed ({hooks.error.format()})", style="red")
|
|
192
|
+
problems_text.append("\n")
|
|
193
|
+
elif hooks.status == ManifestStatus.UNREADABLE:
|
|
194
|
+
problems_text.append(" hooks/hooks.json: ", style="dim")
|
|
195
|
+
problems_text.append(f"unreadable ({hooks.error_message})", style="yellow")
|
|
196
|
+
problems_text.append("\n")
|
|
197
|
+
|
|
198
|
+
console.print(
|
|
199
|
+
Panel(
|
|
200
|
+
problems_text,
|
|
201
|
+
title="[bold yellow]Manifest Problems[/bold yellow]",
|
|
202
|
+
border_style="yellow",
|
|
203
|
+
padding=(0, 1),
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
console.print()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _print_disclaimer() -> None:
|
|
210
|
+
"""Print the informational disclaimer."""
|
|
211
|
+
console.print("[dim]ℹ Informational only; SCC does not enforce plugin internals.[/dim]")
|
|
212
|
+
console.print()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def render_json_output(output: AuditOutput) -> None:
|
|
216
|
+
"""Render audit output as JSON."""
|
|
217
|
+
data = output.to_dict()
|
|
218
|
+
console.print(json.dumps(data, indent=2))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@audit_app.command(name="plugins")
|
|
222
|
+
def audit_plugins_cmd(
|
|
223
|
+
as_json: bool = typer.Option(
|
|
224
|
+
False,
|
|
225
|
+
"--json",
|
|
226
|
+
help="Output as JSON with schemaVersion for CI integration.",
|
|
227
|
+
),
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Audit installed Claude Code plugins.
|
|
230
|
+
|
|
231
|
+
Shows manifest status, MCP servers, and hooks for all installed plugins.
|
|
232
|
+
|
|
233
|
+
Exit codes:
|
|
234
|
+
- 0: All plugins parsed successfully (or no plugins installed)
|
|
235
|
+
- 1: One or more plugins have malformed or unreadable manifests
|
|
236
|
+
"""
|
|
237
|
+
claude_dir = get_claude_dir()
|
|
238
|
+
output = audit_all_plugins(claude_dir)
|
|
239
|
+
|
|
240
|
+
if as_json:
|
|
241
|
+
render_json_output(output)
|
|
242
|
+
else:
|
|
243
|
+
render_human_output(output)
|
|
244
|
+
|
|
245
|
+
# Exit with appropriate code for CI
|
|
246
|
+
raise typer.Exit(code=output.exit_code)
|