sima-cli 2.1.7__tar.gz → 2.1.9__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 (142) hide show
  1. {sima_cli-2.1.7/sima_cli.egg-info → sima_cli-2.1.9}/PKG-INFO +19 -6
  2. {sima_cli-2.1.7 → sima_cli-2.1.9}/README.md +17 -4
  3. {sima_cli-2.1.7 → sima_cli-2.1.9}/pyproject.toml +2 -2
  4. {sima_cli-2.1.7 → sima_cli-2.1.9}/requirements.txt +1 -1
  5. {sima_cli-2.1.7 → sima_cli-2.1.9}/setup.py +1 -1
  6. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/__version__.py +1 -1
  7. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/auth/auth0.py +150 -4
  8. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/auth/devportal.py +73 -40
  9. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/cli.py +46 -15
  10. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/metadata_installer.py +199 -9
  11. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/package_builder.py +5 -1
  12. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/registry.py +18 -1
  13. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/install.py +42 -5
  14. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/utils.py +37 -19
  15. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/elxr.py +213 -34
  16. sima_cli-2.1.9/sima_cli/upgrade/selfupdate.py +409 -0
  17. sima_cli-2.1.9/sima_cli/utils/docker.py +391 -0
  18. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/vulcan/artifacts.py +1 -28
  19. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/vulcan/commands.py +114 -36
  20. {sima_cli-2.1.7 → sima_cli-2.1.9/sima_cli.egg-info}/PKG-INFO +19 -6
  21. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli.egg-info/SOURCES.txt +2 -0
  22. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli.egg-info/requires.txt +1 -1
  23. sima_cli-2.1.9/tests/unit/test_auth.py +251 -0
  24. sima_cli-2.1.9/tests/unit/test_auth0.py +173 -0
  25. sima_cli-2.1.9/tests/unit/test_docker_utils.py +322 -0
  26. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_elxr_update.py +159 -0
  27. sima_cli-2.1.9/tests/unit/test_metadata_installer.py +207 -0
  28. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_package_builder.py +45 -0
  29. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_sdk_image_detection.py +139 -6
  30. sima_cli-2.1.9/tests/unit/test_selfupdate.py +210 -0
  31. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_vulcan.py +113 -31
  32. sima_cli-2.1.7/sima_cli/upgrade/selfupdate.py +0 -236
  33. sima_cli-2.1.7/sima_cli/utils/docker.py +0 -228
  34. sima_cli-2.1.7/tests/unit/test_auth.py +0 -143
  35. sima_cli-2.1.7/tests/unit/test_metadata_installer.py +0 -21
  36. sima_cli-2.1.7/tests/unit/test_selfupdate.py +0 -68
  37. {sima_cli-2.1.7 → sima_cli-2.1.9}/LICENSE +0 -0
  38. {sima_cli-2.1.7 → sima_cli-2.1.9}/MANIFEST.in +0 -0
  39. {sima_cli-2.1.7 → sima_cli-2.1.9}/setup.cfg +0 -0
  40. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/__init__.py +0 -0
  41. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/__main__.py +0 -0
  42. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/app_zoo/__init__.py +0 -0
  43. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/app_zoo/app.py +0 -0
  44. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/app_zoo/commands.py +0 -0
  45. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/auth/__init__.py +0 -0
  46. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/auth/login.py +0 -0
  47. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/auth/oauth.py +0 -0
  48. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/data/__init__.py +0 -0
  49. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/data/resources_internal.yaml +0 -0
  50. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/data/resources_public.yaml +0 -0
  51. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/deploy_only/__init__.py +0 -0
  52. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/deploy_only/device/__init__.py +0 -0
  53. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/deploy_only/device/commands.py +0 -0
  54. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/deploy_only/mpk/__init__.py +0 -0
  55. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/deploy_only/mpk/commands.py +0 -0
  56. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/discover/__init__.py +0 -0
  57. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/discover/discover.py +0 -0
  58. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/discover/linuxll.py +0 -0
  59. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/download/__init__.py +0 -0
  60. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/download/downloader.py +0 -0
  61. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/__init__.py +0 -0
  62. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/github_assets.py +0 -0
  63. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/hostdriver.py +0 -0
  64. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/metadata_info.py +0 -0
  65. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/metadata_validator.py +0 -0
  66. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/optiview.py +0 -0
  67. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/install/palette.py +0 -0
  68. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/mla/__init__.py +0 -0
  69. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/mla/meminfo.py +0 -0
  70. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/model_zoo/__init__.py +0 -0
  71. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/model_zoo/model.py +0 -0
  72. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/network/__init__.py +0 -0
  73. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/network/network.py +0 -0
  74. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/playbooks/__init__.py +0 -0
  75. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/playbooks/commands.py +0 -0
  76. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/playbooks/manager.py +0 -0
  77. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/__init__.py +0 -0
  78. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/cmdexec.py +0 -0
  79. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/commands.py +0 -0
  80. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/config.py +0 -0
  81. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/linux_shared_network.py +0 -0
  82. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/neat.py +0 -0
  83. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/preinstall.py +0 -0
  84. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/requirements.json +0 -0
  85. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/script.py +0 -0
  86. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/stop.py +0 -0
  87. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/sdk/uninstall.py +0 -0
  88. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/serial/__init__.py +0 -0
  89. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/serial/serial.py +0 -0
  90. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/storage/__init__.py +0 -0
  91. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/storage/nvme.py +0 -0
  92. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/storage/sdcard.py +0 -0
  93. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/__init__.py +0 -0
  94. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/bmaptool.py +0 -0
  95. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/bootimg.py +0 -0
  96. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/cleanlog.py +0 -0
  97. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/local.py +0 -0
  98. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/netboot.py +0 -0
  99. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/query.py +0 -0
  100. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/remote.py +0 -0
  101. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/update/updater.py +0 -0
  102. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/upgrade/__init__.py +0 -0
  103. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/__init__.py +0 -0
  104. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/api_common.py +0 -0
  105. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/artifactory.py +0 -0
  106. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/common.py +0 -0
  107. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/config.py +0 -0
  108. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/config_loader.py +0 -0
  109. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/container_registries.py +0 -0
  110. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/device_api.py +0 -0
  111. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/disk.py +0 -0
  112. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/env.py +0 -0
  113. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/errors.py +0 -0
  114. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/mpk_api.py +0 -0
  115. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/net.py +0 -0
  116. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/network.py +0 -0
  117. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/pcie.py +0 -0
  118. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/pkg_update_check.py +0 -0
  119. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/serializers.py +0 -0
  120. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/services.py +0 -0
  121. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/utils/tag.py +0 -0
  122. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli/vulcan/__init__.py +0 -0
  123. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli.egg-info/dependency_links.txt +0 -0
  124. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli.egg-info/entry_points.txt +0 -0
  125. {sima_cli-2.1.7 → sima_cli-2.1.9}/sima_cli.egg-info/top_level.txt +0 -0
  126. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/__init__.py +0 -0
  127. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/e2e/__init__.py +0 -0
  128. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/__init__.py +0 -0
  129. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_app_zoo.py +0 -0
  130. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_cli.py +0 -0
  131. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_cli_stdio.py +0 -0
  132. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_download.py +0 -0
  133. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_firmware.py +0 -0
  134. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_install_stub.py +0 -0
  135. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_model_zoo.py +0 -0
  136. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_netboot.py +0 -0
  137. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_pkg_update_check.py +0 -0
  138. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_sdk_preinstall.py +0 -0
  139. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_sdk_uninstall.py +0 -0
  140. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_skills_commands.py +0 -0
  141. {sima_cli-2.1.7 → sima_cli-2.1.9}/tests/unit/test_skills_manager.py +0 -0
  142. {sima_cli-2.1.7 → sima_cli-2.1.9}/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.7
