sovaria-cli 0.2.0__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.
Files changed (53) hide show
  1. sovaria_cli-0.2.0/.github/workflows/ci.yml +39 -0
  2. sovaria_cli-0.2.0/.github/workflows/publish.yml +32 -0
  3. sovaria_cli-0.2.0/.gitignore +6 -0
  4. sovaria_cli-0.2.0/.python-version +1 -0
  5. sovaria_cli-0.2.0/Makefile +44 -0
  6. sovaria_cli-0.2.0/PKG-INFO +170 -0
  7. sovaria_cli-0.2.0/apps/cli/README.md +147 -0
  8. sovaria_cli-0.2.0/apps/cli/__init__.py +0 -0
  9. sovaria_cli-0.2.0/apps/cli/src/__init__.py +3 -0
  10. sovaria_cli-0.2.0/apps/cli/src/commands/__init__.py +1 -0
  11. sovaria_cli-0.2.0/apps/cli/src/commands/admin.py +160 -0
  12. sovaria_cli-0.2.0/apps/cli/src/commands/api.py +48 -0
  13. sovaria_cli-0.2.0/apps/cli/src/commands/audit.py +181 -0
  14. sovaria_cli-0.2.0/apps/cli/src/commands/auth_product.py +187 -0
  15. sovaria_cli-0.2.0/apps/cli/src/commands/billing.py +513 -0
  16. sovaria_cli-0.2.0/apps/cli/src/commands/chrono.py +248 -0
  17. sovaria_cli-0.2.0/apps/cli/src/commands/context.py +99 -0
  18. sovaria_cli-0.2.0/apps/cli/src/commands/data.py +272 -0
  19. sovaria_cli-0.2.0/apps/cli/src/commands/db.py +528 -0
  20. sovaria_cli-0.2.0/apps/cli/src/commands/deploy.py +1099 -0
  21. sovaria_cli-0.2.0/apps/cli/src/commands/governance.py +491 -0
  22. sovaria_cli-0.2.0/apps/cli/src/commands/iam.py +436 -0
  23. sovaria_cli-0.2.0/apps/cli/src/commands/init_cmd.py +177 -0
  24. sovaria_cli-0.2.0/apps/cli/src/commands/keys.py +120 -0
  25. sovaria_cli-0.2.0/apps/cli/src/commands/login.py +842 -0
  26. sovaria_cli-0.2.0/apps/cli/src/commands/org.py +157 -0
  27. sovaria_cli-0.2.0/apps/cli/src/commands/policy.py +317 -0
  28. sovaria_cli-0.2.0/apps/cli/src/commands/project.py +130 -0
  29. sovaria_cli-0.2.0/apps/cli/src/commands/realtime.py +364 -0
  30. sovaria_cli-0.2.0/apps/cli/src/commands/scope.py +42 -0
  31. sovaria_cli-0.2.0/apps/cli/src/commands/shell.py +415 -0
  32. sovaria_cli-0.2.0/apps/cli/src/commands/sim.py +178 -0
  33. sovaria_cli-0.2.0/apps/cli/src/commands/storage.py +405 -0
  34. sovaria_cli-0.2.0/apps/cli/src/commands/vector.py +204 -0
  35. sovaria_cli-0.2.0/apps/cli/src/main.py +169 -0
  36. sovaria_cli-0.2.0/apps/cli/src/utils/__init__.py +1 -0
  37. sovaria_cli-0.2.0/apps/cli/src/utils/api_client.py +329 -0
  38. sovaria_cli-0.2.0/apps/cli/src/utils/auth_store.py +299 -0
  39. sovaria_cli-0.2.0/apps/cli/src/utils/config.py +185 -0
  40. sovaria_cli-0.2.0/apps/cli/src/utils/display.py +62 -0
  41. sovaria_cli-0.2.0/apps/cli/src/utils/migrations.py +69 -0
  42. sovaria_cli-0.2.0/apps/cli/src/utils/project_context.py +124 -0
  43. sovaria_cli-0.2.0/apps/cli/tests/test_auth_store_profiles.py +103 -0
  44. sovaria_cli-0.2.0/apps/cli/tests/test_cli_security.py +110 -0
  45. sovaria_cli-0.2.0/apps/cli/tests/test_cli_smoke_paths.py +279 -0
  46. sovaria_cli-0.2.0/apps/cli/tests/test_config_profiles.py +55 -0
  47. sovaria_cli-0.2.0/apps/cli/tests/test_main_startup.py +29 -0
  48. sovaria_cli-0.2.0/apps/cli/tests/test_project_and_commands.py +196 -0
  49. sovaria_cli-0.2.0/pyproject.toml +47 -0
  50. sovaria_cli-0.2.0/pytest.ini +3 -0
  51. sovaria_cli-0.2.0/requirements-dev.txt +2 -0
  52. sovaria_cli-0.2.0/requirements.txt +7 -0
  53. sovaria_cli-0.2.0/scripts/cli_sdk_happy_path.sh +163 -0
