systemlink-cli 1.27.7__tar.gz → 1.29.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 (176) hide show
  1. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/PKG-INFO +1 -1
  2. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/pyproject.toml +2 -1
  3. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/_version.py +1 -1
  4. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/mcp_server.py +2 -72
  5. systemlink_cli-1.29.1/slcli/mcp_skills.py +490 -0
  6. systemlink_cli-1.29.1/slcli/skills/slcli/evals/COPILOT_BATCH_RUN_PROMPT.md +127 -0
  7. systemlink_cli-1.29.1/slcli/skills/slcli/evals/README.md +236 -0
  8. systemlink_cli-1.29.1/slcli/skills/slcli/evals/evals.json +642 -0
  9. systemlink_cli-1.29.1/slcli/skills/slcli/evals/evals.schema.json +53 -0
  10. systemlink_cli-1.29.1/slcli/skills/slcli/evals/files/spec-import/battery-specs.csv +4 -0
  11. systemlink_cli-1.29.1/slcli/skills/slcli/evals/files/webapp-package/icon.svg +4 -0
  12. systemlink_cli-1.29.1/slcli/skills/slcli/evals/files/webapp-package/nipkg.config.json +15 -0
  13. systemlink_cli-1.29.1/slcli/skills/slcli/evals/files/webapp-package/webapp-content/index.html +10 -0
  14. systemlink_cli-1.29.1/slcli/skills/slcli/evals/trigger_evals.json +34 -0
  15. systemlink_cli-1.29.1/slcli/skills/slcli/scripts/benchmark_iteration.py +183 -0
  16. systemlink_cli-1.29.1/slcli/skills/slcli/scripts/compare_iteration.py +350 -0
  17. systemlink_cli-1.29.1/slcli/skills/slcli/scripts/eval_manifest.py +134 -0
  18. systemlink_cli-1.29.1/slcli/skills/slcli/scripts/grade_eval_response.py +354 -0
  19. systemlink_cli-1.29.1/slcli/skills/slcli/scripts/grade_iteration.py +283 -0
  20. systemlink_cli-1.29.1/slcli/skills/slcli/scripts/prepare_eval_prompts.py +279 -0
  21. systemlink_cli-1.29.1/slcli/skills/slcli/scripts/prepare_eval_workspace.py +523 -0
  22. systemlink_cli-1.29.1/slcli/skills/slcli/scripts/render_eval_review.py +82 -0
  23. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/LICENSE +0 -0
  24. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/dff-editor/editor.js +0 -0
  25. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/dff-editor/index.html +0 -0
  26. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/__init__.py +0 -0
  27. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/__main__.py +0 -0
  28. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/alarm_click.py +0 -0
  29. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/asset_click.py +0 -0
  30. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/cli_formatters.py +0 -0
  31. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/cli_utils.py +0 -0
  32. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/comment_click.py +0 -0
  33. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/completion_click.py +0 -0
  34. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/config.py +0 -0
  35. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/config_click.py +0 -0
  36. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/dataframe_click.py +0 -0
  37. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/dff_click.py +0 -0
  38. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/dff_decorators.py +0 -0
  39. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/example_click.py +0 -0
  40. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/example_loader.py +0 -0
  41. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/example_provisioner.py +0 -0
  42. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/README.md +0 -0
  43. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/_schema/schema-v1.0.json +0 -0
  44. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-1/README.md +0 -0
  45. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-1/config.yaml +0 -0
  46. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-2/README.md +0 -0
  47. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-2/config.yaml +0 -0
  48. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-3/config.yaml +0 -0
  49. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-3/leakage-current-results.json +0 -0
  50. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-3/overvoltage-results.json +0 -0
  51. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-3/product-xyz-compliance-report.json +0 -0
  52. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-3/product-xyz-specification.csv +0 -0
  53. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/demo-data-3/thermal-response-results.json +0 -0
  54. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
  55. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
  56. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
  57. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
  58. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
  59. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
  60. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
  61. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
  62. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
  63. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  64. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/feed_click.py +0 -0
  65. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/file_click.py +0 -0
  66. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/function_click.py +0 -0
  67. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/function_templates.py +0 -0
  68. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/main.py +0 -0
  69. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/mcp_click.py +0 -0
  70. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/mcp_reachability.py +0 -0
  71. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/notebook_click.py +0 -0
  72. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/pkce.py +0 -0
  73. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/platform.py +0 -0
  74. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/policy_click.py +0 -0
  75. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/policy_utils.py +0 -0
  76. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/profiles.py +0 -0
  77. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/response_handlers.py +0 -0
  78. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/rich_output.py +0 -0
  79. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/routine_click.py +0 -0
  80. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skill_click.py +0 -0
  81. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/SKILL.md +0 -0
  82. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
  83. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/commands.md +0 -0
  84. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
  85. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/example-authoring.md +0 -0
  86. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/filtering.md +0 -0
  87. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/import-specs.min.json +0 -0
  88. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/job-debugging/overview.md +0 -0
  89. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/nipkg/overview.md +0 -0
  90. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/notebook/interfaces.md +0 -0
  91. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/notebook/notebook-patterns.md +0 -0
  92. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/notebook/overview.md +0 -0
  93. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/python-test/overview.md +0 -0
  94. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/routine-examples.md +0 -0
  95. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/troubleshooting.md +0 -0
  96. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/webapp/angular-ui-packages.md +0 -0
  97. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/webapp/deployment.md +0 -0
  98. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/webapp/layout-patterns.md +0 -0
  99. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/webapp/nimble-angular.md +0 -0
  100. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/webapp/overview.md +0 -0
  101. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/webapp/systemlink-services.md +0 -0
  102. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/references/webapp/troubleshooting.md +0 -0
  103. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/skills/slcli/scripts/spec_import_helper.py +0 -0
  104. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/spec_click.py +0 -0
  105. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/ssl_trust.py +0 -0
  106. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/state_click.py +0 -0
  107. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/system_click.py +0 -0
  108. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/system_query_utils.py +0 -0
  109. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/systemlink-logo.svg +0 -0
  110. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/table_utils.py +0 -0
  111. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/tag_click.py +0 -0
  112. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/templates_click.py +0 -0
  113. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/testmonitor_click.py +0 -0
  114. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/universal_handlers.py +0 -0
  115. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/user_click.py +0 -0
  116. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/utils.py +0 -0
  117. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/version_click.py +0 -0
  118. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/web_editor.py +0 -0
  119. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_bootstrap.py +0 -0
  120. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_click.py +0 -0
  121. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/.gitignore +0 -0
  122. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/README.md +0 -0
  123. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/angular.json +0 -0
  124. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/package.json +0 -0
  125. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/scripts/filter-sbom.js +0 -0
  126. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/app-routing.module.ts +0 -0
  127. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/app.component.html +0 -0
  128. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/app.component.scss +0 -0
  129. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/app.component.ts +0 -0
  130. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/app.module.ts +0 -0
  131. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.html +0 -0
  132. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.scss +0 -0
  133. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.ts +0 -0
  134. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/core/state/app-view-state.service.ts +0 -0
  135. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/systemlink-context.service.ts +0 -0
  136. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/theme-sync.service.ts +0 -0
  137. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/webapp-home-data.service.ts +0 -0
  138. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.html +0 -0
  139. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.scss +0 -0
  140. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.ts +0 -0
  141. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.html +0 -0
  142. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.scss +0 -0
  143. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.ts +0 -0
  144. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.html +0 -0
  145. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.scss +0 -0
  146. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.ts +0 -0
  147. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.html +0 -0
  148. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.scss +0 -0
  149. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.ts +0 -0
  150. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.html +0 -0
  151. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.scss +0 -0
  152. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.ts +0 -0
  153. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.html +0 -0
  154. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.scss +0 -0
  155. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.ts +0 -0
  156. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.html +0 -0
  157. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.scss +0 -0
  158. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.ts +0 -0
  159. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.html +0 -0
  160. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.scss +0 -0
  161. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.ts +0 -0
  162. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.html +0 -0
  163. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.scss +0 -0
  164. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.ts +0 -0
  165. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/app/shared/states/view-state.model.ts +0 -0
  166. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/assets/README.md +0 -0
  167. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/index.html +0 -0
  168. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/main.ts +0 -0
  169. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/src/styles.scss +0 -0
  170. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/tsconfig.app.json +0 -0
  171. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/webapp_templates/angular/blank/tsconfig.json +0 -0
  172. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/workflow_preview.py +0 -0
  173. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/workflows_click.py +0 -0
  174. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/workitem_click.py +0 -0
  175. {systemlink_cli-1.27.7 → systemlink_cli-1.29.1}/slcli/workspace_click.py +0 -0
  176. {systemlink_cli-1.27.7 → systemlink_cli-1.29.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.27.7
3
+ Version: 1.29.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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "systemlink-cli"
3
- version = "1.27.7"
3
+ version = "1.29.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" }]
@@ -68,6 +68,7 @@ pytest = ">=7.0"
68
68
  pytest-cov = ">=3.0"
69
69
  pytest-timeout = ">=2.1.0"
70
70
  pyinstaller = "^6.14.2"
71
+ jsonschema = ">=4.0"
71
72
 
72
73
  # Typed stubs for third-party libraries used by mypy in CI
73
74
  types-requests = "*"
@@ -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.27.7"
4
+ __version__ = "1.29.1"
@@ -8,7 +8,6 @@ import asyncio
8
8
  import json
9
9
  import sys
10
10
  import urllib.parse
11
- from pathlib import Path
12
11
  from typing import Annotated, Any, Callable, Dict, List, Literal, Optional, TypeVar
13
12
 
14
13
  from mcp.server.mcpserver import MCPServer
@@ -16,6 +15,7 @@ from mcp.types import ToolAnnotations
16
15
  from pydantic import BaseModel, Field
17
16
 
18
17
  from ._version import __version__
18
+ from .mcp_skills import SlcliSkillsExtension
19
19
 
20
20
  server = MCPServer(
21
21
  name="slcli",
@@ -29,6 +29,7 @@ server = MCPServer(
29
29
  "visible data was found; it does not prove that a resource does not exist."
30
30
  ),
31
31
  version=__version__,
32
+ extensions=[SlcliSkillsExtension()],
32
33
  )
33
34
  T = TypeVar("T")
34
35
 
@@ -40,29 +41,6 @@ _READ_ONLY_TOOL_ANNOTATIONS = ToolAnnotations(
40
41
  )
41
42
 
42
43
 
43
- def _reference_root_candidates() -> List[Path]:
44
- """Return candidate packaged-reference directories for source and frozen layouts."""
45
- candidates: List[Path] = []
46
-
47
- meipass = getattr(sys, "_MEIPASS", None)
48
- if meipass:
49
- candidates.append(Path(meipass) / "skills" / "slcli" / "references")
50
-
51
- if getattr(sys, "frozen", False):
52
- candidates.append(Path(sys.executable).resolve().parent / "skills" / "slcli" / "references")
53
-
54
- candidates.append(Path(__file__).resolve().parent / "skills" / "slcli" / "references")
55
- return candidates
56
-
57
-
58
- def _find_reference_root() -> Path:
59
- """Locate the packaged MCP reference directory."""
60
- for candidate in _reference_root_candidates():
61
- if candidate.is_dir():
62
- return candidate
63
- raise FileNotFoundError("Bundled MCP reference files not found.")
64
-
65
-
66
44
  class WorkspaceQueryResponse(BaseModel):
67
45
  """Structured response returned by workspace discovery."""
68
46
 
@@ -70,54 +48,6 @@ class WorkspaceQueryResponse(BaseModel):
70
48
  count: int = Field(description="Number of workspaces in items.")
71
49
 
72
50
 