3
+ Version: 2.1.9
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
@@ -17,7 +17,7 @@ Requires-Python: >=3.8
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: click>=8.0
20
- Requires-Dist: requests>=2.33.0
20
+ Requires-Dist: requests<3.0,>=2.32.4
21
21
  Requires-Dist: tqdm>=4.64
22
22
  Requires-Dist: pyyaml>=6.0
23
23
  Requires-Dist: paramiko>=3.5.1
@@ -163,10 +163,11 @@ sima-cli vulcan download --env production core main
163
163
  - `dev`: `https://artifacts.neat.paconsultings.com`
164
164
  - `staging`: `https://artifacts.stg.neat.sima.ai`
165
165
  - `production`: `https://artifacts.neat.sima.ai`
166
- - `staging` and `production` are not yet available for Vulcan downloads; use `--env dev` for now.
166
+ - `dev`, `staging`, and `production` are available for Vulcan downloads.
167
167
  - Usage:
168
- - `sima-cli vulcan --env {dev|staging|production} download [REPO] [BRANCH_OR_TAG]`
169
- - `sima-cli vulcan download --env {dev|staging|production} [REPO] [BRANCH_OR_TAG]`
168
+ - `sima-cli vulcan --env {dev|stg|staging|prd|prod|production} download [REPO] [BRANCH_OR_TAG]`
169
+ - `sima-cli vulcan download --env {dev|stg|staging|prd|prod|production} [REPO] [BRANCH_OR_TAG]`
170
+ - Shortcut flags are also available: `--dev`, `--stg`/`--staging`, and `--prd`/`--prod`.
170
171
  - If `REPO` is omitted, the CLI prompts for a repository.
