sima-cli 2.1.12__tar.gz → 2.1.14__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.
Files changed (148) hide show
  1. {sima_cli-2.1.12/sima_cli.egg-info → sima_cli-2.1.14}/PKG-INFO +1 -1
  2. {sima_cli-2.1.12 → sima_cli-2.1.14}/pyproject.toml +1 -1
  3. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/__version__.py +1 -1
  4. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/package_builder.py +11 -1
  5. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/cmdexec.py +16 -4
  6. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/commands.py +78 -5
  7. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/install.py +35 -5
  8. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/neat.py +376 -18
  9. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/network_doctor.py +1 -1
  10. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/utils.py +213 -3
  11. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/docker.py +68 -39
  12. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/vulcan/artifacts.py +35 -12
  13. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/vulcan/commands.py +6 -2
  14. {sima_cli-2.1.12 → sima_cli-2.1.14/sima_cli.egg-info}/PKG-INFO +1 -1
  15. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_docker_utils.py +49 -61
  16. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_package_builder.py +91 -0
  17. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_sdk_image_detection.py +650 -11
  18. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_sdk_network_doctor.py +4 -0
  19. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_vulcan.py +108 -4
  20. {sima_cli-2.1.12 → sima_cli-2.1.14}/LICENSE +0 -0
  21. {sima_cli-2.1.12 → sima_cli-2.1.14}/MANIFEST.in +0 -0
  22. {sima_cli-2.1.12 → sima_cli-2.1.14}/README.md +0 -0
  23. {sima_cli-2.1.12 → sima_cli-2.1.14}/requirements.txt +0 -0
  24. {sima_cli-2.1.12 → sima_cli-2.1.14}/setup.cfg +0 -0
  25. {sima_cli-2.1.12 → sima_cli-2.1.14}/setup.py +0 -0
  26. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/__init__.py +0 -0
  27. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/__main__.py +0 -0
  28. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/app_zoo/__init__.py +0 -0
  29. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/app_zoo/app.py +0 -0
  30. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/app_zoo/commands.py +0 -0
  31. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/auth/__init__.py +0 -0
  32. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/auth/auth0.py +0 -0
  33. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/auth/devportal.py +0 -0
  34. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/auth/login.py +0 -0
  35. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/cli.py +0 -0
  36. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/data/__init__.py +0 -0
  37. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/data/resources_internal.yaml +0 -0
  38. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/data/resources_public.yaml +0 -0
  39. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/deploy_only/__init__.py +0 -0
  40. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/deploy_only/device/__init__.py +0 -0
  41. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/deploy_only/device/commands.py +0 -0
  42. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/deploy_only/mpk/__init__.py +0 -0
  43. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/deploy_only/mpk/commands.py +0 -0
  44. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/discover/__init__.py +0 -0
  45. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/discover/discover.py +0 -0
  46. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/discover/linuxll.py +0 -0
  47. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/download/__init__.py +0 -0
  48. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/download/downloader.py +0 -0
  49. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/__init__.py +0 -0
  50. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/compatibility.py +0 -0
  51. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/github_assets.py +0 -0
  52. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/hostdriver.py +0 -0
  53. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/metadata_info.py +0 -0
  54. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/metadata_installer.py +0 -0
  55. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/metadata_validator.py +0 -0
  56. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/optiview.py +0 -0
  57. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/palette.py +0 -0
  58. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/install/registry.py +0 -0
  59. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/mla/__init__.py +0 -0
  60. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/mla/meminfo.py +0 -0
  61. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/model_zoo/__init__.py +0 -0
  62. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/model_zoo/model.py +0 -0
  63. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/network/__init__.py +0 -0
  64. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/network/network.py +0 -0
  65. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/playbooks/__init__.py +0 -0
  66. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/playbooks/commands.py +0 -0
  67. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/playbooks/manager.py +0 -0
  68. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/__init__.py +0 -0
  69. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/config.py +0 -0
  70. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/linux_devkit_network.py +0 -0
  71. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/linux_shared_network.py +0 -0
  72. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/preinstall.py +0 -0
  73. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/requirements.json +0 -0
  74. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/script.py +0 -0
  75. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/stop.py +0 -0
  76. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/sdk/uninstall.py +0 -0
  77. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/serial/__init__.py +0 -0
  78. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/serial/serial.py +0 -0
  79. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/storage/__init__.py +0 -0
  80. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/storage/nvme.py +0 -0
  81. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/storage/sdcard.py +0 -0
  82. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/__init__.py +0 -0
  83. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/bmaptool.py +0 -0
  84. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/bootimg.py +0 -0
  85. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/cleanlog.py +0 -0
  86. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/elxr.py +0 -0
  87. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/local.py +0 -0
  88. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/netboot.py +0 -0
  89. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/query.py +0 -0
  90. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/remote.py +0 -0
  91. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/update/updater.py +0 -0
  92. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/upgrade/__init__.py +0 -0
  93. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/upgrade/selfupdate.py +0 -0
  94. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/__init__.py +0 -0
  95. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/api_common.py +0 -0
  96. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/artifactory.py +0 -0
  97. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/common.py +0 -0
  98. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/config.py +0 -0
  99. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/config_loader.py +0 -0
  100. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/container_registries.py +0 -0
  101. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/deprecation.py +0 -0
  102. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/device_api.py +0 -0
  103. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/disk.py +0 -0
  104. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/env.py +0 -0
  105. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/errors.py +0 -0
  106. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/mpk_api.py +0 -0
  107. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/net.py +0 -0
  108. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/network.py +0 -0
  109. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/pcie.py +0 -0
  110. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/pkg_update_check.py +0 -0
  111. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/serializers.py +0 -0
  112. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/services.py +0 -0
  113. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/utils/tag.py +0 -0
  114. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli/vulcan/__init__.py +0 -0
  115. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli.egg-info/SOURCES.txt +0 -0
  116. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli.egg-info/dependency_links.txt +0 -0
  117. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli.egg-info/entry_points.txt +0 -0
  118. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli.egg-info/requires.txt +0 -0
  119. {sima_cli-2.1.12 → sima_cli-2.1.14}/sima_cli.egg-info/top_level.txt +0 -0
  120. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/__init__.py +0 -0
  121. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/e2e/__init__.py +0 -0
  122. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/__init__.py +0 -0
  123. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_app_zoo.py +0 -0
  124. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_appzoo_commands.py +0 -0
  125. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_auth.py +0 -0
  126. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_auth0.py +0 -0
  127. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_cli.py +0 -0
  128. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_cli_stdio.py +0 -0
  129. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_cli_update.py +0 -0
  130. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_cli_update_rerun.py +0 -0
  131. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_deprecation.py +0 -0
  132. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_download.py +0 -0
  133. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_elxr_update.py +0 -0
  134. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_firmware.py +0 -0
  135. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_generate_cli_markdown_docs.py +0 -0
  136. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_install_stub.py +0 -0
  137. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_metadata_installer.py +0 -0
  138. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_metadata_validator.py +0 -0
  139. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_model_zoo.py +0 -0
  140. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_netboot.py +0 -0
  141. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_pkg_update_check.py +0 -0
  142. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_sdk_linux_shared_network.py +0 -0
  143. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_sdk_preinstall.py +0 -0
  144. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_sdk_uninstall.py +0 -0
  145. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_selfupdate.py +0 -0
  146. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_skills_commands.py +0 -0
  147. {sima_cli-2.1.12 → sima_cli-2.1.14}/tests/unit/test_skills_manager.py +0 -0
  148. {sima_cli-2.1.12 → sima_cli-2.1.14}/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.12
