sima-cli 2.1.8__tar.gz → 2.1.9__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.
- {sima_cli-2.1.8/sima_cli.egg-info → sima_cli-2.1.9}/PKG-INFO +3 -3
- {sima_cli-2.1.8 → sima_cli-2.1.9}/README.md +1 -1
- {sima_cli-2.1.8 → sima_cli-2.1.9}/pyproject.toml +2 -2
- {sima_cli-2.1.8 → sima_cli-2.1.9}/requirements.txt +1 -1
- {sima_cli-2.1.8 → sima_cli-2.1.9}/setup.py +1 -1
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/__version__.py +1 -1
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/auth/auth0.py +150 -4
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/auth/devportal.py +73 -40
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/metadata_installer.py +4 -4
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/utils.py +35 -19
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/elxr.py +213 -34
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/upgrade/selfupdate.py +2 -1
- sima_cli-2.1.9/sima_cli/utils/docker.py +391 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/vulcan/commands.py +1 -1
- {sima_cli-2.1.8 → sima_cli-2.1.9/sima_cli.egg-info}/PKG-INFO +3 -3
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli.egg-info/SOURCES.txt +2 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli.egg-info/requires.txt +1 -1
- sima_cli-2.1.9/tests/unit/test_auth.py +251 -0
- sima_cli-2.1.9/tests/unit/test_auth0.py +173 -0
- sima_cli-2.1.9/tests/unit/test_docker_utils.py +322 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_elxr_update.py +159 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_sdk_image_detection.py +69 -6
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_selfupdate.py +29 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_vulcan.py +7 -4
- sima_cli-2.1.8/sima_cli/utils/docker.py +0 -228
- sima_cli-2.1.8/tests/unit/test_auth.py +0 -143
- {sima_cli-2.1.8 → sima_cli-2.1.9}/LICENSE +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/MANIFEST.in +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/setup.cfg +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/__main__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/app_zoo/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/app_zoo/app.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/app_zoo/commands.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/auth/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/auth/login.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/auth/oauth.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/cli.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/data/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/data/resources_internal.yaml +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/data/resources_public.yaml +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/deploy_only/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/deploy_only/device/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/deploy_only/device/commands.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/deploy_only/mpk/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/deploy_only/mpk/commands.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/discover/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/discover/discover.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/discover/linuxll.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/download/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/download/downloader.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/github_assets.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/hostdriver.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/metadata_info.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/metadata_validator.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/optiview.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/package_builder.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/palette.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/install/registry.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/mla/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/mla/meminfo.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/model_zoo/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/model_zoo/model.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/network/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/network/network.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/playbooks/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/playbooks/commands.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/playbooks/manager.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/cmdexec.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/commands.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/config.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/install.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/linux_shared_network.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/neat.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/preinstall.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/requirements.json +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/script.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/stop.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/sdk/uninstall.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/serial/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/serial/serial.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/storage/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/storage/nvme.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/storage/sdcard.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/bmaptool.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/bootimg.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/cleanlog.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/local.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/netboot.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/query.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/remote.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/update/updater.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/upgrade/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/api_common.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/artifactory.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/common.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/config.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/config_loader.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/container_registries.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/device_api.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/disk.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/env.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/errors.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/mpk_api.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/net.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/network.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/pcie.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/pkg_update_check.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/serializers.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/services.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/utils/tag.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/vulcan/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli/vulcan/artifacts.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli.egg-info/dependency_links.txt +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli.egg-info/entry_points.txt +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/sima_cli.egg-info/top_level.txt +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/e2e/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/__init__.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_app_zoo.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_cli.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_cli_stdio.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_download.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_firmware.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_install_stub.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_metadata_installer.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_model_zoo.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_netboot.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_package_builder.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_pkg_update_check.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_sdk_preinstall.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_sdk_uninstall.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_skills_commands.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_skills_manager.py +0 -0
- {sima_cli-2.1.8 → sima_cli-2.1.9}/tests/unit/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sima-cli
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.9
|
|
4
4
|
Summary: CLI tool for SiMa Developer Portal to download models, firmware, and apps.
|
|
5
5
|
Home-page: https://developer.sima.ai/
|
|
6
6
|
Author: SiMa.ai
|
|
@@ -17,7 +17,7 @@ Requires-Python: >=3.8
|
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: click>=8.0
|
|
20
|
-
Requires-Dist: requests
|
|
20
|
+
Requires-Dist: requests<3.0,>=2.32.4
|
|
21
21
|
Requires-Dist: tqdm>=4.64
|
|
22
22
|
Requires-Dist: pyyaml>=6.0
|
|
23
23
|
Requires-Dist: paramiko>=3.5.1
|
|
@@ -163,7 +163,7 @@ sima-cli vulcan download --env production core main
|
|
|
163
163
|
- `dev`: `https://artifacts.neat.paconsultings.com`
|
|
164
164
|
- `staging`: `https://artifacts.stg.neat.sima.ai`
|
|
165
165
|
- `production`: `https://artifacts.neat.sima.ai`
|
|
166
|
-
- `dev` and `
|
|
166
|
+
- `dev`, `staging`, and `production` are available for Vulcan downloads.
|
|
167
167
|
- Usage:
|
|
168
168
|
- `sima-cli vulcan --env {dev|stg|staging|prd|prod|production} download [REPO] [BRANCH_OR_TAG]`
|
|
169
169
|
- `sima-cli vulcan download --env {dev|stg|staging|prd|prod|production} [REPO] [BRANCH_OR_TAG]`
|
|
@@ -129,7 +129,7 @@ sima-cli vulcan download --env production core main
|
|
|
129
129
|
- `dev`: `https://artifacts.neat.paconsultings.com`
|
|
130
130
|
- `staging`: `https://artifacts.stg.neat.sima.ai`
|
|
131
131
|
- `production`: `https://artifacts.neat.sima.ai`
|
|
132
|
-
- `dev` and `
|
|
132
|
+
- `dev`, `staging`, and `production` are available for Vulcan downloads.
|
|
133
133
|
- Usage:
|
|
134
134
|
- `sima-cli vulcan --env {dev|stg|staging|prd|prod|production} download [REPO] [BRANCH_OR_TAG]`
|
|
135
135
|
- `sima-cli vulcan download --env {dev|stg|staging|prd|prod|production} [REPO] [BRANCH_OR_TAG]`
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sima-cli"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.9"
|
|
8
8
|
description = "CLI tool for SiMa Developer Portal to download models, firmware, and apps."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
|
|
24
24
|
dependencies = [
|
|
25
25
|
"click>=8.0",
|
|
26
|
-
"requests>=2.
|
|
26
|
+
"requests>=2.32.4,<3.0",
|
|
27
27
|
"tqdm>=4.64",
|
|
28
28
|
"pyyaml>=6.0",
|
|
29
29
|
"paramiko>=3.5.1",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# sima_cli/__version__.py
|
|
2
|
-
__version__ = "2.1.
|
|
2
|
+
__version__ = "2.1.9"
|
|
@@ -15,7 +15,7 @@ import base64
|
|
|
15
15
|
import requests
|
|
16
16
|
import webbrowser
|
|
17
17
|
import click
|
|
18
|
-
from typing import Dict, Optional
|
|
18
|
+
from typing import Dict, Iterable, Optional, Tuple
|
|
19
19
|
|
|
20
20
|
from sima_cli.utils.config_loader import load_resource_config
|
|
21
21
|
|
|
@@ -24,6 +24,16 @@ from sima_cli.utils.config_loader import load_resource_config
|
|
|
24
24
|
# ─────────────────────────────────────────────
|
|
25
25
|
HOME_DIR = os.path.expanduser("~/.sima-cli")
|
|
26
26
|
TOKEN_FILE = os.path.join(HOME_DIR, ".tokens.json")
|
|
27
|
+
COOKIE_FILE = os.path.join(HOME_DIR, ".sima-cli-cookies.txt")
|
|
28
|
+
CSRF_FILE = os.path.join(HOME_DIR, ".sima-cli-csrf.json")
|
|
29
|
+
PROD_USERINFO_AUDIENCE = "https://sima-ai.us.auth0.com/userinfo"
|
|
30
|
+
STAGING_USERINFO_AUDIENCE = "https://dev-d3sxf54xfkcifph2.us.auth0.com/userinfo"
|
|
31
|
+
USERINFO_AUDIENCE = PROD_USERINFO_AUDIENCE
|
|
32
|
+
LATEST_EULA_GRANT = "LatestEULA"
|
|
33
|
+
DOC_ACCESS_GRANT = "DocsAccess"
|
|
34
|
+
DOC_ACCESS_GRANT_ALIASES = (DOC_ACCESS_GRANT, "DocAccess")
|
|
35
|
+
PROD_DISCOURSE_URL = "https://developer.sima.ai/login"
|
|
36
|
+
STAGING_DISCOURSE_URL = "https://discourse-dev.sima.ai/login"
|
|
27
37
|
|
|
28
38
|
# ─────────────────────────────────────────────
|
|
29
39
|
# Configuration loader
|
|
@@ -81,6 +91,16 @@ def load_tokens() -> Optional[Dict]:
|
|
|
81
91
|
return None
|
|
82
92
|
|
|
83
93
|
|
|
94
|
+
def clear_external_login_state():
|
|
95
|
+
"""Remove cached external-auth state so the next login starts cleanly."""
|
|
96
|
+
for path in (TOKEN_FILE, COOKIE_FILE, CSRF_FILE):
|
|
97
|
+
if os.path.exists(path):
|
|
98
|
+
try:
|
|
99
|
+
os.remove(path)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"⚠️ Failed to delete {path}: {e}")
|
|
102
|
+
|
|
103
|
+
|
|
84
104
|
def is_token_valid(tokens: dict) -> bool:
|
|
85
105
|
"""Check if access token still valid based on expires_in field."""
|
|
86
106
|
if not tokens:
|
|
@@ -131,6 +151,123 @@ def is_browser_available():
|
|
|
131
151
|
except webbrowser.Error:
|
|
132
152
|
return False
|
|
133
153
|
|
|
154
|
+
|
|
155
|
+
def _discourse_sign_in_url() -> str:
|
|
156
|
+
if os.getenv("USE_STAGING_DEV_PORTAL", "false").lower() in ("1", "true", "yes"):
|
|
157
|
+
return STAGING_DISCOURSE_URL
|
|
158
|
+
return PROD_DISCOURSE_URL
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _expected_userinfo_audience() -> str:
|
|
162
|
+
if os.getenv("USE_STAGING_DEV_PORTAL", "false").lower() in ("1", "true", "yes"):
|
|
163
|
+
return STAGING_USERINFO_AUDIENCE
|
|
164
|
+
return PROD_USERINFO_AUDIENCE
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _as_iterable(value) -> Iterable:
|
|
168
|
+
if value is None:
|
|
169
|
+
return []
|
|
170
|
+
if isinstance(value, (list, tuple, set)):
|
|
171
|
+
return value
|
|
172
|
+
return [value]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _access_token_claims(tokens: dict) -> dict:
|
|
176
|
+
return decode_jwt_payload(tokens.get("access_token", ""))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _access_token_has_userinfo_audience(claims: dict) -> bool:
|
|
180
|
+
return _expected_userinfo_audience() in _as_iterable(claims.get("aud"))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _access_token_has_grant(claims: dict, grant: str) -> bool:
|
|
184
|
+
grant_names = DOC_ACCESS_GRANT_ALIASES if grant in DOC_ACCESS_GRANT_ALIASES else (grant,)
|
|
185
|
+
for claim_name, claim_value in claims.items():
|
|
186
|
+
if claim_name in ("permissions", "grants", "roles") or claim_name.endswith(("/permissions", "/grants", "/roles")):
|
|
187
|
+
if any(grant_name in _as_iterable(claim_value) for grant_name in grant_names):
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
scope = claims.get("scope")
|
|
191
|
+
if isinstance(scope, str) and any(grant_name in scope.split() for grant_name in grant_names):
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def access_token_has_doc_access(tokens: dict) -> bool:
|
|
198
|
+
return _access_token_has_grant(_access_token_claims(tokens), DOC_ACCESS_GRANT)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def access_token_has_latest_eula(tokens: dict) -> bool:
|
|
202
|
+
return _access_token_has_grant(_access_token_claims(tokens), LATEST_EULA_GRANT)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _validate_access_token_requirements(tokens: dict) -> Tuple[bool, Dict[str, bool]]:
|
|
206
|
+
claims = _access_token_claims(tokens)
|
|
207
|
+
checks = {
|
|
208
|
+
"doc_access": _access_token_has_grant(claims, DOC_ACCESS_GRANT),
|
|
209
|
+
"latest_eula": _access_token_has_grant(claims, LATEST_EULA_GRANT),
|
|
210
|
+
"userinfo_audience": _access_token_has_userinfo_audience(claims),
|
|
211
|
+
}
|
|
212
|
+
return checks["doc_access"] and checks["latest_eula"] and checks["userinfo_audience"], checks
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _prompt_for_discourse_sign_in(checks: Dict[str, bool]) -> bool:
|
|
216
|
+
discourse_url = _discourse_sign_in_url()
|
|
217
|
+
missing = []
|
|
218
|
+
if not checks.get("latest_eula"):
|
|
219
|
+
missing.append("LatestEULA grant")
|
|
220
|
+
if not checks.get("userinfo_audience"):
|
|
221
|
+
missing.append(f"{_expected_userinfo_audience()} audience")
|
|
222
|
+
|
|
223
|
+
click.echo("")
|
|
224
|
+
click.secho("Developer Portal sign-in is required before sima-cli can continue.", fg="yellow")
|
|
225
|
+
if missing:
|
|
226
|
+
click.echo(f"Missing from the access token: {', '.join(missing)}.")
|
|
227
|
+
click.echo("Please sign in to Discourse / Developer Portal and accept the EULA if prompted.")
|
|
228
|
+
|
|
229
|
+
if is_browser_available():
|
|
230
|
+
click.echo(f"Opening sign-in page: {click.style(discourse_url, fg='cyan', bold=True)}")
|
|
231
|
+
try:
|
|
232
|
+
webbrowser.open(discourse_url)
|
|
233
|
+
except Exception:
|
|
234
|
+
click.echo("Browser could not be opened automatically. Open this link manually:")
|
|
235
|
+
click.secho(discourse_url, fg="cyan", bold=True)
|
|
236
|
+
else:
|
|
237
|
+
click.echo("Browser not available. Open this link manually:")
|
|
238
|
+
click.secho(discourse_url, fg="cyan", bold=True)
|
|
239
|
+
|
|
240
|
+
return click.confirm("Have you signed in to Discourse / Developer Portal?", default=True)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _ensure_access_token_requirements(tokens: dict, auth_cfg: dict) -> Optional[dict]:
|
|
244
|
+
while True:
|
|
245
|
+
valid, checks = _validate_access_token_requirements(tokens)
|
|
246
|
+
if valid:
|
|
247
|
+
return tokens
|
|
248
|
+
if not checks.get("doc_access"):
|
|
249
|
+
return tokens
|
|
250
|
+
|
|
251
|
+
if not _prompt_for_discourse_sign_in(checks):
|
|
252
|
+
clear_external_login_state()
|
|
253
|
+
click.echo("Logged out. Run `sima-cli login` again after signing in to Developer Portal.")
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
refresh_token = tokens.get("refresh_token")
|
|
257
|
+
if not refresh_token:
|
|
258
|
+
click.echo("Unable to refresh your access token after Developer Portal sign-in.")
|
|
259
|
+
clear_external_login_state()
|
|
260
|
+
click.echo("Logged out. Run `sima-cli login` again.")
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
refreshed = refresh_access_token(auth_cfg, refresh_token)
|
|
264
|
+
if not refreshed:
|
|
265
|
+
clear_external_login_state()
|
|
266
|
+
click.echo("Logged out. Run `sima-cli login` again.")
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
tokens = refreshed
|
|
270
|
+
|
|
134
271
|
def request_device_code(auth_cfg):
|
|
135
272
|
"""Step 1: Request device code from Auth0."""
|
|
136
273
|
data = {
|
|
@@ -269,16 +406,25 @@ def get_or_refresh_tokens(force=False):
|
|
|
269
406
|
tokens = load_tokens()
|
|
270
407
|
|
|
271
408
|
if tokens and is_token_valid(tokens) and not force:
|
|
272
|
-
|
|
409
|
+
tokens = _ensure_access_token_requirements(tokens, auth_cfg)
|
|
410
|
+
if tokens:
|
|
411
|
+
return tokens
|
|
412
|
+
return None
|
|
273
413
|
|
|
274
414
|
if tokens and tokens.get("refresh_token"):
|
|
275
415
|
refreshed = refresh_access_token(auth_cfg, tokens["refresh_token"])
|
|
276
416
|
if refreshed:
|
|
277
|
-
|
|
278
|
-
|
|
417
|
+
refreshed = _ensure_access_token_requirements(refreshed, auth_cfg)
|
|
418
|
+
if refreshed:
|
|
419
|
+
print_welcome_message(refreshed)
|
|
420
|
+
return refreshed
|
|
421
|
+
return None
|
|
279
422
|
print("⚠️ Refresh failed, falling back to new login.")
|
|
280
423
|
|
|
281
424
|
new_tokens = login_auth0(auth_cfg)
|
|
425
|
+
new_tokens = _ensure_access_token_requirements(new_tokens, auth_cfg)
|
|
426
|
+
if not new_tokens:
|
|
427
|
+
return None
|
|
282
428
|
print_welcome_message(new_tokens)
|
|
283
429
|
return new_tokens
|
|
284
430
|
|
|
@@ -7,6 +7,7 @@ import subprocess
|
|
|
7
7
|
import shutil
|
|
8
8
|
import base64
|
|
9
9
|
import hashlib
|
|
10
|
+
import webbrowser
|
|
10
11
|
|
|
11
12
|
from typing import Optional
|
|
12
13
|
from http.cookiejar import MozillaCookieJar
|
|
@@ -17,9 +18,14 @@ from sima_cli.utils.env import is_sima_board
|
|
|
17
18
|
from sima_cli.auth.auth0 import (
|
|
18
19
|
decode_jwt_payload,
|
|
19
20
|
extract_email,
|
|
21
|
+
access_token_has_doc_access,
|
|
22
|
+
access_token_has_latest_eula,
|
|
23
|
+
get_auth_config,
|
|
20
24
|
get_or_refresh_tokens,
|
|
21
25
|
get_cached_access_token,
|
|
26
|
+
is_browser_available,
|
|
22
27
|
load_tokens,
|
|
28
|
+
refresh_access_token,
|
|
23
29
|
)
|
|
24
30
|
|
|
25
31
|
HOME_DIR = os.path.expanduser("~/.sima-cli")
|
|
@@ -43,6 +49,7 @@ else:
|
|
|
43
49
|
|
|
44
50
|
# Derived endpoints
|
|
45
51
|
LOGIN_URL = f"{DEV_PORTAL}/session"
|
|
52
|
+
DEV_PORTAL_LOGIN_URL = f"{DEV_PORTAL}/login"
|
|
46
53
|
DUMMY_CHECK_URL = f"{DOCS_PORTAL}/pkg_downloads/validation"
|
|
47
54
|
ACCESS_REQUEST_FORM_URL = "https://www2.sima.ai/l/1041271/2025-05-05/37bndg"
|
|
48
55
|
USER_INFO_CLAIM = "https://auth.sima.ai/user_info"
|
|
@@ -61,14 +68,39 @@ HEADERS = {
|
|
|
61
68
|
"sec-ch-ua-platform": '"macOS"',
|
|
62
69
|
}
|
|
63
70
|
|
|
71
|
+
|
|
72
|
+
def _prompt_manual_developer_portal_login(confirm_completion: bool = False) -> bool:
|
|
73
|
+
click.secho(
|
|
74
|
+
f"\nOpen this page to accept EULA to proceed, press Y when you are done:\n{DEV_PORTAL_LOGIN_URL}\n",
|
|
75
|
+
fg="green",
|
|
76
|
+
)
|
|
77
|
+
if confirm_completion:
|
|
78
|
+
return click.confirm("Have you accepted the EULA?", default=True)
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _open_developer_portal_login_page(confirm_manual_completion: bool = False) -> bool:
|
|
83
|
+
if not is_browser_available():
|
|
84
|
+
return _prompt_manual_developer_portal_login(confirm_manual_completion)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
opened = webbrowser.open(DEV_PORTAL_LOGIN_URL)
|
|
88
|
+
except Exception:
|
|
89
|
+
opened = False
|
|
90
|
+
|
|
91
|
+
if not opened:
|
|
92
|
+
return _prompt_manual_developer_portal_login(confirm_manual_completion)
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
|
|
64
96
|
def _handle_eula_flow(session: requests.Session, username: str, domain: str) -> bool:
|
|
65
97
|
try:
|
|
66
98
|
click.echo("\n📄 To continue, you must accept the End-User License Agreement (EULA).")
|
|
67
|
-
click.echo("👉 Please sign in to Developer Portal
|
|
68
|
-
click.echo("👉 If you were not prompted with the EULA acceptance popup,
|
|
69
|
-
|
|
99
|
+
click.echo("👉 Please sign in to Developer Portal in your browser and accept the EULA if prompted.")
|
|
100
|
+
click.echo("👉 If you were not prompted with the EULA acceptance popup, try opening the page in an incognito browser.")
|
|
101
|
+
_open_developer_portal_login_page()
|
|
70
102
|
|
|
71
|
-
if not click.confirm("✅ Have you
|
|
103
|
+
if not click.confirm("✅ Have you signed in to Developer Portal and accepted the EULA?", default=True):
|
|
72
104
|
click.echo("❌ EULA acceptance is required to continue.")
|
|
73
105
|
return False
|
|
74
106
|
|
|
@@ -192,15 +224,20 @@ def _show_access_request_pending_message(already_submitted: bool = False):
|
|
|
192
224
|
click.secho("✅ Your access request has already been submitted.", fg="green")
|
|
193
225
|
else:
|
|
194
226
|
click.secho("✅ Your access request has been submitted.", fg="green")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _show_limited_access_pending_message():
|
|
230
|
+
click.secho("✅ You are signed in with limited Developer Portal access.", fg="green")
|
|
195
231
|
console.print(
|
|
196
232
|
Panel(
|
|
197
233
|
"\n".join(
|
|
198
234
|
[
|
|
199
|
-
"SiMa is reviewing your
|
|
235
|
+
"SiMa is reviewing your account and will grant full access shortly.",
|
|
200
236
|
"Please look out for an email from marketing@marketing.sima.ai.",
|
|
201
|
-
"Once access is granted, run `sima-cli login` again.",
|
|
202
237
|
]
|
|
203
238
|
),
|
|
239
|
+
title="Notice",
|
|
240
|
+
title_align="center",
|
|
204
241
|
border_style="yellow",
|
|
205
242
|
style="yellow",
|
|
206
243
|
expand=False,
|
|
@@ -208,27 +245,26 @@ def _show_access_request_pending_message(already_submitted: bool = False):
|
|
|
208
245
|
)
|
|
209
246
|
|
|
210
247
|
|
|
211
|
-
def
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
)
|
|
248
|
+
def _prompt_eula_acceptance_after_access_request() -> bool:
|
|
249
|
+
click.echo("\n📄 To continue, accept the End-User License Agreement (EULA).")
|
|
250
|
+
click.echo("👉 Please sign in to Developer Portal in your browser and accept the EULA if prompted.")
|
|
251
|
+
_open_developer_portal_login_page()
|
|
252
|
+
if not click.confirm("✅ Have you signed in to Developer Portal and accepted the EULA?", default=True):
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
tokens = load_tokens() or {}
|
|
256
|
+
refresh_token = tokens.get("refresh_token")
|
|
257
|
+
if not refresh_token:
|
|
258
|
+
click.secho("⚠️ Unable to refresh your access token: missing refresh token.", fg="yellow")
|
|
259
|
+
return False
|
|
260
|
+
|
|
261
|
+
click.echo("Refreshing access token after EULA acceptance...")
|
|
262
|
+
refreshed = refresh_access_token(get_auth_config(), refresh_token)
|
|
263
|
+
if not refreshed:
|
|
264
|
+
click.secho("⚠️ Unable to refresh your access token after EULA acceptance.", fg="yellow")
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
return True
|
|
232
268
|
|
|
233
269
|
|
|
234
270
|
def _submit_access_request() -> bool:
|
|
@@ -243,19 +279,10 @@ def _submit_access_request() -> bool:
|
|
|
243
279
|
)
|
|
244
280
|
return False
|
|
245
281
|
|
|
246
|
-
|
|
247
|
-
_show_access_request_pending_message(already_submitted=True)
|
|
248
|
-
_logout_external_credentials()
|
|
249
|
-
return False
|
|
250
|
-
|
|
251
|
-
_show_access_request_info_panel()
|
|
252
|
-
message = click.prompt(
|
|
253
|
-
click.style("Please briefly describe your project", fg="yellow"),
|
|
254
|
-
type=str,
|
|
255
|
-
).strip()
|
|
256
|
-
payload = _build_access_request_payload(claims, message)
|
|
282
|
+
payload = _build_access_request_payload(claims, "sima-cli sign up request")
|
|
257
283
|
|
|
258
284
|
try:
|
|
285
|
+
click.echo("Submitting Developer Portal access request...")
|
|
259
286
|
response = requests.post(ACCESS_REQUEST_FORM_URL, data=payload, timeout=15)
|
|
260
287
|
response.raise_for_status()
|
|
261
288
|
except requests.RequestException as e:
|
|
@@ -264,7 +291,8 @@ def _submit_access_request() -> bool:
|
|
|
264
291
|
|
|
265
292
|
_mark_access_request_submitted(claims)
|
|
266
293
|
_show_access_request_pending_message()
|
|
267
|
-
|
|
294
|
+
if not access_token_has_latest_eula(load_tokens() or {}):
|
|
295
|
+
_prompt_eula_acceptance_after_access_request()
|
|
268
296
|
return False
|
|
269
297
|
|
|
270
298
|
|
|
@@ -355,7 +383,12 @@ def login_external(force=False, loginDocker=True):
|
|
|
355
383
|
if _ACCESS_REQUEST_HANDLED:
|
|
356
384
|
return None
|
|
357
385
|
|
|
358
|
-
get_or_refresh_tokens(force=force)
|
|
386
|
+
tokens = get_or_refresh_tokens(force=force)
|
|
387
|
+
if not tokens:
|
|
388
|
+
return None
|
|
389
|
+
if not access_token_has_doc_access(tokens):
|
|
390
|
+
return _submit_access_request()
|
|
391
|
+
|
|
359
392
|
session, valid = validate_session()
|
|
360
393
|
if valid:
|
|
361
394
|
if loginDocker:
|
|
@@ -12,7 +12,7 @@ import shlex
|
|
|
12
12
|
import platform
|
|
13
13
|
import hashlib
|
|
14
14
|
from urllib.parse import urlparse, quote, urljoin, unquote
|
|
15
|
-
from typing import Dict
|
|
15
|
+
from typing import Dict, List
|
|
16
16
|
from tqdm import tqdm
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import subprocess
|
|
@@ -298,7 +298,7 @@ def _resolve_resource_url(base_url: str, resource: str) -> str:
|
|
|
298
298
|
)
|
|
299
299
|
return urljoin(base_url, encoded_resource)
|
|
300
300
|
|
|
301
|
-
def _resolve_resource_url_candidates(base_url: str, resource: str) ->
|
|
301
|
+
def _resolve_resource_url_candidates(base_url: str, resource: str) -> List[str]:
|
|
302
302
|
primary_url = _resolve_resource_url(base_url, resource)
|
|
303
303
|
|
|
304
304
|
parsed_resource = urlparse(resource)
|
|
@@ -338,12 +338,12 @@ def _normalize_downloaded_metadata_resource(local_path: str, expected_path: Path
|
|
|
338
338
|
|
|
339
339
|
def _download_metadata_file_resource(
|
|
340
340
|
resource: str,
|
|
341
|
-
resource_urls:
|
|
341
|
+
resource_urls: List[str],
|
|
342
342
|
dest_folder: str,
|
|
343
343
|
dest_path: Path,
|
|
344
344
|
internal: bool,
|
|
345
345
|
) -> str:
|
|
346
|
-
errors:
|
|
346
|
+
errors: List[str] = []
|
|
347
347
|
for index, resource_url in enumerate(resource_urls):
|
|
348
348
|
try:
|
|
349
349
|
local_path = download_file_from_url(
|
|
@@ -347,19 +347,22 @@ def prompt_multi_select(baseline_present: bool = False) -> List[str]:
|
|
|
347
347
|
|
|
348
348
|
return deduped
|
|
349
349
|
|
|
350
|
-
def run_command(cmd, capture_output=False):
|
|
350
|
+
def run_command(cmd, capture_output=False, fatal=True):
|
|
351
351
|
"""Run a shell command and return output if requested."""
|
|
352
352
|
try:
|
|
353
353
|
if capture_output:
|
|
354
354
|
return subprocess.check_output(cmd, text=True).strip()
|
|
355
355
|
else:
|
|
356
356
|
subprocess.run(cmd, check=True)
|
|
357
|
-
return None
|
|
357
|
+
return True if not fatal else None
|
|
358
358
|
except subprocess.CalledProcessError as e:
|
|
359
|
-
|
|
359
|
+
prefix = "❌ Command failed" if fatal else "⚠️ Optional command failed"
|
|
360
|
+
print(f"{prefix}: {' '.join(cmd)}")
|
|
360
361
|
if e.stderr:
|
|
361
362
|
print(f"Error: {e.stderr.strip()}")
|
|
362
|
-
|
|
363
|
+
if fatal:
|
|
364
|
+
sys.exit(1)
|
|
365
|
+
return False
|
|
363
366
|
|
|
364
367
|
|
|
365
368
|
### Dynamic port allocation
|
|
@@ -664,7 +667,7 @@ def _copy_sima_cli_auth_cache_to_container(sdk_container_name: str, login_name:
|
|
|
664
667
|
return
|
|
665
668
|
|
|
666
669
|
container_auth_dir = f"/home/{login_name}/.sima-cli"
|
|
667
|
-
run_command([
|
|
670
|
+
if not run_command([
|
|
668
671
|
"docker",
|
|
669
672
|
"exec",
|
|
670
673
|
"-u",
|
|
@@ -673,14 +676,29 @@ def _copy_sima_cli_auth_cache_to_container(sdk_container_name: str, login_name:
|
|
|
673
676
|
"mkdir",
|
|
674
677
|
"-p",
|
|
675
678
|
container_auth_dir,
|
|
676
|
-
])
|
|
679
|
+
], fatal=False):
|
|
680
|
+
print("⚠️ Could not create sima-cli auth cache directory in Neat SDK container; continuing setup.")
|
|
681
|
+
return
|
|
682
|
+
if not run_command([
|
|
683
|
+
"docker",
|
|
684
|
+
"exec",
|
|
685
|
+
"-u",
|
|
686
|
+
"root",
|
|
687
|
+
sdk_container_name,
|
|
688
|
+
"chown",
|
|
689
|
+
f"{uid}:{gid}",
|
|
690
|
+
container_auth_dir,
|
|
691
|
+
], fatal=False):
|
|
692
|
+
print("⚠️ Could not update ownership for sima-cli auth cache directory; continuing setup.")
|
|
677
693
|
|
|
678
694
|
copied = []
|
|
679
695
|
for filename in existing_files:
|
|
680
696
|
host_path = os.path.join(host_sima_cli_dir, filename)
|
|
681
697
|
container_path = f"{container_auth_dir}/{filename}"
|
|
682
|
-
run_command(["docker", "cp", host_path, f"{sdk_container_name}:{container_path}"])
|
|
683
|
-
|
|
698
|
+
if not run_command(["docker", "cp", host_path, f"{sdk_container_name}:{container_path}"], fatal=False):
|
|
699
|
+
print(f"⚠️ Could not copy host sima-cli auth cache file '{filename}' into Neat SDK container; continuing setup.")
|
|
700
|
+
continue
|
|
701
|
+
if not run_command([
|
|
684
702
|
"docker",
|
|
685
703
|
"exec",
|
|
686
704
|
"-u",
|
|
@@ -689,19 +707,15 @@ def _copy_sima_cli_auth_cache_to_container(sdk_container_name: str, login_name:
|
|
|
689
707
|
"chown",
|
|
690
708
|
f"{uid}:{gid}",
|
|
691
709
|
container_path,
|
|
692
|
-
])
|
|
710
|
+
], fatal=False):
|
|
711
|
+
print(f"⚠️ Could not update ownership for copied sima-cli auth cache file '{filename}'; continuing setup.")
|
|
712
|
+
continue
|
|
693
713
|
copied.append(filename)
|
|
694
714
|
|
|
695
|
-
|
|
696
|
-
"
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
"root",
|
|
700
|
-
sdk_container_name,
|
|
701
|
-
"chown",
|
|
702
|
-
f"{uid}:{gid}",
|
|
703
|
-
container_auth_dir,
|
|
704
|
-
])
|
|
715
|
+
if not copied:
|
|
716
|
+
print("ℹ️ No sima-cli auth cache files were copied into Neat SDK container; continuing setup.")
|
|
717
|
+
return
|
|
718
|
+
|
|
705
719
|
print(f"🔐 Copied sima-cli auth cache file(s) into Neat SDK container: {', '.join(copied)}")
|
|
706
720
|
|
|
707
721
|
|
|
@@ -918,6 +932,8 @@ def install_neat_playbooks(sdk_container_name: str, login_name: str) -> None:
|
|
|
918
932
|
login_name,
|
|
919
933
|
"-e",
|
|
920
934
|
"SIMA_CLI_CHECK_FOR_UPDATE=0",
|
|
935
|
+
"-e",
|
|
936
|
+
"GITHUB_TOKEN",
|
|
921
937
|
sdk_container_name,
|
|
922
938
|
"bash",
|
|
923
939
|
"-lc",
|