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,506 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models for marketplace organization configuration.
|
|
3
|
+
|
|
4
|
+
This module defines the data models for:
|
|
5
|
+
- MarketplaceSource: Discriminated union for GitHub, Git, URL, Directory sources
|
|
6
|
+
- OrganizationConfig: Complete org config with marketplaces, defaults, profiles, security
|
|
7
|
+
- TeamProfile: Team-specific plugin configuration
|
|
8
|
+
- SecurityConfig: Organization security policies
|
|
9
|
+
- DefaultsConfig: Organization-wide defaults
|
|
10
|
+
|
|
11
|
+
All models support JSON serialization/deserialization via Pydantic v2.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Annotated, Literal
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field, field_validator
|
|
19
|
+
|
|
20
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
# Marketplace Source Models
|
|
22
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MarketplaceSourceGitHub(BaseModel):
|
|
26
|
+
"""GitHub repository marketplace source.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> source = MarketplaceSourceGitHub(
|
|
30
|
+
... source="github",
|
|
31
|
+
... owner="sundsvall",
|
|
32
|
+
... repo="claude-plugins",
|
|
33
|
+
... branch="main",
|
|
34
|
+
... )
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
source: Literal["github"]
|
|
38
|
+
owner: Annotated[
|
|
39
|
+
str,
|
|
40
|
+
Field(
|
|
41
|
+
pattern=r"^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$",
|
|
42
|
+
description="GitHub organization or user name",
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
repo: Annotated[
|
|
46
|
+
str,
|
|
47
|
+
Field(
|
|
48
|
+
pattern=r"^[a-zA-Z0-9._-]+$",
|
|
49
|
+
description="GitHub repository name",
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
branch: str = Field(default="main", description="Git branch to use")
|
|
53
|
+
path: str = Field(default="/", description="Path within repository to marketplace root")
|
|
54
|
+
headers: dict[str, str] | None = Field(
|
|
55
|
+
default=None,
|
|
56
|
+
description="HTTP headers for authentication (supports ${VAR} expansion)",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MarketplaceSourceGit(BaseModel):
|
|
61
|
+
"""Generic Git repository marketplace source.
|
|
62
|
+
|
|
63
|
+
Supports both HTTPS and SSH URLs.
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> source = MarketplaceSourceGit(
|
|
67
|
+
... source="git",
|
|
68
|
+
... url="https://gitlab.example.se/ai/plugins.git",
|
|
69
|
+
... )
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
source: Literal["git"]
|
|
73
|
+
url: Annotated[
|
|
74
|
+
str,
|
|
75
|
+
Field(
|
|
76
|
+
pattern=r"^(https://|git@)",
|
|
77
|
+
description="Git clone URL (HTTPS or SSH)",
|
|
78
|
+
),
|
|
79
|
+
]
|
|
80
|
+
branch: str = Field(default="main", description="Git branch to use")
|
|
81
|
+
path: str = Field(default="/", description="Path within repository to marketplace root")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MarketplaceSourceURL(BaseModel):
|
|
85
|
+
"""URL-based marketplace source.
|
|
86
|
+
|
|
87
|
+
Downloads marketplace from HTTPS URL. HTTP is forbidden for security.
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
>>> source = MarketplaceSourceURL(
|
|
91
|
+
... source="url",
|
|
92
|
+
... url="https://plugins.sundsvall.se/marketplace.json",
|
|
93
|
+
... )
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
source: Literal["url"]
|
|
97
|
+
url: Annotated[
|
|
98
|
+
str,
|
|
99
|
+
Field(
|
|
100
|
+
pattern=r"^https://",
|
|
101
|
+
description="HTTPS URL to marketplace manifest (HTTP forbidden)",
|
|
102
|
+
),
|
|
103
|
+
]
|
|
104
|
+
headers: dict[str, str] | None = Field(
|
|
105
|
+
default=None,
|
|
106
|
+
description="HTTP headers for authentication (supports ${VAR} expansion)",
|
|
107
|
+
)
|
|
108
|
+
materialization_mode: Literal["self_contained", "metadata_only", "best_effort"] = Field(
|
|
109
|
+
default="self_contained",
|
|
110
|
+
description="How to fetch marketplace content",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class MarketplaceSourceDirectory(BaseModel):
|
|
115
|
+
"""Local directory marketplace source.
|
|
116
|
+
|
|
117
|
+
Points to a local filesystem path containing marketplace plugins.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> source = MarketplaceSourceDirectory(
|
|
121
|
+
... source="directory",
|
|
122
|
+
... path="/opt/scc/marketplaces/internal",
|
|
123
|
+
... )
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
source: Literal["directory"]
|
|
127
|
+
path: str = Field(description="Local filesystem path (absolute or relative to org config)")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Discriminated union for all marketplace source types
|
|
131
|
+
MarketplaceSource = Annotated[
|
|
132
|
+
MarketplaceSourceGitHub
|
|
133
|
+
| MarketplaceSourceGit
|
|
134
|
+
| MarketplaceSourceURL
|
|
135
|
+
| MarketplaceSourceDirectory,
|
|
136
|
+
Field(discriminator="source"),
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
141
|
+
# Config Source Models (Phase 2: Federation)
|
|
142
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ConfigSourceGitHub(BaseModel):
|
|
146
|
+
"""GitHub repository config source for team config files.
|
|
147
|
+
|
|
148
|
+
Similar to MarketplaceSourceGitHub but with path defaulting to ""
|
|
149
|
+
(empty string) instead of "/" to avoid path join issues.
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> source = ConfigSourceGitHub(
|
|
153
|
+
... source="github",
|
|
154
|
+
... owner="sundsvall-backend",
|
|
155
|
+
... repo="team-config",
|
|
156
|
+
... )
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
source: Literal["github"]
|
|
160
|
+
owner: Annotated[
|
|
161
|
+
str,
|
|
162
|
+
Field(
|
|
163
|
+
pattern=r"^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$",
|
|
164
|
+
description="GitHub organization or user name",
|
|
165
|
+
),
|
|
166
|
+
]
|
|
167
|
+
repo: Annotated[
|
|
168
|
+
str,
|
|
169
|
+
Field(
|
|
170
|
+
pattern=r"^[a-zA-Z0-9._-]+$",
|
|
171
|
+
description="GitHub repository name",
|
|
172
|
+
),
|
|
173
|
+
]
|
|
174
|
+
branch: str = Field(default="main", description="Git branch to use")
|
|
175
|
+
path: str = Field(default="", description="Path within repository to config file")
|
|
176
|
+
headers: dict[str, str] | None = Field(
|
|
177
|
+
default=None,
|
|
178
|
+
description="HTTP headers for authentication (supports ${VAR} expansion)",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ConfigSourceGit(BaseModel):
|
|
183
|
+
"""Generic Git repository config source for team config files.
|
|
184
|
+
|
|
185
|
+
Similar to MarketplaceSourceGit but with path defaulting to ""
|
|
186
|
+
(empty string) instead of "/" to avoid path join issues.
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
>>> source = ConfigSourceGit(
|
|
190
|
+
... source="git",
|
|
191
|
+
... url="https://gitlab.sundsvall.se/teams/backend-config.git",
|
|
192
|
+
... )
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
source: Literal["git"]
|
|
196
|
+
url: Annotated[
|
|
197
|
+
str,
|
|
198
|
+
Field(
|
|
199
|
+
pattern=r"^(https://|git@)",
|
|
200
|
+
description="Git clone URL (HTTPS or SSH)",
|
|
201
|
+
),
|
|
202
|
+
]
|
|
203
|
+
branch: str = Field(default="main", description="Git branch to use")
|
|
204
|
+
path: str = Field(default="", description="Path within repository to config file")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class ConfigSourceURL(BaseModel):
|
|
208
|
+
"""URL-based config source for team config files.
|
|
209
|
+
|
|
210
|
+
Downloads team config from HTTPS URL. HTTP is forbidden for security.
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
>>> source = ConfigSourceURL(
|
|
214
|
+
... source="url",
|
|
215
|
+
... url="https://teams.sundsvall.se/backend/team-config.json",
|
|
216
|
+
... )
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
source: Literal["url"]
|
|
220
|
+
url: Annotated[
|
|
221
|
+
str,
|
|
222
|
+
Field(
|
|
223
|
+
pattern=r"^https://",
|
|
224
|
+
description="HTTPS URL to team config (HTTP forbidden)",
|
|
225
|
+
),
|
|
226
|
+
]
|
|
227
|
+
headers: dict[str, str] | None = Field(
|
|
228
|
+
default=None,
|
|
229
|
+
description="HTTP headers for authentication (supports ${VAR} expansion)",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# Discriminated union for all config source types
|
|
234
|
+
# Note: No directory source for security (teams can't reference local paths)
|
|
235
|
+
ConfigSource = Annotated[
|
|
236
|
+
ConfigSourceGitHub | ConfigSourceGit | ConfigSourceURL,
|
|
237
|
+
Field(discriminator="source"),
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
242
|
+
# Trust Grant Model (Phase 2: Federation)
|
|
243
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class TrustGrant(BaseModel):
|
|
247
|
+
"""Trust delegation from org to team.
|
|
248
|
+
|
|
249
|
+
Controls what marketplaces a federated team can use:
|
|
250
|
+
- inherit_org_marketplaces: Whether team can use org-defined marketplaces
|
|
251
|
+
- allow_additional_marketplaces: Whether team can define own marketplaces
|
|
252
|
+
- marketplace_source_patterns: URL patterns allowed for team marketplaces
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
>>> trust = TrustGrant(
|
|
256
|
+
... inherit_org_marketplaces=True,
|
|
257
|
+
... allow_additional_marketplaces=True,
|
|
258
|
+
... marketplace_source_patterns=["github.com/sundsvall-*/**"],
|
|
259
|
+
... )
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
inherit_org_marketplaces: bool = Field(
|
|
263
|
+
default=True,
|
|
264
|
+
description="Whether team inherits org-level marketplace definitions",
|
|
265
|
+
)
|
|
266
|
+
allow_additional_marketplaces: bool = Field(
|
|
267
|
+
default=False,
|
|
268
|
+
description="Whether team can define additional marketplaces",
|
|
269
|
+
)
|
|
270
|
+
marketplace_source_patterns: list[str] = Field(
|
|
271
|
+
default_factory=list,
|
|
272
|
+
description="URL patterns (with globstar) allowed for team marketplaces",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
277
|
+
# Configuration Models
|
|
278
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class DefaultsConfig(BaseModel):
|
|
282
|
+
"""Organization-wide default settings.
|
|
283
|
+
|
|
284
|
+
These settings apply to all teams unless overridden.
|
|
285
|
+
|
|
286
|
+
Semantics for allowed_plugins:
|
|
287
|
+
- None (missing): Unrestricted - all plugins allowed
|
|
288
|
+
- []: Deny all - no plugins allowed
|
|
289
|
+
- ["*"]: Explicit unrestricted via wildcard
|
|
290
|
+
- ["pattern@marketplace"]: Specific whitelist with fnmatch patterns
|
|
291
|
+
|
|
292
|
+
Example:
|
|
293
|
+
>>> defaults = DefaultsConfig(
|
|
294
|
+
... enabled_plugins=["code-review@internal"],
|
|
295
|
+
... disabled_plugins=["debug-*"],
|
|
296
|
+
... allowed_plugins=["*@internal"], # Only internal marketplace
|
|
297
|
+
... )
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
# Governance field
|
|
301
|
+
allowed_plugins: list[str] | None = Field(
|
|
302
|
+
default=None,
|
|
303
|
+
description="Allowed plugins (None=unrestricted, []=deny all, ['*']=explicit unrestricted)",
|
|
304
|
+
)
|
|
305
|
+
# Activation fields
|
|
306
|
+
enabled_plugins: list[str] = Field(
|
|
307
|
+
default_factory=list,
|
|
308
|
+
description="Plugins enabled for all teams by default",
|
|
309
|
+
)
|
|
310
|
+
disabled_plugins: list[str] = Field(
|
|
311
|
+
default_factory=list,
|
|
312
|
+
description="Glob patterns for plugins disabled by default",
|
|
313
|
+
)
|
|
314
|
+
extra_marketplaces: list[str] = Field(
|
|
315
|
+
default_factory=list,
|
|
316
|
+
description="Marketplaces exposed to all teams (for browsing, not auto-enabling)",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class TeamConfig(BaseModel):
|
|
321
|
+
"""External team configuration file (Phase 2: Federation).
|
|
322
|
+
|
|
323
|
+
This is the schema for team-config.json files stored in team repos.
|
|
324
|
+
Teams define their own plugins and marketplaces here.
|
|
325
|
+
|
|
326
|
+
Example:
|
|
327
|
+
>>> config = TeamConfig(
|
|
328
|
+
... schema_version=1,
|
|
329
|
+
... enabled_plugins=["custom-tool@team-mp"],
|
|
330
|
+
... marketplaces={"team-mp": github_source},
|
|
331
|
+
... )
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
schema_version: Annotated[
|
|
335
|
+
int,
|
|
336
|
+
Field(description="Schema version for forward compatibility"),
|
|
337
|
+
]
|
|
338
|
+
enabled_plugins: list[str] = Field(
|
|
339
|
+
default_factory=list,
|
|
340
|
+
description="Plugins enabled by this team config",
|
|
341
|
+
)
|
|
342
|
+
disabled_plugins: list[str] = Field(
|
|
343
|
+
default_factory=list,
|
|
344
|
+
description="Glob patterns for plugins to disable",
|
|
345
|
+
)
|
|
346
|
+
marketplaces: dict[str, MarketplaceSource] = Field(
|
|
347
|
+
default_factory=dict,
|
|
348
|
+
description="Team-defined marketplace sources",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
@field_validator("schema_version")
|
|
352
|
+
@classmethod
|
|
353
|
+
def validate_schema_version(cls, v: int) -> int:
|
|
354
|
+
"""Ensure schema version is supported."""
|
|
355
|
+
if v != 1:
|
|
356
|
+
msg = f"Unsupported schema_version: {v}. Only version 1 is supported."
|
|
357
|
+
raise ValueError(msg)
|
|
358
|
+
return v
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class TeamProfile(BaseModel):
|
|
362
|
+
"""Team-specific configuration.
|
|
363
|
+
|
|
364
|
+
Teams inherit org defaults and can add/disable plugins.
|
|
365
|
+
allowed_plugins provides an allowlist filter (null = allow all).
|
|
366
|
+
|
|
367
|
+
Phase 2 adds optional federation fields:
|
|
368
|
+
- config_source: External config location (if set, team is federated)
|
|
369
|
+
- trust: Trust delegation controls for federated teams
|
|
370
|
+
|
|
371
|
+
Example:
|
|
372
|
+
>>> profile = TeamProfile(
|
|
373
|
+
... description="Backend Team",
|
|
374
|
+
... additional_plugins=["api-tools@internal"],
|
|
375
|
+
... )
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
# Note: 'name' is optional - the display name comes from the profile key
|
|
379
|
+
# or can be explicitly set here for a custom display name
|
|
380
|
+
name: str | None = Field(
|
|
381
|
+
default=None, description="Optional team display name (defaults to profile key)"
|
|
382
|
+
)
|
|
383
|
+
description: str = Field(default="", description="Team description for UI display")
|
|
384
|
+
additional_plugins: list[str] = Field(
|
|
385
|
+
default_factory=list,
|
|
386
|
+
description="Plugins to add beyond org defaults",
|
|
387
|
+
)
|
|
388
|
+
disabled_plugins: list[str] = Field(
|
|
389
|
+
default_factory=list,
|
|
390
|
+
description="Glob patterns for plugins to remove from defaults",
|
|
391
|
+
)
|
|
392
|
+
allowed_plugins: list[str] | None = Field(
|
|
393
|
+
default=None,
|
|
394
|
+
description="Allowlist (null = allow all, [] = allow none)",
|
|
395
|
+
)
|
|
396
|
+
extra_marketplaces: list[str] = Field(
|
|
397
|
+
default_factory=list,
|
|
398
|
+
description="Additional marketplaces for this team",
|
|
399
|
+
)
|
|
400
|
+
# Phase 2: Federation fields
|
|
401
|
+
config_source: ConfigSource | None = Field(
|
|
402
|
+
default=None,
|
|
403
|
+
description="External config source (if set, team is federated)",
|
|
404
|
+
)
|
|
405
|
+
trust: TrustGrant | None = Field(
|
|
406
|
+
default=None,
|
|
407
|
+
description="Trust delegation controls for federated teams",
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class SecurityConfig(BaseModel):
|
|
412
|
+
"""Organization security policies.
|
|
413
|
+
|
|
414
|
+
Blocked plugins are enforced org-wide and cannot be overridden by teams.
|
|
415
|
+
|
|
416
|
+
Example:
|
|
417
|
+
>>> security = SecurityConfig(
|
|
418
|
+
... blocked_plugins=["risky-tool@*"],
|
|
419
|
+
... blocked_reason="Security review pending",
|
|
420
|
+
... )
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
blocked_plugins: list[str] = Field(
|
|
424
|
+
default_factory=list,
|
|
425
|
+
description="Glob patterns for plugins blocked org-wide",
|
|
426
|
+
)
|
|
427
|
+
blocked_reason: str = Field(
|
|
428
|
+
default="Blocked by organization policy",
|
|
429
|
+
description="Message shown when plugin is blocked",
|
|
430
|
+
)
|
|
431
|
+
allow_implicit_marketplaces: bool = Field(
|
|
432
|
+
default=True,
|
|
433
|
+
description="Whether to allow claude-plugins-official",
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
438
|
+
# Organization Configuration
|
|
439
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class OrganizationConfig(BaseModel):
|
|
443
|
+
"""Complete organization configuration.
|
|
444
|
+
|
|
445
|
+
This is the top-level model representing an org.json configuration file.
|
|
446
|
+
|
|
447
|
+
Example:
|
|
448
|
+
>>> config = OrganizationConfig(
|
|
449
|
+
... name="Sundsvall Municipality",
|
|
450
|
+
... schema_version=1,
|
|
451
|
+
... marketplaces={"internal": source},
|
|
452
|
+
... profiles={"backend": profile},
|
|
453
|
+
... )
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
name: Annotated[str, Field(min_length=1, description="Organization display name")]
|
|
457
|
+
schema_version: Annotated[
|
|
458
|
+
int,
|
|
459
|
+
Field(
|
|
460
|
+
description="Schema version for forward compatibility",
|
|
461
|
+
),
|
|
462
|
+
]
|
|
463
|
+
marketplaces: dict[str, MarketplaceSource] = Field(
|
|
464
|
+
default_factory=dict,
|
|
465
|
+
description="Named marketplace sources (key = marketplace name)",
|
|
466
|
+
)
|
|
467
|
+
defaults: DefaultsConfig = Field(
|
|
468
|
+
default_factory=DefaultsConfig,
|
|
469
|
+
description="Organization-wide default settings",
|
|
470
|
+
)
|
|
471
|
+
profiles: dict[str, TeamProfile] = Field(
|
|
472
|
+
default_factory=dict,
|
|
473
|
+
description="Team profiles (key = profile name)",
|
|
474
|
+
)
|
|
475
|
+
security: SecurityConfig = Field(
|
|
476
|
+
default_factory=SecurityConfig,
|
|
477
|
+
description="Organization security policies",
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
@field_validator("schema_version")
|
|
481
|
+
@classmethod
|
|
482
|
+
def validate_schema_version(cls, v: int) -> int:
|
|
483
|
+
"""Ensure schema version is supported."""
|
|
484
|
+
if v != 1:
|
|
485
|
+
msg = f"Unsupported schema_version: {v}. Only version 1 is supported."
|
|
486
|
+
raise ValueError(msg)
|
|
487
|
+
return v
|
|
488
|
+
|
|
489
|
+
def get_team(self, team_id: str) -> TeamProfile | None:
|
|
490
|
+
"""Get a team profile by ID.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
team_id: The profile key (not display name)
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
TeamProfile if found, None otherwise
|
|
497
|
+
"""
|
|
498
|
+
return self.profiles.get(team_id)
|
|
499
|
+
|
|
500
|
+
def list_teams(self) -> list[str]:
|
|
501
|
+
"""List all team profile IDs.
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
List of profile keys (not display names)
|
|
505
|
+
"""
|
|
506
|
+
return list(self.profiles.keys())
|