datex-studio-cli 0.4.15__tar.gz → 0.4.16__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 (201) hide show
  1. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/PKG-INFO +1 -1
  2. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/pyproject.toml +1 -1
  3. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/__init__.py +1 -1
  4. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/auth.py +59 -24
  5. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/crm.py +34 -128
  6. datex_studio_cli-0.4.16/src/dxs/commands/crm_delivery.py +1110 -0
  7. datex_studio_cli-0.4.16/src/dxs/commands/crm_time.py +915 -0
  8. datex_studio_cli-0.4.16/src/dxs/core/auth/tenants.py +164 -0
  9. datex_studio_cli-0.4.16/src/dxs/core/dynamics/html.py +33 -0
  10. datex_studio_cli-0.4.16/src/dxs/core/dynamics/resolvers.py +160 -0
  11. datex_studio_cli-0.4.16/src/dxs/core/dynamics/time_entry.py +148 -0
  12. datex_studio_cli-0.4.15/src/dxs/core/auth/tenants.py +0 -71
  13. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/.gitignore +0 -0
  14. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/README.md +0 -0
  15. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/report-creator.skill +0 -0
  16. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/scripts/analyze_corpus.py +0 -0
  17. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/scripts/generate_rdlx_schema.py +0 -0
  18. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/__main__.py +0 -0
  19. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/cli.py +0 -0
  20. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/__init__.py +0 -0
  21. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/api.py +0 -0
  22. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/branch.py +0 -0
  23. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/cascade.py +0 -0
  24. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/config.py +0 -0
  25. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/configuration.py +0 -0
  26. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/datasource.py +0 -0
  27. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/devops.py +0 -0
  28. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/document.py +0 -0
  29. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/endpoint.py +0 -0
  30. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/env.py +0 -0
  31. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/explore.py +0 -0
  32. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/function.py +0 -0
  33. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/init.py +0 -0
  34. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/marketplace.py +0 -0
  35. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/ng/__init__.py +0 -0
  36. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/ng/_common.py +0 -0
  37. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/ng/api.py +0 -0
  38. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/ng/create.py +0 -0
  39. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/ng/data.py +0 -0
  40. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/ng/pack.py +0 -0
  41. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/ng/preview.py +0 -0
  42. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/odata.py +0 -0
  43. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/organization.py +0 -0
  44. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/proxy.py +0 -0
  45. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/reference.py +0 -0
  46. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/release_notes.py +0 -0
  47. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/repo.py +0 -0
  48. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/__init__.py +0 -0
  49. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/add_cmds.py +0 -0
  50. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/api.py +0 -0
  51. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/batch.py +0 -0
  52. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/create.py +0 -0
  53. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/data.py +0 -0
  54. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/dataset.py +0 -0
  55. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/edit.py +0 -0
  56. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/folder.py +0 -0
  57. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/preview.py +0 -0
  58. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/report/schema_cmds.py +0 -0
  59. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/reset.py +0 -0
  60. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/schema.py +0 -0
  61. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/servicepack.py +0 -0
  62. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/source.py +0 -0
  63. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/studio.py +0 -0
  64. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/telemetry.py +0 -0
  65. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/commands/user.py +0 -0
  66. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/context.py +0 -0
  67. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/__init__.py +0 -0
  68. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/api/__init__.py +0 -0
  69. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/api/app_config.py +0 -0
  70. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/api/client.py +0 -0
  71. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/api/endpoints.py +0 -0
  72. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/api/metadata_models.py +0 -0
  73. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/api/models.py +0 -0
  74. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/api/reference_graph.py +0 -0
  75. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/auth/__init__.py +0 -0
  76. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/auth/datex_tenant.py +0 -0
  77. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/auth/decorators.py +0 -0
  78. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/auth/msal_client.py +0 -0
  79. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/auth/token_cache.py +0 -0
  80. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/cache.py +0 -0
  81. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/__init__.py +0 -0
  82. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/flow_generator.py +0 -0
  83. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/flow_models.py +0 -0
  84. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/generator.py +0 -0
  85. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/models.py +0 -0
  86. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/parsers.py +0 -0
  87. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/resolver.py +0 -0
  88. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/type_def_parser.py +0 -0
  89. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/datasource/validator.py +0 -0
  90. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/designer/__init__.py +0 -0
  91. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/designer/flow_models.py +0 -0
  92. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/designer/models.py +0 -0
  93. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/designer/parsers.py +0 -0
  94. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/devops/__init__.py +0 -0
  95. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/devops/client.py +0 -0
  96. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/devops/helpers.py +0 -0
  97. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/devops/models.py +0 -0
  98. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/dynamics/__init__.py +0 -0
  99. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/dynamics/client.py +0 -0
  100. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/dynamics/metadata_cache.py +0 -0
  101. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/footprint/__init__.py +0 -0
  102. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/footprint/client.py +0 -0
  103. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/footprint/edmx_parser.py +0 -0
  104. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/footprint/metadata.py +0 -0
  105. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/function/__init__.py +0 -0
  106. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/function/generator.py +0 -0
  107. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/function/models.py +0 -0
  108. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/function/validator.py +0 -0
  109. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/graph.py +0 -0
  110. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/onboarding.py +0 -0
  111. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/output/__init__.py +0 -0
  112. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/output/csv_fmt.py +0 -0
  113. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/output/formatter.py +0 -0
  114. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/output/json_fmt.py +0 -0
  115. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/output/redaction.py +0 -0
  116. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/output/yaml_fmt.py +0 -0
  117. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/release_notes.py +0 -0
  118. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/responses.py +0 -0
  119. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/telemetry/__init__.py +0 -0
  120. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/telemetry/config.py +0 -0
  121. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/telemetry/identity.py +0 -0
  122. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/telemetry/recorder.py +0 -0
  123. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/telemetry/scrubber.py +0 -0
  124. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/core/telemetry/sender.py +0 -0
  125. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/models/__init__.py +0 -0
  126. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/ng/__init__.py +0 -0
  127. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/ng/folder.py +0 -0
  128. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/ng/manifest.py +0 -0
  129. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/ng/mocks.py +0 -0
  130. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/ng/preview.py +0 -0
  131. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/ng/scaffold.py +0 -0
  132. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/py.typed +0 -0
  133. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/__init__.py +0 -0
  134. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/datasource_binding.py +0 -0
  135. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/engine.py +0 -0
  136. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/field_parser.py +0 -0
  137. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/folder.py +0 -0
  138. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/manifest.py +0 -0
  139. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/models.py +0 -0
  140. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/preview.py +0 -0
  141. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/preview_arjs.py +0 -0
  142. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/schema.py +0 -0
  143. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/svg_renderer.py +0 -0
  144. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/templates/__init__.py +0 -0
  145. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  146. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  147. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/validate_arjs.py +0 -0
  148. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/validator.py +0 -0
  149. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/report/wrapper.py +0 -0
  150. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/__init__.py +0 -0
  151. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/click_options.py +0 -0
  152. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/clipboard.py +0 -0
  153. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/config.py +0 -0
  154. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/errors.py +0 -0
  155. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/filtering.py +0 -0
  156. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/image.py +0 -0
  157. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/paths.py +0 -0
  158. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/resolvers.py +0 -0
  159. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/responses.py +0 -0
  160. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/restricted.py +0 -0
  161. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/sorting.py +0 -0
  162. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/update_check.py +0 -0
  163. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/utils/versioning.py +0 -0
  164. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/__init__.py +0 -0
  165. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/app.py +0 -0
  166. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/design.py +0 -0
  167. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/proxy_app.py +0 -0
  168. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/404/index.html +0 -0
  169. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/404.html +0 -0
  170. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
  171. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
  172. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  173. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  174. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  175. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
  176. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  177. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
  178. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  179. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  180. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
  181. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
  182. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
  183. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
  184. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
  185. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  186. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  187. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
  188. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  189. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  190. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  191. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  192. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
  193. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/design/capture/index.html +0 -0
  194. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/design/capture/index.txt +0 -0
  195. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/design/index.html +0 -0
  196. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/design/index.txt +0 -0
  197. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/index.html +0 -0
  198. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/index.txt +0 -0
  199. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/render-cli.js +0 -0
  200. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/src/dxs/web/static/report-preview.html +0 -0
  201. {datex_studio_cli-0.4.15 → datex_studio_cli-0.4.16}/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.15
3
+ Version: 0.4.16
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.15"
7
+ version = "0.4.16"
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.15"
3
+ __version__ = "0.4.16"
4
4
  __app_name__ = "dxs"
@@ -15,7 +15,7 @@ from dxs.core.auth.decorators import get_access_token
15
15
  from dxs.core.auth.tenants import org_needs_tenant_login, tenant_authority_for
16
16
  from dxs.core.auth.token_cache import CachedIdentity, get_identity_override
17
17
  from dxs.core.footprint import get_footprint_token
18
- from dxs.utils.errors import AuthenticationError, ValidationError
18
+ from dxs.utils.errors import AuthenticationError, DxsError, ValidationError
19
19
  from dxs.utils.responses import list_response, single
20
20
  from dxs.utils.restricted import restrict_in_restricted_mode
21
21
 
@@ -255,8 +255,12 @@ def auth() -> None:
255
255
  default=None,
256
256
  help="Footprint API app registration client secret (for daemon auth)",
257
257
  )
258
- @click.option("--tenant-id", default=None, help="External Entra tenant ID (customer self-service)")
259
- @click.option("--domain", default=None, help="External Entra domain name (display only)")
258
+ @click.option("--tenant-id", default=None, help="Entra tenant ID (customer self-service)")
259
+ @click.option(
260
+ "--domain",
261
+ default=None,
262
+ help="Entra domain (e.g. csw-wi.com) — resolves the tenant ID for you",
263
+ )
260
264
  @pass_context
261
265
  def login(
262
266
  ctx: DxsContext,
@@ -275,8 +279,11 @@ def login(
275
279
  If you are already authenticated with a different identity, this will
276
280
  add a new identity and set it as active (use 'dxs auth switch' to change).
277
281
 
278
- Optionally specify an organization name to authenticate directly against
279
- an external tenant (for organizations with external B2C authentication).
282
+ Name an organization to authenticate against its own Entra tenant, looked up
283
+ from the API. On a machine with no credentials that lookup is impossible, so
284
+ pass --domain instead: it is resolved against Entra's public discovery
285
+ document and needs no prior authentication. --tenant-id does the same when
286
+ you already know the GUID.
280
287
 
281
288
  Use --client-id and --client-secret together to configure daemon (client
282
289
  credentials) authentication for the Footprint API, needed for cross-tenant
@@ -284,8 +291,10 @@ def login(
284
291
 
285
292
  \b
286
293
  Examples:
287
- dxs auth login # Login to default tenant
288
- dxs auth login CAG # Login to CAG's external tenant
294
+ dxs auth login # Login to default tenant
295
+ dxs auth login CAG # Login to CAG's tenant (needs an identity)
296
+ dxs auth login --domain csw-wi.com # Bootstrap: no identity required
297
+ dxs auth login --tenant-id <guid> # Same, when you know the GUID
289
298
  dxs auth login CAG --client-id <id> --client-secret <secret>
290
299
  """
291
300
  _reject_tenant_override(ctx, "login")
@@ -304,20 +313,33 @@ def login(
304
313
  )
305
314
  raise SystemExit(1)
306
315
 
307
- if tenant_id and org:
316
+ if org and (tenant_id or domain):
308
317
  ctx.output_error(
309
318
  ValidationError(
310
- message="Pass either an organization name or --tenant-id, not both",
319
+ message="Pass either an organization name or --domain/--tenant-id, not both",
311
320
  code="DXS-AUTH-017",
312
321
  suggestions=[
313
- "dxs auth login <org> # Datex staff / known org",
314
- "dxs auth login --tenant-id <id> # customer self-service",
322
+ "dxs auth login <org> # Datex staff / known org",
323
+ "dxs auth login --domain <domain> # customer self-service",
324
+ "dxs auth login --tenant-id <guid> # when you know the GUID",
315
325
  ],
316
326
  )
317
327
  )
318
328
  raise SystemExit(1)
319
329
 
320
- if tenant_id:
330
+ if tenant_id or domain:
331
+ # Bootstrap path: no API lookup, so it works with an empty credentials
332
+ # cache. An explicit GUID wins, making the domain lookup free to skip.
333
+ if not tenant_id:
334
+ from dxs.core.auth.tenants import normalize_entra_domain, resolve_tenant_id_from_domain
335
+
336
+ try:
337
+ tenant_id = resolve_tenant_id_from_domain(domain) # type: ignore[arg-type]
338
+ except DxsError as e:
339
+ ctx.output_error(e)
340
+ raise SystemExit(1) from None
341
+ # Store/display the bare domain, never the sign-in address it came from.
342
+ domain = normalize_entra_domain(domain) # type: ignore[arg-type]
321
343
 
322
344
  def on_device_code_external(device_code: dict[str, Any]) -> None:
323
345
  ctx.output(
@@ -873,12 +895,12 @@ def switch(
873
895
  @click.option(
874
896
  "--tenant-id",
875
897
  default=None,
876
- help="External Entra tenant ID — self-service; skips the organization lookup",
898
+ help="Entra tenant ID — self-service; skips the organization lookup",
877
899
  )
878
900
  @click.option(
879
901
  "--domain",
880
902
  default=None,
881
- help="External Entra domain name (display only)",
903
+ help="Entra domain (e.g. csw-wi.com) — resolves the tenant ID for you",
882
904
  )
883
905
  @click.option("--open/--no-open", default=True, help="Open URL in browser (default: yes)")
884
906
  @pass_context
@@ -889,33 +911,46 @@ def consent(
889
911
  domain: str | None,
890
912
  open: bool,
891
913
  ) -> None:
892
- """Generate admin consent URL(s) for an organization's external tenant.
914
+ """Generate the admin consent URL for an organization's Entra tenant.
893
915
 
894
- Provide either an organization NAME (Datex staff — looked up from the API) or
895
- a --tenant-id directly (customer self-service — no prior authentication needed).
916
+ Identify the tenant one of three ways: an organization NAME (Datex staff —
917
+ looked up from the API), a --domain (resolved against Entra's public
918
+ discovery document, no authentication needed), or a --tenant-id directly.
896
919
  """
897
920
  import webbrowser
898
921
 
922
+ from dxs.core.auth.tenants import normalize_entra_domain, resolve_tenant_id_from_domain
899
923
  from dxs.utils.config import get_settings
900
924
 
901
- if not org and not tenant_id:
925
+ if not org and not tenant_id and not domain:
902
926
  ctx.output_error(
903
927
  ValidationError(
904
- message="Provide an organization name or --tenant-id",
928
+ message="Provide an organization name, --domain, or --tenant-id",
905
929
  code="DXS-AUTH-016",
906
930
  suggestions=[
907
- "dxs auth consent <org> # Datex staff (API lookup)",
908
- "dxs auth consent --tenant-id <id> # customer self-service",
931
+ "dxs auth consent <org> # Datex staff (API lookup)",
932
+ "dxs auth consent --domain <domain> # resolves the tenant for you",
933
+ "dxs auth consent --tenant-id <id> # when you already know the GUID",
909
934
  ],
910
935
  )
911
936
  )
912
937
  raise SystemExit(1)
913
938
 
914
- if tenant_id:
915
- # Self-service: use the supplied descriptor, no API lookup.
939
+ if tenant_id or domain:
940
+ # Self-service: no API lookup. An explicit GUID wins over the domain, so
941
+ # supplying both never costs a network round trip.
916
942
  org_name = org or "your organization"
917
943
  org_id: int | None = None
918
- resolved_tenant_id = tenant_id
944
+ if tenant_id:
945
+ resolved_tenant_id = tenant_id
946
+ else:
947
+ try:
948
+ resolved_tenant_id = resolve_tenant_id_from_domain(domain) # type: ignore[arg-type]
949
+ except DxsError as e:
950
+ ctx.output_error(e)
951
+ raise SystemExit(1) from None
952
+ # Display the bare domain, never the sign-in address it came from.
953
+ domain = normalize_entra_domain(domain) # type: ignore[arg-type]
919
954
  resolved_domain = domain
920
955
  else:
921
956
  org_info = _fetch_organization_by_name(ctx, org) # type: ignore[arg-type]
@@ -9,10 +9,18 @@ import click
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from dxs.cli import DxsContext
12
- from markdownify import markdownify as md
13
12
 
13
+ from dxs.commands.crm_delivery import request_group, requirement_group
14
+ from dxs.commands.crm_time import time_group
14
15
  from dxs.core.auth import require_auth
15
16
  from dxs.core.dynamics.client import DynamicsClient
17
+ from dxs.core.dynamics.html import html_to_markdown as _html_to_markdown
18
+ from dxs.core.dynamics.resolvers import (
19
+ is_guid,
20
+ resolve_account,
21
+ resolve_case_for_write,
22
+ resolve_picklist_value,
23
+ )
16
24
  from dxs.core.output.yaml_fmt import LiteralString
17
25
  from dxs.utils.responses import count_response, list_response, search_response, single
18
26
  from dxs.utils.restricted import check_restricted_mode_for_option, restrict_in_restricted_mode
@@ -31,10 +39,6 @@ _CASE_STATUS_LABELS = {
31
39
  2: "Cancelled",
32
40
  }
33
41
 
34
- _GUID_PATTERN = re.compile(
35
- r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
36
- )
37
-
38
42
 
39
43
  def _extract_attachment_urls(markdown: str | None) -> list[dict[str, str]]:
40
44
  """Extract attachment URLs and UUIDs from markdown content.
@@ -193,29 +197,6 @@ def _download_attachments(
193
197
  return downloaded
194
198
 
195
199
 
196
- def _html_to_markdown(html: str | None) -> str | LiteralString | None:
197
- """Convert HTML content to Markdown.
198
-
199
- Args:
200
- html: HTML string to convert.
201
-
202
- Returns:
203
- Markdown string (LiteralString for multiline), or None if input was None/empty.
204
- """
205
- if not html:
206
- return None
207
-
208
- # Convert HTML to Markdown, stripping script/style tags
209
- markdown = md(html, strip=["script", "style"]).strip()
210
-
211
- # Use LiteralString for multiline content (renders as YAML block scalar)
212
- # LiteralString automatically handles trailing whitespace cleanup
213
- if "\n" in markdown:
214
- return LiteralString(markdown)
215
-
216
- return markdown
217
-
218
-
219
200
  # =============================================================================
220
201
  # HTML → plain text stripping (for compact activity output)
221
202
  # =============================================================================
@@ -553,11 +534,6 @@ _COMMENT_STATE_COMPLETED = 1
553
534
  _COMMENT_STATUS_SENT = 2
554
535
 
555
536
 
556
- def _is_guid(value: str) -> bool:
557
- """Whether a value is a bare GUID rather than a name to look up."""
558
- return bool(_GUID_PATTERN.match(value.strip().strip("{}")))
559
-
560
-
561
537
  def _wrap_description_html(text: str) -> str:
562
538
  """Convert plain text to the HTML the case description field expects.
563
539
 
@@ -614,47 +590,6 @@ def _read_body(text: str | None, text_file: "Path | None", flag: str) -> str:
614
590
  return body
615
591
 
616
592
 
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
-
628
- def _resolve_account(client: "DynamicsClient", value: str) -> tuple[str, str]:
629
- """Resolve an account name or GUID to (account_id, account_name)."""
630
- from dxs.utils.errors import ValidationError
631
-
632
- if _is_guid(value):
633
- account_id = value.strip().strip("{}")
634
- record = client.get(f"/accounts({account_id})", params={"$select": "accountid,name"})
635
- return account_id, record.get("name") or account_id
636
-
637
- matches = client.search_accounts(value).get("value", [])
638
- if len(matches) == 1:
639
- return matches[0]["accountid"], matches[0].get("name") or ""
640
-
641
- if not matches:
642
- raise ValidationError(
643
- message=f"No active account matches '{value}'",
644
- code="DXS-CRM-CASE-ACCOUNT",
645
- suggestions=[
646
- f"Search for the account: dxs crm account search '{value}'",
647
- "Pass the accountid GUID directly if the name is not searchable",
648
- ],
649
- )
650
-
651
- raise ValidationError(
652
- message=f"'{value}' matches {len(matches)} accounts - narrow it or pass the GUID",
653
- code="DXS-CRM-CASE-ACCOUNT",
654
- suggestions=[f"{m.get('name')} ({m.get('accountid')})" for m in matches[:10]],
655
- )
656
-
657
-
658
593
  def _resolve_contact(
659
594
  client: "DynamicsClient", account_id: str, account_name: str, value: str
660
595
  ) -> tuple[str, str]:
@@ -666,7 +601,7 @@ def _resolve_contact(
666
601
  """
667
602
  from dxs.utils.errors import ValidationError
668
603
 
669
- if _is_guid(value):
604
+ if is_guid(value):
670
605
  return value.strip().strip("{}"), value
671
606
 
672
607
  contacts = client.list_contacts_for_account(account_id).get("value", [])
@@ -720,7 +655,7 @@ def _resolve_entitlement(
720
655
  """
721
656
  from dxs.utils.errors import ValidationError
722
657
 
723
- if value and _is_guid(value):
658
+ if value and is_guid(value):
724
659
  return value.strip().strip("{}"), value
725
660
 
726
661
  all_entitlements = client.list_entitlements_for_account(account_id).get("value", [])
@@ -761,45 +696,6 @@ def _resolve_entitlement(
761
696
  )
762
697
 
763
698
 
764
- def _resolve_picklist_value(
765
- client: "DynamicsClient", entity: str, attribute: str, value: str, flag: str
766
- ) -> tuple[int, str]:
767
- """Resolve an option-set label or integer to (value, label).
768
-
769
- Integers are validated rather than trusted, so a stale option value fails
770
- here instead of writing a meaningless number onto the record.
771
- """
772
- from dxs.utils.errors import ValidationError
773
-
774
- options = client.get_picklist_options(entity, attribute)
775
- needle = value.strip().lower()
776
-
777
- def _fail(message: str, candidates: list[dict[str, Any]]) -> ValidationError:
778
- return ValidationError(
779
- message=message,
780
- code="DXS-CRM-CASE-PICKLIST",
781
- suggestions=[f"{o.get('label')} ({o.get('value')})" for o in candidates[:20]],
782
- )
783
-
784
- if re.fullmatch(r"-?\d+", needle):
785
- wanted = int(needle)
786
- for option in options:
787
- if option.get("value") == wanted:
788
- return wanted, str(option.get("label"))
789
- raise _fail(f"{flag}: {wanted} is not a valid {attribute} value", options)
790
-
791
- exact = [o for o in options if str(o.get("label", "")).lower() == needle]
792
- matches = exact or [o for o in options if needle in str(o.get("label", "")).lower()]
793
-
794
- if len(matches) == 1:
795
- return int(matches[0]["value"]), str(matches[0].get("label"))
796
-
797
- if not matches:
798
- raise _fail(f"{flag}: '{value}' is not a valid {attribute} option", options)
799
-
800
- raise _fail(f"{flag}: '{value}' matches {len(matches)} options", matches)
801
-
802
-
803
699
  def _format_entitlement(entitlement: dict[str, Any]) -> dict[str, Any]:
804
700
  """Format an entitlement for display."""
805
701
  return {
@@ -827,9 +723,9 @@ def _format_contact(contact: dict[str, Any]) -> dict[str, Any]:
827
723
  def crm() -> None:
828
724
  """Dynamics CRM / Dataverse commands.
829
725
 
830
- Query support cases, accounts, and activities - or run raw OData against
831
- any Dataverse entity. Read-only apart from 'case create/note/comment' and
832
- 'commit-ref'.
726
+ Query support cases, accounts, activities and time entries - or run raw
727
+ OData against any Dataverse entity. Read-only apart from
728
+ 'case create/note/comment', 'time create' and 'commit-ref'.
833
729
 
834
730
  \b
835
731
  Setup (one-time):
@@ -841,6 +737,9 @@ def crm() -> None:
841
737
  case Support cases: search, get, create, note, comment, types
842
738
  account Accounts (companies): list, search, contacts, entitlements
843
739
  activity Portal comments & emails - by date range or ticket
740
+ time Time entries: list, get, summary, create
741
+ request Project Requests + priced lines (read-only)
742
+ requirement Requirements (project tasks): the unit of delivery
844
743
  metadata Discover entities, fields, and relationships
845
744
  picklist Resolve option-set (picklist) integer values <-> labels
846
745
  odata Raw OData against ANY entity (escape hatch)
@@ -910,6 +809,13 @@ def case() -> None:
910
809
  # Register case as subcommand of crm
911
810
  crm.add_command(case)
912
811
 
812
+ # Time entries live in their own module; crm.py is large enough already.
813
+ crm.add_command(time_group)
814
+
815
+ # Delivery: Project Requests (pre-requirement) and Requirements (the deliverable).
816
+ crm.add_command(request_group)
817
+ crm.add_command(requirement_group)
818
+
913
819
 
914
820
  @case.command()
915
821
  @click.argument("query", required=False, default=None)
@@ -1318,7 +1224,7 @@ def case_create(
1318
1224
  client = DynamicsClient()
1319
1225
 
1320
1226
  ctx.log(f"Resolving account '{account}'...")
1321
- account_id, account_name = _resolve_account(client, account)
1227
+ account_id, account_name = resolve_account(client, account)
1322
1228
 
1323
1229
  ctx.log(f"Resolving contact '{contact}' at {account_name}...")
1324
1230
  contact_id, contact_name = _resolve_contact(client, account_id, account_name, contact)
@@ -1328,10 +1234,10 @@ def case_create(
1328
1234
  client, account_id, account_name, entitlement
1329
1235
  )
1330
1236
 
1331
- type_value, type_label = _resolve_picklist_value(
1237
+ type_value, type_label = resolve_picklist_value(
1332
1238
  client, "incident", "casetypecode", case_type, "--type"
1333
1239
  )
1334
- priority_value, priority_label = _resolve_picklist_value(
1240
+ priority_value, priority_label = resolve_picklist_value(
1335
1241
  client, "incident", "prioritycode", priority, "--priority"
1336
1242
  )
1337
1243
 
@@ -1347,14 +1253,14 @@ def case_create(
1347
1253
 
1348
1254
  impact_label = None
1349
1255
  if business_impact is not None:
1350
- impact_value, impact_label = _resolve_picklist_value(
1256
+ impact_value, impact_label = resolve_picklist_value(
1351
1257
  client, "incident", "daa_immediatebusinessimpact", business_impact, "--business-impact"
1352
1258
  )
1353
1259
  payload["daa_immediatebusinessimpact"] = impact_value
1354
1260
 
1355
1261
  origin_label = None
1356
1262
  if origin is not None:
1357
- origin_value, origin_label = _resolve_picklist_value(
1263
+ origin_value, origin_label = resolve_picklist_value(
1358
1264
  client, "incident", "caseorigincode", origin, "--origin"
1359
1265
  )
1360
1266
  payload["caseorigincode"] = origin_value
@@ -1468,7 +1374,7 @@ def case_note(
1468
1374
  client = DynamicsClient()
1469
1375
 
1470
1376
  ctx.log(f"Resolving case {ticket_number}...")
1471
- incident = _resolve_case_for_write(client, ticket_number)
1377
+ incident = resolve_case_for_write(client, ticket_number)
1472
1378
  incident_id = incident["incidentid"]
1473
1379
 
1474
1380
  payload: dict[str, Any] = {
@@ -1559,7 +1465,7 @@ def case_comment(
1559
1465
  client = DynamicsClient()
1560
1466
 
1561
1467
  ctx.log(f"Resolving case {ticket_number}...")
1562
- incident = _resolve_case_for_write(client, ticket_number)
1468
+ incident = resolve_case_for_write(client, ticket_number)
1563
1469
  incident_id = incident["incidentid"]
1564
1470
 
1565
1471
  account_name = incident.get("_customerid_value@OData.Community.Display.V1.FormattedValue") or ""
@@ -2185,7 +2091,7 @@ def account_contacts(ctx: "DxsContext", account_name: str) -> None:
2185
2091
  """
2186
2092
  client = DynamicsClient()
2187
2093
 
2188
- account_id, resolved_name = _resolve_account(client, account_name)
2094
+ account_id, resolved_name = resolve_account(client, account_name)
2189
2095
  ctx.log(f"Fetching contacts for {resolved_name}...")
2190
2096
  raw = client.list_contacts_for_account(account_id).get("value", [])
2191
2097
 
@@ -2226,7 +2132,7 @@ def account_entitlements(ctx: "DxsContext", account_name: str, include_inactive:
2226
2132
  """
2227
2133
  client = DynamicsClient()
2228
2134
 
2229
- account_id, resolved_name = _resolve_account(client, account_name)
2135
+ account_id, resolved_name = resolve_account(client, account_name)
2230
2136
  ctx.log(f"Fetching entitlements for {resolved_name}...")
2231
2137
  raw = client.list_entitlements_for_account(account_id).get("value", [])
2232
2138
 
@@ -3759,7 +3665,7 @@ def _validate_task_id(task_id: str) -> str:
3759
3665
  from dxs.utils.errors import ValidationError
3760
3666
 
3761
3667
  candidate = task_id.strip().strip("{}")
3762
- if not _GUID_PATTERN.match(candidate):
3668
+ if not is_guid(candidate):
3763
3669
  raise ValidationError(
3764
3670
  message=f"'{task_id}' is not a valid msdyn_projecttask GUID",
3765
3671
  code="DXS-CRM-COMMIT-001",