3
+ Version: 2.1.14
4
4
  Summary: CLI tool for SiMa Developer Portal to download models, firmware, and apps.
5
5
  Home-page: https://community.sima.ai/
6
6
  Author: SiMa.ai
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sima-cli"
7
- version = "2.1.12"
7
+ version = "2.1.14"
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"
@@ -1,2 +1,2 @@
1
1
  # sima_cli/__version__.py
2
- __version__ = "2.1.12"
2
+ __version__ = "2.1.14"
@@ -136,7 +136,17 @@ def _format_size(total_bytes: int) -> str:
136
136
 
137
137
  def _matches_exclude_pattern(resource: str, patterns: Sequence[str]) -> bool:
138
138
  filename = Path(resource).name
139
- return any(pattern and (pattern in resource or pattern in filename) for pattern in patterns)
139
+ path_parts = resource.split("/")
140
+ for pattern in patterns:
141
+ if not pattern:
142
+ continue
143
+ if pattern.startswith(".") and "/" not in pattern and "\\" not in pattern:
144
+ if filename.endswith(pattern) or pattern in path_parts:
145
+ return True
146
+ continue
147
+ if pattern in resource or pattern in filename:
148
+ return True
149
+ return False
140
150
 
141
151
 
142
152
  def collect_artifact_resources(
@@ -18,10 +18,15 @@ from sima_cli.sdk.utils import (
18
18
 
19
19
  console = Console()
20
20
 
21
+
22
+ class SdkContainerUnavailable(RuntimeError):
23
+ """Raised when no running SDK container matches a requested tool."""
24
+
25
+
21
26
  # ─────────────────────────────────────────────
22
27
  # Core executer
23
28
  # ─────────────────────────────────────────────
24
- def exec_container_cmd(ctx, keyword: str, cmd: Optional[str] = None):
29
+ def exec_container_cmd(ctx, keyword: str, cmd: Optional[str] = None, raise_on_missing: bool = False):
25
30
  """
26
31
  Find a running container matching the given SDK keyword (e.g. mpk, yocto),
27
32
  optionally filtering by version (from ctx.obj['version_filter']),
@@ -34,6 +39,8 @@ def exec_container_cmd(ctx, keyword: str, cmd: Optional[str] = None):
34
39
  containers = get_all_containers(running_containers_only=True)
35
40
  if not containers:
36
41
  console.print("[yellow]⚠️ No running containers found.[/yellow]")
42
+ if raise_on_missing:
43
+ raise SdkContainerUnavailable("No running containers found.")
37
44
  sys.exit(0)
38
45
 
39
46
  # ──────────────────────────────────────────────
@@ -61,11 +68,16 @@ def exec_container_cmd(ctx, keyword: str, cmd: Optional[str] = None):
61
68
  )
62
69
 
63
70
  if not matches:
64
- console.print(
65
- f"[red]❌ No running containers found for '{keyword}'"
71
+ message = (
72
+ f"No running containers found for '{keyword}'"
66
73
  + (f" with version '{version_filter}'" if version_filter else "")
67
- + ".[/red]"
74
+ + "."
75
+ )
76
+ console.print(
77
+ f"[red]❌ {message}[/red]"
68
78
  )
79
+ if raise_on_missing:
80
+ raise SdkContainerUnavailable(message)
69
81
  sys.exit(1)
70
82
 
71
83
  # ──────────────────────────────────────────────
@@ -24,10 +24,10 @@ from typing import Optional
24
24
  from rich.console import Console
25
25
  from rich.panel import Panel
26
26
  from sima_cli.sdk.install import setup_and_start
27
- from sima_cli.sdk.cmdexec import exec_container_cmd
27
+ from sima_cli.sdk.cmdexec import SdkContainerUnavailable, exec_container_cmd
28
28
  from sima_cli.sdk.uninstall import remove_containers, remove_unused_images
29
29
  from sima_cli.sdk.stop import stop_containers
30
- from sima_cli.sdk.utils import get_all_containers
30
+ from sima_cli.sdk.utils import get_all_containers, container_matches_sdk_keyword
31
31
  from sima_cli.sdk.utils import extract_short_name
32
32
  from sima_cli.discover.discover import discover_and_probe
33
33
  from rich.table import Table
@@ -38,6 +38,7 @@ from sima_cli.sdk.config import IMAGE_CONFIG
38
38
  from sima_cli.sdk.network_doctor import (
39
39
  build_network_doctor_report,
40
40
  collect_network_doctor_bundle,
41
+ ensure_existing_neat_container_startable,
41
42
  print_network_doctor_report,
42
43
  repair_linux_devkit_network,
43
44
  )
@@ -156,7 +157,66 @@ def _resolve_devkit_ipv4(devkit: Optional[str]) -> str:
156
157
  return value
157
158
 
158
159
 
159
- def launch_sdk_tool(tool: str, cmd, ctx):
160
+ def _container_name(container) -> str:
161
+ return container.get("Names") or container.get("Name") or container.get("name") or ""
162
+
163
+
164
+ def _container_is_running(container) -> bool:
165
+ state = (container.get("State") or "").lower().strip()
166
+ status = (container.get("Status") or "").lower().strip()
167
+ return state == "running" or status.startswith("up") or "running" in status
168
+
169
+
170
+ def _version_matches(container, version_filter: str) -> bool:
171
+ if not version_filter:
172
+ return True
173
+ needle = version_filter.lower()
174
+ name = _container_name(container).lower()
175
+ image = (container.get("Image") or container.get("image") or "").lower()
176
+ return needle in name or needle in image
177
+
178
+
179
+ def _start_stopped_neat_containers(ctx) -> None:
180
+ version_filter = None
181
+ if ctx and getattr(ctx, "obj", None):
182
+ version_filter = ctx.obj.get("version_filter")
183
+
184
+ containers = get_all_containers(running_containers_only=False)
185
+ matches = [
186
+ container
187
+ for container in containers
188
+ if container_matches_sdk_keyword(container, "neat")
189
+ and _version_matches(container, version_filter)
190
+ and not _container_is_running(container)
191
+ ]
192
+
193
+ if not matches:
194
+ message = "No stopped Neat SDK containers found."
195
+ if version_filter:
196
+ message = f"No stopped Neat SDK containers found for version '{version_filter}'."
197
+ raise click.ClickException(f"{message} Run: sima-cli sdk setup")
198
+
199
+ for container in matches:
200
+ name = _container_name(container)
201
+ if not name:
202
+ continue
203
+ try:
204
+ ensure_existing_neat_container_startable(name)
205
+ console.print(
206
+ f"[cyan]▶ Starting existing Neat SDK container:[/cyan] [bold]{name}[/bold] "
207
+ "[dim](this may take a moment)[/dim]"
208
+ )
209
+ subprocess.run(["docker", "start", name], check=True, capture_output=True, text=True)
210
+ except subprocess.CalledProcessError as e:
211
+ raise click.ClickException(
212
+ f"Failed to start Neat SDK container '{name}' while running: {' '.join(e.cmd)}"
213
+ ) from e
214
+ except RuntimeError as e:
215
+ raise click.ClickException(str(e)) from e
216
+ console.print(f"[green]✅ Started Neat SDK container:[/green] [bold]{name}[/bold]")
217
+
218
+
219
+ def launch_sdk_tool(tool: str, cmd, ctx, recover_unavailable: bool = False):
160
220
  """
161
221
  Launch a selected SDK tool container, optionally executing a command inside it.
162
222
  If no command is provided, defaults to an interactive bash login shell.
@@ -182,7 +242,17 @@ def launch_sdk_tool(tool: str, cmd, ctx):
182
242
  else:
183
243
  cmd_str = str(cmd)
184
244
 
185
- exec_container_cmd(ctx, tool, cmd_str)
245
+ if not recover_unavailable:
246
+ exec_container_cmd(ctx, tool, cmd_str)
247
+ return
248
+
249
+ try:
250
+ exec_container_cmd(ctx, tool, cmd_str, raise_on_missing=True)
251
+ except SdkContainerUnavailable:
252
+ if tool != "neat":
253
+ raise
254
+ _start_stopped_neat_containers(ctx)
255
+ exec_container_cmd(ctx, tool, cmd_str)
186
256
 
187
257
 
188
258
  # ------------------------------------------------------------
@@ -554,13 +624,16 @@ def neat(ctx, cmd):
554
624
  running container with bash -lc. If CMD is omitted, sima-cli opens an
555
625
  interactive login shell.
556
626
 
627
+ If no matching Neat SDK container is running, existing stopped Neat SDK
628
+ container(s) are started automatically and the command is retried.
629
+
557
630
  \b
558
631
  Examples:
559
632
  sima-cli sdk neat
560
633
  sima-cli sdk neat python --version
561
634
  sima-cli sdk neat "python app.py --config config.json"
562
635
  """
563
- launch_sdk_tool("neat", cmd, ctx)
636
+ launch_sdk_tool("neat", cmd, ctx, recover_unavailable=True)
564
637
 
565
638
 
566
639
  @sdk.command(
@@ -765,6 +765,22 @@ def _format_gb(byte_count: int) -> str:
765
765
  return f"{byte_count / (1024 ** 3):.1f} GB"
766
766
 
767
767
 
768
+ def _ensure_writable_sdk_extensions_dir(path: Path) -> Path:
769
+ resolved = Path(os.path.realpath(str(path)))
770
+ try:
771
+ resolved.mkdir(parents=True, exist_ok=True)
772
+ probe = resolved / ".sima-cli-write-test"
773
+ with open(probe, "w", encoding="utf-8") as handle:
774
+ handle.write("ok\n")
775
+ probe.unlink()
776
+ except OSError as e:
777
+ raise RuntimeError(
778
+ f"SDK extensions directory is not writable: {resolved}. "
779
+ "Choose another directory where your user can create files."
780
+ ) from e
781
+ return resolved
782
+
783
+
768
784
  def _setup_sdk_extensions(
769
785
  selected_images: List[str],
770
786
  noninteractive: bool = False,
@@ -791,12 +807,16 @@ def _setup_sdk_extensions(
791
807
  if noninteractive or yes_to_all:
792
808
  extensions_dir = default_extensions_dir
793
809
  click.echo(f"ℹ️ Using default SDK extensions directory: {extensions_dir}")
810
+ extensions_dir = _ensure_writable_sdk_extensions_dir(extensions_dir)
794
811
  else:
795
- response = input(f"Enter SDK extensions directory [{default_extensions_dir}]: ").strip()
796
- extensions_dir = Path(response).expanduser() if response else default_extensions_dir
797
-
798
- extensions_dir = Path(os.path.realpath(str(extensions_dir)))
799
- extensions_dir.mkdir(parents=True, exist_ok=True)
812
+ while True:
813
+ response = input(f"Enter SDK extensions directory [{default_extensions_dir}]: ").strip()
814
+ extensions_dir = Path(response).expanduser() if response else default_extensions_dir
815
+ try:
816
+ extensions_dir = _ensure_writable_sdk_extensions_dir(extensions_dir)
817
+ break
818
+ except RuntimeError as e:
819
+ click.secho(f"⚠️ {e}", fg="yellow")
800
820
  print(f"✅ SDK extensions directory configured: {extensions_dir}")
801
821
  return str(extensions_dir)
802
822
 
@@ -1148,6 +1168,16 @@ def setup_and_start(
1148
1168
  configure_container_user(existing_container, login_name, user_uid, user_gid)
1149
1169
 
1150
1170
  if devkit_env and is_neat_sdk_image(img):
1171
+ if not skip_insight:
1172
+ # Re-issue the mounted HTTPS cert for the (possibly new) host
1173
+ # IP before bootstrap restarts neat-insight, so the browser
1174
+ # doesn't hit a SAN mismatch ("connection is not private")
1175
+ # after a re-point.
1176
+ from sima_cli.sdk.neat import refresh_neat_certificates
1177
+ refresh_neat_certificates(
1178
+ workspace, existing_container, devkit_env,
1179
+ yes_to_all=yes_to_all, noninteractive=noninteractive,
1180
+ )
1151
1181
  bootstrap_devkit_container(existing_container, devkit_env)
1152
1182
 
1153
1183
  if len(selected_images) == 1: