sima-cli 0.0.20__py3-none-any.whl → 0.0.22__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.20"
2
+ __version__ = "0.0.22"
sima_cli/cli.py CHANGED
@@ -9,7 +9,10 @@ from sima_cli.__version__ import __version__
9
9
  from sima_cli.utils.config import CONFIG_PATH
10
10
  from sima_cli.install.optiview import install_optiview
11
11
  from sima_cli.install.hostdriver import install_hostdriver
12
+ from sima_cli.install.metadata_installer import install_from_metadata, metadata_resolver
12
13
  from sima_cli.serial.serial import connect_serial
14
+ from sima_cli.nvme.nvme import nvme_format, nvme_remount
15
+ from sima_cli.network.network import network_menu
13
16
 
14
17
  # Entry point for the CLI tool using Click's command group decorator
15
18
  @click.group()
@@ -116,6 +119,7 @@ def download(ctx, url, dest):
116
119
  click.echo(f"\n✅ File downloaded successfully to: {path}")
117
120
  return
118
121
  except Exception as e:
122
+ click.echo(f"❌ Failed to download as file {e}")
119
123
  pass
120
124
 
121
125
  # If that fails, try to treat as a folder and download all files
@@ -140,12 +144,13 @@ def download(ctx, url, dest):
140
144
  help="Skip confirmation after firmware file is downloaded."
141
145
  )
142
146
  @click.option(
143
- '--passwd',
147
+ '-p', '--passwd',
144
148
  default='edgeai',
145
149
  help="Optional SSH password for remote board (default is 'edgeai')."
146
150
  )
151
+ @click.option("-f", "--flavor", type=click.Choice(["headless", "full"], case_sensitive=False), default="headless", show_default=True, help="firmware flavor.")
147
152
  @click.pass_context
148
- def update(ctx, version_or_url, ip, yes, passwd):
153
+ def update(ctx, version_or_url, ip, yes, passwd, flavor):
149
154
  """
150
155
  Run system update across different environments.
151
156
  Downloads and applies firmware updates for PCIe host or SiMa board.
@@ -153,7 +158,7 @@ def update(ctx, version_or_url, ip, yes, passwd):
153
158
  version_or_url: The version string (e.g. '1.5.0') or a direct URL to the firmware package.
154
159
  """
155
160
  internal = ctx.obj.get("internal", False)
156
- perform_update(version_or_url, ip, internal, passwd=passwd, auto_confirm=yes)
161
+ perform_update(version_or_url, ip, internal, passwd=passwd, auto_confirm=yes, flavor=flavor)
157
162
 
158
163
  # ----------------------
159
164
  # Model Zoo Subcommands
@@ -226,18 +231,20 @@ def show_mla_memory_usage(ctx):
226
231
  # ----------------------
227
232
  @main.command(name="bootimg")
228
233
  @click.option("-v", "--version", required=True, help="Firmware version to download and write (e.g., 1.6.0)")
229
- @click.option("--boardtype", type=click.Choice(["modalix", "mlsoc"], case_sensitive=False), default="mlsoc", show_default=True, help="Target board type.")
230
- @click.option("--netboot", is_flag=True, default=False, show_default=True, help="Prepare image for network boot and launch TFTP server.")
231
- @click.option("--autoflash", is_flag=True, default=False, show_default=True, help="Net boot the DevKit and automatically flash the internal storage")
234
+ @click.option("-b", "--boardtype", type=click.Choice(["modalix", "mlsoc"], case_sensitive=False), default="mlsoc", show_default=True, help="Target board type.")
235
+ @click.option("-n", "--netboot", is_flag=True, default=False, show_default=True, help="Prepare image for network boot and launch TFTP server.")
236
+ @click.option("-a", "--autoflash", is_flag=True, default=False, show_default=True, help="Net boot the DevKit and automatically flash the internal storage - TBD")
232
237
  @click.pass_context
233
238
  def bootimg_cmd(ctx, version, boardtype, netboot, autoflash):
