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,157 @@
|
|
|
1
|
+
"""Org status command for showing organization configuration status."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from ...cli_common import console, handle_errors
|
|
11
|
+
from ...config import load_user_config
|
|
12
|
+
from ...core.constants import CLI_VERSION
|
|
13
|
+
from ...json_output import build_envelope
|
|
14
|
+
from ...kinds import Kind
|
|
15
|
+
from ...output_mode import json_output_mode, print_json, set_pretty_mode
|
|
16
|
+
from ...remote import load_from_cache
|
|
17
|
+
from ._builders import build_status_data
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@handle_errors
|
|
21
|
+
def org_status_cmd(
|
|
22
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON envelope"),
|
|
23
|
+
pretty: bool = typer.Option(False, "--pretty", help="Pretty-print JSON (implies --json)"),
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Show current organization configuration status.
|
|
26
|
+
|
|
27
|
+
Displays connection mode (standalone or organization), cache freshness,
|
|
28
|
+
version compatibility, and selected profile.
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
scc org status
|
|
32
|
+
scc org status --json
|
|
33
|
+
scc org status --pretty
|
|
34
|
+
"""
|
|
35
|
+
# --pretty implies --json
|
|
36
|
+
if pretty:
|
|
37
|
+
json_output = True
|
|
38
|
+
set_pretty_mode(True)
|
|
39
|
+
|
|
40
|
+
# Load configuration data
|
|
41
|
+
user_config = load_user_config()
|
|
42
|
+
org_config, cache_meta = load_from_cache()
|
|
43
|
+
|
|
44
|
+
# Build status data
|
|
45
|
+
status_data = build_status_data(user_config, org_config, cache_meta)
|
|
46
|
+
|
|
47
|
+
# JSON output mode
|
|
48
|
+
if json_output:
|
|
49
|
+
with json_output_mode():
|
|
50
|
+
envelope = build_envelope(Kind.ORG_STATUS, data=status_data)
|
|
51
|
+
print_json(envelope)
|
|
52
|
+
raise typer.Exit(0)
|
|
53
|
+
|
|
54
|
+
# Human-readable output
|
|
55
|
+
_render_status_human(status_data)
|
|
56
|
+
raise typer.Exit(0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _render_status_human(status: dict[str, Any]) -> None:
|
|
60
|
+
"""Render status data as human-readable Rich output.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
status: Status data from build_status_data
|
|
64
|
+
"""
|
|
65
|
+
# Mode header
|
|
66
|
+
mode = status["mode"]
|
|
67
|
+
if mode == "standalone":
|
|
68
|
+
console.print("\n[bold cyan]Organization Status[/bold cyan]")
|
|
69
|
+
console.print(" Mode: [yellow]Standalone[/yellow] (no organization configured)")
|
|
70
|
+
console.print("\n [dim]Tip: Run 'scc setup' to connect to an organization[/dim]\n")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
# Organization mode
|
|
74
|
+
console.print("\n[bold cyan]Organization Status[/bold cyan]")
|
|
75
|
+
|
|
76
|
+
# Create a table for organization info
|
|
77
|
+
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
78
|
+
table.add_column("Key", style="dim")
|
|
79
|
+
table.add_column("Value")
|
|
80
|
+
|
|
81
|
+
# Organization info
|
|
82
|
+
org = status.get("organization", {})
|
|
83
|
+
if org:
|
|
84
|
+
org_name = org.get("name") or "[not fetched]"
|
|
85
|
+
table.add_row("Organization", f"[bold]{org_name}[/bold]")
|
|
86
|
+
table.add_row("Source URL", org.get("source_url", "[not configured]"))
|
|
87
|
+
|
|
88
|
+
# Selected profile
|
|
89
|
+
profile = status.get("selected_profile")
|
|
90
|
+
if profile:
|
|
91
|
+
table.add_row("Selected Profile", f"[green]{profile}[/green]")
|
|
92
|
+
else:
|
|
93
|
+
table.add_row("Selected Profile", "[yellow]None[/yellow]")
|
|
94
|
+
|
|
95
|
+
# Available profiles
|
|
96
|
+
available = status.get("available_profiles", [])
|
|
97
|
+
if available:
|
|
98
|
+
table.add_row("Available Profiles", ", ".join(available))
|
|
99
|
+
|
|
100
|
+
console.print(table)
|
|
101
|
+
|
|
102
|
+
# Cache status
|
|
103
|
+
cache = status.get("cache")
|
|
104
|
+
if cache:
|
|
105
|
+
console.print("\n[bold]Cache Status[/bold]")
|
|
106
|
+
cache_table = Table(show_header=False, box=None, padding=(0, 2))
|
|
107
|
+
cache_table.add_column("Key", style="dim")
|
|
108
|
+
cache_table.add_column("Value")
|
|
109
|
+
|
|
110
|
+
if cache.get("valid"):
|
|
111
|
+
cache_table.add_row("Status", "[green]+ Fresh[/green]")
|
|
112
|
+
else:
|
|
113
|
+
cache_table.add_row("Status", "[yellow]! Expired[/yellow]")
|
|
114
|
+
|
|
115
|
+
if cache.get("fetched_at"):
|
|
116
|
+
cache_table.add_row("Fetched At", cache["fetched_at"])
|
|
117
|
+
if cache.get("expires_at"):
|
|
118
|
+
cache_table.add_row("Expires At", cache["expires_at"])
|
|
119
|
+
|
|
120
|
+
console.print(cache_table)
|
|
121
|
+
else:
|
|
122
|
+
console.print("\n[yellow]Cache:[/yellow] Not fetched yet")
|
|
123
|
+
console.print(
|
|
124
|
+
" [dim]Run 'scc start' or 'scc doctor' to fetch the organization config[/dim]"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Version compatibility
|
|
128
|
+
compat = status.get("version_compatibility")
|
|
129
|
+
if compat:
|
|
130
|
+
console.print("\n[bold]Version Compatibility[/bold]")
|
|
131
|
+
compat_table = Table(show_header=False, box=None, padding=(0, 2))
|
|
132
|
+
compat_table.add_column("Key", style="dim")
|
|
133
|
+
compat_table.add_column("Value")
|
|
134
|
+
|
|
135
|
+
if compat.get("compatible"):
|
|
136
|
+
compat_table.add_row("Status", "[green]+ Compatible[/green]")
|
|
137
|
+
else:
|
|
138
|
+
if compat.get("blocking_error"):
|
|
139
|
+
compat_table.add_row("Status", "[red]x Incompatible[/red]")
|
|
140
|
+
compat_table.add_row("Error", f"[red]{compat['blocking_error']}[/red]")
|
|
141
|
+
else:
|
|
142
|
+
compat_table.add_row("Status", "[yellow]! Warnings[/yellow]")
|
|
143
|
+
|
|
144
|
+
if compat.get("schema_version"):
|
|
145
|
+
compat_table.add_row("Schema Version", compat["schema_version"])
|
|
146
|
+
if compat.get("min_cli_version"):
|
|
147
|
+
compat_table.add_row("Min CLI Version", compat["min_cli_version"])
|
|
148
|
+
compat_table.add_row("Current CLI", compat.get("current_cli_version", CLI_VERSION))
|
|
149
|
+
|
|
150
|
+
# Show warnings if any
|
|
151
|
+
warnings = compat.get("warnings", [])
|
|
152
|
+
for warning in warnings:
|
|
153
|
+
console.print(f" [yellow]! {warning}[/yellow]")
|
|
154
|
+
|
|
155
|
+
console.print(compat_table)
|
|
156
|
+
|
|
157
|
+
console.print() # Final newline
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Org update command for refreshing organization and team configs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from ...cli_common import console, handle_errors
|
|
10
|
+
from ...config import load_user_config
|
|
11
|
+
from ...core.exit_codes import EXIT_CONFIG
|
|
12
|
+
from ...json_output import build_envelope
|
|
13
|
+
from ...kinds import Kind
|
|
14
|
+
from ...marketplace.team_fetch import fetch_team_config
|
|
15
|
+
from ...output_mode import json_output_mode, print_json, set_pretty_mode
|
|
16
|
+
from ...panels import create_error_panel, create_success_panel, create_warning_panel
|
|
17
|
+
from ...remote import load_org_config
|
|
18
|
+
from ._builders import _parse_config_source, build_update_data
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@handle_errors
|
|
22
|
+
def org_update_cmd(
|
|
23
|
+
team: str | None = typer.Option(
|
|
24
|
+
None, "--team", "-t", help="Refresh a specific federated team's config"
|
|
25
|
+
),
|
|
26
|
+
all_teams: bool = typer.Option(
|
|
27
|
+
False, "--all-teams", "-a", help="Refresh all federated team configs"
|
|
28
|
+
),
|
|
29
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
30
|
+
pretty: bool = typer.Option(False, "--pretty", help="Pretty-print JSON (implies --json)"),
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Refresh organization config and optionally team configs.
|
|
33
|
+
|
|
34
|
+
By default, refreshes the organization config from its remote source.
|
|
35
|
+
With --team or --all-teams, also refreshes federated team configurations.
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
scc org update # Refresh org config only
|
|
39
|
+
scc org update --team dev # Also refresh 'dev' team config
|
|
40
|
+
scc org update --all-teams # Refresh all federated team configs
|
|
41
|
+
"""
|
|
42
|
+
# --pretty implies --json
|
|
43
|
+
if pretty:
|
|
44
|
+
json_output = True
|
|
45
|
+
set_pretty_mode(True)
|
|
46
|
+
|
|
47
|
+
# Load user config
|
|
48
|
+
user_config = load_user_config()
|
|
49
|
+
|
|
50
|
+
# Check for standalone mode
|
|
51
|
+
is_standalone = user_config.get("standalone", False)
|
|
52
|
+
if is_standalone:
|
|
53
|
+
if json_output:
|
|
54
|
+
with json_output_mode():
|
|
55
|
+
envelope = build_envelope(
|
|
56
|
+
Kind.ORG_UPDATE,
|
|
57
|
+
data={"error": "Cannot update in standalone mode"},
|
|
58
|
+
ok=False,
|
|
59
|
+
errors=["CLI is running in standalone mode"],
|
|
60
|
+
)
|
|
61
|
+
print_json(envelope)
|
|
62
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
63
|
+
console.print(
|
|
64
|
+
create_error_panel(
|
|
65
|
+
"Standalone Mode",
|
|
66
|
+
"Cannot update organization config in standalone mode.",
|
|
67
|
+
hint="Use 'scc setup' to connect to an organization.",
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
71
|
+
|
|
72
|
+
# Check for organization source
|
|
73
|
+
org_source = user_config.get("organization_source")
|
|
74
|
+
if not org_source:
|
|
75
|
+
if json_output:
|
|
76
|
+
with json_output_mode():
|
|
77
|
+
envelope = build_envelope(
|
|
78
|
+
Kind.ORG_UPDATE,
|
|
79
|
+
data={"error": "No organization source configured"},
|
|
80
|
+
ok=False,
|
|
81
|
+
errors=["No organization source configured"],
|
|
82
|
+
)
|
|
83
|
+
print_json(envelope)
|
|
84
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
85
|
+
console.print(
|
|
86
|
+
create_error_panel(
|
|
87
|
+
"No Organization",
|
|
88
|
+
"No organization source is configured.",
|
|
89
|
+
hint="Use 'scc setup' to connect to an organization.",
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
93
|
+
|
|
94
|
+
# Force refresh org config
|
|
95
|
+
org_config = load_org_config(user_config, force_refresh=True)
|
|
96
|
+
if org_config is None:
|
|
97
|
+
if json_output:
|
|
98
|
+
with json_output_mode():
|
|
99
|
+
envelope = build_envelope(
|
|
100
|
+
Kind.ORG_UPDATE,
|
|
101
|
+
data=build_update_data(None),
|
|
102
|
+
ok=False,
|
|
103
|
+
errors=["Failed to fetch organization config"],
|
|
104
|
+
)
|
|
105
|
+
print_json(envelope)
|
|
106
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
107
|
+
console.print(
|
|
108
|
+
create_error_panel(
|
|
109
|
+
"Update Failed",
|
|
110
|
+
"Failed to fetch organization config from remote.",
|
|
111
|
+
hint="Check network connection and organization URL.",
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
115
|
+
|
|
116
|
+
# Get profiles from org config
|
|
117
|
+
profiles = org_config.get("profiles", {})
|
|
118
|
+
|
|
119
|
+
# Handle --team option (single team update)
|
|
120
|
+
team_results: list[dict[str, Any]] | None = None
|
|
121
|
+
if team is not None:
|
|
122
|
+
# Validate team exists
|
|
123
|
+
if team not in profiles:
|
|
124
|
+
if json_output:
|
|
125
|
+
with json_output_mode():
|
|
126
|
+
envelope = build_envelope(
|
|
127
|
+
Kind.ORG_UPDATE,
|
|
128
|
+
data=build_update_data(org_config),
|
|
129
|
+
ok=False,
|
|
130
|
+
errors=[f"Team '{team}' not found in organization config"],
|
|
131
|
+
)
|
|
132
|
+
print_json(envelope)
|
|
133
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
134
|
+
console.print(
|
|
135
|
+
create_error_panel(
|
|
136
|
+
"Team Not Found",
|
|
137
|
+
f"Team '{team}' not found in organization config.",
|
|
138
|
+
hint=f"Available teams: {', '.join(profiles.keys())}",
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
142
|
+
|
|
143
|
+
profile = profiles[team]
|
|
144
|
+
config_source_dict = profile.get("config_source")
|
|
145
|
+
|
|
146
|
+
# Check if team is federated
|
|
147
|
+
if config_source_dict is None:
|
|
148
|
+
team_results = [{"team": team, "success": True, "inline": True}]
|
|
149
|
+
if json_output:
|
|
150
|
+
with json_output_mode():
|
|
151
|
+
data = build_update_data(org_config, team_results)
|
|
152
|
+
envelope = build_envelope(Kind.ORG_UPDATE, data=data)
|
|
153
|
+
print_json(envelope)
|
|
154
|
+
raise typer.Exit(0)
|
|
155
|
+
console.print(
|
|
156
|
+
create_warning_panel(
|
|
157
|
+
"Inline Team",
|
|
158
|
+
f"Team '{team}' is not federated (inline config).",
|
|
159
|
+
hint="Inline teams don't have external configs to refresh.",
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
raise typer.Exit(0)
|
|
163
|
+
|
|
164
|
+
# Fetch team config
|
|
165
|
+
try:
|
|
166
|
+
config_source = _parse_config_source(config_source_dict)
|
|
167
|
+
result = fetch_team_config(config_source, team)
|
|
168
|
+
if result.success:
|
|
169
|
+
team_results = [
|
|
170
|
+
{
|
|
171
|
+
"team": team,
|
|
172
|
+
"success": True,
|
|
173
|
+
"commit_sha": result.commit_sha,
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
else:
|
|
177
|
+
team_results = [
|
|
178
|
+
{
|
|
179
|
+
"team": team,
|
|
180
|
+
"success": False,
|
|
181
|
+
"error": result.error,
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
if json_output:
|
|
185
|
+
with json_output_mode():
|
|
186
|
+
data = build_update_data(org_config, team_results)
|
|
187
|
+
envelope = build_envelope(
|
|
188
|
+
Kind.ORG_UPDATE,
|
|
189
|
+
data=data,
|
|
190
|
+
ok=False,
|
|
191
|
+
errors=[f"Failed to fetch team config: {result.error}"],
|
|
192
|
+
)
|
|
193
|
+
print_json(envelope)
|
|
194
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
195
|
+
console.print(
|
|
196
|
+
create_error_panel(
|
|
197
|
+
"Team Update Failed",
|
|
198
|
+
f"Failed to fetch config for team '{team}'.",
|
|
199
|
+
hint=str(result.error),
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
if json_output:
|
|
205
|
+
with json_output_mode():
|
|
206
|
+
envelope = build_envelope(
|
|
207
|
+
Kind.ORG_UPDATE,
|
|
208
|
+
data=build_update_data(org_config),
|
|
209
|
+
ok=False,
|
|
210
|
+
errors=[f"Error parsing config source: {e}"],
|
|
211
|
+
)
|
|
212
|
+
print_json(envelope)
|
|
213
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
214
|
+
console.print(create_error_panel("Config Error", f"Error parsing config source: {e}"))
|
|
215
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
216
|
+
|
|
217
|
+
# Handle --all-teams option
|
|
218
|
+
elif all_teams:
|
|
219
|
+
team_results = []
|
|
220
|
+
federated_teams = [
|
|
221
|
+
(name, profile)
|
|
222
|
+
for name, profile in profiles.items()
|
|
223
|
+
if profile.get("config_source") is not None
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
if not federated_teams:
|
|
227
|
+
team_results = []
|
|
228
|
+
if json_output:
|
|
229
|
+
with json_output_mode():
|
|
230
|
+
data = build_update_data(org_config, team_results)
|
|
231
|
+
envelope = build_envelope(Kind.ORG_UPDATE, data=data)
|
|
232
|
+
print_json(envelope)
|
|
233
|
+
raise typer.Exit(0)
|
|
234
|
+
console.print(
|
|
235
|
+
create_warning_panel(
|
|
236
|
+
"No Federated Teams",
|
|
237
|
+
"No federated teams found in organization config.",
|
|
238
|
+
hint="All teams use inline configuration.",
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
raise typer.Exit(0)
|
|
242
|
+
|
|
243
|
+
# Fetch all federated team configs
|
|
244
|
+
for team_name, profile in federated_teams:
|
|
245
|
+
config_source_dict = profile["config_source"]
|
|
246
|
+
try:
|
|
247
|
+
config_source = _parse_config_source(config_source_dict)
|
|
248
|
+
result = fetch_team_config(config_source, team_name)
|
|
249
|
+
if result.success:
|
|
250
|
+
team_results.append(
|
|
251
|
+
{
|
|
252
|
+
"team": team_name,
|
|
253
|
+
"success": True,
|
|
254
|
+
"commit_sha": result.commit_sha,
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
team_results.append(
|
|
259
|
+
{
|
|
260
|
+
"team": team_name,
|
|
261
|
+
"success": False,
|
|
262
|
+
"error": result.error,
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
except Exception as e:
|
|
266
|
+
team_results.append(
|
|
267
|
+
{
|
|
268
|
+
"team": team_name,
|
|
269
|
+
"success": False,
|
|
270
|
+
"error": str(e),
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Build output data
|
|
275
|
+
data = build_update_data(org_config, team_results)
|
|
276
|
+
|
|
277
|
+
# JSON output
|
|
278
|
+
if json_output:
|
|
279
|
+
with json_output_mode():
|
|
280
|
+
# Determine overall success
|
|
281
|
+
has_team_failures = team_results is not None and any(
|
|
282
|
+
not t.get("success") for t in team_results
|
|
283
|
+
)
|
|
284
|
+
envelope = build_envelope(
|
|
285
|
+
Kind.ORG_UPDATE,
|
|
286
|
+
data=data,
|
|
287
|
+
ok=not has_team_failures,
|
|
288
|
+
)
|
|
289
|
+
print_json(envelope)
|
|
290
|
+
raise typer.Exit(0)
|
|
291
|
+
|
|
292
|
+
# Human-readable output
|
|
293
|
+
org_data = org_config.get("organization", {})
|
|
294
|
+
org_name = org_data.get("name", "Unknown")
|
|
295
|
+
|
|
296
|
+
if team_results is None:
|
|
297
|
+
# Org-only update
|
|
298
|
+
console.print(
|
|
299
|
+
create_success_panel(
|
|
300
|
+
"Organization Updated",
|
|
301
|
+
{
|
|
302
|
+
"Organization": org_name,
|
|
303
|
+
"Status": "Refreshed from remote",
|
|
304
|
+
},
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
else:
|
|
308
|
+
# Team updates included
|
|
309
|
+
success_count = sum(1 for t in team_results if t.get("success"))
|
|
310
|
+
failed_count = len(team_results) - success_count
|
|
311
|
+
|
|
312
|
+
if failed_count == 0:
|
|
313
|
+
console.print(
|
|
314
|
+
create_success_panel(
|
|
315
|
+
"Update Complete",
|
|
316
|
+
{
|
|
317
|
+
"Organization": org_name,
|
|
318
|
+
"Teams Updated": str(success_count),
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
console.print(
|
|
324
|
+
create_warning_panel(
|
|
325
|
+
"Partial Update",
|
|
326
|
+
f"Organization updated. {success_count} team(s) succeeded, {failed_count} failed.",
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
raise typer.Exit(0)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Org validate command for schema and semantic validation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from ...cli_common import console, handle_errors
|
|
11
|
+
from ...core.exit_codes import EXIT_CONFIG, EXIT_VALIDATION
|
|
12
|
+
from ...json_output import build_envelope
|
|
13
|
+
from ...kinds import Kind
|
|
14
|
+
from ...output_mode import json_output_mode, print_json, set_pretty_mode
|
|
15
|
+
from ...panels import create_error_panel, create_success_panel, create_warning_panel
|
|
16
|
+
from ...validate import validate_org_config
|
|
17
|
+
from ._builders import build_validation_data, check_semantic_errors
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@handle_errors
|
|
21
|
+
def org_validate_cmd(
|
|
22
|
+
source: str = typer.Argument(..., help="Path to config file to validate"),
|
|
23
|
+
schema_version: str = typer.Option(
|
|
24
|
+
"v1", "--schema-version", "-s", help="Schema version (default: v1)"
|
|
25
|
+
),
|
|
26
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
27
|
+
pretty: bool = typer.Option(False, "--pretty", help="Pretty-print JSON (implies --json)"),
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Validate an organization configuration file.
|
|
30
|
+
|
|
31
|
+
Performs both JSON schema validation and semantic checks.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
scc org validate ./org-config.json
|
|
35
|
+
scc org validate ./org-config.json --json
|
|
36
|
+
"""
|
|
37
|
+
# --pretty implies --json
|
|
38
|
+
if pretty:
|
|
39
|
+
json_output = True
|
|
40
|
+
set_pretty_mode(True)
|
|
41
|
+
|
|
42
|
+
# Load config file
|
|
43
|
+
config_path = Path(source).expanduser().resolve()
|
|
44
|
+
if not config_path.exists():
|
|
45
|
+
if json_output:
|
|
46
|
+
with json_output_mode():
|
|
47
|
+
data = build_validation_data(
|
|
48
|
+
source=source,
|
|
49
|
+
schema_errors=[f"File not found: {source}"],
|
|
50
|
+
semantic_errors=[],
|
|
51
|
+
schema_version=schema_version,
|
|
52
|
+
)
|
|
53
|
+
envelope = build_envelope(Kind.ORG_VALIDATION, data=data, ok=False)
|
|
54
|
+
print_json(envelope)
|
|
55
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
56
|
+
console.print(create_error_panel("File Not Found", f"Cannot find config file: {source}"))
|
|
57
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
58
|
+
|
|
59
|
+
# Parse JSON
|
|
60
|
+
try:
|
|
61
|
+
config = json.loads(config_path.read_text())
|
|
62
|
+
except json.JSONDecodeError as e:
|
|
63
|
+
if json_output:
|
|
64
|
+
with json_output_mode():
|
|
65
|
+
data = build_validation_data(
|
|
66
|
+
source=source,
|
|
67
|
+
schema_errors=[f"Invalid JSON: {e}"],
|
|
68
|
+
semantic_errors=[],
|
|
69
|
+
schema_version=schema_version,
|
|
70
|
+
)
|
|
71
|
+
envelope = build_envelope(Kind.ORG_VALIDATION, data=data, ok=False)
|
|
72
|
+
print_json(envelope)
|
|
73
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
74
|
+
console.print(create_error_panel("Invalid JSON", f"Failed to parse JSON: {e}"))
|
|
75
|
+
raise typer.Exit(EXIT_CONFIG)
|
|
76
|
+
|
|
77
|
+
# Validate against schema
|
|
78
|
+
schema_errors = validate_org_config(config, schema_version)
|
|
79
|
+
|
|
80
|
+
# Check semantic errors (only if schema is valid)
|
|
81
|
+
semantic_errors: list[str] = []
|
|
82
|
+
if not schema_errors:
|
|
83
|
+
semantic_errors = check_semantic_errors(config)
|
|
84
|
+
|
|
85
|
+
# Build result data
|
|
86
|
+
data = build_validation_data(
|
|
87
|
+
source=source,
|
|
88
|
+
schema_errors=schema_errors,
|
|
89
|
+
semantic_errors=semantic_errors,
|
|
90
|
+
schema_version=schema_version,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# JSON output mode
|
|
94
|
+
if json_output:
|
|
95
|
+
with json_output_mode():
|
|
96
|
+
is_valid = data["valid"]
|
|
97
|
+
all_errors = schema_errors + semantic_errors
|
|
98
|
+
envelope = build_envelope(
|
|
99
|
+
Kind.ORG_VALIDATION,
|
|
100
|
+
data=data,
|
|
101
|
+
ok=is_valid,
|
|
102
|
+
errors=all_errors if not is_valid else None,
|
|
103
|
+
)
|
|
104
|
+
print_json(envelope)
|
|
105
|
+
raise typer.Exit(0 if is_valid else EXIT_VALIDATION)
|
|
106
|
+
|
|
107
|
+
# Human-readable output
|
|
108
|
+
if data["valid"]:
|
|
109
|
+
console.print(
|
|
110
|
+
create_success_panel(
|
|
111
|
+
"Validation Passed",
|
|
112
|
+
{
|
|
113
|
+
"Source": source,
|
|
114
|
+
"Schema Version": schema_version,
|
|
115
|
+
"Status": "Valid",
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
raise typer.Exit(0)
|
|
120
|
+
|
|
121
|
+
# Show errors
|
|
122
|
+
if schema_errors:
|
|
123
|
+
console.print(
|
|
124
|
+
create_error_panel(
|
|
125
|
+
"Schema Validation Failed",
|
|
126
|
+
"\n".join(f"• {e}" for e in schema_errors),
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if semantic_errors:
|
|
131
|
+
console.print(
|
|
132
|
+
create_warning_panel(
|
|
133
|
+
"Semantic Issues",
|
|
134
|
+
"\n".join(f"• {e}" for e in semantic_errors),
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
raise typer.Exit(EXIT_VALIDATION)
|