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,88 @@
1
+ """
2
+ Provide subprocess utilities for consistent error handling.
3
+
4
+ Define wrapper functions for subprocess execution with graceful timeout
5
+ and missing executable handling.
6
+ """
7
+
8
+ import shutil
9
+ import subprocess
10
+
11
+
12
+ def run_command(
13
+ cmd: list[str],
14
+ timeout: int = 10,
15
+ cwd: str | None = None,
16
+ ) -> str | None:
17
+ """Run command, return stdout if successful, None otherwise.
18
+
19
+ Handle timeouts and missing executables gracefully.
20
+
21
+ Args:
22
+ cmd: Command and arguments as list of strings.
23
+ timeout: Maximum seconds to wait for command.
24
+ cwd: Working directory for command execution.
25
+
26
+ Returns:
27
+ Stripped stdout on success, None on any failure.
28
+ """
29
+ # Pre-check: handle empty command list
30
+ if not cmd:
31
+ return None
32
+
33
+ # Pre-check: is the executable available?
34
+ if not shutil.which(cmd[0]):
35
+ return None
36
+
37
+ try:
38
+ result = subprocess.run(
39
+ cmd,
40
+ capture_output=True,
41
+ text=True,
42
+ timeout=timeout,
43
+ cwd=cwd,
44
+ )
45
+ if result.returncode == 0:
46
+ return result.stdout.strip()
47
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
48
+ pass
49
+ return None
50
+
51
+
52
+ def run_command_bool(
53
+ cmd: list[str],
54
+ timeout: int = 10,
55
+ cwd: str | None = None,
56
+ ) -> bool:
57
+ """Run command, return True if exit code is 0.
58
+
59
+ Args:
60
+ cmd: Command and arguments as list of strings.
61
+ timeout: Maximum seconds to wait for command.
62
+ cwd: Working directory for command execution.
63
+
64
+ Returns:
65
+ True if command succeeded (exit code 0), False otherwise.
66
+ """
67
+ return run_command(cmd, timeout, cwd) is not None
68
+
69
+
70
+ def run_command_lines(
71
+ cmd: list[str],
72
+ timeout: int = 10,
73
+ cwd: str | None = None,
74
+ ) -> list[str]:
75
+ """Run command, return stdout split into lines.
76
+
77
+ Args:
78
+ cmd: Command and arguments as list of strings.
79
+ timeout: Maximum seconds to wait for command.
80
+ cwd: Working directory for command execution.
81
+
82
+ Returns:
83
+ List of output lines on success, empty list on failure.
84
+ """
85
+ output = run_command(cmd, timeout, cwd)
86
+ if output is None:
87
+ return []
88
+ return [line for line in output.split("\n") if line.strip()]
scc_cli/teams.py ADDED
@@ -0,0 +1,383 @@
1
+ """
2
+ Team profile management.
3
+
4
+ Simplified architecture: SCC generates extraKnownMarketplaces + enabledPlugins,
5
+ Claude Code handles plugin fetching, installation, and updates natively.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from . import config as config_module
14
+ from .theme import Indicators
15
+
16
+ if TYPE_CHECKING:
17
+ from .ui.list_screen import ListItem
18
+
19
+
20
+ @dataclass
21
+ class TeamInfo:
22
+ """Information about a team profile.
23
+
24
+ Provides a typed representation of team data for use in the UI layer.
25
+ Use from_dict() to construct from raw config dicts, and to_list_item()
26
+ to convert for display in pickers.
27
+
28
+ Attributes:
29
+ name: Team/profile name (unique identifier).
30
+ description: Human-readable team description.
31
+ plugins: List of plugin identifiers for the team.
32
+ marketplace: Optional marketplace name.
33
+ marketplace_type: Optional marketplace type (e.g., "github").
34
+ marketplace_repo: Optional marketplace repository path.
35
+ credential_status: Credential state ("valid", "expired", "expiring", None).
36
+ """
37
+
38
+ name: str
39
+ description: str = ""
40
+ plugins: list[str] = field(default_factory=list)
41
+ marketplace: str | None = None
42
+ marketplace_type: str | None = None
43
+ marketplace_repo: str | None = None
44
+ credential_status: str | None = None
45
+
46
+ @classmethod
47
+ def from_dict(cls, data: dict[str, Any]) -> TeamInfo:
48
+ """Create TeamInfo from a dict representation.
49
+
50
+ Args:
51
+ data: Dict with team fields (from list_teams or get_team_details).
52
+
53
+ Returns:
54
+ TeamInfo dataclass instance.
55
+ """
56
+ return cls(
57
+ name=data.get("name", "unknown"),
58
+ description=data.get("description", ""),
59
+ plugins=data.get("plugins", []),
60
+ marketplace=data.get("marketplace"),
61
+ marketplace_type=data.get("marketplace_type"),
62
+ marketplace_repo=data.get("marketplace_repo"),
63
+ credential_status=data.get("credential_status"),
64
+ )
65
+
66
+ def to_list_item(self, *, current_team: str | None = None) -> ListItem[TeamInfo]:
67
+ """Convert to ListItem for display in pickers.
68
+
69
+ Args:
70
+ current_team: Currently selected team name (marked with indicator).
71
+
72
+ Returns:
73
+ ListItem suitable for ListScreen display.
74
+
75
+ Example:
76
+ >>> team = TeamInfo(name="platform", description="Platform team")
77
+ >>> item = team.to_list_item(current_team="platform")
78
+ >>> item.label
79
+ '✓ platform'
80
+ """
81
+ from .ui.list_screen import ListItem
82
+
83
+ is_current = current_team is not None and self.name == current_team
84
+
85
+ # Build label with current indicator
86
+ label = f"{Indicators.get('PASS')} {self.name}" if is_current else self.name
87
+
88
+ # Check for credential/governance status
89
+ governance_status: str | None = None
90
+ if self.credential_status == "expired":
91
+ governance_status = "blocked"
92
+ elif self.credential_status == "expiring":
93
+ governance_status = "warning"
94
+
95
+ # Build description parts
96
+ desc_parts: list[str] = []
97
+ if self.description:
98
+ desc_parts.append(self.description)
99
+ if self.credential_status == "expired":
100
+ desc_parts.append("(credentials expired)")
101
+ elif self.credential_status == "expiring":
102
+ desc_parts.append("(credentials expiring)")
103
+
104
+ return ListItem(
105
+ value=self,
106
+ label=label,
107
+ description=" ".join(desc_parts),
108
+ governance_status=governance_status,
109
+ )
110
+
111
+
112
+ def list_teams(
113
+ cfg: dict[str, Any], org_config: dict[str, Any] | None = None
114
+ ) -> list[dict[str, Any]]:
115
+ """List available teams from configuration.
116
+
117
+ Args:
118
+ cfg: User config (used for legacy fallback)
119
+ org_config: Organization config with profiles. If provided, uses
120
+ NEW architecture. If None, falls back to legacy behavior.
121
+
122
+ Returns:
123
+ List of team dicts with name, description, plugin
124
+ """
125
+ # NEW architecture: use org_config for profiles
126
+ if org_config is not None:
127
+ profiles = org_config.get("profiles", {})
128
+ else:
129
+ # Legacy fallback
130
+ profiles = cfg.get("profiles", {})
131
+
132
+ teams = []
133
+ for name, info in profiles.items():
134
+ teams.append(
135
+ {
136
+ "name": name,
137
+ "description": info.get("description", ""),
138
+ "plugins": info.get("additional_plugins", []),
139
+ }
140
+ )
141
+
142
+ return teams
143
+
144
+
145
+ def get_team_details(
146
+ team: str, cfg: dict[str, Any], org_config: dict[str, Any] | None = None
147
+ ) -> dict[str, Any] | None:
148
+ """Get detailed information for a specific team.
149
+
150
+ Args:
151
+ team: Team/profile name.
152
+ cfg: User config (used for legacy fallback).
153
+ org_config: Organization config. If provided, uses NEW architecture.
154
+
155
+ Returns:
156
+ Team details dict, or None if team doesn't exist.
157
+ """
158
+ # NEW architecture: use org_config for profiles
159
+ if org_config is not None:
160
+ profiles = org_config.get("profiles", {})
161
+ # Marketplaces is a dict where keys are marketplace names
162
+ marketplaces = org_config.get("marketplaces", {})
163
+ else:
164
+ # Legacy fallback
165
+ profiles = cfg.get("profiles", {})
166
+ marketplaces = {}
167
+
168
+ team_info = profiles.get(team)
169
+ if not team_info:
170
+ return None
171
+
172
+ # Get plugins from new schema (additional_plugins is a list)
173
+ plugins = team_info.get("additional_plugins", [])
174
+
175
+ # Get marketplace info
176
+ if org_config is not None:
177
+ # NEW: look up marketplace by name from org_config
178
+ # Marketplace name can be explicit in profile, or inferred from plugins
179
+ marketplace_name = team_info.get("marketplace")
180
+ if marketplace_name and marketplace_name in marketplaces:
181
+ marketplace_info = marketplaces[marketplace_name]
182
+ # New schema: {"source": "github", "owner": "...", "repo": "..."}
183
+ return {
184
+ "name": team,
185
+ "description": team_info.get("description", ""),
186
+ "plugins": plugins,
187
+ "marketplace": marketplace_name,
188
+ "marketplace_type": marketplace_info.get("source"),
189
+ "marketplace_repo": marketplace_info.get("repo"),
190
+ }
191
+ else:
192
+ # No explicit marketplace - infer from first plugin if available
193
+ first_marketplace = None
194
+ if plugins:
195
+ for plugin_id in plugins:
196
+ if "@" in plugin_id:
197
+ first_marketplace = plugin_id.split("@")[1]
198
+ break
199
+ return {
200
+ "name": team,
201
+ "description": team_info.get("description", ""),
202
+ "plugins": plugins,
203
+ "marketplace": first_marketplace,
204
+ "marketplace_type": marketplaces.get(first_marketplace, {}).get("source")
205
+ if first_marketplace
206
+ else None,
207
+ "marketplace_repo": marketplaces.get(first_marketplace, {}).get("repo")
208
+ if first_marketplace
209
+ else None,
210
+ }
211
+ else:
212
+ # Legacy: single marketplace in cfg
213
+ marketplace = cfg.get("marketplace", {})
214
+ return {
215
+ "name": team,
216
+ "description": team_info.get("description", ""),
217
+ "plugins": plugins, # List of plugin identifiers
218
+ "marketplace": marketplace.get("name"),
219
+ "marketplace_repo": marketplace.get("repo"),
220
+ }
221
+
222
+
223
+ def get_team_sandbox_settings(team_name: str, cfg: dict[str, Any] | None = None) -> dict[str, Any]:
224
+ """Generate sandbox settings for a team profile.
225
+
226
+ Return settings.json content with extraKnownMarketplaces
227
+ and enabledPlugins configured for Claude Code.
228
+
229
+ This is the core function of the simplified architecture:
230
+ - SCC injects these settings into the Docker sandbox volume
231
+ - Claude Code sees extraKnownMarketplaces and fetches the marketplace
232
+ - Claude Code installs the specified plugins automatically
233
+ - Teams maintain their plugins in the marketplace repo
234
+
235
+ Args:
236
+ team_name: Name of the team profile (e.g., "api-team").
237
+ cfg: Optional config dict. If None, load from config file.
238
+
239
+ Returns:
240
+ Dict with extraKnownMarketplaces and enabledPlugins for settings.json.
241
+ Return empty dict if team has no plugins configured.
242
+ """
243
+ if cfg is None:
244
+ cfg = config_module.load_config()
245
+
246
+ profile = cfg.get("profiles", {}).get(team_name, {})
247
+ plugins = profile.get("additional_plugins", [])
248
+
249
+ # No plugins configured for this profile
250
+ if not plugins:
251
+ return {}
252
+
253
+ # Get marketplace config for building extraKnownMarketplaces
254
+ marketplace = cfg.get("marketplace", {})
255
+ marketplace_name = marketplace.get("name", "sundsvall")
256
+ marketplace_repo = marketplace.get("repo", "sundsvall/claude-plugins-marketplace")
257
+
258
+ # Generate settings that Claude Code understands
259
+ return {
260
+ "extraKnownMarketplaces": {
261
+ marketplace_name: {
262
+ "source": {
263
+ "source": "github",
264
+ "repo": marketplace_repo,
265
+ }
266
+ }
267
+ },
268
+ "enabledPlugins": plugins,
269
+ }
270
+
271
+
272
+ def get_team_plugin_id(team_name: str, cfg: dict[str, Any] | None = None) -> str | None:
273
+ """Get the first plugin ID for a team (e.g., "api-team@sundsvall").
274
+
275
+ For teams with multiple plugins, returns the first one.
276
+ Use get_team_plugins() to get all plugins.
277
+
278
+ Args:
279
+ team_name: Name of the team profile.
280
+ cfg: Optional config dict. If None, load from config file.
281
+
282
+ Returns:
283
+ First plugin ID string, or None if team has no plugins configured.
284
+ """
285
+ if cfg is None:
286
+ cfg = config_module.load_config()
287
+
288
+ profile = cfg.get("profiles", {}).get(team_name, {})
289
+ plugins: list[str] = profile.get("additional_plugins", [])
290
+
291
+ if not plugins:
292
+ return None
293
+
294
+ return plugins[0]
295
+
296
+
297
+ def get_team_plugins(team_name: str, cfg: dict[str, Any] | None = None) -> list[str]:
298
+ """Get all plugin IDs for a team.
299
+
300
+ Args:
301
+ team_name: Name of the team profile.
302
+ cfg: Optional config dict. If None, load from config file.
303
+
304
+ Returns:
305
+ List of plugin ID strings, or empty list if team has no plugins.
306
+ """
307
+ if cfg is None:
308
+ cfg = config_module.load_config()
309
+
310
+ profile = cfg.get("profiles", {}).get(team_name, {})
311
+ plugins: list[str] = profile.get("additional_plugins", [])
312
+ return plugins
313
+
314
+
315
+ def validate_team_profile(
316
+ team_name: str,
317
+ cfg: dict[str, Any] | None = None,
318
+ org_config: dict[str, Any] | None = None,
319
+ ) -> dict[str, Any]:
320
+ """Validate a team profile configuration.
321
+
322
+ Args:
323
+ team_name: Name of the team/profile to validate.
324
+ cfg: User config (for marketplace info when org_config not provided).
325
+ org_config: Organization config with profiles and marketplaces.
326
+ If provided, uses org_config for profiles. If None, reads
327
+ profiles from cfg.
328
+
329
+ Returns:
330
+ Dict with keys: valid (bool), team (str), plugins (list of str),
331
+ errors (list of str), warnings (list of str).
332
+ """
333
+ if cfg is None:
334
+ cfg = config_module.load_config()
335
+
336
+ result: dict[str, Any] = {
337
+ "valid": True,
338
+ "team": team_name,
339
+ "plugins": [],
340
+ "errors": [],
341
+ "warnings": [],
342
+ }
343
+
344
+ # Use org_config for profiles if provided, otherwise use cfg
345
+ if org_config is not None:
346
+ profiles = org_config.get("profiles", {})
347
+ marketplaces = org_config.get("marketplaces", {})
348
+ else:
349
+ profiles = cfg.get("profiles", {})
350
+ marketplaces = {}
351
+
352
+ # Check if team exists
353
+ if team_name not in profiles:
354
+ result["valid"] = False
355
+ result["errors"].append(f"Team '{team_name}' not found in profiles")
356
+ return result
357
+
358
+ profile = profiles[team_name]
359
+ result["plugins"] = profile.get("additional_plugins", [])
360
+
361
+ # Check marketplace configuration
362
+ if org_config is not None:
363
+ # Validate that plugins reference known marketplaces
364
+ for plugin_id in result["plugins"]:
365
+ if "@" in plugin_id:
366
+ marketplace_name = plugin_id.split("@")[1]
367
+ if marketplace_name not in marketplaces:
368
+ result["warnings"].append(
369
+ f"Marketplace '{marketplace_name}' for plugin '{plugin_id}' not found"
370
+ )
371
+ else:
372
+ # Check single marketplace config
373
+ marketplace = cfg.get("marketplace", {})
374
+ if not marketplace.get("repo"):
375
+ result["warnings"].append("No marketplace repo configured")
376
+
377
+ # Check if plugins are configured (not required for 'base' or 'default' profile)
378
+ if not result["plugins"] and team_name not in ("base", "default"):
379
+ result["warnings"].append(
380
+ f"Team '{team_name}' has no plugins configured - using base settings"
381
+ )
382
+
383
+ return result
@@ -0,0 +1,2 @@
1
+ # Templates package for SCC
2
+ # Contains template files like statusline.sh
File without changes
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://scc-cli.dev/schemas/org-v1.json",
3
+ "schema_version": "{{SCHEMA_VERSION}}",
4
+ "min_cli_version": "{{MIN_CLI_VERSION}}",
5
+ "organization": {
6
+ "name": "{{ORG_NAME}}",
7
+ "id": "{{ORG_NAME}}",
8
+ "contact": "admin@{{ORG_DOMAIN}}"
9
+ },
10
+ "defaults": {
11
+ "profile": "base",
12
+ "cache_ttl_hours": 24
13
+ },
14
+ "profiles": {
15
+ "base": {
16
+ "description": "Default profile with no plugins"
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "$schema": "https://scc-cli.dev/schemas/org-v1.json",
3
+ "schema_version": "{{SCHEMA_VERSION}}",
4
+ "min_cli_version": "{{MIN_CLI_VERSION}}",
5
+ "organization": {
6
+ "name": "{{ORG_NAME}}",
7
+ "id": "{{ORG_NAME}}",
8
+ "contact": "admin@{{ORG_DOMAIN}}"
9
+ },
10
+ "marketplaces": [
11
+ {
12
+ "name": "github-public",
13
+ "type": "github",
14
+ "repo": "{{ORG_NAME}}/claude-plugins-marketplace",
15
+ "ref": "main",
16
+ "auth": null
17
+ },
18
+ {
19
+ "name": "github-private",
20
+ "type": "github",
21
+ "repo": "{{ORG_NAME}}/claude-plugins-private",
22
+ "ref": "v1.0.0",
23
+ "auth": "env:GITHUB_TOKEN"
24
+ },
25
+ {
26
+ "name": "gitlab-self-hosted",
27
+ "type": "gitlab",
28
+ "host": "gitlab.{{ORG_DOMAIN}}",
29
+ "repo": "devtools/plugins",
30
+ "ref": "main",
31
+ "auth": "env:GITLAB_TOKEN"
32
+ },
33
+ {
34
+ "name": "custom-https",
35
+ "type": "https",
36
+ "url": "https://plugins.{{ORG_DOMAIN}}/marketplace.json",
37
+ "auth": "env:CUSTOM_API_KEY"
38
+ }
39
+ ],
40
+ "defaults": {
41
+ "profile": "base",
42
+ "cache_ttl_hours": 24
43
+ },
44
+ "profiles": {
45
+ "base": {
46
+ "description": "Default profile without plugins - safe baseline"
47
+ },
48
+ "platform": {
49
+ "description": "Platform engineering with infrastructure tools",
50
+ "plugin": "platform-tools",
51
+ "marketplace": "github-public"
52
+ },
53
+ "backend": {
54
+ "description": "Backend development with API and database tools",
55
+ "plugin": "backend-tools",
56
+ "marketplace": "github-public"
57
+ },
58
+ "frontend": {
59
+ "description": "Frontend development with UI component tools",
60
+ "plugin": "frontend-tools",
61
+ "marketplace": "github-public"
62
+ },
63
+ "security": {
64
+ "description": "Security team with audit and scanning tools",
65
+ "plugin": "security-tools",
66
+ "marketplace": "github-private"
67
+ },
68
+ "devops": {
69
+ "description": "DevOps with CI/CD and deployment tools",
70
+ "plugin": "devops-tools",
71
+ "marketplace": "gitlab-self-hosted"
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://scc-cli.dev/schemas/org-v1.json",
3
+ "schema_version": "{{SCHEMA_VERSION}}",
4
+ "min_cli_version": "{{MIN_CLI_VERSION}}",
5
+ "organization": {
6
+ "name": "{{ORG_NAME}}",
7
+ "id": "{{ORG_NAME}}",
8
+ "contact": "security@{{ORG_DOMAIN}}"
9
+ },
10
+ "marketplaces": [
11
+ {
12
+ "name": "{{ORG_NAME}}-secure",
13
+ "type": "github",
14
+ "repo": "{{ORG_NAME}}/claude-plugins-secure",
15
+ "ref": "v1.0.0",
16
+ "auth": "env:GITHUB_TOKEN"
17
+ }
18
+ ],
19
+ "defaults": {
20
+ "profile": "restricted",
21
+ "cache_ttl_hours": 1
22
+ },
23
+ "profiles": {
24
+ "restricted": {
25
+ "description": "Restricted access - security-audited plugins only"
26
+ },
27
+ "approved": {
28
+ "description": "Approved plugins for general development",
29
+ "plugin": "approved-base",
30
+ "marketplace": "{{ORG_NAME}}-secure"
31
+ },
32
+ "audit": {
33
+ "description": "Audit and compliance team",
34
+ "plugin": "audit-tools",
35
+ "marketplace": "{{ORG_NAME}}-secure"
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$schema": "https://scc-cli.dev/schemas/org-v1.json",
3
+ "schema_version": "{{SCHEMA_VERSION}}",
4
+ "min_cli_version": "{{MIN_CLI_VERSION}}",
5
+ "organization": {
6
+ "name": "{{ORG_NAME}}",
7
+ "id": "{{ORG_NAME}}",
8
+ "contact": "admin@{{ORG_DOMAIN}}"
9
+ },
10
+ "marketplaces": [
11
+ {
12
+ "name": "{{ORG_NAME}}-plugins",
13
+ "type": "github",
14
+ "repo": "{{ORG_NAME}}/claude-plugins-marketplace",
15
+ "ref": "main"
16
+ }
17
+ ],
18
+ "defaults": {
19
+ "profile": "base",
20
+ "cache_ttl_hours": 24
21
+ },
22
+ "profiles": {
23
+ "base": {
24
+ "description": "Default profile without team-specific plugins"
25
+ },
26
+ "platform": {
27
+ "description": "Platform engineering team",
28
+ "plugin": "platform-team",
29
+ "marketplace": "{{ORG_NAME}}-plugins"
30
+ },
31
+ "backend": {
32
+ "description": "Backend development team",
33
+ "plugin": "backend-team",
34
+ "marketplace": "{{ORG_NAME}}-plugins"
35
+ },
36
+ "frontend": {
37
+ "description": "Frontend development team",
38
+ "plugin": "frontend-team",
39
+ "marketplace": "{{ORG_NAME}}-plugins"
40
+ }
41
+ }
42
+ }