systemlink-cli 1.23.0__tar.gz → 1.25.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 (156) hide show
  1. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/PKG-INFO +2 -2
  2. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/pyproject.toml +4 -3
  3. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/_version.py +1 -1
  4. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/config_click.py +168 -21
  5. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/main.py +46 -1
  6. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/mcp_click.py +20 -22
  7. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/mcp_server.py +264 -56
  8. systemlink_cli-1.25.0/slcli/pkce.py +613 -0
  9. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/platform.py +122 -52
  10. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/profiles.py +16 -5
  11. systemlink_cli-1.25.0/slcli/systemlink-logo.svg +20 -0
  12. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/utils.py +182 -18
  13. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workitem_click.py +14 -2
  14. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/LICENSE +0 -0
  15. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/dff-editor/editor.js +0 -0
  16. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/dff-editor/index.html +0 -0
  17. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/__init__.py +0 -0
  18. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/__main__.py +0 -0
  19. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/alarm_click.py +0 -0
  20. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/asset_click.py +0 -0
  21. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/cli_formatters.py +0 -0
  22. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/cli_utils.py +0 -0
  23. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/comment_click.py +0 -0
  24. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/completion_click.py +0 -0
  25. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/config.py +0 -0
  26. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/dataframe_click.py +0 -0
  27. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/dff_click.py +0 -0
  28. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/dff_decorators.py +0 -0
  29. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/example_click.py +0 -0
  30. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/example_loader.py +0 -0
  31. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/example_provisioner.py +0 -0
  32. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/README.md +0 -0
  33. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/_schema/schema-v1.0.json +0 -0
  34. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-1/README.md +0 -0
  35. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-1/config.yaml +0 -0
  36. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-2/README.md +0 -0
  37. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-2/config.yaml +0 -0
  38. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/config.yaml +0 -0
  39. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/leakage-current-results.json +0 -0
  40. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/overvoltage-results.json +0 -0
  41. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/product-xyz-compliance-report.json +0 -0
  42. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/product-xyz-specification.csv +0 -0
  43. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/demo-data-3/thermal-response-results.json +0 -0
  44. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
  45. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
  46. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
  47. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
  48. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
  49. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
  50. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
  51. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
  52. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
  53. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  54. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/feed_click.py +0 -0
  55. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/file_click.py +0 -0
  56. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/function_click.py +0 -0
  57. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/function_templates.py +0 -0
  58. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/mcp_reachability.py +0 -0
  59. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/notebook_click.py +0 -0
  60. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/policy_click.py +0 -0
  61. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/policy_utils.py +0 -0
  62. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/response_handlers.py +0 -0
  63. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/rich_output.py +0 -0
  64. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/routine_click.py +0 -0
  65. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skill_click.py +0 -0
  66. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/SKILL.md +0 -0
  67. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
  68. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/commands.md +0 -0
  69. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
  70. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/example-authoring.md +0 -0
  71. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/filtering.md +0 -0
  72. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/import-specs.min.json +0 -0
  73. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/job-debugging/overview.md +0 -0
  74. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/nipkg/overview.md +0 -0
  75. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/notebook/interfaces.md +0 -0
  76. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/notebook/notebook-patterns.md +0 -0
  77. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/notebook/overview.md +0 -0
  78. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/python-test/overview.md +0 -0
  79. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/routine-examples.md +0 -0
  80. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/troubleshooting.md +0 -0
  81. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/angular-ui-packages.md +0 -0
  82. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/deployment.md +0 -0
  83. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/layout-patterns.md +0 -0
  84. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/nimble-angular.md +0 -0
  85. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/overview.md +0 -0
  86. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/systemlink-services.md +0 -0
  87. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/references/webapp/troubleshooting.md +0 -0
  88. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/skills/slcli/scripts/spec_import_helper.py +0 -0
  89. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/spec_click.py +0 -0
  90. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/ssl_trust.py +0 -0
  91. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/state_click.py +0 -0
  92. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/system_click.py +0 -0
  93. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/system_query_utils.py +0 -0
  94. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/table_utils.py +0 -0
  95. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/tag_click.py +0 -0
  96. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/templates_click.py +0 -0
  97. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/testmonitor_click.py +0 -0
  98. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/universal_handlers.py +0 -0
  99. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/user_click.py +0 -0
  100. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/web_editor.py +0 -0
  101. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_bootstrap.py +0 -0
  102. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_click.py +0 -0
  103. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/.gitignore +0 -0
  104. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/README.md +0 -0
  105. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/angular.json +0 -0
  106. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/package.json +0 -0
  107. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app-routing.module.ts +0 -0
  108. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app.component.html +0 -0
  109. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app.component.scss +0 -0
  110. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app.component.ts +0 -0
  111. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/app.module.ts +0 -0
  112. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.html +0 -0
  113. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.scss +0 -0
  114. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.ts +0 -0
  115. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/state/app-view-state.service.ts +0 -0
  116. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/systemlink-context.service.ts +0 -0
  117. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/theme-sync.service.ts +0 -0
  118. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/webapp-home-data.service.ts +0 -0
  119. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.html +0 -0
  120. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.scss +0 -0
  121. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.ts +0 -0
  122. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.html +0 -0
  123. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.scss +0 -0
  124. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.ts +0 -0
  125. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.html +0 -0
  126. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.scss +0 -0
  127. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.ts +0 -0
  128. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.html +0 -0
  129. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.scss +0 -0
  130. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.ts +0 -0
  131. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.html +0 -0
  132. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.scss +0 -0
  133. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.ts +0 -0
  134. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.html +0 -0
  135. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.scss +0 -0
  136. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.ts +0 -0
  137. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.html +0 -0
  138. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.scss +0 -0
  139. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.ts +0 -0
  140. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.html +0 -0
  141. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.scss +0 -0
  142. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.ts +0 -0
  143. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.html +0 -0
  144. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.scss +0 -0
  145. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.ts +0 -0
  146. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/app/shared/states/view-state.model.ts +0 -0
  147. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/assets/README.md +0 -0
  148. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/index.html +0 -0
  149. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/main.ts +0 -0
  150. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/src/styles.scss +0 -0
  151. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/tsconfig.app.json +0 -0
  152. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/webapp_templates/angular/blank/tsconfig.json +0 -0
  153. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workflow_preview.py +0 -0
  154. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workflows_click.py +0 -0
  155. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workspace_click.py +0 -0
  156. {systemlink_cli-1.23.0 → systemlink_cli-1.25.0}/slcli/workspace_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: systemlink-cli
3
- Version: 1.23.0
3
+ Version: 1.25.0
4
4
  Summary: SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates.
5
5
  License-File: LICENSE
6
6
  Author: Fred Visser
@@ -14,7 +14,7 @@ Provides-Extra: mcp
14
14
  Requires-Dist: click (>=7.1.2)
15
15
  Requires-Dist: cryptography (>=49.0.0)
16
16
  Requires-Dist: keyring (>=25.6.0,<26.0.0)
17
- Requires-Dist: mcp (>=1.0) ; extra == "mcp"
17
+ Requires-Dist: mcp (>=2,<3) ; extra == "mcp"
18
18
  Requires-Dist: packaging (>=21.0)
19
19
  Requires-Dist: pygments (>=2.20.0)
20
20
  Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
@@ -1,10 +1,11 @@
1
1
  [tool.poetry]
2
2
  name = "systemlink-cli"
3
- version = "1.23.0"
3
+ version = "1.25.0"
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"] },
@@ -37,7 +38,7 @@ rich = ">=15,<16"
37
38
  rich-click = ">=1.8,<2"
38
39
  cryptography = ">=49.0.0"
39
40
  pygments = ">=2.20.0"
40
- mcp = { version = ">=1.0", optional = true }
41
+ mcp = { version = ">=2,<3", optional = true }
41
42
 
42
43
  [tool.poetry.extras]
43
44
  mcp = ["mcp"]
@@ -80,7 +81,7 @@ pytest-xdist = "^3.8.0"
80
81
  openpyxl = "^3.1.5"
81
82
 
82
83
  # MCP server (optional feature, also needed for type checking)
83
- mcp = ">=1.0"
84
+ mcp = ">=2,<3"
84
85
 
85
86
  [tool.pytest.ini_options]
86
87
  addopts = "--cov slcli --strict-markers --doctest-modules --ignore=tests/e2e"
@@ -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.23.0"
4
+ __version__ = "1.25.0"
@@ -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,10 @@ 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
+ if auth_scheme == "bearer":
183
+ retry_status = check_web_server_auth(url, credential, auth_scheme=auth_scheme)
184
+ else:
185
+ retry_status = check_service_status(url, credential, auth_scheme=auth_scheme)
178
186
  except (OSError, ValueError) as exc:
179
187
  _exit_with_validation_error(
180
188
  f"Could not save the trusted certificate: {exc}. Profile was not saved.",
@@ -202,6 +210,10 @@ def _add_profile_impl(
202
210
  set_current: bool,
203
211
  readonly: bool,
204
212
  trust_fingerprint: Optional[str] = None,
213
+ auth_mode: str = "api-key",
214
+ client_id: Optional[str] = None,
215
+ scopes: tuple[str, ...] = (),
216
+ callback_port: Optional[int] = None,
205
217
  ) -> None:
206
218
  """Shared implementation for add-profile and login commands.
207
219
 
@@ -217,6 +229,10 @@ def _add_profile_impl(
217
229
  set_current: Whether to set as the current profile
218
230
  readonly: Whether to enable readonly mode
219
231
  trust_fingerprint: Optional SHA-256 fingerprint for non-interactive trust approval
232
+ auth_mode: Authentication flow to use
233
+ client_id: Public OAuth client ID for the PKCE login
234
+ scopes: OAuth scopes for the PKCE login
235
+ callback_port: Optional loopback callback port; zero selects an ephemeral port
220
236
  """
221
237
  # Get profile name
222
238
  if not profile:
@@ -231,11 +247,20 @@ def _add_profile_impl(
231
247
  assert isinstance(url, str)
232
248
  url = _normalize_base_url(url, "SystemLink API URL")
233
249
 
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)
250
+ if auth_mode == "api-key":
251
+ # Get API key - either from flag or prompt
252
+ if not api_key:
253
+ api_key = getpass.getpass("Enter your SystemLink API key: ")
254
+ assert isinstance(api_key, str)
255
+ api_key = _normalize_api_key(api_key)
256
+ elif auth_mode == "pkce":
257
+ if api_key:
258
+ _exit_with_validation_error(
259
+ "--api-key cannot be combined with --auth pkce. "
260
+ "Use --auth api-key for the API-key login flow."
261
+ )
262
+ else:
263
+ _exit_with_validation_error(f"Unsupported authentication flow: {auth_mode}")
239
264
 
240
265
  # Normalize and validate web_url (prompt if not provided)
241
266
  if not web_url:
@@ -244,12 +269,59 @@ def _add_profile_impl(
244
269
  assert isinstance(web_url, str)
245
270
  web_url = _normalize_base_url(web_url, "SystemLink Web UI URL")
246
271
 
247
- # Detect platform type and check service status
272
+ pkce_result: Any = None
273
+ pkce_scopes = list(scopes) if scopes else ["openid", "profile", "email", "offline_access"]
274
+ if auth_mode == "pkce":
275
+ certificate_status = check_web_server_auth(web_url, "", auth_scheme="bearer")
276
+ if certificate_status.get("certificate_error") and not certificate_status.get(
277
+ "server_reachable"
278
+ ):
279
+ _trust_certificate_if_requested(
280
+ web_url,
281
+ "",
282
+ certificate_status,
283
+ trust_fingerprint,
284
+ auth_scheme="bearer",
285
+ )
286
+ if not client_id:
287
+ client_id = click.prompt("PKCE client ID")
288
+ assert isinstance(client_id, str)
289
+ client_id = client_id.strip()
290
+ if not client_id:
291
+ _exit_with_validation_error("PKCE client ID cannot be empty.")
292
+
293
+ from .pkce import PkceError, perform_pkce_login
294
+
295
+ click.echo("Opening the SystemLink Web UI for authentication...")
296
+ try:
297
+ if callback_port is None:
298
+ pkce_result = perform_pkce_login(web_url, client_id, pkce_scopes)
299
+ else:
300
+ pkce_result = perform_pkce_login(
301
+ web_url, client_id, pkce_scopes, callback_port=callback_port
302
+ )
303
+ except PkceError as exc:
304
+ _exit_with_validation_error(f"PKCE login failed: {exc}")
305
+ api_key = pkce_result.access_token
306
+
307
+ assert isinstance(api_key, str)
308
+
309
+ # Validate PKCE against the Web Server identity route; API-key login retains API probes.
248
310
  click.echo("Checking server connectivity and services...")
249
- status = check_service_status(url, api_key)
311
+ if auth_mode == "pkce":
312
+ status = check_web_server_auth(web_url, api_key, auth_scheme="bearer")
313
+ else:
314
+ status = check_service_status(url, api_key)
250
315
 
251
316
  if status.get("certificate_error") and not status.get("server_reachable"):
252
- status = _trust_certificate_if_requested(url, api_key, status, trust_fingerprint)
317
+ validation_url = web_url if auth_mode == "pkce" else url
318
+ status = _trust_certificate_if_requested(
319
+ validation_url,
320
+ api_key,
321
+ status,
322
+ trust_fingerprint,
323
+ auth_scheme="bearer" if auth_mode == "pkce" else "api-key",
324
+ )
253
325
 
254
326
  platform = status["platform"]
255
327
  services = status.get("services", {})
@@ -262,16 +334,26 @@ def _add_profile_impl(
262
334
  )
263
335
 
264
336
  if status["auth_valid"] is False and _all_service_probes_unauthorized(services):
337
+ auth_failure_message = (
338
+ "PKCE bearer token validation failed. The server responded, but the token was not authorized. "
339
+ if auth_mode == "pkce"
340
+ else "API key validation failed. The server responded, but the key was not authorized. "
341
+ )
265
342
  _exit_with_validation_error(
266
- "API key validation failed. The server responded, but the key was not authorized. "
267
- "Profile was not saved.",
343
+ auth_failure_message + "Profile was not saved.",
268
344
  ExitCodes.PERMISSION_DENIED,
269
345
  )
270
346
 
271
347
  if status["auth_valid"] is not True:
272
- _exit_with_validation_error(
348
+ verification_message = (
273
349
  "Connected to the server, but profile verification was inconclusive. Check the "
274
- "API URL, API key, and service availability. Profile was not saved.",
350
+ "Web UI URL, bearer token, and service availability. Profile was not saved."
351
+ if auth_mode == "pkce"
352
+ else "Connected to the server, but profile verification was inconclusive. Check the "
353
+ "API URL, API key, and service availability. Profile was not saved."
354
+ )
355
+ _exit_with_validation_error(
356
+ verification_message,
275
357
  ExitCodes.GENERAL_ERROR,
276
358
  )
277
359
 
@@ -283,7 +365,10 @@ def _add_profile_impl(
283
365
  else:
284
366
  click.echo(" Platform: Unknown (will attempt all features)")
285
367
 
286
- click.echo(" API key: ✓ Authorized")
368
+ if auth_mode == "pkce":
369
+ click.echo(" PKCE bearer token: ✓ Authorized")
370
+ else:
371
+ click.echo(" API key: ✓ Authorized")
287
372
 
288
373
  if status.get("file_query_endpoint") == "query-files":
289
374
  click.echo(" File query: query-files")
@@ -315,13 +400,40 @@ def _add_profile_impl(
315
400
  platform=platform,
316
401
  workspace=workspace,
317
402
  readonly=readonly,
403
+ auth_mode=auth_mode,
404
+ pkce_client_id=client_id if auth_mode == "pkce" else None,
405
+ pkce_scopes=pkce_scopes if auth_mode == "pkce" else None,
318
406
  )
319
407
 
320
408
  # Load config and add profile
321
409
  cfg = ProfileConfig.load()
410
+ previous_profile = cfg.get_profile(profile)
411
+ previous_current_profile = cfg.current_profile
322
412
  cfg.add_profile(new_profile, set_current=set_current)
323
413
  cfg.save()
324
414
 
415
+ if auth_mode == "pkce" and pkce_result is not None:
416
+ from .pkce import save_pkce_credentials
417
+
418
+ try:
419
+ save_pkce_credentials(
420
+ profile,
421
+ pkce_result.access_token,
422
+ pkce_result.refresh_token,
423
+ pkce_result.expires_at,
424
+ )
425
+ except Exception as exc:
426
+ if previous_profile is None:
427
+ cfg.profiles.pop(profile, None)
428
+ else:
429
+ cfg.profiles[profile] = previous_profile
430
+ cfg.current_profile = previous_current_profile
431
+ cfg.save()
432
+ _exit_with_validation_error(
433
+ f"Could not store PKCE credentials securely: {exc}.",
434
+ ExitCodes.GENERAL_ERROR,
435
+ )
436
+
325
437
  click.echo(f"\n✓ Profile '{profile}' saved successfully.")
326
438
  click.echo(f" Server: {url}")
327
439
  click.echo(f" Web URL: {web_url}")
@@ -373,6 +485,7 @@ def register_config_commands(cli: Any) -> None:
373
485
  item = {
374
486
  "name": p.name,
375
487
  "server": p.server,
488
+ "auth-mode": p.auth_mode,
376
489
  "current": p.name == cfg.current_profile,
377
490
  }
378
491
  if p.web_url:
@@ -523,13 +636,16 @@ def register_config_commands(cli: Any) -> None:
523
636
  if profile.platform:
524
637
  rows.append(["Platform", profile.platform or "Unknown"])
525
638
 
526
- if show_secrets:
527
- api_key_display = profile.api_key
639
+ if profile.auth_mode == "pkce":
640
+ rows.append(["Authentication", "PKCE (bearer token in keyring)"])
528
641
  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])
642
+ if show_secrets:
643
+ api_key_display = profile.api_key
644
+ else:
645
+ api_key_display = (
646
+ "****" + profile.api_key[-4:] if len(profile.api_key) >= 4 else "****"
647
+ )
648
+ rows.append(["API Key", api_key_display])
533
649
 
534
650
  if profile.workspace:
535
651
  rows.append(["Workspace", profile.workspace])
@@ -735,6 +851,29 @@ def register_config_commands(cli: Any) -> None:
735
851
  @click.option("--url", help="SystemLink API URL")
736
852
  @click.option("--api-key", help="SystemLink API key")
737
853
  @click.option("--web-url", help="SystemLink Web UI base URL")
854
+ @click.option(
855
+ "--auth",
856
+ "auth_mode",
857
+ type=click.Choice(["api-key", "pkce"]),
858
+ default="api-key",
859
+ show_default=True,
860
+ help="Authentication flow to use",
861
+ )
862
+ @click.option("--client-id", help="Public OAuth client ID for the PKCE prototype")
863
+ @click.option(
864
+ "--callback-port",
865
+ type=click.IntRange(0, 65535),
866
+ help="Loopback callback port; use 0 for an ephemeral port",
867
+ )
868
+ @click.option(
869
+ "--scope",
870
+ "scopes",
871
+ multiple=True,
872
+ help=(
873
+ "OAuth scope to request; may be repeated "
874
+ "(defaults to openid profile email offline_access)"
875
+ ),
876
+ )
738
877
  @click.option("--workspace", "-w", help="Default workspace for this profile")
739
878
  @click.option(
740
879
  "--set-current/--no-set-current",
@@ -758,6 +897,10 @@ def register_config_commands(cli: Any) -> None:
758
897
  url: Optional[str],
759
898
  api_key: Optional[str],
760
899
  web_url: Optional[str],
900
+ auth_mode: str,
901
+ client_id: Optional[str],
902
+ callback_port: Optional[int],
903
+ scopes: tuple[str, ...],
761
904
  workspace: Optional[str],
762
905
  set_current: bool,
763
906
  readonly: bool,
@@ -781,6 +924,10 @@ def register_config_commands(cli: Any) -> None:
781
924
  url=url,
782
925
  api_key=api_key,
783
926
  web_url=web_url,
927
+ auth_mode=auth_mode,
928
+ client_id=client_id,
929
+ callback_port=callback_port,
930
+ scopes=scopes,
784
931
  workspace=workspace,
785
932
  set_current=set_current,
786
933
  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
 
@@ -14,6 +14,8 @@ import click
14
14
 
15
15
  from .utils import ExitCodes
16
16
 
17
+ DEFAULT_STREAMABLE_HTTP_PORT = 8765
18
+
17
19
 
18
20
  def _slcli_exe() -> str:
19
21
  """Return the absolute path to the slcli executable, or 'slcli' as a fallback."""
@@ -26,7 +28,7 @@ def _vscode_server_entry(slcli_exe: str) -> Dict[str, Any]:
26
28
  return {
27
29
  "type": "stdio",
28
30
  "command": slcli_exe,
29
- "args": ["mcp", "serve"],
31
+ "args": ["mcp", "serve", "--transport", "stdio"],
30
32
  }
31
33
 
32
34
 
@@ -34,7 +36,7 @@ def _claude_server_entry(slcli_exe: str) -> Dict[str, Any]:
34
36
  """Return the Claude Desktop / Cursor MCP server JSON object for slcli."""
35
37
  return {
36
38
  "command": slcli_exe,
37
- "args": ["mcp", "serve"],
39
+ "args": ["mcp", "serve", "--transport", "stdio"],
38
40
  }
39
41
 
40
42
 
@@ -150,7 +152,7 @@ def register_mcp_commands(cli: Any) -> None:
150
152
  "--transport",
151
153
  "-T",
152
154
  type=click.Choice(["stdio", "streamable-http"]),
153
- default="stdio",
155
+ default="streamable-http",
154
156
  show_default=True,
155
157
  help=(
156
158
  "Transport layer: 'stdio' for AI client integration, "
@@ -160,7 +162,7 @@ def register_mcp_commands(cli: Any) -> None:
160
162
  @click.option(
161
163
  "--port",
162
164
  "-p",
163
- default=8000,
165
+ default=DEFAULT_STREAMABLE_HTTP_PORT,
164
166
  show_default=True,
165
167
  help="Port to bind to (streamable HTTP transport only).",
166
168
  )
@@ -173,36 +175,35 @@ def register_mcp_commands(cli: Any) -> None:
173
175
  def serve(transport: str, port: int, host: str) -> None:
174
176
  """Start the MCP server.
175
177
 
176
- Defaults to stdio transport for direct AI client integration (VS Code
177
- Copilot, Claude Desktop, Cursor). Switch to streamable HTTP transport to serve
178
- over HTTP — useful for the MCP Inspector, browser-based tooling, or
179
- any client that prefers a persistent HTTP connection.
178
+ Defaults to streamable HTTP on localhost — useful for the MCP Inspector,
179
+ browser-based tooling, or any client that prefers a persistent HTTP connection.
180
+ Use stdio explicitly for AI clients that launch the server as a subprocess.
180
181
 
181
182
  \b
182
- Stdio (default — configure once with 'slcli mcp install'):
183
+ Streamable HTTP (default on http://127.0.0.1:8765/mcp):
183
184
  slcli mcp serve
185
+ slcli mcp serve --transport streamable-http --host 0.0.0.0 --port 9000
184
186
 
185
187
  \b
186
- Streamable HTTP (HTTP server on http://127.0.0.1:8000/mcp):
187
- slcli mcp serve --transport streamable-http
188
- slcli mcp serve --transport streamable-http --host 0.0.0.0 --port 9000
188
+ Stdio (for direct AI client integration):
189
+ slcli mcp serve --transport stdio
189
190
 
190
191
  \b
191
192
  Test with the MCP Inspector (Streamable HTTP):
192
- slcli mcp serve --transport streamable-http
193
+ slcli mcp serve
193
194
  npx @modelcontextprotocol/inspector
194
- # connect to http://127.0.0.1:8000/mcp (transport: Streamable HTTP)
195
+ # connect to http://127.0.0.1:8765/mcp (transport: Streamable HTTP)
195
196
  """
196
197
  try:
197
198
  from .mcp_server import main as run_mcp_server
198
- from .mcp_server import server as mcp_server
199
+ from .mcp_server import run_streamable_http
199
200
  except ImportError:
200
201
  click.echo(
201
202
  "✗ The 'mcp' package is not installed.\n"
202
- " Install it with: pip install 'mcp>=1.0'\n"
203
- " Or in a Poetry project: poetry add mcp\n"
203
+ " Install it with: pip install 'mcp>=2,<3'\n"
204
+ " Or in a Poetry project: poetry add 'mcp>=2,<3'\n"
204
205
  " If slcli was installed with pipx:\n"
205
- " pipx runpip systemlink-cli install 'mcp>=1.0'\n",
206
+ " pipx runpip systemlink-cli install 'mcp>=2,<3'\n",
206
207
  err=True,
207
208
  )
208
209
  sys.exit(ExitCodes.GENERAL_ERROR)
@@ -210,9 +211,6 @@ def register_mcp_commands(cli: Any) -> None:
210
211
  if transport == "stdio":
211
212
  run_mcp_server()
212
213
  else:
213
- mcp_server.settings.host = host
214
- mcp_server.settings.port = port
215
-
216
214
  click.echo(
217
215
  "✓ slcli MCP server starting in streamable HTTP mode on "
218
216
  f"http://{host}:{port}/mcp"
@@ -224,7 +222,7 @@ def register_mcp_commands(cli: Any) -> None:
224
222
  click.echo(" Press Ctrl+C to stop.\n")
225
223
 
226
224
  try:
227
- mcp_server.run(transport="streamable-http")
225
+ run_streamable_http(host=host, port=port)
228
226
  except KeyboardInterrupt:
229
227
  click.echo("\nslcli MCP server stopped")
230
228
  sys.exit(0)