systemlink-cli 1.24.0__tar.gz → 1.25.1__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 (156) hide show
  1. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/PKG-INFO +1 -1
  2. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/pyproject.toml +2 -1
  3. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/_version.py +1 -1
  4. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/config_click.py +165 -21
  5. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/main.py +46 -1
  6. systemlink_cli-1.25.1/slcli/pkce.py +613 -0
  7. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/platform.py +122 -52
  8. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/profiles.py +16 -5
  9. systemlink_cli-1.25.1/slcli/systemlink-logo.svg +20 -0
  10. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/utils.py +182 -18
  11. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workitem_click.py +14 -2
  12. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/LICENSE +0 -0
  13. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/dff-editor/editor.js +0 -0
  14. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/dff-editor/index.html +0 -0
  15. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/__init__.py +0 -0
  16. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/__main__.py +0 -0
  17. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/alarm_click.py +0 -0
  18. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/asset_click.py +0 -0
  19. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/cli_formatters.py +0 -0
  20. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/cli_utils.py +0 -0
  21. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/comment_click.py +0 -0
  22. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/completion_click.py +0 -0
  23. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/config.py +0 -0
  24. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/dataframe_click.py +0 -0
  25. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/dff_click.py +0 -0
  26. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/dff_decorators.py +0 -0
  27. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/example_click.py +0 -0
  28. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/example_loader.py +0 -0
  29. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/example_provisioner.py +0 -0
  30. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/README.md +0 -0
  31. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/_schema/schema-v1.0.json +0 -0
  32. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-1/README.md +0 -0
  33. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-1/config.yaml +0 -0
  34. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-2/README.md +0 -0
  35. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-2/config.yaml +0 -0
  36. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/config.yaml +0 -0
  37. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/leakage-current-results.json +0 -0
  38. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/overvoltage-results.json +0 -0
  39. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/product-xyz-compliance-report.json +0 -0
  40. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/product-xyz-specification.csv +0 -0
  41. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/demo-data-3/thermal-response-results.json +0 -0
  42. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
  43. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
  44. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
  45. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
  46. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
  47. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
  48. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
  49. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
  50. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
  51. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  52. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/feed_click.py +0 -0
  53. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/file_click.py +0 -0
  54. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/function_click.py +0 -0
  55. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/function_templates.py +0 -0
  56. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/mcp_click.py +0 -0
  57. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/mcp_reachability.py +0 -0
  58. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/mcp_server.py +0 -0
  59. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/notebook_click.py +0 -0
  60. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/policy_click.py +0 -0
  61. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/policy_utils.py +0 -0
  62. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/response_handlers.py +0 -0
  63. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/rich_output.py +0 -0
  64. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/routine_click.py +0 -0
  65. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skill_click.py +0 -0
  66. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/SKILL.md +0 -0
  67. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
  68. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/commands.md +0 -0
  69. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
  70. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/example-authoring.md +0 -0
  71. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/filtering.md +0 -0
  72. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/import-specs.min.json +0 -0
  73. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/job-debugging/overview.md +0 -0
  74. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/nipkg/overview.md +0 -0
  75. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/notebook/interfaces.md +0 -0
  76. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/notebook/notebook-patterns.md +0 -0
  77. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/notebook/overview.md +0 -0
  78. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/python-test/overview.md +0 -0
  79. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/routine-examples.md +0 -0
  80. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/troubleshooting.md +0 -0
  81. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/angular-ui-packages.md +0 -0
  82. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/deployment.md +0 -0
  83. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/layout-patterns.md +0 -0
  84. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/nimble-angular.md +0 -0
  85. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/overview.md +0 -0
  86. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/systemlink-services.md +0 -0
  87. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/references/webapp/troubleshooting.md +0 -0
  88. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/skills/slcli/scripts/spec_import_helper.py +0 -0
  89. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/spec_click.py +0 -0
  90. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/ssl_trust.py +0 -0
  91. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/state_click.py +0 -0
  92. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/system_click.py +0 -0
  93. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/system_query_utils.py +0 -0
  94. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/table_utils.py +0 -0
  95. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/tag_click.py +0 -0
  96. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/templates_click.py +0 -0
  97. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/testmonitor_click.py +0 -0
  98. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/universal_handlers.py +0 -0
  99. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/user_click.py +0 -0
  100. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/web_editor.py +0 -0
  101. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_bootstrap.py +0 -0
  102. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_click.py +0 -0
  103. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/.gitignore +0 -0
  104. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/README.md +0 -0
  105. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/angular.json +0 -0
  106. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/package.json +0 -0
  107. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app-routing.module.ts +0 -0
  108. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app.component.html +0 -0
  109. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app.component.scss +0 -0
  110. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app.component.ts +0 -0
  111. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/app.module.ts +0 -0
  112. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.html +0 -0
  113. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.scss +0 -0
  114. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.ts +0 -0
  115. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/state/app-view-state.service.ts +0 -0
  116. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/systemlink-context.service.ts +0 -0
  117. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/theme-sync.service.ts +0 -0
  118. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/webapp-home-data.service.ts +0 -0
  119. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.html +0 -0
  120. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.scss +0 -0
  121. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.ts +0 -0
  122. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.html +0 -0
  123. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.scss +0 -0
  124. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.ts +0 -0
  125. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.html +0 -0
  126. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.scss +0 -0
  127. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.ts +0 -0
  128. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.html +0 -0
  129. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.scss +0 -0
  130. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.ts +0 -0
  131. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.html +0 -0
  132. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.scss +0 -0
  133. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.ts +0 -0
  134. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.html +0 -0
  135. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.scss +0 -0
  136. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.ts +0 -0
  137. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.html +0 -0
  138. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.scss +0 -0
  139. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.ts +0 -0
  140. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.html +0 -0
  141. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.scss +0 -0
  142. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.ts +0 -0
  143. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.html +0 -0
  144. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.scss +0 -0
  145. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.ts +0 -0
  146. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/app/shared/states/view-state.model.ts +0 -0
  147. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/assets/README.md +0 -0
  148. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/index.html +0 -0
  149. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/main.ts +0 -0
  150. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/src/styles.scss +0 -0
  151. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/tsconfig.app.json +0 -0
  152. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/webapp_templates/angular/blank/tsconfig.json +0 -0
  153. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workflow_preview.py +0 -0
  154. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workflows_click.py +0 -0
  155. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workspace_click.py +0 -0
  156. {systemlink_cli-1.24.0 → systemlink_cli-1.25.1}/slcli/workspace_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: systemlink-cli
