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,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)