sima-cli 0.0.14__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 CHANGED
@@ -1,2 +1,2 @@
1
1
  # sima_cli/__version__.py
2
- __version__ = "0.0.14"
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
@@ -4,6 +4,7 @@ import requests
4
4
  from sima_cli.utils.config import set_auth_token, get_auth_token
5
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
  """
@@ -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 login_external():
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("external_url")
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.")
@@ -0,0 +1,9 @@
1
+ artifactory:
2
+ url: "https://developer.sima.ai"
3
+
4
+ auth:
5
+ auth_url: "/login"
6
+ validate_url: "artifactory/api/security/encryptedPassword"
7
+
8
+ download:
9
+ download_url: "artifactory/"
@@ -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)
@@ -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.
@@ -317,6 +355,10 @@ def perform_update(version_or_url: str, ip: str = None, internal: bool = False,
317
355
 
318
356
  if board in ['davinci', 'modalix']:
319
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
+
320
362
  extracted_paths = _download_image(version_or_url, board, internal)
321
363
  click.echo("⚠️ DO NOT INTERRUPT THE UPDATE PROCESS...")
322
364
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sima-cli
3
- Version: 0.0.14
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
@@ -1,13 +1,14 @@
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=TP5y2P3zgHu81qy4udz5Y12lPztKWvIhvZuO1-ajKQw,49
3
+ sima_cli/__version__.py,sha256=rZ8VyuhqgF22Ho9wHPc5IESWFORxhypVEyKqL_-8IvU,49
4
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/login.py,sha256=-0ZUJR3uvrgKACjuSYapTXUN5yfkKz436YYGlAydPaI,3633
8
+ sima_cli/auth/basic_auth.py,sha256=xS_EC0iYyozDSrZEoz44lU5dLBk6covxiCv09WiQECw,4264
9
+ sima_cli/auth/login.py,sha256=Tpe5ZRau4QrlQ6Zxg1rBco4qZ2nr4rkfZAohiTPBbVk,3687
9
10
  sima_cli/data/resources_internal.yaml,sha256=zlQD4cSnZK86bLtTWuvEudZTARKiuIKmB--Jv4ajL8o,200
10
- sima_cli/data/resources_public.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ sima_cli/data/resources_public.yaml,sha256=ZT1CzPrGMfSLVWiRJfW2-jb-ilmh7yNlkqyO5Fvqk58,178
11
12
  sima_cli/download/__init__.py,sha256=6y4O2FOCYFR2jdnQoVi3hRtEoZ0Gw6rydlTy1SGJ5FE,218
12
13
  sima_cli/download/downloader.py,sha256=zL8daM7Fqj1evzfQ9EHL1CVbuYL0_aQNhromqm7LkE0,4863
13
14
  sima_cli/mla/meminfo.py,sha256=ndc8kQJmWGEIdvNh6iIhATGdrkqM2pbddr_eHxaPNfg,1466
@@ -15,15 +16,16 @@ sima_cli/model_zoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
15
16
  sima_cli/model_zoo/model.py,sha256=q91Nrg62j1TqwPO8HiX4nlEFCCmzNEFcyFTBVMbJm8w,9836
16
17
  sima_cli/update/__init__.py,sha256=0P-z-rSaev40IhfJXytK3AFWv2_sdQU4Ry6ei2sEus0,66
17
18
  sima_cli/update/local.py,sha256=jiGrwuU2Z1HV_RT1_dYuI_Ish-f818AvCEk7sAM3l94,3032
19
+ sima_cli/update/query.py,sha256=eOTC2ZAWbFFf_0h8D-MO1HrIsQYRc7fu5OyeFNEAv48,2168
18
20
  sima_cli/update/remote.py,sha256=ePlnvlGHrASMMjYGM9w-6hMeDMgGiJu_BlHLamU1YtI,8420
19
- sima_cli/update/updater.py,sha256=sPJKsIZKKSx_MqGFrpUH11Hh0hULzVj32zIfYwoUWjc,13799
21
+ sima_cli/update/updater.py,sha256=GtYOyhZj_Xk99iI-Zj4hGywAnitkah2MuMoE9NEa9V0,15228
20
22
  sima_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
23
  sima_cli/utils/artifactory.py,sha256=6YyVpzVm8ATy7NEwT9nkWx-wptkXrvG7Wl_zDT6jmLs,2390
22
24
  sima_cli/utils/config.py,sha256=wE-cPQqY_gOqaP8t01xsRHD9tBUGk9MgBUm2GYYxI3E,1616
23
25
  sima_cli/utils/config_loader.py,sha256=7I5we1yiCai18j9R9jvhfUzAmT3OjAqVK35XSLuUw8c,2005
24
26
  sima_cli/utils/env.py,sha256=LJy2eO8cfEYsLuC7p3BT_FAoaZc9emtq6NYhHRBpiBE,5512
25
27
  sima_cli/utils/network.py,sha256=UvqxbqbWUczGFyO-t1SybG7Q-x9kjUVRNIn_D6APzy8,1252
26
- sima_cli-0.0.14.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
28
+ sima_cli-0.0.15.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
27
29
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
30
  tests/test_app_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
31
  tests/test_auth.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -32,8 +34,8 @@ tests/test_download.py,sha256=t87DwxlHs26_ws9rpcHGwr_OrcRPd3hz6Zmm0vRee2U,4465
32
34
  tests/test_firmware.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
35
  tests/test_model_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
36
  tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- sima_cli-0.0.14.dist-info/METADATA,sha256=j3Nh9wE5H9b2fu0A6x35qjya6Ck-LVidx9_zOxSsaUI,3605
36
- sima_cli-0.0.14.dist-info/WHEEL,sha256=ooBFpIzZCPdw3uqIQsOo4qqbA4ZRPxHnOH7peeONza0,91
37
- sima_cli-0.0.14.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
38
- sima_cli-0.0.14.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
39
- sima_cli-0.0.14.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5