171
172
  - If `BRANCH_OR_TAG` is omitted, the CLI downloads `branches.json` and prompts for a branch.
172
173
  - For automation, pass both values and add `--json` for structured output.
@@ -587,7 +588,19 @@ sima-cli selfupdate
587
588
  sima-cli selfupdate --dev
588
589
  ```
589
590
 
590
- - Update from the latest tested artifact installer. On Windows, this prints the PowerShell commands to run in a new shell.
591
+ - Update from Vulcan dev artifacts. If `--branch` is omitted, sima-cli loads `branches.json` and prompts for a branch.
592
+
593
+ ```bash
594
+ sima-cli selfupdate --stg --branch main
595
+ ```
596
+
597
+ - Update from Vulcan staging artifacts.
598
+
599
+ ```bash
600
+ sima-cli selfupdate --neat --branch main
601
+ ```
602
+
603
+ - Update from Vulcan production artifacts. Production aliases are `--prd`, `--prod`, `--neat`, and `--vulcan`.
591
604
 
592
605
  ```bash
593
606
  sima-cli selfupdate -v 0.0.46
@@ -129,10 +129,11 @@ sima-cli vulcan download --env production core main
129
129
  - `dev`: `https://artifacts.neat.paconsultings.com`
130
130
  - `staging`: `https://artifacts.stg.neat.sima.ai`
131
131
  - `production`: `https://artifacts.neat.sima.ai`
132
- - `staging` and `production` are not yet available for Vulcan downloads; use `--env dev` for now.
132
+ - `dev`, `staging`, and `production` are available for Vulcan downloads.
133
133
  - Usage:
134
- - `sima-cli vulcan --env {dev|staging|production} download [REPO] [BRANCH_OR_TAG]`
135
- - `sima-cli vulcan download --env {dev|staging|production} [REPO] [BRANCH_OR_TAG]`
134
+ - `sima-cli vulcan --env {dev|stg|staging|prd|prod|production} download [REPO] [BRANCH_OR_TAG]`
135
+ - `sima-cli vulcan download --env {dev|stg|staging|prd|prod|production} [REPO] [BRANCH_OR_TAG]`
136
+ - Shortcut flags are also available: `--dev`, `--stg`/`--staging`, and `--prd`/`--prod`.
136
137
  - If `REPO` is omitted, the CLI prompts for a repository.
137
138
  - If `BRANCH_OR_TAG` is omitted, the CLI downloads `branches.json` and prompts for a branch.
138
139
  - For automation, pass both values and add `--json` for structured output.
@@ -553,7 +554,19 @@ sima-cli selfupdate
553
554
  sima-cli selfupdate --dev
554
555
  ```
555
556
 
556
- - Update from the latest tested artifact installer. On Windows, this prints the PowerShell commands to run in a new shell.
557
+ - Update from Vulcan dev artifacts. If `--branch` is omitted, sima-cli loads `branches.json` and prompts for a branch.
558
+
559
+ ```bash
560
+ sima-cli selfupdate --stg --branch main
561
+ ```
562
+
563
+ - Update from Vulcan staging artifacts.
564
+
565
+ ```bash
566
+ sima-cli selfupdate --neat --branch main
567
+ ```
568
+
569
+ - Update from Vulcan production artifacts. Production aliases are `--prd`, `--prod`, `--neat`, and `--vulcan`.
557
570
 
558
571
  ```bash