234
239
  """
235
240
  Download and burn a removable media or setup TFTP boot.
236
241
 
242
+ Only supports headless image
243
+
237
244
  Examples:
238
245
  sima-cli bootimg -v 1.6.0
239
246
  sima-cli bootimg -v 1.6.0 --boardtype mlsoc
240
- sima-cli bootimg -v 1.6.0 --boardtype modalix
247
+ sima-cli bootimg -v 1.6.0 --boardtype mlsoc
241
248
  sima-cli bootimg -v 1.6.0 --boardtype modalix --netboot
242
249
  sima-cli bootimg -v 1.6.0 --boardtype modalix --autoflash
243
250
  """
@@ -250,14 +257,15 @@ def bootimg_cmd(ctx, version, boardtype, netboot, autoflash):
250
257
  click.echo(f"📦 Preparing boot image:")
251
258
  click.echo(f" 🔹 Version : {version}")
252
259
  click.echo(f" 🔹 Board Type: {boardtype}")
260
+ click.echo(f" 🔹 F/W Flavor: headless")
253
261
 
254
262
  try:
255
263
  boardtype = boardtype if boardtype != 'mlsoc' else 'davinci'
256
264
  if netboot or autoflash:
257
- setup_netboot(version, boardtype, internal, autoflash)
265
+ setup_netboot(version, boardtype, internal, autoflash, flavor='headless')
258
266
  click.echo("✅ Netboot image prepared and TFTP server is running.")
259
267
  else:
260
- write_image(version, boardtype, 'yocto', internal)
268
+ write_image(version, boardtype, 'yocto', internal, flavor='headless')
261
269
  click.echo("✅ Boot image successfully written.")
262
270
  click.echo("✅ Boot image successfully written.")
263
271
  except Exception as e:
@@ -272,22 +280,44 @@ SDK_INDEPENDENT_COMPONENTS = {"optiview"}
272
280
  ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
273
281
 
274
282
  @main.command(name="install")
275
- @click.argument("component", type=click.Choice(ALL_COMPONENTS, case_sensitive=False))
276
- @click.option("-v", "--version", help="SDK version (required for SDK-dependent components)")
283
+ @click.argument("component", required=False)
284
+ @click.option("-v", "--version", help="SDK version (required for SDK-dependent components unless --metadata is provided)")
285
+ @click.option("-m", "--mirror", help="URL to a metadata.json file for generic installation")
286
+ @click.option("-t", "--tag", help="Tag of the package (optional)")
277
287
  @click.pass_context
278
- def install_cmd(ctx, component, version):
288
+ def install_cmd(ctx, component, version, mirror, tag):
279
289
  """
280
- Install supported components such as SDKs or tools.
290
+ Install supported components such as SDKs, tools, or generic packages via metadata.
281
291
 
282
292
  Examples:
283
293
 
284
294
  sima-cli install hostdriver -v 1.6.0
285
295
 
286
296
  sima-cli install optiview
297
+
298
+ sima-cli install -m https://example.com/packages/foo/metadata.json
299
+
300
+ sima-cli install examples.llima -v 1.7.0
287
301
  """
288
- component = component.lower()
289
302
  internal = ctx.obj.get("internal", False)
290
303
 
304
+ # Metadata-based installation path
305
+ if mirror:
306
+ if component:
307
+ click.echo(f"⚠️ Component '{component}' is ignored when using --metadata. Proceeding with metadata-based installation.")
308
+ click.echo(f"🔧 Installing generic component from metadata URL: {mirror}")
309
+ if install_from_metadata(metadata_url=mirror, internal=internal):
310
+ click.echo("✅ Installation complete.")
311
+ return
312
+
313
+ # No component and no metadata: error
314
+ if not component:
315
+ click.echo("❌ You must specify either a component name or provide --metadata.")
316
+ ctx.exit(1)
317
+
318
+ component = component.lower()
319
+
320
+ # Validate version requirement
291
321
  if component in SDK_DEPENDENT_COMPONENTS and not version:
292
322
  click.echo(f"❌ The component '{component}' requires a specific SDK version. Please provide one using -v.")
293
323
  ctx.exit(1)
@@ -295,7 +325,7 @@ def install_cmd(ctx, component, version):
295
325
  if component in SDK_INDEPENDENT_COMPONENTS and version:
296
326
  click.echo(f"ℹ️ The component '{component}' does not require an SDK version. Ignoring -v {version}.")
297
327
 
298
- # Perform the installation logic
328
+ # Hardcoded component installation
299
329
  if component == "palette":
300
330
  click.echo(f"🔧 Installing SDK component 'palette' for version {version} is not implemented yet...")
301
331
  elif component == "hostdriver":
@@ -304,9 +334,20 @@ def install_cmd(ctx, component, version):
304
334
  elif component == "optiview":
305
335
  click.echo("🔧 Installing tool 'optiview'...")
306
336
  install_optiview()
337
+ else:
338
+ # Case 4: Try to resolve metadata URL from version + tag
339
+ try:
340
+ metadata_url = metadata_resolver(component, version, tag)
341
+ click.echo(f"🔧 Installing '{component}' from resolved metadata: {metadata_url}")
342
+ if install_from_metadata(metadata_url=metadata_url, internal=internal):
343
+ click.echo("✅ Installation complete.")
344
+ except Exception as e:
345
+ click.echo(f"❌ Failed to resolve metadata for component '{component}': {e}")
346
+ ctx.exit(1)
307
347
 
308
348
  click.echo("✅ Installation complete.")
309
349
 
350
+
310
351
  # ----------------------
311
352
  # Serial Subcommands
312
353
  # ----------------------
@@ -326,6 +367,59 @@ def serial_cmd(ctx, baud):
326
367
  - Windows: shows PuTTY/Tera Term setup instructions
