datex-studio-cli 0.5.0__tar.gz → 0.5.2__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.5.0 → datex_studio_cli-0.5.2}/.github/workflows/ci.yml +29 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/.gitignore +6 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/PKG-INFO +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/pyproject.toml +8 -3
- datex_studio_cli-0.5.2/scripts/check_wheel_completeness.py +58 -0
- datex_studio_cli-0.5.2/scripts/datadog_errors.py +275 -0
- datex_studio_cli-0.5.2/scripts/wavelength_query.py +428 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/__init__.py +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/cli.py +58 -2
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/explore.py +5 -30
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/reset.py +1 -0
- datex_studio_cli-0.5.2/src/dxs/commands/skills.py +442 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/api/endpoints.py +15 -0
- datex_studio_cli-0.5.2/src/dxs/core/quartermaster/__init__.py +5 -0
- datex_studio_cli-0.5.2/src/dxs/core/quartermaster/client.py +178 -0
- datex_studio_cli-0.5.2/src/dxs/core/skills/__init__.py +1 -0
- datex_studio_cli-0.5.2/src/dxs/core/skills/frontmatter.py +30 -0
- datex_studio_cli-0.5.2/src/dxs/core/skills/installer.py +148 -0
- datex_studio_cli-0.5.2/src/dxs/core/skills/layout.py +57 -0
- datex_studio_cli-0.5.2/src/dxs/core/skills/lock.py +98 -0
- datex_studio_cli-0.5.2/src/dxs/core/skills/resolver.py +48 -0
- datex_studio_cli-0.5.2/src/dxs/core/skills/staleness.py +55 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/404/index.html +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/404.html +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/design/capture/index.html +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/design/capture/index.txt +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/design/index.html +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/design/index.txt +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/index.html +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/index.txt +1 -1
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/.dockerignore +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/.github/actionlint.yaml +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/.github/workflows/build-image.yml +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/.github/workflows/promote-prod.yml +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/.github/workflows/release.yml +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/CONTEXT.md +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/README.md +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/otel/.env.example +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/otel/Dockerfile +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/otel/README.md +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/otel/apply-local-cache.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/otel/collector-config.yaml +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/otel/docker-compose.yml +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/otel/local-test-settings.json +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/otel/remote-settings.json +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/report-creator.skill +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/scripts/analyze_corpus.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/scripts/generate_rdlx_schema.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/__main__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/api.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/auth.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/branch.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/branch_commit_guard.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/cascade.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/config.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/configuration.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/crm.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/crm_delivery.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/crm_time.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/datasource.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/devops.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/document.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/endpoint.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/env.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/function.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/init.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/marketplace.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/ng/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/ng/_common.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/ng/api.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/ng/create.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/ng/data.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/ng/pack.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/ng/preview.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/odata.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/organization.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/proxy.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/reference.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/release_notes.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/repo.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/add_cmds.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/api.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/batch.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/create.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/data.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/dataset.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/edit.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/folder.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/preview.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/report/schema_cmds.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/schema.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/servicepack.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/source.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/studio.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/telemetry.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/user.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/commands/validation_report.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/context.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/agent_browser.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/api/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/api/app_config.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/api/client.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/api/metadata_models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/api/models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/api/reference_graph.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/api/replacements.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/auth/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/auth/datex_tenant.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/auth/decorators.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/auth/msal_client.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/auth/tenants.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/auth/token_cache.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/cache.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/flow_generator.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/flow_models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/flow_shape.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/generator.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/parsers.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/resolver.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/type_def_parser.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/datasource/validator.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/designer/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/designer/flow_models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/designer/models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/designer/parsers.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/devops/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/devops/client.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/devops/helpers.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/devops/models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/dynamics/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/dynamics/client.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/dynamics/html.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/dynamics/metadata_cache.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/dynamics/pagination.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/dynamics/resolvers.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/dynamics/time_entry.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/footprint/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/footprint/client.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/footprint/edmx_parser.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/footprint/metadata.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/function/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/function/generator.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/function/models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/function/validator.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/graph.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/onboarding.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/output/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/output/csv_fmt.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/output/formatter.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/output/json_fmt.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/output/redaction.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/output/yaml_fmt.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/release_notes.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/responses.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/telemetry/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/telemetry/config.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/telemetry/identity.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/telemetry/recorder.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/telemetry/scrubber.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/core/telemetry/sender.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/models/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/ng/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/ng/folder.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/ng/manifest.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/ng/mocks.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/ng/preview.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/ng/scaffold.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/ng/serve_lock.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/py.typed +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/datasource_binding.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/engine.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/field_parser.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/folder.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/manifest.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/models.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/preview.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/preview_arjs.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/schema.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/svg_renderer.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/templates/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/validate_arjs.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/validator.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/report/wrapper.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/click_options.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/clipboard.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/config.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/errors.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/filtering.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/image.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/paths.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/resolvers.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/responses.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/restricted.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/sorting.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/update_check.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/utils/versioning.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/__init__.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/app.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/design.py +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/proxy_app.py +0 -0
- {datex_studio_cli-0.5.0/src/dxs/web/static/_next/static/hfb4z29yVHQge-muP6roV → datex_studio_cli-0.5.2/src/dxs/web/static/_next/static/5ccHgxTeTrw5DjrFowRBg}/_buildManifest.js +0 -0
- {datex_studio_cli-0.5.0/src/dxs/web/static/_next/static/hfb4z29yVHQge-muP6roV → datex_studio_cli-0.5.2/src/dxs/web/static/_next/static/5ccHgxTeTrw5DjrFowRBg}/_ssgManifest.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/538-b06224e65f71c9a1.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-e3c660196c244183.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-c083dc9f01f32a04.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/app/design/page-689cd5b9e1d9a74e.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/app/layout-cec86f90e385371d.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/app/page-f0380b0f29983264.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/main-app-694cf3568b6c9fa9.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/render-cli.js +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/report-preview.html +0 -0
- {datex_studio_cli-0.5.0 → datex_studio_cli-0.5.2}/src/dxs/web/static/report-validate.html +0 -0
|
@@ -59,3 +59,32 @@ jobs:
|
|
|
59
59
|
- run: uv run ruff check quartermaster
|
|
60
60
|
- run: uv run --package quartermaster mypy quartermaster/src/quartermaster
|
|
61
61
|
- run: uv run --package quartermaster pytest quartermaster/tests -q
|
|
62
|
+
|
|
63
|
+
# The artifact nobody was looking at. 0.5.0 and 0.5.1 both shipped without
|
|
64
|
+
# dxs.core.quartermaster and dxs.core.skills, and 0.5.1 could not run
|
|
65
|
+
# `dxs --version` at all -- while this suite was fully green, because tests
|
|
66
|
+
# import from the source tree and never open the wheel. release.yml calls
|
|
67
|
+
# this workflow, so a broken artifact now blocks the release too.
|
|
68
|
+
package:
|
|
69
|
+
runs-on: arc-dxs
|
|
70
|
+
timeout-minutes: 15
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v7
|
|
73
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
74
|
+
with:
|
|
75
|
+
enable-cache: true
|
|
76
|
+
cache-suffix: package
|
|
77
|
+
download-from-astral-mirror: false
|
|
78
|
+
- run: uv python install 3.13
|
|
79
|
+
- run: uv build
|
|
80
|
+
- name: Every package under src/ must be in the wheel
|
|
81
|
+
run: uv run python scripts/check_wheel_completeness.py
|
|
82
|
+
- name: The built wheel must actually run
|
|
83
|
+
run: |
|
|
84
|
+
# A clean venv, not the workspace: importing from the source tree is
|
|
85
|
+
# exactly what hid this. Resolve deps from the index like a user does.
|
|
86
|
+
uv venv /tmp/smoke
|
|
87
|
+
uv pip install --python /tmp/smoke/bin/python dist/*.whl
|
|
88
|
+
/tmp/smoke/bin/dxs --version
|
|
89
|
+
/tmp/smoke/bin/dxs skills --help > /dev/null
|
|
90
|
+
/tmp/smoke/bin/python -c "import dxs.core.quartermaster, dxs.core.skills"
|
|
@@ -105,3 +105,9 @@ test-preview.svg
|
|
|
105
105
|
|
|
106
106
|
# Live auth matrix config (real accounts)
|
|
107
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.5.
|
|
3
|
+
Version: 0.5.2
|
|
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.5.
|
|
7
|
+
version = "0.5.2"
|
|
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"
|
|
@@ -94,7 +94,7 @@ exclude = [
|
|
|
94
94
|
"documentation/",
|
|
95
95
|
"exploration/",
|
|
96
96
|
"exclude/",
|
|
97
|
-
"skills/",
|
|
97
|
+
"/skills/",
|
|
98
98
|
"tests/",
|
|
99
99
|
"publish.sh",
|
|
100
100
|
"*.analysis.md",
|
|
@@ -102,7 +102,7 @@ exclude = [
|
|
|
102
102
|
"uv.lock",
|
|
103
103
|
"src/dxs/web/frontend/",
|
|
104
104
|
"example-reports/",
|
|
105
|
-
"quartermaster/",
|
|
105
|
+
"/quartermaster/",
|
|
106
106
|
]
|
|
107
107
|
|
|
108
108
|
[tool.uv.workspace]
|
|
@@ -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"]
|
|
@@ -160,6 +164,7 @@ markers = [
|
|
|
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",
|
|
162
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)",
|
|
163
168
|
]
|
|
164
169
|
|
|
165
170
|
[tool.coverage.run]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Assert the built wheel carries every package that exists under src/dxs.
|
|
3
|
+
|
|
4
|
+
Versions 0.5.0 and 0.5.1 both shipped without ``dxs.core.quartermaster`` and
|
|
5
|
+
``dxs.core.skills``. The sdist exclude list named ``skills/`` and
|
|
6
|
+
``quartermaster/`` to drop the top-level directories of those names; hatchling
|
|
7
|
+
matches an unanchored pattern at any depth, so it dropped the nested packages
|
|
8
|
+
too, and ``uv build`` builds the wheel from the sdist it just made. 0.5.1 could
|
|
9
|
+
not even run ``dxs --version``.
|
|
10
|
+
|
|
11
|
+
A full green suite said nothing about any of it: tests import from the source
|
|
12
|
+
tree and never open the artifact. This looks at the artifact.
|
|
13
|
+
|
|
14
|
+
Usage: check_wheel_completeness.py [dist_dir]
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
import zipfile
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
24
|
+
SRC = ROOT / "src"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> int:
|
|
28
|
+
dist = Path(sys.argv[1]) if len(sys.argv) > 1 else ROOT / "dist"
|
|
29
|
+
wheels = sorted(dist.glob("*.whl"))
|
|
30
|
+
if not wheels:
|
|
31
|
+
print(f"no wheel in {dist} — run `uv build` first", file=sys.stderr)
|
|
32
|
+
return 1
|
|
33
|
+
wheel = wheels[-1]
|
|
34
|
+
|
|
35
|
+
# Every directory holding an __init__.py is a package that must ship.
|
|
36
|
+
expected = {
|
|
37
|
+
p.parent.relative_to(SRC).as_posix() for p in SRC.rglob("__init__.py")
|
|
38
|
+
if "__pycache__" not in p.parts
|
|
39
|
+
}
|
|
40
|
+
names = zipfile.ZipFile(wheel).namelist()
|
|
41
|
+
shipped = {n.rsplit("/", 1)[0] for n in names if n.endswith("/__init__.py")}
|
|
42
|
+
|
|
43
|
+
missing = sorted(expected - shipped)
|
|
44
|
+
if missing:
|
|
45
|
+
print(f"{wheel.name} is missing {len(missing)} package(s) present in src/:",
|
|
46
|
+
file=sys.stderr)
|
|
47
|
+
for m in missing:
|
|
48
|
+
print(f" {m}", file=sys.stderr)
|
|
49
|
+
print("\nUsually an unanchored exclude pattern in pyproject.toml matching a "
|
|
50
|
+
"nested path — anchor it with a leading slash.", file=sys.stderr)
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
print(f"{wheel.name}: all {len(expected)} packages present")
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
raise SystemExit(main())
|
|
@@ -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()
|