datex-studio-cli 0.4.25__tar.gz → 0.5.1__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.25 → datex_studio_cli-0.5.1}/.github/workflows/build-image.yml +4 -4
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/workflows/ci.yml +14 -8
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/workflows/promote-prod.yml +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/workflows/release.yml +5 -3
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.gitignore +9 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/PKG-INFO +20 -8
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/README.md +19 -7
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/pyproject.toml +7 -1
- datex_studio_cli-0.5.1/scripts/datadog_errors.py +275 -0
- datex_studio_cli-0.5.1/scripts/wavelength_query.py +428 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/__init__.py +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/cli.py +58 -2
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/auth.py +685 -252
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/datasource.py +3 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/explore.py +5 -30
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/init.py +89 -60
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/odata.py +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/__init__.py +2 -2
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/reset.py +1 -0
- datex_studio_cli-0.5.1/src/dxs/commands/skills.py +442 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/endpoints.py +15 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/datex_tenant.py +2 -2
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/msal_client.py +119 -10
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/tenants.py +120 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/token_cache.py +37 -8
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/generator.py +2 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/footprint/client.py +10 -3
- datex_studio_cli-0.5.1/src/dxs/core/onboarding.py +518 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/engine.py +13 -13
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/preview.py +2 -2
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/preview_arjs.py +2 -2
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/svg_renderer.py +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/design.py +4 -6
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/404/index.html +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/404.html +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/design/capture/index.html +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/design/capture/index.txt +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/design/index.html +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/design/index.txt +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/index.html +1 -1
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/index.txt +1 -1
- datex_studio_cli-0.4.25/src/dxs/core/onboarding.py +0 -151
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.dockerignore +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/.github/actionlint.yaml +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/CONTEXT.md +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/.env.example +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/Dockerfile +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/README.md +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/apply-local-cache.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/collector-config.yaml +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/docker-compose.yml +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/local-test-settings.json +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/otel/remote-settings.json +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/report-creator.skill +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/scripts/analyze_corpus.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/scripts/generate_rdlx_schema.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/__main__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/api.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/branch.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/branch_commit_guard.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/cascade.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/config.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/configuration.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/crm.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/crm_delivery.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/crm_time.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/devops.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/document.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/endpoint.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/env.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/function.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/marketplace.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/_common.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/api.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/create.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/data.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/pack.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/ng/preview.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/organization.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/proxy.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/reference.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/release_notes.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/repo.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/add_cmds.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/api.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/batch.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/create.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/data.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/dataset.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/edit.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/folder.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/preview.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/report/schema_cmds.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/schema.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/servicepack.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/source.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/studio.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/telemetry.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/user.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/commands/validation_report.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/context.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/agent_browser.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/app_config.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/client.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/metadata_models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/reference_graph.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/api/replacements.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/auth/decorators.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/cache.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/flow_generator.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/flow_models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/flow_shape.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/parsers.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/resolver.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/type_def_parser.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/datasource/validator.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/designer/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/designer/flow_models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/designer/models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/designer/parsers.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/devops/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/devops/client.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/devops/helpers.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/devops/models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/client.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/html.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/metadata_cache.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/pagination.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/resolvers.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/dynamics/time_entry.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/footprint/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/footprint/edmx_parser.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/footprint/metadata.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/function/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/function/generator.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/function/models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/function/validator.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/graph.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/csv_fmt.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/formatter.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/json_fmt.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/redaction.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/output/yaml_fmt.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/release_notes.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/responses.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/config.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/identity.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/recorder.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/scrubber.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/core/telemetry/sender.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/models/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/folder.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/manifest.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/mocks.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/preview.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/scaffold.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/ng/serve_lock.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/py.typed +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/datasource_binding.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/field_parser.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/folder.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/manifest.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/models.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/schema.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/templates/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/validate_arjs.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/validator.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/report/wrapper.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/click_options.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/clipboard.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/config.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/errors.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/filtering.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/image.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/paths.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/resolvers.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/responses.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/restricted.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/sorting.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/update_check.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/utils/versioning.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/__init__.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/app.py +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/proxy_app.py +0 -0
- {datex_studio_cli-0.4.25/src/dxs/web/static/_next/static/BbKuTltyqzuSYoNcVtfWa → datex_studio_cli-0.5.1/src/dxs/web/static/_next/static/W6-87g3VtSLhTM2G0U3r1}/_buildManifest.js +0 -0
- {datex_studio_cli-0.4.25/src/dxs/web/static/_next/static/BbKuTltyqzuSYoNcVtfWa → datex_studio_cli-0.5.1/src/dxs/web/static/_next/static/W6-87g3VtSLhTM2G0U3r1}/_ssgManifest.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/538-b06224e65f71c9a1.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-e3c660196c244183.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-c083dc9f01f32a04.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/design/page-689cd5b9e1d9a74e.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/layout-cec86f90e385371d.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/app/page-f0380b0f29983264.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/main-app-694cf3568b6c9fa9.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/render-cli.js +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/report-preview.html +0 -0
- {datex_studio_cli-0.4.25 → datex_studio_cli-0.5.1}/src/dxs/web/static/report-validate.html +0 -0
|
@@ -29,9 +29,9 @@ jobs:
|
|
|
29
29
|
runs-on: arc-dxs
|
|
30
30
|
timeout-minutes: 30
|
|
31
31
|
steps:
|
|
32
|
-
- uses: actions/checkout@
|
|
33
|
-
- uses: docker/setup-buildx-action@
|
|
34
|
-
- uses: docker/login-action@
|
|
32
|
+
- uses: actions/checkout@v7
|
|
33
|
+
- uses: docker/setup-buildx-action@v4
|
|
34
|
+
- uses: docker/login-action@v4
|
|
35
35
|
with:
|
|
36
36
|
registry: ghcr.io
|
|
37
37
|
username: ${{ github.actor }}
|
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
39
39
|
- id: meta
|
|
40
40
|
run: echo "datetag=$(date -u +%Y%m%d).${{ github.run_number }}" >> "$GITHUB_OUTPUT"
|
|
41
41
|
- id: build
|
|
42
|
-
uses: docker/build-push-action@
|
|
42
|
+
uses: docker/build-push-action@v7
|
|
43
43
|
with:
|
|
44
44
|
context: .
|
|
45
45
|
file: quartermaster/Dockerfile
|
|
@@ -20,12 +20,18 @@ jobs:
|
|
|
20
20
|
runs-on: arc-dxs
|
|
21
21
|
timeout-minutes: 30
|
|
22
22
|
steps:
|
|
23
|
-
- uses: actions/checkout@
|
|
24
|
-
- uses: astral-sh/setup-uv@
|
|
23
|
+
- uses: actions/checkout@v7
|
|
24
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
25
25
|
with:
|
|
26
26
|
enable-cache: true
|
|
27
|
+
# Distinct per-job key: cli and service run in parallel and would
|
|
28
|
+
# otherwise race to save the same cache entry (noisy warning).
|
|
29
|
+
cache-suffix: cli
|
|
30
|
+
# The releases.astral.sh mirror 404s from the arc-dxs runners and
|
|
31
|
+
# every download falls back to GitHub Releases anyway — skip it.
|
|
32
|
+
download-from-astral-mirror: false
|
|
27
33
|
- run: uv python install 3.13
|
|
28
|
-
- uses: actions/setup-node@
|
|
34
|
+
- uses: actions/setup-node@v7
|
|
29
35
|
with:
|
|
30
36
|
node-version: 22
|
|
31
37
|
- name: Build the report renderer bundle (snapshot tests shell out to it)
|
|
@@ -35,19 +41,19 @@ jobs:
|
|
|
35
41
|
npm run build:render-cli
|
|
36
42
|
- run: uv sync --all-packages --extra dev
|
|
37
43
|
- run: uv run ruff check .
|
|
38
|
-
-
|
|
39
|
-
run: uv run mypy src/dxs
|
|
40
|
-
continue-on-error: true
|
|
44
|
+
- run: uv run mypy src/dxs
|
|
41
45
|
- run: uv run pytest -q
|
|
42
46
|
|
|
43
47
|
service:
|
|
44
48
|
runs-on: arc-dxs
|
|
45
49
|
timeout-minutes: 20
|
|
46
50
|
steps:
|
|
47
|
-
- uses: actions/checkout@
|
|
48
|
-
- uses: astral-sh/setup-uv@
|
|
51
|
+
- uses: actions/checkout@v7
|
|
52
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
49
53
|
with:
|
|
50
54
|
enable-cache: true
|
|
55
|
+
cache-suffix: service
|
|
56
|
+
download-from-astral-mirror: false
|
|
51
57
|
- run: uv python install 3.13
|
|
52
58
|
- run: uv sync --all-packages --extra dev
|
|
53
59
|
- run: uv run ruff check quartermaster
|
|
@@ -41,10 +41,12 @@ jobs:
|
|
|
41
41
|
contents: write # the dispatch lane pushes the bump commit + tag
|
|
42
42
|
id-token: write # PyPI trusted publishing
|
|
43
43
|
steps:
|
|
44
|
-
- uses: actions/checkout@
|
|
45
|
-
- uses: astral-sh/setup-uv@
|
|
44
|
+
- uses: actions/checkout@v7
|
|
45
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
46
46
|
with:
|
|
47
47
|
enable-cache: true
|
|
48
|
+
cache-suffix: release
|
|
49
|
+
download-from-astral-mirror: false
|
|
48
50
|
- run: uv python install 3.13
|
|
49
51
|
- name: Bump version, commit, and tag (dispatch with a version input)
|
|
50
52
|
if: github.event_name == 'workflow_dispatch' && inputs.version != ''
|
|
@@ -91,7 +93,7 @@ jobs:
|
|
|
91
93
|
echo "version sources disagree — run 'uv lock' and align all three before releasing" >&2
|
|
92
94
|
exit 1
|
|
93
95
|
fi
|
|
94
|
-
- uses: actions/setup-node@
|
|
96
|
+
- uses: actions/setup-node@v7
|
|
95
97
|
with:
|
|
96
98
|
node-version: 22
|
|
97
99
|
- name: Build the web UI (shipped via hatch artifacts)
|
|
@@ -102,3 +102,12 @@ test-preview.svg
|
|
|
102
102
|
|
|
103
103
|
# CAC working copies (dxs ng harnesses) — never committed
|
|
104
104
|
/components/
|
|
105
|
+
|
|
106
|
+
# Live auth matrix config (real accounts)
|
|
107
|
+
tests/live/accounts.local.json
|
|
108
|
+
|
|
109
|
+
# Project-scope `dxs skills` install artifacts (.claude/skills is above).
|
|
110
|
+
# Canonical copies, the codex symlink tree, and the install record.
|
|
111
|
+
.agents/
|
|
112
|
+
.codex/
|
|
113
|
+
skills-lock.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: datex-studio-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -77,7 +77,7 @@ pip install datex-studio-cli
|
|
|
77
77
|
## Quick Start
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
# Authenticate with Azure Entra
|
|
80
|
+
# Authenticate with Azure Entra (opens your browser; device code when headless)
|
|
81
81
|
dxs auth login
|
|
82
82
|
|
|
83
83
|
# Check authentication status
|
|
@@ -203,11 +203,19 @@ Set `DXS_RESTRICTED_MODE=1` to block potentially dangerous operations:
|
|
|
203
203
|
|
|
204
204
|
| Command | Description |
|
|
205
205
|
|---------|-------------|
|
|
206
|
-
| `login` |
|
|
206
|
+
| `login [selector]` | Sign in — browser + PKCE by default, device code when headless or with `--use-device-code`/`--no-browser` |
|
|
207
207
|
| `logout` | Clear credentials (`--all` for all identities) |
|
|
208
208
|
| `status` | Show current authentication status and token expiration |
|
|
209
209
|
| `list` | List all cached identities |
|
|
210
|
-
| `switch <
|
|
210
|
+
| `switch <selector>` | Switch identity (username, org, Entra domain, or tenant ID) |
|
|
211
|
+
|
|
212
|
+
One selector grammar is shared by `login`, `switch`, and `init`: an email
|
|
213
|
+
address derives the tenant from its domain (works on a fresh machine), a
|
|
214
|
+
domain or tenant GUID names the tenant directly, anything else is an
|
|
215
|
+
organization name. `--domain`/`--tenant-id` name the tenant to authenticate
|
|
216
|
+
against and always win; combined with an email selector they express a guest
|
|
217
|
+
signing in to another organization's tenant. A usable cached identity for the
|
|
218
|
+
target skips the browser entirely (`--force` re-authenticates).
|
|
211
219
|
|
|
212
220
|
**Multi-Identity Workflow:**
|
|
213
221
|
|
|
@@ -215,14 +223,18 @@ Set `DXS_RESTRICTED_MODE=1` to block potentially dangerous operations:
|
|
|
215
223
|
# Login to primary organization
|
|
216
224
|
dxs auth login
|
|
217
225
|
|
|
218
|
-
#
|
|
219
|
-
dxs auth login
|
|
226
|
+
# Customer self-service: the tenant is derived from your address
|
|
227
|
+
dxs auth login you@csw-wi.com
|
|
228
|
+
|
|
229
|
+
# Login to an additional organization (fresh auth against its tenant)
|
|
230
|
+
dxs auth login OtherOrg
|
|
220
231
|
|
|
221
232
|
# List all identities
|
|
222
233
|
dxs auth list
|
|
223
234
|
|
|
224
|
-
# Switch between
|
|
225
|
-
dxs auth switch
|
|
235
|
+
# Switch between identities — org name, domain, tenant ID, or username
|
|
236
|
+
dxs auth switch OtherOrg
|
|
237
|
+
dxs auth switch csw-wi.com
|
|
226
238
|
```
|
|
227
239
|
|
|
228
240
|
### CLI Settings (`dxs settings`)
|
|
@@ -24,7 +24,7 @@ pip install datex-studio-cli
|
|
|
24
24
|
## Quick Start
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
# Authenticate with Azure Entra
|
|
27
|
+
# Authenticate with Azure Entra (opens your browser; device code when headless)
|
|
28
28
|
dxs auth login
|
|
29
29
|
|
|
30
30
|
# Check authentication status
|
|
@@ -150,11 +150,19 @@ Set `DXS_RESTRICTED_MODE=1` to block potentially dangerous operations:
|
|
|
150
150
|
|
|
151
151
|
| Command | Description |
|
|
152
152
|
|---------|-------------|
|
|
153
|
-
| `login` |
|
|
153
|
+
| `login [selector]` | Sign in — browser + PKCE by default, device code when headless or with `--use-device-code`/`--no-browser` |
|
|
154
154
|
| `logout` | Clear credentials (`--all` for all identities) |
|
|
155
155
|
| `status` | Show current authentication status and token expiration |
|
|
156
156
|
| `list` | List all cached identities |
|
|
157
|
-
| `switch <
|
|
157
|
+
| `switch <selector>` | Switch identity (username, org, Entra domain, or tenant ID) |
|
|
158
|
+
|
|
159
|
+
One selector grammar is shared by `login`, `switch`, and `init`: an email
|
|
160
|
+
address derives the tenant from its domain (works on a fresh machine), a
|
|
161
|
+
domain or tenant GUID names the tenant directly, anything else is an
|
|
162
|
+
organization name. `--domain`/`--tenant-id` name the tenant to authenticate
|
|
163
|
+
against and always win; combined with an email selector they express a guest
|
|
164
|
+
signing in to another organization's tenant. A usable cached identity for the
|
|
165
|
+
target skips the browser entirely (`--force` re-authenticates).
|
|
158
166
|
|
|
159
167
|
**Multi-Identity Workflow:**
|
|
160
168
|
|
|
@@ -162,14 +170,18 @@ Set `DXS_RESTRICTED_MODE=1` to block potentially dangerous operations:
|
|
|
162
170
|
# Login to primary organization
|
|
163
171
|
dxs auth login
|
|
164
172
|
|
|
165
|
-
#
|
|
166
|
-
dxs auth login
|
|
173
|
+
# Customer self-service: the tenant is derived from your address
|
|
174
|
+
dxs auth login you@csw-wi.com
|
|
175
|
+
|
|
176
|
+
# Login to an additional organization (fresh auth against its tenant)
|
|
177
|
+
dxs auth login OtherOrg
|
|
167
178
|
|
|
168
179
|
# List all identities
|
|
169
180
|
dxs auth list
|
|
170
181
|
|
|
171
|
-
# Switch between
|
|
172
|
-
dxs auth switch
|
|
182
|
+
# Switch between identities — org name, domain, tenant ID, or username
|
|
183
|
+
dxs auth switch OtherOrg
|
|
184
|
+
dxs auth switch csw-wi.com
|
|
173
185
|
```
|
|
174
186
|
|
|
175
187
|
### CLI Settings (`dxs settings`)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "datex-studio-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.1"
|
|
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"
|
|
@@ -149,6 +149,10 @@ ignore_missing_imports = true
|
|
|
149
149
|
module = ["fastapi.*", "uvicorn.*"]
|
|
150
150
|
ignore_missing_imports = true
|
|
151
151
|
|
|
152
|
+
[[tool.mypy.overrides]]
|
|
153
|
+
module = ["pyodbc"]
|
|
154
|
+
ignore_missing_imports = true
|
|
155
|
+
|
|
152
156
|
[tool.pytest.ini_options]
|
|
153
157
|
testpaths = ["tests"]
|
|
154
158
|
python_files = ["test_*.py"]
|
|
@@ -159,6 +163,8 @@ markers = [
|
|
|
159
163
|
"internal_commands_gated: exercise the devops/crm visibility gate (no DXS_INTERNAL_COMMANDS override)",
|
|
160
164
|
"real_auth_state: exercise real auth state (no injected DXS_ACCESS_TOKEN; the variable is scrubbed)",
|
|
161
165
|
"telemetry_live: run without the suite-wide DXS_TELEMETRY=0 kill-switch",
|
|
166
|
+
"live_auth: sign in against real Entra tenants (opt-in; needs DXS_LIVE_AUTH=1 + a human)",
|
|
167
|
+
"mssql: integration tests against a throwaway SQL Server container (opt-in; needs DXS_MSSQL_TEST=1)",
|
|
162
168
|
]
|
|
163
169
|
|
|
164
170
|
[tool.coverage.run]
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = ["httpx", "python-dotenv"]
|
|
4
|
+
# ///
|
|
5
|
+
"""Pull recent CLI error events from Datadog Logs for investigation.
|
|
6
|
+
|
|
7
|
+
Queries the Datadog Logs Search API for ``service:dxs-cli status:error`` (the
|
|
8
|
+
telemetry forwarder's shape — see docs/telemetry-datadog-mapping.md) and prints
|
|
9
|
+
either a summary grouped by command + error code, or the raw events.
|
|
10
|
+
|
|
11
|
+
Credentials come from the project ``.env``: DATADOG_API_KEY, DATADOG_APP_KEY,
|
|
12
|
+
DATADOG_SITE (e.g. ``us3.datadoghq.com``).
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
uv run scripts/datadog_errors.py # last 24h, summary
|
|
16
|
+
uv run scripts/datadog_errors.py --since 7d # last 7 days
|
|
17
|
+
uv run scripts/datadog_errors.py --since 7d --list # one line per event
|
|
18
|
+
uv run scripts/datadog_errors.py --code DXS-API-001 --detail # full stacks
|
|
19
|
+
uv run scripts/datadog_errors.py --command source --version 0.5.0 -f json
|
|
20
|
+
uv run scripts/datadog_errors.py -q 'usr.email:*@datexcorp.com' # extra filter
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import re
|
|
29
|
+
import sys
|
|
30
|
+
import time
|
|
31
|
+
from collections import Counter, defaultdict
|
|
32
|
+
from datetime import datetime, timedelta, timezone
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
SUMMARY_FIELDS = (
|
|
37
|
+
"ts",
|
|
38
|
+
"command",
|
|
39
|
+
"error_code",
|
|
40
|
+
"error_class",
|
|
41
|
+
"error_message",
|
|
42
|
+
"cli_version",
|
|
43
|
+
"api_target",
|
|
44
|
+
"env",
|
|
45
|
+
"usr_email",
|
|
46
|
+
"org",
|
|
47
|
+
"os",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_since(value: str) -> timedelta:
|
|
52
|
+
m = re.fullmatch(r"(\d+)([mhd])", value.strip())
|
|
53
|
+
if not m:
|
|
54
|
+
raise argparse.ArgumentTypeError("expected like 30m, 6h, 7d")
|
|
55
|
+
n, unit = int(m.group(1)), m.group(2)
|
|
56
|
+
return timedelta(**{{"m": "minutes", "h": "hours", "d": "days"}[unit]: n})
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def build_query(args: argparse.Namespace) -> str:
|
|
60
|
+
parts = [f"service:{args.service}", "status:error"]
|
|
61
|
+
if args.env:
|
|
62
|
+
parts.append(f"env:{args.env}")
|
|
63
|
+
if args.api_target:
|
|
64
|
+
parts.append(f"@api_target:{args.api_target}")
|
|
65
|
+
if args.code:
|
|
66
|
+
parts.append(f"@error_code:{args.code}")
|
|
67
|
+
if args.command:
|
|
68
|
+
parts.append(f'@command:"{args.command}"')
|
|
69
|
+
if args.version:
|
|
70
|
+
parts.append(f"@version:{args.version}")
|
|
71
|
+
if args.query:
|
|
72
|
+
parts.append(f"({args.query})")
|
|
73
|
+
return " ".join(parts)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def fetch_events(
|
|
77
|
+
site: str, api_key: str, app_key: str, query: str, since: timedelta, limit: int
|
|
78
|
+
) -> list[dict[str, Any]]:
|
|
79
|
+
import httpx
|
|
80
|
+
|
|
81
|
+
now = datetime.now(timezone.utc)
|
|
82
|
+
body: dict[str, Any] = {
|
|
83
|
+
"filter": {
|
|
84
|
+
"query": query,
|
|
85
|
+
"from": (now - since).isoformat(),
|
|
86
|
+
"to": now.isoformat(),
|
|
87
|
+
},
|
|
88
|
+
"sort": "-timestamp",
|
|
89
|
+
"page": {"limit": min(limit, 1000)},
|
|
90
|
+
}
|
|
91
|
+
headers = {
|
|
92
|
+
"DD-API-KEY": api_key,
|
|
93
|
+
"DD-APPLICATION-KEY": app_key,
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
}
|
|
96
|
+
events: list[dict[str, Any]] = []
|
|
97
|
+
retries = 0
|
|
98
|
+
with httpx.Client(base_url=f"https://api.{site}", headers=headers, timeout=30) as client:
|
|
99
|
+
while True:
|
|
100
|
+
resp = client.post("/api/v2/logs/events/search", json=body)
|
|
101
|
+
if resp.status_code == 429 and retries < 3:
|
|
102
|
+
wait = int(
|
|
103
|
+
resp.headers.get("Retry-After") or resp.headers.get("X-RateLimit-Reset") or 5
|
|
104
|
+
)
|
|
105
|
+
print(f"rate limited; retrying in {wait}s", file=sys.stderr)
|
|
106
|
+
time.sleep(wait)
|
|
107
|
+
retries += 1
|
|
108
|
+
continue
|
|
109
|
+
if resp.status_code != 200:
|
|
110
|
+
print(f"Datadog API {resp.status_code}: {resp.text[:500]}", file=sys.stderr)
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
payload = resp.json()
|
|
113
|
+
events.extend(payload.get("data", []))
|
|
114
|
+
cursor = payload.get("meta", {}).get("page", {}).get("after")
|
|
115
|
+
if not cursor or len(events) >= limit:
|
|
116
|
+
break
|
|
117
|
+
body["page"]["cursor"] = cursor
|
|
118
|
+
return events[:limit]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def full_command(group: str | None, argv: Any) -> str | None:
|
|
122
|
+
"""`command` is only the Click group (e.g. `configuration`); recover the
|
|
123
|
+
subcommand path from argv: the bare-word tokens that follow the group."""
|
|
124
|
+
if not group or not isinstance(argv, list):
|
|
125
|
+
return group
|
|
126
|
+
try:
|
|
127
|
+
i = argv.index(group)
|
|
128
|
+
except ValueError:
|
|
129
|
+
return group
|
|
130
|
+
words = [group]
|
|
131
|
+
for tok in argv[i + 1 : i + 3]:
|
|
132
|
+
if isinstance(tok, str) and re.fullmatch(r"[a-z][a-z-]*", tok):
|
|
133
|
+
words.append(tok)
|
|
134
|
+
else:
|
|
135
|
+
break
|
|
136
|
+
return " ".join(words)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def flatten(event: dict[str, Any]) -> dict[str, Any]:
|
|
140
|
+
"""Reduce a Datadog log event to the attributes worth reading."""
|
|
141
|
+
attrs = event.get("attributes", {})
|
|
142
|
+
custom = attrs.get("attributes", {}) # user attributes live one level down
|
|
143
|
+
tags = dict(t.split(":", 1) for t in attrs.get("tags", []) if ":" in t)
|
|
144
|
+
usr = custom.get("usr", {}) or {}
|
|
145
|
+
err = custom.get("error", {}) or {}
|
|
146
|
+
return {
|
|
147
|
+
"id": event.get("id"),
|
|
148
|
+
"ts": attrs.get("timestamp"),
|
|
149
|
+
"message": attrs.get("message"),
|
|
150
|
+
"command": full_command(custom.get("command"), custom.get("argv")),
|
|
151
|
+
"argv": custom.get("argv"),
|
|
152
|
+
"error_code": custom.get("error_code") or tags.get("error_code"),
|
|
153
|
+
"error_class": custom.get("error_class") or err.get("kind"),
|
|
154
|
+
"error_message": custom.get("error_message") or err.get("message"),
|
|
155
|
+
"error_stack": custom.get("error_stack") or err.get("stack"),
|
|
156
|
+
"exit_code": custom.get("exit_code"),
|
|
157
|
+
"duration_ms": custom.get("duration_ms"),
|
|
158
|
+
"cli_version": custom.get("version") or tags.get("version"),
|
|
159
|
+
"org": custom.get("datex_org_name") or tags.get("org"),
|
|
160
|
+
"python_version": custom.get("python_version"),
|
|
161
|
+
"os": custom.get("os", {}).get("name")
|
|
162
|
+
if isinstance(custom.get("os"), dict)
|
|
163
|
+
else custom.get("os"),
|
|
164
|
+
"os_release": custom.get("os_release"),
|
|
165
|
+
"api_target": custom.get("api_target") or tags.get("api_target"),
|
|
166
|
+
"env": tags.get("env"),
|
|
167
|
+
"usr_id": usr.get("id"),
|
|
168
|
+
"usr_email": usr.get("email"),
|
|
169
|
+
"usr_name": usr.get("name"),
|
|
170
|
+
"session_id": (custom.get("session") or {}).get("id"),
|
|
171
|
+
"install_id": custom.get("install_id"),
|
|
172
|
+
"ctx_branch_id": custom.get("ctx_branch_id"),
|
|
173
|
+
"ctx_org": custom.get("ctx_org"),
|
|
174
|
+
"ctx_env": custom.get("ctx_env"),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def print_summary(rows: list[dict[str, Any]]) -> None:
|
|
179
|
+
if not rows:
|
|
180
|
+
print("No matching error events.")
|
|
181
|
+
return
|
|
182
|
+
groups: dict[tuple[str, str, str], list[dict[str, Any]]] = defaultdict(list)
|
|
183
|
+
for r in rows:
|
|
184
|
+
groups[(r["command"] or "?", r["error_code"] or "-", r["error_class"] or "?")].append(r)
|
|
185
|
+
print(f"{len(rows)} error events, {len(groups)} distinct (command, code, class)\n")
|
|
186
|
+
print(f"{'count':>5} {'command':<28} {'code':<14} {'class':<24} sample message")
|
|
187
|
+
for key, grp in sorted(groups.items(), key=lambda kv: -len(kv[1])):
|
|
188
|
+
cmd, code, cls = key
|
|
189
|
+
msg = (grp[0]["error_message"] or "")[:80].replace("\n", " ")
|
|
190
|
+
print(f"{len(grp):>5} {cmd:<28} {code:<14} {cls:<24} {msg}")
|
|
191
|
+
users = Counter(r["usr_email"] or r["usr_name"] or "?" for r in grp)
|
|
192
|
+
versions = Counter(r["cli_version"] or "?" for r in grp)
|
|
193
|
+
print(
|
|
194
|
+
f" last={grp[0]['ts']} users={dict(users.most_common(3))}"
|
|
195
|
+
f" versions={dict(versions.most_common(3))}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def print_list(rows: list[dict[str, Any]]) -> None:
|
|
200
|
+
for r in rows:
|
|
201
|
+
msg = (r["error_message"] or "")[:100].replace("\n", " ")
|
|
202
|
+
print(
|
|
203
|
+
f"{r['ts']} {r['cli_version'] or '?':<8} {r['usr_email'] or r['usr_name'] or '?':<32}"
|
|
204
|
+
f" {r['command'] or '?':<28} {r['error_code'] or '-':<14} {msg}"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def print_detail(rows: list[dict[str, Any]]) -> None:
|
|
209
|
+
for r in rows:
|
|
210
|
+
print("=" * 100)
|
|
211
|
+
for k in SUMMARY_FIELDS + ("argv", "ctx_branch_id", "ctx_org", "session_id", "id"):
|
|
212
|
+
if r.get(k) is not None:
|
|
213
|
+
print(f"{k:<14} {r[k]}")
|
|
214
|
+
if r.get("error_stack"):
|
|
215
|
+
print("--- stack ---")
|
|
216
|
+
print(r["error_stack"])
|
|
217
|
+
print("=" * 100)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def main(argv: list[str] | None = None) -> None:
|
|
221
|
+
p = argparse.ArgumentParser(
|
|
222
|
+
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
|
|
223
|
+
)
|
|
224
|
+
p.add_argument(
|
|
225
|
+
"--since",
|
|
226
|
+
type=parse_since,
|
|
227
|
+
default="24h",
|
|
228
|
+
help="lookback window, e.g. 30m, 6h, 7d (default 24h)",
|
|
229
|
+
)
|
|
230
|
+
p.add_argument("--service", default="dxs-cli", help="Datadog service tag (default dxs-cli)")
|
|
231
|
+
p.add_argument("--env", help="ddtag env: staging|prod|local (which Quartermaster received it)")
|
|
232
|
+
p.add_argument("--api-target", help="dev|qa|prod|default (which Datex environment the CLI hit)")
|
|
233
|
+
p.add_argument("--code", help="filter by DXS-* error code")
|
|
234
|
+
p.add_argument(
|
|
235
|
+
"--command", help="filter by Click group, e.g. configuration (subcommand is only in argv)"
|
|
236
|
+
)
|
|
237
|
+
p.add_argument("--version", help="filter by cli_version")
|
|
238
|
+
p.add_argument("-q", "--query", help="extra raw Datadog log query ANDed onto the filter")
|
|
239
|
+
p.add_argument("-n", "--limit", type=int, default=500, help="max events to fetch (default 500)")
|
|
240
|
+
mode = p.add_mutually_exclusive_group()
|
|
241
|
+
mode.add_argument("--list", action="store_true", help="one line per event")
|
|
242
|
+
mode.add_argument("--detail", action="store_true", help="every field + stack per event")
|
|
243
|
+
p.add_argument("-f", "--format", choices=["text", "json"], default="text")
|
|
244
|
+
p.add_argument("--show-query", action="store_true", help="print the Datadog query and exit")
|
|
245
|
+
args = p.parse_args(argv)
|
|
246
|
+
|
|
247
|
+
from dotenv import load_dotenv
|
|
248
|
+
|
|
249
|
+
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
|
|
250
|
+
api_key, app_key = os.environ.get("DATADOG_API_KEY"), os.environ.get("DATADOG_APP_KEY")
|
|
251
|
+
site = os.environ.get("DATADOG_SITE", "datadoghq.com")
|
|
252
|
+
if not api_key or not app_key:
|
|
253
|
+
print("DATADOG_API_KEY and DATADOG_APP_KEY must be set (expected in .env)", file=sys.stderr)
|
|
254
|
+
sys.exit(1)
|
|
255
|
+
|
|
256
|
+
query = build_query(args)
|
|
257
|
+
if args.show_query:
|
|
258
|
+
print(query)
|
|
259
|
+
return
|
|
260
|
+
print(f"query: {query} window: last {args.since}", file=sys.stderr)
|
|
261
|
+
|
|
262
|
+
rows = [flatten(e) for e in fetch_events(site, api_key, app_key, query, args.since, args.limit)]
|
|
263
|
+
if args.format == "json":
|
|
264
|
+
json.dump(rows, sys.stdout, indent=2, default=str)
|
|
265
|
+
print()
|
|
266
|
+
elif args.detail:
|
|
267
|
+
print_detail(rows)
|
|
268
|
+
elif args.list:
|
|
269
|
+
print_list(rows)
|
|
270
|
+
else:
|
|
271
|
+
print_summary(rows)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
main()
|