datex-studio-cli 0.3.4__tar.gz → 0.4.0__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 (161) hide show
  1. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/PKG-INFO +1 -1
  2. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/pyproject.toml +1 -1
  3. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/__init__.py +1 -1
  4. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/cli.py +64 -0
  5. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/context.py +1 -0
  6. datex_studio_cli-0.4.0/src/dxs/utils/update_check.py +221 -0
  7. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/.gitignore +0 -0
  8. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/README.md +0 -0
  9. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/report-creator.skill +0 -0
  10. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/scripts/analyze_corpus.py +0 -0
  11. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/scripts/generate_rdlx_schema.py +0 -0
  12. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/__main__.py +0 -0
  13. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/__init__.py +0 -0
  14. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/api.py +0 -0
  15. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/auth.py +0 -0
  16. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/branch.py +0 -0
  17. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/config.py +0 -0
  18. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/crm.py +0 -0
  19. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/datasource.py +0 -0
  20. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/devops.py +0 -0
  21. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/document.py +0 -0
  22. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/endpoint.py +0 -0
  23. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/env.py +0 -0
  24. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/explore.py +0 -0
  25. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/function.py +0 -0
  26. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/marketplace.py +0 -0
  27. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/odata.py +0 -0
  28. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/organization.py +0 -0
  29. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/proxy.py +0 -0
  30. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/release_notes.py +0 -0
  31. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/repo.py +0 -0
  32. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/__init__.py +0 -0
  33. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/add_cmds.py +0 -0
  34. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/api.py +0 -0
  35. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/batch.py +0 -0
  36. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/create.py +0 -0
  37. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/data.py +0 -0
  38. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/dataset.py +0 -0
  39. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/edit.py +0 -0
  40. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/folder.py +0 -0
  41. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/preview.py +0 -0
  42. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/report/schema_cmds.py +0 -0
  43. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/schema.py +0 -0
  44. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/servicepack.py +0 -0
  45. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/source.py +0 -0
  46. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/studio.py +0 -0
  47. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/commands/user.py +0 -0
  48. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/__init__.py +0 -0
  49. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/api/__init__.py +0 -0
  50. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/api/client.py +0 -0
  51. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/api/endpoints.py +0 -0
  52. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/api/metadata_models.py +0 -0
  53. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/api/models.py +0 -0
  54. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/auth/__init__.py +0 -0
  55. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/auth/decorators.py +0 -0
  56. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/auth/msal_client.py +0 -0
  57. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/auth/token_cache.py +0 -0
  58. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/cache.py +0 -0
  59. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/__init__.py +0 -0
  60. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/flow_generator.py +0 -0
  61. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/flow_models.py +0 -0
  62. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/generator.py +0 -0
  63. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/models.py +0 -0
  64. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/parsers.py +0 -0
  65. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/resolver.py +0 -0
  66. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/type_def_parser.py +0 -0
  67. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/validator.py +0 -0
  68. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/designer/__init__.py +0 -0
  69. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/designer/flow_models.py +0 -0
  70. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/designer/models.py +0 -0
  71. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/designer/parsers.py +0 -0
  72. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/devops/__init__.py +0 -0
  73. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/devops/client.py +0 -0
  74. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/devops/helpers.py +0 -0
  75. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/devops/models.py +0 -0
  76. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/dynamics/__init__.py +0 -0
  77. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/dynamics/client.py +0 -0
  78. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/footprint/__init__.py +0 -0
  79. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/footprint/client.py +0 -0
  80. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/footprint/edmx_parser.py +0 -0
  81. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/footprint/metadata.py +0 -0
  82. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/function/__init__.py +0 -0
  83. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/function/generator.py +0 -0
  84. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/function/models.py +0 -0
  85. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/function/validator.py +0 -0
  86. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/graph.py +0 -0
  87. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/output/__init__.py +0 -0
  88. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/output/csv_fmt.py +0 -0
  89. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/output/formatter.py +0 -0
  90. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/output/json_fmt.py +0 -0
  91. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/output/redaction.py +0 -0
  92. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/output/yaml_fmt.py +0 -0
  93. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/release_notes.py +0 -0
  94. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/core/responses.py +0 -0
  95. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/models/__init__.py +0 -0
  96. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/__init__.py +0 -0
  97. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/datasource_binding.py +0 -0
  98. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/engine.py +0 -0
  99. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/field_parser.py +0 -0
  100. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/folder.py +0 -0
  101. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/manifest.py +0 -0
  102. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/models.py +0 -0
  103. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/preview.py +0 -0
  104. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/preview_arjs.py +0 -0
  105. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/schema.py +0 -0
  106. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/svg_renderer.py +0 -0
  107. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/templates/__init__.py +0 -0
  108. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
  109. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
  110. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/validate_arjs.py +0 -0
  111. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/validator.py +0 -0
  112. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/report/wrapper.py +0 -0
  113. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/__init__.py +0 -0
  114. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/click_options.py +0 -0
  115. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/config.py +0 -0
  116. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/errors.py +0 -0
  117. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/filtering.py +0 -0
  118. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/image.py +0 -0
  119. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/paths.py +0 -0
  120. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/resolvers.py +0 -0
  121. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/responses.py +0 -0
  122. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/restricted.py +0 -0
  123. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/utils/sorting.py +0 -0
  124. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/__init__.py +0 -0
  125. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/app.py +0 -0
  126. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/design.py +0 -0
  127. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/proxy_app.py +0 -0
  128. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/404/index.html +0 -0
  129. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/404.html +0 -0
  130. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
  131. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
  132. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
  133. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
  134. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
  135. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
  136. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
  137. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
  138. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
  139. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
  140. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
  141. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
  142. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
  143. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
  144. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
  145. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
  146. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
  147. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
  148. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
  149. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
  150. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  151. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
  152. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
  153. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/design/capture/index.html +0 -0
  154. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/design/capture/index.txt +0 -0
  155. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/design/index.html +0 -0
  156. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/design/index.txt +0 -0
  157. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/index.html +0 -0
  158. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/index.txt +0 -0
  159. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/render-cli.js +0 -0
  160. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/src/dxs/web/static/report-preview.html +0 -0
  161. {datex_studio_cli-0.3.4 → datex_studio_cli-0.4.0}/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.3.4
3
+ Version: 0.4.0
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.3.4"
7
+ version = "0.4.0"
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.3.4"
3
+ __version__ = "0.4.0"
4
4
  __app_name__ = "dxs"
@@ -162,6 +162,13 @@ class DxsGroup(click.Group):
162
162
  default=False,
163
163
  help="Show authentication environment variables for debugging",
164
164
  )
165
+ @click.option(
166
+ "--no-update-check",
167
+ "no_update_check",
168
+ is_flag=True,
169
+ default=False,
170
+ help="Skip the PyPI update check for this invocation",
171
+ )
165
172
  @click.pass_context
166
173
  def cli(
167
174
  ctx: click.Context,
@@ -177,6 +184,7 @@ def cli(
177
184
  save_path: str | None,
178
185
  force_overwrite: bool,
179
186
  diagnose_auth: bool,
187
+ no_update_check: bool,
180
188
  ) -> None:
181
189
  """Datex Studio CLI - Command-line interface for Datex Studio platform.
182
190
 
@@ -249,6 +257,9 @@ def cli(
249
257
  if diagnose_auth:
250
258
  _show_auth_diagnostics()
251
259
 
260
+ # Stash for the result_callback to honor --no-update-check
261
+ dxs_ctx.no_update_check = no_update_check
262
+
252
263
 
253
264
  def _mask_token(token: str | None, visible_chars: int = 8) -> str:
254
265
  """Mask a token for display, showing only first/last few characters.
@@ -366,6 +377,59 @@ def register_commands() -> None:
366
377
  cli.add_command(schema.schema)
367
378
 
368
379
 
380
+ @cli.result_callback()
381
+ @click.pass_context
382
+ def _after_command(ctx: click.Context, result: Any, **kwargs: Any) -> Any:
383
+ """Run post-command hooks (currently: PyPI update notice)."""
384
+ from dxs.utils.update_check import maybe_show_update_notice
385
+
386
+ dxs_ctx: DxsContext | None = ctx.find_object(DxsContext)
387
+ skip = bool(dxs_ctx and dxs_ctx.no_update_check)
388
+ try:
389
+ maybe_show_update_notice(skip=skip)
390
+ except Exception: # noqa: BLE001 - never let the update check break the CLI
391
+ pass
392
+ return result
393
+
394
+
395
+ @cli.command("update")
396
+ @click.option(
397
+ "--check",
398
+ "check_only",
399
+ is_flag=True,
400
+ default=False,
401
+ help="Print whether an update is available without prompting to install",
402
+ )
403
+ def update_cmd(check_only: bool) -> None:
404
+ """Check PyPI for a newer release and optionally upgrade in place."""
405
+ from dxs.utils.update_check import (
406
+ _fetch_latest_version,
407
+ _run_upgrade,
408
+ detect_upgrade_command,
409
+ is_newer,
410
+ )
411
+
412
+ click.echo(f"Current version: {__version__}", err=True)
413
+ click.echo("Checking PyPI...", err=True)
414
+ latest = _fetch_latest_version()
415
+ if latest is None:
416
+ click.echo("Could not reach PyPI. Check your network and try again.", err=True)
417
+ sys.exit(1)
418
+ click.echo(f"Latest on PyPI: {latest}", err=True)
419
+
420
+ if not is_newer(latest, __version__):
421
+ click.echo("You are on the latest version.", err=True)
422
+ return
423
+
424
+ upgrade_cmd = detect_upgrade_command()
425
+ click.echo(f"Upgrade command: {upgrade_cmd}", err=True)
426
+ if check_only:
427
+ return
428
+
429
+ if click.confirm("Upgrade now?", default=True, err=True):
430
+ sys.exit(_run_upgrade(upgrade_cmd))
431
+
432
+
369
433
  # Register commands when module loads
370
434
  register_commands()
371
435
 
@@ -29,6 +29,7 @@ class DxsContext:
29
29
  self.save_path: str | None = None
30
30
  self.force_overwrite: bool = False
31
31
  self.explicit_output_format: bool = False
32
+ self.no_update_check: bool = False
32
33
 
33
34
  def output(self, data: Any, include_metadata: bool = True) -> None:
34
35
  """Output data in the configured format.
@@ -0,0 +1,221 @@
1
+ """PyPI update check.
2
+
3
+ On each invocation, reads a cached "latest version on PyPI" entry. If a newer
4
+ version is available and we're attached to an interactive terminal, prints a
5
+ banner and offers to upgrade in place. The PyPI fetch happens in a background
6
+ daemon thread so the user's command is never delayed.
7
+
8
+ Environment / flag controls:
9
+ - ``DXS_NO_UPDATE_CHECK=1`` disables both the check and the banner.
10
+ - ``--no-update-check`` (CLI flag) does the same per-invocation.
11
+ - ``DXS_UPDATE_CHECK_DEBUG=1`` surfaces sender errors (development only).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import os
18
+ import re
19
+ import subprocess
20
+ import sys
21
+ import threading
22
+ import time
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ import click
27
+
28
+ from dxs import __version__
29
+ from dxs.utils.paths import get_datex_home
30
+
31
+ PYPI_URL = "https://pypi.org/pypi/datex-studio-cli/json"
32
+ PACKAGE_NAME = "datex-studio-cli"
33
+ CACHE_TTL_SECONDS = 24 * 60 * 60
34
+ PROMPT_DEDUPE_SECONDS = 60 * 60 # at most one prompt per hour per machine
35
+ HTTP_TIMEOUT_SECONDS = 2.0
36
+
37
+ _CI_ENV_VARS = ("CI", "CONTINUOUS_INTEGRATION", "GITHUB_ACTIONS", "BUILDKITE", "TF_BUILD")
38
+
39
+
40
+ def _cache_path() -> Path:
41
+ return get_datex_home() / "update_check.json"
42
+
43
+
44
+ def _debug(msg: str) -> None:
45
+ if os.environ.get("DXS_UPDATE_CHECK_DEBUG", "").lower() in ("1", "true", "yes"):
46
+ click.echo(f"[update-check] {msg}", err=True)
47
+
48
+
49
+ def is_disabled() -> bool:
50
+ """Return True if the update check has been disabled via env var."""
51
+ return os.environ.get("DXS_NO_UPDATE_CHECK", "").lower() in ("1", "true", "yes")
52
+
53
+
54
+ def _is_ci() -> bool:
55
+ return any(os.environ.get(v) for v in _CI_ENV_VARS)
56
+
57
+
58
+ def _read_cache() -> dict[str, Any] | None:
59
+ path = _cache_path()
60
+ if not path.exists():
61
+ return None
62
+ try:
63
+ data = json.loads(path.read_text())
64
+ if isinstance(data, dict):
65
+ return data
66
+ except (json.JSONDecodeError, OSError) as e:
67
+ _debug(f"cache read failed: {e}")
68
+ return None
69
+
70
+
71
+ def _write_cache(data: dict[str, Any]) -> None:
72
+ try:
73
+ _cache_path().write_text(json.dumps(data))
74
+ except OSError as e:
75
+ _debug(f"cache write failed: {e}")
76
+
77
+
78
+ def _fetch_latest_version() -> str | None:
79
+ """Fetch the latest version from PyPI. Returns None on any failure."""
80
+ try:
81
+ import httpx
82
+
83
+ response = httpx.get(PYPI_URL, timeout=HTTP_TIMEOUT_SECONDS)
84
+ response.raise_for_status()
85
+ latest = response.json()["info"]["version"]
86
+ return str(latest) if latest else None
87
+ except Exception as e: # noqa: BLE001 - never let telemetry break the CLI
88
+ _debug(f"fetch failed: {e}")
89
+ return None
90
+
91
+
92
+ def _refresh_cache_worker() -> None:
93
+ latest = _fetch_latest_version()
94
+ if not latest:
95
+ return
96
+ cache = _read_cache() or {}
97
+ cache["latest"] = latest
98
+ cache["checked_at"] = time.time()
99
+ _write_cache(cache)
100
+
101
+
102
+ def _maybe_refresh_async(cache: dict[str, Any] | None) -> None:
103
+ """Spawn a daemon thread to refresh the cache if it's missing or stale."""
104
+ needs_refresh = cache is None or (time.time() - float(cache.get("checked_at", 0))) > CACHE_TTL_SECONDS
105
+ if not needs_refresh:
106
+ return
107
+ thread = threading.Thread(target=_refresh_cache_worker, daemon=True, name="dxs-update-check")
108
+ thread.start()
109
+
110
+
111
+ _VERSION_RE = re.compile(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?")
112
+
113
+
114
+ def _parse_version(v: str) -> tuple[int, int, int]:
115
+ """Parse an X.Y.Z version into a tuple. Pre-release suffixes are ignored.
116
+
117
+ Returns (0, 0, 0) on unparseable input — treated as oldest possible.
118
+ """
119
+ m = _VERSION_RE.match(v.strip())
120
+ if not m:
121
+ return (0, 0, 0)
122
+ return (int(m.group(1) or 0), int(m.group(2) or 0), int(m.group(3) or 0))
123
+
124
+
125
+ def is_newer(candidate: str, current: str) -> bool:
126
+ """Return True if `candidate` is a newer version than `current`."""
127
+ return _parse_version(candidate) > _parse_version(current)
128
+
129
+
130
+ def detect_upgrade_command() -> str:
131
+ """Best-effort detection of how dxs was installed.
132
+
133
+ Returns the shell command the user should run to upgrade.
134
+ """
135
+ exe = sys.executable
136
+ # uv tool installs land under ~/.local/share/uv/tools/<pkg>/...
137
+ if "/uv/tools/" in exe or "\\uv\\tools\\" in exe:
138
+ return f"uv tool upgrade {PACKAGE_NAME}"
139
+ # pipx installs land under ~/.local/pipx/venvs/<pkg>/...
140
+ if "/pipx/" in exe or "\\pipx\\" in exe:
141
+ return f"pipx upgrade {PACKAGE_NAME}"
142
+ # Fallback: pip in whatever interpreter the user is on
143
+ return f"{exe} -m pip install --upgrade {PACKAGE_NAME}"
144
+
145
+
146
+ def _run_upgrade(command: str) -> int:
147
+ """Run the upgrade command and stream its output to the user.
148
+
149
+ Returns the subprocess exit code.
150
+ """
151
+ click.echo(f"Running: {command}", err=True)
152
+ try:
153
+ completed = subprocess.run(command, shell=True, check=False)
154
+ return completed.returncode
155
+ except Exception as e: # noqa: BLE001
156
+ click.echo(f"Upgrade failed to start: {e}", err=True)
157
+ return 1
158
+
159
+
160
+ def _interactive() -> bool:
161
+ """True only when stdin, stderr, and stdout are all attached to a TTY.
162
+
163
+ We require stdout TTY too, because piping/redirecting stdout is the strongest
164
+ signal that the user is consuming structured output programmatically and
165
+ won't appreciate a banner mid-stream.
166
+ """
167
+ return all(s.isatty() for s in (sys.stdin, sys.stderr, sys.stdout))
168
+
169
+
170
+ def maybe_show_update_notice(skip: bool = False) -> None:
171
+ """Check the cache and, if a newer version is available, prompt to upgrade.
172
+
173
+ Always returns quickly — the network fetch happens in a background thread.
174
+
175
+ Args:
176
+ skip: If True, skip both the check and any cache refresh. Used to honor
177
+ ``--no-update-check``.
178
+ """
179
+ if skip or is_disabled() or _is_ci():
180
+ return
181
+ if not _interactive():
182
+ return
183
+
184
+ cache = _read_cache()
185
+ _maybe_refresh_async(cache)
186
+
187
+ if not cache:
188
+ return
189
+
190
+ latest = cache.get("latest")
191
+ if not isinstance(latest, str) or not is_newer(latest, __version__):
192
+ return
193
+
194
+ last_prompted = float(cache.get("last_prompted_at", 0))
195
+ if time.time() - last_prompted < PROMPT_DEDUPE_SECONDS:
196
+ return
197
+
198
+ upgrade_cmd = detect_upgrade_command()
199
+ click.echo("", err=True)
200
+ click.echo(
201
+ f" dxs {latest} is available (you have {__version__})",
202
+ err=True,
203
+ )
204
+ click.echo(f" Upgrade command: {upgrade_cmd}", err=True)
205
+
206
+ cache["last_prompted_at"] = time.time()
207
+ _write_cache(cache)
208
+
209
+ try:
210
+ if click.confirm(" Upgrade now?", default=False, err=True):
211
+ exit_code = _run_upgrade(upgrade_cmd)
212
+ if exit_code == 0:
213
+ click.echo(
214
+ " Upgrade complete. Re-run your command to use the new version.",
215
+ err=True,
216
+ )
217
+ else:
218
+ click.echo(f" Upgrade exited with code {exit_code}.", err=True)
219
+ except (click.Abort, EOFError):
220
+ click.echo("", err=True)
221
+ return