@@ -0,0 +1,39 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install SDK + CLI
21
+ run: |
22
+ set -euo pipefail
23
+ if [ -f ../sovaria-sdk/apps/sdk/python/pyproject.toml ]; then
24
+ pip install -e ../sovaria-sdk/apps/sdk/python
25
+ elif [ -f sovaria-sdk/apps/sdk/python/pyproject.toml ]; then
26
+ pip install -e sovaria-sdk/apps/sdk/python
27
+ else
28
+ pip install "sovaria-sdk>=0.2.0"
29
+ fi
30
+ pip install -e ".[dev]"
31
+
32
+ - name: Run tests
33
+ run: pytest apps/cli/tests -q
34
+
35
+ - name: Build wheel
36
+ run: python -m build
37
+
38
+ - name: Validate wheel metadata
39
+ run: python -m twine check dist/*
@@ -0,0 +1,32 @@
1
+ name: Publish CLI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ confirm:
7
+ description: 'Type "publish" to upload sovaria-cli to PyPI'
8
+ required: true
9
+
10
+ jobs:
11
+ publish:
12
+ if: github.event.inputs.confirm == 'publish'
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+ permissions:
16
+ contents: read
17
+ id-token: write
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+
25
+ - name: Build wheel
26
+ run: |
27
+ pip install build twine
28
+ python -m build
29
+ python -m twine check dist/*
30
+
31
+ - name: Publish to PyPI
32
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ .venv/
3
+ .ruff_cache/
4
+ .mypy_cache/
5
+ dist/
6
+ *.egg-info/
@@ -0,0 +1 @@
1
+ 3.12.11
@@ -0,0 +1,44 @@
1
+ .PHONY: help install test lint format run smoke clean build publish-check
2
+
3
+ PYTHON ?= python3
4
+ PIP ?= pip
5
+ SDK_ROOT ?= ../sovaria-sdk
6
+
7
+ BLUE := \033[0;34m
8
+ GREEN := \033[0;32m
9
+ NC := \033[0m
10
+
11
+ help: ## Show this help message
12
+ @echo '$(BLUE)Sovaria CLI - Available Commands:$(NC)'
13
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[0;32m%-20s\033[0m %s\n", $$1, $$2}'
14
+
15
+ install: ## Install CLI in editable mode with local SDK
16
+ @if [ -d "$(SDK_ROOT)/apps/sdk/python" ]; then \
17
+ $(PIP) install -e "$(SDK_ROOT)/apps/sdk/python"; \
18
+ fi
19
+ $(PIP) install -e ".[dev]"
20
+
21
+ test: ## Run CLI tests
22
+ $(PYTHON) -m pytest apps/cli/tests
23
+
24
+ lint: ## Run Python linters for CLI
25
+ @ruff check apps/cli || true
26
+
27
+ format: ## Format CLI Python code
28
+ @ruff format apps/cli
29
+
30
+ run: ## Show CLI help
31
+ @$(PYTHON) -m apps.cli.src.main --help
32
+
33
+ smoke: ## Run CLI/SDK happy path smoke test
34
+ @bash scripts/cli_sdk_happy_path.sh
35
+
36
+ build: ## Build installable wheel
37
+ $(PYTHON) -m build
38
+
39
+ publish-check: build ## Validate wheel metadata before PyPI upload
40
+ $(PYTHON) -m twine check dist/*
41
+
42
+ clean: ## Remove local generated Python artifacts
43
+ @find . -type d \( -name "__pycache__" -o -name ".pytest_cache" -o -name ".ruff_cache" -o -name "dist" -o -name "build" -o -name "*.egg-info" \) -prune -exec rm -rf {} +
44
+ @echo '$(GREEN)✓ Cleaned CLI repo$(NC)'
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.5
2
+ Name: sovaria-cli
3
+ Version: 0.2.0
4
+ Summary: Sovaria BaaS command-line interface (sov)
5
+ Project-URL: Homepage, https://sovaria.in
6
+ Project-URL: Documentation, https://docs.sovaria.in/cli
7
+ Project-URL: Repository, https://github.com/sovaria/sovaria-cli
8
+ Author: Sovaria Team
9
+ License: MIT
10
+ Requires-Python: >=3.11
11
+ Requires-Dist: httpx>=0.28.1
12
+ Requires-Dist: keyring>=25.0
13
+ Requires-Dist: pyyaml>=6.0
14
+ Requires-Dist: sovaria-sdk>=0.2.0
15
+ Requires-Dist: tomli-w>=1.2.0
16
+ Requires-Dist: typer>=0.17.4
17
+ Provides-Extra: dev
18
+ Requires-Dist: build>=1.2; extra == 'dev'
19
+ Requires-Dist: pytest>=8.0; extra == 'dev'
20
+ Requires-Dist: ruff>=0.9; extra == 'dev'
21
+ Requires-Dist: twine>=6.0; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Sovaria CLI
25
+
26
+ Operator CLI for gateway-backed Sovaria project and platform workflows.
27
+
28
+ ## Run
29
+
30
+ Examples below use `sov ...` command syntax.
31
+
32
+ If `sov` is installed on your PATH:
33
+
34
+ ```bash
35
+ sov --help
36
+ ```
37
+
38
+ From repo root without installation:
39
+
40
+ ```bash
41
+ python -m apps.cli.src.main --help
42
+ ```
43
+
44
+ ## Cross-SDK Happy Path Smoke
45
+
46
+ Use the repository smoke script to validate one shared happy path across:
47
+
48
+ - CLI auth + scoped SQL call
49
+ - TypeScript SDK SQL + engine health
50
+ - Python SDK SQL + engine health
51
+
52
+ ```bash
53
+ SOVARIA_URL=http://localhost:8000 \
54
+ SOVARIA_ORG_ID=<org_uuid> \
55
+ SOVARIA_PROJECT_ID=<project_uuid> \
56
+ SOVARIA_EMAIL=<owner_email> \
57
+ SOVARIA_PASSWORD='<owner_password>' \
58
+ scripts/cli_sdk_happy_path.sh
59
+ ```
60
+
61
+ If you already have valid CLI tokens, omit `SOVARIA_EMAIL` and `SOVARIA_PASSWORD`.
62
+
63
+ ## Core Command Groups
64
+
65
+ - `sov auth ...` auth login/logout
66
+ - `sov scope ...` default org/project/env scope
67
+ - `sov data ...` Atlas-Doc and Atlas-Rel
68
+ - `sov realtime ...` Pulse realtime and e2ee
69
+ - `sov policy ...` Guard policy management
70
+ - `sov audit ...` Echo audit and simulation
71
+ - `sov chrono ...` Chrono timeline/snapshot operations
72
+ - `sov fn ...` Forge function deploy/status/invoke
73
+ - `sov project ...` project config template + scaffold creation
74
+ - `sov api call ...` raw gateway API request
75
+
76
+ ## Project Bootstrap (Config JSON)
77
+
78
+ Create a template config file:
79
+
80
+ ```bash
81
+ sov project init-config my-app --output sovaria.config.json
82
+ ```
83
+
84
+ Then scaffold a project from that config:
85
+
86
+ ```bash
87
+ sov project create --config sovaria.config.json --dir .
88
+ ```
89
+
90
+ ## Function Sandbox Flow
91
+
92
+ Deploy a function bundle (zipped from local folder):
93
+
94
+ ```bash
95
+ sov fn deploy ./my-function --name hello --project <project_id> --note "add audit logging"
96
+ ```
97
+
98
+ Poll deployment status:
99
+
100
+ ```bash
101
+ sov fn status <deploy_id> --project <project_id>
102
+ ```
103
+
104
+ Invoke the deployed function in its project sandbox:
105
+
106
+ ```bash
107
+ sov fn invoke hello --project <project_id> --data '{"message":"hi"}'
108
+ ```
109
+
110
+ Run a simulation invocation (tracked separately from normal invoke):
111
+
112
+ ```bash
113
+ sov fn invoke hello --project <project_id> --simulate --data '{"message":"dry-run"}'
114
+ ```
115
+
116
+ Queue heavy async invocation and wait for completion:
117
+
118
+ ```bash
119
+ sov fn invoke hello --project <project_id> --execution-profile heavy --async --wait --data-file payload.json
120
+ ```
121
+
122
+ List function versions:
123
+
124
+ ```bash
125
+ sov fn versions hello --project <project_id>
126
+ ```
127
+
128
+ List functions with active versions + queue depth:
129
+
130
+ ```bash
131
+ sov fn functions --project <project_id>
132
+ ```
133
+
134
+ List invocation history:
135
+
136
+ ```bash
137
+ sov fn history hello --project <project_id> --mode invoke
138
+ ```
139
+
140
+ List durable async jobs:
141
+
142
+ ```bash
143
+ sov fn jobs hello --project <project_id> --status QUEUED
144
+ ```
145
+
146
+ Promote a specific function version:
147
+
148
+ ```bash
149
+ sov fn promote hello --project <project_id> --version 3 --reason "promote stable release"
150
+ ```
151
+
152
+ Rollback to previous active version:
153
+
154
+ ```bash
155
+ sov fn rollback hello --project <project_id> --reason "rollback regression"
156
+ ```
157
+
158
+ Diff two simulation invocations:
159
+
160
+ ```bash
161
+ sov fn sim-diff hello <left_invocation_id> <right_invocation_id> --project <project_id>
162
+ ```
163
+
164
+ ## Scope Requirement
165
+
166
+ Realtime and relational routes require org/project scope. You can pass scope per command (`--org --project --env`) or set defaults:
167
+
168
+ ```bash
169
+ sov scope set <org_id> <project_id> --env <env_id>
170
+ ```
@@ -0,0 +1,147 @@
1
+ # Sovaria CLI
2
+
3
+ Operator CLI for gateway-backed Sovaria project and platform workflows.
4
+
5
+ ## Run
6
+
7
+ Examples below use `sov ...` command syntax.
8
+
9
+ If `sov` is installed on your PATH:
10
+
11
+ ```bash
12
+ sov --help
13
+ ```
14
+
15
+ From repo root without installation:
16
+
17
+ ```bash
18
+ python -m apps.cli.src.main --help
19
+ ```
20
+
21
+ ## Cross-SDK Happy Path Smoke
22
+
23
+ Use the repository smoke script to validate one shared happy path across:
24
+
25
+ - CLI auth + scoped SQL call
26
+ - TypeScript SDK SQL + engine health
27
+ - Python SDK SQL + engine health
28
+
29
+ ```bash
30
+ SOVARIA_URL=http://localhost:8000 \
31
+ SOVARIA_ORG_ID=<org_uuid> \
32
+ SOVARIA_PROJECT_ID=<project_uuid> \
33
+ SOVARIA_EMAIL=<owner_email> \
34
+ SOVARIA_PASSWORD='<owner_password>' \
35
+ scripts/cli_sdk_happy_path.sh
36
+ ```
37
+
38
+ If you already have valid CLI tokens, omit `SOVARIA_EMAIL` and `SOVARIA_PASSWORD`.
39
+
40
+ ## Core Command Groups
41
+
42
+ - `sov auth ...` auth login/logout
43
+ - `sov scope ...` default org/project/env scope
44
+ - `sov data ...` Atlas-Doc and Atlas-Rel
45
+ - `sov realtime ...` Pulse realtime and e2ee
46
+ - `sov policy ...` Guard policy management
47
+ - `sov audit ...` Echo audit and simulation
48
+ - `sov chrono ...` Chrono timeline/snapshot operations
49
+ - `sov fn ...` Forge function deploy/status/invoke
50
+ - `sov project ...` project config template + scaffold creation
51
+ - `sov api call ...` raw gateway API request
52
+
53
+ ## Project Bootstrap (Config JSON)
54
+
55
+ Create a template config file:
56
+
57
+ ```bash
58
+ sov project init-config my-app --output sovaria.config.json
59
+ ```
60
+
61
+ Then scaffold a project from that config:
62
+
63
+ ```bash
64
+ sov project create --config sovaria.config.json --dir .
65
+ ```
66
+
67
+ ## Function Sandbox Flow
68
+
69
+ Deploy a function bundle (zipped from local folder):
70
+
71
+ ```bash
72
+ sov fn deploy ./my-function --name hello --project <project_id> --note "add audit logging"
73
+ ```
74
+
75
+ Poll deployment status:
76
+
77
+ ```bash
78
+ sov fn status <deploy_id> --project <project_id>
79
+ ```
80
+
81
+ Invoke the deployed function in its project sandbox:
82
+
83
+ ```bash
84
+ sov fn invoke hello --project <project_id> --data '{"message":"hi"}'
85
+ ```
86
+
87
+ Run a simulation invocation (tracked separately from normal invoke):
88
+
89
+ ```bash
90
+ sov fn invoke hello --project <project_id> --simulate --data '{"message":"dry-run"}'
91
+ ```
92
+
93
+ Queue heavy async invocation and wait for completion:
94
+
95
+ ```bash
96
+ sov fn invoke hello --project <project_id> --execution-profile heavy --async --wait --data-file payload.json
97
+ ```
98
+
99
+ List function versions:
100
+
101
+ ```bash
102
+ sov fn versions hello --project <project_id>
103
+ ```
104
+
105
+ List functions with active versions + queue depth:
106
+
107
+ ```bash
108
+ sov fn functions --project <project_id>
109
+ ```
110
+
111
+ List invocation history:
112
+
113
+ ```bash
114
+ sov fn history hello --project <project_id> --mode invoke
115
+ ```
116
+
117
+ List durable async jobs:
118
+
119
+ ```bash
120
+ sov fn jobs hello --project <project_id> --status QUEUED
121
+ ```
122
+
123
+ Promote a specific function version:
124
+
125
+ ```bash
126
+ sov fn promote hello --project <project_id> --version 3 --reason "promote stable release"
127
+ ```
128
+
129
+ Rollback to previous active version:
130
+
131
+ ```bash
132
+ sov fn rollback hello --project <project_id> --reason "rollback regression"
133
+ ```
134
+
135
+ Diff two simulation invocations:
136
+
137
+ ```bash
138
+ sov fn sim-diff hello <left_invocation_id> <right_invocation_id> --project <project_id>
139
+ ```
140
+
141
+ ## Scope Requirement
142
+
143
+ Realtime and relational routes require org/project scope. You can pass scope per command (`--org --project --env`) or set defaults:
144
+
145
+ ```bash
146
+ sov scope set <org_id> <project_id> --env <env_id>
147
+ ```
File without changes
@@ -0,0 +1,3 @@
1
+ """Sovaria CLI — sov command."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1 @@
1
+ """CLI commands package."""
@@ -0,0 +1,160 @@
1
+ """Platform admin commands (error traces, usage overview, transparency)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from urllib.parse import quote, urlencode
6
+
7
+ import typer
8
+
9
+ from apps.cli.src.utils.api_client import CLIRequestError, call_api
10
+ from apps.cli.src.utils.display import print_json
11
+
12
+ app = typer.Typer(help="Platform admin: error traces, usage, transparency")
13
+
14
+ error_traces_app = typer.Typer(help="Error trace inspection and resolution")
15
+ transparency_app = typer.Typer(help="Access transparency audit log")
16
+
17
+ app.add_typer(error_traces_app, name="error-traces")
18
+ app.add_typer(transparency_app, name="transparency")
19
+
20
+
21
+ @error_traces_app.command("list")
22
+ def error_traces_list(
23
+ service: str | None = typer.Option(None, "--service"),
24
+ project_id: str | None = typer.Option(None, "--project"),
25
+ error_class: str | None = typer.Option(None, "--error-class"),
26
+ severity: str | None = typer.Option(None, "--severity"),
27
+ resolved: bool | None = typer.Option(None, "--resolved/--unresolved"),
28
+ since: str | None = typer.Option(None, "--since", help="ISO-8601 lower bound"),
29
+ limit: int = typer.Option(50, "--limit", min=1, max=200),
30
+ profile: str | None = typer.Option(None, "--profile"),
31
+ ) -> None:
32
+ """List recent platform error traces."""
33
+ params: dict[str, str] = {"limit": str(limit)}
34
+ if service:
35
+ params["service"] = service
36
+ if project_id:
37
+ params["project_id"] = project_id
38
+ if error_class:
39
+ params["error_class"] = error_class
40
+ if severity:
41
+ params["severity"] = severity
42
+ if resolved is not None:
43
+ params["resolved"] = str(resolved).lower()
44
+ if since:
45
+ params["since"] = since
46
+ try:
47
+ result = call_api(
48
+ method="GET",
49
+ path="/api/v1/error-traces",
50
+ profile=profile,
51
+ require_scope=False,
52
+ params=params,
53
+ )
54
+ except CLIRequestError as exc:
55
+ typer.echo(str(exc), err=True)
56
+ raise typer.Exit(1) from exc
57
+ print_json(result)
58
+
59
+
60
+ @error_traces_app.command("get")
61
+ def error_traces_get(
62
+ trace_id: str = typer.Argument(..., help="Error trace ID"),
63
+ profile: str | None = typer.Option(None, "--profile"),
64
+ ) -> None:
65
+ """Fetch one error trace with ordered hops."""
66
+ try:
67
+ result = call_api(
68
+ method="GET",
69
+ path=f"/api/v1/error-traces/{quote(trace_id, safe='')}",
70
+ profile=profile,
71
+ require_scope=False,
72
+ )
73
+ except CLIRequestError as exc:
74
+ typer.echo(str(exc), err=True)
75
+ raise typer.Exit(1) from exc
76
+ print_json(result)
77
+
78
+
79
+ @error_traces_app.command("resolve")
80
+ def error_traces_resolve(
81
+ trace_id: str = typer.Argument(..., help="Error trace ID"),
82
+ resolved: bool = typer.Option(True, "--resolved/--unresolved"),
83
+ profile: str | None = typer.Option(None, "--profile"),
84
+ ) -> None:
85
+ """Mark an error trace resolved or unresolved."""
86
+ try:
87
+ result = call_api(
88
+ method="PATCH",
89
+ path=f"/api/v1/error-traces/{quote(trace_id, safe='')}/resolved",
90
+ profile=profile,
91
+ require_scope=False,
92
+ body={"resolved": resolved},
93
+ )
94
+ except CLIRequestError as exc:
95
+ typer.echo(str(exc), err=True)
96
+ raise typer.Exit(1) from exc
97
+ print_json(result)
98
+
99
+
100
+ @app.command("usage-overview")
101
+ def usage_overview(
102
+ period: str | None = typer.Option(None, "--period", help="Billing period YYYY-MM"),
103
+ limit: int = typer.Option(100, "--limit", min=1, max=500),
104
+ profile: str | None = typer.Option(None, "--profile"),
105
+ ) -> None:
106
+ """Fetch cross-tenant usage overview (platform admin)."""
107
+ params: dict[str, str] = {}
108
+ if period:
109
+ params["period"] = period
110
+ if limit != 100:
111
+ params["limit"] = str(limit)
112
+ try:
113
+ result = call_api(
114
+ method="GET",
115
+ path="/api/v1/admin/usage/overview",
116
+ profile=profile,
117
+ require_scope=False,
118
+ params=params or None,
119
+ )
120
+ except CLIRequestError as exc:
121
+ typer.echo(str(exc), err=True)
122
+ raise typer.Exit(1) from exc
123
+ print_json(result)
124
+
125
+
126
+ @transparency_app.command("list")
127
+ def transparency_list(
128
+ org_id: str = typer.Option(..., "--org", help="Organisation ID"),
129
+ project_id: str | None = typer.Option(None, "--project"),
130
+ event_type: str | None = typer.Option(None, "--event-type"),
131
+ since: str | None = typer.Option(None, "--since", help="ISO-8601 lower bound"),
132
+ limit: int = typer.Option(50, "--limit", min=1, max=200),
133
+ offset: int = typer.Option(0, "--offset", min=0),
134
+ profile: str | None = typer.Option(None, "--profile"),
135
+ ) -> None:
136
+ """List tenant-visible staff access transparency events."""
137
+ query: dict[str, str] = {
138
+ "org_id": org_id,
139
+ "limit": str(limit),
140
+ "offset": str(offset),
141
+ }
142
+ if project_id:
143
+ query["project_id"] = project_id
144
+ if event_type:
145
+ query["event_type"] = event_type
146
+ if since:
147
+ query["since"] = since
148
+ path = f"/api/v1/access-transparency?{urlencode(query)}"
149
+ try:
150
+ result = call_api(
151
+ method="GET",
152
+ path=path,
153
+ profile=profile,
154
+ org_id=org_id,
155
+ require_scope=False,
156
+ )
157
+ except CLIRequestError as exc:
158
+ typer.echo(str(exc), err=True)
159
+ raise typer.Exit(1) from exc
160
+ print_json(result)
@@ -0,0 +1,48 @@
1
+ """Raw API passthrough commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from apps.cli.src.utils.api_client import CLIRequestError, call_api, parse_json_arg, print_json
8
+
9
+ app = typer.Typer(help="Raw gateway API calls")
10
+
11
+
12
+ @app.command("call")
13
+ def call_command(
14
+ method: str = typer.Argument(..., help="HTTP method, e.g. GET/POST"),
15
+ path: str = typer.Argument(..., help="Gateway path, e.g. /api/v1/system/engines/health"),
16
+ body: str | None = typer.Option(None, "--body", help="JSON request body"),
17
+ query: str | None = typer.Option(None, "--query", help="JSON object query params"),
18
+ profile: str | None = typer.Option(None, "--profile"),
19
+ no_auth: bool = typer.Option(False, "--no-auth", help="Do not send Authorization header"),
20
+ require_scope: bool = typer.Option(False, "--require-scope"),
21
+ org_id: str | None = typer.Option(None, "--org"),
22
+ project_id: str | None = typer.Option(None, "--project"),
23
+ env_id: str | None = typer.Option(None, "--env"),
24
+ ) -> None:
25
+ """Call any gateway endpoint directly."""
26
+ try:
27
+ parsed_body = parse_json_arg(body, label="--body")
28
+ parsed_query = parse_json_arg(query, label="--query")
29
+ if parsed_query is not None and not isinstance(parsed_query, dict):
30
+ raise CLIRequestError("--query must be a JSON object")
31
+
32
+ result = call_api(
33
+ method=method,
34
+ path=path,
35
+ profile=profile,
36
+ body=parsed_body,
37
+ params={str(k): str(v) for k, v in (parsed_query or {}).items()},
38
+ require_auth=not no_auth,
39
+ require_scope=require_scope,
40
+ org_id=org_id,
41
+ project_id=project_id,
42
+ env_id=env_id,
43
+ )
44
+ except CLIRequestError as exc:
45
+ typer.echo(str(exc), err=True)
46
+ raise typer.Exit(1) from exc
47
+
48
+ typer.echo(print_json(result))