3
- Version: 1.24.0
3
+ Version: 1.25.1
4
4
  Summary: SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates.
5
5
  License-File: LICENSE
6
6
  Author: Fred Visser
@@ -1,10 +1,11 @@
1
1
  [tool.poetry]
2
2
  name = "systemlink-cli"
3
- version = "1.24.0"
3
+ version = "1.25.1"
4
4
  description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
5
5
  authors = ["Fred Visser <fred.visser@emerson.com>"]
6
6
  packages = [{ include = "slcli" }]
7
7
  include = [
8
+ { path = "slcli/systemlink-logo.svg", format = ["wheel", "sdist"] },
8
9
  { path = "dff-editor/index.html", format = ["wheel", "sdist"] },
9
10
  { path = "dff-editor/editor.js", format = ["wheel", "sdist"] },
10
11
  { path = "slcli/skills", format = ["wheel", "sdist"] },
@@ -1,4 +1,4 @@
1
1
  """Version information for slcli."""
2
2
 
3
3
  # This file is auto-generated. Do not edit manually.
4
- __version__ = "1.24.0"
4
+ __version__ = "1.25.1"
@@ -16,6 +16,7 @@ from .platform import (
16
16
  PLATFORM_SLE,
17
17
  PLATFORM_SLS,
18
18
  check_service_status,
19
+ check_web_server_auth,
19
20
  )
20
21
  from .profiles import ProfileConfig, Profile, check_config_file_permissions
21
22
  from .rich_output import render_table
@@ -134,7 +135,11 @@ def _show_certificate_warning(certificate: dict[str, Any]) -> None:
134
135
 
135
136
 
136
137
  def _trust_certificate_if_requested(
137
- url: str, api_key: str, status: dict[str, Any], trust_fingerprint: Optional[str]
138
+ url: str,
139
+ credential: str,
140
+ status: dict[str, Any],
141
+ trust_fingerprint: Optional[str],
142
+ auth_scheme: str = "api-key",
138
143
  ) -> dict[str, Any]:
139
144
  """Prompt for or apply certificate trust after a verification failure."""
140
145
  certificate_details = status.get("certificate")
@@ -174,7 +179,7 @@ def _trust_certificate_if_requested(
174
179
 
175
180
  try:
176
181
  save_managed_certificate(certificate)
177
- retry_status = check_service_status(url, api_key)
182
+ retry_status = check_service_status(url, credential, auth_scheme=auth_scheme)
178
183
  except (OSError, ValueError) as exc:
179
184
  _exit_with_validation_error(
180
185
  f"Could not save the trusted certificate: {exc}. Profile was not saved.",
@@ -202,6 +207,10 @@ def _add_profile_impl(
202
207
  set_current: bool,
203
208
  readonly: bool,
204
209
  trust_fingerprint: Optional[str] = None,
210
+ auth_mode: str = "api-key",
211
+ client_id: Optional[str] = None,
212
+ scopes: tuple[str, ...] = (),
213
+ callback_port: Optional[int] = None,
205
214
  ) -> None:
206
215
  """Shared implementation for add-profile and login commands.
207
216
 
@@ -217,6 +226,10 @@ def _add_profile_impl(
217
226
  set_current: Whether to set as the current profile
218
227
  readonly: Whether to enable readonly mode
219
228
  trust_fingerprint: Optional SHA-256 fingerprint for non-interactive trust approval
229
+ auth_mode: Authentication flow to use
230
+ client_id: Public OAuth client ID for the PKCE login
231
+ scopes: OAuth scopes for the PKCE login
232
+ callback_port: Optional loopback callback port; zero selects an ephemeral port
220
233
  """
221
234
  # Get profile name
222
235
  if not profile:
@@ -231,11 +244,20 @@ def _add_profile_impl(
231
244
  assert isinstance(url, str)
232
245
  url = _normalize_base_url(url, "SystemLink API URL")
233
246
 
234
- # Get API key - either from flag or prompt
235
- if not api_key:
236
- api_key = getpass.getpass("Enter your SystemLink API key: ")
237
- assert isinstance(api_key, str)
238
- api_key = _normalize_api_key(api_key)
247
+ if auth_mode == "api-key":
248
+ # Get API key - either from flag or prompt
249
+ if not api_key:
250
+ api_key = getpass.getpass("Enter your SystemLink API key: ")
251
+ assert isinstance(api_key, str)
252
+ api_key = _normalize_api_key(api_key)
253
+ elif auth_mode == "pkce":
254
+ if api_key:
255
+ _exit_with_validation_error(
256
+ "--api-key cannot be combined with --auth pkce. "
257
+ "Use --auth api-key for the API-key login flow."
258
+ )
259
+ else:
260
+ _exit_with_validation_error(f"Unsupported authentication flow: {auth_mode}")
239
261
 
240
262
  # Normalize and validate web_url (prompt if not provided)
241
263
  if not web_url:
@@ -244,12 +266,59 @@ def _add_profile_impl(
244
266
  assert isinstance(web_url, str)
245
267
  web_url = _normalize_base_url(web_url, "SystemLink Web UI URL")
246
268
 
247
- # Detect platform type and check service status
269
+ pkce_result: Any = None
270
+ pkce_scopes = list(scopes) if scopes else ["openid", "profile", "email", "offline_access"]
271
+ if auth_mode == "pkce":
272
+ certificate_status = check_web_server_auth(web_url, "", auth_scheme="bearer")
273
+ if certificate_status.get("certificate_error") and not certificate_status.get(
274
+ "server_reachable"
275
+ ):
276
+ _trust_certificate_if_requested(
277
+ web_url,
278
+ "",
279
+ certificate_status,
280
+ trust_fingerprint,
281
+ auth_scheme="bearer",
282
+ )
283
+ if not client_id:
284
+ client_id = click.prompt("PKCE client ID")
285
+ assert isinstance(client_id, str)
286
+ client_id = client_id.strip()
287
+ if not client_id:
288
+ _exit_with_validation_error("PKCE client ID cannot be empty.")
289
+
290
+ from .pkce import PkceError, perform_pkce_login
291
+
292
+ click.echo("Opening the SystemLink Web UI for authentication...")
293
+ try:
294
+ if callback_port is None:
295
+ pkce_result = perform_pkce_login(web_url, client_id, pkce_scopes)
296
+ else:
297
+ pkce_result = perform_pkce_login(
298
+ web_url, client_id, pkce_scopes, callback_port=callback_port
299
+ )
300
+ except PkceError as exc:
301
+ _exit_with_validation_error(f"PKCE login failed: {exc}")
302
+ api_key = pkce_result.access_token
303
+
304
+ assert isinstance(api_key, str)
305
+
306
+ # PKCE uses the Web URL for bearer service probes; API-key login uses the API URL.
248
307
  click.echo("Checking server connectivity and services...")
249
- status = check_service_status(url, api_key)
308
+ if auth_mode == "pkce":
309
+ status = check_service_status(web_url, api_key, auth_scheme="bearer")
310
+ else:
311
+ status = check_service_status(url, api_key)
250
312
 
251
313
  if status.get("certificate_error") and not status.get("server_reachable"):
252
- status = _trust_certificate_if_requested(url, api_key, status, trust_fingerprint)
314
+ validation_url = web_url if auth_mode == "pkce" else url
315
+ status = _trust_certificate_if_requested(
316
+ validation_url,
317
+ api_key,
318
+ status,
319
+ trust_fingerprint,
320
+ auth_scheme="bearer" if auth_mode == "pkce" else "api-key",
321
+ )
253
322
 
254
323
  platform = status["platform"]
255
324
  services = status.get("services", {})
@@ -262,16 +331,26 @@ def _add_profile_impl(
262
331
  )
263
332
 
264
333
  if status["auth_valid"] is False and _all_service_probes_unauthorized(services):
334
+ auth_failure_message = (
335
+ "PKCE bearer token validation failed. The server responded, but the token was not authorized. "
336
+ if auth_mode == "pkce"
337
+ else "API key validation failed. The server responded, but the key was not authorized. "
338
+ )
265
339
  _exit_with_validation_error(
266
- "API key validation failed. The server responded, but the key was not authorized. "
267
- "Profile was not saved.",
340
+ auth_failure_message + "Profile was not saved.",
268
341
  ExitCodes.PERMISSION_DENIED,
269
342
  )
270
343
 
271
344
  if status["auth_valid"] is not True:
272
- _exit_with_validation_error(
345
+ verification_message = (
273
346
  "Connected to the server, but profile verification was inconclusive. Check the "
274
- "API URL, API key, and service availability. Profile was not saved.",
347
+ "Web UI URL, bearer token, and service availability. Profile was not saved."
348
+ if auth_mode == "pkce"
349
+ else "Connected to the server, but profile verification was inconclusive. Check the "
350
+ "API URL, API key, and service availability. Profile was not saved."
351
+ )
352
+ _exit_with_validation_error(
353
+ verification_message,
275
354
  ExitCodes.GENERAL_ERROR,
276
355
  )
277
356
 
@@ -283,7 +362,10 @@ def _add_profile_impl(
283
362
  else:
284
363
  click.echo(" Platform: Unknown (will attempt all features)")
285
364
 
286
- click.echo(" API key: ✓ Authorized")
365
+ if auth_mode == "pkce":
366
+ click.echo(" PKCE bearer token: ✓ Authorized")
367
+ else:
368
+ click.echo(" API key: ✓ Authorized")
287
369
 
288
370
  if status.get("file_query_endpoint") == "query-files":
289
371
  click.echo(" File query: query-files")
@@ -315,13 +397,40 @@ def _add_profile_impl(
315
397
  platform=platform,
316
398
  workspace=workspace,
317
399
  readonly=readonly,
400
+ auth_mode=auth_mode,
401
+ pkce_client_id=client_id if auth_mode == "pkce" else None,
402
+ pkce_scopes=pkce_scopes if auth_mode == "pkce" else None,
318
403
  )
319
404
 
320
405
  # Load config and add profile
321
406
  cfg = ProfileConfig.load()
407
+ previous_profile = cfg.get_profile(profile)
408
+ previous_current_profile = cfg.current_profile
322
409
  cfg.add_profile(new_profile, set_current=set_current)
323
410
  cfg.save()
324
411
 
412
+ if auth_mode == "pkce" and pkce_result is not None:
413
+ from .pkce import save_pkce_credentials
414
+
415
+ try:
416
+ save_pkce_credentials(
417
+ profile,
418
+ pkce_result.access_token,
419
+ pkce_result.refresh_token,
420
+ pkce_result.expires_at,
421
+ )
422
+ except Exception as exc:
423
+ if previous_profile is None:
424
+ cfg.profiles.pop(profile, None)
425
+ else:
426
+ cfg.profiles[profile] = previous_profile
427
+ cfg.current_profile = previous_current_profile
428
+ cfg.save()
429
+ _exit_with_validation_error(
430
+ f"Could not store PKCE credentials securely: {exc}.",
431
+ ExitCodes.GENERAL_ERROR,
432
+ )
433
+
325
434
  click.echo(f"\n✓ Profile '{profile}' saved successfully.")
326
435
  click.echo(f" Server: {url}")
327
436
  click.echo(f" Web URL: {web_url}")
@@ -373,6 +482,7 @@ def register_config_commands(cli: Any) -> None:
373
482
  item = {
374
483
  "name": p.name,
375
484
  "server": p.server,
485
+ "auth-mode": p.auth_mode,
376
486
  "current": p.name == cfg.current_profile,
377
487
  }
378
488
  if p.web_url:
@@ -523,13 +633,16 @@ def register_config_commands(cli: Any) -> None:
523
633
  if profile.platform:
524
634
  rows.append(["Platform", profile.platform or "Unknown"])
525
635
 
526
- if show_secrets:
527
- api_key_display = profile.api_key
636
+ if profile.auth_mode == "pkce":
637
+ rows.append(["Authentication", "PKCE (bearer token in keyring)"])
528
638
  else:
529
- api_key_display = (
530
- "****" + profile.api_key[-4:] if len(profile.api_key) >= 4 else "****"
531
- )
532
- rows.append(["API Key", api_key_display])
639
+ if show_secrets:
640
+ api_key_display = profile.api_key
641
+ else:
642
+ api_key_display = (
643
+ "****" + profile.api_key[-4:] if len(profile.api_key) >= 4 else "****"
644
+ )
645
+ rows.append(["API Key", api_key_display])
533
646
 
534
647
  if profile.workspace:
535
648
  rows.append(["Workspace", profile.workspace])
@@ -735,6 +848,29 @@ def register_config_commands(cli: Any) -> None:
735
848
  @click.option("--url", help="SystemLink API URL")
736
849
  @click.option("--api-key", help="SystemLink API key")
737
850
  @click.option("--web-url", help="SystemLink Web UI base URL")
851
+ @click.option(
852
+ "--auth",
853
+ "auth_mode",
854
+ type=click.Choice(["api-key", "pkce"]),
855
+ default="api-key",
856
+ show_default=True,
857
+ help="Authentication flow to use",
858
+ )
859
+ @click.option("--client-id", help="Public OAuth client ID for the PKCE prototype")
860
+ @click.option(
861
+ "--callback-port",
862
+ type=click.IntRange(0, 65535),
863
+ help="Loopback callback port; use 0 for an ephemeral port",
864
+ )
865
+ @click.option(
866
+ "--scope",
867
+ "scopes",
868
+ multiple=True,
869
+ help=(
870
+ "OAuth scope to request; may be repeated "
871
+ "(defaults to openid profile email offline_access)"
872
+ ),
873
+ )
738
874
  @click.option("--workspace", "-w", help="Default workspace for this profile")
739
875
  @click.option(
740
876
  "--set-current/--no-set-current",
@@ -758,6 +894,10 @@ def register_config_commands(cli: Any) -> None:
758
894
  url: Optional[str],
759
895
  api_key: Optional[str],
760
896
  web_url: Optional[str],
897
+ auth_mode: str,
898
+ client_id: Optional[str],
899
+ callback_port: Optional[int],
900
+ scopes: tuple[str, ...],
761
901
  workspace: Optional[str],
762
902
  set_current: bool,
763
903
  readonly: bool,
@@ -781,6 +921,10 @@ def register_config_commands(cli: Any) -> None:
781
921
  url=url,
782
922
  api_key=api_key,
783
923
  web_url=web_url,
924
+ auth_mode=auth_mode,
925
+ client_id=client_id,
926
+ callback_port=callback_port,
927
+ scopes=scopes,
784
928
  workspace=workspace,
785
929
  set_current=set_current,
786
930
  readonly=readonly,
@@ -415,6 +415,29 @@ def ca_info() -> None:
415
415
  @click.option("--url", help="SystemLink API URL")
416
416
  @click.option("--api-key", help="SystemLink API key")
417
417
  @click.option("--web-url", help="SystemLink Web UI base URL")
418
+ @click.option(
419
+ "--auth",
420
+ "auth_mode",
421
+ type=click.Choice(["api-key", "pkce"]),
422
+ default="api-key",
423
+ show_default=True,
424
+ help="Authentication flow to use",
425
+ )
426
+ @click.option("--client-id", help="Public OAuth client ID for the PKCE prototype")
427
+ @click.option(
428
+ "--callback-port",
429
+ type=click.IntRange(0, 65535),
430
+ help="Loopback callback port; use 0 for an ephemeral port",
431
+ )
432
+ @click.option(
433
+ "--scope",
434
+ "scopes",
435
+ multiple=True,
436
+ help=(
437
+ "OAuth scope to request; may be repeated "
438
+ "(defaults to openid profile email offline_access)"
439
+ ),
440
+ )
418
441
  @click.option("--workspace", "-w", help="Default workspace for this profile")
419
442
  @click.option(
420
443
  "--set-current/--no-set-current",
@@ -438,6 +461,10 @@ def login(
438
461
  url: Optional[str],
439
462
  api_key: Optional[str],
440
463
  web_url: Optional[str],
464
+ auth_mode: str,
465
+ client_id: Optional[str],
466
+ callback_port: Optional[int],
467
+ scopes: tuple[str, ...],
441
468
  workspace: Optional[str],
442
469
  set_current: bool,
443
470
  readonly: bool,
@@ -464,6 +491,10 @@ def login(
464
491
  url=url,
465
492
  api_key=api_key,
466
493
  web_url=web_url,
494
+ auth_mode=auth_mode,
495
+ client_id=client_id,
496
+ callback_port=callback_port,
497
+ scopes=scopes,
467
498
  workspace=workspace,
468
499
  set_current=set_current,
469
500
  readonly=readonly,
@@ -486,6 +517,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
486
517
  from .profiles import ProfileConfig
487
518
 
488
519
  cfg = ProfileConfig.load()
520
+ removed_profiles: list[str] = []
489
521
 
490
522
  if remove_all:
491
523
  if not force:
@@ -497,6 +529,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
497
529
  return
498
530
 
499
531
  # Clear all profiles
532
+ removed_profiles = list(cfg.profiles)
500
533
  cfg.profiles.clear()
501
534
  cfg.current_profile = None
502
535
  cfg.save()
@@ -517,6 +550,7 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
517
550
  return
518
551
 
519
552
  cfg.delete_profile(profile)
553
+ removed_profiles = [profile]
520
554
  cfg.save()
521
555
  click.echo(f"✓ Profile '{profile}' removed.")
522
556
 
@@ -536,12 +570,20 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
536
570
  return
537
571
 
538
572
  cfg.delete_profile(current)
573
+ removed_profiles = [current]
539
574
  cfg.save()
540
575
  click.echo(f"✓ Profile '{current}' removed.")
541
576
  if cfg.current_profile:
542
577
  click.echo(f" Current profile is now: {cfg.current_profile}")
543
578
 
544
579
  # Also clean up legacy keyring entries
580
+ try:
581
+ from .pkce import delete_pkce_credentials
582
+
583
+ for removed_profile in removed_profiles:
584
+ delete_pkce_credentials(removed_profile)
585
+ except Exception:
586
+ pass
545
587
  try:
546
588
  keyring.delete_password("systemlink-cli", "SYSTEMLINK_API_KEY")
547
589
  except Exception:
@@ -606,7 +648,10 @@ def info(format: str, skip_health: bool, debug: bool) -> None:
606
648
  elif platform_info.get("server_reachable") is False:
607
649
  status = "✗ Server unreachable"
608
650
  elif platform_info.get("auth_valid") is False:
609
- status = "✗ API key unauthorized"
651
+ credential_name = (
652
+ "Bearer token" if active_profile and active_profile.auth_mode == "pkce" else "API key"
653
+ )
654
+ status = f"✗ {credential_name} unauthorized"
610
655
  else:
611
656
  status = "✓ Connected"
612
657