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