datex-studio-cli 0.4.19__tar.gz → 0.4.20__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 (216) hide show
  1. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/PKG-INFO +1 -1
  2. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/pyproject.toml +1 -1
  3. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/__init__.py +1 -1
  4. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/auth.py +228 -39
  5. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/explore.py +1 -1
  6. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/init.py +21 -16
  7. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/source.py +10 -3
  8. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/client.py +23 -0
  9. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/datex_tenant.py +209 -6
  10. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/msal_client.py +127 -26
  11. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/devops/client.py +2 -0
  12. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/client.py +5 -0
  13. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/config.py +16 -14
  14. datex_studio_cli-0.4.20/src/dxs/utils/versioning.py +22 -0
  15. datex_studio_cli-0.4.19/src/dxs/utils/versioning.py +0 -22
  16. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/.gitignore +0 -0
  17. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/CONTEXT.md +0 -0
  18. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/README.md +0 -0
  19. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/.env.example +0 -0
  20. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/Dockerfile +0 -0
  21. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/README.md +0 -0
  22. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/apply-local-cache.js +0 -0
  23. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/collector-config.yaml +0 -0
  24. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/docker-compose.yml +0 -0
  25. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/local-test-settings.json +0 -0
  26. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/remote-settings.json +0 -0
  27. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/report-creator.skill +0 -0
  28. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/scripts/analyze_corpus.py +0 -0
  29. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/scripts/generate_rdlx_schema.py +0 -0
  30. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/__main__.py +0 -0
  31. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/cli.py +0 -0
  32. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/__init__.py +0 -0
  33. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/api.py +0 -0
  34. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/branch.py +0 -0
  35. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/branch_commit_guard.py +0 -0
  36. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/cascade.py +0 -0
  37. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/config.py +0 -0
  38. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/configuration.py +0 -0
  39. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/crm.py +0 -0
  40. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/crm_delivery.py +0 -0
  41. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/crm_time.py +0 -0
  42. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/datasource.py +0 -0
  43. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/devops.py +0 -0
  44. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/document.py +0 -0
  45. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/endpoint.py +0 -0
  46. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/env.py +0 -0
  47. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/function.py +0 -0
  48. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/marketplace.py +0 -0
  49. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/__init__.py +0 -0
  50. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/_common.py +0 -0
  51. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/api.py +0 -0
  52. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/create.py +0 -0
  53. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/data.py +0 -0
  54. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/pack.py +0 -0
  55. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/preview.py +0 -0
  56. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/odata.py +0 -0
  57. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/organization.py +0 -0
  58. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/proxy.py +0 -0
  59. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/reference.py +0 -0
  60. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/release_notes.py +0 -0
  61. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/repo.py +0 -0
  62. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/__init__.py +0 -0
  63. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/add_cmds.py +0 -0
  64. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/api.py +0 -0
  65. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/batch.py +0 -0
  66. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/create.py +0 -0
  67. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/data.py +0 -0
  68. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/dataset.py +0 -0
  69. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/edit.py +0 -0
  70. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/folder.py +0 -0
  71. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/preview.py +0 -0
  72. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/schema_cmds.py +0 -0
  73. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/reset.py +0 -0
  74. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/schema.py +0 -0
  75. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/servicepack.py +0 -0
  76. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/studio.py +0 -0
  77. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/telemetry.py +0 -0
  78. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/user.py +0 -0
  79. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/validation_report.py +0 -0
  80. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/context.py +0 -0
  81. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/__init__.py +0 -0
  82. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/agent_browser.py +0 -0
  83. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/__init__.py +0 -0
  84. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/app_config.py +0 -0
  85. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/endpoints.py +0 -0
  86. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/metadata_models.py +0 -0
  87. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/models.py +0 -0
  88. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/reference_graph.py +0 -0
  89. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/__init__.py +0 -0
  90. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/decorators.py +0 -0
  91. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/tenants.py +0 -0
  92. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/token_cache.py +0 -0
  93. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/cache.py +0 -0
  94. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/__init__.py +0 -0
  95. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/flow_generator.py +0 -0
  96. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/flow_models.py +0 -0
  97. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/flow_shape.py +0 -0
  98. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/generator.py +0 -0
  99. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/models.py +0 -0
  100. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/parsers.py +0 -0
  101. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/resolver.py +0 -0
  102. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/type_def_parser.py +0 -0
  103. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/validator.py +0 -0
  104. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/designer/__init__.py +0 -0
  105. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/designer/flow_models.py +0 -0
  106. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/designer/models.py +0 -0
  107. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/designer/parsers.py +0 -0
  108. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/devops/__init__.py +0 -0
  109. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/devops/helpers.py +0 -0
  110. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/devops/models.py +0 -0
  111. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/__init__.py +0 -0
  112. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/html.py +0 -0
  113. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/metadata_cache.py +0 -0
  114. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/pagination.py +0 -0
  115. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/resolvers.py +0 -0
  116. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/time_entry.py +0 -0
  117. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/footprint/__init__.py +0 -0
  118. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/footprint/client.py +0 -0
  119. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/footprint/edmx_parser.py +0 -0
  120. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/footprint/metadata.py +0 -0
  121. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/function/__init__.py +0 -0
  122. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/function/generator.py +0 -0
  123. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/function/models.py +0 -0
  124. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/function/validator.py +0 -0
  125. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/graph.py +0 -0
  126. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/onboarding.py +0 -0
  127. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/__init__.py +0 -0
  128. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/csv_fmt.py +0 -0
  129. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/formatter.py +0 -0
  130. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/json_fmt.py +0 -0
  131. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/redaction.py +0 -0
  132. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/yaml_fmt.py +0 -0
  133. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/release_notes.py +0 -0
  134. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/responses.py +0 -0
  135. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/__init__.py +0 -0
  136. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/config.py +0 -0
  137. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/identity.py +0 -0
  138. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/recorder.py +0 -0
  139. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/scrubber.py +0 -0
  140. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/sender.py +0 -0
  141. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/models/__init__.py +0 -0
  142. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/__init__.py +0 -0
  143. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/folder.py +0 -0
  144. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/manifest.py +0 -0
  145. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/mocks.py +0 -0
  146. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/preview.py +0 -0
  147. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/scaffold.py +0 -0
  148. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/serve_lock.py +0 -0
  149. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/py.typed +0 -0
  150. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/__init__.py +0 -0
  151. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/datasource_binding.py +0 -0
  152. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/engine.py +0 -0
  153. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/field_parser.py +0 -0
  154. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/folder.py +0 -0
  155. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/manifest.py +0 -0
  156. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/models.py +0 -0
  157. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/preview.py +0 -0
  158. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/preview_arjs.py +0 -0
  159. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/schema.py +0 -0
  160. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/svg_renderer.py +0 -0
  161. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/templates/__init__.py +0 -0
  162. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  163. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  164. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/validate_arjs.py +0 -0
  165. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/validator.py +0 -0
  166. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/wrapper.py +0 -0
  167. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/__init__.py +0 -0
  168. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/click_options.py +0 -0
  169. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/clipboard.py +0 -0
  170. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/errors.py +0 -0
  171. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/filtering.py +0 -0
  172. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/image.py +0 -0
  173. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/paths.py +0 -0
  174. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/resolvers.py +0 -0
  175. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/responses.py +0 -0
  176. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/restricted.py +0 -0
  177. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/sorting.py +0 -0
  178. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/update_check.py +0 -0
  179. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/__init__.py +0 -0
  180. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/app.py +0 -0
  181. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/design.py +0 -0
  182. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/proxy_app.py +0 -0
  183. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/404/index.html +0 -0
  184. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/404.html +0 -0
  185. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
  186. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
  187. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  188. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  189. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  190. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
  191. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  192. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
  193. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  194. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  195. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
  196. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
  197. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
  198. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
  199. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
  200. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  201. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  202. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
  203. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  204. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  205. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  206. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  207. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
  208. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/design/capture/index.html +0 -0
  209. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/design/capture/index.txt +0 -0
  210. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/design/index.html +0 -0
  211. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/design/index.txt +0 -0
  212. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/index.html +0 -0
  213. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/index.txt +0 -0
  214. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/render-cli.js +0 -0
  215. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/report-preview.html +0 -0
  216. {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/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.4.19
3
+ Version: 0.4.20
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.4.19"
7
+ version = "0.4.20"
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.4.19"
3
+ __version__ = "0.4.20"
4
4
  __app_name__ = "dxs"
@@ -15,6 +15,7 @@ from dxs.core.auth.datex_tenant import (
15
15
  is_datex_identity,
16
16
  )
17
17
  from dxs.core.auth.decorators import get_access_token
18
+ from dxs.core.auth.msal_client import ConsentOutcome
18
19
  from dxs.core.auth.tenants import (
19
20
  identity_needs_tenant_authority,
20
21
  is_foreign_tenant,
@@ -51,6 +52,117 @@ def _reject_tenant_override(ctx: DxsContext, command: str) -> None:
51
52
  raise SystemExit(1)
52
53
 
53
54
 
55
+ def consent_failure_items(outcomes: list[ConsentOutcome] | None) -> list[dict[str, Any]]:
56
+ """Map failed consent rounds to payload entries (empty when all succeeded).
57
+
58
+ AADSTS650057 means the resource is not listed on the CLI app registration —
59
+ no amount of retrying or user consent can fix that, so it carries an
60
+ explicit escalation suggestion (issue #135).
61
+ """
62
+ items: list[dict[str, Any]] = []
63
+ for outcome in outcomes or []:
64
+ if outcome.ok:
65
+ continue
66
+ entry: dict[str, Any] = {
67
+ "resource": outcome.resource,
68
+ "error": outcome.error,
69
+ "error_description": outcome.error_description,
70
+ }
71
+ if outcome.aadsts_code == "650057":
72
+ entry["suggestion"] = (
73
+ "This resource is not listed on the CLI app registration, so consent "
74
+ "cannot be requested for it — ask an Entra admin to grant tenant-wide "
75
+ "consent for the resource to the Datex Studio CLI app."
76
+ )
77
+ items.append(entry)
78
+ return items
79
+
80
+
81
+ def _run_targeted_consent(ctx: DxsContext, resource_key: str) -> None:
82
+ """(Re)consent one Datex-internal resource without a full re-login.
83
+
84
+ Backs ``dxs auth login --consent devops|crm`` — the recovery path for a
85
+ user whose login-time consent round was skipped or silently failed
86
+ (issue #136). Uses the cached Datex identity; the consent round requests
87
+ the *named* scope so dynamic user consent applies (issue #135).
88
+ """
89
+ from dxs.core.auth.datex_tenant import consent_datex_resource
90
+ from dxs.utils.config import get_settings
91
+
92
+ settings = get_settings()
93
+ if resource_key == "devops":
94
+ resource = "Azure DevOps"
95
+ consent_scopes = settings.azure_devops_scopes
96
+ acquire_scopes = settings.azure_devops_scopes
97
+ error_code = "DXS-DEVOPS-AUTH-001"
98
+ else:
99
+ if not settings.dynamics_crm_url:
100
+ ctx.output_error(
101
+ ValidationError(
102
+ message=(
103
+ "dynamics_crm_url is not configured; there is no Dynamics CRM "
104
+ "resource to consent to."
105
+ ),
106
+ code="DXS-DYNAMICS-CONFIG-001",
107
+ suggestions=[
108
+ "Run: dxs config set dynamics_crm_url https://yourorg.crm.dynamics.com",
109
+ "Then re-run: dxs auth login --consent crm",
110
+ ],
111
+ )
112
+ )
113
+ raise SystemExit(1)
114
+ resource = "Dynamics CRM"
115
+ consent_scopes = settings.dynamics_crm_consent_scopes
116
+ acquire_scopes = settings.dynamics_crm_scopes
117
+ error_code = "DXS-DYNAMICS-AUTH-001"
118
+
119
+ def on_device_code(device_code: dict[str, Any]) -> None:
120
+ ctx.output(
121
+ single(
122
+ item={
123
+ "status": "awaiting_authentication",
124
+ "action_required": {
125
+ "type": "device_code",
126
+ "instructions": (
127
+ f"Sign in with your Datex account to consent to {resource}"
128
+ ),
129
+ "url": device_code["verification_uri"],
130
+ "code": device_code["user_code"],
131
+ "expires_in_seconds": device_code["expires_in"],
132
+ },
133
+ "message": device_code.get("message", ""),
134
+ },
135
+ semantic_key="authentication",
136
+ ),
137
+ include_metadata=False,
138
+ )
139
+
140
+ try:
141
+ outcome = consent_datex_resource(
142
+ consent_scopes=consent_scopes,
143
+ acquire_scopes=acquire_scopes,
144
+ resource=resource,
145
+ error_code=error_code,
146
+ on_device_code=on_device_code,
147
+ )
148
+ except DxsError as e:
149
+ ctx.output_error(e)
150
+ raise SystemExit(1) from None
151
+
152
+ ctx.output(
153
+ single(
154
+ item={
155
+ "status": "consented",
156
+ "message": f"Consent for {resource} granted and verified.",
157
+ "resource": resource,
158
+ "account": outcome["identity"],
159
+ "has_token": True,
160
+ },
161
+ semantic_key="authentication",
162
+ )
163
+ )
164
+
165
+
54
166
  def run_standard_device_login(
55
167
  ctx: DxsContext,
56
168
  on_device_code: Callable[[dict[str, Any]], None],
@@ -64,6 +176,7 @@ def run_standard_device_login(
64
176
  organization_id: int | None = None,
65
177
  organization_name: str | None = None,
66
178
  api_base_url: str | None = None,
179
+ consent_outcomes: list[ConsentOutcome] | None = None,
67
180
  ) -> CachedIdentity:
68
181
  """Run the multi-resource device-code login and save the identity.
69
182
 
@@ -88,6 +201,11 @@ def run_standard_device_login(
88
201
  that target a non-default environment (e.g. ``dxs init --target qa``) must
89
202
  pass it so the identity is stamped from the same environment its profile is
90
203
  detected against, not the stale lru_cached default.
204
+
205
+ ``consent_outcomes``, when given, receives one entry per additional consent
206
+ round so callers can include failures in their success payload. Failed
207
+ rounds are always logged as warnings here regardless — an additional
208
+ resource failing never aborts the login, so this is the only signal.
91
209
  """
92
210
  from dxs.utils.config import get_settings
93
211
 
@@ -117,25 +235,36 @@ def run_standard_device_login(
117
235
  tenant is known, so DevOps/CRM consent is requested only for Datex
118
236
  staff — a customer must never be prompted for Datex-internal
119
237
  resources by name.
238
+
239
+ The CRM round requests the *named* scope, not ``.default``:
240
+ ``.default`` is static consent and fails with AADSTS650057 for any
241
+ first-time user because Dataverse is not listed on the CLI app
242
+ registration (issue #135). Token acquisition keeps ``.default``.
120
243
  """
121
244
  internal = (
122
245
  [
123
246
  settings.azure_devops_scopes,
124
- settings.dynamics_crm_scopes,
125
- settings.dynamics_crm_admin_scopes,
126
- settings.data_export_service_scopes,
247
+ settings.dynamics_crm_consent_scopes,
127
248
  ]
128
249
  if claims_are_datex(token_response.get("id_token_claims") or {})
129
250
  else []
130
251
  )
131
252
  return [scopes for scopes in [*internal, [settings.footprint_scope]] if scopes]
132
253
 
254
+ outcomes: list[ConsentOutcome] = consent_outcomes if consent_outcomes is not None else []
133
255
  result = client.authenticate_device_code_multi_resource(
134
256
  primary_scopes=settings.azure_scopes,
135
257
  additional_scope_sets=additional_scope_sets,
136
258
  on_device_code=on_device_code,
137
259
  on_additional_consent_needed=consent_cb,
260
+ consent_outcomes=outcomes,
138
261
  )
262
+ for entry in consent_failure_items(outcomes):
263
+ detail = entry["error_description"] or entry["error"] or "unknown error"
264
+ warning = f"WARNING: consent for {entry['resource']} was not obtained: {detail}"
265
+ if "suggestion" in entry:
266
+ warning += f" {entry['suggestion']}"
267
+ ctx.log(warning)
139
268
 
140
269
  saved_identity = cache.save_identity(
141
270
  result,
@@ -173,13 +302,16 @@ def _auth_success_item(
173
302
  saved_identity: CachedIdentity,
174
303
  *,
175
304
  message: str = "Successfully authenticated",
305
+ consent_outcomes: list[ConsentOutcome] | None = None,
176
306
  ) -> dict[str, Any]:
177
307
  """Build the standard ``authenticated`` response payload shared by the login paths.
178
308
 
179
309
  The external-tenant descriptor is derived from the saved identity — never from
180
310
  which CLI flags were passed — so the payload cannot disagree with the cache
181
311
  (a --tenant-id login with the home tenant is persisted as internal). Adds the
182
- ``footprint_daemon_auth`` marker when the identity carries daemon creds.
312
+ ``footprint_daemon_auth`` marker when the identity carries daemon creds, and
313
+ ``consent_failures`` when an additional consent round failed — the primary
314
+ login succeeded, so this is where a missing consent becomes visible.
183
315
  """
184
316
  item: dict[str, Any] = {
185
317
  "status": "authenticated",
@@ -194,6 +326,9 @@ def _auth_success_item(
194
326
  item["expires_at"] = saved_identity.expires_at.isoformat()
195
327
  if saved_identity.footprint_client_id:
196
328
  item["footprint_daemon_auth"] = "configured"
329
+ failures = consent_failure_items(consent_outcomes)
330
+ if failures:
331
+ item["consent_failures"] = failures
197
332
  return item
198
333
 
199
334
 
@@ -279,6 +414,16 @@ def auth() -> None:
279
414
  default=None,
280
415
  help="Entra domain (e.g. csw-wi.com) — resolves the tenant ID for you",
281
416
  )
417
+ @click.option(
418
+ "--consent",
419
+ "consent_resource",
420
+ type=click.Choice(["devops", "crm"]),
421
+ default=None,
422
+ help=(
423
+ "Only (re)run the consent round for one Datex-internal resource, using the "
424
+ "cached Datex identity — no full re-login"
425
+ ),
426
+ )
282
427
  @pass_context
283
428
  def login(
284
429
  ctx: DxsContext,
@@ -287,6 +432,7 @@ def login(
287
432
  client_secret: str | None,
288
433
  tenant_id: str | None,
289
434
  domain: str | None,
435
+ consent_resource: str | None,
290
436
  ) -> None:
291
437
  """Authenticate with Azure Entra using device code flow.
292
438
 
@@ -307,6 +453,9 @@ def login(
307
453
  credentials) authentication for the Footprint API, needed for cross-tenant
308
454
  access where OBO flow is not available.
309
455
 
456
+ Use --consent devops|crm (Datex staff) to grant a missing consent for one
457
+ internal resource without re-running the whole login.
458
+
310
459
  \b
311
460
  Examples:
312
461
  dxs auth login # Login to default tenant
@@ -314,6 +463,7 @@ def login(
314
463
  dxs auth login --domain csw-wi.com # Bootstrap: no identity required
315
464
  dxs auth login --tenant-id <guid> # Same, when you know the GUID
316
465
  dxs auth login CAG --client-id <id> --client-secret <secret>
466
+ dxs auth login --consent devops # Grant the missing DevOps consent only
317
467
  """
318
468
  _reject_tenant_override(ctx, "login")
319
469
 
@@ -331,6 +481,23 @@ def login(
331
481
  )
332
482
  raise SystemExit(1)
333
483
 
484
+ if consent_resource:
485
+ if org or tenant_id or domain or client_id or client_secret:
486
+ ctx.output_error(
487
+ ValidationError(
488
+ message=(
489
+ "--consent runs a single consent round for the cached Datex "
490
+ "identity; it cannot be combined with an organization, tenant, "
491
+ "or daemon credentials"
492
+ ),
493
+ code="DXS-AUTH-024",
494
+ suggestions=["Run 'dxs auth login --consent devops|crm' by itself"],
495
+ )
496
+ )
497
+ raise SystemExit(1)
498
+ _run_targeted_consent(ctx, consent_resource)
499
+ return
500
+
334
501
  if org and (tenant_id or domain):
335
502
  ctx.output_error(
336
503
  ValidationError(
@@ -380,6 +547,7 @@ def login(
380
547
  include_metadata=False,
381
548
  )
382
549
 
550
+ external_outcomes: list[ConsentOutcome] = []
383
551
  try:
384
552
  saved_identity = run_standard_device_login(
385
553
  ctx,
@@ -388,12 +556,15 @@ def login(
388
556
  client_secret=client_secret,
389
557
  tenant_id=tenant_id,
390
558
  domain=domain,
559
+ consent_outcomes=external_outcomes,
391
560
  )
392
561
  except AuthenticationError as e:
393
562
  ctx.output_error(e)
394
563
  raise SystemExit(1) from None
395
564
 
396
- external_success_item = _auth_success_item(saved_identity)
565
+ external_success_item = _auth_success_item(
566
+ saved_identity, consent_outcomes=external_outcomes
567
+ )
397
568
  ctx.output(single(item=external_success_item, semantic_key="authentication"))
398
569
  return
399
570
 
@@ -438,6 +609,7 @@ def login(
438
609
  include_metadata=False,
439
610
  )
440
611
 
612
+ login_outcomes: list[ConsentOutcome] = []
441
613
  try:
442
614
  saved_identity = run_standard_device_login(
443
615
  ctx,
@@ -445,12 +617,13 @@ def login(
445
617
  client_id=client_id,
446
618
  client_secret=client_secret,
447
619
  on_additional_consent_needed=on_additional_consent_needed,
620
+ consent_outcomes=login_outcomes,
448
621
  )
449
622
  except AuthenticationError as e:
450
623
  ctx.output_error(e)
451
624
  raise SystemExit(1) from None
452
625
 
453
- success_item = _auth_success_item(saved_identity)
626
+ success_item = _auth_success_item(saved_identity, consent_outcomes=login_outcomes)
454
627
  ctx.output(single(item=success_item, semantic_key="authentication"))
455
628
 
456
629
 
@@ -1415,7 +1588,9 @@ def _switch_by_organization(
1415
1588
  # Home-tenant org (Datex, or an org with no tenant of its own): the
1416
1589
  # current identity's tokens already cover it, but the org binding on
1417
1590
  # the cached identity must still be re-pointed at the requested org.
1418
- _rebind_home_tenant_identity(ctx, cache, org_info)
1591
+ _rebind_home_tenant_identity(
1592
+ ctx, cache, org_info, client_id=client_id, client_secret=client_secret
1593
+ )
1419
1594
  return
1420
1595
 
1421
1596
  # Step 4: Reject a misregistered home-tenant org BEFORE any identity lookup —
@@ -1551,6 +1726,8 @@ def _rebind_home_tenant_identity(
1551
1726
  ctx: DxsContext,
1552
1727
  cache: MultiIdentityTokenCache,
1553
1728
  org_info: Any,
1729
+ client_id: str | None = None,
1730
+ client_secret: str | None = None,
1554
1731
  ) -> None:
1555
1732
  """Re-point the active home-tenant identity at ``org_info`` (issue #112).
1556
1733
 
@@ -1561,6 +1738,10 @@ def _rebind_home_tenant_identity(
1561
1738
  made the silent no-op hard to spot — while writes fail with a misleading
1562
1739
  ``DXS-API-400``. Only the two org fields move; tokens and the active
1563
1740
  identity selection stay untouched (the issue #83 contract).
1741
+
1742
+ Supplied daemon credentials are persisted on the active identity on both
1743
+ outcomes (issue #133) — the same contract the other org-switch branches
1744
+ honor. Without credentials, the already-bound outcome writes nothing.
1564
1745
  """
1565
1746
  active = cache.get_active_identity()
1566
1747
  if active is None or identity_needs_tenant_authority(active):
@@ -1587,27 +1768,29 @@ def _rebind_home_tenant_identity(
1587
1768
  )
1588
1769
  raise SystemExit(1)
1589
1770
 
1771
+ storage_key = cache.resolve_stored_key(active) or active.identity_key
1772
+
1590
1773
  already_bound = active.organization_id == org_info.id and (
1591
1774
  (active.organization_name or "").lower() == org_info.name.lower()
1592
1775
  )
1593
1776
  if already_bound:
1594
- ctx.output(
1595
- single(
1596
- item={
1597
- "status": "no_action_required",
1598
- "message": (
1599
- f"Active identity is already bound to organization '{org_info.name}'"
1600
- ),
1601
- "active_identity": active.account_username,
1602
- "organization": org_info.name,
1603
- "organization_id": org_info.id,
1604
- },
1605
- semantic_key="authentication",
1606
- )
1607
- )
1777
+ identity = active
1778
+ if client_id and client_secret:
1779
+ with_creds = cache.update_identity_daemon_creds(storage_key, client_id, client_secret)
1780
+ if with_creds is not None:
1781
+ identity = with_creds
1782
+ item: dict[str, Any] = {
1783
+ "status": "no_action_required",
1784
+ "message": (f"Active identity is already bound to organization '{org_info.name}'"),
1785
+ "active_identity": active.account_username,
1786
+ "organization": org_info.name,
1787
+ "organization_id": org_info.id,
1788
+ }
1789
+ if identity.footprint_client_id:
1790
+ item["footprint_daemon_auth"] = "configured"
1791
+ ctx.output(single(item=item, semantic_key="authentication"))
1608
1792
  return
1609
1793
 
1610
- storage_key = cache.resolve_stored_key(active) or active.identity_key
1611
1794
  updated = cache.update_identity_org_info(storage_key, org_info.id, org_info.name)
1612
1795
  if updated is None:
1613
1796
  ctx.output_error(
@@ -1618,23 +1801,26 @@ def _rebind_home_tenant_identity(
1618
1801
  )
1619
1802
  raise SystemExit(1)
1620
1803
 
1621
- ctx.output(
1622
- single(
1623
- item={
1624
- "status": "organization_rebound",
1625
- "message": (
1626
- f"Re-bound the active identity to organization '{org_info.name}' "
1627
- "(home tenant; no login required)"
1628
- ),
1629
- "active_identity": active.account_username,
1630
- "organization": org_info.name,
1631
- "organization_id": org_info.id,
1632
- "previous_organization": active.organization_name,
1633
- "previous_organization_id": active.organization_id,
1634
- },
1635
- semantic_key="authentication",
1636
- )
1637
- )
1804
+ if client_id and client_secret:
1805
+ with_creds = cache.update_identity_daemon_creds(storage_key, client_id, client_secret)
1806
+ if with_creds is not None:
1807
+ updated = with_creds
1808
+
1809
+ item = {
1810
+ "status": "organization_rebound",
1811
+ "message": (
1812
+ f"Re-bound the active identity to organization '{org_info.name}' "
1813
+ "(home tenant; no login required)"
1814
+ ),
1815
+ "active_identity": active.account_username,
1816
+ "organization": org_info.name,
1817
+ "organization_id": org_info.id,
1818
+ "previous_organization": active.organization_name,
1819
+ "previous_organization_id": active.organization_id,
1820
+ }
1821
+ if updated.footprint_client_id:
1822
+ item["footprint_daemon_auth"] = "configured"
1823
+ ctx.output(single(item=item, semantic_key="authentication"))
1638
1824
 
1639
1825
 
1640
1826
  def _handled_home_tenant_external_org(ctx: DxsContext, org_info: Any) -> bool:
@@ -1726,6 +1912,7 @@ def _authenticate_external_tenant(
1726
1912
  include_metadata=False,
1727
1913
  )
1728
1914
 
1915
+ org_outcomes: list[ConsentOutcome] = []
1729
1916
  try:
1730
1917
  ctx.log(f"Initiating device code authentication for {org_info.name}...")
1731
1918
  saved_identity = run_standard_device_login(
@@ -1739,11 +1926,13 @@ def _authenticate_external_tenant(
1739
1926
  external_entra_id=org_info.external_entra_id,
1740
1927
  organization_id=org_info.id,
1741
1928
  organization_name=org_info.name,
1929
+ consent_outcomes=org_outcomes,
1742
1930
  )
1743
1931
 
1744
1932
  success_item = _auth_success_item(
1745
1933
  saved_identity,
1746
1934
  message=f"Successfully authenticated with {org_info.name}",
1935
+ consent_outcomes=org_outcomes,
1747
1936
  )
1748
1937
  ctx.output(single(item=success_item, semantic_key="authentication"))
1749
1938
 
@@ -337,7 +337,7 @@ def _build_config_index(client: ApiClient, branch_id: int, ctx: DxsContext) -> C
337
337
  )
338
338
  except ApiError as e:
339
339
  # Log JSON parse errors as warnings (e.g., endpoints type)
340
- if e.code == "DXS-API-JSON-001":
340
+ if e.code in ("DXS-API-JSON-001", "DXS-API-ROUTE-404"):
341
341
  ctx.log(f"Warning: {ctype} returned non-JSON response")
342
342
  # Other API errors are expected (type may not exist)
343
343
  except Exception:
@@ -5,11 +5,12 @@ from typing import Any
5
5
 
6
6
  import click
7
7
 
8
- from dxs.commands.auth import run_standard_device_login
8
+ from dxs.commands.auth import consent_failure_items, run_standard_device_login
9
9
  from dxs.context import DxsContext, pass_context
10
10
  from dxs.core.api.client import ApiClient
11
11
  from dxs.core.auth import MSALClient, MultiIdentityTokenCache
12
12
  from dxs.core.auth.decorators import get_access_token
13
+ from dxs.core.auth.msal_client import ConsentOutcome
13
14
  from dxs.core.auth.tenants import is_foreign_tenant
14
15
  from dxs.core.onboarding import (
15
16
  AuthAction,
@@ -180,6 +181,7 @@ def init(
180
181
  # actually persisted.
181
182
  external_mode = is_foreign_tenant(tenant_id)
182
183
 
184
+ consent_outcomes: list[ConsentOutcome] = []
183
185
  if action is AuthAction.INTERACTIVE:
184
186
 
185
187
  def on_device_code(dc: dict[str, Any]) -> None:
@@ -204,6 +206,7 @@ def init(
204
206
  tenant_id=tenant_id,
205
207
  domain=domain,
206
208
  api_base_url=effective_api_base_url,
209
+ consent_outcomes=consent_outcomes,
207
210
  )
208
211
  except AuthenticationError as e:
209
212
  combined = f"{e.message} {e.details}"
@@ -297,18 +300,20 @@ def init(
297
300
  if profile is not None and profile.is_staff is not None:
298
301
  profile_label = "staff" if profile.is_staff else "customer"
299
302
 
300
- ctx.output(
301
- single(
302
- item={
303
- "status": "initialized",
304
- "message": "Datex Studio CLI is configured.",
305
- "organization": profile.org_name if profile else None,
306
- "organization_id": profile.org_id if profile else None,
307
- "profile": profile_label,
308
- "devops_org": profile.devops_org if profile else None,
309
- "api_base_url": effective_api_base_url,
310
- "footprint_daemon_auth": "configured" if daemon_persisted else None,
311
- },
312
- semantic_key="init",
313
- )
314
- )
303
+ item: dict[str, Any] = {
304
+ "status": "initialized",
305
+ "message": "Datex Studio CLI is configured.",
306
+ "organization": profile.org_name if profile else None,
307
+ "organization_id": profile.org_id if profile else None,
308
+ "profile": profile_label,
309
+ "devops_org": profile.devops_org if profile else None,
310
+ "api_base_url": effective_api_base_url,
311
+ "footprint_daemon_auth": "configured" if daemon_persisted else None,
312
+ }
313
+ # A failed additional-consent round does not fail init (the primary login
314
+ # succeeded), but it must be visible next to the success status.
315
+ consent_failures = consent_failure_items(consent_outcomes)
316
+ if consent_failures:
317
+ item["consent_failures"] = consent_failures
318
+
319
+ ctx.output(single(item=item, semantic_key="init"))
@@ -281,12 +281,19 @@ def status(ctx: DxsContext, branch: int | None, repo: int | None) -> None:
281
281
  )
282
282
 
283
283
  client = ApiClient()
284
- result = {}
284
+ result: dict[str, Any] = {}
285
285
 
286
- # Get locks (requires repo_id)
286
+ # Get locks (requires repo_id). The locks route takes an applicationGroupId,
287
+ # so resolve the repo to its group(s) first — same resolution as `source locks`.
287
288
  if repo_id:
288
289
  ctx.log(f"Fetching locks for repository {repo_id}...")
289
- locks_data = client.get(SourceControlEndpoints.locks(repo_id))
290
+ group_ids = _lock_group_ids(client, repo_id)
291
+ if group_ids:
292
+ ctx.log(f"Repository {repo_id} resolved to application group(s) {group_ids}")
293
+ locks_data = _fetch_locks_for_groups(client, group_ids)
294
+ else:
295
+ # Back-compat: the value matched no repository — try it as a group ID.
296
+ locks_data = client.get(SourceControlEndpoints.locks(repo_id))
290
297
  result["locks"] = locks_data
291
298
 
292
299
  # Get feature branch changes if branch_id provided
@@ -224,6 +224,29 @@ class ApiClient:
224
224
  content_type = response.headers.get("content-type", "")
225
225
  response_preview = response.text[:500] if response.text else "(empty)"
226
226
 
227
+ # An HTML page with a success status from an API path is the SPA
228
+ # catch-all answering for a route the server does not serve — a
229
+ # missing route, not a server malfunction.
230
+ body_head = response.text.lstrip().lower() if response.text else ""
231
+ is_html_page = body_head.startswith(("<!doctype", "<html"))
232
+ if "text/html" in content_type.lower() and is_html_page:
233
+ raise ApiError(
234
+ message=("The API route does not exist (server returned the web app instead)"),
235
+ code="DXS-API-ROUTE-404",
236
+ status_code=response.status_code,
237
+ details={
238
+ "url": str(response.url),
239
+ "content_type": content_type,
240
+ "response_preview": response_preview,
241
+ },
242
+ suggestions=[
243
+ "This usually means the CLI command targets an endpoint "
244
+ "that has been renamed or removed",
245
+ "Check the URL in details.url against the server's API",
246
+ "Update the CLI if a newer version repoints this command",
247
+ ],
248
+ ) from e
249
+
227
250
  raise ApiError(
228
251
  message=f"API returned non-JSON response (status {response.status_code})",
229
252
  code="DXS-API-JSON-001",