datex-studio-cli 0.4.14__tar.gz → 0.4.15__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 (195) hide show
  1. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/PKG-INFO +1 -1
  2. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/pyproject.toml +1 -1
  3. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/__init__.py +1 -1
  4. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/crm.py +344 -7
  5. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/dynamics/client.py +10 -1
  6. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/.gitignore +0 -0
  7. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/README.md +0 -0
  8. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/report-creator.skill +0 -0
  9. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/scripts/analyze_corpus.py +0 -0
  10. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/scripts/generate_rdlx_schema.py +0 -0
  11. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/__main__.py +0 -0
  12. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/cli.py +0 -0
  13. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/__init__.py +0 -0
  14. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/api.py +0 -0
  15. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/auth.py +0 -0
  16. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/branch.py +0 -0
  17. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/cascade.py +0 -0
  18. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/config.py +0 -0
  19. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/configuration.py +0 -0
  20. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/datasource.py +0 -0
  21. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/devops.py +0 -0
  22. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/document.py +0 -0
  23. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/endpoint.py +0 -0
  24. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/env.py +0 -0
  25. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/explore.py +0 -0
  26. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/function.py +0 -0
  27. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/init.py +0 -0
  28. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/marketplace.py +0 -0
  29. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/ng/__init__.py +0 -0
  30. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/ng/_common.py +0 -0
  31. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/ng/api.py +0 -0
  32. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/ng/create.py +0 -0
  33. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/ng/data.py +0 -0
  34. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/ng/pack.py +0 -0
  35. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/ng/preview.py +0 -0
  36. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/odata.py +0 -0
  37. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/organization.py +0 -0
  38. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/proxy.py +0 -0
  39. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/reference.py +0 -0
  40. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/release_notes.py +0 -0
  41. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/repo.py +0 -0
  42. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/__init__.py +0 -0
  43. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/add_cmds.py +0 -0
  44. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/api.py +0 -0
  45. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/batch.py +0 -0
  46. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/create.py +0 -0
  47. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/data.py +0 -0
  48. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/dataset.py +0 -0
  49. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/edit.py +0 -0
  50. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/folder.py +0 -0
  51. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/preview.py +0 -0
  52. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/report/schema_cmds.py +0 -0
  53. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/reset.py +0 -0
  54. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/schema.py +0 -0
  55. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/servicepack.py +0 -0
  56. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/source.py +0 -0
  57. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/studio.py +0 -0
  58. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/telemetry.py +0 -0
  59. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/commands/user.py +0 -0
  60. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/context.py +0 -0
  61. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/__init__.py +0 -0
  62. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/api/__init__.py +0 -0
  63. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/api/app_config.py +0 -0
  64. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/api/client.py +0 -0
  65. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/api/endpoints.py +0 -0
  66. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/api/metadata_models.py +0 -0
  67. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/api/models.py +0 -0
  68. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/api/reference_graph.py +0 -0
  69. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/auth/__init__.py +0 -0
  70. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/auth/datex_tenant.py +0 -0
  71. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/auth/decorators.py +0 -0
  72. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/auth/msal_client.py +0 -0
  73. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/auth/tenants.py +0 -0
  74. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/auth/token_cache.py +0 -0
  75. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/cache.py +0 -0
  76. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/__init__.py +0 -0
  77. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/flow_generator.py +0 -0
  78. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/flow_models.py +0 -0
  79. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/generator.py +0 -0
  80. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/models.py +0 -0
  81. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/parsers.py +0 -0
  82. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/resolver.py +0 -0
  83. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/type_def_parser.py +0 -0
  84. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/datasource/validator.py +0 -0
  85. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/designer/__init__.py +0 -0
  86. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/designer/flow_models.py +0 -0
  87. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/designer/models.py +0 -0
  88. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/designer/parsers.py +0 -0
  89. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/devops/__init__.py +0 -0
  90. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/devops/client.py +0 -0
  91. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/devops/helpers.py +0 -0
  92. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/devops/models.py +0 -0
  93. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/dynamics/__init__.py +0 -0
  94. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/dynamics/metadata_cache.py +0 -0
  95. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/footprint/__init__.py +0 -0
  96. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/footprint/client.py +0 -0
  97. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/footprint/edmx_parser.py +0 -0
  98. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/footprint/metadata.py +0 -0
  99. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/function/__init__.py +0 -0
  100. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/function/generator.py +0 -0
  101. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/function/models.py +0 -0
  102. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/function/validator.py +0 -0
  103. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/graph.py +0 -0
  104. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/onboarding.py +0 -0
  105. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/output/__init__.py +0 -0
  106. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/output/csv_fmt.py +0 -0
  107. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/output/formatter.py +0 -0
  108. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/output/json_fmt.py +0 -0
  109. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/output/redaction.py +0 -0
  110. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/output/yaml_fmt.py +0 -0
  111. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/release_notes.py +0 -0
  112. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/responses.py +0 -0
  113. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/telemetry/__init__.py +0 -0
  114. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/telemetry/config.py +0 -0
  115. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/telemetry/identity.py +0 -0
  116. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/telemetry/recorder.py +0 -0
  117. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/telemetry/scrubber.py +0 -0
  118. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/core/telemetry/sender.py +0 -0
  119. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/models/__init__.py +0 -0
  120. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/ng/__init__.py +0 -0
  121. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/ng/folder.py +0 -0
  122. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/ng/manifest.py +0 -0
  123. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/ng/mocks.py +0 -0
  124. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/ng/preview.py +0 -0
  125. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/ng/scaffold.py +0 -0
  126. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/py.typed +0 -0
  127. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/__init__.py +0 -0
  128. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/datasource_binding.py +0 -0
  129. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/engine.py +0 -0
  130. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/field_parser.py +0 -0
  131. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/folder.py +0 -0
  132. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/manifest.py +0 -0
  133. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/models.py +0 -0
  134. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/preview.py +0 -0
  135. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/preview_arjs.py +0 -0
  136. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/schema.py +0 -0
  137. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/svg_renderer.py +0 -0
  138. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/templates/__init__.py +0 -0
  139. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  140. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  141. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/validate_arjs.py +0 -0
  142. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/validator.py +0 -0
  143. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/report/wrapper.py +0 -0
  144. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/__init__.py +0 -0
  145. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/click_options.py +0 -0
  146. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/clipboard.py +0 -0
  147. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/config.py +0 -0
  148. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/errors.py +0 -0
  149. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/filtering.py +0 -0
  150. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/image.py +0 -0
  151. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/paths.py +0 -0
  152. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/resolvers.py +0 -0
  153. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/responses.py +0 -0
  154. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/restricted.py +0 -0
  155. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/sorting.py +0 -0
  156. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/update_check.py +0 -0
  157. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/utils/versioning.py +0 -0
  158. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/__init__.py +0 -0
  159. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/app.py +0 -0
  160. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/design.py +0 -0
  161. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/proxy_app.py +0 -0
  162. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/404/index.html +0 -0
  163. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/404.html +0 -0
  164. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
  165. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
  166. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  167. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  168. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  169. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
  170. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  171. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
  172. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  173. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  174. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
  175. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
  176. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
  177. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
  178. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
  179. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  180. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  181. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
  182. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  183. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  184. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  185. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  186. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
  187. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/design/capture/index.html +0 -0
  188. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/design/capture/index.txt +0 -0
  189. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/design/index.html +0 -0
  190. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/design/index.txt +0 -0
  191. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/index.html +0 -0
  192. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/index.txt +0 -0
  193. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/render-cli.js +0 -0
  194. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/src/dxs/web/static/report-preview.html +0 -0
  195. {datex_studio_cli-0.4.14 → datex_studio_cli-0.4.15}/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.14
3
+ Version: 0.4.15
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.14"
7
+ version = "0.4.15"
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.14"
3
+ __version__ = "0.4.15"
4
4
  __app_name__ = "dxs"
@@ -525,6 +525,33 @@ _DESCRIPTION_WRAPPER = (
525
525
 
526
526
  _ENTITLEMENT_ACTIVE_STATE = 1
527
527
 
528
+ _ANNOTATION_ENTITY_SET = "annotations"
529
+ _PORTAL_COMMENT_ENTITY_SET = "adx_portalcomments"
530
+
531
+ # The CRM rich-text editor's own markup. Notes and portal comments authored in
532
+ # the UI all carry it, so a CLI-authored one renders like its neighbours rather
533
+ # than as an unstyled string.
534
+ _CK_WRAPPER = (
535
+ '<div class="ck-content" data-wrapper="true" dir="ltr" '
536
+ 'style="--ck-image-style-spacing: 1.5em; --ck-inline-image-style-spacing: '
537
+ "calc(var(--ck-image-style-spacing) / 2); font-family: Segoe UI; "
538
+ 'font-size: 11pt;">{body}</div>'
539
+ )
540
+
541
+ # Activity-party roles. 8 (regarding) and 9 (owner) are added by the platform.
542
+ _PARTY_SENDER = 1
543
+ _PARTY_RECIPIENT = 2
544
+
545
+ # adx_portalcommentdirectioncode.
546
+ _COMMENT_DIRECTION_OUTGOING = 2
547
+
548
+ # Marking the comment sent is a second step. An activity is created in the Open
549
+ # state, `statecode` is not writable on create, and "Sent" belongs to Completed
550
+ # — so setting it in the create body fails the whole request with
551
+ # "2 is not a valid status code for state code adx_portalcommentState.Open".
552
+ _COMMENT_STATE_COMPLETED = 1
553
+ _COMMENT_STATUS_SENT = 2
554
+
528
555
 
529
556
  def _is_guid(value: str) -> bool:
530
557
  """Whether a value is a bare GUID rather than a name to look up."""
@@ -549,6 +576,55 @@ def _wrap_description_html(text: str) -> str:
549
576
  )
550
577
 
551
578
 
579
+ def _wrap_note_html(text: str) -> str:
580
+ """Convert plain text to the CRM editor's HTML for a note or comment body.
581
+
582
+ Blank-line-separated paragraphs become ``<p style="margin: 0;">`` siblings
583
+ inside one wrapper div; single newlines become ``<br>``. Quotes are left as
584
+ typed — see :func:`_wrap_description_html` for why.
585
+ """
586
+ from html import escape
587
+
588
+ paragraphs = [p for p in re.split(r"\n\s*\n", text.strip()) if p.strip()]
589
+ body = "".join(
590
+ f'<p style="margin: 0;">{escape(p.strip(), quote=False).replace(chr(10), "<br>")}</p>'
591
+ for p in paragraphs
592
+ )
593
+ return _CK_WRAPPER.format(body=body)
594
+
595
+
596
+ def _read_body(text: str | None, text_file: "Path | None", flag: str) -> str:
597
+ """Take the body from an argument or a file, and insist it is not blank."""
598
+ from dxs.utils.errors import ValidationError
599
+
600
+ if text is not None and text_file is not None:
601
+ raise ValidationError(
602
+ message=f"Pass either {flag} text or --text-file, not both",
603
+ code="DXS-CRM-CASE-BODY",
604
+ suggestions=["The body has one source; the other would be silently dropped"],
605
+ )
606
+
607
+ body = text_file.read_text(encoding="utf-8") if text_file else text
608
+ if body is None or not body.strip():
609
+ raise ValidationError(
610
+ message="The body cannot be empty",
611
+ code="DXS-CRM-CASE-BODY",
612
+ suggestions=["Pass the text as an argument, or read it from --text-file"],
613
+ )
614
+ return body
615
+
616
+
617
+ def _resolve_case_for_write(client: "DynamicsClient", ticket_number: str) -> dict[str, Any]:
618
+ """Resolve a ticket number to the one incident a write may land on.
619
+
620
+ ``resolve_ticket`` raises when a ticket number matches several cases, which
621
+ is what we want here: appending to an arbitrary one of them would be worse
622
+ than failing.
623
+ """
624
+ incident = client.resolve_ticket(ticket_number)
625
+ return client.get_incident(incident["incidentid"])
626
+
627
+
552
628
  def _resolve_account(client: "DynamicsClient", value: str) -> tuple[str, str]:
553
629
  """Resolve an account name or GUID to (account_id, account_name)."""
554
630
  from dxs.utils.errors import ValidationError
@@ -752,7 +828,8 @@ def crm() -> None:
752
828
  """Dynamics CRM / Dataverse commands.
753
829
 
754
830
  Query support cases, accounts, and activities - or run raw OData against
755
- any Dataverse entity. Read-only apart from 'case create' and 'commit-ref'.
831
+ any Dataverse entity. Read-only apart from 'case create/note/comment' and
832
+ 'commit-ref'.
756
833
 
757
834
  \b
758
835
  Setup (one-time):
@@ -761,7 +838,7 @@ def crm() -> None:
761
838
 
762
839
  \b
763
840
  Pick a command:
764
- case Support cases (incidents): search, get, create, query, types
841
+ case Support cases: search, get, create, note, comment, types
765
842
  account Accounts (companies): list, search, contacts, entitlements
766
843
  activity Portal comments & emails - by date range or ticket
767
844
  metadata Discover entities, fields, and relationships
@@ -800,11 +877,19 @@ def case() -> None:
800
877
 
801
878
  \b
802
879
  Subcommands:
803
- search Find cases by text, account, status, date, or type
804
- get Fetch cases by ticket number (+ activity / notes)
805
- create Open a new case (the only write here - try --dry-run first)
806
- query Custom OData over incidents (full field control)
807
- types List case-type picklist values
880
+ search Find cases by text, account, status, date, or type
881
+ get Fetch cases by ticket number (+ activity / notes)
882
+ create Open a new case
883
+ note Add an internal note (staff-only)
884
+ comment Reply to the customer (they see it on the portal)
885
+ query Custom OData over incidents (full field control)
886
+ types List case-type picklist values
887
+
888
+ \b
889
+ note vs comment - the difference is who reads it:
890
+ note -> annotation, internal to staff
891
+ comment -> portal comment, visible to the customer
892
+ Writes support --dry-run; 'comment' also confirms before posting.
808
893
 
809
894
  \b
810
895
  Ticket numbers are NOT unique - 'get' returns a list; check
@@ -1328,6 +1413,258 @@ def case_create(
1328
1413
  ctx.output(single(item=item, semantic_key="case", created=True))
1329
1414
 
1330
1415
 
1416
+ @case.command("note")
1417
+ @click.argument("ticket_number")
1418
+ @click.argument("text", required=False, default=None)
1419
+ @click.option(
1420
+ "--text-file",
1421
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
1422
+ default=None,
1423
+ help="Read the note body from a file instead of the command line.",
1424
+ )
1425
+ @click.option("--subject", default=None, help="Optional note subject (usually left blank).")
1426
+ @click.option(
1427
+ "--dry-run",
1428
+ is_flag=True,
1429
+ default=False,
1430
+ help="Show the note that would be created, without creating it.",
1431
+ )
1432
+ @click.pass_obj
1433
+ @require_auth
1434
+ @restrict_in_restricted_mode("writes to Dynamics CRM records")
1435
+ def case_note(
1436
+ ctx: "DxsContext",
1437
+ ticket_number: str,
1438
+ text: str | None,
1439
+ text_file: Path | None,
1440
+ subject: str | None,
1441
+ dry_run: bool,
1442
+ ) -> None:
1443
+ """Add an internal note to a case.
1444
+
1445
+ Writes an annotation — what the CRM Notes tab shows and what
1446
+ 'dxs crm case get --include-notes' reads back. It is internal to staff:
1447
+ the customer does not see it on the support portal. To reply to the
1448
+ customer, use 'dxs crm case comment' instead.
1449
+
1450
+ \b
1451
+ Arguments:
1452
+ TICKET_NUMBER Case ticket number (e.g. C260303_0017)
1453
+ TEXT The note body (or use --text-file)
1454
+
1455
+ \b
1456
+ Examples:
1457
+ dxs crm case note C260303_0017 "Root cause is the material swap rule."
1458
+ dxs crm case note C260303_0017 --text-file findings.md
1459
+ dxs crm case note C260303_0017 "Checked the logs" --dry-run
1460
+
1461
+ \b
1462
+ Ticket numbers are not unique. If this one matches more than one case the
1463
+ command fails rather than picking one — pull the case list with
1464
+ 'dxs crm case get <ticket>' and note the right one by GUID.
1465
+ """
1466
+ body = _read_body(text, text_file, "the note")
1467
+
1468
+ client = DynamicsClient()
1469
+
1470
+ ctx.log(f"Resolving case {ticket_number}...")
1471
+ incident = _resolve_case_for_write(client, ticket_number)
1472
+ incident_id = incident["incidentid"]
1473
+
1474
+ payload: dict[str, Any] = {
1475
+ "notetext": _wrap_note_html(body),
1476
+ "isdocument": False,
1477
+ "objectid_incident@odata.bind": f"/incidents({incident_id})",
1478
+ }
1479
+ if subject:
1480
+ payload["subject"] = subject
1481
+
1482
+ item: dict[str, Any] = {
1483
+ "ticketnumber": incident.get("ticketnumber"),
1484
+ "case_title": incident.get("title"),
1485
+ "incident_id": incident_id,
1486
+ "visibility": "internal",
1487
+ }
1488
+
1489
+ if dry_run:
1490
+ ctx.output(single(item={**item, "dry_run": True, "record": payload}, semantic_key="note"))
1491
+ return
1492
+
1493
+ ctx.log(f"Adding note to {incident.get('ticketnumber')}...")
1494
+ note_id = client.create(f"/{_ANNOTATION_ENTITY_SET}", payload)
1495
+
1496
+ ctx.output(single(item={**item, "note_id": note_id, "created": True}, semantic_key="note"))
1497
+
1498
+
1499
+ @case.command("comment")
1500
+ @click.argument("ticket_number")
1501
+ @click.argument("text", required=False, default=None)
1502
+ @click.option(
1503
+ "--text-file",
1504
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
1505
+ default=None,
1506
+ help="Read the comment body from a file instead of the command line.",
1507
+ )
1508
+ @click.option(
1509
+ "--to",
1510
+ "to_contact",
1511
+ default=None,
1512
+ help="Recipient contact - name, email or GUID. Defaults to the case's primary contact.",
1513
+ )
1514
+ @click.option("--yes", is_flag=True, default=False, help="Skip the confirmation prompt.")
1515
+ @click.option(
1516
+ "--dry-run",
1517
+ is_flag=True,
1518
+ default=False,
1519
+ help="Show the comment that would be posted, without posting it.",
1520
+ )
1521
+ @click.pass_obj
1522
+ @require_auth
1523
+ @restrict_in_restricted_mode("writes to Dynamics CRM records")
1524
+ def case_comment(
1525
+ ctx: "DxsContext",
1526
+ ticket_number: str,
1527
+ text: str | None,
1528
+ text_file: Path | None,
1529
+ to_contact: str | None,
1530
+ yes: bool,
1531
+ dry_run: bool,
1532
+ ) -> None:
1533
+ """Reply to the customer on a case (visible to them).
1534
+
1535
+ Posts an outgoing portal comment — what the customer reads and replies to
1536
+ on the support portal, and what 'dxs crm activity portalcomment' lists.
1537
+ This is customer-facing, so it confirms before posting unless --yes is
1538
+ given. For a staff-only note, use 'dxs crm case note' instead.
1539
+
1540
+ \b
1541
+ Arguments:
1542
+ TICKET_NUMBER Case ticket number (e.g. C260303_0017)
1543
+ TEXT The comment body (or use --text-file)
1544
+
1545
+ \b
1546
+ Examples:
1547
+ dxs crm case comment C260303_0017 "We've identified the cause."
1548
+ dxs crm case comment C260303_0017 --text-file reply.md --dry-run
1549
+ dxs crm case comment C260303_0017 "Fixed in 2.4" --to kelly@example.com --yes
1550
+
1551
+ \b
1552
+ The recipient defaults to the case's primary contact. Run --dry-run first
1553
+ to see who that resolves to.
1554
+ """
1555
+ from dxs.utils.errors import ApiError, DxsError, ValidationError
1556
+
1557
+ body = _read_body(text, text_file, "the comment")
1558
+
1559
+ client = DynamicsClient()
1560
+
1561
+ ctx.log(f"Resolving case {ticket_number}...")
1562
+ incident = _resolve_case_for_write(client, ticket_number)
1563
+ incident_id = incident["incidentid"]
1564
+
1565
+ account_name = incident.get("_customerid_value@OData.Community.Display.V1.FormattedValue") or ""
1566
+ contact_id: str | None
1567
+ if to_contact:
1568
+ account_id = incident.get("_customerid_value")
1569
+ if not account_id:
1570
+ raise ValidationError(
1571
+ message=f"Case {ticket_number} has no customer account to look up contacts in",
1572
+ code="DXS-CRM-CASE-RECIPIENT",
1573
+ suggestions=["Pass --to as a contact GUID"],
1574
+ )
1575
+ contact_id, contact_name = _resolve_contact(client, account_id, account_name, to_contact)
1576
+ else:
1577
+ contact_id = incident.get("_primarycontactid_value")
1578
+ contact_name = (
1579
+ incident.get("_primarycontactid_value@OData.Community.Display.V1.FormattedValue") or ""
1580
+ )
1581
+ if not contact_id:
1582
+ raise ValidationError(
1583
+ message=f"Case {ticket_number} has no primary contact to reply to",
1584
+ code="DXS-CRM-CASE-RECIPIENT",
1585
+ suggestions=[
1586
+ "Name the recipient explicitly: --to <name|email>",
1587
+ f"List the account's contacts: dxs crm account contacts '{account_name}'",
1588
+ ],
1589
+ )
1590
+
1591
+ sender_id = client.whoami()
1592
+
1593
+ payload: dict[str, Any] = {
1594
+ "description": _wrap_note_html(body),
1595
+ "adx_portalcommentdirectioncode": _COMMENT_DIRECTION_OUTGOING,
1596
+ "regardingobjectid_incident_adx_portalcomment@odata.bind": f"/incidents({incident_id})",
1597
+ "adx_portalcomment_activity_parties": [
1598
+ {
1599
+ "partyid_systemuser@odata.bind": f"/systemusers({sender_id})",
1600
+ "participationtypemask": _PARTY_SENDER,
1601
+ },
1602
+ {
1603
+ "partyid_contact@odata.bind": f"/contacts({contact_id})",
1604
+ "participationtypemask": _PARTY_RECIPIENT,
1605
+ },
1606
+ ],
1607
+ }
1608
+
1609
+ item: dict[str, Any] = {
1610
+ "ticketnumber": incident.get("ticketnumber"),
1611
+ "case_title": incident.get("title"),
1612
+ "incident_id": incident_id,
1613
+ "recipient": {"id": contact_id, "name": contact_name},
1614
+ "account": account_name,
1615
+ "visibility": "customer",
1616
+ }
1617
+
1618
+ if dry_run:
1619
+ ctx.output(
1620
+ single(item={**item, "dry_run": True, "record": payload}, semantic_key="comment")
1621
+ )
1622
+ return
1623
+
1624
+ if not yes:
1625
+ # click.echo, not ctx.log: the preview is part of the prompt, and being
1626
+ # asked to approve a customer-facing message without seeing it — which
1627
+ # is what --quiet would otherwise do — is worse than not being asked.
1628
+ preview = [
1629
+ "This posts a reply the customer will see on the support portal.",
1630
+ f" Case: {incident.get('ticketnumber')} ({account_name})",
1631
+ f" Recipient: {contact_name}",
1632
+ "",
1633
+ *(f" | {line}" for line in body.strip().splitlines()),
1634
+ "",
1635
+ ]
1636
+ click.echo("\n".join(preview), err=True)
1637
+ click.confirm("Post this comment to the customer?", abort=True, err=True)
1638
+
1639
+ ctx.log(f"Posting comment on {incident.get('ticketnumber')}...")
1640
+ comment_id = client.create(f"/{_PORTAL_COMMENT_ENTITY_SET}", payload)
1641
+
1642
+ # Second step, not decoration: a comment left Open is a draft the customer
1643
+ # never sees, so a failure here is reported as a failure to send.
1644
+ try:
1645
+ client.patch(
1646
+ f"/{_PORTAL_COMMENT_ENTITY_SET}({comment_id})",
1647
+ {"statecode": _COMMENT_STATE_COMPLETED, "statuscode": _COMMENT_STATUS_SENT},
1648
+ )
1649
+ except DxsError as exc:
1650
+ raise ApiError(
1651
+ message=(
1652
+ f"Comment saved as a draft on {incident.get('ticketnumber')} but could not "
1653
+ f"be marked sent - the customer will not see it ({exc.message})"
1654
+ ),
1655
+ code="DXS-CRM-COMMENT-UNSENT",
1656
+ details={"comment_id": comment_id, "incident_id": incident_id},
1657
+ suggestions=[
1658
+ "Mark it sent in CRM, or delete the draft and retry: "
1659
+ f'dxs crm odata adx_portalcomments -f "activityid eq {comment_id}"',
1660
+ ],
1661
+ ) from exc
1662
+
1663
+ ctx.output(
1664
+ single(item={**item, "comment_id": comment_id, "created": True}, semantic_key="comment")
1665
+ )
1666
+
1667
+
1331
1668
  @case.command("get")
1332
1669
  @click.argument("ticket_number")
1333
1670
  @click.option(
@@ -896,7 +896,7 @@ class DynamicsClient:
896
896
  INCIDENT_SELECT = (
897
897
  "incidentid,ticketnumber,title,description,"
898
898
  "statecode,statuscode,createdon,modifiedon,"
899
- "_customerid_value,_ownerid_value,"
899
+ "_customerid_value,_ownerid_value,_primarycontactid_value,"
900
900
  "casetypecode,prioritycode,severitycode,daa_immediatebusinessimpact,"
901
901
  "isescalated,escalatedon,resolveby,responseby,"
902
902
  "adx_resolution,adx_resolutiondate"
@@ -915,6 +915,15 @@ class DynamicsClient:
915
915
  )
916
916
  return ",".join(expand_parts)
917
917
 
918
+ def whoami(self) -> str:
919
+ """Return the calling user's systemuserid.
920
+
921
+ Needed to name the sender on an outgoing activity: the platform sets
922
+ ``ownerid`` on its own, but the sender activity party has to be built
923
+ explicitly.
924
+ """
925
+ return cast(str, self.get("/WhoAmI")["UserId"])
926
+
918
927
  def list_contacts_for_account(self, account_id: str) -> dict[str, Any]:
919
928
  """List the active contacts belonging to an account.
920
929