datex-studio-cli 0.4.9__tar.gz → 0.4.10__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 (171) hide show
  1. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/PKG-INFO +1 -1
  2. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/pyproject.toml +1 -1
  3. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/__init__.py +1 -1
  4. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/auth.py +51 -4
  5. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/studio.py +85 -19
  6. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/auth/decorators.py +33 -11
  7. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/auth/msal_client.py +34 -6
  8. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/auth/token_cache.py +204 -59
  9. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/footprint/client.py +5 -0
  10. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/paths.py +33 -11
  11. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/app.py +3 -0
  12. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/.gitignore +0 -0
  13. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/README.md +0 -0
  14. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/report-creator.skill +0 -0
  15. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/scripts/analyze_corpus.py +0 -0
  16. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/scripts/generate_rdlx_schema.py +0 -0
  17. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/__main__.py +0 -0
  18. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/cli.py +0 -0
  19. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/__init__.py +0 -0
  20. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/api.py +0 -0
  21. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/branch.py +0 -0
  22. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/config.py +0 -0
  23. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/configuration.py +0 -0
  24. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/crm.py +0 -0
  25. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/datasource.py +0 -0
  26. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/devops.py +0 -0
  27. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/document.py +0 -0
  28. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/endpoint.py +0 -0
  29. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/env.py +0 -0
  30. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/explore.py +0 -0
  31. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/function.py +0 -0
  32. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/marketplace.py +0 -0
  33. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/odata.py +0 -0
  34. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/organization.py +0 -0
  35. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/proxy.py +0 -0
  36. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/release_notes.py +0 -0
  37. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/repo.py +0 -0
  38. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/__init__.py +0 -0
  39. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/add_cmds.py +0 -0
  40. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/api.py +0 -0
  41. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/batch.py +0 -0
  42. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/create.py +0 -0
  43. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/data.py +0 -0
  44. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/dataset.py +0 -0
  45. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/edit.py +0 -0
  46. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/folder.py +0 -0
  47. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/preview.py +0 -0
  48. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/report/schema_cmds.py +0 -0
  49. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/schema.py +0 -0
  50. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/servicepack.py +0 -0
  51. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/source.py +0 -0
  52. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/telemetry.py +0 -0
  53. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/commands/user.py +0 -0
  54. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/context.py +0 -0
  55. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/__init__.py +0 -0
  56. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/api/__init__.py +0 -0
  57. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/api/app_config.py +0 -0
  58. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/api/client.py +0 -0
  59. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/api/endpoints.py +0 -0
  60. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/api/metadata_models.py +0 -0
  61. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/api/models.py +0 -0
  62. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/auth/__init__.py +0 -0
  63. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/cache.py +0 -0
  64. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/__init__.py +0 -0
  65. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/flow_generator.py +0 -0
  66. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/flow_models.py +0 -0
  67. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/generator.py +0 -0
  68. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/models.py +0 -0
  69. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/parsers.py +0 -0
  70. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/resolver.py +0 -0
  71. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/type_def_parser.py +0 -0
  72. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/datasource/validator.py +0 -0
  73. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/designer/__init__.py +0 -0
  74. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/designer/flow_models.py +0 -0
  75. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/designer/models.py +0 -0
  76. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/designer/parsers.py +0 -0
  77. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/devops/__init__.py +0 -0
  78. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/devops/client.py +0 -0
  79. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/devops/helpers.py +0 -0
  80. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/devops/models.py +0 -0
  81. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/dynamics/__init__.py +0 -0
  82. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/dynamics/client.py +0 -0
  83. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/dynamics/metadata_cache.py +0 -0
  84. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/footprint/__init__.py +0 -0
  85. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/footprint/edmx_parser.py +0 -0
  86. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/footprint/metadata.py +0 -0
  87. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/function/__init__.py +0 -0
  88. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/function/generator.py +0 -0
  89. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/function/models.py +0 -0
  90. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/function/validator.py +0 -0
  91. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/graph.py +0 -0
  92. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/output/__init__.py +0 -0
  93. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/output/csv_fmt.py +0 -0
  94. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/output/formatter.py +0 -0
  95. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/output/json_fmt.py +0 -0
  96. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/output/redaction.py +0 -0
  97. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/output/yaml_fmt.py +0 -0
  98. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/release_notes.py +0 -0
  99. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/responses.py +0 -0
  100. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/telemetry/__init__.py +0 -0
  101. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/telemetry/config.py +0 -0
  102. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/telemetry/identity.py +0 -0
  103. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/telemetry/recorder.py +0 -0
  104. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/telemetry/scrubber.py +0 -0
  105. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/core/telemetry/sender.py +0 -0
  106. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/models/__init__.py +0 -0
  107. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/__init__.py +0 -0
  108. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/datasource_binding.py +0 -0
  109. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/engine.py +0 -0
  110. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/field_parser.py +0 -0
  111. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/folder.py +0 -0
  112. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/manifest.py +0 -0
  113. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/models.py +0 -0
  114. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/preview.py +0 -0
  115. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/preview_arjs.py +0 -0
  116. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/schema.py +0 -0
  117. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/svg_renderer.py +0 -0
  118. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/templates/__init__.py +0 -0
  119. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  120. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  121. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/validate_arjs.py +0 -0
  122. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/validator.py +0 -0
  123. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/report/wrapper.py +0 -0
  124. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/__init__.py +0 -0
  125. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/click_options.py +0 -0
  126. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/config.py +0 -0
  127. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/errors.py +0 -0
  128. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/filtering.py +0 -0
  129. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/image.py +0 -0
  130. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/resolvers.py +0 -0
  131. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/responses.py +0 -0
  132. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/restricted.py +0 -0
  133. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/sorting.py +0 -0
  134. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/utils/update_check.py +0 -0
  135. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/__init__.py +0 -0
  136. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/design.py +0 -0
  137. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/proxy_app.py +0 -0
  138. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/404/index.html +0 -0
  139. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/404.html +0 -0
  140. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
  141. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
  142. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  143. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  144. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  145. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
  146. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  147. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
  148. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  149. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  150. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
  151. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
  152. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
  153. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
  154. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
  155. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  156. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  157. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
  158. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  159. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  160. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  161. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  162. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
  163. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/design/capture/index.html +0 -0
  164. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/design/capture/index.txt +0 -0
  165. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/design/index.html +0 -0
  166. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/design/index.txt +0 -0
  167. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/index.html +0 -0
  168. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/index.txt +0 -0
  169. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/render-cli.js +0 -0
  170. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/src/dxs/web/static/report-preview.html +0 -0
  171. {datex_studio_cli-0.4.9 → datex_studio_cli-0.4.10}/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.9