73
- _CAPABILITIES = """# slcli MCP capabilities
74
-
75
- The server exposes query-oriented tools for the main SystemLink resource types.
76
-
77
- Core discovery tools:
78
- - query_workspaces, query_users
79
- - search_tags, read_tag_values, get_tag_by_path, query_tag_history
80
- - query_systems, query_assets, query_alarms
81
- - query_test_results, get_test_steps
82
- - query_files, query_notebooks
83
- - query_workitems, query_workitem_templates, query_workflows
84
- - query_feeds, query_feed_packages
85
- - query_webapps
86
- - query_policies, query_comments
87
-
88
- Use the corresponding get_* tool when you already know the resource ID/path.
89
- Most query tools support a small set of structured filters plus a raw service
90
- filter when the underlying API supports it.
91
-
92
- Detailed references are available as read-only resources:
93
- - slcli://docs/commands
94
- - slcli://docs/filtering
95
- """
96
-
97
-
98
- @server.resource("slcli://capabilities")
99
- def capabilities() -> str:
100
- """Return the tool-selection rules and reference resource index."""
101
- return _CAPABILITIES
102
-
103
-
104
- def _read_reference(filename: str) -> str:
105
- """Read a packaged slcli skill reference for an MCP resource."""
106
- return (_find_reference_root() / filename).read_text(encoding="utf-8")
107
-
108
-
109
- @server.resource("slcli://docs/commands", mime_type="text/markdown")
110
- def commands_reference() -> str:
111
- """Return the complete slcli command and resource reference."""
112
- return _read_reference("commands.md")
113
-
114
-
115
- @server.resource("slcli://docs/filtering", mime_type="text/markdown")
116
- def filtering_reference() -> str:
117
- """Return filtering and substitution guidance for SystemLink services."""
118
- return _read_reference("filtering.md")
119
-
120
-
121
51
  @server.prompt(
122
52
  name="investigate_failed_test_results",
123
53
  title="Investigate failed test results",
@@ -0,0 +1,490 @@
1
+ """MCP Skills extension support for the bundled slcli Agent Skill."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import re
8
+ import sys
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any, Awaitable, Callable, Dict, List, Mapping, Optional, Tuple
12
+ from urllib.parse import unquote, urlsplit
13
+
14
+ import yaml # type: ignore[import-untyped]
15
+ from mcp.server.context import ServerRequestContext
16
+ from mcp.server.extension import Extension, MethodBinding, ResourceBinding
17
+ from mcp.server.mcpserver import require_client_extension
18
+ from mcp.server.mcpserver.resources import BinaryResource, Resource, TextResource
19
+ from mcp.shared.exceptions import MCPError
20
+ from mcp.types import (
21
+ INVALID_PARAMS,
22
+ METHOD_NOT_FOUND,
23
+ PaginatedRequestParams,
24
+ RequestParams,
25
+ Resource as MCPResource,
26
+ )
27
+ from mcp.types.version import is_version_at_least
28
+ from pydantic import BaseModel, ConfigDict, Field
29
+
30
+ SKILLS_EXTENSION_IDENTIFIER = "io.modelcontextprotocol/skills"
31
+ SKILLS_PROTOCOL_VERSION = "2026-07-28"
32
+ SKILL_URI = "skill://slcli/SKILL.md"
33
+ SKILL_ROOT_URI = "skill://slcli"
34
+ DIRECTORY_MIME_TYPE = "inode/directory"
35
+ SKILLS_CACHE_TTL_MS = 300_000
36
+ SKILLS_CACHE_SCOPE = "public"
37
+ MAX_RESOURCES_PER_SKILL = 512
38
+ MAX_SKILL_SIZE = 16 * 1024 * 1024
39
+
40
+ _SKILL_NAME_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
41
+ _FRONTMATTER_PATTERN = re.compile(r"\A---[ \t]*\r?\n(.*?)\r?\n---[ \t]*(?:\r?\n|\Z)", re.DOTALL)
42
+ _MIME_TYPES = {
43
+ ".html": "text/html",
44
+ ".json": "application/json",
45
+ ".md": "text/markdown",
46
+ ".py": "text/x-python",
47
+ ".svg": "image/svg+xml",
48
+ }
49
+
50
+
51
+ class GetSkillParams(RequestParams):
52
+ """Parameters for the skills/get request."""
53
+
54
+ uri: str
55
+
56
+
57
+ class ListSkillsParams(PaginatedRequestParams):
58
+ """Parameters for the skills/list request."""
59
+
60
+
61
+ class ReadDirectoryParams(PaginatedRequestParams):
62
+ """Parameters for the resources/directory/read request."""
63
+
64
+ uri: str
65
+
66
+
67
+ class SkillResourceManifest(BaseModel):
68
+ """Digest and size metadata for one skill resource."""
69
+
70
+ uri: str
71
+ digest: str
72
+ size: int = Field(ge=0)
73
+
74
+
75
+ class SkillEntry(BaseModel):
76
+ """A complete MCP Skills entry for one Agent Skill."""
77
+
78
+ uri: str
79
+ frontmatter: Dict[str, Any]
80
+ resources: List[SkillResourceManifest]
81
+
82
+
83
+ class ListSkillsResult(BaseModel):
84
+ """Result returned by skills/list."""
85
+
86
+ result_type: str = Field(default="complete", alias="resultType")
87
+ skills: List[SkillEntry]
88
+ ttl_ms: int = Field(default=SKILLS_CACHE_TTL_MS, alias="ttlMs", ge=0)
89
+ cache_scope: str = Field(default=SKILLS_CACHE_SCOPE, alias="cacheScope")
90
+
91
+ model_config = ConfigDict(populate_by_name=True)
92
+
93
+
94
+ class GetSkillResult(BaseModel):
95
+ """Result returned by skills/get."""
96
+
97
+ result_type: str = Field(default="complete", alias="resultType")
98
+ skill: SkillEntry
99
+ ttl_ms: int = Field(default=SKILLS_CACHE_TTL_MS, alias="ttlMs", ge=0)
100
+ cache_scope: str = Field(default=SKILLS_CACHE_SCOPE, alias="cacheScope")
101
+
102
+ model_config = ConfigDict(populate_by_name=True)
103
+
104
+
105
+ class ReadDirectoryResult(BaseModel):
106
+ """Result returned by resources/directory/read."""
107
+
108
+ result_type: str = Field(default="complete", alias="resultType")
109
+ resources: List[MCPResource]
110
+
111
+ model_config = ConfigDict(populate_by_name=True)
112
+
113
+
114
+ @dataclass(frozen=True)
115
+ class SkillFile:
116
+ """One immutable file in the published skill namespace."""
117
+
118
+ relative_path: str
119
+ uri: str
120
+ content: bytes
121
+ mime_type: str
122
+ resource: Resource
123
+ manifest: SkillResourceManifest
124
+
125
+
126
+ @dataclass(frozen=True)
127
+ class SkillCatalog:
128
+ """The validated, immutable view of one published skill."""
129
+
130
+ entry: SkillEntry
131
+ files: Mapping[str, SkillFile]
132
+ directories: Mapping[str, Tuple[MCPResource, ...]]
133
+
134
+ def resources(self) -> List[ResourceBinding]:
135
+ """Return all file resources contributed to the MCP server."""
136
+ return [ResourceBinding(resource=file.resource) for file in self.files.values()]
137
+
138
+ def validate_file_uri(self, uri: str) -> SkillFile:
139
+ """Return a file for a canonical skill URI or raise Invalid params."""
140
+ relative_path = _relative_path_for_uri(uri)
141
+ file = self.files.get(relative_path)
142
+ if file is None:
143
+ raise _invalid_params(f"No skill resource is served at {uri}")
144
+ return file
145
+
146
+ def directory_children(self, uri: str) -> Tuple[MCPResource, ...]:
147
+ """Return direct children for a canonical directory URI."""
148
+ relative_path = _relative_path_for_uri(uri)
149
+ try:
150
+ return self.directories[uri]
151
+ except KeyError as exc:
152
+ if relative_path in self.files:
153
+ raise _invalid_params(f"{uri} is not a directory resource") from exc
154
+ raise _invalid_params(f"No directory resource is served at {uri}") from exc
155
+
156
+
157
+ def _invalid_params(message: str) -> MCPError:
158
+ """Create the standard error for an invalid Skills request."""
159
+ return MCPError(code=INVALID_PARAMS, message=message)
160
+
161
+
162
+ def _modern_only(
163
+ handler: Callable[[ServerRequestContext[Any, Any], Any], Awaitable[Any]],
164
+ ) -> Callable[[ServerRequestContext[Any, Any], Any], Awaitable[Any]]:
165
+ """Restrict an extension method to the target revision and later known revisions."""
166
+
167
+ async def gated(context: ServerRequestContext[Any, Any], params: Any) -> Any:
168
+ if not is_version_at_least(context.protocol_version, SKILLS_PROTOCOL_VERSION):
169
+ raise MCPError(code=METHOD_NOT_FOUND, message="Method not found")
170
+ require_client_extension(context, SKILLS_EXTENSION_IDENTIFIER)
171
+ return await handler(context, params)
172
+
173
+ return gated
174
+
175
+
176
+ def _skill_root_candidates() -> List[Path]:
177
+ """Return source and frozen bundled-skill roots."""
178
+ candidates: List[Path] = []
179
+ meipass = getattr(sys, "_MEIPASS", None)
180
+ if meipass:
181
+ candidates.append(Path(meipass) / "skills" / "slcli")
182
+ if getattr(sys, "frozen", False):
183
+ candidates.append(Path(sys.executable).resolve().parent / "skills" / "slcli")
184
+ candidates.append(Path(__file__).resolve().parent / "skills" / "slcli")
185
+ return candidates
186
+
187
+
188
+ def _find_skill_root() -> Path:
189
+ """Locate the bundled skill directory."""
190
+ for candidate in _skill_root_candidates():
191
+ if candidate.is_dir():
192
+ return candidate
193
+ raise FileNotFoundError("Bundled slcli skill not found.")
194
+
195
+
196
+ def _ensure_regular_file(path: Path) -> None:
197
+ """Reject symlinks and non-regular files in published content."""
198
+ if path.is_symlink():
199
+ raise ValueError(f"Published skill path must not be a symlink: {path}")
200
+ if not path.is_file():
201
+ raise ValueError(f"Published skill path must be a regular file: {path}")
202
+
203
+
204
+ def _published_paths(root: Path) -> List[Path]:
205
+ """Return files allowed by the explicit MCP publication policy."""
206
+ if root.is_symlink() or not root.is_dir():
207
+ raise ValueError(f"Skill root must be a real directory: {root}")
208
+
209
+ skill_file = root / "SKILL.md"
210
+ _ensure_regular_file(skill_file)
211
+ paths = [skill_file]
212
+
213
+ references = root / "references"
214
+ if references.is_symlink() or references.exists():
215
+ if references.is_symlink() or not references.is_dir():
216
+ raise ValueError(f"Skill references must be a real directory: {references}")
217
+ for path in sorted(references.rglob("*")):
218
+ if path.is_symlink():
219
+ raise ValueError(f"Published skill path must not be a symlink: {path}")
220
+ relative_parts = path.relative_to(root).parts
221
+ if any(part.startswith(".") or part == "__pycache__" for part in relative_parts):
222
+ continue
223
+ if path.is_dir():
224
+ continue
225
+ if not path.is_file():
226
+ raise ValueError(f"Published skill path must be a regular file: {path}")
227
+ if path.suffix != ".pyc":
228
+ paths.append(path)
229
+
230
+ scripts = root / "scripts"
231
+ if scripts.is_symlink() or scripts.exists():
232
+ if scripts.is_symlink() or not scripts.is_dir():
233
+ raise ValueError(f"Skill scripts must be a real directory: {scripts}")
234
+ for path in sorted(scripts.iterdir()):
235
+ if path.is_symlink():
236
+ raise ValueError(f"Published skill path must not be a symlink: {path}")
237
+ if path.name.startswith(".") or path.name == "__pycache__":
238
+ continue
239
+ if not path.is_file():
240
+ raise ValueError(f"Published skill path must be a regular file: {path}")
241
+ if path.suffix == ".py":
242
+ paths.append(path)
243
+
244
+ return sorted(paths, key=lambda path: path.relative_to(root).as_posix())
245
+
246
+
247
+ def _frontmatter(content: bytes, path: Path) -> Dict[str, Any]:
248
+ """Parse and validate the complete YAML frontmatter object."""
249
+ try:
250
+ text = content.decode("utf-8")
251
+ except UnicodeDecodeError as exc:
252
+ raise ValueError(f"SKILL.md must be UTF-8: {path}") from exc
253
+ match = _FRONTMATTER_PATTERN.match(text)
254
+ if match is None:
255
+ raise ValueError(f"SKILL.md must begin with YAML frontmatter: {path}")
256
+ parsed = yaml.safe_load(match.group(1))
257
+ if not isinstance(parsed, dict) or not all(isinstance(key, str) for key in parsed):
258
+ raise ValueError(f"SKILL.md frontmatter must be a JSON object: {path}")
259
+ try:
260
+ json.dumps(parsed)
261
+ except (TypeError, ValueError) as exc:
262
+ raise ValueError(f"SKILL.md frontmatter must contain JSON values: {path}") from exc
263
+ name = parsed.get("name")
264
+ description = parsed.get("description")
265
+ if not isinstance(name, str) or not _SKILL_NAME_PATTERN.fullmatch(name) or len(name) > 64:
266
+ raise ValueError(f"SKILL.md has an invalid name: {path}")
267
+ if not isinstance(description, str) or not description.strip():
268
+ raise ValueError(f"SKILL.md must have a non-empty description: {path}")
269
+ return parsed
270
+
271
+
272
+ def _mime_type(path: Path) -> str:
273
+ """Return the explicit MIME type for a published skill file."""
274
+ try:
275
+ return _MIME_TYPES[path.suffix.lower()]
276
+ except KeyError as exc:
277
+ raise ValueError(f"Unsupported published skill file type: {path}") from exc
278
+
279
+
280
+ def _resource(
281
+ uri: str,
282
+ name: str,
283
+ mime_type: str,
284
+ content: bytes,
285
+ description: Optional[str] = None,
286
+ ) -> Resource:
287
+ """Build a resource whose served content matches the indexed bytes."""
288
+ try:
289
+ text = content.decode("utf-8")
290
+ except UnicodeDecodeError:
291
+ return BinaryResource(
292
+ uri=uri,
293
+ name=name,
294
+ description=description,
295
+ mime_type=mime_type,
296
+ data=content,
297
+ )
298
+ return TextResource(
299
+ uri=uri,
300
+ name=name,
301
+ description=description,
302
+ mime_type=mime_type,
303
+ text=text,
304
+ )
305
+
306
+
307
+ def _uri_for(relative_path: str) -> str:
308
+ """Return the canonical URI for a skill-relative POSIX path."""
309
+ return f"{SKILL_ROOT_URI}/{relative_path}"
310
+
311
+
312
+ def _relative_path_for_uri(uri: str) -> str:
313
+ """Validate a skill URI and return its canonical relative path."""
314
+ try:
315
+ parsed = urlsplit(uri)
316
+ except ValueError as exc:
317
+ raise _invalid_params(f"Invalid skill URI: {uri}") from exc
318
+ decoded_path = unquote(parsed.path)
319
+ if (
320
+ parsed.scheme == "skill"
321
+ and parsed.netloc == "slcli"
322
+ and not parsed.query
323
+ and not parsed.fragment
324
+ and decoded_path == ""
325
+ and uri == SKILL_ROOT_URI
326
+ ):
327
+ return ""
328
+ if (
329
+ parsed.scheme != "skill"
330
+ or parsed.netloc != "slcli"
331
+ or parsed.query
332
+ or parsed.fragment
333
+ or "\\" in decoded_path
334
+ or "\x00" in decoded_path
335
+ or any(part in {"", ".", ".."} for part in decoded_path.lstrip("/").split("/"))
336
+ or not decoded_path.startswith("/")
337
+ ):
338
+ raise _invalid_params(f"Invalid skill URI: {uri}")
339
+ relative_path = decoded_path.lstrip("/")
340
+ canonical = _uri_for(relative_path) if relative_path else SKILL_ROOT_URI
341
+ if uri != canonical:
342
+ raise _invalid_params(f"Invalid skill URI: {uri}")
343
+ return relative_path
344
+
345
+
346
+ def build_skill_catalog(root: Path) -> SkillCatalog:
347
+ """Build and fully validate the static catalog for the bundled skill."""
348
+ paths = _published_paths(root)
349
+ if len(paths) > MAX_RESOURCES_PER_SKILL:
350
+ raise ValueError("Bundled slcli skill exceeds the resource limit")
351
+
352
+ contents = {path: path.read_bytes() for path in paths}
353
+ skill_content = contents[root / "SKILL.md"]
354
+ frontmatter = _frontmatter(skill_content, root / "SKILL.md")
355
+ if frontmatter["name"] != root.name:
356
+ raise ValueError("SKILL.md name must match its skill directory")
357
+
358
+ total_size = sum(len(content) for content in contents.values())
359
+ if total_size > MAX_SKILL_SIZE:
360
+ raise ValueError("Bundled slcli skill exceeds the total size limit")
361
+
362
+ files: Dict[str, SkillFile] = {}
363
+ for path, content in contents.items():
364
+ relative_path = path.relative_to(root).as_posix()
365
+ uri = _uri_for(relative_path)
366
+ mime_type = _mime_type(path)
367
+ manifest = SkillResourceManifest(
368
+ uri=uri,
369
+ digest=f"sha256:{hashlib.sha256(content).hexdigest()}",
370
+ size=len(content),
371
+ )
372
+ is_skill_file = relative_path == "SKILL.md"
373
+ files[relative_path] = SkillFile(
374
+ relative_path=relative_path,
375
+ uri=uri,
376
+ content=content,
377
+ mime_type=mime_type,
378
+ resource=_resource(
379
+ uri,
380
+ frontmatter["name"] if is_skill_file else path.name,
381
+ mime_type,
382
+ content,
383
+ frontmatter["description"] if is_skill_file else None,
384
+ ),
385
+ manifest=manifest,
386
+ )
387
+
388
+ directories: Dict[str, List[MCPResource]] = {SKILL_ROOT_URI: []}
389
+ for relative_path in files:
390
+ parts = relative_path.split("/")
391
+ for index in range(1, len(parts)):
392
+ directory_path = "/".join(parts[:index])
393
+ directories.setdefault(_uri_for(directory_path), [])
394
+
395
+ for directory_uri in list(directories):
396
+ relative_directory = _relative_path_for_uri(directory_uri)
397
+ children: Dict[str, MCPResource] = {}
398
+ for relative_path, file in files.items():
399
+ path = Path(relative_path)
400
+ parent = (
401
+ SKILL_ROOT_URI if path.parent == Path(".") else _uri_for(path.parent.as_posix())
402
+ )
403
+ if parent == directory_uri:
404
+ children[path.name] = MCPResource(
405
+ uri=file.uri,
406
+ name=frontmatter["name"] if relative_path == "SKILL.md" else path.name,
407
+ description=(
408
+ frontmatter["description"] if relative_path == "SKILL.md" else None
409
+ ),
410
+ mime_type=file.mime_type,
411
+ )
412
+ elif relative_directory and relative_path.startswith(f"{relative_directory}/"):
413
+ child_name = relative_path[len(relative_directory) + 1 :].split("/", 1)[0]
414
+ child_path = f"{relative_directory}/{child_name}"
415
+ children.setdefault(
416
+ child_name,
417
+ MCPResource(
418
+ uri=_uri_for(child_path), name=child_name, mime_type=DIRECTORY_MIME_TYPE
419
+ ),
420
+ )
421
+ elif not relative_directory and "/" in relative_path:
422
+ child_name = relative_path.split("/", 1)[0]
423
+ children.setdefault(
424
+ child_name,
425
+ MCPResource(
426
+ uri=_uri_for(child_name), name=child_name, mime_type=DIRECTORY_MIME_TYPE
427
+ ),
428
+ )
429
+ directories[directory_uri] = list(children.values())
430
+ directories[directory_uri].sort(key=lambda resource: resource.name or "")
431
+
432
+ manifests = [files[path].manifest for path in sorted(files)]
433
+ entry = SkillEntry(uri=SKILL_URI, frontmatter=frontmatter, resources=manifests)
434
+ return SkillCatalog(
435
+ entry=entry,
436
+ files=files,
437
+ directories={key: tuple(value) for key, value in directories.items()},
438
+ )
439
+
440
+
441
+ class SlcliSkillsExtension(Extension):
442
+ """Serve the packaged slcli Agent Skill through the MCP Skills extension."""
443
+
444
+ identifier = SKILLS_EXTENSION_IDENTIFIER
445
+
446
+ def __init__(self, root: Optional[Path] = None) -> None:
447
+ """Build and validate the static catalog from the bundled skill root."""
448
+ self.catalog = build_skill_catalog(root or _find_skill_root())
449
+
450
+ def settings(self) -> Dict[str, Any]:
451
+ """Advertise optional directory reads."""
452
+ return {"directoryRead": True}
453
+
454
+ def resources(self) -> List[ResourceBinding]:
455
+ """Contribute every published skill file as a normal MCP resource."""
456
+ return self.catalog.resources()
457
+
458
+ def methods(self) -> List[MethodBinding]:
459
+ """Return the Skills extension request handlers."""
460
+ return [
461
+ MethodBinding("skills/list", ListSkillsParams, _modern_only(self._list_skills)),
462
+ MethodBinding("skills/get", GetSkillParams, _modern_only(self._get_skill)),
463
+ MethodBinding(
464
+ "resources/directory/read", ReadDirectoryParams, _modern_only(self._read_directory)
465
+ ),
466
+ ]
467
+
468
+ async def _list_skills(
469
+ self, _: ServerRequestContext[Any, Any], params: ListSkillsParams
470
+ ) -> ListSkillsResult:
471
+ """Return the complete static skill catalog."""
472
+ if params.cursor is not None:
473
+ raise _invalid_params("The static slcli skill catalog does not support pagination")
474
+ return ListSkillsResult(skills=[self.catalog.entry])
475
+
476
+ async def _get_skill(
477
+ self, _: ServerRequestContext[Any, Any], params: GetSkillParams
478
+ ) -> GetSkillResult:
479
+ """Return the catalog entry for the requested skill URI."""
480
+ if params.uri != SKILL_URI:
481
+ raise _invalid_params(f"No skill is served at {params.uri}")
482
+ return GetSkillResult(skill=self.catalog.entry)
483
+
484
+ async def _read_directory(
485
+ self, _: ServerRequestContext[Any, Any], params: ReadDirectoryParams
486
+ ) -> ReadDirectoryResult:
487
+ """Return direct children of a published skill directory."""
488
+ if params.cursor is not None:
489
+ raise _invalid_params("The static slcli skill directories do not support pagination")
490
+ return ReadDirectoryResult(resources=list(self.catalog.directory_children(params.uri)))