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.
- sovaria_cli-0.2.0/.github/workflows/ci.yml +39 -0
- sovaria_cli-0.2.0/.github/workflows/publish.yml +32 -0
- sovaria_cli-0.2.0/.gitignore +6 -0
- sovaria_cli-0.2.0/.python-version +1 -0
- sovaria_cli-0.2.0/Makefile +44 -0
- sovaria_cli-0.2.0/PKG-INFO +170 -0
- sovaria_cli-0.2.0/apps/cli/README.md +147 -0
- sovaria_cli-0.2.0/apps/cli/__init__.py +0 -0
- sovaria_cli-0.2.0/apps/cli/src/__init__.py +3 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/__init__.py +1 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/admin.py +160 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/api.py +48 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/audit.py +181 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/auth_product.py +187 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/billing.py +513 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/chrono.py +248 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/context.py +99 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/data.py +272 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/db.py +528 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/deploy.py +1099 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/governance.py +491 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/iam.py +436 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/init_cmd.py +177 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/keys.py +120 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/login.py +842 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/org.py +157 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/policy.py +317 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/project.py +130 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/realtime.py +364 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/scope.py +42 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/shell.py +415 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/sim.py +178 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/storage.py +405 -0
- sovaria_cli-0.2.0/apps/cli/src/commands/vector.py +204 -0
- sovaria_cli-0.2.0/apps/cli/src/main.py +169 -0
- sovaria_cli-0.2.0/apps/cli/src/utils/__init__.py +1 -0
- sovaria_cli-0.2.0/apps/cli/src/utils/api_client.py +329 -0
- sovaria_cli-0.2.0/apps/cli/src/utils/auth_store.py +299 -0
- sovaria_cli-0.2.0/apps/cli/src/utils/config.py +185 -0
- sovaria_cli-0.2.0/apps/cli/src/utils/display.py +62 -0
- sovaria_cli-0.2.0/apps/cli/src/utils/migrations.py +69 -0
- sovaria_cli-0.2.0/apps/cli/src/utils/project_context.py +124 -0
- sovaria_cli-0.2.0/apps/cli/tests/test_auth_store_profiles.py +103 -0
- sovaria_cli-0.2.0/apps/cli/tests/test_cli_security.py +110 -0
- sovaria_cli-0.2.0/apps/cli/tests/test_cli_smoke_paths.py +279 -0
- sovaria_cli-0.2.0/apps/cli/tests/test_config_profiles.py +55 -0
- sovaria_cli-0.2.0/apps/cli/tests/test_main_startup.py +29 -0
- sovaria_cli-0.2.0/apps/cli/tests/test_project_and_commands.py +196 -0
- sovaria_cli-0.2.0/pyproject.toml +47 -0
- sovaria_cli-0.2.0/pytest.ini +3 -0
- sovaria_cli-0.2.0/requirements-dev.txt +2 -0
- sovaria_cli-0.2.0/requirements.txt +7 -0
- 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 @@
|
|
|
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 @@
|
|
|
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))
|