3
+ Version: 0.4.10
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.9"
7
+ version = "0.4.10"
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.9"
3
+ __version__ = "0.4.10"
4
4
  __app_name__ = "dxs"
@@ -14,6 +14,44 @@ from dxs.utils.responses import list_response, single
14
14
  from dxs.utils.restricted import restrict_in_restricted_mode
15
15
 
16
16
 
17
+ def _format_age(seconds: int) -> str:
18
+ """Human-readable age from a number of seconds."""
19
+ if seconds < 60:
20
+ return f"{seconds} seconds"
21
+ if seconds < 3600:
22
+ return f"{seconds // 60} minutes"
23
+ if seconds < 86400:
24
+ return f"{seconds // 3600} hours"
25
+ return f"{seconds // 86400} days"
26
+
27
+
28
+ def _refresh_token_info(identity: Any, now: datetime) -> dict[str, Any]:
29
+ """Describe the identity's refresh token: presence and age.
30
+
31
+ Azure does not return a refresh-token expiry, so we surface the age of the
32
+ stored token (recorded when it was obtained) to help diagnose refresh issues.
33
+ """
34
+ if not identity.refresh_token:
35
+ return {
36
+ "present": False,
37
+ "note": "No refresh token stored; a full login is required.",
38
+ }
39
+
40
+ info: dict[str, Any] = {
41
+ "present": True,
42
+ "note": "Azure does not expose refresh-token expiry; age shown for reference.",
43
+ }
44
+ acquired_at = identity.refresh_token_acquired_at
45
+ if acquired_at is not None:
46
+ age_seconds = int((now - acquired_at).total_seconds())
47
+ info["acquired_at"] = acquired_at.isoformat()
48
+ info["age_human"] = _format_age(max(age_seconds, 0))
49
+ else:
50
+ info["acquired_at"] = None
51
+ info["age_human"] = "unknown (log in again to start tracking age)"
52
+ return info
53
+
54
+
17
55
  @click.group()
