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,267 @@
|
|
|
1
|
+
"""Organization import command implementation.
|
|
2
|
+
|
|
3
|
+
Provides the org import command for importing organization configurations
|
|
4
|
+
from URLs or shorthands.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
import typer
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from ...cli_common import console, handle_errors
|
|
17
|
+
from ...config import load_user_config, save_user_config
|
|
18
|
+
from ...core.exit_codes import EXIT_CONFIG, EXIT_VALIDATION
|
|
19
|
+
from ...json_output import build_envelope
|
|
20
|
+
from ...kinds import Kind
|
|
21
|
+
from ...output_mode import json_output_mode, print_json, set_pretty_mode
|
|
22
|
+
from ...panels import create_error_panel, create_success_panel
|
|
23
|
+
from ...remote import save_to_cache
|
|
24
|
+
from ...source_resolver import ResolveError, resolve_source
|
|
25
|
+
from ...validate import validate_org_config
|
|
26
|
+
from ._builders import build_import_preview_data
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@handle_errors
|
|
33
|
+
def org_import_cmd(
|
|
34
|
+
source: str = typer.Argument(..., help="URL or shorthand (e.g., github:org/repo)"),
|
|
35
|
+
preview: bool = typer.Option(False, "--preview", "-p", help="Preview import without saving"),
|
|
36
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON envelope"),
|
|
37
|
+
pretty: bool = typer.Option(False, "--pretty", help="Pretty-print JSON (implies --json)"),
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Import an organization configuration from a URL.
|
|
40
|
+
|
|
41
|
+
Supports direct URLs and shorthands like github:org/repo.
|
|
42
|
+
Use --preview to validate without saving.
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
scc org import https://example.com/org-config.json
|
|
46
|
+
scc org import github:acme/configs
|
|
47
|
+
scc org import github:acme/configs --preview
|
|
48
|
+
scc org import https://example.com/org.json --json
|
|
49
|
+
"""
|
|
50
|
+
# --pretty implies --json
|
|
51
|
+
if pretty:
|
|
52
|
+
json_output = True
|
|
53
|
+
set_pretty_mode(True)
|
|
54
|
+
|
|
55
|
+
# Resolve source URL (handles shorthands like github:org/repo)
|
|
56
|
+
resolved = resolve_source(source)
|
|
57
|
+
if isinstance(resolved, ResolveError):
|
|
58
|
+
error_msg = resolved.message
|
|
59
|
+
if resolved.suggestion:
|
|
60
|
+
error_msg = f"{resolved.message}\n{resolved.suggestion}"
|
|
61
|
+
if json_output:
|
|
62
|
+
with json_output_mode():
|
|
63
|
+
envelope = build_envelope(
|
|
64
|
+
Kind.ORG_IMPORT_PREVIEW if preview else Kind.ORG_IMPORT,
|
|
65
|
+
data={"error": error_msg, "source": source},
|
|
66
|
+
ok=False,
|
|
67
|
+
errors=[error_msg],
|
|
68
|
+
)
|
|
69
|
+
print_json(envelope)
|
|
70
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
71
|
+
console.print(create_error_panel("Invalid Source", error_msg))
|
|
72
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
73
|
+
|
|
74
|
+
resolved_url = resolved.resolved_url
|
|
75
|
+
|
|
76
|
+
# Fetch the config from URL
|
|
77
|
+
try:
|
|
78
|
+
response = requests.get(resolved_url, timeout=30)
|
|
79
|
+
except requests.RequestException as e:
|
|
80
|
+
error_msg = f"Failed to fetch config: {e}"
|
|
81
|
+
if json_output:
|
|
82
|
+
with json_output_mode():
|
|
83
|
+
envelope = build_envelope(
|
|
84
|
+
Kind.ORG_IMPORT_PREVIEW if preview else Kind.ORG_IMPORT,
|
|
85
|
+
data={"error": error_msg, "source": source, "resolved_url": resolved_url},
|
|
86
|
+
ok=False,
|
|
87
|
+
errors=[error_msg],
|
|
88
|
+
)
|
|
89
|
+
print_json(envelope)
|
|
90
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
91
|
+
console.print(create_error_panel("Network Error", error_msg))
|
|
92
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
93
|
+
|
|
94
|
+
# Check HTTP status
|
|
95
|
+
if response.status_code == 404:
|
|
96
|
+
error_msg = f"Config not found at {resolved_url}"
|
|
97
|
+
if json_output:
|
|
98
|
+
with json_output_mode():
|
|
99
|
+
envelope = build_envelope(
|
|
100
|
+
Kind.ORG_IMPORT_PREVIEW if preview else Kind.ORG_IMPORT,
|
|
101
|
+
data={"error": error_msg, "source": source, "resolved_url": resolved_url},
|
|
102
|
+
ok=False,
|
|
103
|
+
errors=[error_msg],
|
|
104
|
+
)
|
|
105
|
+
print_json(envelope)
|
|
106
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
107
|
+
console.print(create_error_panel("Not Found", error_msg))
|
|
108
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
109
|
+
|
|
110
|
+
if response.status_code != 200:
|
|
111
|
+
error_msg = f"HTTP {response.status_code} from {resolved_url}"
|
|
112
|
+
if json_output:
|
|
113
|
+
with json_output_mode():
|
|
114
|
+
envelope = build_envelope(
|
|
115
|
+
Kind.ORG_IMPORT_PREVIEW if preview else Kind.ORG_IMPORT,
|
|
116
|
+
data={"error": error_msg, "source": source, "resolved_url": resolved_url},
|
|
117
|
+
ok=False,
|
|
118
|
+
errors=[error_msg],
|
|
119
|
+
)
|
|
120
|
+
print_json(envelope)
|
|
121
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
122
|
+
console.print(create_error_panel("HTTP Error", error_msg))
|
|
123
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
124
|
+
|
|
125
|
+
# Parse JSON response
|
|
126
|
+
try:
|
|
127
|
+
config = response.json()
|
|
128
|
+
except json.JSONDecodeError as e:
|
|
129
|
+
error_msg = f"Invalid JSON in response: {e}"
|
|
130
|
+
if json_output:
|
|
131
|
+
with json_output_mode():
|
|
132
|
+
envelope = build_envelope(
|
|
133
|
+
Kind.ORG_IMPORT_PREVIEW if preview else Kind.ORG_IMPORT,
|
|
134
|
+
data={"error": error_msg, "source": source, "resolved_url": resolved_url},
|
|
135
|
+
ok=False,
|
|
136
|
+
errors=[error_msg],
|
|
137
|
+
)
|
|
138
|
+
print_json(envelope)
|
|
139
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
140
|
+
console.print(create_error_panel("Invalid JSON", error_msg))
|
|
141
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
142
|
+
|
|
143
|
+
# Validate config against schema
|
|
144
|
+
validation_errors = validate_org_config(config, "v1")
|
|
145
|
+
|
|
146
|
+
# Build preview data
|
|
147
|
+
preview_data = build_import_preview_data(
|
|
148
|
+
source=source,
|
|
149
|
+
resolved_url=resolved_url,
|
|
150
|
+
config=config,
|
|
151
|
+
validation_errors=validation_errors,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Preview mode: show info without saving
|
|
155
|
+
if preview:
|
|
156
|
+
if json_output:
|
|
157
|
+
with json_output_mode():
|
|
158
|
+
envelope = build_envelope(Kind.ORG_IMPORT_PREVIEW, data=preview_data)
|
|
159
|
+
print_json(envelope)
|
|
160
|
+
raise typer.Exit(0)
|
|
161
|
+
|
|
162
|
+
# Human-readable preview
|
|
163
|
+
_render_import_preview(preview_data)
|
|
164
|
+
raise typer.Exit(0)
|
|
165
|
+
|
|
166
|
+
# Import mode: validate and save
|
|
167
|
+
if not preview_data["valid"]:
|
|
168
|
+
if json_output:
|
|
169
|
+
with json_output_mode():
|
|
170
|
+
envelope = build_envelope(
|
|
171
|
+
Kind.ORG_IMPORT,
|
|
172
|
+
data=preview_data,
|
|
173
|
+
ok=False,
|
|
174
|
+
errors=validation_errors,
|
|
175
|
+
)
|
|
176
|
+
print_json(envelope)
|
|
177
|
+
raise typer.Exit(EXIT_VALIDATION)
|
|
178
|
+
console.print(
|
|
179
|
+
create_error_panel(
|
|
180
|
+
"Validation Failed",
|
|
181
|
+
"\n".join(f"* {e}" for e in validation_errors),
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
raise typer.Exit(EXIT_VALIDATION)
|
|
185
|
+
|
|
186
|
+
# Save to user config
|
|
187
|
+
user_config = load_user_config()
|
|
188
|
+
user_config["organization_source"] = {
|
|
189
|
+
"url": resolved_url,
|
|
190
|
+
"auth": getattr(resolved, "auth_spec", None),
|
|
191
|
+
}
|
|
192
|
+
user_config["standalone"] = False
|
|
193
|
+
save_user_config(user_config)
|
|
194
|
+
|
|
195
|
+
# Cache the fetched config
|
|
196
|
+
etag = response.headers.get("ETag")
|
|
197
|
+
save_to_cache(config, source_url=resolved_url, etag=etag, ttl_hours=24)
|
|
198
|
+
|
|
199
|
+
# Build import result data
|
|
200
|
+
import_data = {
|
|
201
|
+
**preview_data,
|
|
202
|
+
"imported": True,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if json_output:
|
|
206
|
+
with json_output_mode():
|
|
207
|
+
envelope = build_envelope(Kind.ORG_IMPORT, data=import_data)
|
|
208
|
+
print_json(envelope)
|
|
209
|
+
raise typer.Exit(0)
|
|
210
|
+
|
|
211
|
+
# Human-readable success
|
|
212
|
+
org_name = preview_data["organization"]["name"] or "organization"
|
|
213
|
+
console.print(
|
|
214
|
+
create_success_panel(
|
|
215
|
+
"Import Successful",
|
|
216
|
+
{
|
|
217
|
+
"Organization": org_name,
|
|
218
|
+
"Source": source,
|
|
219
|
+
"Profiles": ", ".join(preview_data["available_profiles"]) or "None",
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
raise typer.Exit(0)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _render_import_preview(preview: dict[str, Any]) -> None:
|
|
227
|
+
"""Render import preview as human-readable Rich output.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
preview: Preview data from build_import_preview_data
|
|
231
|
+
"""
|
|
232
|
+
console.print("\n[bold cyan]Organization Config Preview[/bold cyan]")
|
|
233
|
+
|
|
234
|
+
# Create info table
|
|
235
|
+
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
236
|
+
table.add_column("Key", style="dim")
|
|
237
|
+
table.add_column("Value")
|
|
238
|
+
|
|
239
|
+
org = preview.get("organization", {})
|
|
240
|
+
table.add_row("Organization", f"[bold]{org.get('name') or '[unnamed]'}[/bold]")
|
|
241
|
+
|
|
242
|
+
if preview["source"] != preview["resolved_url"]:
|
|
243
|
+
table.add_row("Source", preview["source"])
|
|
244
|
+
table.add_row("Resolved URL", preview["resolved_url"])
|
|
245
|
+
else:
|
|
246
|
+
table.add_row("Source", preview["source"])
|
|
247
|
+
|
|
248
|
+
if preview.get("schema_version"):
|
|
249
|
+
table.add_row("Schema Version", preview["schema_version"])
|
|
250
|
+
if preview.get("min_cli_version"):
|
|
251
|
+
table.add_row("Min CLI Version", preview["min_cli_version"])
|
|
252
|
+
|
|
253
|
+
profiles = preview.get("available_profiles", [])
|
|
254
|
+
if profiles:
|
|
255
|
+
table.add_row("Available Profiles", ", ".join(profiles))
|
|
256
|
+
|
|
257
|
+
console.print(table)
|
|
258
|
+
|
|
259
|
+
# Validation status
|
|
260
|
+
if preview["valid"]:
|
|
261
|
+
console.print("\n[green]Configuration is valid[/green]")
|
|
262
|
+
else:
|
|
263
|
+
console.print("\n[red]Configuration is invalid[/red]")
|
|
264
|
+
for error in preview.get("validation_errors", []):
|
|
265
|
+
console.print(f" [red]* {error}[/red]")
|
|
266
|
+
|
|
267
|
+
console.print("\n[dim]Use 'scc org import <source>' without --preview to import[/dim]\n")
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Organization init command implementation.
|
|
2
|
+
|
|
3
|
+
Provides the org init command for generating organization configuration
|
|
4
|
+
skeletons from templates.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from ...cli_common import console, handle_errors
|
|
15
|
+
from ...core.exit_codes import EXIT_CONFIG
|
|
16
|
+
from ...json_output import build_envelope
|
|
17
|
+
from ...kinds import Kind
|
|
18
|
+
from ...org_templates import (
|
|
19
|
+
TemplateNotFoundError,
|
|
20
|
+
TemplateVars,
|
|
21
|
+
list_templates,
|
|
22
|
+
render_template_string,
|
|
23
|
+
)
|
|
24
|
+
from ...output_mode import json_output_mode, print_json, set_pretty_mode
|
|
25
|
+
from ...panels import create_error_panel, create_success_panel, create_warning_panel
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@handle_errors
|
|
29
|
+
def org_init_cmd(
|
|
30
|
+
template: str = typer.Option(
|
|
31
|
+
"minimal",
|
|
32
|
+
"--template",
|
|
33
|
+
"-t",
|
|
34
|
+
help="Template to use (minimal, teams, strict, reference).",
|
|
35
|
+
),
|
|
36
|
+
org_name: str = typer.Option(
|
|
37
|
+
"my-org",
|
|
38
|
+
"--org-name",
|
|
39
|
+
"-n",
|
|
40
|
+
help="Organization name for template substitution.",
|
|
41
|
+
),
|
|
42
|
+
org_domain: str = typer.Option(
|
|
43
|
+
"example.com",
|
|
44
|
+
"--org-domain",
|
|
45
|
+
"-d",
|
|
46
|
+
help="Organization domain for template substitution.",
|
|
47
|
+
),
|
|
48
|
+
stdout: bool = typer.Option(
|
|
49
|
+
False,
|
|
50
|
+
"--stdout",
|
|
51
|
+
help="Print generated config to stdout instead of writing to file.",
|
|
52
|
+
),
|
|
53
|
+
output: Path | None = typer.Option(
|
|
54
|
+
None,
|
|
55
|
+
"--output",
|
|
56
|
+
"-o",
|
|
57
|
+
help="Write config to specified file path.",
|
|
58
|
+
),
|
|
59
|
+
force: bool = typer.Option(
|
|
60
|
+
False,
|
|
61
|
+
"--force",
|
|
62
|
+
"-f",
|
|
63
|
+
help="Overwrite existing file without prompting.",
|
|
64
|
+
),
|
|
65
|
+
list_templates_flag: bool = typer.Option(
|
|
66
|
+
False,
|
|
67
|
+
"--list-templates",
|
|
68
|
+
"-l",
|
|
69
|
+
help="List available templates and exit.",
|
|
70
|
+
),
|
|
71
|
+
json_output: bool = typer.Option(
|
|
72
|
+
False,
|
|
73
|
+
"--json",
|
|
74
|
+
help="Output in JSON envelope format.",
|
|
75
|
+
),
|
|
76
|
+
pretty: bool = typer.Option(
|
|
77
|
+
False,
|
|
78
|
+
"--pretty",
|
|
79
|
+
help="Pretty-print JSON output with indentation.",
|
|
80
|
+
),
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Generate an organization config skeleton from templates.
|
|
83
|
+
|
|
84
|
+
Templates provide starting points for organization configurations:
|
|
85
|
+
- minimal: Simple quickstart with sensible defaults
|
|
86
|
+
- teams: Multi-team setup with delegation
|
|
87
|
+
- strict: Security-focused for regulated industries
|
|
88
|
+
- reference: Complete reference with all fields documented
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
scc org init --list-templates # Show available templates
|
|
92
|
+
scc org init --stdout # Print minimal config to stdout
|
|
93
|
+
scc org init -t teams --stdout # Print teams template
|
|
94
|
+
scc org init -o org.json # Write to org.json
|
|
95
|
+
scc org init -n acme -d acme.com -o . # Customize and write
|
|
96
|
+
"""
|
|
97
|
+
if pretty:
|
|
98
|
+
set_pretty_mode(True)
|
|
99
|
+
|
|
100
|
+
# Handle --list-templates
|
|
101
|
+
if list_templates_flag:
|
|
102
|
+
_handle_list_templates(json_output)
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
# Require either --stdout or --output
|
|
106
|
+
if not stdout and output is None:
|
|
107
|
+
if json_output:
|
|
108
|
+
with json_output_mode():
|
|
109
|
+
envelope = build_envelope(
|
|
110
|
+
Kind.ORG_INIT,
|
|
111
|
+
data={"error": "Must specify --stdout or --output"},
|
|
112
|
+
ok=False,
|
|
113
|
+
)
|
|
114
|
+
print_json(envelope)
|
|
115
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
116
|
+
console.print(
|
|
117
|
+
create_warning_panel(
|
|
118
|
+
"Output Required",
|
|
119
|
+
"Must specify either --stdout or --output to generate config.",
|
|
120
|
+
hint="Use --list-templates to see available templates.",
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
124
|
+
|
|
125
|
+
# Generate config from template
|
|
126
|
+
try:
|
|
127
|
+
vars = TemplateVars(org_name=org_name, org_domain=org_domain)
|
|
128
|
+
config_json = render_template_string(template, vars)
|
|
129
|
+
except TemplateNotFoundError as e:
|
|
130
|
+
if json_output:
|
|
131
|
+
with json_output_mode():
|
|
132
|
+
envelope = build_envelope(
|
|
133
|
+
Kind.ORG_INIT,
|
|
134
|
+
data={
|
|
135
|
+
"error": str(e),
|
|
136
|
+
"available_templates": e.available,
|
|
137
|
+
},
|
|
138
|
+
ok=False,
|
|
139
|
+
)
|
|
140
|
+
print_json(envelope)
|
|
141
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
142
|
+
console.print(
|
|
143
|
+
create_error_panel(
|
|
144
|
+
"Template Not Found",
|
|
145
|
+
str(e),
|
|
146
|
+
hint=f"Available templates: {', '.join(e.available)}",
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
150
|
+
|
|
151
|
+
# Handle --stdout
|
|
152
|
+
if stdout:
|
|
153
|
+
if json_output:
|
|
154
|
+
# In JSON mode with --stdout, just print the raw config
|
|
155
|
+
# The config itself is the output, not wrapped in envelope
|
|
156
|
+
console.print(config_json)
|
|
157
|
+
else:
|
|
158
|
+
console.print(config_json)
|
|
159
|
+
raise typer.Exit(0)
|
|
160
|
+
|
|
161
|
+
# Handle --output
|
|
162
|
+
if output is not None:
|
|
163
|
+
# Resolve output path
|
|
164
|
+
if output.is_dir():
|
|
165
|
+
output_path = output / "org-config.json"
|
|
166
|
+
else:
|
|
167
|
+
output_path = output
|
|
168
|
+
|
|
169
|
+
# Check for existing file
|
|
170
|
+
if output_path.exists() and not force:
|
|
171
|
+
if json_output:
|
|
172
|
+
with json_output_mode():
|
|
173
|
+
envelope = build_envelope(
|
|
174
|
+
Kind.ORG_INIT,
|
|
175
|
+
data={
|
|
176
|
+
"error": f"File already exists: {output_path}",
|
|
177
|
+
"file": str(output_path),
|
|
178
|
+
},
|
|
179
|
+
ok=False,
|
|
180
|
+
)
|
|
181
|
+
print_json(envelope)
|
|
182
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
183
|
+
console.print(
|
|
184
|
+
create_error_panel(
|
|
185
|
+
"File Exists",
|
|
186
|
+
f"File already exists: {output_path}",
|
|
187
|
+
hint="Use --force to overwrite.",
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
191
|
+
|
|
192
|
+
# Write file
|
|
193
|
+
output_path.write_text(config_json)
|
|
194
|
+
|
|
195
|
+
if json_output:
|
|
196
|
+
with json_output_mode():
|
|
197
|
+
envelope = build_envelope(
|
|
198
|
+
Kind.ORG_INIT,
|
|
199
|
+
data={
|
|
200
|
+
"file": str(output_path),
|
|
201
|
+
"template": template,
|
|
202
|
+
"org_name": org_name,
|
|
203
|
+
"org_domain": org_domain,
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
print_json(envelope)
|
|
207
|
+
else:
|
|
208
|
+
console.print(
|
|
209
|
+
create_success_panel(
|
|
210
|
+
"Config Created",
|
|
211
|
+
{
|
|
212
|
+
"File": str(output_path),
|
|
213
|
+
"Template": template,
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
raise typer.Exit(0)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _handle_list_templates(json_output: bool) -> None:
|
|
221
|
+
"""Handle --list-templates flag.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
json_output: Whether to output JSON envelope format.
|
|
225
|
+
"""
|
|
226
|
+
templates = list_templates()
|
|
227
|
+
|
|
228
|
+
if json_output:
|
|
229
|
+
with json_output_mode():
|
|
230
|
+
template_data = [
|
|
231
|
+
{
|
|
232
|
+
"name": t.name,
|
|
233
|
+
"description": t.description,
|
|
234
|
+
"level": t.level,
|
|
235
|
+
"use_case": t.use_case,
|
|
236
|
+
}
|
|
237
|
+
for t in templates
|
|
238
|
+
]
|
|
239
|
+
envelope = build_envelope(
|
|
240
|
+
Kind.ORG_TEMPLATE_LIST,
|
|
241
|
+
data={"templates": template_data},
|
|
242
|
+
)
|
|
243
|
+
print_json(envelope)
|
|
244
|
+
raise typer.Exit(0)
|
|
245
|
+
|
|
246
|
+
# Human-readable output
|
|
247
|
+
console.print("\n[bold cyan]Available Organization Config Templates[/bold cyan]\n")
|
|
248
|
+
|
|
249
|
+
table = Table(show_header=True, header_style="bold")
|
|
250
|
+
table.add_column("Template", style="cyan")
|
|
251
|
+
table.add_column("Level")
|
|
252
|
+
table.add_column("Description")
|
|
253
|
+
|
|
254
|
+
for t in templates:
|
|
255
|
+
level_style = {
|
|
256
|
+
"beginner": "green",
|
|
257
|
+
"intermediate": "yellow",
|
|
258
|
+
"advanced": "red",
|
|
259
|
+
"reference": "blue",
|
|
260
|
+
}.get(t.level, "")
|
|
261
|
+
table.add_row(
|
|
262
|
+
t.name,
|
|
263
|
+
f"[{level_style}]{t.level}[/{level_style}]" if level_style else t.level,
|
|
264
|
+
t.description,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
console.print(table)
|
|
268
|
+
console.print("\n[dim]Use: scc org init --template <name> --stdout[/dim]\n")
|
|
269
|
+
raise typer.Exit(0)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Org schema command for printing bundled schema."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from ...cli_common import console, handle_errors
|
|
10
|
+
from ...core.exit_codes import EXIT_CONFIG
|
|
11
|
+
from ...json_output import build_envelope
|
|
12
|
+
from ...kinds import Kind
|
|
13
|
+
from ...output_mode import json_output_mode, print_json, set_pretty_mode
|
|
14
|
+
from ...panels import create_error_panel
|
|
15
|
+
from ...validate import load_bundled_schema
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@handle_errors
|
|
19
|
+
def org_schema_cmd(
|
|
20
|
+
schema_version: str = typer.Option(
|
|
21
|
+
"v1", "--version", "-v", help="Schema version to print (default: v1)"
|
|
22
|
+
),
|
|
23
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON envelope"),
|
|
24
|
+
pretty: bool = typer.Option(False, "--pretty", help="Pretty-print JSON (implies --json)"),
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Print the bundled organization config schema.
|
|
27
|
+
|
|
28
|
+
Useful for understanding the expected configuration format
|
|
29
|
+
or for use with external validators.
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
scc org schema
|
|
33
|
+
scc org schema --json
|
|
34
|
+
"""
|
|
35
|
+
# --pretty implies --json
|
|
36
|
+
if pretty:
|
|
37
|
+
json_output = True
|
|
38
|
+
set_pretty_mode(True)
|
|
39
|
+
|
|
40
|
+
# Load schema
|
|
41
|
+
try:
|
|
42
|
+
schema = load_bundled_schema(schema_version)
|
|
43
|
+
except FileNotFoundError:
|
|
44
|
+
if json_output:
|
|
45
|
+
with json_output_mode():
|
|
46
|
+
envelope = build_envelope(
|
|
47
|
+
Kind.ORG_SCHEMA,
|
|
48
|
+
data={"error": f"Schema version '{schema_version}' not found"},
|
|
49
|
+
ok=False,
|
|
50
|
+
errors=[f"Schema version '{schema_version}' not found"],
|
|
51
|
+
)
|
|
52
|
+
print_json(envelope)
|
|
53
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
54
|
+
console.print(
|
|
55
|
+
create_error_panel(
|
|
56
|
+
"Schema Not Found",
|
|
57
|
+
f"Schema version '{schema_version}' does not exist.",
|
|
58
|
+
"Available version: v1",
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
62
|
+
|
|
63
|
+
# JSON envelope output
|
|
64
|
+
if json_output:
|
|
65
|
+
with json_output_mode():
|
|
66
|
+
data = {
|
|
67
|
+
"schema_version": schema_version,
|
|
68
|
+
"schema": schema,
|
|
69
|
+
}
|
|
70
|
+
envelope = build_envelope(Kind.ORG_SCHEMA, data=data)
|
|
71
|
+
print_json(envelope)
|
|
72
|
+
raise typer.Exit(0)
|
|
73
|
+
|
|
74
|
+
# Raw schema output (for piping to files or validators)
|
|
75
|
+
print(json.dumps(schema, indent=2)) # noqa: T201
|
|
76
|
+
raise typer.Exit(0)
|