559
572
  sima-cli selfupdate -v 0.0.46
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sima-cli"
7
- version = "2.1.7"
7
+ version = "2.1.9"
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"
@@ -23,7 +23,7 @@ classifiers = [
23
23
 
24
24
  dependencies = [
25
25
  "click>=8.0",
26
- "requests>=2.33.0",
26
+ "requests>=2.32.4,<3.0",
27
27
  "tqdm>=4.64",
28
28
  "pyyaml>=6.0",
29
29
  "paramiko>=3.5.1",
@@ -1,5 +1,5 @@
1
1
  click>=8.0
2
- requests>=2.33.0
2
+ requests>=2.32.4,<3.0
3
3
  tqdm>=4.64
4
4
  pyyaml>=6.0
5
5
  paramiko>=3.5.1
@@ -18,7 +18,7 @@ setup(
18
18
  },
19
19
  install_requires=[
20
20
  "click>=8.0,<9.0",
21
- "requests>=2.33.0,<3.0",
21
+ "requests>=2.32.4,<3.0",
22
22
  "tqdm>=4.64,<5.0",
23
23
  "InquirerPy>=0.3.4,<0.4",
24
24
  "tftpy>=0.8.6,<0.9",
@@ -1,2 +1,2 @@
1
1
  # sima_cli/__version__.py
2
- __version__ = "2.1.7"
2
+ __version__ = "2.1.9"
@@ -15,7 +15,7 @@ import base64
15
15
  import requests
16
16
  import webbrowser
17
17
  import click
18
- from typing import Dict, Optional
18
+ from typing import Dict, Iterable, Optional, Tuple
19
19
 
20
20
  from sima_cli.utils.config_loader import load_resource_config
21
21
 
@@ -24,6 +24,16 @@ from sima_cli.utils.config_loader import load_resource_config
24
24
  # ─────────────────────────────────────────────
25
25
  HOME_DIR = os.path.expanduser("~/.sima-cli")
26
26
  TOKEN_FILE = os.path.join(HOME_DIR, ".tokens.json")
27
+ COOKIE_FILE = os.path.join(HOME_DIR, ".sima-cli-cookies.txt")
28
+ CSRF_FILE = os.path.join(HOME_DIR, ".sima-cli-csrf.json")
29
+ PROD_USERINFO_AUDIENCE = "https://sima-ai.us.auth0.com/userinfo"
30
+ STAGING_USERINFO_AUDIENCE = "https://dev-d3sxf54xfkcifph2.us.auth0.com/userinfo"
31
+ USERINFO_AUDIENCE = PROD_USERINFO_AUDIENCE
32
+ LATEST_EULA_GRANT = "LatestEULA"
33
+ DOC_ACCESS_GRANT = "DocsAccess"
34
+ DOC_ACCESS_GRANT_ALIASES = (DOC_ACCESS_GRANT, "DocAccess")
35
+ PROD_DISCOURSE_URL = "https://developer.sima.ai/login"
36
+ STAGING_DISCOURSE_URL = "https://discourse-dev.sima.ai/login"
27
37
 
28
38
  # ─────────────────────────────────────────────
29
39
  # Configuration loader
@@ -81,6 +91,16 @@ def load_tokens() -> Optional[Dict]:
81
91
  return None
82
92
 
83
93
 
94
+ def clear_external_login_state():
95
+ """Remove cached external-auth state so the next login starts cleanly."""
96
+ for path in (TOKEN_FILE, COOKIE_FILE, CSRF_FILE):
97
+ if os.path.exists(path):
98
+ try:
99
+ os.remove(path)
100
+ except Exception as e:
101
+ print(f"⚠️ Failed to delete {path}: {e}")
102
+
103
+
84
104
  def is_token_valid(tokens: dict) -> bool:
85
105
  """Check if access token still valid based on expires_in field."""
86
106
  if not tokens:
@@ -131,6 +151,123 @@ def is_browser_available():
131
151
  except webbrowser.Error:
132
152
  return False
133
153
 
154
+
155
+ def _discourse_sign_in_url() -> str:
156
+ if os.getenv("USE_STAGING_DEV_PORTAL", "false").lower() in ("1", "true", "yes"):
157
+ return STAGING_DISCOURSE_URL
158
+ return PROD_DISCOURSE_URL
159
+
160
+
161
+ def _expected_userinfo_audience() -> str:
162
+ if os.getenv("USE_STAGING_DEV_PORTAL", "false").lower() in ("1", "true", "yes"):
163
+ return STAGING_USERINFO_AUDIENCE
164
+ return PROD_USERINFO_AUDIENCE
165
+
166
+
167
+ def _as_iterable(value) -> Iterable:
168
+ if value is None:
169
+ return []
170
+ if isinstance(value, (list, tuple, set)):
171
+ return value
172
+ return [value]
173
+
174
+
175
+ def _access_token_claims(tokens: dict) -> dict:
176
+ return decode_jwt_payload(tokens.get("access_token", ""))
177
+
178
+
179
+ def _access_token_has_userinfo_audience(claims: dict) -> bool:
180
+ return _expected_userinfo_audience() in _as_iterable(claims.get("aud"))
181
+
182
+
183
+ def _access_token_has_grant(claims: dict, grant: str) -> bool:
184
+ grant_names = DOC_ACCESS_GRANT_ALIASES if grant in DOC_ACCESS_GRANT_ALIASES else (grant,)
185
+ for claim_name, claim_value in claims.items():
186
+ if claim_name in ("permissions", "grants", "roles") or claim_name.endswith(("/permissions", "/grants", "/roles")):
187
+ if any(grant_name in _as_iterable(claim_value) for grant_name in grant_names):
188
+ return True
189
+
190
+ scope = claims.get("scope")
191
+ if isinstance(scope, str) and any(grant_name in scope.split() for grant_name in grant_names):
192
+ return True
193
+
194
+ return False
195
+
196
+
197
+ def access_token_has_doc_access(tokens: dict) -> bool:
198
+ return _access_token_has_grant(_access_token_claims(tokens), DOC_ACCESS_GRANT)
199
+
200
+
201
+ def access_token_has_latest_eula(tokens: dict) -> bool:
202
+ return _access_token_has_grant(_access_token_claims(tokens), LATEST_EULA_GRANT)
203
+
204
+
205
+ def _validate_access_token_requirements(tokens: dict) -> Tuple[bool, Dict[str, bool]]:
206
+ claims = _access_token_claims(tokens)
207
+ checks = {
208
+ "doc_access": _access_token_has_grant(claims, DOC_ACCESS_GRANT),
209
+ "latest_eula": _access_token_has_grant(claims, LATEST_EULA_GRANT),
210
+ "userinfo_audience": _access_token_has_userinfo_audience(claims),
211
+ }
212
+ return checks["doc_access"] and checks["latest_eula"] and checks["userinfo_audience"], checks
213
+
214
+
215
+ def _prompt_for_discourse_sign_in(checks: Dict[str, bool]) -> bool:
216
+ discourse_url = _discourse_sign_in_url()
217
+ missing = []
218
+ if not checks.get("latest_eula"):
219
+ missing.append("LatestEULA grant")
220
+ if not checks.get("userinfo_audience"):
221
+ missing.append(f"{_expected_userinfo_audience()} audience")
222
+
223
+ click.echo("")
224
+ click.secho("Developer Portal sign-in is required before sima-cli can continue.", fg="yellow")
225
+ if missing:
226
+ click.echo(f"Missing from the access token: {', '.join(missing)}.")
227
+ click.echo("Please sign in to Discourse / Developer Portal and accept the EULA if prompted.")
228
+
229
+ if is_browser_available():
230
+ click.echo(f"Opening sign-in page: {click.style(discourse_url, fg='cyan', bold=True)}")
231
+ try:
232
+ webbrowser.open(discourse_url)
233
+ except Exception:
234
+ click.echo("Browser could not be opened automatically. Open this link manually:")
235
+ click.secho(discourse_url, fg="cyan", bold=True)
236
+ else:
237
+ click.echo("Browser not available. Open this link manually:")
238
+ click.secho(discourse_url, fg="cyan", bold=True)
239
+
240
+ return click.confirm("Have you signed in to Discourse / Developer Portal?", default=True)
241
+
242
+
243
+ def _ensure_access_token_requirements(tokens: dict, auth_cfg: dict) -> Optional[dict]:
244
+ while True:
245
+ valid, checks = _validate_access_token_requirements(tokens)
246
+ if valid:
247
+ return tokens
248
+ if not checks.get("doc_access"):
249
+ return tokens
250
+
251
+ if not _prompt_for_discourse_sign_in(checks):
252
+ clear_external_login_state()
253
+ click.echo("Logged out. Run `sima-cli login` again after signing in to Developer Portal.")
254
+ return None
255
+
256
+ refresh_token = tokens.get("refresh_token")
257
+ if not refresh_token:
258
+ click.echo("Unable to refresh your access token after Developer Portal sign-in.")
259
+ clear_external_login_state()
260
+ click.echo("Logged out. Run `sima-cli login` again.")
261
+ return None
262
+
263
+ refreshed = refresh_access_token(auth_cfg, refresh_token)
264
+ if not refreshed:
265
+ clear_external_login_state()
266
+ click.echo("Logged out. Run `sima-cli login` again.")
267
+ return None
268
+
269
+ tokens = refreshed
270
+
134
271
  def request_device_code(auth_cfg):
135
272
  """Step 1: Request device code from Auth0."""
136
273
  data = {
@@ -269,16 +406,25 @@ def get_or_refresh_tokens(force=False):
269
406
  tokens = load_tokens()
270
407
 
271
408
  if tokens and is_token_valid(tokens) and not force:
272
- return tokens
409
+ tokens = _ensure_access_token_requirements(tokens, auth_cfg)
410
+ if tokens:
411
+ return tokens
412
+ return None
273
413
 
274
414
  if tokens and tokens.get("refresh_token"):
275
415
  refreshed = refresh_access_token(auth_cfg, tokens["refresh_token"])
276
416
  if refreshed:
277
- print_welcome_message(refreshed)
278
- return refreshed
417
+ refreshed = _ensure_access_token_requirements(refreshed, auth_cfg)
418
+ if refreshed:
419
+ print_welcome_message(refreshed)
420
+ return refreshed
421
+ return None
279
422
  print("⚠️ Refresh failed, falling back to new login.")
280
423
 
281
424
  new_tokens = login_auth0(auth_cfg)
425
+ new_tokens = _ensure_access_token_requirements(new_tokens, auth_cfg)
426
+ if not new_tokens:
427
+ return None
282
428
  print_welcome_message(new_tokens)
283
429
  return new_tokens
284
430
 
@@ -7,6 +7,7 @@ import subprocess
7
7
  import shutil
8
8
  import base64
9
9
  import hashlib
10
+ import webbrowser
10
11
 
11
12
  from typing import Optional
12
13
  from http.cookiejar import MozillaCookieJar
@@ -17,9 +18,14 @@ from sima_cli.utils.env import is_sima_board
17
18
  from sima_cli.auth.auth0 import (
18
19
  decode_jwt_payload,
19
20
  extract_email,
21
+ access_token_has_doc_access,
22
+ access_token_has_latest_eula,
23
+ get_auth_config,
20
24
  get_or_refresh_tokens,
21
25
  get_cached_access_token,
26
+ is_browser_available,
22
27
  load_tokens,
28
+ refresh_access_token,
23
29
  )
24
30
 
25
31
  HOME_DIR = os.path.expanduser("~/.sima-cli")
@@ -43,6 +49,7 @@ else:
43
49
 
44
50
  # Derived endpoints
45
51
  LOGIN_URL = f"{DEV_PORTAL}/session"
52
+ DEV_PORTAL_LOGIN_URL = f"{DEV_PORTAL}/login"
46
53
  DUMMY_CHECK_URL = f"{DOCS_PORTAL}/pkg_downloads/validation"
47
54
  ACCESS_REQUEST_FORM_URL = "https://www2.sima.ai/l/1041271/2025-05-05/37bndg"
48
55
  USER_INFO_CLAIM = "https://auth.sima.ai/user_info"
@@ -61,14 +68,39 @@ HEADERS = {
61
68
  "sec-ch-ua-platform": '"macOS"',
62
69
  }
63
70
 
71
+
72
+ def _prompt_manual_developer_portal_login(confirm_completion: bool = False) -> bool:
73
+ click.secho(
74
+ f"\nOpen this page to accept EULA to proceed, press Y when you are done:\n{DEV_PORTAL_LOGIN_URL}\n",
75
+ fg="green",
76
+ )
77
+ if confirm_completion:
78
+ return click.confirm("Have you accepted the EULA?", default=True)
79
+ return False
80
+
81
+
82
+ def _open_developer_portal_login_page(confirm_manual_completion: bool = False) -> bool:
83
+ if not is_browser_available():
84
+ return _prompt_manual_developer_portal_login(confirm_manual_completion)
85
+
86
+ try:
87
+ opened = webbrowser.open(DEV_PORTAL_LOGIN_URL)
88
+ except Exception:
89
+ opened = False
90
+
91
+ if not opened:
92
+ return _prompt_manual_developer_portal_login(confirm_manual_completion)
93
+ return True
94
+
95
+
64
96
  def _handle_eula_flow(session: requests.Session, username: str, domain: str) -> bool:
65
97
  try:
66
98
  click.echo("\n📄 To continue, you must accept the End-User License Agreement (EULA).")
67
- click.echo("👉 Please sign in to Developer Portal on your browser, then open the following URL to accept the EULA:")
68
- click.echo("👉 If you were not prompted with the EULA acceptance popup, please open the URL in the incogniton browser.")
69
- click.echo(f"\n {DUMMY_CHECK_URL}\n")
99
+ click.echo("👉 Please sign in to Developer Portal in your browser and accept the EULA if prompted.")
100
+ click.echo("👉 If you were not prompted with the EULA acceptance popup, try opening the page in an incognito browser.")
101
+ _open_developer_portal_login_page()
70
102
 
71
- if not click.confirm("✅ Have you completed the EULA form in your browser?", default=True):
103
+ if not click.confirm("✅ Have you signed in to Developer Portal and accepted the EULA?", default=True):
72
104
  click.echo("❌ EULA acceptance is required to continue.")
73
105
  return False
74
106
 
@@ -192,15 +224,20 @@ def _show_access_request_pending_message(already_submitted: bool = False):
192
224
  click.secho("✅ Your access request has already been submitted.", fg="green")
193
225
  else:
194
226
  click.secho("✅ Your access request has been submitted.", fg="green")
227
+
228
+
229
+ def _show_limited_access_pending_message():
230
+ click.secho("✅ You are signed in with limited Developer Portal access.", fg="green")
195
231
  console.print(
196
232
  Panel(
197
233
  "\n".join(
198
234
  [
199
- "SiMa is reviewing your request and will grant access shortly.",
235
+ "SiMa is reviewing your account and will grant full access shortly.",
200
236
  "Please look out for an email from marketing@marketing.sima.ai.",
201
- "Once access is granted, run `sima-cli login` again.",
202
237
  ]
203
238
  ),
239
+ title="Notice",
240
+ title_align="center",
204
241
  border_style="yellow",
205
242
  style="yellow",
206
243
  expand=False,
@@ -208,27 +245,26 @@ def _show_access_request_pending_message(already_submitted: bool = False):
208
245
  )
209
246
 
210
247
 
211
- def _show_access_request_info_panel():
212
- console.print(
213
- Panel(
214
- "\n".join(
215
- [
216
- "Welcome to the SiMa.ai Developer Portal.",
217
- "",
218
- "To download digital assets from the Developer Portal, "
219
- "SiMa's business team needs to grant access after you "
220
- "provide a few additional details, including your project goal.",
221
- "",
222
- "Once approved, you will receive an email from "
223
- "marketing@marketing.sima.ai. Please check your email "
224
- "client's spam filter.",
225
- ]
226
- ),
227
- title="Developer Portal Access Request",
228
- border_style="yellow",
229
- expand=False,
230
- )
231
- )
248
+ def _prompt_eula_acceptance_after_access_request() -> bool:
249
+ click.echo("\n📄 To continue, accept the End-User License Agreement (EULA).")
250
+ click.echo("👉 Please sign in to Developer Portal in your browser and accept the EULA if prompted.")
251
+ _open_developer_portal_login_page()
252
+ if not click.confirm("✅ Have you signed in to Developer Portal and accepted the EULA?", default=True):
253
+ return False
254
+
255
+ tokens = load_tokens() or {}
256
+ refresh_token = tokens.get("refresh_token")
257
+ if not refresh_token:
258
+ click.secho("⚠️ Unable to refresh your access token: missing refresh token.", fg="yellow")
259
+ return False
260
+
261
+ click.echo("Refreshing access token after EULA acceptance...")
262
+ refreshed = refresh_access_token(get_auth_config(), refresh_token)
263
+ if not refreshed:
264
+ click.secho("⚠️ Unable to refresh your access token after EULA acceptance.", fg="yellow")
265
+ return False
266
+
267
+ return True
232
268
 
233
269
 
234
270
  def _submit_access_request() -> bool:
@@ -243,19 +279,10 @@ def _submit_access_request() -> bool:
243
279
  )
244
280
  return False
245
281
 
246
- if _has_submitted_access_request(claims):
247
- _show_access_request_pending_message(already_submitted=True)
248
- _logout_external_credentials()
249
- return False
250
-
251
- _show_access_request_info_panel()
252
- message = click.prompt(
253
- click.style("Please briefly describe your project", fg="yellow"),
254
- type=str,
255
- ).strip()
256
- payload = _build_access_request_payload(claims, message)
282
+ payload = _build_access_request_payload(claims, "sima-cli sign up request")
257
283
 
258
284
  try:
285
+ click.echo("Submitting Developer Portal access request...")
259
286
  response = requests.post(ACCESS_REQUEST_FORM_URL, data=payload, timeout=15)
260
287
  response.raise_for_status()
261
288
  except requests.RequestException as e:
@@ -264,7 +291,8 @@ def _submit_access_request() -> bool:
264
291
 
265
292
  _mark_access_request_submitted(claims)
266
293
  _show_access_request_pending_message()
267
- _logout_external_credentials()
294
+ if not access_token_has_latest_eula(load_tokens() or {}):
295
+ _prompt_eula_acceptance_after_access_request()
268
296
  return False
269
297
 
270
298
 
@@ -355,7 +383,12 @@ def login_external(force=False, loginDocker=True):
355
383
  if _ACCESS_REQUEST_HANDLED:
356
384
  return None
357
385
 
358
- get_or_refresh_tokens(force=force)
386
+ tokens = get_or_refresh_tokens(force=force)
387
+ if not tokens:
388
+ return None
389
+ if not access_token_has_doc_access(tokens):
390
+ return _submit_access_request()
391
+
359
392
  session, valid = validate_session()
360
393
  if valid:
361
394
  if loginDocker:
@@ -30,7 +30,13 @@ from sima_cli.install.registry import register_packages_commands
30
30
  from sima_cli.upgrade.selfupdate import register_selfupdate_command
31
31
  from sima_cli.playbooks import register_playbook_commands
32
32
  from sima_cli.vulcan import register_vulcan_commands
33
- from sima_cli.vulcan.commands import ENV_BASE_URLS, install_vulcan_package
33
+ from sima_cli.vulcan.commands import (
34
+ ENV_METAVAR,
35
+ _environment_shortcut_options,
36
+ _normalize_environment_option,
37
+ _resolve_environment,
38
+ install_vulcan_package,
39
+ )
34
40
 
35
41
  def _configure_stdio_errors() -> None:
36
42
  for stream in (getattr(sys, "stdout", None), getattr(sys, "stderr", None)):
@@ -505,21 +511,29 @@ ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
505
511
  @click.argument("component", required=False)
506
512
  @click.option("-v", "--version", help="SDK version (required for SDK-dependent components unless --metadata is provided)")
507
513
  @click.option("-m", "--mirror", help="URL to a metadata.json file for generic installation")
508
- @click.option("-t", "--tag", help="Tag of the package. With --vulcan, metadata variant type such as minimum.")
509
- @click.option("--vulcan", "use_vulcan", is_flag=True, help="Install from Vulcan artifacts using the Vulcan package resolver.")
514
+ @click.option("-t", "--tag", help="Tag of the package. With --neat, metadata variant type such as minimum.")
515
+ @click.option("--neat", "use_neat", is_flag=True, help="Install from Neat artifacts using the Neat package resolver.")
516
+ @click.option(
517
+ "--vulcan",
518
+ "use_vulcan",
519
+ is_flag=True,
520
+ help="Install from Neat/Vulcan artifacts. Compatibility alias for --neat.",
521
+ )
522
+ @_environment_shortcut_options
510
523
  @click.option(
511
524
  "--env",
512
525
  "vulcan_environment",
513
- type=click.Choice(sorted(ENV_BASE_URLS), case_sensitive=False),
526
+ metavar=ENV_METAVAR,
527
+ callback=_normalize_environment_option,
514
528
  default=None,
515
- help="Vulcan artifact environment. Used with --vulcan. Defaults to production.",
529
+ help="Neat artifact environment. Used with --neat or --vulcan. Defaults to production.",
516
530
  )
517
531
  @click.option(
518
532
  "--base-url",
519
533
  "vulcan_base_url",
520
534
  default=None,
521
- envvar="SIMA_VULCAN_BASE_URL",
522
- help="Override the Vulcan artifact base URL. Used with --vulcan.",
535
+ envvar="SIMA_NEAT_BASE_URL",
536
+ help="Override the Neat artifact base URL. Used with --neat or --vulcan.",
523
537
  )
524
538
  @click.option(
525
539
  "-d",
@@ -527,9 +541,9 @@ ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
527
541
  default=".",
528
542
  show_default=True,
529
543
  type=click.Path(file_okay=False, dir_okay=True, path_type=str),
530
- help="Directory where Vulcan package resources are downloaded and installed. Used with --vulcan.",
544
+ help="Directory where Neat package resources are downloaded and installed. Used with --neat or --vulcan.",
531
545
  )
532
- @click.option("--json", "json_output", is_flag=True, help="With --vulcan, print resolved metadata URL and exit.")
546
+ @click.option("--json", "json_output", is_flag=True, help="With --neat or --vulcan, print resolved metadata URL and exit.")
533
547
  @click.option(
534
548
  "-f",
535
549
  "--force",
@@ -538,7 +552,21 @@ ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
538
552
  help="Force installation even if compatibility checks fail.",
539
553
  )
540
554
  @click.pass_context
541
- def install_cmd(ctx, component, version, mirror, tag, use_vulcan, vulcan_environment, vulcan_base_url, install_dir, json_output, force):
555
+ def install_cmd(
556
+ ctx,
557
+ component,
558
+ version,
559
+ mirror,
560
+ tag,
561
+ use_neat,
562
+ use_vulcan,
563
+ vulcan_environment,
564
+ environment_flag,
565
+ vulcan_base_url,
566
+ install_dir,
567
+ json_output,
568
+ force,
569
+ ):
542
570
  """
543
571
  Install SiMa packages.
544
572
 
@@ -573,15 +601,18 @@ def install_cmd(ctx, component, version, mirror, tag, use_vulcan, vulcan_environ
573
601
  """
574
602
  internal = ctx.obj.get("internal", False)
575
603
 
576
- if use_vulcan:
604
+ use_neat_resolver = use_neat or use_vulcan
605
+ if use_neat_resolver:
606
+ if use_neat and use_vulcan:
607
+ raise click.ClickException("Use only one of --neat or --vulcan.")
577
608
  if mirror:
578
- raise click.ClickException("--mirror cannot be used with --vulcan.")
609
+ raise click.ClickException("--mirror cannot be used with --neat.")
579
610
  if not component:
580
- raise click.ClickException("You must specify a Vulcan target when using --vulcan.")
611
+ raise click.ClickException("You must specify a Neat target when using --neat.")
581
612
  install_vulcan_package(
582
613
  target=component,
583
- environment=vulcan_environment or "production",
584
- base_url=vulcan_base_url,
614
+ environment=_resolve_environment(vulcan_environment, environment_flag),
615
+ base_url=vulcan_base_url or os.getenv("SIMA_VULCAN_BASE_URL"),
585
616
  package_type=tag,
586
617
  install_dir=install_dir,
587
618
  force=force,