18
56
  def auth() -> None:
19
57
  """Authentication commands.
@@ -398,8 +436,8 @@ def status(ctx: DxsContext) -> None:
398
436
  # If primary token is expired, try to refresh it
399
437
  if is_expired:
400
438
  ctx.log("Primary access token is expired, attempting to refresh...")
401
- refreshed_token = cache.try_refresh()
402
- if refreshed_token:
439
+ outcome = cache.try_refresh_detailed()
440
+ if outcome.access_token:
403
441
  ctx.log("Successfully refreshed access token")
404
442
  refreshed_identity = cache.get_active_identity()
405
443
  if refreshed_identity:
@@ -415,6 +453,9 @@ def status(ctx: DxsContext) -> None:
415
453
  "message": "Authentication token has expired and refresh failed",
416
454
  "account": active_identity.account_username,
417
455
  "expired_at": active_identity.expires_at.isoformat(),
456
+ "refresh_error": outcome.error,
457
+ "refresh_error_description": outcome.error_description,
458
+ "refresh_token": _refresh_token_info(active_identity, now),
418
459
  "suggestion": "Run 'dxs auth login' to re-authenticate",
419
460
  },
420
461
  semantic_key="authentication",
@@ -430,9 +471,14 @@ def status(ctx: DxsContext) -> None:
430
471
  else:
431
472
  return f"{seconds // 3600} hours"
432
473
 
433
- # Check all cached tokens for each resource
474
+ # Check all cached tokens for each resource.
475
+ # External-tenant identities must be probed against their own tenant authority;
476
+ # redeeming their refresh token against the default tenant returns invalid_grant.
434
477
  settings = get_settings()
435
- client = MSALClient()
478
+ if active_identity.external_entra_id and active_identity.tenant_id:
479
+ client = MSALClient.for_tenant(active_identity.tenant_id)
480
+ else:
481
+ client = MSALClient()
436
482
 
437
483
  resources = {
438
484
  "datex_api": {
@@ -525,6 +571,7 @@ def status(ctx: DxsContext) -> None:
525
571
  "expires_in_seconds": expires_in_seconds,
526
572
  "expires_in_human": format_expiration(expires_in_seconds),
527
573
  },
574
+ "refresh_token": _refresh_token_info(active_identity, now),
528
575
  },
529
576
  "tokens": tokens_info if tokens_info else None,
530
577
  }
@@ -109,19 +109,41 @@ def _launch_server(port: int, no_browser: bool, dev: bool, replace: bool = False
109
109
  uvicorn.run(app, host="127.0.0.1", port=port, log_level="warning", lifespan="on")
110
110
 
111
111
 
112
- def _require_server() -> dict[str, int]:
113
- """Read the studio lockfile and return server info, or raise an error.
112
+ def _resolve_server_port(probe_port: int) -> int:
113
+ """Return the port of a running studio server, preferring the lockfile.
114
114
 
115
- Returns:
116
- dict with ``port`` and ``pid`` keys.
115
+ Falls back to probing the well-known port so a server started in another
116
+ shell or session is still found even when this process can't read or trust
117
+ the lockfile — e.g. a sandboxed Claude Code agent shell on Windows whose
118
+ PID-liveness check or ``Path.home()`` resolves differently than the user's
119
+ interactive session (issue #38).
117
120
 
118
121
  Raises:
119
- click.ClickException: If no running studio server is found.
122
+ click.ClickException: If no running studio server can be located.
120
123
  """
121
124
  lock = read_studio_lock()
