datex-studio-cli 0.5.4__tar.gz → 0.5.6__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 (248) hide show
  1. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.github/workflows/promote-prod.yml +16 -2
  2. datex_studio_cli-0.5.6/.github/workflows/release.yml +178 -0
  3. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/PKG-INFO +1 -1
  4. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/pyproject.toml +2 -1
  5. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/__init__.py +1 -1
  6. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/cli.py +98 -25
  7. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/branch.py +509 -5
  8. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/document.py +19 -8
  9. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/init.py +36 -18
  10. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/odata.py +30 -17
  11. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/release_notes.py +53 -31
  12. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/schema.py +679 -9
  13. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/source.py +200 -254
  14. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/__init__.py +8 -0
  15. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/app_config.py +89 -1
  16. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/endpoints.py +82 -1
  17. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/metadata_models.py +35 -1
  18. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/models.py +17 -5
  19. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/datex_tenant.py +39 -2
  20. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/flow_generator.py +2 -2
  21. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/flow_shape.py +24 -16
  22. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/validator.py +5 -4
  23. datex_studio_cli-0.5.6/src/dxs/core/footprint/capabilities.py +156 -0
  24. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/footprint/edmx_parser.py +104 -1
  25. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/onboarding.py +11 -2
  26. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/release_notes.py +72 -57
  27. datex_studio_cli-0.5.6/src/dxs/core/workitem_resolve.py +474 -0
  28. datex_studio_cli-0.5.6/src/dxs/core/workitems.py +332 -0
  29. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/errors.py +4 -0
  30. datex_studio_cli-0.5.6/src/dxs/utils/http_errors.py +109 -0
  31. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/app.py +1 -0
  32. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/404/index.html +1 -1
  33. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/404.html +1 -1
  34. datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/chunks/247-8b4d885f516d109f.js +1 -0
  35. datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/chunks/app/design/page-3e8e7d9d780d48c9.js +1 -0
  36. datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/chunks/app/page-6fafc1af296a566d.js +1 -0
  37. datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/css/e7143a5b42f30598.css +3 -0
  38. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/design/capture/index.html +1 -1
  39. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/design/capture/index.txt +2 -2
  40. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/design/index.html +1 -1
  41. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/design/index.txt +3 -3
  42. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/index.html +1 -1
  43. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/index.txt +3 -3
  44. datex_studio_cli-0.5.4/.github/workflows/release.yml +0 -108
  45. datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -1
  46. datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/chunks/app/design/page-689cd5b9e1d9a74e.js +0 -1
  47. datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/chunks/app/page-f0380b0f29983264.js +0 -1
  48. datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -3
  49. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.dockerignore +0 -0
  50. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.github/actionlint.yaml +0 -0
  51. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.github/workflows/build-image.yml +0 -0
  52. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.github/workflows/ci.yml +0 -0
  53. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.gitignore +0 -0
  54. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/CONTEXT.md +0 -0
  55. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/README.md +0 -0
  56. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/.env.example +0 -0
  57. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/Dockerfile +0 -0
  58. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/README.md +0 -0
  59. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/apply-local-cache.js +0 -0
  60. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/collector-config.yaml +0 -0
  61. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/docker-compose.yml +0 -0
  62. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/local-test-settings.json +0 -0
  63. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/remote-settings.json +0 -0
  64. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/report-creator.skill +0 -0
  65. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/analyze_corpus.py +0 -0
  66. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/check_wheel_completeness.py +0 -0
  67. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/datadog_errors.py +0 -0
  68. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/generate_rdlx_schema.py +0 -0
  69. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/wavelength_query.py +0 -0
  70. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/__main__.py +0 -0
  71. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/__init__.py +0 -0
  72. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/api.py +0 -0
  73. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/auth.py +0 -0
  74. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/branch_commit_guard.py +0 -0
  75. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/cascade.py +0 -0
  76. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/config.py +0 -0
  77. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/configuration.py +0 -0
  78. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/crm.py +0 -0
  79. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/crm_delivery.py +0 -0
  80. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/crm_time.py +0 -0
  81. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/datasource.py +0 -0
  82. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/devops.py +0 -0
  83. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/endpoint.py +0 -0
  84. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/env.py +0 -0
  85. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/explore.py +0 -0
  86. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/function.py +0 -0
  87. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/marketplace.py +0 -0
  88. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/__init__.py +0 -0
  89. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/_common.py +0 -0
  90. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/api.py +0 -0
  91. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/create.py +0 -0
  92. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/data.py +0 -0
  93. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/pack.py +0 -0
  94. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/preview.py +0 -0
  95. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/organization.py +0 -0
  96. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/proxy.py +0 -0
  97. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/reference.py +0 -0
  98. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/repo.py +0 -0
  99. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/__init__.py +0 -0
  100. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/add_cmds.py +0 -0
  101. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/api.py +0 -0
  102. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/batch.py +0 -0
  103. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/create.py +0 -0
  104. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/data.py +0 -0
  105. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/dataset.py +0 -0
  106. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/edit.py +0 -0
  107. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/folder.py +0 -0
  108. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/preview.py +0 -0
  109. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/schema_cmds.py +0 -0
  110. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/reset.py +0 -0
  111. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/servicepack.py +0 -0
  112. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/skills.py +0 -0
  113. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/studio.py +0 -0
  114. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/telemetry.py +0 -0
  115. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/user.py +0 -0
  116. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/validation_report.py +0 -0
  117. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/context.py +0 -0
  118. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/__init__.py +0 -0
  119. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/agent_browser.py +0 -0
  120. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/client.py +0 -0
  121. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/reference_graph.py +0 -0
  122. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/replacements.py +0 -0
  123. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/__init__.py +0 -0
  124. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/browser_pages.py +0 -0
  125. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/decorators.py +0 -0
  126. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/msal_client.py +0 -0
  127. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/pages/error.html +0 -0
  128. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/pages/success.html +0 -0
  129. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/tenants.py +0 -0
  130. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/token_cache.py +0 -0
  131. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/cache.py +0 -0
  132. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/__init__.py +0 -0
  133. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/flow_models.py +0 -0
  134. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/generator.py +0 -0
  135. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/models.py +0 -0
  136. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/parsers.py +0 -0
  137. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/resolver.py +0 -0
  138. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/type_def_parser.py +0 -0
  139. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/designer/__init__.py +0 -0
  140. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/designer/flow_models.py +0 -0
  141. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/designer/models.py +0 -0
  142. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/designer/parsers.py +0 -0
  143. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/devops/__init__.py +0 -0
  144. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/devops/client.py +0 -0
  145. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/devops/helpers.py +0 -0
  146. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/devops/models.py +0 -0
  147. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/__init__.py +0 -0
  148. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/client.py +0 -0
  149. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/html.py +0 -0
  150. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/metadata_cache.py +0 -0
  151. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/pagination.py +0 -0
  152. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/resolvers.py +0 -0
  153. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/time_entry.py +0 -0
  154. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/footprint/__init__.py +0 -0
  155. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/footprint/client.py +0 -0
  156. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/footprint/metadata.py +0 -0
  157. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/function/__init__.py +0 -0
  158. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/function/generator.py +0 -0
  159. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/function/models.py +0 -0
  160. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/function/validator.py +0 -0
  161. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/graph.py +0 -0
  162. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/__init__.py +0 -0
  163. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/csv_fmt.py +0 -0
  164. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/formatter.py +0 -0
  165. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/json_fmt.py +0 -0
  166. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/redaction.py +0 -0
  167. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/yaml_fmt.py +0 -0
  168. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/quartermaster/__init__.py +0 -0
  169. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/quartermaster/client.py +0 -0
  170. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/quartermaster/tenants.py +0 -0
  171. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/responses.py +0 -0
  172. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/__init__.py +0 -0
  173. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/frontmatter.py +0 -0
  174. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/installer.py +0 -0
  175. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/layout.py +0 -0
  176. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/lock.py +0 -0
  177. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/resolver.py +0 -0
  178. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/staleness.py +0 -0
  179. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/__init__.py +0 -0
  180. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/config.py +0 -0
  181. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/identity.py +0 -0
  182. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/recorder.py +0 -0
  183. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/scrubber.py +0 -0
  184. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/sender.py +0 -0
  185. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/models/__init__.py +0 -0
  186. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/__init__.py +0 -0
  187. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/folder.py +0 -0
  188. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/manifest.py +0 -0
  189. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/mocks.py +0 -0
  190. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/preview.py +0 -0
  191. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/scaffold.py +0 -0
  192. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/serve_lock.py +0 -0
  193. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/py.typed +0 -0
  194. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/__init__.py +0 -0
  195. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/datasource_binding.py +0 -0
  196. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/engine.py +0 -0
  197. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/field_parser.py +0 -0
  198. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/folder.py +0 -0
  199. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/manifest.py +0 -0
  200. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/models.py +0 -0
  201. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/preview.py +0 -0
  202. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/preview_arjs.py +0 -0
  203. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/schema.py +0 -0
  204. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/svg_renderer.py +0 -0
  205. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/templates/__init__.py +0 -0
  206. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  207. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  208. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/validate_arjs.py +0 -0
  209. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/validator.py +0 -0
  210. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/wrapper.py +0 -0
  211. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/__init__.py +0 -0
  212. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/click_options.py +0 -0
  213. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/clipboard.py +0 -0
  214. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/config.py +0 -0
  215. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/filtering.py +0 -0
  216. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/image.py +0 -0
  217. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/paths.py +0 -0
  218. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/resolvers.py +0 -0
  219. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/responses.py +0 -0
  220. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/restricted.py +0 -0
  221. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/sorting.py +0 -0
  222. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/update_check.py +0 -0
  223. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/versioning.py +0 -0
  224. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/__init__.py +0 -0
  225. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/design.py +0 -0
  226. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/proxy_app.py +0 -0
  227. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  228. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  229. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  230. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/538-b06224e65f71c9a1.js +0 -0
  231. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  232. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  233. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  234. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-e3c660196c244183.js +0 -0
  235. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-c083dc9f01f32a04.js +0 -0
  236. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/app/layout-cec86f90e385371d.js +0 -0
  237. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  238. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  239. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/main-app-694cf3568b6c9fa9.js +0 -0
  240. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  241. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  242. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  243. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  244. {datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/CAvN0dC1A3kKCmq1ZHlgY → datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/lyKgXuwqDbkDi4sPVtE8F}/_buildManifest.js +0 -0
  245. {datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/CAvN0dC1A3kKCmq1ZHlgY → datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/lyKgXuwqDbkDi4sPVtE8F}/_ssgManifest.js +0 -0
  246. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/render-cli.js +0 -0
  247. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/report-preview.html +0 -0
  248. {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/report-validate.html +0 -0
@@ -82,6 +82,7 @@ jobs:
82
82
  fi
83
83
  - name: Commit the prod digest bump
84
84
  run: |
85
+ set -euo pipefail
85
86
  digest='${{ steps.resolve.outputs.digest }}'
86
87
  sed -i -E "s|^ digest:.*| digest: \"${digest}\"|" quartermaster/deploy/prod/values.yaml
87
88
  if git diff --quiet; then
@@ -94,11 +95,24 @@ jobs:
94
95
  git commit \
95
96
  -m "chore(deploy): prod → ${digest}" \
96
97
  -m "Promoted by @${{ github.actor }} via promote-prod (ref input: ${{ inputs.ref }})"
98
+ # Same collision release.yml handles: main moves under us while the
99
+ # job runs. Only one ref goes up here, so there is nothing for
100
+ # --atomic to make atomic — the asymmetry with release.yml is the
101
+ # point, not an oversight. What does carry over is checking the
102
+ # rebase. Unchecked, a conflicted one left HEAD detached at main's
103
+ # tip — someone else's promotion — where the next iteration's push is
104
+ # "Everything up-to-date" and the loop exits 0. A green run that
105
+ # promoted nothing, on the workflow that moves prod.
97
106
  for attempt in 1 2 3; do
98
107
  if git push origin HEAD:main; then
99
108
  exit 0
100
109
  fi
101
- git pull --rebase origin main
110
+ echo "push attempt ${attempt} lost a race with another push to main; rebasing" >&2
111
+ if ! git pull --rebase origin main; then
112
+ git rebase --abort || true
113
+ echo "could not rebase the prod bump onto main — nothing was pushed; re-dispatch once main is settled" >&2
114
+ exit 1
115
+ fi
102
116
  done
103
- echo "prod bump push failed after 3 attempts" >&2
117
+ echo "prod bump push failed after 3 attempts — nothing landed, re-dispatch" >&2
104
118
  exit 1
@@ -0,0 +1,178 @@
1
+ # The CLI's PyPI release lane (spec §5), retiring publish.sh and ~/.pypirc.
2
+ # Two ways in, one publish path:
3
+ # - tag push vX.Y.Z — versions must already be aligned across pyproject,
4
+ # __init__, and uv.lock (the uv.lock entry is the one nothing else updates)
5
+ # - manual dispatch with an optional `version` input — the workflow bumps
6
+ # pyproject/__init__/uv.lock, commits to main, tags vX.Y.Z, then publishes.
7
+ # A blank input publishes the version already in the files (the retry lane
8
+ # for a release that failed after its bump commit landed).
9
+ # The bump's branch+tag push is atomic and rebase-retried: main pushes race
10
+ # with it for the whole CI wait, and a lost race must leave nothing behind
11
+ # rather than a bump commit with no tag.
12
+ # Both lanes: full CI → version-agreement assert → frontend build with a hard
13
+ # existence check (the gitignored web/static/ ships via hatch `artifacts`,
14
+ # which silently includes nothing when absent) → uv build + publish via PyPI
15
+ # trusted publishing.
16
+ # Prerequisite: a trusted publisher for this repo/workflow configured on PyPI.
17
+ name: Release to PyPI
18
+
19
+ on:
20
+ push:
21
+ tags: ["v*"]
22
+ workflow_dispatch:
23
+ inputs:
24
+ version:
25
+ description: "New version (X.Y.Z) — bumps files, commits to main, tags, publishes. Blank — publish the version already in the files."
26
+ required: false
27
+ type: string
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ concurrency:
33
+ group: release-pypi
34
+
35
+ jobs:
36
+ ci:
37
+ uses: ./.github/workflows/ci.yml
38
+
39
+ publish:
40
+ needs: ci
41
+ runs-on: arc-dxs
42
+ timeout-minutes: 30
43
+ permissions:
44
+ contents: write # the dispatch lane pushes the bump commit + tag
45
+ id-token: write # PyPI trusted publishing
46
+ steps:
47
+ - uses: actions/checkout@v7
48
+ with:
49
+ # The bump lane rebases onto main when it loses a push race; a
50
+ # depth-1 checkout has no history to rebase against.
51
+ fetch-depth: 0
52
+ - uses: astral-sh/setup-uv@v10.0.1
53
+ with:
54
+ enable-cache: true
55
+ cache-suffix: release
56
+ download-from-astral-mirror: false
57
+ - run: uv python install 3.13
58
+ - name: Bump version, commit, and tag (dispatch with a version input)
59
+ if: github.event_name == 'workflow_dispatch' && inputs.version != ''
60
+ env:
61
+ VERSION: ${{ inputs.version }}
62
+ run: |
63
+ set -euo pipefail
64
+ if [ "${GITHUB_REF}" != "refs/heads/main" ]; then
65
+ echo "version bumps may only be dispatched from main (got ${GITHUB_REF})" >&2
66
+ exit 1
67
+ fi
68
+ if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
69
+ echo "version must be X.Y.Z (got '$VERSION')" >&2
70
+ exit 1
71
+ fi
72
+ tag="v$VERSION"
73
+ # The version-reuse guard, checked before anything is written: a taken
74
+ # version fails here rather than surfacing later as a push rejection
75
+ # that reads like a lost race.
76
+ if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
77
+ echo "${tag} already exists on origin — pick a new version, or re-dispatch with a blank version input to publish the version already in the files" >&2
78
+ exit 1
79
+ fi
80
+ # A tag is permanent, and the agreement assert below runs after this
81
+ # step — too late to stop a bad one. A tag pushed onto files that
82
+ # disagree burns the version outright: the assert then fails the
83
+ # release, and the version can never be retried either, because the
84
+ # preflight above refuses a tag that exists. So agreement is checked
85
+ # here, before either lane pushes. (The assert step stays: it is the
86
+ # only check for the tag-push and blank-version lanes, which never
87
+ # reach this step.)
88
+ assert_versions_agree() {
89
+ local pyproject init lock
90
+ pyproject=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
91
+ init=$(grep -m1 '__version__' src/dxs/__init__.py | cut -d'"' -f2)
92
+ lock=$(awk '/^name = "datex-studio-cli"$/ {found=1} found && /^version = / {print; exit}' uv.lock | cut -d'"' -f2)
93
+ if [ "$pyproject" != "$VERSION" ] || [ "$init" != "$VERSION" ] || [ "$lock" != "$VERSION" ]; then
94
+ echo "version files disagree with ${VERSION} (pyproject=${pyproject} __init__=${init} uv.lock=${lock}) — align all three, run 'uv lock', commit, then re-dispatch; nothing was pushed" >&2
95
+ exit 1
96
+ fi
97
+ }
98
+ current=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
99
+ if [ "$VERSION" = "$current" ]; then
100
+ # The bump commit already landed and only the tag is missing (a
101
+ # half-done state older runs could leave behind). Nothing to push to
102
+ # main, so no race to lose — but a partial bump, where pyproject
103
+ # moved and __init__/uv.lock did not, reaches exactly here.
104
+ assert_versions_agree
105
+ git tag "${tag}"
106
+ git push origin "refs/tags/${tag}"
107
+ exit 0
108
+ fi
109
+ sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
110
+ sed -i "s/^__version__ = \".*\"/__version__ = \"$VERSION\"/" src/dxs/__init__.py
111
+ uv lock
112
+ git config user.name "github-actions[bot]"
113
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
114
+ git add pyproject.toml src/dxs/__init__.py uv.lock
115
+ git commit -m "Bump version to $VERSION"
116
+ assert_versions_agree
117
+ # Branch and tag go up together. The race window here is the whole CI
118
+ # wait, and any merge during it triggers a staging-digest bump that
119
+ # pushes to main — so losing a push is normal and gets a rebase-retry,
120
+ # exactly as promote-prod.yml does. --atomic is what keeps a lost race
121
+ # from being worse than a retry: without it a branch push that lands
122
+ # ahead of a failing tag push leaves main carrying "Bump version to
123
+ # X.Y.Z" with no tag and nothing published.
124
+ for attempt in 1 2 3; do
125
+ # Re-pointed on every attempt, not once before the loop: a rebase
126
+ # replays the bump onto a new commit, and the tag has to follow it
127
+ # rather than the commit the rebase left behind.
128
+ git tag -f "${tag}"
129
+ if git push --atomic origin HEAD:main "refs/tags/${tag}"; then
130
+ exit 0
131
+ fi
132
+ # The preflight cleared this tag, so a rejection is normally the
133
+ # lost race a rebase fixes. If the tag itself appeared meanwhile,
134
+ # rebasing can never help and the advice below would send the
135
+ # operator to a re-dispatch the preflight then refuses.
136
+ if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
137
+ echo "${tag} appeared on origin while this run was pushing — nothing landed from this run; pick a new version" >&2
138
+ exit 1
139
+ fi
140
+ echo "push attempt ${attempt} lost a race with another push to main; rebasing" >&2
141
+ if ! git pull --rebase origin main; then
142
+ git rebase --abort || true
143
+ echo "could not rebase the bump commit onto main — nothing was pushed; re-dispatch the same version once main is settled" >&2
144
+ exit 1
145
+ fi
146
+ done
147
+ echo "bump push failed after 3 attempts — --atomic means nothing landed, so re-dispatch the same version" >&2
148
+ exit 1
149
+ - name: Assert expected version == pyproject == __init__ == uv.lock
150
+ env:
151
+ VERSION: ${{ inputs.version }}
152
+ run: |
153
+ pyproject=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
154
+ init=$(grep -m1 '__version__' src/dxs/__init__.py | cut -d'"' -f2)
155
+ lock=$(awk '/^name = "datex-studio-cli"$/ {found=1} found && /^version = / {print; exit}' uv.lock | cut -d'"' -f2)
156
+ if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
157
+ expected="${GITHUB_REF#refs/tags/v}"
158
+ else
159
+ expected="${VERSION:-$pyproject}"
160
+ fi
161
+ echo "expected=${expected} pyproject=${pyproject} __init__=${init} uv.lock=${lock}"
162
+ if [ "$expected" != "$pyproject" ] || [ "$pyproject" != "$init" ] || [ "$pyproject" != "$lock" ]; then
163
+ echo "version sources disagree — run 'uv lock' and align all three before releasing" >&2
164
+ exit 1
165
+ fi
166
+ - uses: actions/setup-node@v7
167
+ with:
168
+ node-version: 22
169
+ - name: Build the web UI (shipped via hatch artifacts)
170
+ working-directory: src/dxs/web/frontend
171
+ run: |
172
+ npm ci
173
+ npm run build
174
+ - name: Assert the frontend build actually produced the static export
175
+ run: test -f src/dxs/web/static/index.html
176
+ - run: uv build
177
+ - name: Publish via trusted publishing
178
+ run: uv publish
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datex-studio-cli
3
- Version: 0.5.4
3
+ Version: 0.5.6
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.4"
7
+ version = "0.5.6"
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"
@@ -165,6 +165,7 @@ markers = [
165
165
  "telemetry_live: run without the suite-wide DXS_TELEMETRY=0 kill-switch",
166
166
  "live_auth: sign in against real Entra tenants (opt-in; needs DXS_LIVE_AUTH=1 + a human)",
167
167
  "mssql: integration tests against a throwaway SQL Server container (opt-in; needs DXS_MSSQL_TEST=1)",
168
+ "schema_capability_probe: exercise the real Indexes capability probe (no assumed-capable override)",
168
169
  ]
169
170
 
170
171
  [tool.coverage.run]
@@ -1,4 +1,4 @@
1
1
  """Datex Studio CLI - Command-line interface for Datex Studio platform."""
2
2
 
3
- __version__ = "0.5.4"
3
+ __version__ = "0.5.6"
4
4
  __app_name__ = "dxs"
@@ -1,14 +1,16 @@
1
1
  """Main CLI entry point for Datex Studio CLI."""
2
2
 
3
+ import errno
4
+ import os
3
5
  import sys
4
6
  from collections.abc import Callable
5
- from typing import Any, TypeVar, cast
7
+ from typing import Any, NoReturn, TypeVar, cast
6
8
 
7
9
  import click
8
10
 
9
11
  from dxs import __app_name__, __version__
10
12
  from dxs.context import DxsContext, pass_context # re-exported for backward compat
11
- from dxs.core.auth.datex_tenant import DATEX_INTERNAL_COMMANDS, internal_commands_available
13
+ from dxs.core.auth.datex_tenant import InternalCommandGateMixin
12
14
  from dxs.core.output import OutputFormat, format_error
13
15
  from dxs.core.output.redaction import (
14
16
  AUTHOR_VERBOSE_FIELDS as _AUTHOR_VERBOSE_FIELDS, # noqa: F401 — re-exported
@@ -121,12 +123,12 @@ HIDDEN_COMMAND_ALIASES = frozenset({"config"})
121
123
  RAW_ARGS_META_KEY = "dxs.raw_args"
122
124
 
123
125
 
124
- class DxsGroup(click.Group):
126
+ class DxsGroup(InternalCommandGateMixin, click.Group):
125
127
  """Custom Group that shows global options in subcommand help.
126
128
 
127
- Also gates the Datex-internal command groups (``devops``, ``crm``). The gate
128
- is applied at lookup time rather than at registration so it reflects the
129
- identity cache as of *this* invocation — registration happens once at import.
129
+ Gating of the Datex-internal command groups comes from
130
+ :class:`InternalCommandGateMixin`, shared with the subgroups that hold one
131
+ (``dxs source branch workitem``).
130
132
  """
131
133
 
132
134
  def invoke(self, ctx: click.Context) -> Any:
@@ -136,28 +138,20 @@ class DxsGroup(click.Group):
136
138
  so ``_resolve_command_path`` reads them back from ``ctx.meta``.
137
139
  """
138
140
  ctx.meta[RAW_ARGS_META_KEY] = [*getattr(ctx, "_protected_args", []), *ctx.args]
139
- return super().invoke(ctx)
140
-
141
- def _is_gated(self, cmd_name: str) -> bool:
142
- """Whether ``cmd_name`` is Datex-internal and unavailable to this user."""
143
- return cmd_name in DATEX_INTERNAL_COMMANDS and not internal_commands_available()
141
+ try:
142
+ return super().invoke(ctx)
143
+ except (BrokenPipeError, OSError) as e:
144
+ # Handle a closed stdout here, inside Click's own invoke, rather
145
+ # than around cli(): Click catches EPIPE itself and exits 1, so by
146
+ # the time main() sees anything it is a SystemExit. Raising
147
+ # SystemExit from here passes straight through that handler.
148
+ if not _is_broken_pipe(e):
149
+ raise
150
+ _exit_broken_pipe()
144
151
 
145
152
  def list_commands(self, ctx: click.Context) -> list[str]:
146
153
  """List visible subcommands, omitting hidden aliases and gated groups."""
147
- return [
148
- name
149
- for name in super().list_commands(ctx)
150
- if name not in HIDDEN_COMMAND_ALIASES and not self._is_gated(name)
151
- ]
152
-
153
- def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
154
- """Resolve a subcommand, refusing unavailable Datex-internal groups."""
155
- if self._is_gated(cmd_name):
156
- ctx.fail(
157
- f"'{cmd_name}' is a Datex-internal command group and requires a "
158
- "Datex tenant identity. Run 'dxs auth login' with your Datex account."
159
- )
160
- return super().get_command(ctx, cmd_name)
154
+ return [name for name in super().list_commands(ctx) if name not in HIDDEN_COMMAND_ALIASES]
161
155
 
162
156
  def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
163
157
  """Write options to the formatter, including global options."""
@@ -547,6 +541,15 @@ def handle_errors(f: F) -> F:
547
541
  _record_error_silently(e, dxs_ctx)
548
542
  dxs_ctx.output_error(e)
549
543
  sys.exit(1)
544
+ except (BrokenPipeError, OSError) as e:
545
+ # A closed stdout is not a command failure: re-raise so main()
546
+ # can silence it. Rendering an error envelope here would both
547
+ # lie to the user and record a bogus command.error.
548
+ if not _is_broken_pipe(e):
549
+ _record_error_silently(e, dxs_ctx)
550
+ dxs_ctx.output_error(e)
551
+ sys.exit(1)
552
+ raise
550
553
  except (click.ClickException, click.Abort, click.exceptions.Exit):
551
554
  # Let Click handle its own exceptions and control-flow signals
552
555
  # (Abort from confirm(abort=True), Exit from ctx.exit) — these are
@@ -560,6 +563,67 @@ def handle_errors(f: F) -> F:
560
563
  return cast(F, wrapper)
561
564
 
562
565
 
566
+ #: 128 + SIGPIPE, the conventional shell status for "died because the pipe closed".
567
+ BROKEN_PIPE_EXIT_CODE = 141
568
+
569
+
570
+ def _is_broken_pipe(exc: BaseException) -> bool:
571
+ """True when ``exc`` is stdout's consumer going away rather than a failure.
572
+
573
+ POSIX raises ``BrokenPipeError`` (EPIPE). Windows has no SIGPIPE and instead
574
+ fails the underlying ``flush()`` with ``OSError [Errno 22] Invalid argument``
575
+ — so EINVAL counts only there, since on POSIX it means a hundred other
576
+ things and must not be mistaken for a closed pipe.
577
+ """
578
+ if isinstance(exc, BrokenPipeError):
579
+ return True
580
+ if not isinstance(exc, OSError):
581
+ return False
582
+ if exc.errno == errno.EPIPE:
583
+ return True
584
+ return exc.errno == errno.EINVAL and sys.platform == "win32"
585
+
586
+
587
+ def _silence_stdout() -> None:
588
+ """Point stdout at devnull so interpreter shutdown cannot re-raise.
589
+
590
+ Python flushes the standard streams on exit; against a closed pipe that
591
+ raises a second time, past any handler we control, and CPython prints
592
+ "Exception ignored in: <_io.TextIOWrapper name='<stdout>'>". Redirecting the
593
+ file descriptor is the documented way to avoid it. Best-effort: under pytest
594
+ or Click's CliRunner stdout is a StringIO with no real fileno.
595
+ """
596
+ devnull = None
597
+ try:
598
+ devnull = os.open(os.devnull, os.O_WRONLY)
599
+ os.dup2(devnull, sys.stdout.fileno())
600
+ except Exception: # noqa: BLE001 - silencing output must never itself fail
601
+ pass
602
+ finally:
603
+ if devnull is not None:
604
+ try:
605
+ os.close(devnull)
606
+ except OSError:
607
+ pass
608
+
609
+
610
+ def _exit_broken_pipe() -> NoReturn:
611
+ """Leave quietly after the consumer of stdout went away."""
612
+ _silence_stdout()
613
+ _record_broken_pipe_silently()
614
+ sys.exit(BROKEN_PIPE_EXIT_CODE)
615
+
616
+
617
+ def _record_broken_pipe_silently() -> None:
618
+ """Record a closed pipe as a completed run, never as command.error."""
619
+ try:
620
+ from dxs.core.telemetry import record_end
621
+
622
+ record_end(exit_code=BROKEN_PIPE_EXIT_CODE)
623
+ except Exception: # noqa: BLE001
624
+ pass
625
+
626
+
563
627
  def _record_error_silently(exc: BaseException, dxs_ctx: "DxsContext | None" = None) -> None:
564
628
  """Emit a telemetry error event; never raises.
565
629
 
@@ -850,6 +914,15 @@ def main() -> None:
850
914
  # SystemExit from sys.exit() inside handle_errors — telemetry already
851
915
  # recorded an error there; just propagate.
852
916
  raise
917
+ except (BrokenPipeError, OSError) as e:
918
+ if not _is_broken_pipe(e):
919
+ _record_error_silently(e)
920
+ raise
921
+ # The consumer stopped reading (`| head`, an agent that moved on).
922
+ # Say nothing, record a completed run, and exit on the SIGPIPE
923
+ # convention so `set -o pipefail` scripts can still tell truncated
924
+ # output from a clean one.
925
+ _exit_broken_pipe()
853
926
  except BaseException as e:
854
927
  # Anything else escaping cli() shouldn't take telemetry with it.
855
928
  _record_error_silently(e)