327
368
  """
328
369
  connect_serial(ctx, baud)
370
+
371
+ # ----------------------
372
+ # Network Subcommands
373
+ # ----------------------
374
+ @main.command(name="network")
375
+ @click.pass_context
376
+ def network_cmd(ctx):
377
+ """
378
+ Setup Network IP address on the DevKit
379
+
380
+ This command only works on the DevKit. It allows user to switch between DHCP and Static (Default addresses) IP.
381
+
382
+ """
383
+ network_menu()
384
+
385
+ # ----------------------
386
+ # NVME Subcommands
387
+ # ----------------------
388
+ NVME_OPERATIONS = {"format", "remount"}
389
+ @main.command(name="nvme")
390
+ @click.argument("operation", type=click.Choice(NVME_OPERATIONS, case_sensitive=False))
391
+ @click.pass_context
392
+ def nvme_cmd(ctx, operation):
393
+ """
394
+ Perform NVMe operations on the Modalix DevKit.
395
+
396
+ Available operations:
397
+
398
+ format - Format the NVMe drive and mount it to /mnt/nvme
399
+
400
+ remount - Remount the existing NVMe partition to /mnt/nvme
401
+
402
+ Example:
403
+ sima-cli nvme format
404
+
405
+ sima-cli nvme remount
406
+ """
407
+ operation = operation.lower()
408
+
409
+ if operation == "format":
410
+ nvme_format()
411
+
412
+ elif operation == "remount":
413
+ try:
414
+ nvme_remount()
415
+ except Exception as e:
416
+ click.echo(f"❌ Failed to remount NVMe drive: {e}")
417
+ ctx.exit(1)
418
+
419
+ else:
420
+ click.echo(f"❌ Unsupported NVMe operation: {operation}")
421
+ ctx.exit(1)
422
+
329
423
  # ----------------------
330
424
  # App Zoo Subcommands
331
425
  # ----------------------
@@ -80,10 +80,14 @@ def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = Fa
80
80
  headers["Authorization"] = f"Bearer {auth_token}"
81
81
  request_fn = requests.get
82
82
  head_fn = requests.head
83
- else:
83
+ elif 'https://docs.sima.ai' in url:
84
84
  session = login_external()
85
85
  request_fn = session.get
86
86
  head_fn = session.head
87
+ else:
88
+ session = requests.Session()
89
+ request_fn = session.get
90
+ head_fn = session.head
87
91
 
88
92
  # HEAD request to get total file size
89
93
  head = head_fn(url, headers=headers, timeout=10)
@@ -95,7 +99,7 @@ def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = Fa
95
99
  existing_size = os.path.getsize(dest_path)
96
100
 
97
101
  if existing_size == total_size:
98
- print(f"✔ File already exists and is complete: {file_name}")
102
+ print(f"✔ File already exists and is complete: {file_name}")
99
103
  return dest_path
100
104
  elif existing_size < total_size:
101
105
  resume_header['Range'] = f'bytes={existing_size}-'
@@ -113,7 +117,7 @@ def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = Fa
113
117
  final_total = existing_size + content_length
114
118
 
115
119
  with open(dest_path, mode) as f, tqdm(
116
- desc=f"Downloading {file_name}",
120
+ desc=f"⬇️ Downloading {file_name}",
117
121
  total=final_total,
118
122
  initial=existing_size,
119
123
  unit='B',
@@ -0,0 +1,57 @@
1
+ from rich import print
2
+ from rich.table import Table
3
+ from rich.panel import Panel
4
+
5
+ def print_metadata_summary(metadata: dict):
6
+ table = Table(show_header=False, box=None, padding=(0, 1))
7
+
8
+ table.add_row("[bold]Name[/bold]", metadata.get("name", "N/A"))
9
+ table.add_row("[bold]Version[/bold]", metadata.get("version", "N/A"))
10
+ table.add_row("[bold]Release[/bold]", metadata.get("release", "N/A"))
11
+ table.add_row("[bold]Description[/bold]", metadata.get("description", "N/A"))
12
+
13
+ # Platform info
14
+ platform_info = []
15
+ for p in metadata.get("platforms", []):
16
+ platform_type = p.get("type", "unknown")
17
+ if platform_type == "board":
18
+ compat = ", ".join(p.get("compatible_with", []))
19
+ platform_info.append(f"{platform_type} ({compat})")
20
+ elif platform_type in ("host", "generic"):
21
+ os_list = ", ".join(p.get("os", []))
22
+ platform_info.append(f"{platform_type} ({os_list})")
23
+ else:
24
+ platform_info.append(platform_type)
25
+
26
+ table.add_row("[bold]Platforms[/bold]", "; ".join(platform_info) or "N/A")
27
+
28
+ # Resources
29
+ resource_count = len(metadata.get("resources", []))
30
+ table.add_row("[bold]Resources[/bold]", f"{resource_count} file(s)")
31
+
32
+ # Size
33
+ size = metadata.get("size", {})
34
+ table.add_row("[bold]Download Size[/bold]", size.get("download", "N/A"))
35
+ table.add_row("[bold]Install Size[/bold]", size.get("install", "N/A"))
36
+
37
+ print()
38
+ print(Panel(table, title="📦 Package Summary", expand=False))
39
+ print()
40
+
41
+
42
+ def parse_size_string_to_bytes(size_str: str) -> int:
43
+ """
44
+ Convert a size string like '40GB' or '512MB' to bytes.
45
+ """
46
+ size_str = size_str.strip().upper()
47
+ units = {"KB": 10**3, "MB": 10**6, "GB": 10**9}
48
+
49
+ for unit, multiplier in units.items():
50
+ if size_str.endswith(unit):
51
+ try:
52
+ value = float(size_str[:-len(unit)].strip())
53
+ return int(value * multiplier)
54
+ except ValueError:
55
+ raise ValueError(f"Invalid numeric value in size string: '{size_str}'")
56
+
57
+ raise ValueError(f"Unrecognized size unit in '{size_str}'. Must be KB, MB, or GB.")