datex-studio-cli 0.4.19__tar.gz → 0.4.20__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.
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/PKG-INFO +1 -1
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/pyproject.toml +1 -1
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/__init__.py +1 -1
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/auth.py +228 -39
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/explore.py +1 -1
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/init.py +21 -16
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/source.py +10 -3
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/client.py +23 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/datex_tenant.py +209 -6
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/msal_client.py +127 -26
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/devops/client.py +2 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/client.py +5 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/config.py +16 -14
- datex_studio_cli-0.4.20/src/dxs/utils/versioning.py +22 -0
- datex_studio_cli-0.4.19/src/dxs/utils/versioning.py +0 -22
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/.gitignore +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/CONTEXT.md +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/README.md +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/.env.example +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/Dockerfile +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/README.md +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/apply-local-cache.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/collector-config.yaml +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/docker-compose.yml +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/local-test-settings.json +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/otel/remote-settings.json +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/report-creator.skill +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/scripts/analyze_corpus.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/scripts/generate_rdlx_schema.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/__main__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/cli.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/api.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/branch.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/branch_commit_guard.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/cascade.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/config.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/configuration.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/crm.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/crm_delivery.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/crm_time.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/datasource.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/devops.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/document.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/endpoint.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/env.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/function.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/marketplace.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/_common.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/api.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/create.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/data.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/pack.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/ng/preview.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/odata.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/organization.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/proxy.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/reference.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/release_notes.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/repo.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/add_cmds.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/api.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/batch.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/create.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/data.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/dataset.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/edit.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/folder.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/preview.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/report/schema_cmds.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/reset.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/schema.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/servicepack.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/studio.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/telemetry.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/user.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/commands/validation_report.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/context.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/agent_browser.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/app_config.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/endpoints.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/metadata_models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/api/reference_graph.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/decorators.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/tenants.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/auth/token_cache.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/cache.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/flow_generator.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/flow_models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/flow_shape.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/generator.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/parsers.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/resolver.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/type_def_parser.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/datasource/validator.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/designer/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/designer/flow_models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/designer/models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/designer/parsers.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/devops/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/devops/helpers.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/devops/models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/html.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/metadata_cache.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/pagination.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/resolvers.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/dynamics/time_entry.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/footprint/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/footprint/client.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/footprint/edmx_parser.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/footprint/metadata.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/function/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/function/generator.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/function/models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/function/validator.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/graph.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/onboarding.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/csv_fmt.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/formatter.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/json_fmt.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/redaction.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/output/yaml_fmt.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/release_notes.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/responses.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/config.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/identity.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/recorder.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/scrubber.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/core/telemetry/sender.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/models/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/folder.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/manifest.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/mocks.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/preview.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/scaffold.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/ng/serve_lock.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/py.typed +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/datasource_binding.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/engine.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/field_parser.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/folder.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/manifest.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/models.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/preview.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/preview_arjs.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/schema.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/svg_renderer.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/templates/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/validate_arjs.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/validator.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/report/wrapper.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/click_options.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/clipboard.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/errors.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/filtering.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/image.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/paths.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/resolvers.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/responses.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/restricted.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/sorting.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/utils/update_check.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/__init__.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/app.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/design.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/proxy_app.py +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/404/index.html +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/404.html +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/design/capture/index.html +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/design/capture/index.txt +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/design/index.html +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/design/index.txt +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/index.html +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/index.txt +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/render-cli.js +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/src/dxs/web/static/report-preview.html +0 -0
- {datex_studio_cli-0.4.19 → datex_studio_cli-0.4.20}/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.
|
|
3
|
+
Version: 0.4.20
|
|
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
|
|
@@ -15,6 +15,7 @@ from dxs.core.auth.datex_tenant import (
|
|
|
15
15
|
is_datex_identity,
|
|
16
16
|
)
|
|
17
17
|
from dxs.core.auth.decorators import get_access_token
|
|
18
|
+
from dxs.core.auth.msal_client import ConsentOutcome
|
|
18
19
|
from dxs.core.auth.tenants import (
|
|
19
20
|
identity_needs_tenant_authority,
|
|
20
21
|
is_foreign_tenant,
|
|
@@ -51,6 +52,117 @@ def _reject_tenant_override(ctx: DxsContext, command: str) -> None:
|
|
|
51
52
|
raise SystemExit(1)
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
def consent_failure_items(outcomes: list[ConsentOutcome] | None) -> list[dict[str, Any]]:
|
|
56
|
+
"""Map failed consent rounds to payload entries (empty when all succeeded).
|
|
57
|
+
|
|
58
|
+
AADSTS650057 means the resource is not listed on the CLI app registration —
|
|
59
|
+
no amount of retrying or user consent can fix that, so it carries an
|
|
60
|
+
explicit escalation suggestion (issue #135).
|
|
61
|
+
"""
|
|
62
|
+
items: list[dict[str, Any]] = []
|
|
63
|
+
for outcome in outcomes or []:
|
|
64
|
+
if outcome.ok:
|
|
65
|
+
continue
|
|
66
|
+
entry: dict[str, Any] = {
|
|
67
|
+
"resource": outcome.resource,
|
|
68
|
+
"error": outcome.error,
|
|
69
|
+
"error_description": outcome.error_description,
|
|
70
|
+
}
|
|
71
|
+
if outcome.aadsts_code == "650057":
|
|
72
|
+
entry["suggestion"] = (
|
|
73
|
+
"This resource is not listed on the CLI app registration, so consent "
|
|
74
|
+
"cannot be requested for it — ask an Entra admin to grant tenant-wide "
|
|
75
|
+
"consent for the resource to the Datex Studio CLI app."
|
|
76
|
+
)
|
|
77
|
+
items.append(entry)
|
|
78
|
+
return items
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _run_targeted_consent(ctx: DxsContext, resource_key: str) -> None:
|
|
82
|
+
"""(Re)consent one Datex-internal resource without a full re-login.
|
|
83
|
+
|
|
84
|
+
Backs ``dxs auth login --consent devops|crm`` — the recovery path for a
|
|
85
|
+
user whose login-time consent round was skipped or silently failed
|
|
86
|
+
(issue #136). Uses the cached Datex identity; the consent round requests
|
|
87
|
+
the *named* scope so dynamic user consent applies (issue #135).
|
|
88
|
+
"""
|
|
89
|
+
from dxs.core.auth.datex_tenant import consent_datex_resource
|
|
90
|
+
from dxs.utils.config import get_settings
|
|
91
|
+
|
|
92
|
+
settings = get_settings()
|
|
93
|
+
if resource_key == "devops":
|
|
94
|
+
resource = "Azure DevOps"
|
|
95
|
+
consent_scopes = settings.azure_devops_scopes
|
|
96
|
+
acquire_scopes = settings.azure_devops_scopes
|
|
97
|
+
error_code = "DXS-DEVOPS-AUTH-001"
|
|
98
|
+
else:
|
|
99
|
+
if not settings.dynamics_crm_url:
|
|
100
|
+
ctx.output_error(
|
|
101
|
+
ValidationError(
|
|
102
|
+
message=(
|
|
103
|
+
"dynamics_crm_url is not configured; there is no Dynamics CRM "
|
|
104
|
+
"resource to consent to."
|
|
105
|
+
),
|
|
106
|
+
code="DXS-DYNAMICS-CONFIG-001",
|
|
107
|
+
suggestions=[
|
|
108
|
+
"Run: dxs config set dynamics_crm_url https://yourorg.crm.dynamics.com",
|
|
109
|
+
"Then re-run: dxs auth login --consent crm",
|
|
110
|
+
],
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
raise SystemExit(1)
|
|
114
|
+
resource = "Dynamics CRM"
|
|
115
|
+
consent_scopes = settings.dynamics_crm_consent_scopes
|
|
116
|
+
acquire_scopes = settings.dynamics_crm_scopes
|
|
117
|
+
error_code = "DXS-DYNAMICS-AUTH-001"
|
|
118
|
+
|
|
119
|
+
def on_device_code(device_code: dict[str, Any]) -> None:
|
|
120
|
+
ctx.output(
|
|
121
|
+
single(
|
|
122
|
+
item={
|
|
123
|
+
"status": "awaiting_authentication",
|
|
124
|
+
"action_required": {
|
|
125
|
+
"type": "device_code",
|
|
126
|
+
"instructions": (
|
|
127
|
+
f"Sign in with your Datex account to consent to {resource}"
|
|
128
|
+
),
|
|
129
|
+
"url": device_code["verification_uri"],
|
|
130
|
+
"code": device_code["user_code"],
|
|
131
|
+
"expires_in_seconds": device_code["expires_in"],
|
|
132
|
+
},
|
|
133
|
+
"message": device_code.get("message", ""),
|
|
134
|
+
},
|
|
135
|
+
semantic_key="authentication",
|
|
136
|
+
),
|
|
137
|
+
include_metadata=False,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
outcome = consent_datex_resource(
|
|
142
|
+
consent_scopes=consent_scopes,
|
|
143
|
+
acquire_scopes=acquire_scopes,
|
|
144
|
+
resource=resource,
|
|
145
|
+
error_code=error_code,
|
|
146
|
+
on_device_code=on_device_code,
|
|
147
|
+
)
|
|
148
|
+
except DxsError as e:
|
|
149
|
+
ctx.output_error(e)
|
|
150
|
+
raise SystemExit(1) from None
|
|
151
|
+
|
|
152
|
+
ctx.output(
|
|
153
|
+
single(
|
|
154
|
+
item={
|
|
155
|
+
"status": "consented",
|
|
156
|
+
"message": f"Consent for {resource} granted and verified.",
|
|
157
|
+
"resource": resource,
|
|
158
|
+
"account": outcome["identity"],
|
|
159
|
+
"has_token": True,
|
|
160
|
+
},
|
|
161
|
+
semantic_key="authentication",
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
54
166
|
def run_standard_device_login(
|
|
55
167
|
ctx: DxsContext,
|
|
56
168
|
on_device_code: Callable[[dict[str, Any]], None],
|
|
@@ -64,6 +176,7 @@ def run_standard_device_login(
|
|
|
64
176
|
organization_id: int | None = None,
|
|
65
177
|
organization_name: str | None = None,
|
|
66
178
|
api_base_url: str | None = None,
|
|
179
|
+
consent_outcomes: list[ConsentOutcome] | None = None,
|
|
67
180
|
) -> CachedIdentity:
|
|
68
181
|
"""Run the multi-resource device-code login and save the identity.
|
|
69
182
|
|
|
@@ -88,6 +201,11 @@ def run_standard_device_login(
|
|
|
88
201
|
that target a non-default environment (e.g. ``dxs init --target qa``) must
|
|
89
202
|
pass it so the identity is stamped from the same environment its profile is
|
|
90
203
|
detected against, not the stale lru_cached default.
|
|
204
|
+
|
|
205
|
+
``consent_outcomes``, when given, receives one entry per additional consent
|
|
206
|
+
round so callers can include failures in their success payload. Failed
|
|
207
|
+
rounds are always logged as warnings here regardless — an additional
|
|
208
|
+
resource failing never aborts the login, so this is the only signal.
|
|
91
209
|
"""
|
|
92
210
|
from dxs.utils.config import get_settings
|
|
93
211
|
|
|
@@ -117,25 +235,36 @@ def run_standard_device_login(
|
|
|
117
235
|
tenant is known, so DevOps/CRM consent is requested only for Datex
|
|
118
236
|
staff — a customer must never be prompted for Datex-internal
|
|
119
237
|
resources by name.
|
|
238
|
+
|
|
239
|
+
The CRM round requests the *named* scope, not ``.default``:
|
|
240
|
+
``.default`` is static consent and fails with AADSTS650057 for any
|
|
241
|
+
first-time user because Dataverse is not listed on the CLI app
|
|
242
|
+
registration (issue #135). Token acquisition keeps ``.default``.
|
|
120
243
|
"""
|
|
121
244
|
internal = (
|
|
122
245
|
[
|
|
123
246
|
settings.azure_devops_scopes,
|
|
124
|
-
settings.
|
|
125
|
-
settings.dynamics_crm_admin_scopes,
|
|
126
|
-
settings.data_export_service_scopes,
|
|
247
|
+
settings.dynamics_crm_consent_scopes,
|
|
127
248
|
]
|
|
128
249
|
if claims_are_datex(token_response.get("id_token_claims") or {})
|
|
129
250
|
else []
|
|
130
251
|
)
|
|
131
252
|
return [scopes for scopes in [*internal, [settings.footprint_scope]] if scopes]
|
|
132
253
|
|
|
254
|
+
outcomes: list[ConsentOutcome] = consent_outcomes if consent_outcomes is not None else []
|
|
133
255
|
result = client.authenticate_device_code_multi_resource(
|
|
134
256
|
primary_scopes=settings.azure_scopes,
|
|
135
257
|
additional_scope_sets=additional_scope_sets,
|
|
136
258
|
on_device_code=on_device_code,
|
|
137
259
|
on_additional_consent_needed=consent_cb,
|
|
260
|
+
consent_outcomes=outcomes,
|
|
138
261
|
)
|
|
262
|
+
for entry in consent_failure_items(outcomes):
|
|
263
|
+
detail = entry["error_description"] or entry["error"] or "unknown error"
|
|
264
|
+
warning = f"WARNING: consent for {entry['resource']} was not obtained: {detail}"
|
|
265
|
+
if "suggestion" in entry:
|
|
266
|
+
warning += f" {entry['suggestion']}"
|
|
267
|
+
ctx.log(warning)
|
|
139
268
|
|
|
140
269
|
saved_identity = cache.save_identity(
|
|
141
270
|
result,
|
|
@@ -173,13 +302,16 @@ def _auth_success_item(
|
|
|
173
302
|
saved_identity: CachedIdentity,
|
|
174
303
|
*,
|
|
175
304
|
message: str = "Successfully authenticated",
|
|
305
|
+
consent_outcomes: list[ConsentOutcome] | None = None,
|
|
176
306
|
) -> dict[str, Any]:
|
|
177
307
|
"""Build the standard ``authenticated`` response payload shared by the login paths.
|
|
178
308
|
|
|
179
309
|
The external-tenant descriptor is derived from the saved identity — never from
|
|
180
310
|
which CLI flags were passed — so the payload cannot disagree with the cache
|
|
181
311
|
(a --tenant-id login with the home tenant is persisted as internal). Adds the
|
|
182
|
-
``footprint_daemon_auth`` marker when the identity carries daemon creds
|
|
312
|
+
``footprint_daemon_auth`` marker when the identity carries daemon creds, and
|
|
313
|
+
``consent_failures`` when an additional consent round failed — the primary
|
|
314
|
+
login succeeded, so this is where a missing consent becomes visible.
|
|
183
315
|
"""
|
|
184
316
|
item: dict[str, Any] = {
|
|
185
317
|
"status": "authenticated",
|
|
@@ -194,6 +326,9 @@ def _auth_success_item(
|
|
|
194
326
|
item["expires_at"] = saved_identity.expires_at.isoformat()
|
|
195
327
|
if saved_identity.footprint_client_id:
|
|
196
328
|
item["footprint_daemon_auth"] = "configured"
|
|
329
|
+
failures = consent_failure_items(consent_outcomes)
|
|
330
|
+
if failures:
|
|
331
|
+
item["consent_failures"] = failures
|
|
197
332
|
return item
|
|
198
333
|
|
|
199
334
|
|
|
@@ -279,6 +414,16 @@ def auth() -> None:
|
|
|
279
414
|
default=None,
|
|
280
415
|
help="Entra domain (e.g. csw-wi.com) — resolves the tenant ID for you",
|
|
281
416
|
)
|
|
417
|
+
@click.option(
|
|
418
|
+
"--consent",
|
|
419
|
+
"consent_resource",
|
|
420
|
+
type=click.Choice(["devops", "crm"]),
|
|
421
|
+
default=None,
|
|
422
|
+
help=(
|
|
423
|
+
"Only (re)run the consent round for one Datex-internal resource, using the "
|
|
424
|
+
"cached Datex identity — no full re-login"
|
|
425
|
+
),
|
|
426
|
+
)
|
|
282
427
|
@pass_context
|
|
283
428
|
def login(
|
|
284
429
|
ctx: DxsContext,
|
|
@@ -287,6 +432,7 @@ def login(
|
|
|
287
432
|
client_secret: str | None,
|
|
288
433
|
tenant_id: str | None,
|
|
289
434
|
domain: str | None,
|
|
435
|
+
consent_resource: str | None,
|
|
290
436
|
) -> None:
|
|
291
437
|
"""Authenticate with Azure Entra using device code flow.
|
|
292
438
|
|
|
@@ -307,6 +453,9 @@ def login(
|
|
|
307
453
|
credentials) authentication for the Footprint API, needed for cross-tenant
|
|
308
454
|
access where OBO flow is not available.
|
|
309
455
|
|
|
456
|
+
Use --consent devops|crm (Datex staff) to grant a missing consent for one
|
|
457
|
+
internal resource without re-running the whole login.
|
|
458
|
+
|
|
310
459
|
\b
|
|
311
460
|
Examples:
|
|
312
461
|
dxs auth login # Login to default tenant
|
|
@@ -314,6 +463,7 @@ def login(
|
|
|
314
463
|
dxs auth login --domain csw-wi.com # Bootstrap: no identity required
|
|
315
464
|
dxs auth login --tenant-id <guid> # Same, when you know the GUID
|
|
316
465
|
dxs auth login CAG --client-id <id> --client-secret <secret>
|
|
466
|
+
dxs auth login --consent devops # Grant the missing DevOps consent only
|
|
317
467
|
"""
|
|
318
468
|
_reject_tenant_override(ctx, "login")
|
|
319
469
|
|
|
@@ -331,6 +481,23 @@ def login(
|
|
|
331
481
|
)
|
|
332
482
|
raise SystemExit(1)
|
|
333
483
|
|
|
484
|
+
if consent_resource:
|
|
485
|
+
if org or tenant_id or domain or client_id or client_secret:
|
|
486
|
+
ctx.output_error(
|
|
487
|
+
ValidationError(
|
|
488
|
+
message=(
|
|
489
|
+
"--consent runs a single consent round for the cached Datex "
|
|
490
|
+
"identity; it cannot be combined with an organization, tenant, "
|
|
491
|
+
"or daemon credentials"
|
|
492
|
+
),
|
|
493
|
+
code="DXS-AUTH-024",
|
|
494
|
+
suggestions=["Run 'dxs auth login --consent devops|crm' by itself"],
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
raise SystemExit(1)
|
|
498
|
+
_run_targeted_consent(ctx, consent_resource)
|
|
499
|
+
return
|
|
500
|
+
|
|
334
501
|
if org and (tenant_id or domain):
|
|
335
502
|
ctx.output_error(
|
|
336
503
|
ValidationError(
|
|
@@ -380,6 +547,7 @@ def login(
|
|
|
380
547
|
include_metadata=False,
|
|
381
548
|
)
|
|
382
549
|
|
|
550
|
+
external_outcomes: list[ConsentOutcome] = []
|
|
383
551
|
try:
|
|
384
552
|
saved_identity = run_standard_device_login(
|
|
385
553
|
ctx,
|
|
@@ -388,12 +556,15 @@ def login(
|
|
|
388
556
|
client_secret=client_secret,
|
|
389
557
|
tenant_id=tenant_id,
|
|
390
558
|
domain=domain,
|
|
559
|
+
consent_outcomes=external_outcomes,
|
|
391
560
|
)
|
|
392
561
|
except AuthenticationError as e:
|
|
393
562
|
ctx.output_error(e)
|
|
394
563
|
raise SystemExit(1) from None
|
|
395
564
|
|
|
396
|
-
external_success_item = _auth_success_item(
|
|
565
|
+
external_success_item = _auth_success_item(
|
|
566
|
+
saved_identity, consent_outcomes=external_outcomes
|
|
567
|
+
)
|
|
397
568
|
ctx.output(single(item=external_success_item, semantic_key="authentication"))
|
|
398
569
|
return
|
|
399
570
|
|
|
@@ -438,6 +609,7 @@ def login(
|
|
|
438
609
|
include_metadata=False,
|
|
439
610
|
)
|
|
440
611
|
|
|
612
|
+
login_outcomes: list[ConsentOutcome] = []
|
|
441
613
|
try:
|
|
442
614
|
saved_identity = run_standard_device_login(
|
|
443
615
|
ctx,
|
|
@@ -445,12 +617,13 @@ def login(
|
|
|
445
617
|
client_id=client_id,
|
|
446
618
|
client_secret=client_secret,
|
|
447
619
|
on_additional_consent_needed=on_additional_consent_needed,
|
|
620
|
+
consent_outcomes=login_outcomes,
|
|
448
621
|
)
|
|
449
622
|
except AuthenticationError as e:
|
|
450
623
|
ctx.output_error(e)
|
|
451
624
|
raise SystemExit(1) from None
|
|
452
625
|
|
|
453
|
-
success_item = _auth_success_item(saved_identity)
|
|
626
|
+
success_item = _auth_success_item(saved_identity, consent_outcomes=login_outcomes)
|
|
454
627
|
ctx.output(single(item=success_item, semantic_key="authentication"))
|
|
455
628
|
|
|
456
629
|
|
|
@@ -1415,7 +1588,9 @@ def _switch_by_organization(
|
|
|
1415
1588
|
# Home-tenant org (Datex, or an org with no tenant of its own): the
|
|
1416
1589
|
# current identity's tokens already cover it, but the org binding on
|
|
1417
1590
|
# the cached identity must still be re-pointed at the requested org.
|
|
1418
|
-
_rebind_home_tenant_identity(
|
|
1591
|
+
_rebind_home_tenant_identity(
|
|
1592
|
+
ctx, cache, org_info, client_id=client_id, client_secret=client_secret
|
|
1593
|
+
)
|
|
1419
1594
|
return
|
|
1420
1595
|
|
|
1421
1596
|
# Step 4: Reject a misregistered home-tenant org BEFORE any identity lookup —
|
|
@@ -1551,6 +1726,8 @@ def _rebind_home_tenant_identity(
|
|
|
1551
1726
|
ctx: DxsContext,
|
|
1552
1727
|
cache: MultiIdentityTokenCache,
|
|
1553
1728
|
org_info: Any,
|
|
1729
|
+
client_id: str | None = None,
|
|
1730
|
+
client_secret: str | None = None,
|
|
1554
1731
|
) -> None:
|
|
1555
1732
|
"""Re-point the active home-tenant identity at ``org_info`` (issue #112).
|
|
1556
1733
|
|
|
@@ -1561,6 +1738,10 @@ def _rebind_home_tenant_identity(
|
|
|
1561
1738
|
made the silent no-op hard to spot — while writes fail with a misleading
|
|
1562
1739
|
``DXS-API-400``. Only the two org fields move; tokens and the active
|
|
1563
1740
|
identity selection stay untouched (the issue #83 contract).
|
|
1741
|
+
|
|
1742
|
+
Supplied daemon credentials are persisted on the active identity on both
|
|
1743
|
+
outcomes (issue #133) — the same contract the other org-switch branches
|
|
1744
|
+
honor. Without credentials, the already-bound outcome writes nothing.
|
|
1564
1745
|
"""
|
|
1565
1746
|
active = cache.get_active_identity()
|
|
1566
1747
|
if active is None or identity_needs_tenant_authority(active):
|
|
@@ -1587,27 +1768,29 @@ def _rebind_home_tenant_identity(
|
|
|
1587
1768
|
)
|
|
1588
1769
|
raise SystemExit(1)
|
|
1589
1770
|
|
|
1771
|
+
storage_key = cache.resolve_stored_key(active) or active.identity_key
|
|
1772
|
+
|
|
1590
1773
|
already_bound = active.organization_id == org_info.id and (
|
|
1591
1774
|
(active.organization_name or "").lower() == org_info.name.lower()
|
|
1592
1775
|
)
|
|
1593
1776
|
if already_bound:
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1777
|
+
identity = active
|
|
1778
|
+
if client_id and client_secret:
|
|
1779
|
+
with_creds = cache.update_identity_daemon_creds(storage_key, client_id, client_secret)
|
|
1780
|
+
if with_creds is not None:
|
|
1781
|
+
identity = with_creds
|
|
1782
|
+
item: dict[str, Any] = {
|
|
1783
|
+
"status": "no_action_required",
|
|
1784
|
+
"message": (f"Active identity is already bound to organization '{org_info.name}'"),
|
|
1785
|
+
"active_identity": active.account_username,
|
|
1786
|
+
"organization": org_info.name,
|
|
1787
|
+
"organization_id": org_info.id,
|
|
1788
|
+
}
|
|
1789
|
+
if identity.footprint_client_id:
|
|
1790
|
+
item["footprint_daemon_auth"] = "configured"
|
|
1791
|
+
ctx.output(single(item=item, semantic_key="authentication"))
|
|
1608
1792
|
return
|
|
1609
1793
|
|
|
1610
|
-
storage_key = cache.resolve_stored_key(active) or active.identity_key
|
|
1611
1794
|
updated = cache.update_identity_org_info(storage_key, org_info.id, org_info.name)
|
|
1612
1795
|
if updated is None:
|
|
1613
1796
|
ctx.output_error(
|
|
@@ -1618,23 +1801,26 @@ def _rebind_home_tenant_identity(
|
|
|
1618
1801
|
)
|
|
1619
1802
|
raise SystemExit(1)
|
|
1620
1803
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1804
|
+
if client_id and client_secret:
|
|
1805
|
+
with_creds = cache.update_identity_daemon_creds(storage_key, client_id, client_secret)
|
|
1806
|
+
if with_creds is not None:
|
|
1807
|
+
updated = with_creds
|
|
1808
|
+
|
|
1809
|
+
item = {
|
|
1810
|
+
"status": "organization_rebound",
|
|
1811
|
+
"message": (
|
|
1812
|
+
f"Re-bound the active identity to organization '{org_info.name}' "
|
|
1813
|
+
"(home tenant; no login required)"
|
|
1814
|
+
),
|
|
1815
|
+
"active_identity": active.account_username,
|
|
1816
|
+
"organization": org_info.name,
|
|
1817
|
+
"organization_id": org_info.id,
|
|
1818
|
+
"previous_organization": active.organization_name,
|
|
1819
|
+
"previous_organization_id": active.organization_id,
|
|
1820
|
+
}
|
|
1821
|
+
if updated.footprint_client_id:
|
|
1822
|
+
item["footprint_daemon_auth"] = "configured"
|
|
1823
|
+
ctx.output(single(item=item, semantic_key="authentication"))
|
|
1638
1824
|
|
|
1639
1825
|
|
|
1640
1826
|
def _handled_home_tenant_external_org(ctx: DxsContext, org_info: Any) -> bool:
|
|
@@ -1726,6 +1912,7 @@ def _authenticate_external_tenant(
|
|
|
1726
1912
|
include_metadata=False,
|
|
1727
1913
|
)
|
|
1728
1914
|
|
|
1915
|
+
org_outcomes: list[ConsentOutcome] = []
|
|
1729
1916
|
try:
|
|
1730
1917
|
ctx.log(f"Initiating device code authentication for {org_info.name}...")
|
|
1731
1918
|
saved_identity = run_standard_device_login(
|
|
@@ -1739,11 +1926,13 @@ def _authenticate_external_tenant(
|
|
|
1739
1926
|
external_entra_id=org_info.external_entra_id,
|
|
1740
1927
|
organization_id=org_info.id,
|
|
1741
1928
|
organization_name=org_info.name,
|
|
1929
|
+
consent_outcomes=org_outcomes,
|
|
1742
1930
|
)
|
|
1743
1931
|
|
|
1744
1932
|
success_item = _auth_success_item(
|
|
1745
1933
|
saved_identity,
|
|
1746
1934
|
message=f"Successfully authenticated with {org_info.name}",
|
|
1935
|
+
consent_outcomes=org_outcomes,
|
|
1747
1936
|
)
|
|
1748
1937
|
ctx.output(single(item=success_item, semantic_key="authentication"))
|
|
1749
1938
|
|
|
@@ -337,7 +337,7 @@ def _build_config_index(client: ApiClient, branch_id: int, ctx: DxsContext) -> C
|
|
|
337
337
|
)
|
|
338
338
|
except ApiError as e:
|
|
339
339
|
# Log JSON parse errors as warnings (e.g., endpoints type)
|
|
340
|
-
if e.code
|
|
340
|
+
if e.code in ("DXS-API-JSON-001", "DXS-API-ROUTE-404"):
|
|
341
341
|
ctx.log(f"Warning: {ctype} returned non-JSON response")
|
|
342
342
|
# Other API errors are expected (type may not exist)
|
|
343
343
|
except Exception:
|
|
@@ -5,11 +5,12 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
7
|
|
|
8
|
-
from dxs.commands.auth import run_standard_device_login
|
|
8
|
+
from dxs.commands.auth import consent_failure_items, run_standard_device_login
|
|
9
9
|
from dxs.context import DxsContext, pass_context
|
|
10
10
|
from dxs.core.api.client import ApiClient
|
|
11
11
|
from dxs.core.auth import MSALClient, MultiIdentityTokenCache
|
|
12
12
|
from dxs.core.auth.decorators import get_access_token
|
|
13
|
+
from dxs.core.auth.msal_client import ConsentOutcome
|
|
13
14
|
from dxs.core.auth.tenants import is_foreign_tenant
|
|
14
15
|
from dxs.core.onboarding import (
|
|
15
16
|
AuthAction,
|
|
@@ -180,6 +181,7 @@ def init(
|
|
|
180
181
|
# actually persisted.
|
|
181
182
|
external_mode = is_foreign_tenant(tenant_id)
|
|
182
183
|
|
|
184
|
+
consent_outcomes: list[ConsentOutcome] = []
|
|
183
185
|
if action is AuthAction.INTERACTIVE:
|
|
184
186
|
|
|
185
187
|
def on_device_code(dc: dict[str, Any]) -> None:
|
|
@@ -204,6 +206,7 @@ def init(
|
|
|
204
206
|
tenant_id=tenant_id,
|
|
205
207
|
domain=domain,
|
|
206
208
|
api_base_url=effective_api_base_url,
|
|
209
|
+
consent_outcomes=consent_outcomes,
|
|
207
210
|
)
|
|
208
211
|
except AuthenticationError as e:
|
|
209
212
|
combined = f"{e.message} {e.details}"
|
|
@@ -297,18 +300,20 @@ def init(
|
|
|
297
300
|
if profile is not None and profile.is_staff is not None:
|
|
298
301
|
profile_label = "staff" if profile.is_staff else "customer"
|
|
299
302
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
303
|
+
item: dict[str, Any] = {
|
|
304
|
+
"status": "initialized",
|
|
305
|
+
"message": "Datex Studio CLI is configured.",
|
|
306
|
+
"organization": profile.org_name if profile else None,
|
|
307
|
+
"organization_id": profile.org_id if profile else None,
|
|
308
|
+
"profile": profile_label,
|
|
309
|
+
"devops_org": profile.devops_org if profile else None,
|
|
310
|
+
"api_base_url": effective_api_base_url,
|
|
311
|
+
"footprint_daemon_auth": "configured" if daemon_persisted else None,
|
|
312
|
+
}
|
|
313
|
+
# A failed additional-consent round does not fail init (the primary login
|
|
314
|
+
# succeeded), but it must be visible next to the success status.
|
|
315
|
+
consent_failures = consent_failure_items(consent_outcomes)
|
|
316
|
+
if consent_failures:
|
|
317
|
+
item["consent_failures"] = consent_failures
|
|
318
|
+
|
|
319
|
+
ctx.output(single(item=item, semantic_key="init"))
|
|
@@ -281,12 +281,19 @@ def status(ctx: DxsContext, branch: int | None, repo: int | None) -> None:
|
|
|
281
281
|
)
|
|
282
282
|
|
|
283
283
|
client = ApiClient()
|
|
284
|
-
result = {}
|
|
284
|
+
result: dict[str, Any] = {}
|
|
285
285
|
|
|
286
|
-
# Get locks (requires repo_id)
|
|
286
|
+
# Get locks (requires repo_id). The locks route takes an applicationGroupId,
|
|
287
|
+
# so resolve the repo to its group(s) first — same resolution as `source locks`.
|
|
287
288
|
if repo_id:
|
|
288
289
|
ctx.log(f"Fetching locks for repository {repo_id}...")
|
|
289
|
-
|
|
290
|
+
group_ids = _lock_group_ids(client, repo_id)
|
|
291
|
+
if group_ids:
|
|
292
|
+
ctx.log(f"Repository {repo_id} resolved to application group(s) {group_ids}")
|
|
293
|
+
locks_data = _fetch_locks_for_groups(client, group_ids)
|
|
294
|
+
else:
|
|
295
|
+
# Back-compat: the value matched no repository — try it as a group ID.
|
|
296
|
+
locks_data = client.get(SourceControlEndpoints.locks(repo_id))
|
|
290
297
|
result["locks"] = locks_data
|
|
291
298
|
|
|
292
299
|
# Get feature branch changes if branch_id provided
|
|
@@ -224,6 +224,29 @@ class ApiClient:
|
|
|
224
224
|
content_type = response.headers.get("content-type", "")
|
|
225
225
|
response_preview = response.text[:500] if response.text else "(empty)"
|
|
226
226
|
|
|
227
|
+
# An HTML page with a success status from an API path is the SPA
|
|
228
|
+
# catch-all answering for a route the server does not serve — a
|
|
229
|
+
# missing route, not a server malfunction.
|
|
230
|
+
body_head = response.text.lstrip().lower() if response.text else ""
|
|
231
|
+
is_html_page = body_head.startswith(("<!doctype", "<html"))
|
|
232
|
+
if "text/html" in content_type.lower() and is_html_page:
|
|
233
|
+
raise ApiError(
|
|
234
|
+
message=("The API route does not exist (server returned the web app instead)"),
|
|
235
|
+
code="DXS-API-ROUTE-404",
|
|
236
|
+
status_code=response.status_code,
|
|
237
|
+
details={
|
|
238
|
+
"url": str(response.url),
|
|
239
|
+
"content_type": content_type,
|
|
240
|
+
"response_preview": response_preview,
|
|
241
|
+
},
|
|
242
|
+
suggestions=[
|
|
243
|
+
"This usually means the CLI command targets an endpoint "
|
|
244
|
+
"that has been renamed or removed",
|
|
245
|
+
"Check the URL in details.url against the server's API",
|
|
246
|
+
"Update the CLI if a newer version repoints this command",
|
|
247
|
+
],
|
|
248
|
+
) from e
|
|
249
|
+
|
|
227
250
|
raise ApiError(
|
|
228
251
|
message=f"API returned non-JSON response (status {response.status_code})",
|
|
229
252
|
code="DXS-API-JSON-001",
|