datex-studio-cli 0.5.6__tar.gz → 0.5.7__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 (247) hide show
  1. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/PKG-INFO +1 -1
  2. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/pyproject.toml +1 -1
  3. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/__init__.py +1 -1
  4. datex_studio_cli-0.5.7/src/dxs/commands/skills.py +656 -0
  5. datex_studio_cli-0.5.7/src/dxs/core/skills/discovery.py +98 -0
  6. datex_studio_cli-0.5.7/src/dxs/core/skills/inspection.py +163 -0
  7. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/installer.py +13 -2
  8. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/lock.py +37 -15
  9. datex_studio_cli-0.5.7/src/dxs/core/skills/protection.py +118 -0
  10. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/404/index.html +1 -1
  11. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/404.html +1 -1
  12. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/design/capture/index.html +1 -1
  13. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/design/capture/index.txt +1 -1
  14. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/design/index.html +1 -1
  15. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/design/index.txt +1 -1
  16. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/index.html +1 -1
  17. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/index.txt +1 -1
  18. datex_studio_cli-0.5.6/src/dxs/commands/skills.py +0 -442
  19. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.dockerignore +0 -0
  20. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/actionlint.yaml +0 -0
  21. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/workflows/build-image.yml +0 -0
  22. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/workflows/ci.yml +0 -0
  23. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/workflows/promote-prod.yml +0 -0
  24. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/workflows/release.yml +0 -0
  25. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.gitignore +0 -0
  26. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/CONTEXT.md +0 -0
  27. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/README.md +0 -0
  28. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/.env.example +0 -0
  29. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/Dockerfile +0 -0
  30. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/README.md +0 -0
  31. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/apply-local-cache.js +0 -0
  32. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/collector-config.yaml +0 -0
  33. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/docker-compose.yml +0 -0
  34. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/local-test-settings.json +0 -0
  35. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/remote-settings.json +0 -0
  36. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/report-creator.skill +0 -0
  37. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/analyze_corpus.py +0 -0
  38. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/check_wheel_completeness.py +0 -0
  39. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/datadog_errors.py +0 -0
  40. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/generate_rdlx_schema.py +0 -0
  41. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/wavelength_query.py +0 -0
  42. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/__main__.py +0 -0
  43. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/cli.py +0 -0
  44. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/__init__.py +0 -0
  45. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/api.py +0 -0
  46. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/auth.py +0 -0
  47. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/branch.py +0 -0
  48. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/branch_commit_guard.py +0 -0
  49. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/cascade.py +0 -0
  50. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/config.py +0 -0
  51. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/configuration.py +0 -0
  52. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/crm.py +0 -0
  53. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/crm_delivery.py +0 -0
  54. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/crm_time.py +0 -0
  55. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/datasource.py +0 -0
  56. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/devops.py +0 -0
  57. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/document.py +0 -0
  58. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/endpoint.py +0 -0
  59. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/env.py +0 -0
  60. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/explore.py +0 -0
  61. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/function.py +0 -0
  62. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/init.py +0 -0
  63. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/marketplace.py +0 -0
  64. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/__init__.py +0 -0
  65. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/_common.py +0 -0
  66. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/api.py +0 -0
  67. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/create.py +0 -0
  68. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/data.py +0 -0
  69. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/pack.py +0 -0
  70. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/preview.py +0 -0
  71. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/odata.py +0 -0
  72. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/organization.py +0 -0
  73. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/proxy.py +0 -0
  74. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/reference.py +0 -0
  75. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/release_notes.py +0 -0
  76. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/repo.py +0 -0
  77. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/__init__.py +0 -0
  78. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/add_cmds.py +0 -0
  79. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/api.py +0 -0
  80. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/batch.py +0 -0
  81. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/create.py +0 -0
  82. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/data.py +0 -0
  83. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/dataset.py +0 -0
  84. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/edit.py +0 -0
  85. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/folder.py +0 -0
  86. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/preview.py +0 -0
  87. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/schema_cmds.py +0 -0
  88. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/reset.py +0 -0
  89. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/schema.py +0 -0
  90. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/servicepack.py +0 -0
  91. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/source.py +0 -0
  92. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/studio.py +0 -0
  93. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/telemetry.py +0 -0
  94. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/user.py +0 -0
  95. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/validation_report.py +0 -0
  96. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/context.py +0 -0
  97. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/__init__.py +0 -0
  98. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/agent_browser.py +0 -0
  99. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/__init__.py +0 -0
  100. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/app_config.py +0 -0
  101. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/client.py +0 -0
  102. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/endpoints.py +0 -0
  103. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/metadata_models.py +0 -0
  104. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/models.py +0 -0
  105. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/reference_graph.py +0 -0
  106. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/replacements.py +0 -0
  107. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/__init__.py +0 -0
  108. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/browser_pages.py +0 -0
  109. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/datex_tenant.py +0 -0
  110. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/decorators.py +0 -0
  111. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/msal_client.py +0 -0
  112. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/pages/error.html +0 -0
  113. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/pages/success.html +0 -0
  114. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/tenants.py +0 -0
  115. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/token_cache.py +0 -0
  116. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/cache.py +0 -0
  117. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/__init__.py +0 -0
  118. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/flow_generator.py +0 -0
  119. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/flow_models.py +0 -0
  120. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/flow_shape.py +0 -0
  121. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/generator.py +0 -0
  122. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/models.py +0 -0
  123. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/parsers.py +0 -0
  124. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/resolver.py +0 -0
  125. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/type_def_parser.py +0 -0
  126. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/validator.py +0 -0
  127. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/designer/__init__.py +0 -0
  128. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/designer/flow_models.py +0 -0
  129. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/designer/models.py +0 -0
  130. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/designer/parsers.py +0 -0
  131. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/devops/__init__.py +0 -0
  132. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/devops/client.py +0 -0
  133. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/devops/helpers.py +0 -0
  134. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/devops/models.py +0 -0
  135. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/__init__.py +0 -0
  136. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/client.py +0 -0
  137. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/html.py +0 -0
  138. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/metadata_cache.py +0 -0
  139. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/pagination.py +0 -0
  140. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/resolvers.py +0 -0
  141. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/time_entry.py +0 -0
  142. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/__init__.py +0 -0
  143. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/capabilities.py +0 -0
  144. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/client.py +0 -0
  145. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/edmx_parser.py +0 -0
  146. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/metadata.py +0 -0
  147. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/function/__init__.py +0 -0
  148. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/function/generator.py +0 -0
  149. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/function/models.py +0 -0
  150. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/function/validator.py +0 -0
  151. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/graph.py +0 -0
  152. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/onboarding.py +0 -0
  153. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/__init__.py +0 -0
  154. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/csv_fmt.py +0 -0
  155. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/formatter.py +0 -0
  156. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/json_fmt.py +0 -0
  157. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/redaction.py +0 -0
  158. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/yaml_fmt.py +0 -0
  159. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/quartermaster/__init__.py +0 -0
  160. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/quartermaster/client.py +0 -0
  161. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/quartermaster/tenants.py +0 -0
  162. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/release_notes.py +0 -0
  163. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/responses.py +0 -0
  164. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/__init__.py +0 -0
  165. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/frontmatter.py +0 -0
  166. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/layout.py +0 -0
  167. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/resolver.py +0 -0
  168. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/staleness.py +0 -0
  169. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/__init__.py +0 -0
  170. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/config.py +0 -0
  171. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/identity.py +0 -0
  172. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/recorder.py +0 -0
  173. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/scrubber.py +0 -0
  174. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/sender.py +0 -0
  175. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/workitem_resolve.py +0 -0
  176. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/workitems.py +0 -0
  177. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/models/__init__.py +0 -0
  178. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/__init__.py +0 -0
  179. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/folder.py +0 -0
  180. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/manifest.py +0 -0
  181. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/mocks.py +0 -0
  182. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/preview.py +0 -0
  183. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/scaffold.py +0 -0
  184. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/serve_lock.py +0 -0
  185. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/py.typed +0 -0
  186. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/__init__.py +0 -0
  187. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/datasource_binding.py +0 -0
  188. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/engine.py +0 -0
  189. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/field_parser.py +0 -0
  190. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/folder.py +0 -0
  191. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/manifest.py +0 -0
  192. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/models.py +0 -0
  193. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/preview.py +0 -0
  194. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/preview_arjs.py +0 -0
  195. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/schema.py +0 -0
  196. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/svg_renderer.py +0 -0
  197. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/templates/__init__.py +0 -0
  198. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  199. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  200. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/validate_arjs.py +0 -0
  201. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/validator.py +0 -0
  202. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/wrapper.py +0 -0
  203. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/__init__.py +0 -0
  204. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/click_options.py +0 -0
  205. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/clipboard.py +0 -0
  206. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/config.py +0 -0
  207. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/errors.py +0 -0
  208. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/filtering.py +0 -0
  209. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/http_errors.py +0 -0
  210. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/image.py +0 -0
  211. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/paths.py +0 -0
  212. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/resolvers.py +0 -0
  213. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/responses.py +0 -0
  214. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/restricted.py +0 -0
  215. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/sorting.py +0 -0
  216. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/update_check.py +0 -0
  217. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/versioning.py +0 -0
  218. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/__init__.py +0 -0
  219. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/app.py +0 -0
  220. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/design.py +0 -0
  221. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/proxy_app.py +0 -0
  222. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  223. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/247-8b4d885f516d109f.js +0 -0
  224. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  225. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  226. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/538-b06224e65f71c9a1.js +0 -0
  227. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  228. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  229. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  230. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-e3c660196c244183.js +0 -0
  231. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-c083dc9f01f32a04.js +0 -0
  232. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/design/page-3e8e7d9d780d48c9.js +0 -0
  233. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/layout-cec86f90e385371d.js +0 -0
  234. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/page-6fafc1af296a566d.js +0 -0
  235. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  236. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  237. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/main-app-694cf3568b6c9fa9.js +0 -0
  238. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  239. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  240. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  241. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  242. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/css/e7143a5b42f30598.css +0 -0
  243. {datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/lyKgXuwqDbkDi4sPVtE8F → datex_studio_cli-0.5.7/src/dxs/web/static/_next/static/hzAtAMXcc3ZhMB9a3KyPp}/_buildManifest.js +0 -0
  244. {datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/lyKgXuwqDbkDi4sPVtE8F → datex_studio_cli-0.5.7/src/dxs/web/static/_next/static/hzAtAMXcc3ZhMB9a3KyPp}/_ssgManifest.js +0 -0
  245. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/render-cli.js +0 -0
  246. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/report-preview.html +0 -0
  247. {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/report-validate.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datex-studio-cli
3
- Version: 0.5.6
3
+ Version: 0.5.7
4
4
  Summary: CLI for Datex Studio low-code platform, designed for LLM-based AI agents
5
5
  Project-URL: Homepage, https://github.com/datex/datex-studio-cli
6
6
  Project-URL: Documentation, https://github.com/datex/datex-studio-cli
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datex-studio-cli"
7
- version = "0.5.6"
7
+ version = "0.5.7"
8
8
  description = "CLI for Datex Studio low-code platform, designed for LLM-based AI agents"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,4 +1,4 @@
1
1
  """Datex Studio CLI - Command-line interface for Datex Studio platform."""
2
2
 
3
- __version__ = "0.5.6"
3
+ __version__ = "0.5.7"
4
4
  __app_name__ = "dxs"
@@ -0,0 +1,656 @@
1
+ """Skill management: dxs skills (issue #95).
2
+
3
+ Replaces ``npx skills add/update`` for Datex skills. Bundles come from the
4
+ Quartermaster companion service, which resolves the running CLI version
5
+ against the skills repo's ``compat.yaml`` and serves the matching ref —
6
+ clients need no GitHub account, npm, or Node. Files land exactly where
7
+ ``npx skills`` put them (``~/.agents/skills`` + symlinks into each agent's
8
+ skills directory), so Claude Code and Codex need no configuration.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import shlex
15
+ import subprocess
16
+ from datetime import datetime, timezone
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ import click
21
+
22
+ from dxs import __version__
23
+ from dxs.cli import handle_errors
24
+ from dxs.context import DxsContext, pass_context
25
+ from dxs.core.auth import require_auth
26
+ from dxs.core.quartermaster.client import QuartermasterClient
27
+ from dxs.core.skills.discovery import scan_projects
28
+ from dxs.core.skills.frontmatter import installed_depends
29
+ from dxs.core.skills.inspection import (
30
+ DIRECTORY_CONFLICT_GUIDANCE,
31
+ forget_migrated_npx_entries,
32
+ inspect_layout,
33
+ npx_lock_path,
34
+ )
35
+ from dxs.core.skills.installer import install_bundle, remove_skill
36
+ from dxs.core.skills.layout import AGENTS, Layout, layout_for
37
+ from dxs.core.skills.lock import Lock, LockEntry, read_lock, write_lock
38
+ from dxs.core.skills.protection import CheckoutProtection, installation_paths
39
+ from dxs.core.skills.resolver import ResolutionError, closure
40
+ from dxs.utils.errors import DxsError, ValidationError
41
+ from dxs.utils.responses import list_response
42
+ from dxs.utils.restricted import check_restricted_mode_for_option, restrict_in_restricted_mode
43
+
44
+ CATALOG_PATH = "/skills/v1/catalog"
45
+ BUNDLE_PATH = "/skills/v1/bundle"
46
+ MANIFEST_NAME = "MANIFEST.json"
47
+
48
+
49
+ def _scope_options(f: Any) -> Any:
50
+ f = click.option(
51
+ "--project",
52
+ is_flag=True,
53
+ help="Use the current directory (.agents/skills + skills-lock.json) instead of ~",
54
+ )(f)
55
+ return f
56
+
57
+
58
+ def _agent_option(f: Any) -> Any:
59
+ return click.option(
60
+ "--agent",
61
+ "agents",
62
+ multiple=True,
63
+ type=click.Choice(list(AGENTS)),
64
+ help="Agent(s) to link the skill into; default: all",
65
+ )(f)
66
+
67
+
68
+ def _resolve_agents(agents: tuple[str, ...]) -> tuple[str, ...]:
69
+ return tuple(agents) if agents else tuple(AGENTS)
70
+
71
+
72
+ def _fetch_catalog(client: QuartermasterClient) -> dict[str, Any]:
73
+ body = client.get_json(CATALOG_PATH, params={"cli_version": __version__})
74
+ if not isinstance(body, dict) or not isinstance(body.get("skills"), list):
75
+ raise DxsError(
76
+ code="DXS-QM-003",
77
+ message="Quartermaster returned an unexpected catalog shape",
78
+ )
79
+ return body
80
+
81
+
82
+ def _read_manifest(bundle: bytes) -> dict[str, Any]:
83
+ """Read ``MANIFEST.json`` from the bundle root.
84
+
85
+ It used to arrive as an ``X-Skills-Manifest`` response header, but it grows
86
+ with the catalog -- a path and a 64-char sha256 per skill -- and nginx 502s
87
+ when a response header block overflows a proxy buffer, which capped the
88
+ bundle at 27 skills. Missing or unreadable is not fatal: the skills still
89
+ install, the lock just records no hash, so every one reads as outdated.
90
+ """
91
+ import io
92
+ import json
93
+ import tarfile
94
+
95
+ try:
96
+ with tarfile.open(fileobj=io.BytesIO(bundle), mode="r:gz") as tar:
97
+ member = tar.extractfile(MANIFEST_NAME)
98
+ if member is None:
99
+ return {}
100
+ manifest = json.loads(member.read())
101
+ except (tarfile.TarError, KeyError, ValueError, OSError):
102
+ return {}
103
+ return manifest if isinstance(manifest, dict) else {}
104
+
105
+
106
+ def _fetch_bundle(client: QuartermasterClient, names: list[str]) -> tuple[bytes, dict[str, Any]]:
107
+ body, headers = client.get_bytes(
108
+ BUNDLE_PATH, params={"cli_version": __version__, "skill": names}
109
+ )
110
+ return body, {
111
+ "ref": headers.get("x-skills-ref", ""),
112
+ "commit": headers.get("x-skills-commit", ""),
113
+ "manifest": _read_manifest(body),
114
+ }
115
+
116
+
117
+ def _install(
118
+ ctx: DxsContext,
119
+ client: QuartermasterClient,
120
+ layout: Layout,
121
+ names: list[str],
122
+ agents: tuple[str, ...],
123
+ *,
124
+ force: bool,
125
+ status: str,
126
+ ) -> list[dict[str, Any]]:
127
+ """Download ``names``, install them, record the lock, and report per skill."""
128
+ protection = CheckoutProtection()
129
+ protection.assert_writable(
130
+ [
131
+ npx_lock_path(layout),
132
+ *(path for name in names for path in installation_paths(layout, name)),
133
+ ]
134
+ )
135
+ lock = read_lock(layout.lock_path, strict=True)
136
+ read_lock(npx_lock_path(layout), strict=True)
137
+ owned_copies = {
138
+ name: tuple(a for a in entry.agents if a not in entry.conflicts)
139
+ for name, entry in lock.skills.items()
140
+ if entry.link_mode == "copy"
141
+ }
142
+ bundle, meta = _fetch_bundle(client, names)
143
+ results = install_bundle(bundle, layout, agents, force=force, owned_copies=owned_copies)
144
+ now = datetime.now(timezone.utc).isoformat()
145
+ report: list[dict[str, Any]] = []
146
+ conflicts: dict[str, tuple[str, ...]] = {}
147
+ for result in results:
148
+ info = meta["manifest"].get(result.name, {})
149
+ linked = tuple(a for a in agents if a not in result.conflicts)
150
+ previous = lock.skills.get(result.name)
151
+ unresolved = tuple(a for a in previous.conflicts if a not in agents) if previous else ()
152
+ lock.skills[result.name] = LockEntry(
153
+ name=result.name,
154
+ skill_path=f"{info.get('path', '')}/SKILL.md".lstrip("/"),
155
+ computed_hash=str(info.get("hash", "")),
156
+ ref=str(meta["ref"]),
157
+ commit=str(meta["commit"]),
158
+ cli_version=__version__,
159
+ installed_at=now,
160
+ agents=tuple(dict.fromkeys((*lock.skills[result.name].agents, *agents)))
161
+ if result.name in lock.skills
162
+ else agents,
163
+ conflicts=(*unresolved, *result.conflicts),
164
+ link_mode=result.link_mode,
165
+ )
166
+ if result.conflicts:
167
+ conflicts[result.name] = result.conflicts
168
+ report.append(
169
+ {
170
+ "name": result.name,
171
+ "status": status,
172
+ "ref": meta["ref"],
173
+ "commit": meta["commit"],
174
+ "agents": list(linked),
175
+ "link_mode": result.link_mode,
176
+ "conflicts": list(result.conflicts),
177
+ }
178
+ )
179
+ write_lock(layout.lock_path, lock)
180
+ ctx.log(f"Installed {len(results)} skill(s) into {layout.canonical}")
181
+ forget_migrated_npx_entries(
182
+ layout, [r.name for r in results if not lock.skills[r.name].conflicts]
183
+ )
184
+ if conflicts:
185
+ lines = [
186
+ f"{name}: {', '.join(layout.agent_dirs[a].as_posix() + '/' + name for a in ags)}"
187
+ for name, ags in conflicts.items()
188
+ ]
189
+ raise DxsError(
190
+ code="DXS-SKILLS-003",
191
+ message=(
192
+ "Installed, but could not link into agent directories that already hold a real "
193
+ "directory of the same name: " + "; ".join(lines)
194
+ ),
195
+ details={"conflicts": {n: list(a) for n, a in conflicts.items()}},
196
+ suggestions=[
197
+ DIRECTORY_CONFLICT_GUIDANCE,
198
+ ],
199
+ )
200
+ return report
201
+
202
+
203
+ def _lock_or_empty_error(layout: Layout) -> Lock:
204
+ lock = read_lock(layout.lock_path)
205
+ if not lock.skills:
206
+ raise DxsError(
207
+ code="DXS-SKILLS-002",
208
+ message=f"No dxs-managed skills are installed ({layout.scope} scope)",
209
+ suggestions=[
210
+ "Run 'dxs skills add --all' to install every Datex skill",
211
+ "Run 'dxs skills list --available' to see what can be installed",
212
+ ],
213
+ )
214
+ return lock
215
+
216
+
217
+ @click.group()
218
+ def skills() -> None:
219
+ """Manage Datex agent skills (Claude Code, Codex) via Quartermaster."""
220
+
221
+
222
+ @skills.command("add")
223
+ @click.argument("names", nargs=-1)
224
+ @click.option("--all", "install_all", is_flag=True, help="Install every skill in the catalog")
225
+ @_scope_options
226
+ @_agent_option
227
+ @click.option("--force", is_flag=True, help="Replace real directories that block linking")
228
+ @pass_context
229
+ @handle_errors
230
+ @require_auth
231
+ @restrict_in_restricted_mode("writes skill files into agent directories")
232
+ def add_cmd(
233
+ ctx: DxsContext,
234
+ names: tuple[str, ...],
235
+ install_all: bool,
236
+ project: bool,
237
+ agents: tuple[str, ...],
238
+ force: bool,
239
+ ) -> None:
240
+ """Install skills (and their dependencies) for this CLI version.
241
+
242
+ Examples:
243
+ dxs skills add report-creator
244
+ dxs skills add --all
245
+ dxs skills add report-creator --project --agent claude
246
+ """
247
+ if not names and not install_all:
248
+ raise ValidationError(
249
+ message="Name at least one skill, or pass --all",
250
+ code="DXS-SKILLS-005",
251
+ suggestions=["dxs skills list --available", "dxs skills add --all"],
252
+ )
253
+ layout = layout_for(project)
254
+ with QuartermasterClient() as client:
255
+ catalog = _fetch_catalog(client)
256
+ requested = [s["name"] for s in catalog["skills"]] if install_all else list(names)
257
+ try:
258
+ ordered = closure(requested, catalog["skills"])
259
+ except ResolutionError as exc:
260
+ raise DxsError(
261
+ code="DXS-SKILLS-001",
262
+ message=str(exc),
263
+ suggestions=["Run 'dxs skills list --available' to see the catalog"],
264
+ ) from exc
265
+ report = _install(
266
+ ctx, client, layout, ordered, _resolve_agents(agents), force=force, status="installed"
267
+ )
268
+ ctx.output(
269
+ list_response(
270
+ report, "skills", scope=layout.scope, ref=catalog.get("ref"), line=catalog.get("line")
271
+ )
272
+ )
273
+
274
+
275
+ @skills.command("update")
276
+ @click.argument("names", nargs=-1)
277
+ @_scope_options
278
+ @click.option("--force", is_flag=True, help="Replace real directories that block linking")
279
+ @pass_context
280
+ @handle_errors
281
+ @require_auth
282
+ @restrict_in_restricted_mode("rewrites skill files in agent directories")
283
+ def update_cmd(ctx: DxsContext, names: tuple[str, ...], project: bool, force: bool) -> None:
284
+ """Refresh installed skills to what this CLI version expects.
285
+
286
+ Re-downloads a skill when its content changed upstream or when it was
287
+ installed against a different dxs version, or its files/links need repair.
288
+ Legacy Datex npx installations are adopted; retired skills are reported, not removed.
289
+ """
290
+ layout = layout_for(project)
291
+ before = {row["name"]: row for row in inspect_layout(layout)}
292
+ eligible = {name: row for name, row in before.items() if row["status"] != "unmanaged"}
293
+ if not eligible:
294
+ _lock_or_empty_error(layout)
295
+ targets = list(names) if names else sorted(eligible)
296
+ unknown = [n for n in targets if n not in eligible]
297
+ if unknown:
298
+ raise DxsError(
299
+ code="DXS-SKILLS-001",
300
+ message=f"not installed by dxs or npx Datex skills: {', '.join(unknown)}",
301
+ suggestions=["Run 'dxs skills doctor' to inspect installed skills"],
302
+ )
303
+ if all(before[name]["status"] == "protected" for name in targets):
304
+ ctx.output(list_response([before[name] for name in targets], "skills", scope=layout.scope))
305
+ return
306
+ with QuartermasterClient() as client:
307
+ catalog = _fetch_catalog(client)
308
+ by_name = {s["name"]: s for s in catalog["skills"]}
309
+ lock = read_lock(layout.lock_path)
310
+ changed = [
311
+ name
312
+ for name in targets
313
+ if before[name]["status"] != "protected"
314
+ and (
315
+ name not in by_name
316
+ or before[name]["status"] != "ok"
317
+ or lock.skills[name].computed_hash != by_name[name]["hash"]
318
+ )
319
+ ]
320
+ actions, retired, protected = _refresh_layout(
321
+ ctx, client, catalog, layout, before, changed, force=force
322
+ )
323
+ after = {row["name"]: row for row in inspect_layout(layout)}
324
+ report = []
325
+ for name in sorted(set(targets) | set(actions)):
326
+ row = after[name]
327
+ if name in protected:
328
+ row["status"] = "protected"
329
+ row["problems"] = [protected[name]]
330
+ row["fix"] = None
331
+ elif row["status"] == "protected":
332
+ pass
333
+ elif name in retired:
334
+ row["status"] = "retired"
335
+ elif name in actions and row["status"] == "ok":
336
+ row["status"] = (
337
+ "migrated" if before.get(name, {}).get("status") == "legacy" else "updated"
338
+ )
339
+ elif name not in actions:
340
+ row["status"] = "current"
341
+ report.append(row)
342
+ ctx.output(
343
+ list_response(
344
+ report, "skills", scope=layout.scope, ref=catalog.get("ref"), line=catalog.get("line")
345
+ )
346
+ )
347
+ if any(row["status"] == "broken" for row in report):
348
+ raise SystemExit(1)
349
+
350
+
351
+ @skills.command("list")
352
+ @click.option("--available", is_flag=True, help="Also list the catalog for this CLI version")
353
+ @_scope_options
354
+ @pass_context
355
+ @handle_errors
356
+ def list_cmd(ctx: DxsContext, available: bool, project: bool) -> None:
357
+ """List dxs-managed skills (offline), or the whole catalog with --available."""
358
+ layout = layout_for(project)
359
+ lock = read_lock(layout.lock_path)
360
+ if not available:
361
+ installed: list[dict[str, Any]] = [
362
+ {
363
+ "name": e.name,
364
+ "scope": layout.scope,
365
+ "cli_version": e.cli_version,
366
+ "ref": e.ref,
367
+ "commit": e.commit,
368
+ "agents": list(e.agents),
369
+ "link_mode": e.link_mode,
370
+ "installed_at": e.installed_at,
371
+ }
372
+ for _, e in sorted(lock.skills.items())
373
+ ]
374
+ ctx.output(list_response(installed, "skills", scope=layout.scope))
375
+ return
376
+ with QuartermasterClient() as client:
377
+ catalog = _fetch_catalog(client)
378
+ items: list[dict[str, Any]] = []
379
+ for skill in catalog["skills"]:
380
+ entry = lock.skills.get(skill["name"])
381
+ if entry is None:
382
+ status = "available"
383
+ elif entry.computed_hash != skill["hash"] or entry.cli_version != __version__:
384
+ status = "outdated"
385
+ else:
386
+ status = "current"
387
+ items.append(
388
+ {
389
+ "name": skill["name"],
390
+ "description": skill.get("description", ""),
391
+ "depends": list(skill.get("depends", [])),
392
+ "installed": entry is not None,
393
+ "status": status,
394
+ }
395
+ )
396
+ ctx.output(
397
+ list_response(
398
+ items, "skills", scope=layout.scope, ref=catalog.get("ref"), line=catalog.get("line")
399
+ )
400
+ )
401
+
402
+
403
+ @skills.command("remove")
404
+ @click.argument("names", nargs=-1, required=True)
405
+ @_scope_options
406
+ @pass_context
407
+ @handle_errors
408
+ @restrict_in_restricted_mode("deletes skill files from agent directories")
409
+ def remove_cmd(ctx: DxsContext, names: tuple[str, ...], project: bool) -> None:
410
+ """Remove dxs-managed skills. Dependents that remain are reported."""
411
+ layout = layout_for(project)
412
+ lock = read_lock(layout.lock_path)
413
+ unknown = [n for n in names if n not in lock.skills]
414
+ if unknown:
415
+ raise DxsError(
416
+ code="DXS-SKILLS-001",
417
+ message=f"not installed by dxs: {', '.join(unknown)}",
418
+ suggestions=["Run 'dxs skills list' to see what dxs manages"],
419
+ )
420
+ report = []
421
+ for name in names:
422
+ entry = lock.skills[name]
423
+ dependents = sorted(
424
+ other
425
+ for other in lock.skills
426
+ if other not in names and name in installed_depends(layout.canonical / other)
427
+ )
428
+ installed_agents = tuple(a for a in entry.agents if a not in entry.conflicts)
429
+ removed = remove_skill(name, layout, installed_agents, entry.link_mode)
430
+ del lock.skills[name]
431
+ if dependents:
432
+ ctx.log(f"Warning: {name} is still required by {', '.join(dependents)}")
433
+ report.append({"name": name, "removed": removed, "dependents": dependents})
434
+ write_lock(layout.lock_path, lock)
435
+ ctx.output(list_response(report, "skills", scope=layout.scope))
436
+
437
+
438
+ def _refresh_layout(
439
+ ctx: DxsContext,
440
+ client: QuartermasterClient,
441
+ catalog: dict[str, Any],
442
+ layout: Layout,
443
+ before: dict[str, dict[str, Any]],
444
+ targets: list[str],
445
+ *,
446
+ force: bool,
447
+ ) -> tuple[set[str], set[str], dict[str, str]]:
448
+ """Refresh dependency closures without broadening unrelated agent placements."""
449
+ by_name = {s["name"]: s for s in catalog["skills"]}
450
+ lock = read_lock(layout.lock_path)
451
+ retired = {name for name in targets if name not in by_name}
452
+ placements: dict[str, tuple[str, ...]] = {}
453
+ protected: dict[str, str] = {}
454
+ for name in targets:
455
+ if name in retired:
456
+ continue
457
+ dependencies = closure([name], catalog["skills"])
458
+ protected_dependencies = [
459
+ n for n in dependencies if before.get(n, {}).get("status") == "protected"
460
+ ]
461
+ if protected_dependencies:
462
+ protected[name] = (
463
+ "Skipped: dependency belongs to a protected contributor checkout: "
464
+ + ", ".join(protected_dependencies)
465
+ )
466
+ continue
467
+ for dependency in dependencies:
468
+ existing = before.get(dependency)
469
+ if existing and existing["status"] == "unmanaged":
470
+ raise DxsError(
471
+ code="DXS-SKILLS-007",
472
+ message=f"Cannot replace unmanaged dependency {dependency} in {layout.root}",
473
+ )
474
+ agents = tuple(before[name]["agents"])
475
+ previous = placements.get(dependency, tuple(existing["agents"]) if existing else ())
476
+ placements[dependency] = tuple(dict.fromkeys((*previous, *agents)))
477
+ groups: dict[tuple[str, ...], list[str]] = {}
478
+ for name, agents in placements.items():
479
+ entry = lock.skills.get(name)
480
+ if (
481
+ name not in targets
482
+ and entry
483
+ and before[name]["status"] == "ok"
484
+ and entry.computed_hash == by_name[name]["hash"]
485
+ and set(agents) == set(entry.agents)
486
+ ):
487
+ continue
488
+ if any(agent not in AGENTS for agent in agents):
489
+ raise DxsError(code="DXS-SKILLS-007", message=f"Unsupported agent placement for {name}")
490
+ groups.setdefault(agents, []).append(name)
491
+ attempted: set[str] = set()
492
+ for agents, names in groups.items():
493
+ try:
494
+ _install(ctx, client, layout, names, agents, force=force, status="updated")
495
+ except DxsError as exc:
496
+ if exc.code != "DXS-SKILLS-003":
497
+ raise
498
+ # Partial installs are recorded; the caller reports their health.
499
+ attempted.update(names)
500
+ return attempted, retired, protected
501
+
502
+
503
+ @require_auth
504
+ def _repair_layouts(ctx: DxsContext, layouts: list[Layout], *, force: bool) -> list[dict[str, Any]]:
505
+ reports: list[dict[str, Any]] = []
506
+ with QuartermasterClient() as client:
507
+ catalog = _fetch_catalog(client)
508
+ for layout in layouts:
509
+ before = {row["name"]: row for row in inspect_layout(layout)}
510
+ targets = [
511
+ name
512
+ for name, row in before.items()
513
+ if row["status"] in ("broken", "stale", "legacy")
514
+ ]
515
+ actions, retired, protected = _refresh_layout(
516
+ ctx, client, catalog, layout, before, targets, force=force
517
+ )
518
+ for row in inspect_layout(layout):
519
+ if row["name"] in protected:
520
+ row["status"] = "protected"
521
+ row["problems"] = [protected[row["name"]]]
522
+ row["fix"] = None
523
+ elif row["name"] in retired:
524
+ row["status"] = "retired"
525
+ row["problems"].append(
526
+ "No longer available in the catalog for this CLI version"
527
+ )
528
+ row["fix"] = None
529
+ elif row["name"] in actions and row["status"] == "ok":
530
+ row["action"] = (
531
+ "migrated"
532
+ if before.get(row["name"], {}).get("status") == "legacy"
533
+ else "repaired"
534
+ )
535
+ reports.append(row)
536
+ return reports
537
+
538
+
539
+ @skills.command("doctor")
540
+ @click.option("--project", is_flag=True, help="Inspect only current-project skills")
541
+ @click.option("--user", "user_only", is_flag=True, help="Inspect only user skills")
542
+ @click.option(
543
+ "--scan",
544
+ "scan_roots",
545
+ multiple=True,
546
+ type=click.Path(exists=True, file_okay=False, path_type=Path),
547
+ help="Search this workspace for Datex project installs (repeatable; replaces default scopes)",
548
+ )
549
+ @click.option(
550
+ "--max-depth",
551
+ default=4,
552
+ show_default=True,
553
+ type=click.IntRange(0, 64),
554
+ help="Scan depth below each root; zero inspects only the root",
555
+ )
556
+ @click.option(
557
+ "--max-directories",
558
+ default=10000,
559
+ show_default=True,
560
+ type=click.IntRange(min=1),
561
+ help="Maximum directories visited across scan roots",
562
+ )
563
+ @click.option("--fix", is_flag=True, help="Migrate Datex skills and repair managed installations")
564
+ @click.option("--force", is_flag=True, help="With --fix, replace real directories blocking links")
565
+ @pass_context
566
+ @handle_errors
567
+ def doctor_cmd(
568
+ ctx: DxsContext,
569
+ project: bool,
570
+ user_only: bool,
571
+ scan_roots: tuple[Path, ...],
572
+ max_depth: int,
573
+ max_directories: int,
574
+ fix: bool,
575
+ force: bool,
576
+ ) -> None:
577
+ """Inspect user/current-project skills and scan the parent directory for other projects.
578
+
579
+ Scans are bounded by --max-depth and --max-directories. Use --fix to repair
580
+ all reported Datex installations, or --project/--user to limit the scope.
581
+ """
582
+ if force and not fix:
583
+ raise click.UsageError("--force requires --fix")
584
+ if fix:
585
+ check_restricted_mode_for_option("--fix", "rewrites skill files and installer records")
586
+ if sum((project, user_only, bool(scan_roots))) > 1:
587
+ raise click.UsageError("Choose only one of --project, --user, or --scan")
588
+ protection = CheckoutProtection()
589
+ default_scan = not (project or user_only or scan_roots)
590
+ roots = (Path.cwd().parent,) if default_scan else scan_roots
591
+ scan = (
592
+ scan_projects(
593
+ roots, max_depth=max_depth, max_directories=max_directories, protection=protection
594
+ )
595
+ if roots
596
+ else None
597
+ )
598
+ if default_scan:
599
+ # Always inspect the user and current project, even when their files are
600
+ # untracked or a scan limit prevents discovery. Prefer user scope if the
601
+ # home directory also appears among discovered project roots.
602
+ layouts = [layout_for(False), layout_for(True), *(scan.layouts if scan else [])]
603
+ by_root: dict[Path, Layout] = {}
604
+ for layout in layouts:
605
+ by_root.setdefault(layout.root.resolve(), layout)
606
+ layouts = list(by_root.values())
607
+ elif scan is not None:
608
+ layouts = scan.layouts
609
+ else:
610
+ layouts = [layout_for(project)]
611
+ report: list[dict[str, Any]] = []
612
+ errors: list[str] = []
613
+ valid_layouts: list[Layout] = []
614
+ for layout in layouts:
615
+ try:
616
+ report.extend(inspect_layout(layout, protection))
617
+ valid_layouts.append(layout)
618
+ except (DxsError, OSError) as exc:
619
+ errors.append(f"{layout.scope} ({layout.root}): {exc}")
620
+ if fix and any(row["status"] in ("broken", "stale", "legacy") for row in report):
621
+ report = _repair_layouts(ctx, valid_layouts, force=force)
622
+ if scan is not None:
623
+ for row in report:
624
+ if row["fix"] and row["scope"] == "project":
625
+ quoted_root = (
626
+ subprocess.list2cmdline([row["root"]])
627
+ if os.name == "nt"
628
+ else shlex.quote(row["root"])
629
+ )
630
+ row["fix"] = f"dxs skills doctor --scan {quoted_root} --max-depth 0 --fix" + (
631
+ " --force" if row["status"] == "broken" else ""
632
+ )
633
+ metadata: dict[str, Any] = {
634
+ "scope": "all" if default_scan else "scan" if scan else "project" if project else "user",
635
+ "cli_version": __version__,
636
+ }
637
+ if scan is not None:
638
+ metadata["scan"] = {
639
+ "roots": [str(p.resolve()) for p in roots],
640
+ "default": default_scan,
641
+ "visited": scan.visited,
642
+ "depth_limited": scan.depth_limited,
643
+ "limit_reached": scan.limit_reached,
644
+ "errors": scan.errors,
645
+ "max_depth": max_depth,
646
+ "max_directories": max_directories,
647
+ }
648
+ metadata["protected_checkouts"] = sorted(str(p) for p in protection.protected)
649
+ metadata["errors"] = errors
650
+ ctx.output(list_response(report, "skills", **metadata))
651
+ if errors:
652
+ raise SystemExit(1)
653
+ if scan is not None and (scan.limit_reached or scan.errors):
654
+ raise SystemExit(1)
655
+ if any(row["status"] not in ("ok", "unmanaged", "protected") for row in report):
656
+ raise SystemExit(1)