datex-studio-cli 0.4.1__tar.gz → 0.4.2__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 (170) hide show
  1. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/PKG-INFO +1 -1
  2. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/pyproject.toml +1 -1
  3. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/__init__.py +1 -1
  4. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/cli.py +79 -8
  5. datex_studio_cli-0.4.2/src/dxs/commands/api.py +236 -0
  6. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/auth.py +114 -35
  7. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/config.py +7 -3
  8. datex_studio_cli-0.4.2/src/dxs/commands/configuration.py +248 -0
  9. datex_studio_cli-0.4.2/src/dxs/commands/telemetry.py +183 -0
  10. datex_studio_cli-0.4.2/src/dxs/core/telemetry/__init__.py +29 -0
  11. datex_studio_cli-0.4.2/src/dxs/core/telemetry/config.py +143 -0
  12. datex_studio_cli-0.4.2/src/dxs/core/telemetry/identity.py +89 -0
  13. datex_studio_cli-0.4.2/src/dxs/core/telemetry/recorder.py +230 -0
  14. datex_studio_cli-0.4.2/src/dxs/core/telemetry/scrubber.py +83 -0
  15. datex_studio_cli-0.4.2/src/dxs/core/telemetry/sender.py +215 -0
  16. datex_studio_cli-0.4.1/src/dxs/commands/api.py +0 -147
  17. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/.gitignore +0 -0
  18. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/README.md +0 -0
  19. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/report-creator.skill +0 -0
  20. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/scripts/analyze_corpus.py +0 -0
  21. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/scripts/generate_rdlx_schema.py +0 -0
  22. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/__main__.py +0 -0
  23. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/__init__.py +0 -0
  24. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/branch.py +0 -0
  25. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/crm.py +0 -0
  26. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/datasource.py +0 -0
  27. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/devops.py +0 -0
  28. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/document.py +0 -0
  29. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/endpoint.py +0 -0
  30. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/env.py +0 -0
  31. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/explore.py +0 -0
  32. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/function.py +0 -0
  33. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/marketplace.py +0 -0
  34. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/odata.py +0 -0
  35. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/organization.py +0 -0
  36. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/proxy.py +0 -0
  37. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/release_notes.py +0 -0
  38. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/repo.py +0 -0
  39. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/__init__.py +0 -0
  40. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/add_cmds.py +0 -0
  41. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/api.py +0 -0
  42. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/batch.py +0 -0
  43. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/create.py +0 -0
  44. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/data.py +0 -0
  45. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/dataset.py +0 -0
  46. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/edit.py +0 -0
  47. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/folder.py +0 -0
  48. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/preview.py +0 -0
  49. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/report/schema_cmds.py +0 -0
  50. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/schema.py +0 -0
  51. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/servicepack.py +0 -0
  52. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/source.py +0 -0
  53. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/studio.py +0 -0
  54. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/commands/user.py +0 -0
  55. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/context.py +0 -0
  56. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/__init__.py +0 -0
  57. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/api/__init__.py +0 -0
  58. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/api/client.py +0 -0
  59. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/api/endpoints.py +0 -0
  60. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/api/metadata_models.py +0 -0
  61. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/api/models.py +0 -0
  62. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/auth/__init__.py +0 -0
  63. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/auth/decorators.py +0 -0
  64. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/auth/msal_client.py +0 -0
  65. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/auth/token_cache.py +0 -0
  66. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/cache.py +0 -0
  67. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/__init__.py +0 -0
  68. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/flow_generator.py +0 -0
  69. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/flow_models.py +0 -0
  70. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/generator.py +0 -0
  71. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/models.py +0 -0
  72. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/parsers.py +0 -0
  73. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/resolver.py +0 -0
  74. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/type_def_parser.py +0 -0
  75. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/datasource/validator.py +0 -0
  76. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/designer/__init__.py +0 -0
  77. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/designer/flow_models.py +0 -0
  78. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/designer/models.py +0 -0
  79. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/designer/parsers.py +0 -0
  80. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/devops/__init__.py +0 -0
  81. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/devops/client.py +0 -0
  82. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/devops/helpers.py +0 -0
  83. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/devops/models.py +0 -0
  84. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/dynamics/__init__.py +0 -0
  85. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/dynamics/client.py +0 -0
  86. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/footprint/__init__.py +0 -0
  87. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/footprint/client.py +0 -0
  88. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/footprint/edmx_parser.py +0 -0
  89. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/footprint/metadata.py +0 -0
  90. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/function/__init__.py +0 -0
  91. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/function/generator.py +0 -0
  92. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/function/models.py +0 -0
  93. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/function/validator.py +0 -0
  94. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/graph.py +0 -0
  95. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/output/__init__.py +0 -0
  96. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/output/csv_fmt.py +0 -0
  97. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/output/formatter.py +0 -0
  98. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/output/json_fmt.py +0 -0
  99. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/output/redaction.py +0 -0
  100. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/output/yaml_fmt.py +0 -0
  101. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/release_notes.py +0 -0
  102. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/core/responses.py +0 -0
  103. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/models/__init__.py +0 -0
  104. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/__init__.py +0 -0
  105. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/datasource_binding.py +0 -0
  106. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/engine.py +0 -0
  107. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/field_parser.py +0 -0
  108. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/folder.py +0 -0
  109. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/manifest.py +0 -0
  110. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/models.py +0 -0
  111. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/preview.py +0 -0
  112. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/preview_arjs.py +0 -0
  113. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/schema.py +0 -0
  114. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/svg_renderer.py +0 -0
  115. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/templates/__init__.py +0 -0
  116. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  117. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  118. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/validate_arjs.py +0 -0
  119. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/validator.py +0 -0
  120. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/report/wrapper.py +0 -0
  121. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/__init__.py +0 -0
  122. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/click_options.py +0 -0
  123. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/config.py +0 -0
  124. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/errors.py +0 -0
  125. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/filtering.py +0 -0
  126. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/image.py +0 -0
  127. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/paths.py +0 -0
  128. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/resolvers.py +0 -0
  129. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/responses.py +0 -0
  130. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/restricted.py +0 -0
  131. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/sorting.py +0 -0
  132. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/utils/update_check.py +0 -0
  133. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/__init__.py +0 -0
  134. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/app.py +0 -0
  135. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/design.py +0 -0
  136. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/proxy_app.py +0 -0
  137. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/404/index.html +0 -0
  138. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/404.html +0 -0
  139. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
  140. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
  141. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  142. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  143. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  144. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
  145. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  146. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
  147. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  148. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  149. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
  150. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
  151. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
  152. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
  153. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
  154. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  155. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  156. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
  157. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  158. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  159. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  160. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  161. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
  162. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/design/capture/index.html +0 -0
  163. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/design/capture/index.txt +0 -0
  164. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/design/index.html +0 -0
  165. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/design/index.txt +0 -0
  166. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/index.html +0 -0
  167. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/index.txt +0 -0
  168. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/render-cli.js +0 -0
  169. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/report-preview.html +0 -0
  170. {datex_studio_cli-0.4.1 → datex_studio_cli-0.4.2}/src/dxs/web/static/report-validate.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datex-studio-cli
