scc-cli 1.4.0__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 (112) 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 +259 -0
  8. scc_cli/cli_admin.py +683 -0
  9. scc_cli/cli_audit.py +245 -0
  10. scc_cli/cli_common.py +166 -0
  11. scc_cli/cli_config.py +527 -0
  12. scc_cli/cli_exceptions.py +705 -0
  13. scc_cli/cli_helpers.py +244 -0
  14. scc_cli/cli_init.py +272 -0
  15. scc_cli/cli_launch.py +1400 -0
  16. scc_cli/cli_org.py +1433 -0
  17. scc_cli/cli_support.py +322 -0
  18. scc_cli/cli_team.py +858 -0
  19. scc_cli/cli_worktree.py +865 -0
  20. scc_cli/config.py +583 -0
  21. scc_cli/console.py +562 -0
  22. scc_cli/constants.py +79 -0
  23. scc_cli/contexts.py +377 -0
  24. scc_cli/deprecation.py +54 -0
  25. scc_cli/deps.py +189 -0
  26. scc_cli/docker/__init__.py +127 -0
  27. scc_cli/docker/core.py +466 -0
  28. scc_cli/docker/credentials.py +726 -0
  29. scc_cli/docker/launch.py +603 -0
  30. scc_cli/doctor/__init__.py +99 -0
  31. scc_cli/doctor/checks.py +1082 -0
  32. scc_cli/doctor/render.py +346 -0
  33. scc_cli/doctor/types.py +66 -0
  34. scc_cli/errors.py +288 -0
  35. scc_cli/evaluation/__init__.py +27 -0
  36. scc_cli/evaluation/apply_exceptions.py +207 -0
  37. scc_cli/evaluation/evaluate.py +97 -0
  38. scc_cli/evaluation/models.py +80 -0
  39. scc_cli/exit_codes.py +55 -0
  40. scc_cli/git.py +1405 -0
  41. scc_cli/json_command.py +166 -0
  42. scc_cli/json_output.py +96 -0
  43. scc_cli/kinds.py +62 -0
  44. scc_cli/marketplace/__init__.py +123 -0
  45. scc_cli/marketplace/compute.py +377 -0
  46. scc_cli/marketplace/constants.py +87 -0
  47. scc_cli/marketplace/managed.py +135 -0
  48. scc_cli/marketplace/materialize.py +723 -0
  49. scc_cli/marketplace/normalize.py +548 -0
  50. scc_cli/marketplace/render.py +238 -0
  51. scc_cli/marketplace/resolve.py +459 -0
  52. scc_cli/marketplace/schema.py +502 -0
  53. scc_cli/marketplace/sync.py +257 -0
  54. scc_cli/marketplace/team_cache.py +195 -0
  55. scc_cli/marketplace/team_fetch.py +688 -0
  56. scc_cli/marketplace/trust.py +244 -0
  57. scc_cli/models/__init__.py +41 -0
  58. scc_cli/models/exceptions.py +273 -0
  59. scc_cli/models/plugin_audit.py +434 -0
  60. scc_cli/org_templates.py +269 -0
  61. scc_cli/output_mode.py +167 -0
  62. scc_cli/panels.py +113 -0
  63. scc_cli/platform.py +350 -0
  64. scc_cli/profiles.py +1034 -0
  65. scc_cli/remote.py +443 -0
  66. scc_cli/schemas/__init__.py +1 -0
  67. scc_cli/schemas/org-v1.schema.json +456 -0
  68. scc_cli/schemas/team-config.v1.schema.json +163 -0
  69. scc_cli/sessions.py +425 -0
  70. scc_cli/setup.py +582 -0
  71. scc_cli/source_resolver.py +470 -0
  72. scc_cli/stats.py +378 -0
  73. scc_cli/stores/__init__.py +13 -0
  74. scc_cli/stores/exception_store.py +251 -0
  75. scc_cli/subprocess_utils.py +88 -0
  76. scc_cli/teams.py +339 -0
  77. scc_cli/templates/__init__.py +2 -0
  78. scc_cli/templates/org/__init__.py +0 -0
  79. scc_cli/templates/org/minimal.json +19 -0
  80. scc_cli/templates/org/reference.json +74 -0
  81. scc_cli/templates/org/strict.json +38 -0
  82. scc_cli/templates/org/teams.json +42 -0
  83. scc_cli/templates/statusline.sh +75 -0
  84. scc_cli/theme.py +348 -0
  85. scc_cli/ui/__init__.py +124 -0
  86. scc_cli/ui/branding.py +68 -0
  87. scc_cli/ui/chrome.py +395 -0
  88. scc_cli/ui/dashboard/__init__.py +62 -0
  89. scc_cli/ui/dashboard/_dashboard.py +669 -0
  90. scc_cli/ui/dashboard/loaders.py +369 -0
  91. scc_cli/ui/dashboard/models.py +184 -0
  92. scc_cli/ui/dashboard/orchestrator.py +337 -0
  93. scc_cli/ui/formatters.py +443 -0
  94. scc_cli/ui/gate.py +350 -0
  95. scc_cli/ui/help.py +157 -0
  96. scc_cli/ui/keys.py +521 -0
  97. scc_cli/ui/list_screen.py +431 -0
  98. scc_cli/ui/picker.py +700 -0
  99. scc_cli/ui/prompts.py +200 -0
  100. scc_cli/ui/wizard.py +490 -0
  101. scc_cli/update.py +680 -0
  102. scc_cli/utils/__init__.py +39 -0
  103. scc_cli/utils/fixit.py +264 -0
  104. scc_cli/utils/fuzzy.py +124 -0
  105. scc_cli/utils/locks.py +101 -0
  106. scc_cli/utils/ttl.py +376 -0
  107. scc_cli/validate.py +455 -0
  108. scc_cli-1.4.0.dist-info/METADATA +369 -0
  109. scc_cli-1.4.0.dist-info/RECORD +112 -0
  110. scc_cli-1.4.0.dist-info/WHEEL +4 -0
  111. scc_cli-1.4.0.dist-info/entry_points.txt +2 -0
  112. scc_cli-1.4.0.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,339 @@
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
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
+ plugin: Optional plugin name 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
+ plugin: str | None = None
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
+ plugin=data.get("plugin"),
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
+ "plugin": info.get("plugin"),
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 = org_config.get("marketplaces", [])
162
+ else:
163
+ # Legacy fallback
164
+ profiles = cfg.get("profiles", {})
165
+ marketplaces = []
166
+
167
+ team_info = profiles.get(team)
168
+ if not team_info:
169
+ return None
170
+
171
+ # Get marketplace info
172
+ if org_config is not None:
173
+ # NEW: look up marketplace by name from org_config
174
+ marketplace_name = team_info.get("marketplace")
175
+ marketplace: dict[str, Any] = next(
176
+ (m for m in marketplaces if m.get("name") == marketplace_name),
177
+ {},
178
+ )
179
+ return {
180
+ "name": team,
181
+ "description": team_info.get("description", ""),
182
+ "plugin": team_info.get("plugin"),
183
+ "marketplace": marketplace.get("name"),
184
+ "marketplace_type": marketplace.get("type"),
185
+ "marketplace_repo": marketplace.get("repo"),
186
+ }
187
+ else:
188
+ # Legacy: single marketplace in cfg
189
+ marketplace = cfg.get("marketplace", {})
190
+ return {
191
+ "name": team,
192
+ "description": team_info.get("description", ""),
193
+ "plugin": team_info.get("plugin"),
194
+ "marketplace": marketplace.get("name"),
195
+ "marketplace_repo": marketplace.get("repo"),
196
+ }
197
+
198
+
199
+ def get_team_sandbox_settings(team_name: str, cfg: dict[str, Any] | None = None) -> dict[str, Any]:
200
+ """Generate sandbox settings for a team profile.
201
+
202
+ Return settings.json content with extraKnownMarketplaces
203
+ and enabledPlugins configured for Claude Code.
204
+
205
+ This is the core function of the simplified architecture:
206
+ - SCC injects these settings into the Docker sandbox volume
207
+ - Claude Code sees extraKnownMarketplaces and fetches the marketplace
208
+ - Claude Code installs the specified plugin automatically
209
+ - Teams maintain their plugins in the marketplace repo
210
+
211
+ Args:
212
+ team_name: Name of the team profile (e.g., "api-team").
213
+ cfg: Optional config dict. If None, load from config file.
214
+
215
+ Returns:
216
+ Dict with extraKnownMarketplaces and enabledPlugins for settings.json.
217
+ Return empty dict if team has no plugin configured.
218
+ """
219
+ if cfg is None:
220
+ cfg = config_module.load_config()
221
+
222
+ marketplace = cfg.get("marketplace", {})
223
+ marketplace_name = marketplace.get("name", "sundsvall")
224
+ marketplace_repo = marketplace.get("repo", "sundsvall/claude-plugins-marketplace")
225
+
226
+ profile = cfg.get("profiles", {}).get(team_name, {})
227
+ plugin_name = profile.get("plugin")
228
+
229
+ # No plugin configured for this profile
230
+ if not plugin_name:
231
+ return {}
232
+
233
+ # Generate settings that Claude Code understands
234
+ return {
235
+ "extraKnownMarketplaces": {
236
+ marketplace_name: {
237
+ "source": {
238
+ "source": "github",
239
+ "repo": marketplace_repo,
240
+ }
241
+ }
242
+ },
243
+ "enabledPlugins": [f"{plugin_name}@{marketplace_name}"],
244
+ }
245
+
246
+
247
+ def get_team_plugin_id(team_name: str, cfg: dict[str, Any] | None = None) -> str | None:
248
+ """Get the full plugin ID for a team (e.g., "api-team@sundsvall").
249
+
250
+ Args:
251
+ team_name: Name of the team profile.
252
+ cfg: Optional config dict. If None, load from config file.
253
+
254
+ Returns:
255
+ Full plugin ID string, or None if team has no plugin configured.
256
+ """
257
+ if cfg is None:
258
+ cfg = config_module.load_config()
259
+
260
+ marketplace = cfg.get("marketplace", {})
261
+ marketplace_name = marketplace.get("name", "sundsvall")
262
+
263
+ profile = cfg.get("profiles", {}).get(team_name, {})
264
+ plugin_name = profile.get("plugin")
265
+
266
+ if not plugin_name:
267
+ return None
268
+
269
+ return f"{plugin_name}@{marketplace_name}"
270
+
271
+
272
+ def validate_team_profile(
273
+ team_name: str,
274
+ cfg: dict[str, Any] | None = None,
275
+ org_config: dict[str, Any] | None = None,
276
+ ) -> dict[str, Any]:
277
+ """Validate a team profile configuration.
278
+
279
+ Args:
280
+ team_name: Name of the team/profile to validate.
281
+ cfg: User config (deprecated, kept for backward compatibility).
282
+ org_config: Organization config with profiles and marketplaces.
283
+ If provided, use NEW architecture. If None, fall back to
284
+ legacy behavior (reading profiles from cfg).
285
+
286
+ Returns:
287
+ Dict with keys: valid (bool), team (str), plugin (str or None),
288
+ errors (list of str), warnings (list of str).
289
+ """
290
+ if cfg is None:
291
+ cfg = config_module.load_config()
292
+
293
+ result: dict[str, Any] = {
294
+ "valid": True,
295
+ "team": team_name,
296
+ "plugin": None,
297
+ "errors": [],
298
+ "warnings": [],
299
+ }
300
+
301
+ # NEW architecture: use org_config for profiles
302
+ if org_config is not None:
303
+ profiles = org_config.get("profiles", {})
304
+ marketplaces = org_config.get("marketplaces", [])
305
+ else:
306
+ # Legacy fallback: read from user config (deprecated)
307
+ profiles = cfg.get("profiles", {})
308
+ marketplaces = []
309
+
310
+ # Check if team exists
311
+ if team_name not in profiles:
312
+ result["valid"] = False
313
+ result["errors"].append(f"Team '{team_name}' not found in profiles")
314
+ return result
315
+
316
+ profile = profiles[team_name]
317
+ result["plugin"] = profile.get("plugin")
318
+
319
+ # Check marketplace configuration (NEW architecture)
320
+ if org_config is not None:
321
+ marketplace_name = profile.get("marketplace")
322
+ if marketplace_name:
323
+ # Find the marketplace in org_config
324
+ marketplace_found = any(m.get("name") == marketplace_name for m in marketplaces)
325
+ if not marketplace_found:
326
+ result["warnings"].append(f"Marketplace '{marketplace_name}' not found")
327
+ else:
328
+ # Legacy: check single marketplace
329
+ marketplace = cfg.get("marketplace", {})
330
+ if not marketplace.get("repo"):
331
+ result["warnings"].append("No marketplace repo configured")
332
+
333
+ # Check if plugin is configured (not required for 'base' profile)
334
+ if not result["plugin"] and team_name != "base":
335
+ result["warnings"].append(
336
+ f"Team '{team_name}' has no plugin configured - using base settings"
337
+ )
338
+
339
+ 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
+ }
@@ -0,0 +1,75 @@
1
+ #!/bin/bash
2
+ # SCC Status Line for Claude Code
3
+ # Shows: Model | Git branch/worktree | Lines changed
4
+ #
5
+ # Install: scc statusline --install
6
+ # This script receives JSON from Claude Code via stdin
7
+
8
+ # Read JSON input from stdin
9
+ input=$(cat)
10
+
11
+ # Extract values using jq
12
+ MODEL=$(echo "$input" | jq -r '.model.display_name // "Unknown"')
13
+ CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir // "."')
14
+ PROJECT_DIR=$(echo "$input" | jq -r '.workspace.project_dir // "."')
15
+ LINES_ADDED=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
16
+ LINES_REMOVED=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
17
+
18
+ # Colors
19
+ CYAN="\033[36m"
20
+ GREEN="\033[32m"
21
+ RED="\033[31m"
22
+ MAGENTA="\033[35m"
23
+ YELLOW="\033[33m"
24
+ WHITE="\033[1;37m"
25
+ DIM="\033[2m"
26
+ RESET="\033[0m"
27
+
28
+ # Git information
29
+ GIT_INFO=""
30
+ cd "$CURRENT_DIR" 2>/dev/null || cd "$PROJECT_DIR" 2>/dev/null
31
+
32
+ if git rev-parse --git-dir > /dev/null 2>&1; then
33
+ # Get branch name
34
+ BRANCH=$(git branch --show-current 2>/dev/null)
35
+ if [ -z "$BRANCH" ]; then
36
+ # Detached HEAD - show short SHA
37
+ BRANCH=$(git rev-parse --short HEAD 2>/dev/null || echo "detached")
38
+ fi
39
+
40
+ # Check if we're in a worktree
41
+ GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
42
+
43
+ if [[ "$GIT_DIR" == *".git/worktrees/"* ]]; then
44
+ # In a worktree - show ⎇ icon
45
+ WORKTREE_NAME=$(basename "$(dirname "$GIT_DIR")" 2>/dev/null)
46
+ GIT_INFO="${MAGENTA}⎇ ${WORKTREE_NAME}${RESET}:${CYAN}${BRANCH}${RESET}"
47
+ else
48
+ # Regular repo - show 🌿 icon
49
+ GIT_INFO="${CYAN}🌿 ${BRANCH}${RESET}"
50
+ fi
51
+
52
+ # Check for uncommitted changes
53
+ if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
54
+ GIT_INFO="${GIT_INFO}${YELLOW}*${RESET}"
55
+ fi
56
+ fi
57
+
58
+ # Lines changed (only show if any changes made)
59
+ LINES_INFO=""
60
+ if [ "$LINES_ADDED" -gt 0 ] || [ "$LINES_REMOVED" -gt 0 ]; then
61
+ LINES_INFO=" ${DIM}|${RESET} ${GREEN}+${LINES_ADDED}${RESET} ${RED}-${LINES_REMOVED}${RESET}"
62
+ fi
63
+
64
+ # Build the status line
65
+ # Format: [Model] 🌿 branch* | +156 -23
66
+ OUTPUT="${WHITE}[${MODEL}]${RESET}"
67
+
68
+ if [ -n "$GIT_INFO" ]; then
69
+ OUTPUT="${OUTPUT} ${GIT_INFO}"
70
+ fi
71
+
72
+ OUTPUT="${OUTPUT}${LINES_INFO}"
73
+
74
+ # Output (printf handles escape codes)
75
+ printf "%b\n" "$OUTPUT"