sima-cli 0.0.13__py3-none-any.whl → 0.0.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sima_cli/__version__.py +1 -1
- sima_cli/auth/basic_auth.py +132 -0
- sima_cli/auth/login.py +6 -4
- sima_cli/cli.py +43 -15
- sima_cli/data/resources_internal.yaml +9 -0
- sima_cli/data/resources_public.yaml +9 -0
- sima_cli/mla/meminfo.py +47 -0
- sima_cli/model_zoo/model.py +117 -2
- sima_cli/update/query.py +64 -0
- sima_cli/update/updater.py +74 -27
- sima_cli/utils/config_loader.py +30 -5
- {sima_cli-0.0.13.dist-info → sima_cli-0.0.15.dist-info}/METADATA +3 -1
- {sima_cli-0.0.13.dist-info → sima_cli-0.0.15.dist-info}/RECORD +17 -12
- {sima_cli-0.0.13.dist-info → sima_cli-0.0.15.dist-info}/WHEEL +1 -1
- {sima_cli-0.0.13.dist-info → sima_cli-0.0.15.dist-info}/entry_points.txt +0 -0
- {sima_cli-0.0.13.dist-info → sima_cli-0.0.15.dist-info}/licenses/LICENSE +0 -0
- {sima_cli-0.0.13.dist-info → sima_cli-0.0.15.dist-info}/top_level.txt +0 -0
sima_cli/__version__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# sima_cli/__version__.py
|
2
|
-
__version__ = "0.0.
|
2
|
+
__version__ = "0.0.15"
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import os
|
2
|
+
import click
|
3
|
+
import getpass
|
4
|
+
import requests
|
5
|
+
import json
|
6
|
+
from http.cookiejar import MozillaCookieJar
|
7
|
+
|
8
|
+
HOME_DIR = os.path.expanduser("~/.sima-cli")
|
9
|
+
COOKIE_JAR_PATH = os.path.join(HOME_DIR, ".sima-cli-cookies.txt")
|
10
|
+
CSRF_PATH = os.path.join(HOME_DIR, ".sima-cli-csrf.json")
|
11
|
+
|
12
|
+
CSRF_URL = "https://developer.sima.ai/session/csrf"
|
13
|
+
LOGIN_URL = "https://developer.sima.ai/session"
|
14
|
+
DUMMY_CHECK_URL = "https://docs.sima.ai/pkg_downloads/dummy"
|
15
|
+
|
16
|
+
HEADERS = {
|
17
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
18
|
+
"X-Requested-With": "XMLHttpRequest",
|
19
|
+
"Referer": "https://developer.sima.ai/login",
|
20
|
+
"Origin": "https://developer.sima.ai",
|
21
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
22
|
+
"sec-ch-ua": '"Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"',
|
23
|
+
"sec-ch-ua-mobile": "?0",
|
24
|
+
"sec-ch-ua-platform": '"macOS"',
|
25
|
+
}
|
26
|
+
|
27
|
+
|
28
|
+
def _is_session_valid(session: requests.Session) -> bool:
|
29
|
+
try:
|
30
|
+
response = session.get(DUMMY_CHECK_URL, allow_redirects=False)
|
31
|
+
return response.status_code == 200
|
32
|
+
except Exception:
|
33
|
+
return False
|
34
|
+
|
35
|
+
def _delete_auth_files():
|
36
|
+
for path in [COOKIE_JAR_PATH, CSRF_PATH]:
|
37
|
+
if os.path.exists(path):
|
38
|
+
try:
|
39
|
+
os.remove(path)
|
40
|
+
except Exception as e:
|
41
|
+
click.echo(f"⚠️ Could not delete {path}: {e}")
|
42
|
+
|
43
|
+
|
44
|
+
def _save_cookie_jar(session: requests.Session):
|
45
|
+
cj = MozillaCookieJar(COOKIE_JAR_PATH)
|
46
|
+
for c in session.cookies:
|
47
|
+
cj.set_cookie(c)
|
48
|
+
cj.save(ignore_discard=True)
|
49
|
+
|
50
|
+
|
51
|
+
def _load_cookie_jar(session: requests.Session):
|
52
|
+
if os.path.exists(COOKIE_JAR_PATH):
|
53
|
+
cj = MozillaCookieJar()
|
54
|
+
cj.load(COOKIE_JAR_PATH, ignore_discard=True)
|
55
|
+
session.cookies.update(cj)
|
56
|
+
|
57
|
+
|
58
|
+
def _load_csrf_token() -> str:
|
59
|
+
if os.path.exists(CSRF_PATH):
|
60
|
+
with open(CSRF_PATH, "r") as f:
|
61
|
+
data = json.load(f)
|
62
|
+
return data.get("csrf", "")
|
63
|
+
return ""
|
64
|
+
|
65
|
+
|
66
|
+
def _fetch_and_store_csrf_token(session: requests.Session) -> str:
|
67
|
+
try:
|
68
|
+
resp = session.get(CSRF_URL)
|
69
|
+
resp.raise_for_status()
|
70
|
+
csrf_token = resp.json().get("csrf")
|
71
|
+
if csrf_token:
|
72
|
+
with open(CSRF_PATH, "w") as f:
|
73
|
+
json.dump({"csrf": csrf_token}, f)
|
74
|
+
return csrf_token
|
75
|
+
except Exception as e:
|
76
|
+
click.echo(f"❌ Failed to fetch CSRF token: {e}")
|
77
|
+
return ""
|
78
|
+
|
79
|
+
|
80
|
+
def login_external():
|
81
|
+
"""Interactive login workflow with CSRF token, cookie caching, and dummy session validation."""
|
82
|
+
for attempt in range(1, 4):
|
83
|
+
session = requests.Session()
|
84
|
+
session.headers.update(HEADERS)
|
85
|
+
|
86
|
+
_load_cookie_jar(session)
|
87
|
+
csrf_token = _load_csrf_token()
|
88
|
+
|
89
|
+
if not csrf_token:
|
90
|
+
csrf_token = _fetch_and_store_csrf_token(session)
|
91
|
+
|
92
|
+
if not csrf_token:
|
93
|
+
click.echo("❌ CSRF token is missing or invalid.")
|
94
|
+
continue
|
95
|
+
|
96
|
+
session.headers["X-CSRF-Token"] = csrf_token
|
97
|
+
|
98
|
+
if _is_session_valid(session):
|
99
|
+
click.echo("🚀 Reusing existing session.")
|
100
|
+
return session
|
101
|
+
|
102
|
+
# Prompt user login
|
103
|
+
_delete_auth_files()
|
104
|
+
click.echo(f"🔐 Login attempt {attempt}/3")
|
105
|
+
username = click.prompt("Email or Username")
|
106
|
+
password = getpass.getpass("Password: ")
|
107
|
+
|
108
|
+
login_data = {
|
109
|
+
"login": username,
|
110
|
+
"password": password,
|
111
|
+
"second_factor_method": "1"
|
112
|
+
}
|
113
|
+
|
114
|
+
try:
|
115
|
+
resp = session.post(LOGIN_URL, data=login_data)
|
116
|
+
name = resp.json().get('users')[0]['name'] if 'users' in resp.json() else ''
|
117
|
+
if resp.status_code != 200:
|
118
|
+
click.echo(f"⚠️ Login request returned status {resp.status_code}")
|
119
|
+
continue
|
120
|
+
except Exception as e:
|
121
|
+
click.echo(f"❌ Login request failed: {e}")
|
122
|
+
continue
|
123
|
+
|
124
|
+
if _is_session_valid(session):
|
125
|
+
_save_cookie_jar(session)
|
126
|
+
click.echo(f"✅ Login successful. Welcome to Sima Developer Portal, {name}!")
|
127
|
+
return session
|
128
|
+
else:
|
129
|
+
click.echo("❌ Login failed.")
|
130
|
+
|
131
|
+
click.echo("❌ Login failed after 3 attempts.")
|
132
|
+
raise SystemExit(1)
|
sima_cli/auth/login.py
CHANGED
@@ -2,8 +2,9 @@ import click
|
|
2
2
|
import getpass
|
3
3
|
import requests
|
4
4
|
from sima_cli.utils.config import set_auth_token, get_auth_token
|
5
|
-
from sima_cli.utils.config_loader import load_resource_config
|
5
|
+
from sima_cli.utils.config_loader import load_resource_config, artifactory_url
|
6
6
|
from sima_cli.utils.artifactory import exchange_identity_token, validate_token
|
7
|
+
from sima_cli.auth.basic_auth import login_external
|
7
8
|
|
8
9
|
def login(method: str = "external"):
|
9
10
|
"""
|
@@ -30,7 +31,7 @@ def login_internal():
|
|
30
31
|
|
31
32
|
cfg = load_resource_config()
|
32
33
|
auth_cfg = cfg.get("internal", {}).get("auth", {})
|
33
|
-
base_url =
|
34
|
+
base_url = artifactory_url()
|
34
35
|
validate_url = auth_cfg.get("validate_url")
|
35
36
|
internal_url = auth_cfg.get("internal_url")
|
36
37
|
validate_url = f"{base_url}/{validate_url}"
|
@@ -67,13 +68,14 @@ def login_internal():
|
|
67
68
|
click.echo(f"💾 Short-lived access token saved successfully for {user_name} (valid for 7 days).")
|
68
69
|
|
69
70
|
|
70
|
-
def
|
71
|
+
def _login_external():
|
71
72
|
"""
|
72
73
|
External login using Developer Portal endpoint defined in the 'public' section of YAML config.
|
73
74
|
Prompts for username/password and retrieves access token.
|
74
75
|
"""
|
76
|
+
|
75
77
|
cfg = load_resource_config()
|
76
|
-
auth_url = cfg.get("public", {}).get("auth", {}).get("
|
78
|
+
auth_url = cfg.get("public", {}).get("auth", {}).get("auth_url")
|
77
79
|
|
78
80
|
if not auth_url:
|
79
81
|
click.echo("❌ External auth URL not configured in YAML.")
|
sima_cli/cli.py
CHANGED
@@ -2,8 +2,9 @@ import os
|
|
2
2
|
import click
|
3
3
|
from sima_cli.utils.env import get_environment_type
|
4
4
|
from sima_cli.update.updater import perform_update
|
5
|
-
from sima_cli.model_zoo.model import list_models, download_model
|
5
|
+
from sima_cli.model_zoo.model import list_models, download_model, describe_model
|
6
6
|
from sima_cli.utils.config_loader import internal_resource_exists
|
7
|
+
from sima_cli.mla.meminfo import monitor_simaai_mem_chart
|
7
8
|
|
8
9
|
# Entry point for the CLI tool using Click's command group decorator
|
9
10
|
@click.group()
|
@@ -29,7 +30,6 @@ def main(ctx, internal):
|
|
29
30
|
|
30
31
|
ctx.obj["internal"] = internal
|
31
32
|
|
32
|
-
from sima_cli.utils.env import get_environment_type
|
33
33
|
env_type, env_subtype = get_environment_type()
|
34
34
|
|
35
35
|
if internal:
|
@@ -37,10 +37,6 @@ def main(ctx, internal):
|
|
37
37
|
else:
|
38
38
|
click.echo(f"🔧 Environment: {env_type} ({env_subtype})")
|
39
39
|
|
40
|
-
if not internal:
|
41
|
-
click.echo(f"external environment is not supported yet..")
|
42
|
-
exit(0)
|
43
|
-
|
44
40
|
# ----------------------
|
45
41
|
# Authentication Command
|
46
42
|
# ----------------------
|
@@ -92,20 +88,14 @@ def download(ctx, url, dest):
|
|
92
88
|
@main.command(name="update")
|
93
89
|
@click.argument('version_or_url')
|
94
90
|
@click.option('--ip', help="Target device IP address for remote firmware update.")
|
95
|
-
|
96
|
-
'--board',
|
97
|
-
default='davinci',
|
98
|
-
type=click.Choice(['davinci', 'modalix'], case_sensitive=False),
|
99
|
-
show_default=True,
|
100
|
-
help="Target board type (davinci or modalix)."
|
101
|
-
)
|
91
|
+
|
102
92
|
@click.option(
|
103
93
|
'--passwd',
|
104
94
|
default='edgeai',
|
105
95
|
help="Optional SSH password for remote board (default is 'edgeai')."
|
106
96
|
)
|
107
97
|
@click.pass_context
|
108
|
-
def update(ctx, version_or_url, ip,
|
98
|
+
def update(ctx, version_or_url, ip, passwd):
|
109
99
|
"""
|
110
100
|
Run system update across different environments.
|
111
101
|
Downloads and applies firmware updates for PCIe host or SiMa board.
|
@@ -113,7 +103,11 @@ def update(ctx, version_or_url, ip, board, passwd):
|
|
113
103
|
version_or_url: The version string (e.g. '1.5.0') or a direct URL to the firmware package.
|
114
104
|
"""
|
115
105
|
internal = ctx.obj.get("internal", False)
|
116
|
-
|
106
|
+
if not internal:
|
107
|
+
click.echo(f"external environment is not supported yet..")
|
108
|
+
exit(0)
|
109
|
+
|
110
|
+
perform_update(version_or_url, ip, internal, passwd=passwd)
|
117
111
|
|
118
112
|
# ----------------------
|
119
113
|
# Model Zoo Subcommands
|
@@ -125,6 +119,11 @@ def modelzoo(ctx, ver):
|
|
125
119
|
"""Access models from the Model Zoo."""
|
126
120
|
ctx.ensure_object(dict)
|
127
121
|
ctx.obj['ver'] = ver
|
122
|
+
internal = ctx.obj.get("internal", False)
|
123
|
+
if not internal:
|
124
|
+
click.echo(f"external environment is not supported yet..")
|
125
|
+
exit(0)
|
126
|
+
|
128
127
|
pass
|
129
128
|
|
130
129
|
@modelzoo.command("list")
|
@@ -146,6 +145,35 @@ def get_model(ctx, model_name):
|
|
146
145
|
click.echo(f"Getting model '{model_name}' for version: {ver}")
|
147
146
|
download_model(internal, ver, model_name)
|
148
147
|
|
148
|
+
@modelzoo.command("describe")
|
149
|
+
@click.argument('model_name')
|
150
|
+
@click.pass_context
|
151
|
+
def get_model(ctx, model_name):
|
152
|
+
"""Download a specific model."""
|
153
|
+
ver = ctx.obj.get("ver")
|
154
|
+
internal = ctx.obj.get("internal", False)
|
155
|
+
click.echo(f"Getting model '{model_name}' for version: {ver}")
|
156
|
+
describe_model(internal, ver, model_name)
|
157
|
+
|
158
|
+
# ----------------------
|
159
|
+
# Authentication Command
|
160
|
+
# ----------------------
|
161
|
+
@main.group()
|
162
|
+
@click.pass_context
|
163
|
+
def mla(ctx):
|
164
|
+
"""Machine Learning Accelerator Utilities."""
|
165
|
+
env_type, _ = get_environment_type()
|
166
|
+
if env_type != 'board':
|
167
|
+
click.echo("❌ This command can only be executed on the SiMa board.")
|
168
|
+
pass
|
169
|
+
|
170
|
+
@mla.command("meminfo")
|
171
|
+
@click.pass_context
|
172
|
+
def show_mla_memory_usage(ctx):
|
173
|
+
"""Show MLA Memory usage overtime."""
|
174
|
+
monitor_simaai_mem_chart()
|
175
|
+
pass
|
176
|
+
|
149
177
|
# ----------------------
|
150
178
|
# App Zoo Subcommands
|
151
179
|
# ----------------------
|
sima_cli/mla/meminfo.py
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
import time
|
2
|
+
import re
|
3
|
+
import sys
|
4
|
+
import select
|
5
|
+
import subprocess
|
6
|
+
import plotext as plt
|
7
|
+
|
8
|
+
def monitor_simaai_mem_chart(sample_interval_sec=5, max_samples=100):
|
9
|
+
sizes = []
|
10
|
+
|
11
|
+
def read_allocated_size():
|
12
|
+
try:
|
13
|
+
output = subprocess.check_output(['sudo', 'cat', '/dev/simaai-mem'], text=True)
|
14
|
+
match = re.search(r"Total allocated size:\s+0x([0-9a-fA-F]+)", output)
|
15
|
+
if match:
|
16
|
+
size_bytes = int(match.group(1), 16)
|
17
|
+
return size_bytes / (1024 * 1024)
|
18
|
+
except Exception as e:
|
19
|
+
print(f"Error reading /dev/simaai-mem: {e}")
|
20
|
+
return None
|
21
|
+
|
22
|
+
print("📈 Monitoring MLA memory usage... (Press 'Ctrl+C' to quit)")
|
23
|
+
|
24
|
+
while True:
|
25
|
+
# Check for quit key
|
26
|
+
if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
|
27
|
+
key = sys.stdin.read(1)
|
28
|
+
if key.lower() == 'q':
|
29
|
+
print("\n❌ Exiting memory monitor...")
|
30
|
+
break
|
31
|
+
|
32
|
+
size_mb = read_allocated_size()
|
33
|
+
if size_mb is not None:
|
34
|
+
sizes.append(size_mb)
|
35
|
+
sizes = sizes[-max_samples:]
|
36
|
+
|
37
|
+
if sizes:
|
38
|
+
plt.clear_data()
|
39
|
+
plt.clc()
|
40
|
+
plt.title("SIMA MLA Memory Usage (MB)")
|
41
|
+
plt.xlabel("Seconds")
|
42
|
+
plt.ylabel("Memory (MB)")
|
43
|
+
plt.plot(sizes)
|
44
|
+
plt.ylim(min(sizes) * 0.95, max(sizes) * 1.05)
|
45
|
+
plt.show()
|
46
|
+
|
47
|
+
time.sleep(sample_interval_sec)
|
sima_cli/model_zoo/model.py
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
import requests
|
4
4
|
import click
|
5
5
|
import os
|
6
|
+
import yaml
|
6
7
|
from urllib.parse import urlparse
|
7
|
-
|
8
|
+
from rich import print
|
9
|
+
from rich.table import Table
|
10
|
+
from rich.panel import Panel
|
8
11
|
from sima_cli.utils.config import get_auth_token
|
12
|
+
from sima_cli.utils.config_loader import artifactory_url
|
9
13
|
from sima_cli.download import download_file_from_url
|
10
14
|
|
11
|
-
ARTIFACTORY_BASE_URL =
|
15
|
+
ARTIFACTORY_BASE_URL = artifactory_url() + '/artifactory'
|
12
16
|
|
13
17
|
def _is_valid_url(url: str) -> bool:
|
14
18
|
try:
|
@@ -17,6 +21,109 @@ def _is_valid_url(url: str) -> bool:
|
|
17
21
|
except:
|
18
22
|
return False
|
19
23
|
|
24
|
+
def _describe_model_internal(ver: str, model_name: str):
|
25
|
+
repo = "sima-qa-releases"
|
26
|
+
base_path = f"SiMaCLI-SDK-Releases/{ver}-Release/modelzoo_edgematic/{model_name}"
|
27
|
+
aql_query = f"""
|
28
|
+
items.find({{
|
29
|
+
"repo": "{repo}",
|
30
|
+
"path": "{base_path}",
|
31
|
+
"$or": [
|
32
|
+
{{ "name": {{ "$match": "*.yaml" }} }},
|
33
|
+
{{ "name": {{ "$match": "*.yml" }} }}
|
34
|
+
],
|
35
|
+
"type": "file"
|
36
|
+
}}).include("name", "path", "repo")
|
37
|
+
""".strip()
|
38
|
+
|
39
|
+
headers = {
|
40
|
+
"Content-Type": "text/plain",
|
41
|
+
"Authorization": f"Bearer {get_auth_token(internal=True)}"
|
42
|
+
}
|
43
|
+
|
44
|
+
aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
|
45
|
+
response = requests.post(aql_url, data=aql_query, headers=headers)
|
46
|
+
if response.status_code != 200:
|
47
|
+
click.echo(f"❌ Failed to list model files. Status: {response.status_code}")
|
48
|
+
click.echo(response.text)
|
49
|
+
return
|
50
|
+
|
51
|
+
files = response.json().get("results", [])
|
52
|
+
yaml_file = next((f for f in files if f["name"].endswith((".yaml", ".yml"))), None)
|
53
|
+
|
54
|
+
if not yaml_file:
|
55
|
+
click.echo(f"⚠️ No .yaml or .yml file found under: {base_path}")
|
56
|
+
return
|
57
|
+
|
58
|
+
# Download the YAML file
|
59
|
+
yaml_url = f"{ARTIFACTORY_BASE_URL}/{repo}/{yaml_file['path']}/{yaml_file['name']}"
|
60
|
+
response = requests.get(yaml_url, headers={"Authorization": f"Bearer {get_auth_token(internal=True)}"})
|
61
|
+
|
62
|
+
if response.status_code != 200:
|
63
|
+
click.echo(f"❌ Failed to fetch YAML: {response.status_code}")
|
64
|
+
return
|
65
|
+
|
66
|
+
try:
|
67
|
+
data = yaml.safe_load(response.text)
|
68
|
+
except yaml.YAMLError as e:
|
69
|
+
click.echo(f"❌ Failed to parse YAML: {e}")
|
70
|
+
return
|
71
|
+
|
72
|
+
model = data.get("model", {})
|
73
|
+
pipeline = data.get("pipeline", {})
|
74
|
+
|
75
|
+
print(Panel.fit(f"[bold green]{model.get('name', 'Unknown')}[/bold green] - {model.get('task', 'Unknown Task')}",
|
76
|
+
subtitle=f"Status: [yellow]{model.get('status', 'n/a')}[/yellow]"))
|
77
|
+
|
78
|
+
desc_table = Table(title="Description", show_header=False)
|
79
|
+
for k, v in (model.get("description") or {}).items():
|
80
|
+
desc_table.add_row(k.capitalize(), v or "-")
|
81
|
+
print(desc_table)
|
82
|
+
|
83
|
+
dataset = model.get("dataset", {})
|
84
|
+
dataset_table = Table(title="Dataset", header_style="bold magenta")
|
85
|
+
dataset_table.add_column("Key")
|
86
|
+
dataset_table.add_column("Value")
|
87
|
+
dataset_table.add_row("Name", dataset.get("name", "-"))
|
88
|
+
for k, v in (dataset.get("params") or {}).items():
|
89
|
+
dataset_table.add_row(k, str(v))
|
90
|
+
dataset_table.add_row("Accuracy", dataset.get("accuracy", "-"))
|
91
|
+
dataset_table.add_row("Calibration", dataset.get("calibration", "-"))
|
92
|
+
print(dataset_table)
|
93
|
+
|
94
|
+
if qm := model.get("quality_metric"):
|
95
|
+
print(Panel.fit(f"Quality Metric: [cyan]{qm.get('name')}[/cyan]"))
|
96
|
+
|
97
|
+
q = model.get("quantization_settings", {})
|
98
|
+
q_table = Table(title="Quantization Settings", header_style="bold blue")
|
99
|
+
q_table.add_column("Setting")
|
100
|
+
q_table.add_column("Value")
|
101
|
+
|
102
|
+
q_table.add_row("Calibration Samples", str(q.get("calibration_num_samples", "-")))
|
103
|
+
q_table.add_row("Calibration Method", q.get("calibration_method", "-"))
|
104
|
+
q_table.add_row("Requantization Mode", q.get("requantization_mode", "-"))
|
105
|
+
q_table.add_row("Bias Correction", str(q.get("bias_correction", "-")))
|
106
|
+
|
107
|
+
aq = q.get("activation_quantization_scheme", {})
|
108
|
+
wq = q.get("weight_quantization_scheme", {})
|
109
|
+
|
110
|
+
q_table.add_row("Activation Quant", f"Asym: {aq.get('asymmetric')}, PerCh: {aq.get('per_channel')}, Bits: {aq.get('bits')}")
|
111
|
+
q_table.add_row("Weight Quant", f"Asym: {wq.get('asymmetric')}, PerCh: {wq.get('per_channel')}, Bits: {wq.get('bits')}")
|
112
|
+
print(q_table)
|
113
|
+
|
114
|
+
transforms = pipeline.get("transforms", [])
|
115
|
+
t_table = Table(title="Pipeline Transforms", header_style="bold green")
|
116
|
+
t_table.add_column("Name")
|
117
|
+
t_table.add_column("Params")
|
118
|
+
|
119
|
+
for step in transforms:
|
120
|
+
name = step.get("name")
|
121
|
+
params = step.get("params", {})
|
122
|
+
param_str = ", ".join(f"{k}={v}" for k, v in params.items()) if params else "-"
|
123
|
+
t_table.add_row(name, param_str)
|
124
|
+
|
125
|
+
print(t_table)
|
126
|
+
|
20
127
|
def _download_model_internal(ver: str, model_name: str):
|
21
128
|
repo = "sima-qa-releases"
|
22
129
|
base_path = f"SiMaCLI-SDK-Releases/{ver}-Release/modelzoo_edgematic/{model_name}"
|
@@ -92,6 +199,7 @@ def _list_available_models_internal(version: str):
|
|
92
199
|
""".strip()
|
93
200
|
|
94
201
|
aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
|
202
|
+
print(aql_url)
|
95
203
|
headers = {
|
96
204
|
"Content-Type": "text/plain",
|
97
205
|
"Authorization": f"Bearer {get_auth_token(internal=True)}"
|
@@ -138,6 +246,13 @@ def download_model(internal, ver, model_name):
|
|
138
246
|
else:
|
139
247
|
print('External model zoo not supported yet')
|
140
248
|
|
249
|
+
def describe_model(internal, ver, model_name):
|
250
|
+
if internal:
|
251
|
+
click.echo("Model Zoo Source : SiMa Artifactory...")
|
252
|
+
return _describe_model_internal(ver, model_name)
|
253
|
+
else:
|
254
|
+
print('External model zoo not supported yet')
|
255
|
+
|
141
256
|
# Module CLI tests
|
142
257
|
if __name__ == "__main__":
|
143
258
|
import sys
|
sima_cli/update/query.py
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
import requests
|
2
|
+
from sima_cli.utils.config_loader import load_resource_config, artifactory_url
|
3
|
+
from sima_cli.utils.config import get_auth_token
|
4
|
+
|
5
|
+
ARTIFACTORY_BASE_URL = artifactory_url() + '/artifactory'
|
6
|
+
|
7
|
+
def _list_available_firmware_versions_internal(board: str, match_keyword: str = None):
|
8
|
+
fw_path = f"{board}"
|
9
|
+
aql_query = f"""
|
10
|
+
items.find({{
|
11
|
+
"repo": "soc-images",
|
12
|
+
"path": {{
|
13
|
+
"$match": "{fw_path}/*"
|
14
|
+
}},
|
15
|
+
"type": "folder"
|
16
|
+
}}).include("repo", "path", "name")
|
17
|
+
""".strip()
|
18
|
+
|
19
|
+
aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
|
20
|
+
headers = {
|
21
|
+
"Content-Type": "text/plain",
|
22
|
+
"Authorization": f"Bearer {get_auth_token(internal=True)}"
|
23
|
+
}
|
24
|
+
|
25
|
+
response = requests.post(aql_url, data=aql_query, headers=headers)
|
26
|
+
if response.status_code != 200:
|
27
|
+
return None
|
28
|
+
|
29
|
+
results = response.json().get("results", [])
|
30
|
+
|
31
|
+
# Reconstruct full paths and remove board prefix
|
32
|
+
full_paths = {
|
33
|
+
f"{item['path']}/{item['name']}".replace(fw_path + "/", "")
|
34
|
+
for item in results
|
35
|
+
}
|
36
|
+
|
37
|
+
# Extract top-level folders
|
38
|
+
top_level_folders = sorted({path.split("/")[0] for path in full_paths})
|
39
|
+
|
40
|
+
if match_keyword:
|
41
|
+
match_keyword = match_keyword.lower()
|
42
|
+
top_level_folders = [
|
43
|
+
f for f in top_level_folders if match_keyword in f.lower()
|
44
|
+
]
|
45
|
+
|
46
|
+
return top_level_folders
|
47
|
+
|
48
|
+
|
49
|
+
def list_available_firmware_versions(board: str, match_keyword: str = None, internal: bool = False):
|
50
|
+
"""
|
51
|
+
Public interface to list available firmware versions.
|
52
|
+
|
53
|
+
Parameters:
|
54
|
+
- board: str – Name of the board (e.g. 'davinci')
|
55
|
+
- match_keyword: str – Optional keyword to filter versions (case-insensitive)
|
56
|
+
- internal: bool – Must be True to access internal Artifactory
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
- List[str] of firmware version folder names, or None if access is not allowed
|
60
|
+
"""
|
61
|
+
if not internal:
|
62
|
+
raise PermissionError("Internal access required to list firmware versions.")
|
63
|
+
|
64
|
+
return _list_available_firmware_versions_internal(board, match_keyword)
|
sima_cli/update/updater.py
CHANGED
@@ -5,6 +5,7 @@ import time
|
|
5
5
|
import tempfile
|
6
6
|
import tarfile
|
7
7
|
import subprocess
|
8
|
+
from InquirerPy import inquirer
|
8
9
|
from urllib.parse import urlparse
|
9
10
|
from typing import List
|
10
11
|
from sima_cli.utils.env import get_environment_type
|
@@ -12,6 +13,7 @@ from sima_cli.download import download_file_from_url
|
|
12
13
|
from sima_cli.utils.config_loader import load_resource_config
|
13
14
|
from sima_cli.update.remote import push_and_update_remote_board, get_remote_board_info, reboot_remote_board
|
14
15
|
from sima_cli.update.local import get_local_board_info, push_and_update_local_board
|
16
|
+
from sima_cli.update.query import list_available_firmware_versions
|
15
17
|
|
16
18
|
def _resolve_firmware_url(version_or_url: str, board: str, internal: bool = False) -> str:
|
17
19
|
"""
|
@@ -44,6 +46,42 @@ def _resolve_firmware_url(version_or_url: str, board: str, internal: bool = Fals
|
|
44
46
|
download_url = url.rstrip("/") + f"/soc-images/{board}/{version_or_url}/artifacts/release.tar.gz"
|
45
47
|
return download_url
|
46
48
|
|
49
|
+
def _pick_from_available_versions(board: str, version_or_url: str, internal: bool) -> str:
|
50
|
+
"""
|
51
|
+
Presents an interactive menu (with search) for selecting a firmware version.
|
52
|
+
"""
|
53
|
+
|
54
|
+
if "http" in version_or_url:
|
55
|
+
return version_or_url
|
56
|
+
|
57
|
+
available_versions = list_available_firmware_versions(board, version_or_url, internal=True)
|
58
|
+
|
59
|
+
if len(available_versions) > 1:
|
60
|
+
click.echo("Multiple firmware versions found matching your input:")
|
61
|
+
|
62
|
+
selected_version = inquirer.fuzzy(
|
63
|
+
message="Select a version:",
|
64
|
+
choices=available_versions,
|
65
|
+
max_height="70%", # scrollable
|
66
|
+
instruction="(Use ↑↓ to navigate, / to search, Enter to select)"
|
67
|
+
).execute()
|
68
|
+
|
69
|
+
if not selected_version:
|
70
|
+
click.echo("No selection made. Exiting.", err=True)
|
71
|
+
raise SystemExit(1)
|
72
|
+
|
73
|
+
return selected_version
|
74
|
+
|
75
|
+
elif len(available_versions) == 1:
|
76
|
+
return available_versions[0]
|
77
|
+
|
78
|
+
else:
|
79
|
+
click.echo(
|
80
|
+
f"No firmware versions found matching keyword '{version_or_url}' for board '{board}'.",
|
81
|
+
err=True
|
82
|
+
)
|
83
|
+
raise SystemExit(1)
|
84
|
+
|
47
85
|
def _sanitize_url_to_filename(url: str) -> str:
|
48
86
|
"""
|
49
87
|
Convert a URL to a safe filename by replacing slashes and removing protocol.
|
@@ -289,7 +327,7 @@ def _update_remote(extracted_paths: List[str], ip: str, board: str, passwd: str,
|
|
289
327
|
return script_path
|
290
328
|
|
291
329
|
|
292
|
-
def perform_update(version_or_url: str, ip: str = None,
|
330
|
+
def perform_update(version_or_url: str, ip: str = None, internal: bool = False, passwd: str = "edgeai"):
|
293
331
|
r"""
|
294
332
|
Update the system based on environment and input.
|
295
333
|
|
@@ -304,36 +342,45 @@ def perform_update(version_or_url: str, ip: str = None, board: str = "davinci",
|
|
304
342
|
board (str): Board type, must be 'davinci' or 'modalix'.
|
305
343
|
passwd : non-default password in case user has changed the password of the board user `sima`
|
306
344
|
"""
|
307
|
-
board = board.lower()
|
308
|
-
if board not in ("davinci", "modalix"):
|
309
|
-
click.echo(f"❌ Invalid board type '{board}'. Must be 'davinci' or 'modalix'.")
|
310
|
-
return
|
311
|
-
|
312
345
|
try:
|
346
|
+
board = ''
|
313
347
|
env_type, env_subtype = get_environment_type()
|
314
348
|
click.echo(f"🔄 Running update for environment: {env_type} ({env_subtype})")
|
315
349
|
click.echo(f"🔧 Requested version or URL: {version_or_url}")
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
350
|
+
|
351
|
+
if env_type == 'board':
|
352
|
+
board, version = get_local_board_info()
|
353
|
+
else:
|
354
|
+
board, version = get_remote_board_info(ip, passwd)
|
355
|
+
|
356
|
+
if board in ['davinci', 'modalix']:
|
357
|
+
click.echo(f"🔧 Target board: {board}, board currently running: {version}")
|
358
|
+
|
359
|
+
if 'http' not in version_or_url:
|
360
|
+
version_or_url = _pick_from_available_versions(board, version_or_url, internal)
|
361
|
+
|
362
|
+
extracted_paths = _download_image(version_or_url, board, internal)
|
363
|
+
click.echo("⚠️ DO NOT INTERRUPT THE UPDATE PROCESS...")
|
364
|
+
|
365
|
+
if len(extracted_paths) > 0:
|
366
|
+
if env_type == "host" and env_subtype == 'linux':
|
367
|
+
# Always update the remote device first then update the host driver, otherwise the host would
|
368
|
+
# not be able to connect to the board
|
369
|
+
click.echo("👉 Updating PCIe host driver and downloading firmware...")
|
370
|
+
script_path = _update_remote(extracted_paths, ip, board, passwd, reboot_and_wait = False)
|
371
|
+
_update_host(script_path, board, ip, passwd)
|
372
|
+
elif env_type == "board":
|
373
|
+
_update_board(extracted_paths, board, passwd)
|
374
|
+
elif env_type == "sdk":
|
375
|
+
click.echo("👉 Updating firmware from within the Palette SDK...: Not implemented yet")
|
376
|
+
elif ip:
|
377
|
+
click.echo(f"👉 Updating firmware on remote board at {ip}...")
|
378
|
+
_update_remote(extracted_paths, ip, board, passwd, reboot_and_wait = True)
|
379
|
+
else:
|
380
|
+
click.echo("❌ Unknown environment. Use --ip to specify target device.")
|
381
|
+
else:
|
382
|
+
click.echo("❌ Unable to retrieve target board information")
|
383
|
+
|
337
384
|
except Exception as e:
|
338
385
|
click.echo(f"❌ Update failed {e}")
|
339
386
|
|
sima_cli/utils/config_loader.py
CHANGED
@@ -13,7 +13,6 @@ def load_resource_config():
|
|
13
13
|
"internal": {}
|
14
14
|
}
|
15
15
|
|
16
|
-
# Public config (bundled with the package)
|
17
16
|
public_path = os.path.abspath(
|
18
17
|
os.path.join(os.path.dirname(__file__), "..", "data", "resources_public.yaml")
|
19
18
|
)
|
@@ -21,11 +20,14 @@ def load_resource_config():
|
|
21
20
|
with open(public_path, "r") as f:
|
22
21
|
config["public"] = yaml.safe_load(f) or {}
|
23
22
|
|
24
|
-
|
25
|
-
|
23
|
+
internal_path = os.path.abspath(
|
24
|
+
os.path.join(os.path.dirname(__file__), "..", "data", "resources_internal.yaml")
|
25
|
+
)
|
26
26
|
if os.path.exists(internal_path):
|
27
27
|
with open(internal_path, "r") as f:
|
28
28
|
config["internal"] = yaml.safe_load(f) or {}
|
29
|
+
else:
|
30
|
+
print(f"Internal resource map not found... {internal_path}")
|
29
31
|
|
30
32
|
return config
|
31
33
|
|
@@ -36,5 +38,28 @@ def internal_resource_exists():
|
|
36
38
|
Returns:
|
37
39
|
bool: True if the internal resource file exists, False otherwise.
|
38
40
|
"""
|
39
|
-
internal_path = os.path.
|
40
|
-
|
41
|
+
internal_path = os.path.abspath(
|
42
|
+
os.path.join(os.path.dirname(__file__), "..", "data", "resources_internal.yaml")
|
43
|
+
)
|
44
|
+
return os.path.exists(internal_path)
|
45
|
+
|
46
|
+
def artifactory_url():
|
47
|
+
"""
|
48
|
+
Retrieve the Artifactory base URL from the internal resource configuration.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
str or None: The Artifactory URL if found, otherwise None.
|
52
|
+
|
53
|
+
Notes:
|
54
|
+
- Expects 'load_resource_config()' to return a dictionary with an 'internal' section
|
55
|
+
containing an 'artifactory' section with a 'url' field.
|
56
|
+
- If any error occurs (e.g., missing fields, file issues), prints an error message
|
57
|
+
and returns None.
|
58
|
+
"""
|
59
|
+
try:
|
60
|
+
cfg = load_resource_config()
|
61
|
+
return cfg.get("internal", {}).get("artifactory", {}).get("url", {})
|
62
|
+
except Exception as e:
|
63
|
+
print('Unable to retrieve Artifactory URL')
|
64
|
+
return None
|
65
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: sima-cli
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.15
|
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
|
@@ -21,6 +21,8 @@ Requires-Dist: click
|
|
21
21
|
Requires-Dist: tqdm
|
22
22
|
Requires-Dist: pyyaml
|
23
23
|
Requires-Dist: paramiko
|
24
|
+
Requires-Dist: plotext
|
25
|
+
Requires-Dist: rich
|
24
26
|
Dynamic: author
|
25
27
|
Dynamic: license-file
|
26
28
|
Dynamic: requires-python
|
@@ -1,26 +1,31 @@
|
|
1
1
|
sima_cli/__init__.py,sha256=Nb2jSg9-CX1XvSc1c21U9qQ3atINxphuNkNfmR-9P3o,332
|
2
2
|
sima_cli/__main__.py,sha256=ehzD6AZ7zGytC2gLSvaJatxeD0jJdaEvNJvwYeGsWOg,69
|
3
|
-
sima_cli/__version__.py,sha256=
|
4
|
-
sima_cli/cli.py,sha256=
|
3
|
+
sima_cli/__version__.py,sha256=rZ8VyuhqgF22Ho9wHPc5IESWFORxhypVEyKqL_-8IvU,49
|
4
|
+
sima_cli/cli.py,sha256=yd4ClOInhKAFA2ffMX1lUPRvWVziMZLHToSTAr_eyAA,6894
|
5
5
|
sima_cli/app_zoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
sima_cli/app_zoo/app.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
sima_cli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
sima_cli/auth/
|
8
|
+
sima_cli/auth/basic_auth.py,sha256=xS_EC0iYyozDSrZEoz44lU5dLBk6covxiCv09WiQECw,4264
|
9
|
+
sima_cli/auth/login.py,sha256=Tpe5ZRau4QrlQ6Zxg1rBco4qZ2nr4rkfZAohiTPBbVk,3687
|
10
|
+
sima_cli/data/resources_internal.yaml,sha256=zlQD4cSnZK86bLtTWuvEudZTARKiuIKmB--Jv4ajL8o,200
|
11
|
+
sima_cli/data/resources_public.yaml,sha256=ZT1CzPrGMfSLVWiRJfW2-jb-ilmh7yNlkqyO5Fvqk58,178
|
9
12
|
sima_cli/download/__init__.py,sha256=6y4O2FOCYFR2jdnQoVi3hRtEoZ0Gw6rydlTy1SGJ5FE,218
|
10
13
|
sima_cli/download/downloader.py,sha256=zL8daM7Fqj1evzfQ9EHL1CVbuYL0_aQNhromqm7LkE0,4863
|
14
|
+
sima_cli/mla/meminfo.py,sha256=ndc8kQJmWGEIdvNh6iIhATGdrkqM2pbddr_eHxaPNfg,1466
|
11
15
|
sima_cli/model_zoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
-
sima_cli/model_zoo/model.py,sha256=
|
16
|
+
sima_cli/model_zoo/model.py,sha256=q91Nrg62j1TqwPO8HiX4nlEFCCmzNEFcyFTBVMbJm8w,9836
|
13
17
|
sima_cli/update/__init__.py,sha256=0P-z-rSaev40IhfJXytK3AFWv2_sdQU4Ry6ei2sEus0,66
|
14
18
|
sima_cli/update/local.py,sha256=jiGrwuU2Z1HV_RT1_dYuI_Ish-f818AvCEk7sAM3l94,3032
|
19
|
+
sima_cli/update/query.py,sha256=eOTC2ZAWbFFf_0h8D-MO1HrIsQYRc7fu5OyeFNEAv48,2168
|
15
20
|
sima_cli/update/remote.py,sha256=ePlnvlGHrASMMjYGM9w-6hMeDMgGiJu_BlHLamU1YtI,8420
|
16
|
-
sima_cli/update/updater.py,sha256=
|
21
|
+
sima_cli/update/updater.py,sha256=GtYOyhZj_Xk99iI-Zj4hGywAnitkah2MuMoE9NEa9V0,15228
|
17
22
|
sima_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
23
|
sima_cli/utils/artifactory.py,sha256=6YyVpzVm8ATy7NEwT9nkWx-wptkXrvG7Wl_zDT6jmLs,2390
|
19
24
|
sima_cli/utils/config.py,sha256=wE-cPQqY_gOqaP8t01xsRHD9tBUGk9MgBUm2GYYxI3E,1616
|
20
|
-
sima_cli/utils/config_loader.py,sha256=
|
25
|
+
sima_cli/utils/config_loader.py,sha256=7I5we1yiCai18j9R9jvhfUzAmT3OjAqVK35XSLuUw8c,2005
|
21
26
|
sima_cli/utils/env.py,sha256=LJy2eO8cfEYsLuC7p3BT_FAoaZc9emtq6NYhHRBpiBE,5512
|
22
27
|
sima_cli/utils/network.py,sha256=UvqxbqbWUczGFyO-t1SybG7Q-x9kjUVRNIn_D6APzy8,1252
|
23
|
-
sima_cli-0.0.
|
28
|
+
sima_cli-0.0.15.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
|
24
29
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
30
|
tests/test_app_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
31
|
tests/test_auth.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -29,8 +34,8 @@ tests/test_download.py,sha256=t87DwxlHs26_ws9rpcHGwr_OrcRPd3hz6Zmm0vRee2U,4465
|
|
29
34
|
tests/test_firmware.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
35
|
tests/test_model_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
36
|
tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
-
sima_cli-0.0.
|
33
|
-
sima_cli-0.0.
|
34
|
-
sima_cli-0.0.
|
35
|
-
sima_cli-0.0.
|
36
|
-
sima_cli-0.0.
|
37
|
+
sima_cli-0.0.15.dist-info/METADATA,sha256=Gfm1zm3xrURxtFf9DMeq5d5vjkko8Mnk8sWVpvW4EU8,3605
|
38
|
+
sima_cli-0.0.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
39
|
+
sima_cli-0.0.15.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
|
40
|
+
sima_cli-0.0.15.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
|
41
|
+
sima_cli-0.0.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|