datex-studio-cli 0.4.25__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/workflows/build-image.yml +4 -4
  2. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/workflows/ci.yml +14 -8
  3. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/workflows/promote-prod.yml +1 -1
  4. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/workflows/release.yml +5 -3
  5. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.gitignore +9 -0
  6. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/PKG-INFO +20 -8
  7. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/README.md +19 -7
  8. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/pyproject.toml +7 -1
  9. datex_studio_cli-0.5.1/scripts/datadog_errors.py +275 -0
  10. datex_studio_cli-0.5.1/scripts/wavelength_query.py +428 -0
  11. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/__init__.py +1 -1
  12. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/cli.py +58 -2
  13. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/auth.py +685 -252
  14. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/datasource.py +3 -1
  15. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/explore.py +5 -30
  16. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/init.py +89 -60
  17. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/odata.py +1 -1
  18. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/__init__.py +2 -2
  19. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/reset.py +1 -0
  20. datex_studio_cli-0.5.1/src/dxs/commands/skills.py +442 -0
  21. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/endpoints.py +15 -0
  22. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/datex_tenant.py +2 -2
  23. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/msal_client.py +119 -10
  24. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/tenants.py +120 -0
  25. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/token_cache.py +37 -8
  26. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/generator.py +2 -1
  27. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/footprint/client.py +10 -3
  28. datex_studio_cli-0.5.1/src/dxs/core/onboarding.py +518 -0
  29. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/engine.py +13 -13
  30. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/preview.py +2 -2
  31. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/preview_arjs.py +2 -2
  32. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/svg_renderer.py +1 -1
  33. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/design.py +4 -6
  34. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/404/index.html +1 -1
  35. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/404.html +1 -1
  36. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/design/capture/index.html +1 -1
  37. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/design/capture/index.txt +1 -1
  38. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/design/index.html +1 -1
  39. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/design/index.txt +1 -1
  40. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/index.html +1 -1
  41. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/index.txt +1 -1
  42. datex_studio_cli-0.4.25/src/dxs/core/onboarding.py +0 -151
  43. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.dockerignore +0 -0
  44. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/actionlint.yaml +0 -0
  45. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/CONTEXT.md +0 -0
  46. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/.env.example +0 -0
  47. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/Dockerfile +0 -0
  48. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/README.md +0 -0
  49. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/apply-local-cache.js +0 -0
  50. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/collector-config.yaml +0 -0
  51. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/docker-compose.yml +0 -0
  52. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/local-test-settings.json +0 -0
  53. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/remote-settings.json +0 -0
  54. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/report-creator.skill +0 -0
  55. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/scripts/analyze_corpus.py +0 -0
  56. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/scripts/generate_rdlx_schema.py +0 -0
  57. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/__main__.py +0 -0
  58. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/__init__.py +0 -0
  59. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/api.py +0 -0
  60. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/branch.py +0 -0
  61. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/branch_commit_guard.py +0 -0
  62. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/cascade.py +0 -0
  63. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/config.py +0 -0
  64. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/configuration.py +0 -0
  65. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/crm.py +0 -0
  66. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/crm_delivery.py +0 -0
  67. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/crm_time.py +0 -0
  68. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/devops.py +0 -0
  69. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/document.py +0 -0
  70. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/endpoint.py +0 -0
  71. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/env.py +0 -0
  72. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/function.py +0 -0
  73. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/marketplace.py +0 -0
  74. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/__init__.py +0 -0
  75. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/_common.py +0 -0
  76. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/api.py +0 -0
  77. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/create.py +0 -0
  78. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/data.py +0 -0
  79. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/pack.py +0 -0
  80. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/preview.py +0 -0
  81. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/organization.py +0 -0
  82. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/proxy.py +0 -0
  83. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/reference.py +0 -0
  84. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/release_notes.py +0 -0
  85. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/repo.py +0 -0
  86. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/add_cmds.py +0 -0
  87. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/api.py +0 -0
  88. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/batch.py +0 -0
  89. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/create.py +0 -0
  90. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/data.py +0 -0
  91. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/dataset.py +0 -0
  92. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/edit.py +0 -0
  93. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/folder.py +0 -0
  94. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/preview.py +0 -0
  95. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/schema_cmds.py +0 -0
  96. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/schema.py +0 -0
  97. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/servicepack.py +0 -0
  98. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/source.py +0 -0
  99. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/studio.py +0 -0
  100. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/telemetry.py +0 -0
  101. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/user.py +0 -0
  102. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/validation_report.py +0 -0
  103. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/context.py +0 -0
  104. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/__init__.py +0 -0
  105. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/agent_browser.py +0 -0
  106. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/__init__.py +0 -0
  107. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/app_config.py +0 -0
  108. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/client.py +0 -0
  109. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/metadata_models.py +0 -0
  110. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/models.py +0 -0
  111. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/reference_graph.py +0 -0
  112. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/replacements.py +0 -0
  113. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/__init__.py +0 -0
  114. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/decorators.py +0 -0
  115. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/cache.py +0 -0
  116. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/__init__.py +0 -0
  117. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/flow_generator.py +0 -0
  118. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/flow_models.py +0 -0
  119. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/flow_shape.py +0 -0
  120. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/models.py +0 -0
  121. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/parsers.py +0 -0
  122. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/resolver.py +0 -0
  123. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/type_def_parser.py +0 -0
  124. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/validator.py +0 -0
  125. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/designer/__init__.py +0 -0
  126. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/designer/flow_models.py +0 -0
  127. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/designer/models.py +0 -0
  128. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/designer/parsers.py +0 -0
  129. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/devops/__init__.py +0 -0
  130. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/devops/client.py +0 -0
  131. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/devops/helpers.py +0 -0
  132. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/devops/models.py +0 -0
  133. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/__init__.py +0 -0
  134. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/client.py +0 -0
  135. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/html.py +0 -0
  136. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/metadata_cache.py +0 -0
  137. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/pagination.py +0 -0
  138. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/resolvers.py +0 -0
  139. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/time_entry.py +0 -0
  140. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/footprint/__init__.py +0 -0
  141. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/footprint/edmx_parser.py +0 -0
  142. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/footprint/metadata.py +0 -0
  143. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/function/__init__.py +0 -0
  144. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/function/generator.py +0 -0
  145. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/function/models.py +0 -0
  146. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/function/validator.py +0 -0
  147. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/graph.py +0 -0
  148. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/__init__.py +0 -0
  149. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/csv_fmt.py +0 -0
  150. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/formatter.py +0 -0
  151. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/json_fmt.py +0 -0
  152. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/redaction.py +0 -0
  153. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/yaml_fmt.py +0 -0
  154. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/release_notes.py +0 -0
  155. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/responses.py +0 -0
  156. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/__init__.py +0 -0
  157. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/config.py +0 -0
  158. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/identity.py +0 -0
  159. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/recorder.py +0 -0
  160. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/scrubber.py +0 -0
  161. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/sender.py +0 -0
  162. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/models/__init__.py +0 -0
  163. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/__init__.py +0 -0
  164. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/folder.py +0 -0
  165. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/manifest.py +0 -0
  166. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/mocks.py +0 -0
  167. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/preview.py +0 -0
  168. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/scaffold.py +0 -0
  169. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/serve_lock.py +0 -0
  170. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/py.typed +0 -0
  171. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/__init__.py +0 -0
  172. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/datasource_binding.py +0 -0
  173. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/field_parser.py +0 -0
  174. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/folder.py +0 -0
  175. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/manifest.py +0 -0
  176. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/models.py +0 -0
  177. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/schema.py +0 -0
  178. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/templates/__init__.py +0 -0
  179. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  180. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  181. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/validate_arjs.py +0 -0
  182. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/validator.py +0 -0
  183. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/wrapper.py +0 -0
  184. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/__init__.py +0 -0
  185. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/click_options.py +0 -0
  186. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/clipboard.py +0 -0
  187. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/config.py +0 -0
  188. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/errors.py +0 -0
  189. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/filtering.py +0 -0
  190. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/image.py +0 -0
  191. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/paths.py +0 -0
  192. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/resolvers.py +0 -0
  193. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/responses.py +0 -0
  194. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/restricted.py +0 -0
  195. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/sorting.py +0 -0
  196. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/update_check.py +0 -0
  197. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/versioning.py +0 -0
  198. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/__init__.py +0 -0
  199. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/app.py +0 -0
  200. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/proxy_app.py +0 -0
  201. {datex_studio_cli-0.4.25/src/dxs/web/static/_next/static/BbKuTltyqzuSYoNcVtfWa → datex_studio_cli-0.5.1/src/dxs/web/static/_next/static/W6-87g3VtSLhTM2G0U3r1}/_buildManifest.js +0 -0
  202. {datex_studio_cli-0.4.25/src/dxs/web/static/_next/static/BbKuTltyqzuSYoNcVtfWa → datex_studio_cli-0.5.1/src/dxs/web/static/_next/static/W6-87g3VtSLhTM2G0U3r1}/_ssgManifest.js +0 -0
  203. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  204. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  205. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  206. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/538-b06224e65f71c9a1.js +0 -0
  207. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  208. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
  209. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  210. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  211. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-e3c660196c244183.js +0 -0
  212. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-c083dc9f01f32a04.js +0 -0
  213. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/design/page-689cd5b9e1d9a74e.js +0 -0
  214. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/layout-cec86f90e385371d.js +0 -0
  215. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/page-f0380b0f29983264.js +0 -0
  216. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  217. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  218. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/main-app-694cf3568b6c9fa9.js +0 -0
  219. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  220. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  221. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  222. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  223. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
  224. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/render-cli.js +0 -0
  225. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/report-preview.html +0 -0
  226. {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/report-validate.html +0 -0
@@ -29,9 +29,9 @@ jobs:
29
29
  runs-on: arc-dxs
30
30
  timeout-minutes: 30
31
31
  steps:
32
- - uses: actions/checkout@v4
33
- - uses: docker/setup-buildx-action@v3
34
- - uses: docker/login-action@v3
32
+ - uses: actions/checkout@v7
33
+ - uses: docker/setup-buildx-action@v4
34
+ - uses: docker/login-action@v4
35
35
  with:
36
36
  registry: ghcr.io
37
37
  username: ${{ github.actor }}
@@ -39,7 +39,7 @@ jobs:
39
39
  - id: meta
40
40
  run: echo "datetag=$(date -u +%Y%m%d).${{ github.run_number }}" >> "$GITHUB_OUTPUT"
41
41
  - id: build
42
- uses: docker/build-push-action@v6
42
+ uses: docker/build-push-action@v7
43
43
  with:
44
44
  context: .
45
45
  file: quartermaster/Dockerfile
@@ -20,12 +20,18 @@ jobs:
20
20
  runs-on: arc-dxs
21
21
  timeout-minutes: 30
22
22
  steps:
23
- - uses: actions/checkout@v4
24
- - uses: astral-sh/setup-uv@v5
23
+ - uses: actions/checkout@v7
24
+ - uses: astral-sh/setup-uv@v10.0.1
25
25
  with:
26
26
  enable-cache: true
27
+ # Distinct per-job key: cli and service run in parallel and would
28
+ # otherwise race to save the same cache entry (noisy warning).
29
+ cache-suffix: cli
30
+ # The releases.astral.sh mirror 404s from the arc-dxs runners and
31
+ # every download falls back to GitHub Releases anyway — skip it.
32
+ download-from-astral-mirror: false
27
33
  - run: uv python install 3.13
28
- - uses: actions/setup-node@v4
34
+ - uses: actions/setup-node@v7
29
35
  with:
30
36
  node-version: 22
31
37
  - name: Build the report renderer bundle (snapshot tests shell out to it)
@@ -35,19 +41,19 @@ jobs:
35
41
  npm run build:render-cli
36
42
  - run: uv sync --all-packages --extra dev
37
43
  - run: uv run ruff check .
38
- - name: mypy (non-blocking — 21 pre-existing errors, see #171)
39
- run: uv run mypy src/dxs
40
- continue-on-error: true
44
+ - run: uv run mypy src/dxs
41
45
  - run: uv run pytest -q
42
46
 
43
47
  service:
44
48
  runs-on: arc-dxs
45
49
  timeout-minutes: 20
46
50
  steps:
47
- - uses: actions/checkout@v4
48
- - uses: astral-sh/setup-uv@v5
51
+ - uses: actions/checkout@v7
52
+ - uses: astral-sh/setup-uv@v10.0.1
49
53
  with:
50
54
  enable-cache: true
55
+ cache-suffix: service
56
+ download-from-astral-mirror: false
51
57
  - run: uv python install 3.13
52
58
  - run: uv sync --all-packages --extra dev
53
59
  - run: uv run ruff check quartermaster
@@ -39,7 +39,7 @@ jobs:
39
39
  echo "confirmation mismatch: expected the literal string promote-to-prod" >&2
40
40
  exit 1
41
41
  fi
42
- - uses: actions/checkout@v4
42
+ - uses: actions/checkout@v7
43
43
  with:
44
44
  fetch-depth: 0
45
45
  - id: resolve
@@ -41,10 +41,12 @@ jobs:
41
41
  contents: write # the dispatch lane pushes the bump commit + tag
42
42
  id-token: write # PyPI trusted publishing
43
43
  steps:
44
- - uses: actions/checkout@v4
45
- - uses: astral-sh/setup-uv@v5
44
+ - uses: actions/checkout@v7
45
+ - uses: astral-sh/setup-uv@v10.0.1
46
46
  with:
47
47
  enable-cache: true
48
+ cache-suffix: release
49
+ download-from-astral-mirror: false
48
50
  - run: uv python install 3.13
49
51
  - name: Bump version, commit, and tag (dispatch with a version input)
50
52
  if: github.event_name == 'workflow_dispatch' && inputs.version != ''
@@ -91,7 +93,7 @@ jobs:
91
93
  echo "version sources disagree — run 'uv lock' and align all three before releasing" >&2
92
94
  exit 1
93
95
  fi
94
- - uses: actions/setup-node@v4
96
+ - uses: actions/setup-node@v7
95
97
  with:
96
98
  node-version: 22
97
99
  - name: Build the web UI (shipped via hatch artifacts)
@@ -102,3 +102,12 @@ test-preview.svg
102
102
 
103
103
  # CAC working copies (dxs ng harnesses) — never committed
104
104
  /components/
105
+
106
+ # Live auth matrix config (real accounts)
107
+ tests/live/accounts.local.json
108
+
109
+ # Project-scope `dxs skills` install artifacts (.claude/skills is above).
110
+ # Canonical copies, the codex symlink tree, and the install record.
111
+ .agents/
112
+ .codex/
113
+ skills-lock.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datex-studio-cli
3
- Version: 0.4.25
3
+ Version: 0.5.1
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
@@ -77,7 +77,7 @@ pip install datex-studio-cli
77
77
  ## Quick Start
78
78
 
79
79
  ```bash
80
- # Authenticate with Azure Entra
80
+ # Authenticate with Azure Entra (opens your browser; device code when headless)
81
81
  dxs auth login
82
82
 
83
83
  # Check authentication status
@@ -203,11 +203,19 @@ Set `DXS_RESTRICTED_MODE=1` to block potentially dangerous operations:
203
203
 
204
204
  | Command | Description |
205
205
  |---------|-------------|
206
- | `login` | Authenticate with Azure Entra (device code flow) |
206
+ | `login [selector]` | Sign in — browser + PKCE by default, device code when headless or with `--use-device-code`/`--no-browser` |
207
207
  | `logout` | Clear credentials (`--all` for all identities) |
208
208
  | `status` | Show current authentication status and token expiration |
209
209
  | `list` | List all cached identities |
210
- | `switch <identity>` | Switch to a different cached identity |
210
+ | `switch <selector>` | Switch identity (username, org, Entra domain, or tenant ID) |
211
+
212
+ One selector grammar is shared by `login`, `switch`, and `init`: an email
213
+ address derives the tenant from its domain (works on a fresh machine), a
214
+ domain or tenant GUID names the tenant directly, anything else is an
215
+ organization name. `--domain`/`--tenant-id` name the tenant to authenticate
216
+ against and always win; combined with an email selector they express a guest
217
+ signing in to another organization's tenant. A usable cached identity for the
218
+ target skips the browser entirely (`--force` re-authenticates).
211
219
 
212
220
  **Multi-Identity Workflow:**
213
221
 
@@ -215,14 +223,18 @@ Set `DXS_RESTRICTED_MODE=1` to block potentially dangerous operations:
215
223
  # Login to primary organization
216
224
  dxs auth login
217
225
 
218
- # Login to additional organization (prompts for new auth)
219
- dxs auth login --org other-org
226
+ # Customer self-service: the tenant is derived from your address
227
+ dxs auth login you@csw-wi.com
228
+
229
+ # Login to an additional organization (fresh auth against its tenant)
230
+ dxs auth login OtherOrg
220
231
 
221
232
  # List all identities
222
233
  dxs auth list
223
234
 
224
- # Switch between organizations
225
- dxs auth switch other-org
235
+ # Switch between identities — org name, domain, tenant ID, or username
236
+ dxs auth switch OtherOrg
237
+ dxs auth switch csw-wi.com
226
238
  ```
227
239
 
228
240
  ### CLI Settings (`dxs settings`)
@@ -24,7 +24,7 @@ pip install datex-studio-cli
24
24
  ## Quick Start
25
25
 
26
26
  ```bash
27
- # Authenticate with Azure Entra
27
+ # Authenticate with Azure Entra (opens your browser; device code when headless)
28
28
  dxs auth login
29
29
 
30
30
  # Check authentication status
@@ -150,11 +150,19 @@ Set `DXS_RESTRICTED_MODE=1` to block potentially dangerous operations:
150
150
 
151
151
  | Command | Description |
152
152
  |---------|-------------|
153
- | `login` | Authenticate with Azure Entra (device code flow) |
153
+ | `login [selector]` | Sign in — browser + PKCE by default, device code when headless or with `--use-device-code`/`--no-browser` |
154
154
  | `logout` | Clear credentials (`--all` for all identities) |
155
155
  | `status` | Show current authentication status and token expiration |
156
156
  | `list` | List all cached identities |
157
- | `switch <identity>` | Switch to a different cached identity |
157
+ | `switch <selector>` | Switch identity (username, org, Entra domain, or tenant ID) |
158
+
159
+ One selector grammar is shared by `login`, `switch`, and `init`: an email
160
+ address derives the tenant from its domain (works on a fresh machine), a
161
+ domain or tenant GUID names the tenant directly, anything else is an
162
+ organization name. `--domain`/`--tenant-id` name the tenant to authenticate
163
+ against and always win; combined with an email selector they express a guest
164
+ signing in to another organization's tenant. A usable cached identity for the
165
+ target skips the browser entirely (`--force` re-authenticates).
158
166
 
159
167
  **Multi-Identity Workflow:**
160
168
 
@@ -162,14 +170,18 @@ Set `DXS_RESTRICTED_MODE=1` to block potentially dangerous operations:
162
170
  # Login to primary organization
163
171
  dxs auth login
164
172
 
165
- # Login to additional organization (prompts for new auth)
166
- dxs auth login --org other-org
173
+ # Customer self-service: the tenant is derived from your address
174
+ dxs auth login you@csw-wi.com
175
+
176
+ # Login to an additional organization (fresh auth against its tenant)
177
+ dxs auth login OtherOrg
167
178
 
168
179
  # List all identities
169
180
  dxs auth list
170
181
 
171
- # Switch between organizations
172
- dxs auth switch other-org
182
+ # Switch between identities — org name, domain, tenant ID, or username
183
+ dxs auth switch OtherOrg
184
+ dxs auth switch csw-wi.com
173
185
  ```
174
186
 
175
187
  ### CLI Settings (`dxs settings`)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datex-studio-cli"
7
- version = "0.4.25"
7
+ version = "0.5.1"
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"
@@ -149,6 +149,10 @@ ignore_missing_imports = true
149
149
  module = ["fastapi.*", "uvicorn.*"]
150
150
  ignore_missing_imports = true
151
151
 
152
+ [[tool.mypy.overrides]]
153
+ module = ["pyodbc"]
154
+ ignore_missing_imports = true
155
+
152
156
  [tool.pytest.ini_options]
153
157
  testpaths = ["tests"]
154
158
  python_files = ["test_*.py"]
@@ -159,6 +163,8 @@ markers = [
159
163
  "internal_commands_gated: exercise the devops/crm visibility gate (no DXS_INTERNAL_COMMANDS override)",
160
164
  "real_auth_state: exercise real auth state (no injected DXS_ACCESS_TOKEN; the variable is scrubbed)",
161
165
  "telemetry_live: run without the suite-wide DXS_TELEMETRY=0 kill-switch",
166
+ "live_auth: sign in against real Entra tenants (opt-in; needs DXS_LIVE_AUTH=1 + a human)",
167
+ "mssql: integration tests against a throwaway SQL Server container (opt-in; needs DXS_MSSQL_TEST=1)",
162
168
  ]
163
169
 
164
170
  [tool.coverage.run]
@@ -0,0 +1,275 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["httpx", "python-dotenv"]
4
+ # ///
5
+ """Pull recent CLI error events from Datadog Logs for investigation.
6
+
7
+ Queries the Datadog Logs Search API for ``service:dxs-cli status:error`` (the
8
+ telemetry forwarder's shape — see docs/telemetry-datadog-mapping.md) and prints
9
+ either a summary grouped by command + error code, or the raw events.
10
+
11
+ Credentials come from the project ``.env``: DATADOG_API_KEY, DATADOG_APP_KEY,
12
+ DATADOG_SITE (e.g. ``us3.datadoghq.com``).
13
+
14
+ Usage:
15
+ uv run scripts/datadog_errors.py # last 24h, summary
16
+ uv run scripts/datadog_errors.py --since 7d # last 7 days
17
+ uv run scripts/datadog_errors.py --since 7d --list # one line per event
18
+ uv run scripts/datadog_errors.py --code DXS-API-001 --detail # full stacks
19
+ uv run scripts/datadog_errors.py --command source --version 0.5.0 -f json
20
+ uv run scripts/datadog_errors.py -q 'usr.email:*@datexcorp.com' # extra filter
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import json
27
+ import os
28
+ import re
29
+ import sys
30
+ import time
31
+ from collections import Counter, defaultdict
32
+ from datetime import datetime, timedelta, timezone
33
+ from pathlib import Path
34
+ from typing import Any
35
+
36
+ SUMMARY_FIELDS = (
37
+ "ts",
38
+ "command",
39
+ "error_code",
40
+ "error_class",
41
+ "error_message",
42
+ "cli_version",
43
+ "api_target",
44
+ "env",
45
+ "usr_email",
46
+ "org",
47
+ "os",
48
+ )
49
+
50
+
51
+ def parse_since(value: str) -> timedelta:
52
+ m = re.fullmatch(r"(\d+)([mhd])", value.strip())
53
+ if not m:
54
+ raise argparse.ArgumentTypeError("expected like 30m, 6h, 7d")
55
+ n, unit = int(m.group(1)), m.group(2)
56
+ return timedelta(**{{"m": "minutes", "h": "hours", "d": "days"}[unit]: n})
57
+
58
+
59
+ def build_query(args: argparse.Namespace) -> str:
60
+ parts = [f"service:{args.service}", "status:error"]
61
+ if args.env:
62
+ parts.append(f"env:{args.env}")
63
+ if args.api_target:
64
+ parts.append(f"@api_target:{args.api_target}")
65
+ if args.code:
66
+ parts.append(f"@error_code:{args.code}")
67
+ if args.command:
68
+ parts.append(f'@command:"{args.command}"')
69
+ if args.version:
70
+ parts.append(f"@version:{args.version}")
71
+ if args.query:
72
+ parts.append(f"({args.query})")
73
+ return " ".join(parts)
74
+
75
+
76
+ def fetch_events(
77
+ site: str, api_key: str, app_key: str, query: str, since: timedelta, limit: int
78
+ ) -> list[dict[str, Any]]:
79
+ import httpx
80
+
81
+ now = datetime.now(timezone.utc)
82
+ body: dict[str, Any] = {
83
+ "filter": {
84
+ "query": query,
85
+ "from": (now - since).isoformat(),
86
+ "to": now.isoformat(),
87
+ },
88
+ "sort": "-timestamp",
89
+ "page": {"limit": min(limit, 1000)},
90
+ }
91
+ headers = {
92
+ "DD-API-KEY": api_key,
93
+ "DD-APPLICATION-KEY": app_key,
94
+ "Content-Type": "application/json",
95
+ }
96
+ events: list[dict[str, Any]] = []
97
+ retries = 0
98
+ with httpx.Client(base_url=f"https://api.{site}", headers=headers, timeout=30) as client:
99
+ while True:
100
+ resp = client.post("/api/v2/logs/events/search", json=body)
101
+ if resp.status_code == 429 and retries < 3:
102
+ wait = int(
103
+ resp.headers.get("Retry-After") or resp.headers.get("X-RateLimit-Reset") or 5
104
+ )
105
+ print(f"rate limited; retrying in {wait}s", file=sys.stderr)
106
+ time.sleep(wait)
107
+ retries += 1
108
+ continue
109
+ if resp.status_code != 200:
110
+ print(f"Datadog API {resp.status_code}: {resp.text[:500]}", file=sys.stderr)
111
+ sys.exit(1)
112
+ payload = resp.json()
113
+ events.extend(payload.get("data", []))
114
+ cursor = payload.get("meta", {}).get("page", {}).get("after")
115
+ if not cursor or len(events) >= limit:
116
+ break
117
+ body["page"]["cursor"] = cursor
118
+ return events[:limit]
119
+
120
+
121
+ def full_command(group: str | None, argv: Any) -> str | None:
122
+ """`command` is only the Click group (e.g. `configuration`); recover the
123
+ subcommand path from argv: the bare-word tokens that follow the group."""
124
+ if not group or not isinstance(argv, list):
125
+ return group
126
+ try:
127
+ i = argv.index(group)
128
+ except ValueError:
129
+ return group
130
+ words = [group]
131
+ for tok in argv[i + 1 : i + 3]:
132
+ if isinstance(tok, str) and re.fullmatch(r"[a-z][a-z-]*", tok):
133
+ words.append(tok)
134
+ else:
135
+ break
136
+ return " ".join(words)
137
+
138
+
139
+ def flatten(event: dict[str, Any]) -> dict[str, Any]:
140
+ """Reduce a Datadog log event to the attributes worth reading."""
141
+ attrs = event.get("attributes", {})
142
+ custom = attrs.get("attributes", {}) # user attributes live one level down
143
+ tags = dict(t.split(":", 1) for t in attrs.get("tags", []) if ":" in t)
144
+ usr = custom.get("usr", {}) or {}
145
+ err = custom.get("error", {}) or {}
146
+ return {
147
+ "id": event.get("id"),
148
+ "ts": attrs.get("timestamp"),
149
+ "message": attrs.get("message"),
150
+ "command": full_command(custom.get("command"), custom.get("argv")),
151
+ "argv": custom.get("argv"),
152
+ "error_code": custom.get("error_code") or tags.get("error_code"),
153
+ "error_class": custom.get("error_class") or err.get("kind"),
154
+ "error_message": custom.get("error_message") or err.get("message"),
155
+ "error_stack": custom.get("error_stack") or err.get("stack"),
156
+ "exit_code": custom.get("exit_code"),
157
+ "duration_ms": custom.get("duration_ms"),
158
+ "cli_version": custom.get("version") or tags.get("version"),
159
+ "org": custom.get("datex_org_name") or tags.get("org"),
160
+ "python_version": custom.get("python_version"),
161
+ "os": custom.get("os", {}).get("name")
162
+ if isinstance(custom.get("os"), dict)
163
+ else custom.get("os"),
164
+ "os_release": custom.get("os_release"),
165
+ "api_target": custom.get("api_target") or tags.get("api_target"),
166
+ "env": tags.get("env"),
167
+ "usr_id": usr.get("id"),
168
+ "usr_email": usr.get("email"),
169
+ "usr_name": usr.get("name"),
170
+ "session_id": (custom.get("session") or {}).get("id"),
171
+ "install_id": custom.get("install_id"),
172
+ "ctx_branch_id": custom.get("ctx_branch_id"),
173
+ "ctx_org": custom.get("ctx_org"),
174
+ "ctx_env": custom.get("ctx_env"),
175
+ }
176
+
177
+
178
+ def print_summary(rows: list[dict[str, Any]]) -> None:
179
+ if not rows:
180
+ print("No matching error events.")
181
+ return
182
+ groups: dict[tuple[str, str, str], list[dict[str, Any]]] = defaultdict(list)
183
+ for r in rows:
184
+ groups[(r["command"] or "?", r["error_code"] or "-", r["error_class"] or "?")].append(r)
185
+ print(f"{len(rows)} error events, {len(groups)} distinct (command, code, class)\n")
186
+ print(f"{'count':>5} {'command':<28} {'code':<14} {'class':<24} sample message")
187
+ for key, grp in sorted(groups.items(), key=lambda kv: -len(kv[1])):
188
+ cmd, code, cls = key
189
+ msg = (grp[0]["error_message"] or "")[:80].replace("\n", " ")
190
+ print(f"{len(grp):>5} {cmd:<28} {code:<14} {cls:<24} {msg}")
191
+ users = Counter(r["usr_email"] or r["usr_name"] or "?" for r in grp)
192
+ versions = Counter(r["cli_version"] or "?" for r in grp)
193
+ print(
194
+ f" last={grp[0]['ts']} users={dict(users.most_common(3))}"
195
+ f" versions={dict(versions.most_common(3))}"
196
+ )
197
+
198
+
199
+ def print_list(rows: list[dict[str, Any]]) -> None:
200
+ for r in rows:
201
+ msg = (r["error_message"] or "")[:100].replace("\n", " ")
202
+ print(
203
+ f"{r['ts']} {r['cli_version'] or '?':<8} {r['usr_email'] or r['usr_name'] or '?':<32}"
204
+ f" {r['command'] or '?':<28} {r['error_code'] or '-':<14} {msg}"
205
+ )
206
+
207
+
208
+ def print_detail(rows: list[dict[str, Any]]) -> None:
209
+ for r in rows:
210
+ print("=" * 100)
211
+ for k in SUMMARY_FIELDS + ("argv", "ctx_branch_id", "ctx_org", "session_id", "id"):
212
+ if r.get(k) is not None:
213
+ print(f"{k:<14} {r[k]}")
214
+ if r.get("error_stack"):
215
+ print("--- stack ---")
216
+ print(r["error_stack"])
217
+ print("=" * 100)
218
+
219
+
220
+ def main(argv: list[str] | None = None) -> None:
221
+ p = argparse.ArgumentParser(
222
+ description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
223
+ )
224
+ p.add_argument(
225
+ "--since",
226
+ type=parse_since,
227
+ default="24h",
228
+ help="lookback window, e.g. 30m, 6h, 7d (default 24h)",
229
+ )
230
+ p.add_argument("--service", default="dxs-cli", help="Datadog service tag (default dxs-cli)")
231
+ p.add_argument("--env", help="ddtag env: staging|prod|local (which Quartermaster received it)")
232
+ p.add_argument("--api-target", help="dev|qa|prod|default (which Datex environment the CLI hit)")
233
+ p.add_argument("--code", help="filter by DXS-* error code")
234
+ p.add_argument(
235
+ "--command", help="filter by Click group, e.g. configuration (subcommand is only in argv)"
236
+ )
237
+ p.add_argument("--version", help="filter by cli_version")
238
+ p.add_argument("-q", "--query", help="extra raw Datadog log query ANDed onto the filter")
239
+ p.add_argument("-n", "--limit", type=int, default=500, help="max events to fetch (default 500)")
240
+ mode = p.add_mutually_exclusive_group()
241
+ mode.add_argument("--list", action="store_true", help="one line per event")
242
+ mode.add_argument("--detail", action="store_true", help="every field + stack per event")
243
+ p.add_argument("-f", "--format", choices=["text", "json"], default="text")
244
+ p.add_argument("--show-query", action="store_true", help="print the Datadog query and exit")
245
+ args = p.parse_args(argv)
246
+
247
+ from dotenv import load_dotenv
248
+
249
+ load_dotenv(Path(__file__).resolve().parent.parent / ".env")
250
+ api_key, app_key = os.environ.get("DATADOG_API_KEY"), os.environ.get("DATADOG_APP_KEY")
251
+ site = os.environ.get("DATADOG_SITE", "datadoghq.com")
252
+ if not api_key or not app_key:
253
+ print("DATADOG_API_KEY and DATADOG_APP_KEY must be set (expected in .env)", file=sys.stderr)
254
+ sys.exit(1)
255
+
256
+ query = build_query(args)
257
+ if args.show_query:
258
+ print(query)
259
+ return
260
+ print(f"query: {query} window: last {args.since}", file=sys.stderr)
261
+
262
+ rows = [flatten(e) for e in fetch_events(site, api_key, app_key, query, args.since, args.limit)]
263
+ if args.format == "json":
264
+ json.dump(rows, sys.stdout, indent=2, default=str)
265
+ print()
266
+ elif args.detail:
267
+ print_detail(rows)
268
+ elif args.list:
269
+ print_list(rows)
270
+ else:
271
+ print_summary(rows)
272
+
273
+
274
+ if __name__ == "__main__":
275
+ main()