122
- if lock is None:
123
- raise click.ClickException("No studio server running. Start with: dxs studio")
124
- return lock
125
+ if lock is not None:
126
+ return int(lock["port"])
127
+ if _port_in_use(probe_port):
128
+ return probe_port
129
+ raise click.ClickException("No studio server running. Start with: dxs studio")
130
+
131
+
132
+ def _unreachable_error(subcommand: str, port: int, file: str | None, exc: Exception) -> str:
133
+ """Build an actionable error for a server that is found but unreachable.
134
+
135
+ Distinguishes "no server" (handled in :func:`_resolve_server_port`) from
136
+ "found but the request failed" — typically a sandboxed agent shell that
137
+ can't open a loopback connection to the user's server.
138
+ """
139
+ arg = f" {file}" if file else ""
140
+ return (
141
+ f"Found a studio server on port {port} but couldn't reach it: {exc}\n"
142
+ "If Studio is running in your own terminal and this is a sandboxed agent "
143
+ "shell (common on Windows), the agent process may be blocked from the "
144
+ "local server. Run the command in your own session instead:\n"
145
+ f" ! dxs studio {subcommand}{arg}"
146
+ )
125
147
 
126
148
 
127
149
  @click.group(invoke_without_command=True)
@@ -166,18 +188,24 @@ def studio(ctx: click.Context, port: int, no_browser: bool, dev: bool, replace:
166
188
 
167
189
  @studio.command("open")
168
190
  @click.argument("file", type=click.Path())
169
- def studio_open(file: str) -> None:
191
+ @click.option(
192
+ "--port",
193
+ "-p",
194
+ type=int,
195
+ default=5051,
196
+ help="Port to probe when no studio lockfile is present (default: 5051)",
197
+ )
198
+ def studio_open(file: str, port: int) -> None:
170
199
  """Open a report file in the studio design canvas.
171
200
 
172
201
  FILE is the path to a report file (.rdlx-json) to open.
173
202
  """
174
- lock = _require_server()
175
- port = lock["port"]
203
+ server_port = _resolve_server_port(port)
176
204
  resolved = str(Path(file).resolve())
177
205
 
178
206
  try:
179
207
  resp = httpx.post(
180
- f"http://127.0.0.1:{port}/api/design/open",
208
+ f"http://127.0.0.1:{server_port}/api/design/open",
181
209
  json={"path": resolved},
182
210
  timeout=10.0,
183
211
  )
@@ -190,31 +218,69 @@ def studio_open(file: str) -> None:
190
218
  raise click.ClickException(detail)
191
219
  resp.raise_for_status()
192
220
  except httpx.HTTPError as exc:
193
- raise click.ClickException(f"Failed to open file in studio: {exc}") from exc
221
+ raise click.ClickException(_unreachable_error("open", server_port, file, exc)) from exc
194
222
 
195
223
  data = resp.json()
196
224
  file_path = data.get("path", resolved)
197
- click.echo(f"Opened {file_path} in studio (http://127.0.0.1:{port}/design)")
225
+ click.echo(f"Opened {file_path} in studio (http://127.0.0.1:{server_port}/design)")
198
226
 
199
227
 
200
228
  @studio.command("close")
201
- def studio_close() -> None:
229
+ @click.option(
230
+ "--port",
231
+ "-p",
232
+ type=int,
233
+ default=5051,
234
+ help="Port to probe when no studio lockfile is present (default: 5051)",
235
+ )
236
+ def studio_close(port: int) -> None:
202
237
  """Close the current design session in studio."""
203
- lock = _require_server()
204
- port = lock["port"]
238
+ server_port = _resolve_server_port(port)
205
239
 
206
240
  try:
207
241
  resp = httpx.post(
208
- f"http://127.0.0.1:{port}/api/design/close",
242
+ f"http://127.0.0.1:{server_port}/api/design/close",
209
243
  timeout=10.0,
210
244
  )
211
245
  resp.raise_for_status()
212
246
  except httpx.HTTPError as exc:
213
- raise click.ClickException(f"Failed to close design session: {exc}") from exc
247
+ raise click.ClickException(_unreachable_error("close", server_port, None, exc)) from exc
214
248
 
215
249
  click.echo("Design session closed")
216
250
 
217
251
 
252
+ @studio.command("stop")
253
+ @click.option(
254
+ "--port",
255
+ "-p",
256
+ type=int,
257
+ default=5051,
258
+ help="Port to probe when no studio lockfile is present (default: 5051)",
259
+ )
260
+ def studio_stop(port: int) -> None:
261
+ """Stop the studio server tracked by the lockfile.
262
+
263
+ Only terminates a server whose PID this CLI recorded in its own lockfile;
264
+ a process we don't manage is never killed.
265
+ """
266
+ lock = read_studio_lock()
267
+ if lock is None:
268
+ if _port_in_use(port):
269
+ raise click.ClickException(
270
+ f"A server is running on port {port} but it isn't tracked by a "
271
+ "studio lockfile, so it won't be stopped automatically. Stop it "
272
+ "from the terminal that launched it."
273
+ )
274
+ raise click.ClickException("No studio server running.")
275
+
276
+ pid = int(lock["pid"])
277
+ server_port = int(lock["port"])
278
+ _terminate_pid(pid)
279
+ _await_port_free(server_port)
280
+ remove_studio_lock()
281
+ click.echo(f"Stopped studio (pid {pid}) on port {server_port}.")
282
+
283
+
218
284
  @studio.command("status")
219
285
  @click.option(
220
286
  "--port",
@@ -9,13 +9,21 @@ from collections.abc import Callable
9
9
  from datetime import datetime, timezone
10
10
  from typing import Any, TypeVar
11
11
 
12
- from dxs.core.auth.token_cache import CachedIdentity, MultiIdentityTokenCache
12
+ from dxs.core.auth.token_cache import CachedIdentity, MultiIdentityTokenCache, RefreshResult
13
13
  from dxs.utils.errors import AuthenticationError
14
14
 
15
15
  F = TypeVar("F", bound=Callable[..., Any])
16
16
 
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
+
20
+ def _refresh_failed_message(outcome: RefreshResult) -> str:
21
+ """Build an error message that includes the underlying refresh failure reason."""
22
+ base = "Authentication token expired and refresh failed"
23
+ detail = outcome.error_description or outcome.error
24
+ return f"{base}: {detail}" if detail else base
25
+
26
+
19
27
  # Module-level cache for refreshed tokens (avoids repeated refresh calls)
20
28
  _env_token_cache: dict[str, tuple[str, datetime]] = {}
21
29
 
@@ -167,18 +175,22 @@ def require_auth(f: F) -> F:
167
175
 
168
176
  if token is None:
169
177
  # Token expired or missing - try to refresh
170
- refreshed_token = cache.try_refresh()
171
- if refreshed_token is not None:
178
+ outcome = cache.try_refresh_detailed()
179
+ if outcome.access_token is not None:
172
180
  # Refresh succeeded, continue
173
181
  return f(*args, **kwargs)
174
182
 
175
- # Refresh failed - raise appropriate error
183
+ # Refresh failed - raise appropriate error, surfacing the real reason
176
184
  identity = cache.get_active_identity()
177
185
  if identity is not None:
178
186
  raise AuthenticationError(
179
- message="Authentication token expired and refresh failed",
187
+ message=_refresh_failed_message(outcome),
180
188
  code="DXS-AUTH-001",
181
- details={"expired_at": identity.expires_at.isoformat()},
189
+ details={
190
+ "expired_at": identity.expires_at.isoformat(),
191
+ "refresh_error": outcome.error,
192
+ "refresh_error_description": outcome.error_description,
193
+ },
182
194
  suggestions=[
183
195
  "Run 'dxs auth login' to re-authenticate",
184
196
  ],
@@ -238,16 +250,20 @@ def get_access_token() -> str:
238
250
  return token
239
251
 
240
252
  # Token expired or missing - try to refresh
241
- refreshed_token = cache.try_refresh()
242
- if refreshed_token is not None:
243
- return refreshed_token
253
+ outcome = cache.try_refresh_detailed()
254
+ if outcome.access_token is not None:
255
+ return outcome.access_token
244
256
 
245
- # Refresh failed - raise appropriate error
257
+ # Refresh failed - raise appropriate error, surfacing the real reason
246
258
  identity = cache.get_active_identity()
247
259
  if identity is not None:
248
260
  raise AuthenticationError(
249
- message="Authentication token expired and refresh failed",
261
+ message=_refresh_failed_message(outcome),
250
262
  code="DXS-AUTH-001",
263
+ details={
264
+ "refresh_error": outcome.error,
265
+ "refresh_error_description": outcome.error_description,
266
+ },
251
267
  suggestions=["Run 'dxs auth login' to re-authenticate"],
252
268
  )
253
269
  else:
@@ -330,6 +346,12 @@ def get_access_token_for_scopes(scopes: list[str]) -> str:
330
346
  expires_in = result.get("expires_in", 3600)
331
347
  _env_token_cache[cache_key] = (token, datetime.now(timezone.utc))
332
348
  file_cache.save_resource_token(cache_key, token, expires_in)
349
+ # Persist the rotated refresh token so the stored token stays fresh.
350
+ # Only for default-tenant identities: this client uses the default
351
+ # authority, so a rotated token is only valid for such identities.
352
+ new_rt = result.get("refresh_token")
353
+ if new_rt and not identity.external_entra_id:
354
+ file_cache.update_refresh_token(new_rt)
333
355
  return token
334
356
  except Exception as e:
335
357
  logger.debug("Failed to acquire token for scopes %s: %s", scopes, e)
@@ -384,24 +384,52 @@ class MSALClient:
384
384
 
385
385
  return None
386
386
 
387
- def refresh_access_token(self, refresh_token: str) -> dict[str, Any] | None:
388
- """Refresh access token using refresh token grant.
387
+ def refresh_access_token_detailed(
388
+ self, refresh_token: str, scopes: list[str] | None = None
389
+ ) -> dict[str, Any]:
390
+ """Refresh an access token and return the raw MSAL result.
391
+
392
+ Unlike :meth:`refresh_access_token`, this never returns ``None`` and never
393
+ raises: on success the dict contains ``access_token`` (and usually a rotated
394
+ ``refresh_token``); on failure it contains ``error`` and ``error_description``
395
+ (e.g. the underlying ``AADSTS`` code). Callers can surface that reason instead
396
+ of a generic "refresh failed".
389
397
 
390
398
  Args:
391
399
  refresh_token: The refresh token from a previous authentication.
400
+ scopes: Scopes to request. Defaults to the client's configured scopes.
392
401
 
393
402
  Returns:
394
- Token response dict if successful, None if refresh failed.
403
+ The MSAL token response dict, or a synthetic error dict.
395
404
  """
396
405
  try:
397
406
  result = self._app.acquire_token_by_refresh_token(
398
407
  refresh_token=refresh_token,
399
- scopes=self._scopes,
408
+ scopes=scopes or self._scopes,
400
409
  )
401
- if result and "access_token" in result:
402
- return cast(dict[str, Any], result)
403
410
  except Exception as e:
404
411
  logger.debug("Failed to refresh access token: %s", e)
412
+ return {"error": "exception", "error_description": str(e)}
413
+
414
+ if result:
415
+ return cast(dict[str, Any], result)
416
+ return {
417
+ "error": "no_result",
418
+ "error_description": "No response received from the token endpoint.",
419
+ }
420
+
421
+ def refresh_access_token(self, refresh_token: str) -> dict[str, Any] | None:
422
+ """Refresh access token using refresh token grant.
423
+
424
+ Args:
425
+ refresh_token: The refresh token from a previous authentication.
426
+
427
+ Returns:
428
+ Token response dict if successful, None if refresh failed.
429
+ """
430
+ result = self.refresh_access_token_detailed(refresh_token)
431
+ if "access_token" in result:
432
+ return result
405
433
  return None
406
434
 
407
435
  def get_accounts(self) -> list[dict[str, Any]]: