sima-cli 0.0.35__py3-none-any.whl → 0.0.36__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.35"
2
+ __version__ = "0.0.36"
@@ -121,7 +121,6 @@ def login_external():
121
121
  session.headers["X-CSRF-Token"] = csrf_token
122
122
 
123
123
  if _is_session_valid(session):
124
- click.echo("🚀 You are already logged in.")
125
124
  return session
126
125
 
127
126
  # Fresh login prompt
sima_cli/cli.py CHANGED
@@ -313,9 +313,7 @@ def install_cmd(ctx, component, version, mirror, tag):
313
313
  if component:
314
314
  click.echo(f"⚠️ Component '{component}' is ignored when using --metadata. Proceeding with metadata-based installation.")
315
315
  click.echo(f"🔧 Installing generic component from metadata URL: {mirror}")
316
- if install_from_metadata(metadata_url=mirror, internal=internal):
317
- click.echo("✅ Installation complete.")
318
- return
316
+ return install_from_metadata(metadata_url=mirror, internal=internal)
319
317
 
320
318
  # No component and no metadata: error
321
319
  if not component:
@@ -324,6 +322,10 @@ def install_cmd(ctx, component, version, mirror, tag):
324
322
 
325
323
  component = component.lower()
326
324
 
325
+ # if user specified gh: as component, treat it the same as -m
326
+ if component.startswith("gh:"):
327
+ return install_from_metadata(metadata_url=component, internal=False)
328
+
327
329
  # Validate version requirement
328
330
  if component in SDK_DEPENDENT_COMPONENTS and not version:
329
331
  click.echo(f"❌ The component '{component}' requires a specific SDK version. Please provide one using -v.")
@@ -135,6 +135,38 @@ def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = Fa
135
135
 
136
136
  return dest_path
137
137
 
138
+ def check_url_available(url: str, internal: bool = False) -> bool:
139
+ """
140
+ Perform a HEAD request to check if a resource is available.
141
+
142
+ Args:
143
+ url (str): The full URL to check.
144
+ internal (bool): Whether this is an internal resource on Artifactory.
145
+
146
+ Returns:
147
+ bool: True if the resource is available (status 200–399), False otherwise.
148
+ """
149
+ headers = {}
150
+ try:
151
+ if internal:
152
+ auth_token = get_auth_token(internal=True)
153
+ if auth_token:
154
+ headers["Authorization"] = f"Bearer {auth_token}"
155
+ head_fn = requests.head
156
+ elif 'https://docs.sima.ai' in url:
157
+ session = login_external()
158
+ head_fn = session.head
159
+ else:
160
+ session = requests.Session()
161
+ head_fn = session.head
162
+
163
+ resp = head_fn(url, headers=headers, timeout=10, allow_redirects=True)
164
+ # Consider any 2xx or 3xx as "available"
165
+ return 200 <= resp.status_code < 400
166
+
167
+ except Exception as e:
168
+ print(f"⚠️ HEAD check failed for {url}: {e}")
169
+ return False
138
170
 
139
171
  def download_folder_from_url(url: str, dest_folder: str = ".", internal: bool = False) -> List[str]:
140
172
  """
@@ -15,6 +15,7 @@ from tqdm import tqdm
15
15
  from urllib.parse import urljoin
16
16
  from pathlib import Path
17
17
  import subprocess
18
+ import requests
18
19
 
19
20
  from rich.console import Console
20
21
  from rich.panel import Panel
@@ -29,6 +30,153 @@ from sima_cli.install.metadata_info import print_metadata_summary, parse_size_st
29
30
 
30
31
  console = Console()
31
32
 
33
+ def _copy_dir(src: Path, dest: Path, label: str):
34
+ """
35
+ Copy files from src → dest, merging with existing files (no deletion).
36
+ Ensures that all parent directories for dest are created.
37
+ """
38
+ if not src.exists():
39
+ raise FileNotFoundError(f"SDK {label} not found: {src}")
40
+
41
+ # Ensure full path exists
42
+ dest.mkdir(parents=True, exist_ok=True)
43
+
44
+ # Copy tree correctly
45
+ for item in src.iterdir():
46
+ target = dest / item.name
47
+ if item.is_dir():
48
+ shutil.copytree(item, target, dirs_exist_ok=True)
49
+ else:
50
+ shutil.copy2(item, target)
51
+
52
+ click.echo(f"✅ Copied {label} into {dest}")
53
+
54
+ def _prepare_pipeline_project(repo_dir: Path):
55
+ """
56
+ Prepare a pipeline project by copying required SDK sources into the repo.
57
+
58
+ Steps:
59
+ 1. Copy core sources into the project folder
60
+ 2. Parse .project/pluginsInfo
61
+ 3. Copy required plugin sources from the SDK plugin zoo
62
+ """
63
+ plugins_info_file = repo_dir / ".project" / "pluginsInfo.json"
64
+ if not plugins_info_file.exists():
65
+ return
66
+
67
+ click.echo("📦 Preparing pipeline project...")
68
+
69
+ try:
70
+ data = json.loads(plugins_info_file.read_text())
71
+ plugins = data.get("pluginsInfo", [])
72
+ except Exception as e:
73
+ raise RuntimeError(f"Failed to read {plugins_info_file}: {e}")
74
+
75
+ # Step a: copy core
76
+ # Define what to copy
77
+ copy_map = [
78
+ (
79
+ Path("/usr/local/simaai/plugin_zoo/gst-simaai-plugins-base/core"),
80
+ repo_dir / "core",
81
+ "core"
82
+ ),
83
+ (
84
+ Path("/usr/local/simaai/utils/gst_app"),
85
+ repo_dir / "dependencies" / "gst_app",
86
+ "dependencies/gst_app"
87
+ ),
88
+ (
89
+ Path("/usr/local/simaai/plugin_zoo/gst-simaai-plugins-base/gst/templates"),
90
+ repo_dir / "plugins" / "templates",
91
+ "plugins/templates"
92
+ ),
93
+ ]
94
+
95
+ # Execute
96
+ for src, dest, label in copy_map:
97
+ _copy_dir(src, dest, label)
98
+
99
+ # Step b/c: scan plugin paths and copy SDK plugins
100
+ sdk_plugins_base = Path("/usr/local/simaai/plugin_zoo/gst-simaai-plugins-base/gst")
101
+ dest_plugins_dir = repo_dir / "plugins"
102
+ dest_plugins_dir.mkdir(exist_ok=True)
103
+
104
+ for plugin in plugins:
105
+ try:
106
+ path = plugin.get("path", "")
107
+ if not path:
108
+ continue
109
+ parts = path.split("/")
110
+ if len(parts) < 2:
111
+ continue
112
+
113
+ plugin_name = parts[1]
114
+ sdk_plugin_path = sdk_plugins_base / plugin_name
115
+ if not sdk_plugin_path.exists():
116
+ click.echo(
117
+ f"⚠️ Missing plugin source: {plugin_name} in the SDK, skipping. "
118
+ "It's possible that this is a custom plugin already exists in the repo"
119
+ )
120
+ continue
121
+
122
+ dest_plugin_path = dest_plugins_dir / plugin_name
123
+ dest_plugin_path.mkdir(parents=True, exist_ok=True)
124
+
125
+ # Merge instead of deleting
126
+ shutil.copytree(sdk_plugin_path, dest_plugin_path, dirs_exist_ok=True)
127
+
128
+ click.echo(f"✅ Copied plugin {plugin_name} into {dest_plugin_path}")
129
+
130
+ except Exception as e:
131
+ click.echo(f"❌ Error copying plugin {plugin}: {e}")
132
+
133
+ click.echo("🎉 Pipeline project prepared.")
134
+
135
+ def _download_github_repo(owner: str, repo: str, ref: str, dest_folder: str, token: str = None) -> str:
136
+ """
137
+ Download and extract a GitHub repo tarball via the REST API (no git required).
138
+
139
+ Args:
140
+ owner (str): GitHub org/user
141
+ repo (str): Repo name
142
+ ref (str): Branch, tag, or commit (default = default branch)
143
+ dest_folder (str): Where to extract
144
+ token (str): Optional GitHub token for private repos
145
+
146
+ Returns:
147
+ str: Path to the extracted repo
148
+ """
149
+ url = f"https://api.github.com/repos/{owner}/{repo}/tarball/{ref}" if ref else f"https://api.github.com/repos/{owner}/{repo}/tarball"
150
+ headers = {}
151
+ if token:
152
+ headers["Authorization"] = f"Bearer {token}"
153
+
154
+ click.echo(f"🐙 Downloading GitHub repo: {owner}/{repo}" + (f"@{ref}" if ref else ""))
155
+
156
+ with requests.get(url, headers=headers, stream=True) as r:
157
+ if r.status_code in (401, 403):
158
+ raise PermissionError("Authentication required for GitHub repo")
159
+ r.raise_for_status()
160
+
161
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".tar.gz") as tmp_file:
162
+ for chunk in r.iter_content(chunk_size=8192):
163
+ tmp_file.write(chunk)
164
+ tmp_path = Path(tmp_file.name)
165
+
166
+ repo_dir = Path(dest_folder) / repo
167
+ repo_dir.mkdir(parents=True, exist_ok=True)
168
+
169
+ _extract_tar_strip_top_level(tmp_path, repo_dir)
170
+ tmp_path.unlink(missing_ok=True)
171
+ click.echo(f"✅ Downloaded GitHub repo to folder: {repo_dir}")
172
+
173
+ try:
174
+ _prepare_pipeline_project(repo_dir)
175
+ except Exception as e:
176
+ click.echo(f"⚠️ Pipeline preparation skipped: {e}")
177
+
178
+ return str(repo_dir)
179
+
32
180
  def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal: bool = False, skip_models: bool = False) -> list:
33
181
  """
34
182
  Downloads resources defined in metadata to a local destination folder.
@@ -84,11 +232,34 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
84
232
  model_path = snapshot_download(
85
233
  repo_id=repo_id,
86
234
  local_dir=target_dir,
87
- revision=revision # None if not specified
235
+ local_dir_use_symlinks=False
88
236
  )
89
237
  local_paths.append(model_path)
90
238
  continue
91
239
 
240
+ if resource.startswith("gh:"):
241
+ resource_spec = resource[3:]
242
+ if "@" in resource_spec:
243
+ repo_id, ref = resource_spec.split("@", 1)
244
+ else:
245
+ repo_id, ref = resource_spec, None
246
+
247
+ if "/" not in repo_id:
248
+ raise click.ClickException(f"❌ Invalid GitHub repo spec: {resource}")
249
+
250
+ owner, name = repo_id.split("/", 1)
251
+
252
+ try:
253
+ token = os.getenv("GITHUB_TOKEN", None)
254
+ repo_path = _download_github_repo(owner, name, ref, dest_folder, token)
255
+ except Exception as e:
256
+ raise click.ClickException(
257
+ f"❌ Failed to download GitHub repo {owner}/{name}@{ref or 'default'}: {e}"
258
+ )
259
+
260
+ local_paths.append(repo_path)
261
+ continue
262
+
92
263
  # Handle normal relative or absolute URLs
93
264
  resource_url = urljoin(base_url, resource)
94
265
  local_path = download_file_from_url(
@@ -296,6 +467,25 @@ def _extract_zip_streaming(zip_path: Path, extract_dir: Path, overwrite: bool =
296
467
 
297
468
  print(f"✅ Extracted {len(members)} entries to {extract_dir}/")
298
469
 
470
+ def _extract_tar_strip_top_level(tar_path: Path, extract_dir: Path):
471
+ """Extract a GitHub tarball, stripping the top-level folder."""
472
+ with tarfile.open(tar_path, "r:*") as tar:
473
+ members = tar.getmembers()
474
+
475
+ # Detect top-level prefix (first part before '/')
476
+ top_level = None
477
+ if members:
478
+ first_name = members[0].name
479
+ top_level = first_name.split("/", 1)[0]
480
+
481
+ for member in members:
482
+ # Strip top-level folder
483
+ if top_level and member.name.startswith(top_level + "/"):
484
+ member.name = member.name[len(top_level) + 1 :]
485
+ if not member.name:
486
+ continue
487
+ tar.extract(member, path=extract_dir)
488
+
299
489
  def _combine_multipart_files(folder: str):
300
490
  """
301
491
  Scan a folder for multipart files like name-split-aa, -ab, etc.,
@@ -353,20 +543,27 @@ def _combine_multipart_files(folder: str):
353
543
 
354
544
  print(f"✅ Extracted to: {extract_dir}/")
355
545
 
356
- def _extract_archives_in_folder(folder: str):
546
+ def _extract_archives_in_folder(folder: str, local_paths):
357
547
  """
358
- Extract all .tar.gz and .zip files in the given folder into subdirectories.
548
+ Extract .tar, .gz, .tar.gz, and .zip files in the given folder,
549
+ but only if they are listed in local_paths.
359
550
  Uses streaming to avoid NFS performance issues.
360
551
  """
361
552
  folder = Path(folder)
553
+ local_paths = {str(Path(p).resolve()) for p in local_paths}
554
+
362
555
  for file in folder.iterdir():
363
556
  if not file.is_file():
364
557
  continue
365
558
 
366
- # TAR.GZ
367
- if file.suffixes == [".tar", ".gz"] or file.name.endswith(".tar.gz"):
559
+ file_resolved = str(file.resolve())
560
+ if file_resolved not in local_paths:
561
+ continue
562
+
563
+ # TAR, GZ, TAR.GZ → all handled by _extract_tar_streaming
564
+ if file.suffix in [".tar", ".gz"] or file.name.endswith(".tar.gz"):
368
565
  extract_dir = folder / file.stem.replace(".tar", "")
369
- print(f"📦 Extracting TAR.GZ: {file.name} to {extract_dir}/")
566
+ print(f"📦 Extracting TAR/GZ: {file.name} to {extract_dir}/")
370
567
  _extract_tar_streaming(file, extract_dir)
371
568
 
372
569
  # ZIP
@@ -471,8 +668,45 @@ def _run_installation_script(metadata: Dict, extract_path: str = "."):
471
668
 
472
669
  print("✅ Installation completed successfully.")
473
670
 
671
+ def _resolve_github_metadata_url(gh_ref: str) -> str:
672
+ """
673
+ Resolve a GitHub shorthand like gh:org/repo@tag into a raw URL for metadata.json.
674
+ If tag is omitted, defaults to 'main'.
675
+ """
676
+ try:
677
+ _, repo_ref = gh_ref.split(":", 1) # remove 'gh:'
678
+ if "@" in repo_ref:
679
+ org_repo, tag = repo_ref.split("@", 1)
680
+ else:
681
+ org_repo, tag = repo_ref, "main"
682
+
683
+ owner, repo = org_repo.split("/", 1)
684
+ token = os.getenv("GITHUB_TOKEN")
685
+
686
+ # Use GitHub API to fetch the metadata.json file
687
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/metadata.json?ref={tag}"
688
+ headers = {"Accept": "application/vnd.github.v3.raw"}
689
+ if token:
690
+ headers["Authorization"] = f"token {token}"
691
+
692
+ r = requests.get(api_url, headers=headers)
693
+ r.raise_for_status()
694
+
695
+ # Write metadata.json locally so downstream logic can use a file/URL
696
+ local_path = os.path.join("/tmp", f"{repo}-{tag}-metadata.json")
697
+ with open(local_path, "wb") as f:
698
+ f.write(r.content)
699
+
700
+ return local_path
701
+ except Exception as e:
702
+ raise RuntimeError(f"Failed to resolve GitHub metadata URL {gh_ref}: {e}")
703
+
474
704
  def install_from_metadata(metadata_url: str, internal: bool, install_dir: str = '.'):
475
705
  try:
706
+ if metadata_url.startswith("gh:"):
707
+ metadata_url = _resolve_github_metadata_url(metadata_url)
708
+ internal = False
709
+
476
710
  metadata, _ = _download_and_validate_metadata(metadata_url, internal)
477
711
  print_metadata_summary(metadata=metadata)
478
712
 
@@ -481,7 +715,7 @@ def install_from_metadata(metadata_url: str, internal: bool, install_dir: str =
481
715
  local_paths = _download_assets(metadata, metadata_url, install_dir, internal)
482
716
  if len(local_paths) > 0:
483
717
  _combine_multipart_files(install_dir)
484
- _extract_archives_in_folder(install_dir)
718
+ _extract_archives_in_folder(install_dir, local_paths)
485
719
  _run_installation_script(metadata=metadata, extract_path=install_dir)
486
720
 
487
721
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sima-cli
3
- Version: 0.0.35
3
+ Version: 0.0.36
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,20 +1,20 @@
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=dV_wUomyW1FHtFgcX6fG1NT7ViHhyASu0XFi6X2mgRA,49
4
- sima_cli/cli.py,sha256=qnAQtlaM87tM8t9663NTIxxqMWIpU5--4V-v_0-gYZQ,17224
3
+ sima_cli/__version__.py,sha256=Bbhf5gpY7WnVH4M-cc4ByQEC0_SMh5CebOd37T5sqCA,49
4
+ sima_cli/cli.py,sha256=qm8rtydcugVdd37Rd79weMdX78Y7SKWaUe4LGfX-1XY,17339
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/basic_auth.py,sha256=ht_mVXBtxV2UGvUYwvhkPHs4cMWL5Hw2B_OFxWdKw6c,8825
8
+ sima_cli/auth/basic_auth.py,sha256=UMEXCCnNQpjpp6RZxREs6iiKtYyaeqZBnTSR0wA8s6Q,8767
9
9
  sima_cli/auth/login.py,sha256=nE-dSHK_husXw1XJaEcOe3I1_bnwHkLgO_BqKuQODDM,3781
10
10
  sima_cli/data/resources_internal.yaml,sha256=zlQD4cSnZK86bLtTWuvEudZTARKiuIKmB--Jv4ajL8o,200
11
11
  sima_cli/data/resources_public.yaml,sha256=U7hmUomGeQ2ULdo1BU2OQHr0PyKBamIdK9qrutDlX8o,201
12
12
  sima_cli/download/__init__.py,sha256=6y4O2FOCYFR2jdnQoVi3hRtEoZ0Gw6rydlTy1SGJ5FE,218
13
- sima_cli/download/downloader.py,sha256=nCBrr_0WdnKTIyecwKpg1sCdfm_4PSQTRPwEbiezy8M,5339
13
+ sima_cli/download/downloader.py,sha256=UQdrBWLQsPQygaoVaufOjbzWmRoNnk6pgLdnbnVi04U,6436
14
14
  sima_cli/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  sima_cli/install/hostdriver.py,sha256=kAWDLebs60mbWIyTbUxmNrChcKW1uD5r7FtWNSUVUE4,5852
16
16
  sima_cli/install/metadata_info.py,sha256=wmMqwzGfXbuilkqaxRVrFOzOtTOiONkmPCyA2oDAQpA,2168
17
- sima_cli/install/metadata_installer.py,sha256=kWhERFYfv3hkfAkFJ5yIMu0vv4Kkav8w1DPOOQEysmc,19098
17
+ sima_cli/install/metadata_installer.py,sha256=zIxs9nSX7EC1d6qxL2woZiMSQfFfIOzB2natyTvIPDI,27428
18
18
  sima_cli/install/metadata_validator.py,sha256=7954rp9vFRNnqmIMvCVTjq40kUIEbGXzfc8HmQmChe0,5221
19
19
  sima_cli/install/optiview.py,sha256=r4DYdQDTUbZVCR87hl5T21gsjZrhqpU8hWnYxKmUJ_k,4790
20
20
  sima_cli/install/palette.py,sha256=uRznoHa4Mv9ZXHp6AoqknfC3RxpYNKi9Ins756Cyifk,3930
@@ -46,7 +46,7 @@ sima_cli/utils/env.py,sha256=LtV8S1kCkOpi-7Gj4rhidQRN13x_NDKy4W_LxujheeI,8400
46
46
  sima_cli/utils/net.py,sha256=WVntA4CqipkNrrkA4tBVRadJft_pMcGYh4Re5xk3rqo,971
47
47
  sima_cli/utils/network.py,sha256=UvqxbqbWUczGFyO-t1SybG7Q-x9kjUVRNIn_D6APzy8,1252
48
48
  sima_cli/utils/pkg_update_check.py,sha256=IAV_NAOsBDL_lYNYMRYfdZWuVq-rJ_zzHjJJZ7UQaoc,3274
49
- sima_cli-0.0.35.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
49
+ sima_cli-0.0.36.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
50
50
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  tests/test_app_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  tests/test_auth.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -55,8 +55,8 @@ tests/test_download.py,sha256=t87DwxlHs26_ws9rpcHGwr_OrcRPd3hz6Zmm0vRee2U,4465
55
55
  tests/test_firmware.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  tests/test_model_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
- sima_cli-0.0.35.dist-info/METADATA,sha256=wT-QS9rbFlezkg5M_CmGhmMmHhxZ4XqPVovYiLJLSRw,3705
59
- sima_cli-0.0.35.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
- sima_cli-0.0.35.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
61
- sima_cli-0.0.35.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
62
- sima_cli-0.0.35.dist-info/RECORD,,
58
+ sima_cli-0.0.36.dist-info/METADATA,sha256=nRB0ysTR2pmyDNlBC6Tx7gk1VPJ6o83ZulKr99RLJTo,3705
59
+ sima_cli-0.0.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
+ sima_cli-0.0.36.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
61
+ sima_cli-0.0.36.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
62
+ sima_cli-0.0.36.dist-info/RECORD,,