inspire-skill 2.0.0__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 (231) hide show
  1. inspire_skill-2.0.0/PKG-INFO +30 -0
  2. inspire_skill-2.0.0/inspire/__init__.py +3 -0
  3. inspire_skill-2.0.0/inspire/accounts/__init__.py +44 -0
  4. inspire_skill-2.0.0/inspire/accounts/storage.py +141 -0
  5. inspire_skill-2.0.0/inspire/bridge/__init__.py +1 -0
  6. inspire_skill-2.0.0/inspire/bridge/forge.py +713 -0
  7. inspire_skill-2.0.0/inspire/bridge/tunnel/__init__.py +66 -0
  8. inspire_skill-2.0.0/inspire/bridge/tunnel/config.py +115 -0
  9. inspire_skill-2.0.0/inspire/bridge/tunnel/models.py +197 -0
  10. inspire_skill-2.0.0/inspire/bridge/tunnel/rtunnel.py +126 -0
  11. inspire_skill-2.0.0/inspire/bridge/tunnel/scp.py +110 -0
  12. inspire_skill-2.0.0/inspire/bridge/tunnel/ssh.py +230 -0
  13. inspire_skill-2.0.0/inspire/bridge/tunnel/ssh_exec.py +312 -0
  14. inspire_skill-2.0.0/inspire/cli/__init__.py +5 -0
  15. inspire_skill-2.0.0/inspire/cli/commands/__init__.py +35 -0
  16. inspire_skill-2.0.0/inspire/cli/commands/account/__init__.py +32 -0
  17. inspire_skill-2.0.0/inspire/cli/commands/account/add.py +206 -0
  18. inspire_skill-2.0.0/inspire/cli/commands/account/current.py +26 -0
  19. inspire_skill-2.0.0/inspire/cli/commands/account/list_cmd.py +22 -0
  20. inspire_skill-2.0.0/inspire/cli/commands/account/remove.py +30 -0
  21. inspire_skill-2.0.0/inspire/cli/commands/account/use.py +23 -0
  22. inspire_skill-2.0.0/inspire/cli/commands/config/__init__.py +23 -0
  23. inspire_skill-2.0.0/inspire/cli/commands/config/check.py +324 -0
  24. inspire_skill-2.0.0/inspire/cli/commands/config/context.py +230 -0
  25. inspire_skill-2.0.0/inspire/cli/commands/config/env_cmd.py +95 -0
  26. inspire_skill-2.0.0/inspire/cli/commands/config/show.py +309 -0
  27. inspire_skill-2.0.0/inspire/cli/commands/hpc/__init__.py +26 -0
  28. inspire_skill-2.0.0/inspire/cli/commands/hpc/hpc_commands.py +580 -0
  29. inspire_skill-2.0.0/inspire/cli/commands/hpc/hpc_events.py +83 -0
  30. inspire_skill-2.0.0/inspire/cli/commands/hpc/hpc_metrics.py +57 -0
  31. inspire_skill-2.0.0/inspire/cli/commands/image/__init__.py +51 -0
  32. inspire_skill-2.0.0/inspire/cli/commands/image/image_commands.py +857 -0
  33. inspire_skill-2.0.0/inspire/cli/commands/init/__init__.py +20 -0
  34. inspire_skill-2.0.0/inspire/cli/commands/init/discover.py +2722 -0
  35. inspire_skill-2.0.0/inspire/cli/commands/init/env_detect.py +162 -0
  36. inspire_skill-2.0.0/inspire/cli/commands/init/errors.py +35 -0
  37. inspire_skill-2.0.0/inspire/cli/commands/init/init_cmd.py +386 -0
  38. inspire_skill-2.0.0/inspire/cli/commands/init/json_report.py +95 -0
  39. inspire_skill-2.0.0/inspire/cli/commands/init/templates.py +274 -0
  40. inspire_skill-2.0.0/inspire/cli/commands/init/toml_helpers.py +96 -0
  41. inspire_skill-2.0.0/inspire/cli/commands/job/__init__.py +32 -0
  42. inspire_skill-2.0.0/inspire/cli/commands/job/job_commands.py +809 -0
  43. inspire_skill-2.0.0/inspire/cli/commands/job/job_create.py +309 -0
  44. inspire_skill-2.0.0/inspire/cli/commands/job/job_deps.py +23 -0
  45. inspire_skill-2.0.0/inspire/cli/commands/job/job_events.py +114 -0
  46. inspire_skill-2.0.0/inspire/cli/commands/job/job_logs.py +1171 -0
  47. inspire_skill-2.0.0/inspire/cli/commands/job/job_metrics.py +56 -0
  48. inspire_skill-2.0.0/inspire/cli/commands/model/__init__.py +25 -0
  49. inspire_skill-2.0.0/inspire/cli/commands/model/model_commands.py +192 -0
  50. inspire_skill-2.0.0/inspire/cli/commands/notebook/__init__.py +87 -0
  51. inspire_skill-2.0.0/inspire/cli/commands/notebook/connection_test_cmd.py +110 -0
  52. inspire_skill-2.0.0/inspire/cli/commands/notebook/connections_cmd.py +122 -0
  53. inspire_skill-2.0.0/inspire/cli/commands/notebook/forget_cmd.py +60 -0
  54. inspire_skill-2.0.0/inspire/cli/commands/notebook/notebook_commands.py +966 -0
  55. inspire_skill-2.0.0/inspire/cli/commands/notebook/notebook_create_flow.py +1352 -0
  56. inspire_skill-2.0.0/inspire/cli/commands/notebook/notebook_events.py +98 -0
  57. inspire_skill-2.0.0/inspire/cli/commands/notebook/notebook_lifecycle.py +116 -0
  58. inspire_skill-2.0.0/inspire/cli/commands/notebook/notebook_lookup.py +520 -0
  59. inspire_skill-2.0.0/inspire/cli/commands/notebook/notebook_metrics.py +76 -0
  60. inspire_skill-2.0.0/inspire/cli/commands/notebook/notebook_presenters.py +106 -0
  61. inspire_skill-2.0.0/inspire/cli/commands/notebook/notebook_ssh_flow.py +961 -0
  62. inspire_skill-2.0.0/inspire/cli/commands/notebook/refresh_cmd.py +144 -0
  63. inspire_skill-2.0.0/inspire/cli/commands/notebook/remote_exec.py +783 -0
  64. inspire_skill-2.0.0/inspire/cli/commands/notebook/remote_scp.py +172 -0
  65. inspire_skill-2.0.0/inspire/cli/commands/notebook/remote_shell.py +264 -0
  66. inspire_skill-2.0.0/inspire/cli/commands/notebook/set_default_cmd.py +54 -0
  67. inspire_skill-2.0.0/inspire/cli/commands/notebook/top.py +408 -0
  68. inspire_skill-2.0.0/inspire/cli/commands/project/__init__.py +32 -0
  69. inspire_skill-2.0.0/inspire/cli/commands/project/project_commands.py +645 -0
  70. inspire_skill-2.0.0/inspire/cli/commands/ray/__init__.py +39 -0
  71. inspire_skill-2.0.0/inspire/cli/commands/ray/ray_commands.py +865 -0
  72. inspire_skill-2.0.0/inspire/cli/commands/resources/__init__.py +20 -0
  73. inspire_skill-2.0.0/inspire/cli/commands/resources/resources_list.py +858 -0
  74. inspire_skill-2.0.0/inspire/cli/commands/resources/resources_nodes.py +220 -0
  75. inspire_skill-2.0.0/inspire/cli/commands/resources/resources_specs.py +333 -0
  76. inspire_skill-2.0.0/inspire/cli/commands/run.py +391 -0
  77. inspire_skill-2.0.0/inspire/cli/commands/serving/__init__.py +32 -0
  78. inspire_skill-2.0.0/inspire/cli/commands/serving/serving_commands.py +291 -0
  79. inspire_skill-2.0.0/inspire/cli/commands/serving/serving_metrics.py +48 -0
  80. inspire_skill-2.0.0/inspire/cli/commands/update.py +317 -0
  81. inspire_skill-2.0.0/inspire/cli/commands/user/__init__.py +21 -0
  82. inspire_skill-2.0.0/inspire/cli/commands/user/user_commands.py +174 -0
  83. inspire_skill-2.0.0/inspire/cli/context.py +34 -0
  84. inspire_skill-2.0.0/inspire/cli/formatters/__init__.py +24 -0
  85. inspire_skill-2.0.0/inspire/cli/formatters/human_formatter.py +405 -0
  86. inspire_skill-2.0.0/inspire/cli/formatters/json_formatter.py +47 -0
  87. inspire_skill-2.0.0/inspire/cli/logging_setup.py +204 -0
  88. inspire_skill-2.0.0/inspire/cli/main.py +143 -0
  89. inspire_skill-2.0.0/inspire/cli/utils/__init__.py +7 -0
  90. inspire_skill-2.0.0/inspire/cli/utils/auth.py +81 -0
  91. inspire_skill-2.0.0/inspire/cli/utils/compute_group_autoselect.py +44 -0
  92. inspire_skill-2.0.0/inspire/cli/utils/errors.py +55 -0
  93. inspire_skill-2.0.0/inspire/cli/utils/events.py +238 -0
  94. inspire_skill-2.0.0/inspire/cli/utils/id_resolver.py +259 -0
  95. inspire_skill-2.0.0/inspire/cli/utils/job_cache.py +11 -0
  96. inspire_skill-2.0.0/inspire/cli/utils/job_cache_api.py +175 -0
  97. inspire_skill-2.0.0/inspire/cli/utils/job_cli.py +150 -0
  98. inspire_skill-2.0.0/inspire/cli/utils/job_submit.py +224 -0
  99. inspire_skill-2.0.0/inspire/cli/utils/metrics_plot.py +286 -0
  100. inspire_skill-2.0.0/inspire/cli/utils/metrics_shared.py +614 -0
  101. inspire_skill-2.0.0/inspire/cli/utils/notebook_cli.py +45 -0
  102. inspire_skill-2.0.0/inspire/cli/utils/notebook_post_start.py +183 -0
  103. inspire_skill-2.0.0/inspire/cli/utils/output.py +57 -0
  104. inspire_skill-2.0.0/inspire/cli/utils/profile.py +40 -0
  105. inspire_skill-2.0.0/inspire/cli/utils/spec_resolver.py +108 -0
  106. inspire_skill-2.0.0/inspire/cli/utils/tunnel_reconnect.py +237 -0
  107. inspire_skill-2.0.0/inspire/cli/utils/update_notice.py +197 -0
  108. inspire_skill-2.0.0/inspire/compute_groups.py +64 -0
  109. inspire_skill-2.0.0/inspire/config/__init__.py +77 -0
  110. inspire_skill-2.0.0/inspire/config/env.py +75 -0
  111. inspire_skill-2.0.0/inspire/config/load.py +95 -0
  112. inspire_skill-2.0.0/inspire/config/load_account_layer.py +166 -0
  113. inspire_skill-2.0.0/inspire/config/load_accounts.py +58 -0
  114. inspire_skill-2.0.0/inspire/config/load_common.py +237 -0
  115. inspire_skill-2.0.0/inspire/config/load_env.py +152 -0
  116. inspire_skill-2.0.0/inspire/config/load_layers.py +104 -0
  117. inspire_skill-2.0.0/inspire/config/load_runtime.py +106 -0
  118. inspire_skill-2.0.0/inspire/config/models.py +209 -0
  119. inspire_skill-2.0.0/inspire/config/options/__init__.py +1 -0
  120. inspire_skill-2.0.0/inspire/config/options/api.py +169 -0
  121. inspire_skill-2.0.0/inspire/config/options/forge.py +73 -0
  122. inspire_skill-2.0.0/inspire/config/options/infra.py +145 -0
  123. inspire_skill-2.0.0/inspire/config/options/project.py +99 -0
  124. inspire_skill-2.0.0/inspire/config/rtunnel_defaults.py +63 -0
  125. inspire_skill-2.0.0/inspire/config/schema.py +92 -0
  126. inspire_skill-2.0.0/inspire/config/schema_categories.py +19 -0
  127. inspire_skill-2.0.0/inspire/config/schema_models.py +76 -0
  128. inspire_skill-2.0.0/inspire/config/ssh_runtime.py +66 -0
  129. inspire_skill-2.0.0/inspire/config/toml.py +45 -0
  130. inspire_skill-2.0.0/inspire/config/workspaces.py +91 -0
  131. inspire_skill-2.0.0/inspire/platform/__init__.py +1 -0
  132. inspire_skill-2.0.0/inspire/platform/openapi/__init__.py +40 -0
  133. inspire_skill-2.0.0/inspire/platform/openapi/auth.py +39 -0
  134. inspire_skill-2.0.0/inspire/platform/openapi/client.py +358 -0
  135. inspire_skill-2.0.0/inspire/platform/openapi/endpoints.py +62 -0
  136. inspire_skill-2.0.0/inspire/platform/openapi/errors.py +73 -0
  137. inspire_skill-2.0.0/inspire/platform/openapi/hpc_jobs.py +193 -0
  138. inspire_skill-2.0.0/inspire/platform/openapi/http.py +167 -0
  139. inspire_skill-2.0.0/inspire/platform/openapi/inference_servings.py +146 -0
  140. inspire_skill-2.0.0/inspire/platform/openapi/jobs.py +161 -0
  141. inspire_skill-2.0.0/inspire/platform/openapi/models.py +57 -0
  142. inspire_skill-2.0.0/inspire/platform/openapi/nodes.py +48 -0
  143. inspire_skill-2.0.0/inspire/platform/openapi/resources.py +222 -0
  144. inspire_skill-2.0.0/inspire/platform/web/__init__.py +1 -0
  145. inspire_skill-2.0.0/inspire/platform/web/browser_api/__init__.py +201 -0
  146. inspire_skill-2.0.0/inspire/platform/web/browser_api/availability/__init__.py +22 -0
  147. inspire_skill-2.0.0/inspire/platform/web/browser_api/availability/api.py +278 -0
  148. inspire_skill-2.0.0/inspire/platform/web/browser_api/availability/models.py +45 -0
  149. inspire_skill-2.0.0/inspire/platform/web/browser_api/availability/select.py +142 -0
  150. inspire_skill-2.0.0/inspire/platform/web/browser_api/core.py +173 -0
  151. inspire_skill-2.0.0/inspire/platform/web/browser_api/hpc_jobs.py +183 -0
  152. inspire_skill-2.0.0/inspire/platform/web/browser_api/images.py +392 -0
  153. inspire_skill-2.0.0/inspire/platform/web/browser_api/jobs.py +318 -0
  154. inspire_skill-2.0.0/inspire/platform/web/browser_api/metrics.py +235 -0
  155. inspire_skill-2.0.0/inspire/platform/web/browser_api/models.py +163 -0
  156. inspire_skill-2.0.0/inspire/platform/web/browser_api/notebooks.py +772 -0
  157. inspire_skill-2.0.0/inspire/platform/web/browser_api/playwright_notebooks.py +342 -0
  158. inspire_skill-2.0.0/inspire/platform/web/browser_api/projects.py +392 -0
  159. inspire_skill-2.0.0/inspire/platform/web/browser_api/ray_jobs.py +479 -0
  160. inspire_skill-2.0.0/inspire/platform/web/browser_api/rtunnel.py +2209 -0
  161. inspire_skill-2.0.0/inspire/platform/web/browser_api/servings.py +201 -0
  162. inspire_skill-2.0.0/inspire/platform/web/browser_api/users.py +96 -0
  163. inspire_skill-2.0.0/inspire/platform/web/browser_api/workspaces.py +64 -0
  164. inspire_skill-2.0.0/inspire/platform/web/resources.py +230 -0
  165. inspire_skill-2.0.0/inspire/platform/web/session/__init__.py +238 -0
  166. inspire_skill-2.0.0/inspire/platform/web/session/auth.py +366 -0
  167. inspire_skill-2.0.0/inspire/platform/web/session/browser_client.py +173 -0
  168. inspire_skill-2.0.0/inspire/platform/web/session/models.py +155 -0
  169. inspire_skill-2.0.0/inspire/platform/web/session/proxy.py +126 -0
  170. inspire_skill-2.0.0/inspire/platform/web/session/requests.py +61 -0
  171. inspire_skill-2.0.0/inspire/platform/web/session/workspace.py +167 -0
  172. inspire_skill-2.0.0/inspire_skill.egg-info/PKG-INFO +30 -0
  173. inspire_skill-2.0.0/inspire_skill.egg-info/SOURCES.txt +229 -0
  174. inspire_skill-2.0.0/inspire_skill.egg-info/dependency_links.txt +1 -0
  175. inspire_skill-2.0.0/inspire_skill.egg-info/entry_points.txt +2 -0
  176. inspire_skill-2.0.0/inspire_skill.egg-info/requires.txt +13 -0
  177. inspire_skill-2.0.0/inspire_skill.egg-info/top_level.txt +1 -0
  178. inspire_skill-2.0.0/pyproject.toml +88 -0
  179. inspire_skill-2.0.0/setup.cfg +4 -0
  180. inspire_skill-2.0.0/tests/test_account_commands.py +375 -0
  181. inspire_skill-2.0.0/tests/test_bridge_exec.py +1634 -0
  182. inspire_skill-2.0.0/tests/test_bridge_scp.py +389 -0
  183. inspire_skill-2.0.0/tests/test_browser_api_metrics.py +320 -0
  184. inspire_skill-2.0.0/tests/test_browser_api_ray_jobs.py +511 -0
  185. inspire_skill-2.0.0/tests/test_browser_api_servings.py +205 -0
  186. inspire_skill-2.0.0/tests/test_cli_commands.py +2585 -0
  187. inspire_skill-2.0.0/tests/test_cli_smoke.py +83 -0
  188. inspire_skill-2.0.0/tests/test_compute_group_autoselect.py +236 -0
  189. inspire_skill-2.0.0/tests/test_config_files.py +1528 -0
  190. inspire_skill-2.0.0/tests/test_cpu_compute_group_fixes.py +198 -0
  191. inspire_skill-2.0.0/tests/test_debug_logging.py +121 -0
  192. inspire_skill-2.0.0/tests/test_error_utils.py +125 -0
  193. inspire_skill-2.0.0/tests/test_forge_utils.py +256 -0
  194. inspire_skill-2.0.0/tests/test_hpc_commands.py +322 -0
  195. inspire_skill-2.0.0/tests/test_id_resolver.py +170 -0
  196. inspire_skill-2.0.0/tests/test_image_commands.py +1111 -0
  197. inspire_skill-2.0.0/tests/test_main_help.py +31 -0
  198. inspire_skill-2.0.0/tests/test_notebook_alias_defaults.py +220 -0
  199. inspire_skill-2.0.0/tests/test_notebook_commands.py +1499 -0
  200. inspire_skill-2.0.0/tests/test_notebook_create_flow.py +645 -0
  201. inspire_skill-2.0.0/tests/test_notebook_jupyter.py +240 -0
  202. inspire_skill-2.0.0/tests/test_notebook_metrics.py +391 -0
  203. inspire_skill-2.0.0/tests/test_notebook_post_start.py +91 -0
  204. inspire_skill-2.0.0/tests/test_notebook_rtunnel_commands.py +255 -0
  205. inspire_skill-2.0.0/tests/test_notebook_rtunnel_flow.py +195 -0
  206. inspire_skill-2.0.0/tests/test_notebook_rtunnel_helpers.py +1040 -0
  207. inspire_skill-2.0.0/tests/test_notebook_rtunnel_probe.py +196 -0
  208. inspire_skill-2.0.0/tests/test_notebook_rtunnel_verify.py +28 -0
  209. inspire_skill-2.0.0/tests/test_notebook_top.py +197 -0
  210. inspire_skill-2.0.0/tests/test_notebook_wait.py +114 -0
  211. inspire_skill-2.0.0/tests/test_openapi_client_config.py +86 -0
  212. inspire_skill-2.0.0/tests/test_openapi_hpc_jobs.py +133 -0
  213. inspire_skill-2.0.0/tests/test_openapi_inference_servings.py +225 -0
  214. inspire_skill-2.0.0/tests/test_openapi_jobs.py +111 -0
  215. inspire_skill-2.0.0/tests/test_openapi_proxy_config.py +118 -0
  216. inspire_skill-2.0.0/tests/test_openapi_resource_manager.py +86 -0
  217. inspire_skill-2.0.0/tests/test_project_list_commands.py +252 -0
  218. inspire_skill-2.0.0/tests/test_project_selection.py +481 -0
  219. inspire_skill-2.0.0/tests/test_resource_metrics_variants.py +293 -0
  220. inspire_skill-2.0.0/tests/test_resources_specs_command.py +352 -0
  221. inspire_skill-2.0.0/tests/test_rtunnel_binary.py +75 -0
  222. inspire_skill-2.0.0/tests/test_rtunnel_defaults.py +117 -0
  223. inspire_skill-2.0.0/tests/test_serving_commands.py +51 -0
  224. inspire_skill-2.0.0/tests/test_ssh_exec.py +243 -0
  225. inspire_skill-2.0.0/tests/test_ssh_runtime_config.py +195 -0
  226. inspire_skill-2.0.0/tests/test_tunnel_reconnect.py +130 -0
  227. inspire_skill-2.0.0/tests/test_utils.py +894 -0
  228. inspire_skill-2.0.0/tests/test_web_config_resolution.py +53 -0
  229. inspire_skill-2.0.0/tests/test_web_session.py +767 -0
  230. inspire_skill-2.0.0/tests/test_web_session_proxy.py +112 -0
  231. inspire_skill-2.0.0/tests/test_workspace_selection.py +83 -0
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: inspire-skill
3
+ Version: 2.0.0
4
+ Summary: CLI for the Inspire training platform: notebook lifecycle, distributed/HPC job submission, SSH tunneling, image and resource ops
5
+ Author: Inspire Platform Team
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/realZillionX/InspireSkill
8
+ Project-URL: Repository, https://github.com/realZillionX/InspireSkill
9
+ Keywords: inspire,hpc,training,cli,machine-learning
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: requests>=2.28.0
22
+ Requires-Dist: click>=8.0.0
23
+ Requires-Dist: playwright>=1.57.0
24
+ Requires-Dist: matplotlib>=3.8.0
25
+ Requires-Dist: tomli>=1.2.0; python_version < "3.11"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
29
+ Requires-Dist: black>=23.0.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.0.280; extra == "dev"
@@ -0,0 +1,3 @@
1
+ """Inspire Training Platform SDK."""
2
+
3
+ __version__ = "2.0.0"
@@ -0,0 +1,44 @@
1
+ """Account storage — one account = one isolated directory on disk.
2
+
3
+ See :mod:`inspire.accounts.storage` for the rationale and layout. This
4
+ package re-exports the storage helpers so callers can do
5
+ ``from inspire.accounts import current_account, account_config_path``.
6
+ """
7
+
8
+ from inspire.accounts.storage import (
9
+ CONFIG_FILENAME,
10
+ AccountError,
11
+ account_config_path,
12
+ account_dir,
13
+ account_exists,
14
+ accounts_dir,
15
+ clear_current_account,
16
+ create_account,
17
+ current_account,
18
+ current_file,
19
+ ensure_inspire_home,
20
+ inspire_home,
21
+ list_accounts,
22
+ remove_account,
23
+ set_current_account,
24
+ validate_name,
25
+ )
26
+
27
+ __all__ = [
28
+ "CONFIG_FILENAME",
29
+ "AccountError",
30
+ "account_config_path",
31
+ "account_dir",
32
+ "account_exists",
33
+ "accounts_dir",
34
+ "clear_current_account",
35
+ "create_account",
36
+ "current_account",
37
+ "current_file",
38
+ "ensure_inspire_home",
39
+ "inspire_home",
40
+ "list_accounts",
41
+ "remove_account",
42
+ "set_current_account",
43
+ "validate_name",
44
+ ]
@@ -0,0 +1,141 @@
1
+ """File-based account storage.
2
+
3
+ One account = one isolated directory under ``~/.inspire/accounts/<name>/``.
4
+ The active account is named in a single line at ``~/.inspire/current``. No
5
+ layered merge, no ``[accounts."<name>"]`` sections, no env-var precedence
6
+ chains — every account's state (config.toml, bridges.json, web_session.json,
7
+ rtunnel cache) lives inside its own directory and never leaks into another.
8
+
9
+ All callers must resolve per-account paths through helpers here rather than
10
+ hard-coding ``~/.inspire/accounts/<name>/...`` strings, so there is only one
11
+ place to change when the on-disk layout evolves.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import re
18
+ import shutil
19
+ from pathlib import Path
20
+
21
+ CONFIG_FILENAME = "config.toml"
22
+
23
+
24
+ def _atomic_write_text(target: Path, content: str) -> None:
25
+ """Write *content* to *target* atomically (temp file + ``os.replace``).
26
+
27
+ Matches the pattern already used by
28
+ ``inspire.platform.web.session.models.WebSession.save`` — keep partial
29
+ writes out of the target path so concurrent ``account use`` or a crash
30
+ mid-write never leaves a half-written ``current`` / ``config.toml``.
31
+ """
32
+ target.parent.mkdir(parents=True, exist_ok=True)
33
+ tmp = target.with_name(target.name + ".tmp")
34
+ with open(tmp, "w", encoding="utf-8") as f:
35
+ f.write(content)
36
+ f.flush()
37
+ os.fsync(f.fileno())
38
+ os.replace(tmp, target)
39
+
40
+ _NAME_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
41
+
42
+
43
+ class AccountError(Exception):
44
+ """Raised for account-related failures (not found, already exists, bad name)."""
45
+
46
+
47
+ def validate_name(name: str) -> str:
48
+ candidate = (name or "").strip()
49
+ if not _NAME_PATTERN.match(candidate):
50
+ raise AccountError(
51
+ f"Invalid account name: {name!r}. Allowed: letters, digits, '.', '_', '-'; "
52
+ "must start with a letter or digit; 1-64 chars."
53
+ )
54
+ return candidate
55
+
56
+
57
+ def inspire_home() -> Path:
58
+ return Path.home() / ".inspire"
59
+
60
+
61
+ def accounts_dir() -> Path:
62
+ return inspire_home() / "accounts"
63
+
64
+
65
+ def current_file() -> Path:
66
+ return inspire_home() / "current"
67
+
68
+
69
+ def account_dir(name: str) -> Path:
70
+ return accounts_dir() / validate_name(name)
71
+
72
+
73
+ def account_config_path(name: str) -> Path:
74
+ return account_dir(name) / CONFIG_FILENAME
75
+
76
+
77
+ def ensure_inspire_home() -> None:
78
+ inspire_home().mkdir(parents=True, exist_ok=True)
79
+ accounts_dir().mkdir(parents=True, exist_ok=True)
80
+
81
+
82
+ def list_accounts() -> list[str]:
83
+ root = accounts_dir()
84
+ if not root.exists():
85
+ return []
86
+ return sorted(
87
+ p.name
88
+ for p in root.iterdir()
89
+ if p.is_dir() and (p / CONFIG_FILENAME).exists()
90
+ )
91
+
92
+
93
+ def account_exists(name: str) -> bool:
94
+ try:
95
+ return validate_name(name) in list_accounts()
96
+ except AccountError:
97
+ return False
98
+
99
+
100
+ def current_account() -> str | None:
101
+ try:
102
+ raw = current_file().read_text(encoding="utf-8").strip()
103
+ except FileNotFoundError:
104
+ return None
105
+ return raw or None
106
+
107
+
108
+ def set_current_account(name: str) -> None:
109
+ validated = validate_name(name)
110
+ if not account_exists(validated):
111
+ raise AccountError(f"Account not found: {validated}")
112
+ ensure_inspire_home()
113
+ _atomic_write_text(current_file(), validated + "\n")
114
+
115
+
116
+ def clear_current_account() -> None:
117
+ try:
118
+ current_file().unlink()
119
+ except FileNotFoundError:
120
+ pass
121
+
122
+
123
+ def create_account(name: str, config_content: str, *, overwrite: bool = False) -> Path:
124
+ validated = validate_name(name)
125
+ target = accounts_dir() / validated
126
+ if target.exists() and not overwrite:
127
+ raise AccountError(f"Account already exists: {validated}")
128
+ ensure_inspire_home()
129
+ target.mkdir(parents=True, exist_ok=overwrite)
130
+ _atomic_write_text(target / CONFIG_FILENAME, config_content)
131
+ return target
132
+
133
+
134
+ def remove_account(name: str) -> None:
135
+ validated = validate_name(name)
136
+ target = accounts_dir() / validated
137
+ if not target.exists():
138
+ raise AccountError(f"Account not found: {validated}")
139
+ shutil.rmtree(target)
140
+ if current_account() == validated:
141
+ clear_current_account()
@@ -0,0 +1 @@
1
+ """Bridge execution integrations (forge workflows, tunnels, SSH)."""