3
- Version: 0.4.1
3
+ Version: 0.4.2
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.1"
7
+ version = "0.4.2"
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.1"
3
+ __version__ = "0.4.2"
4
4
  __app_name__ = "dxs"
@@ -260,6 +260,17 @@ def cli(
260
260
  # Stash for the result_callback to honor --no-update-check
261
261
  dxs_ctx.no_update_check = no_update_check
262
262
 
263
+ # Telemetry: record invocation start (must come before any subcommand runs).
264
+ # Wrapped so a telemetry bug can never block the CLI.
265
+ try:
266
+ from dxs.core.telemetry import maybe_show_first_run_notice, record_start
267
+
268
+ invoked = ctx.invoked_subcommand or "<root>"
269
+ record_start(argv=sys.argv[1:], command=invoked)
270
+ maybe_show_first_run_notice()
271
+ except Exception: # noqa: BLE001
272
+ pass
273
+
263
274
 
264
275
  def _mask_token(token: str | None, visible_chars: int = 8) -> str:
265
276
  """Mask a token for display, showing only first/last few characters.
@@ -316,18 +327,30 @@ def handle_errors(f: F) -> F:
316
327
  try:
317
328
  return ctx.invoke(f, *args, **kwargs)
318
329
  except DxsError as e:
330
+ _record_error_silently(e)
319
331
  dxs_ctx.output_error(e)
320
332
  sys.exit(1)
321
333
  except click.ClickException:
322
334
  # Let Click handle its own exceptions
323
335
  raise
324
336
  except Exception as e:
337
+ _record_error_silently(e)
325
338
  dxs_ctx.output_error(e)
326
339
  sys.exit(1)
327
340
 
328
341
  return cast(F, wrapper)
329
342
 
330
343
 
344
+ def _record_error_silently(exc: BaseException) -> None:
345
+ """Emit a telemetry error event; never raises."""
346
+ try:
347
+ from dxs.core.telemetry import record_error
348
+
349
+ record_error(exc)
350
+ except Exception: # noqa: BLE001
351
+ pass
352
+
353
+
331
354
  # Import and register command groups
332
355
  # These imports are here to avoid circular imports
333
356
  def register_commands() -> None:
@@ -336,6 +359,7 @@ def register_commands() -> None:
336
359
  api,
337
360
  auth,
338
361
  config,
362
+ configuration,
339
363
  crm,
340
364
  datasource,
341
365
  devops,
@@ -355,6 +379,8 @@ def register_commands() -> None:
355
379
  cli.add_command(api.api)
356
380
  cli.add_command(auth.auth)
357
381
  cli.add_command(config.config)
382
+ cli.add_command(config.config, name="settings")
383
+ cli.add_command(configuration.configuration)
358
384
  cli.add_command(crm.crm)
359
385
  cli.add_command(datasource.datasource)
360
386
  cli.add_command(devops.devops)
@@ -376,14 +402,30 @@ def register_commands() -> None:
376
402
 
377
403
  cli.add_command(schema.schema)
378
404
 
405
+ # Telemetry subcommands (status / enable / disable / test)
406
+ from dxs.commands import telemetry as telemetry_cmd
407
+
408
+ cli.add_command(telemetry_cmd.telemetry)
409
+
379
410
 
380
411
  @cli.result_callback()
381
412
  @click.pass_context
382
413
  def _after_command(ctx: click.Context, result: Any, **kwargs: Any) -> Any:
383
- """Run post-command hooks (currently: PyPI update notice)."""
414
+ """Run post-command hooks (telemetry success event + PyPI update notice)."""
384
415
  from dxs.utils.update_check import maybe_show_update_notice
385
416
 
386
417
  dxs_ctx: DxsContext | None = ctx.find_object(DxsContext)
418
+
419
+ # Emit a success event. handle_errors-decorated commands never reach here
420
+ # on failure, so the only path through this branch is a successful run.
421
+ try:
422
+ from dxs.core.telemetry import record_end
423
+
424
+ ctx_extras = _ctx_extras(dxs_ctx)
425
+ record_end(exit_code=0, ctx_extras=ctx_extras)
426
+ except Exception: # noqa: BLE001
427
+ pass
428
+
387
429
  skip = bool(dxs_ctx and dxs_ctx.no_update_check)
388
430
  try:
389
431
  maybe_show_update_notice(skip=skip)
@@ -392,6 +434,18 @@ def _after_command(ctx: click.Context, result: Any, **kwargs: Any) -> Any:
392
434
  return result
393
435
 
394
436
 
437
+ def _ctx_extras(dxs_ctx: "DxsContext | None") -> dict[str, Any]:
438
+ """Pluck branch/repo/org/env from DxsContext for telemetry events."""
439
+ if dxs_ctx is None:
440
+ return {}
441
+ return {
442
+ "branch_id": dxs_ctx.branch,
443
+ "repo_id": dxs_ctx.repo,
444
+ "org": dxs_ctx.org,
445
+ "env": dxs_ctx.env,
446
+ }
447
+
448
+
395
449
  @cli.command("update")
396
450
  @click.option(
397
451
  "--check",
@@ -438,13 +492,30 @@ register_commands()
438
492
  def main() -> None:
439
493
  """Main entry point for the CLI."""
440
494
  try:
441
- cli()
442
- except DxsError as e:
443
- # Use the module-level format captured during CLI init
444
- # (Click context is torn down before exceptions reach here)
445
- formatted = format_error(e, _last_output_format)
446
- click.echo(formatted, err=True)
447
- sys.exit(1)
495
+ try:
496
+ cli()
497
+ except DxsError as e:
498
+ _record_error_silently(e)
499
+ # Use the module-level format captured during CLI init
500
+ # (Click context is torn down before exceptions reach here)
501
+ formatted = format_error(e, _last_output_format)
502
+ click.echo(formatted, err=True)
503
+ sys.exit(1)
504
+ except SystemExit:
505
+ # SystemExit from sys.exit() inside handle_errors — telemetry already
506
+ # recorded an error there; just propagate.
507
+ raise
508
+ except BaseException as e:
509
+ # Anything else escaping cli() shouldn't take telemetry with it.
510
+ _record_error_silently(e)
511
+ raise
512
+ finally:
513
+ try:
514
+ from dxs.core.telemetry import flush
515
+
516
+ flush(timeout=0.5)
517
+ except Exception: # noqa: BLE001
518
+ pass
448
519
 
449
520
 
450
521
  if __name__ == "__main__":
@@ -0,0 +1,236 @@
1
+ """Raw API request command: dxs api [METHOD] [URL] [PAYLOAD]."""
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import click
9
+ import httpx
10
+
11
+ from dxs.cli import DxsContext, pass_context
12
+ from dxs.core.auth import get_access_token, require_auth
13
+ from dxs.utils.config import get_settings, is_restricted_mode
14
+ from dxs.utils.errors import ApiError, RestrictedModeError
15
+ from dxs.utils.responses import single
16
+
17
+
18
+ @click.command()
19
+ @click.argument(
20
+ "method", type=click.Choice(["GET", "POST", "PUT", "PATCH", "DELETE"], case_sensitive=False)
21
+ )
22
+ @click.argument("url")
23
+ @click.argument("payload", required=False, default=None)
24
+ @click.option(
25
+ "--data-file",
26
+ "-D",
27
+ "data_file",
28
+ type=click.Path(exists=True, dir_okay=False, readable=True, path_type=Path),
29
+ default=None,
30
+ help="Read JSON request body from a file (avoids shell escaping for large payloads).",
31
+ )
32
+ @click.option(
33
+ "--output-file",
34
+ "-O",
35
+ "output_file",
36
+ type=click.Path(dir_okay=False, writable=True, path_type=Path),
37
+ default=None,
38
+ help="Write the response body to a file. Implies --raw.",
39
+ )
40
+ @click.option(
41
+ "--raw",
42
+ is_flag=True,
43
+ default=False,
44
+ help="Print only the response body (no envelope) as JSON to stdout. Useful for piping to jq.",
45
+ )
46
+ @click.option(
47
+ "--headers",
48
+ "-H",
49
+ multiple=True,
50
+ help="Additional headers in format 'Key: Value'",
51
+ )
52
+ @pass_context
53
+ @require_auth
54
+ def api(
55
+ ctx: DxsContext,
56
+ method: str,
57
+ url: str,
58
+ payload: str | None,
59
+ data_file: Path | None,
60
+ output_file: Path | None,
61
+ raw: bool,
62
+ headers: tuple[str, ...],
63
+ ) -> None:
64
+ """Make raw API requests with automatic authentication.
65
+
66
+ Automatically adds Bearer token authentication and handles the HTTP request.
67
+ Useful for testing and exploring API endpoints.
68
+
69
+ \b
70
+ Arguments:
71
+ METHOD HTTP method (GET, POST, PUT, PATCH, DELETE)
72
+ URL Full URL or relative path (e.g., /organizations/mine)
73
+ PAYLOAD JSON payload for POST/PUT/PATCH (optional; alternative: --data-file)
74
+
75
+ \b
76
+ Options:
77
+ -D, --data-file Read JSON body from a file (recommended for large payloads)
78
+ -O, --output-file Write response body to a file (implies --raw)
79
+ --raw Emit only the response body (no envelope) for piping
80
+ -H, --headers Additional headers (repeatable)
81
+
82
+ \b
83
+ Examples:
84
+ dxs api GET /organizations/mine
85
+ dxs api GET /applications/64/hubconfigurations/8642750 --raw -O hub.json
86
+ dxs api PUT /applications/64/hubconfigurations/8642750 --data-file hub.json
87
+ dxs api POST /applications -D payload.json
88
+ dxs api GET /branches -H "X-Custom: value"
89
+ """
90
+ if payload is not None and data_file is not None:
91
+ raise ApiError(
92
+ message="Pass either a positional PAYLOAD or --data-file, not both.",
93
+ code="DXS-API-PAYLOAD-002",
94
+ suggestions=["Drop one of the two arguments and re-run."],
95
+ )
96
+
97
+ # --output-file implies --raw (writing an envelope to disk would defeat the purpose).
98
+ if output_file is not None:
99
+ raw = True
100
+
101
+ # Block mutating methods in restricted mode
102
+ if is_restricted_mode() and method.upper() != "GET":
103
+ raise RestrictedModeError(
104
+ command_name=f"api {method.upper()}",
105
+ reason="performs mutating API requests",
106
+ )
107
+
108
+ settings = get_settings()
109
+ token = get_access_token()
110
+
111
+ # Determine if URL is relative or absolute
112
+ if url.startswith("http://") or url.startswith("https://"):
113
+ full_url = url
114
+ else:
115
+ # Relative path - prepend base URL
116
+ base_url = settings.api_base_url.rstrip("/")
117
+ path = url if url.startswith("/") else f"/{url}"
118
+ full_url = f"{base_url}{path}"
119
+
120
+ ctx.log(f"Making {method} request to {full_url}")
121
+
122
+ # Build headers
123
+ request_headers = {
124
+ "Authorization": f"Bearer {token}",
125
+ "Content-Type": "application/json",
126
+ "Accept": "application/json",
127
+ }
128
+
129
+ # Parse additional headers
130
+ for header in headers:
131
+ if ":" in header:
132
+ key, value = header.split(":", 1)
133
+ request_headers[key.strip()] = value.strip()
134
+
135
+ # Resolve and parse payload from positional arg or --data-file
136
+ payload_data = None
137
+ payload_source: str | None = None
138
+ if data_file is not None:
139
+ payload_source = f"--data-file {data_file}"
140
+ try:
141
+ payload_data = json.loads(data_file.read_text(encoding="utf-8"))
142
+ except json.JSONDecodeError as e:
143
+ raise ApiError(
144
+ message=f"Invalid JSON in {data_file}: {e}",
145
+ code="DXS-API-PAYLOAD-001",
146
+ suggestions=[f"Validate the file with: python -m json.tool {data_file}"],
147
+ ) from e
148
+ elif payload:
149
+ payload_source = "positional argument"
150
+ try:
151
+ payload_data = json.loads(payload)
152
+ except json.JSONDecodeError as e:
153
+ raise ApiError(
154
+ message=f"Invalid JSON payload: {e}",
155
+ code="DXS-API-PAYLOAD-001",
156
+ suggestions=[
157
+ "Ensure the payload is valid JSON",
158
+ "For large or complex bodies, use --data-file PATH instead.",
159
+ ],
160
+ ) from e
161
+
162
+ # Make the request
163
+ try:
164
+ with httpx.Client(timeout=settings.api_timeout, verify=settings.verify_ssl) as client:
165
+ response = client.request(
166
+ method=method.upper(),
167
+ url=full_url,
168
+ headers=request_headers,
169
+ json=payload_data if payload_data else None,
170
+ )
171
+
172
+ # Decode body once
173
+ try:
174
+ body: Any = response.json()
175
+ body_is_json = True
176
+ except Exception:
177
+ body = response.text
178
+ body_is_json = False
179
+
180
+ if raw:
181
+ _emit_raw(ctx, body, body_is_json, output_file)
182
+ return
183
+
184
+ response_data: dict[str, Any] = {
185
+ "status_code": response.status_code,
186
+ "headers": dict(response.headers),
187
+ "url": str(response.url),
188
+ "method": method.upper(),
189
+ "body": body,
190
+ "request": {
191
+ "url": full_url,
192
+ "method": method.upper(),
193
+ "headers": request_headers,
194
+ },
195
+ }
196
+ if payload_data:
197
+ response_data["request"]["payload"] = payload_data
198
+ if payload_source:
199
+ response_data["request"]["payload_source"] = payload_source
200
+
201
+ ctx.output(
202
+ single(
203
+ item=response_data,
204
+ semantic_key="api_response",
205
+ )
206
+ )
207
+
208
+ except httpx.RequestError as e:
209
+ raise ApiError(
210
+ message=f"Request failed: {e}",
211
+ code="DXS-API-REQUEST-001",
212
+ details={"url": full_url, "error": str(e)},
213
+ ) from e
214
+
215
+
216
+ def _emit_raw(
217
+ ctx: DxsContext,
218
+ body: Any,
219
+ body_is_json: bool,
220
+ output_file: Path | None,
221
+ ) -> None:
222
+ """Print or save the response body without the envelope."""
223
+ if body_is_json:
224
+ rendered = json.dumps(body, indent=2, sort_keys=False, ensure_ascii=False)
225
+ else:
226
+ rendered = body if isinstance(body, str) else str(body)
227
+
228
+ if output_file is not None:
229
+ output_file.write_text(rendered, encoding="utf-8")
230
+ ctx.log(f"Wrote response body to {output_file}")
231
+ return
232
+
233
+ sys.stdout.write(rendered)
234
+ if not rendered.endswith("\n"):
235
+ sys.stdout.write("\n")
236
+ sys.stdout.flush()
@@ -581,8 +581,23 @@ def list_identities(ctx: DxsContext) -> None:
581
581
 
582
582
  @auth.command()
583
583
  @click.argument("identifier")
584
+ @click.option(
585
+ "--client-id",
586
+ default=None,
587
+ help="Footprint API app registration client ID (for daemon auth)",
588
+ )
589
+ @click.option(
590
+ "--client-secret",
591
+ default=None,
592
+ help="Footprint API app registration client secret (for daemon auth)",
593
+ )
584
594
  @pass_context
585
- def switch(ctx: DxsContext, identifier: str) -> None:
595
+ def switch(
596
+ ctx: DxsContext,
597
+ identifier: str,
598
+ client_id: str | None,
599
+ client_secret: str | None,
600
+ ) -> None:
586
601
  """Switch to a different identity or organization.
587
602
 
588
603
  Auto-detects the identifier type:
@@ -593,15 +608,35 @@ def switch(ctx: DxsContext, identifier: str) -> None:
593
608
  this command will initiate device code flow to authenticate against the
594
609
  external tenant if no cached identity exists for that tenant.
595
610
 
611
+ Use --client-id and --client-secret together to configure daemon (client
612
+ credentials) authentication for the Footprint API on the target identity.
613
+ Required for cross-tenant access where OBO flow is not available. The
614
+ credentials are stored on the identity and reused for subsequent OData
615
+ queries.
616
+
596
617
  \b
597
618
  Examples:
598
619
  dxs auth switch user@acme.com # Switch by username (email)
599
620
  dxs auth switch CAG # Switch by organization name
621
+ dxs auth switch CAG --client-id <id> --client-secret <secret>
600
622
  """
623
+ if bool(client_id) != bool(client_secret):
624
+ ctx.output_error(
625
+ ValidationError(
626
+ message="--client-id and --client-secret must be provided together",
627
+ code="DXS-AUTH-015",
628
+ suggestions=[
629
+ "Provide both --client-id and --client-secret for daemon auth",
630
+ "Or omit both to switch without changing daemon credentials",
631
+ ],
632
+ )
633
+ )
634
+ raise SystemExit(1)
635
+
601
636
  if "@" in identifier:
602
- _switch_by_username(ctx, identifier)
637
+ _switch_by_username(ctx, identifier, client_id=client_id, client_secret=client_secret)
603
638
  else:
604
- _switch_by_organization(ctx, identifier)
639
+ _switch_by_organization(ctx, identifier, client_id=client_id, client_secret=client_secret)
605
640
 
606
641
 
607
642
  @auth.command()
@@ -681,7 +716,11 @@ def consent(ctx: DxsContext, org: str, open: bool) -> None:
681
716
  footprint_consent_url = f"{base_consent}?client_id={fp_client_id}" if fp_client_id else None
682
717
 
683
718
  consent_apps = [
684
- {"name": "Datex Studio API", "client_id": settings.azure_client_id, "url": datex_consent_url},
719
+ {
720
+ "name": "Datex Studio API",
721
+ "client_id": settings.azure_client_id,
722
+ "url": datex_consent_url,
723
+ },
685
724
  ]
686
725
  if footprint_consent_url and fp_client_id:
687
726
  consent_apps.append(
@@ -867,7 +906,12 @@ def _build_token_status(claims: dict[str, Any]) -> dict[str, Any]:
867
906
  return status
868
907
 
869
908
 
870
- def _switch_by_username(ctx: DxsContext, username: str) -> None:
909
+ def _switch_by_username(
910
+ ctx: DxsContext,
911
+ username: str,
912
+ client_id: str | None = None,
913
+ client_secret: str | None = None,
914
+ ) -> None:
871
915
  """Switch to an identity by username (original behavior)."""
872
916
  cache = MultiIdentityTokenCache()
873
917
 
@@ -922,22 +966,32 @@ def _switch_by_username(ctx: DxsContext, username: str) -> None:
922
966
  )
923
967
  raise SystemExit(1) from None
924
968
 
925
- ctx.output(
926
- single(
927
- item={
928
- "status": "switched",
929
- "message": f"Switched to {username}",
930
- "previous_identity": previous_username,
931
- "active_identity": username,
932
- "organization": new_identity.organization_name,
933
- "organization_id": new_identity.organization_id,
934
- },
935
- semantic_key="authentication",
936
- )
937
- )
969
+ # Persist daemon credentials on the identity if supplied
970
+ if client_id and client_secret:
971
+ updated = cache.update_identity_daemon_creds(username, client_id, client_secret)
972
+ if updated is not None:
973
+ new_identity = updated
974
+
975
+ item: dict[str, Any] = {
976
+ "status": "switched",
977
+ "message": f"Switched to {username}",
978
+ "previous_identity": previous_username,
979
+ "active_identity": username,
980
+ "organization": new_identity.organization_name,
981
+ "organization_id": new_identity.organization_id,
982
+ }
983
+ if new_identity.footprint_client_id:
984
+ item["footprint_daemon_auth"] = "configured"
985
+
986
+ ctx.output(single(item=item, semantic_key="authentication"))
938
987
 
939
988
 
940
- def _switch_by_organization(ctx: DxsContext, org_name: str) -> None:
989
+ def _switch_by_organization(
990
+ ctx: DxsContext,
991
+ org_name: str,
992
+ client_id: str | None = None,
993
+ client_secret: str | None = None,
994
+ ) -> None:
941
995
  """Switch to an organization, authenticating against external tenant if needed."""
942
996
 
943
997
  cache = MultiIdentityTokenCache()
@@ -946,7 +1000,14 @@ def _switch_by_organization(ctx: DxsContext, org_name: str) -> None:
946
1000
  existing_by_org = cache.get_identity_by_org_name(org_name)
947
1001
  if existing_by_org:
948
1002
  # We have an identity for this org - switch to it
949
- _activate_existing_identity(ctx, cache, existing_by_org, org_name)
1003
+ _activate_existing_identity(
1004
+ ctx,
1005
+ cache,
1006
+ existing_by_org,
1007
+ org_name,
1008
+ client_id=client_id,
1009
+ client_secret=client_secret,
1010
+ )
950
1011
  return
951
1012
 
952
1013
  # Step 2: Fetch organization details from API
@@ -992,7 +1053,14 @@ def _switch_by_organization(ctx: DxsContext, org_name: str) -> None:
992
1053
  org_info.id,
993
1054
  org_info.name,
994
1055
  )
995
- _activate_existing_identity(ctx, cache, existing_by_tenant, org_name)
1056
+ _activate_existing_identity(
1057
+ ctx,
1058
+ cache,
1059
+ existing_by_tenant,
1060
+ org_name,
1061
+ client_id=client_id,
1062
+ client_secret=client_secret,
1063
+ )
996
1064
  return
997
1065
 
998
1066
  # Step 5: Need to authenticate against external tenant
@@ -1006,7 +1074,9 @@ def _switch_by_organization(ctx: DxsContext, org_name: str) -> None:
1006
1074
  )
1007
1075
  raise SystemExit(1)
1008
1076
 
1009
- _authenticate_external_tenant(ctx, cache, org_info)
1077
+ _authenticate_external_tenant(
1078
+ ctx, cache, org_info, client_id=client_id, client_secret=client_secret
1079
+ )
1010
1080
 
1011
1081
 
1012
1082
  def _activate_existing_identity(
@@ -1014,6 +1084,8 @@ def _activate_existing_identity(
1014
1084
  cache: MultiIdentityTokenCache,
1015
1085
  identity: Any,
1016
1086
  org_name: str,
1087
+ client_id: str | None = None,
1088
+ client_secret: str | None = None,
1017
1089
  ) -> None:
1018
1090
  """Activate an existing cached identity."""
1019
1091
  # Check if token is expired
@@ -1034,20 +1106,27 @@ def _activate_existing_identity(
1034
1106
 
1035
1107
  cache.set_active_identity(identity.identity_key)
1036
1108
 
1037
- ctx.output(
1038
- single(
1039
- item={
1040
- "status": "switched",
1041
- "message": f"Switched to organization {org_name}",
1042
- "previous_identity": previous_username,
1043
- "active_identity": identity.account_username,
1044
- "organization": identity.organization_name,
1045
- "organization_id": identity.organization_id,
1046
- "external_tenant": identity.external_entra_id,
1047
- },
1048
- semantic_key="authentication",
1109
+ # Persist daemon credentials on the identity if supplied
1110
+ if client_id and client_secret and identity.account_username:
1111
+ updated = cache.update_identity_daemon_creds(
1112
+ identity.account_username, client_id, client_secret
1049
1113
  )
1050
- )
1114
+ if updated is not None:
1115
+ identity = updated
1116
+
1117
+ item: dict[str, Any] = {
1118
+ "status": "switched",
1119
+ "message": f"Switched to organization {org_name}",
1120
+ "previous_identity": previous_username,
1121
+ "active_identity": identity.account_username,
1122
+ "organization": identity.organization_name,
1123
+ "organization_id": identity.organization_id,
1124
+ "external_tenant": identity.external_entra_id,
1125
+ }
1126
+ if identity.footprint_client_id:
1127
+ item["footprint_daemon_auth"] = "configured"
1128
+
1129
+ ctx.output(single(item=item, semantic_key="authentication"))
1051
1130
 
1052
1131
 
1053
1132
  def _fetch_organization_by_name(ctx: DxsContext, org_name: str) -> Any:
@@ -10,10 +10,14 @@ from dxs.utils.restricted import restrict_in_restricted_mode
10
10
 
11
11
  @click.group()
12
12
  def config() -> None:
13
- """Configuration commands.
13
+ """CLI settings stored in ~/.datex/config.yaml.
14
14
 
15
- Manage CLI configuration stored in ~/.datex/config.yaml.
16
- Configuration can also be set via environment variables (prefixed with DXS_).
15
+ Manages local CLI configuration (api_base_url, default_org, etc.). Settings
16
+ can also be supplied via environment variables (prefixed with DXS_).
17
+
18
+ Aliased as `dxs settings` to disambiguate from `dxs configuration`, which
19
+ operates on platform configuration objects (hubs, grids, forms, flows, etc.)
20
+ on a Datex Studio branch.
17
21
  """
18
22
  pass
19
23