sovaria-cli 0.2.4__tar.gz → 0.2.7__tar.gz
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.
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/PKG-INFO +2 -1
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/__init__.py +1 -1
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/admin.py +14 -14
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/api.py +4 -4
- sovaria_cli-0.2.7/apps/cli/src/commands/audit.py +172 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/auth_product.py +36 -32
- sovaria_cli-0.2.7/apps/cli/src/commands/billing.py +498 -0
- sovaria_cli-0.2.7/apps/cli/src/commands/chrono.py +232 -0
- sovaria_cli-0.2.7/apps/cli/src/commands/collections.py +38 -0
- sovaria_cli-0.2.7/apps/cli/src/commands/connect.py +129 -0
- sovaria_cli-0.2.7/apps/cli/src/commands/context.py +190 -0
- sovaria_cli-0.2.7/apps/cli/src/commands/dashboard.py +166 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/data.py +37 -39
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/db.py +474 -96
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/deploy.py +562 -351
- sovaria_cli-0.2.7/apps/cli/src/commands/gen.py +85 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/governance.py +223 -248
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/iam.py +188 -208
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/init_cmd.py +17 -14
- sovaria_cli-0.2.7/apps/cli/src/commands/inspect.py +53 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/keys.py +32 -27
- sovaria_cli-0.2.7/apps/cli/src/commands/link_cmd.py +108 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/login.py +321 -298
- sovaria_cli-0.2.7/apps/cli/src/commands/logs.py +103 -0
- sovaria_cli-0.2.7/apps/cli/src/commands/oidc_providers.py +199 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/org.py +18 -9
- sovaria_cli-0.2.7/apps/cli/src/commands/passkeys.py +82 -0
- sovaria_cli-0.2.7/apps/cli/src/commands/policy.py +299 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/project.py +10 -8
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/realtime.py +139 -146
- sovaria_cli-0.2.7/apps/cli/src/commands/schema.py +61 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/scope.py +36 -17
- sovaria_cli-0.2.7/apps/cli/src/commands/secrets.py +192 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/sim.py +92 -54
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/storage.py +54 -58
- sovaria_cli-0.2.7/apps/cli/src/commands/test_cmd.py +167 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/vector.py +43 -40
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/main.py +35 -1
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/utils/api_client.py +13 -2
- sovaria_cli-0.2.7/apps/cli/src/utils/api_helpers.py +52 -0
- sovaria_cli-0.2.7/apps/cli/src/utils/display.py +102 -0
- sovaria_cli-0.2.7/apps/cli/src/utils/local_function_server.py +148 -0
- sovaria_cli-0.2.7/apps/cli/src/utils/log_tail.py +53 -0
- sovaria_cli-0.2.7/apps/cli/src/utils/output_mode.py +18 -0
- sovaria_cli-0.2.7/apps/cli/src/utils/premium_display.py +1349 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/utils/project_context.py +21 -0
- sovaria_cli-0.2.7/apps/cli/src/utils/schema_introspect.py +223 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/utils/scope_picker.py +6 -24
- sovaria_cli-0.2.7/apps/cli/src/utils/theme.py +24 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/tests/test_cli_smoke_paths.py +10 -14
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/tests/test_main_startup.py +1 -1
- sovaria_cli-0.2.7/apps/cli/tests/test_phase4_commands.py +91 -0
- sovaria_cli-0.2.7/apps/cli/tests/test_phase5_commands.py +97 -0
- sovaria_cli-0.2.7/apps/cli/tests/test_phase6_commands.py +133 -0
- sovaria_cli-0.2.7/apps/cli/tests/test_premium_display.py +132 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/tests/test_project_and_commands.py +56 -12
- sovaria_cli-0.2.7/apps/cli/tests/test_supabase_style_commands.py +53 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/pyproject.toml +2 -1
- sovaria_cli-0.2.7/scripts/smoke_all_commands.sh +107 -0
- sovaria_cli-0.2.7/scripts/smoke_chunks.sh +90 -0
- sovaria_cli-0.2.4/apps/cli/src/commands/audit.py +0 -181
- sovaria_cli-0.2.4/apps/cli/src/commands/billing.py +0 -513
- sovaria_cli-0.2.4/apps/cli/src/commands/chrono.py +0 -248
- sovaria_cli-0.2.4/apps/cli/src/commands/context.py +0 -208
- sovaria_cli-0.2.4/apps/cli/src/commands/policy.py +0 -317
- sovaria_cli-0.2.4/apps/cli/src/utils/display.py +0 -62
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/.github/workflows/ci.yml +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/.github/workflows/publish.yml +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/.gitignore +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/.python-version +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/Makefile +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/README.md +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/__init__.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/__init__.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/commands/shell.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/utils/__init__.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/utils/auth_store.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/utils/config.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/src/utils/migrations.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/tests/test_auth_store_profiles.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/tests/test_cli_security.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/tests/test_config_profiles.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/apps/cli/tests/test_scope_picker.py +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/pytest.ini +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/requirements-dev.txt +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/requirements.txt +0 -0
- {sovaria_cli-0.2.4 → sovaria_cli-0.2.7}/scripts/cli_sdk_happy_path.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sovaria-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Sovaria BaaS command-line interface (sov)
|
|
5
5
|
Project-URL: Homepage, https://sovaria.in
|
|
6
6
|
Project-URL: Documentation, https://docs.sovaria.in/cli
|
|
@@ -11,6 +11,7 @@ Requires-Python: >=3.11
|
|
|
11
11
|
Requires-Dist: httpx>=0.28.1
|
|
12
12
|
Requires-Dist: keyring>=25.0
|
|
13
13
|
Requires-Dist: pyyaml>=6.0
|
|
14
|
+
Requires-Dist: rich>=13.7.0
|
|
14
15
|
Requires-Dist: sovaria-sdk>=0.2.0
|
|
15
16
|
Requires-Dist: tomli-w>=1.2.0
|
|
16
17
|
Requires-Dist: typer>=0.17.4
|
|
@@ -7,7 +7,8 @@ from urllib.parse import quote, urlencode
|
|
|
7
7
|
import typer
|
|
8
8
|
|
|
9
9
|
from apps.cli.src.utils.api_client import CLIRequestError, call_api
|
|
10
|
-
from apps.cli.src.utils.display import print_json
|
|
10
|
+
from apps.cli.src.utils.display import handle_cli_error, handle_command_error, print_json
|
|
11
|
+
from apps.cli.src.utils.project_context import resolve_org_id
|
|
11
12
|
|
|
12
13
|
app = typer.Typer(help="Platform admin: error traces, usage, transparency")
|
|
13
14
|
|
|
@@ -52,8 +53,7 @@ def error_traces_list(
|
|
|
52
53
|
params=params,
|
|
53
54
|
)
|
|
54
55
|
except CLIRequestError as exc:
|
|
55
|
-
|
|
56
|
-
raise typer.Exit(1) from exc
|
|
56
|
+
handle_cli_error(exc)
|
|
57
57
|
print_json(result)
|
|
58
58
|
|
|
59
59
|
|
|
@@ -71,8 +71,7 @@ def error_traces_get(
|
|
|
71
71
|
require_scope=False,
|
|
72
72
|
)
|
|
73
73
|
except CLIRequestError as exc:
|
|
74
|
-
|
|
75
|
-
raise typer.Exit(1) from exc
|
|
74
|
+
handle_cli_error(exc)
|
|
76
75
|
print_json(result)
|
|
77
76
|
|
|
78
77
|
|
|
@@ -92,8 +91,7 @@ def error_traces_resolve(
|
|
|
92
91
|
body={"resolved": resolved},
|
|
93
92
|
)
|
|
94
93
|
except CLIRequestError as exc:
|
|
95
|
-
|
|
96
|
-
raise typer.Exit(1) from exc
|
|
94
|
+
handle_cli_error(exc)
|
|
97
95
|
print_json(result)
|
|
98
96
|
|
|
99
97
|
|
|
@@ -118,14 +116,13 @@ def usage_overview(
|
|
|
118
116
|
params=params or None,
|
|
119
117
|
)
|
|
120
118
|
except CLIRequestError as exc:
|
|
121
|
-
|
|
122
|
-
raise typer.Exit(1) from exc
|
|
119
|
+
handle_cli_error(exc)
|
|
123
120
|
print_json(result)
|
|
124
121
|
|
|
125
122
|
|
|
126
123
|
@transparency_app.command("list")
|
|
127
124
|
def transparency_list(
|
|
128
|
-
org_id: str = typer.Option(
|
|
125
|
+
org_id: str | None = typer.Option(None, "--org", help="Organisation ID (defaults to scoped org)"),
|
|
129
126
|
project_id: str | None = typer.Option(None, "--project"),
|
|
130
127
|
event_type: str | None = typer.Option(None, "--event-type"),
|
|
131
128
|
since: str | None = typer.Option(None, "--since", help="ISO-8601 lower bound"),
|
|
@@ -134,8 +131,12 @@ def transparency_list(
|
|
|
134
131
|
profile: str | None = typer.Option(None, "--profile"),
|
|
135
132
|
) -> None:
|
|
136
133
|
"""List tenant-visible staff access transparency events."""
|
|
134
|
+
try:
|
|
135
|
+
resolved_org = resolve_org_id(profile=profile, org_id=org_id, require=True)
|
|
136
|
+
except ValueError as exc:
|
|
137
|
+
handle_command_error(exc)
|
|
137
138
|
query: dict[str, str] = {
|
|
138
|
-
"org_id":
|
|
139
|
+
"org_id": resolved_org,
|
|
139
140
|
"limit": str(limit),
|
|
140
141
|
"offset": str(offset),
|
|
141
142
|
}
|
|
@@ -151,10 +152,9 @@ def transparency_list(
|
|
|
151
152
|
method="GET",
|
|
152
153
|
path=path,
|
|
153
154
|
profile=profile,
|
|
154
|
-
org_id=
|
|
155
|
+
org_id=resolved_org,
|
|
155
156
|
require_scope=False,
|
|
156
157
|
)
|
|
157
158
|
except CLIRequestError as exc:
|
|
158
|
-
|
|
159
|
-
raise typer.Exit(1) from exc
|
|
159
|
+
handle_cli_error(exc)
|
|
160
160
|
print_json(result)
|
|
@@ -4,7 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
|
-
from apps.cli.src.utils.api_client import CLIRequestError, call_api, parse_json_arg
|
|
7
|
+
from apps.cli.src.utils.api_client import CLIRequestError, call_api, parse_json_arg
|
|
8
|
+
from apps.cli.src.utils.display import handle_cli_error, print_json
|
|
8
9
|
|
|
9
10
|
app = typer.Typer(help="Raw gateway API calls")
|
|
10
11
|
|
|
@@ -42,7 +43,6 @@ def call_command(
|
|
|
42
43
|
env_id=env_id,
|
|
43
44
|
)
|
|
44
45
|
except CLIRequestError as exc:
|
|
45
|
-
|
|
46
|
-
raise typer.Exit(1) from exc
|
|
46
|
+
handle_cli_error(exc)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
print_json(result)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Echo audit and simulation commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from apps.cli.src.utils.api_client import CLIRequestError, parse_json_arg
|
|
8
|
+
from apps.cli.src.utils.api_helpers import premium_api, premium_mutate
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Echo audit trail and simulation operations")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command("append")
|
|
14
|
+
def append_audit(
|
|
15
|
+
event_type: str = typer.Option(..., "--event-type"),
|
|
16
|
+
actor_id: str = typer.Option(..., "--actor-id"),
|
|
17
|
+
source: str = typer.Option(..., "--source"),
|
|
18
|
+
action: str = typer.Option(..., "--action"),
|
|
19
|
+
resource_id: str | None = typer.Option(None, "--resource-id"),
|
|
20
|
+
metadata: str | None = typer.Option(None, "--metadata", help="JSON object"),
|
|
21
|
+
ip_address: str | None = typer.Option(None, "--ip-address"),
|
|
22
|
+
profile: str | None = typer.Option(None, "--profile"),
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Append an immutable audit entry."""
|
|
25
|
+
parsed_metadata = parse_json_arg(metadata, label="--metadata") if metadata else {}
|
|
26
|
+
if parsed_metadata is not None and not isinstance(parsed_metadata, dict):
|
|
27
|
+
raise CLIRequestError("--metadata must be a JSON object")
|
|
28
|
+
premium_mutate(
|
|
29
|
+
"Appending audit entry…",
|
|
30
|
+
success="Appended",
|
|
31
|
+
title="Audit entry",
|
|
32
|
+
method="POST",
|
|
33
|
+
path="/api/v1/audit/",
|
|
34
|
+
profile=profile,
|
|
35
|
+
body={
|
|
36
|
+
"event_type": event_type,
|
|
37
|
+
"actor_id": actor_id,
|
|
38
|
+
"resource_id": resource_id,
|
|
39
|
+
"source": source,
|
|
40
|
+
"action": action,
|
|
41
|
+
"metadata": parsed_metadata or {},
|
|
42
|
+
"ip_address": ip_address,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command("list")
|
|
48
|
+
def list_audit(
|
|
49
|
+
actor_id: str | None = typer.Option(None, "--actor-id"),
|
|
50
|
+
event_type: str | None = typer.Option(None, "--event-type"),
|
|
51
|
+
source: str | None = typer.Option(None, "--source"),
|
|
52
|
+
resource_id: str | None = typer.Option(None, "--resource-id"),
|
|
53
|
+
limit: int = typer.Option(50, "--limit"),
|
|
54
|
+
offset: int = typer.Option(0, "--offset"),
|
|
55
|
+
profile: str | None = typer.Option(None, "--profile"),
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Query audit log entries."""
|
|
58
|
+
params = {
|
|
59
|
+
"limit": str(limit),
|
|
60
|
+
"offset": str(offset),
|
|
61
|
+
}
|
|
62
|
+
if actor_id:
|
|
63
|
+
params["actor_id"] = actor_id
|
|
64
|
+
if event_type:
|
|
65
|
+
params["event_type"] = event_type
|
|
66
|
+
if source:
|
|
67
|
+
params["source"] = source
|
|
68
|
+
if resource_id:
|
|
69
|
+
params["resource_id"] = resource_id
|
|
70
|
+
|
|
71
|
+
premium_api(
|
|
72
|
+
"Loading audit entries…",
|
|
73
|
+
success="Loaded",
|
|
74
|
+
title="Audit log",
|
|
75
|
+
method="GET",
|
|
76
|
+
path="/api/v1/audit/",
|
|
77
|
+
profile=profile,
|
|
78
|
+
params=params,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.command("verify")
|
|
83
|
+
def verify_chain(profile: str | None = typer.Option(None, "--profile")) -> None:
|
|
84
|
+
"""Verify audit hash-chain integrity."""
|
|
85
|
+
premium_api(
|
|
86
|
+
"Verifying audit chain…",
|
|
87
|
+
success="Verified",
|
|
88
|
+
title="Audit verification",
|
|
89
|
+
method="GET",
|
|
90
|
+
path="/api/v1/audit/verify",
|
|
91
|
+
profile=profile,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command("count")
|
|
96
|
+
def audit_count(profile: str | None = typer.Option(None, "--profile")) -> None:
|
|
97
|
+
"""Return audit log entry count."""
|
|
98
|
+
premium_api(
|
|
99
|
+
"Counting audit entries…",
|
|
100
|
+
success="Loaded",
|
|
101
|
+
title="Audit count",
|
|
102
|
+
method="GET",
|
|
103
|
+
path="/api/v1/audit/count",
|
|
104
|
+
profile=profile,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@app.command("sim-run")
|
|
109
|
+
def sim_run(
|
|
110
|
+
simulation_name: str = typer.Argument(...),
|
|
111
|
+
operations: str = typer.Argument(..., help="JSON array operations"),
|
|
112
|
+
context: str | None = typer.Option(None, "--context", help="JSON object"),
|
|
113
|
+
include_execution_tree: bool = typer.Option(False, "--include-execution-tree"),
|
|
114
|
+
profile: str | None = typer.Option(None, "--profile"),
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Run an Echo phantom simulation."""
|
|
117
|
+
parsed_ops = parse_json_arg(operations, label="operations")
|
|
118
|
+
if not isinstance(parsed_ops, list):
|
|
119
|
+
raise CLIRequestError("operations must be a JSON array")
|
|
120
|
+
|
|
121
|
+
parsed_context = parse_json_arg(context, label="--context") if context else {}
|
|
122
|
+
if parsed_context is not None and not isinstance(parsed_context, dict):
|
|
123
|
+
raise CLIRequestError("--context must be a JSON object")
|
|
124
|
+
|
|
125
|
+
premium_api(
|
|
126
|
+
f"Running simulation '{simulation_name}'…",
|
|
127
|
+
success="Completed",
|
|
128
|
+
title="Simulation",
|
|
129
|
+
method="POST",
|
|
130
|
+
path="/api/v1/sim/run",
|
|
131
|
+
profile=profile,
|
|
132
|
+
body={
|
|
133
|
+
"simulation_name": simulation_name,
|
|
134
|
+
"operations": parsed_ops,
|
|
135
|
+
"echo_flag": True,
|
|
136
|
+
"include_execution_tree": include_execution_tree,
|
|
137
|
+
"context": parsed_context or {},
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.command("sim-plan")
|
|
143
|
+
def sim_plan(
|
|
144
|
+
simulation_name: str = typer.Argument(...),
|
|
145
|
+
operations: str = typer.Argument(..., help="JSON array operations"),
|
|
146
|
+
context: str | None = typer.Option(None, "--context", help="JSON object"),
|
|
147
|
+
profile: str | None = typer.Option(None, "--profile"),
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Preview an Echo simulation plan."""
|
|
150
|
+
parsed_ops = parse_json_arg(operations, label="operations")
|
|
151
|
+
if not isinstance(parsed_ops, list):
|
|
152
|
+
raise CLIRequestError("operations must be a JSON array")
|
|
153
|
+
|
|
154
|
+
parsed_context = parse_json_arg(context, label="--context") if context else {}
|
|
155
|
+
if parsed_context is not None and not isinstance(parsed_context, dict):
|
|
156
|
+
raise CLIRequestError("--context must be a JSON object")
|
|
157
|
+
|
|
158
|
+
premium_api(
|
|
159
|
+
f"Planning simulation '{simulation_name}'…",
|
|
160
|
+
success="Planned",
|
|
161
|
+
title="Simulation plan",
|
|
162
|
+
method="POST",
|
|
163
|
+
path="/api/v1/sim/plan",
|
|
164
|
+
profile=profile,
|
|
165
|
+
body={
|
|
166
|
+
"simulation_name": simulation_name,
|
|
167
|
+
"operations": parsed_ops,
|
|
168
|
+
"echo_flag": True,
|
|
169
|
+
"include_execution_tree": True,
|
|
170
|
+
"context": parsed_context or {},
|
|
171
|
+
},
|
|
172
|
+
)
|
|
@@ -7,7 +7,9 @@ from urllib.parse import quote
|
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
|
-
from apps.cli.src.utils.api_client import CLIRequestError, call_api
|
|
10
|
+
from apps.cli.src.utils.api_client import CLIRequestError, call_api
|
|
11
|
+
from apps.cli.src.utils.display import handle_cli_error, print_json
|
|
12
|
+
from apps.cli.src.utils.premium_display import run_with_spinner
|
|
11
13
|
from apps.cli.src.utils.config import get_scope
|
|
12
14
|
|
|
13
15
|
app = typer.Typer(help="Manage product users within an org/project scope")
|
|
@@ -49,17 +51,20 @@ def list_users(
|
|
|
49
51
|
) -> None:
|
|
50
52
|
"""List product users for the resolved org/project scope."""
|
|
51
53
|
try:
|
|
52
|
-
result =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
result = run_with_spinner(
|
|
55
|
+
"Loading app users…",
|
|
56
|
+
lambda: call_api(
|
|
57
|
+
method="GET",
|
|
58
|
+
path=_project_auth_path(profile, project_id, "/users"),
|
|
59
|
+
profile=profile,
|
|
60
|
+
require_scope=True,
|
|
61
|
+
**_scope_args(org_id, project_id, env_id),
|
|
62
|
+
),
|
|
63
|
+
success="Users loaded",
|
|
58
64
|
)
|
|
59
65
|
except CLIRequestError as exc:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
typer.echo(print_json(result))
|
|
66
|
+
handle_cli_error(exc)
|
|
67
|
+
print_json(result, title="App users")
|
|
63
68
|
|
|
64
69
|
|
|
65
70
|
@app.command("ban")
|
|
@@ -81,9 +86,8 @@ def ban_user(
|
|
|
81
86
|
**_scope_args(org_id, project_id, env_id),
|
|
82
87
|
)
|
|
83
88
|
except CLIRequestError as exc:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
typer.echo(print_json(result))
|
|
89
|
+
handle_cli_error(exc)
|
|
90
|
+
print_json(result)
|
|
87
91
|
|
|
88
92
|
|
|
89
93
|
@app.command("delete")
|
|
@@ -105,9 +109,8 @@ def delete_user(
|
|
|
105
109
|
**_scope_args(org_id, project_id, env_id),
|
|
106
110
|
)
|
|
107
111
|
except CLIRequestError as exc:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
typer.echo(print_json(result))
|
|
112
|
+
handle_cli_error(exc)
|
|
113
|
+
print_json(result)
|
|
111
114
|
|
|
112
115
|
|
|
113
116
|
@app.command("roles")
|
|
@@ -129,9 +132,8 @@ def get_roles(
|
|
|
129
132
|
**_scope_args(org_id, project_id, env_id),
|
|
130
133
|
)
|
|
131
134
|
except CLIRequestError as exc:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
typer.echo(print_json(result))
|
|
135
|
+
handle_cli_error(exc)
|
|
136
|
+
print_json(result)
|
|
135
137
|
|
|
136
138
|
|
|
137
139
|
@app.command("roles-set")
|
|
@@ -155,9 +157,8 @@ def set_roles(
|
|
|
155
157
|
**_scope_args(org_id, project_id, env_id),
|
|
156
158
|
)
|
|
157
159
|
except CLIRequestError as exc:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
typer.echo(print_json(result))
|
|
160
|
+
handle_cli_error(exc)
|
|
161
|
+
print_json(result)
|
|
161
162
|
|
|
162
163
|
|
|
163
164
|
@app.command("create")
|
|
@@ -173,15 +174,18 @@ def create_user(
|
|
|
173
174
|
"""Create a product user in the current project scope."""
|
|
174
175
|
display_name = name or email.split("@", maxsplit=1)[0]
|
|
175
176
|
try:
|
|
176
|
-
result =
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
result = run_with_spinner(
|
|
178
|
+
f"Creating user {email}…",
|
|
179
|
+
lambda: call_api(
|
|
180
|
+
method="POST",
|
|
181
|
+
path=_project_auth_path(profile, project_id, "/users"),
|
|
182
|
+
profile=profile,
|
|
183
|
+
require_scope=True,
|
|
184
|
+
**_scope_args(org_id, project_id, env_id),
|
|
185
|
+
body={"name": display_name, "email": email, "password": password},
|
|
186
|
+
),
|
|
187
|
+
success="User created",
|
|
183
188
|
)
|
|
184
189
|
except CLIRequestError as exc:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
typer.echo(print_json(result))
|
|
190
|
+
handle_cli_error(exc)
|
|
191
|
+
print_json(result)
|