datex-studio-cli 0.5.6__tar.gz → 0.5.7__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.6 → datex_studio_cli-0.5.7}/PKG-INFO +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/pyproject.toml +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/__init__.py +1 -1
- datex_studio_cli-0.5.7/src/dxs/commands/skills.py +656 -0
- datex_studio_cli-0.5.7/src/dxs/core/skills/discovery.py +98 -0
- datex_studio_cli-0.5.7/src/dxs/core/skills/inspection.py +163 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/installer.py +13 -2
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/lock.py +37 -15
- datex_studio_cli-0.5.7/src/dxs/core/skills/protection.py +118 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/404/index.html +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/404.html +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/design/capture/index.html +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/design/capture/index.txt +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/design/index.html +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/design/index.txt +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/index.html +1 -1
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/index.txt +1 -1
- datex_studio_cli-0.5.6/src/dxs/commands/skills.py +0 -442
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.dockerignore +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/actionlint.yaml +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/workflows/build-image.yml +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/workflows/ci.yml +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/workflows/promote-prod.yml +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.github/workflows/release.yml +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/.gitignore +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/CONTEXT.md +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/README.md +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/.env.example +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/Dockerfile +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/README.md +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/apply-local-cache.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/collector-config.yaml +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/docker-compose.yml +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/local-test-settings.json +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/otel/remote-settings.json +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/report-creator.skill +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/analyze_corpus.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/check_wheel_completeness.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/datadog_errors.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/generate_rdlx_schema.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/scripts/wavelength_query.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/__main__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/cli.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/api.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/auth.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/branch.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/branch_commit_guard.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/cascade.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/config.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/configuration.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/crm.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/crm_delivery.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/crm_time.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/datasource.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/devops.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/document.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/endpoint.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/env.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/explore.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/function.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/init.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/marketplace.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/_common.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/api.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/create.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/data.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/pack.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/ng/preview.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/odata.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/organization.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/proxy.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/reference.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/release_notes.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/repo.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/add_cmds.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/api.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/batch.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/create.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/data.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/dataset.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/edit.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/folder.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/preview.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/report/schema_cmds.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/reset.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/schema.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/servicepack.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/source.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/studio.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/telemetry.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/user.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/commands/validation_report.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/context.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/agent_browser.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/app_config.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/client.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/endpoints.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/metadata_models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/reference_graph.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/api/replacements.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/browser_pages.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/datex_tenant.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/decorators.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/msal_client.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/pages/error.html +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/pages/success.html +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/tenants.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/auth/token_cache.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/cache.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/flow_generator.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/flow_models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/flow_shape.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/generator.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/parsers.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/resolver.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/type_def_parser.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/datasource/validator.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/designer/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/designer/flow_models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/designer/models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/designer/parsers.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/devops/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/devops/client.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/devops/helpers.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/devops/models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/client.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/html.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/metadata_cache.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/pagination.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/resolvers.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/dynamics/time_entry.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/capabilities.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/client.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/edmx_parser.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/footprint/metadata.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/function/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/function/generator.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/function/models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/function/validator.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/graph.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/onboarding.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/csv_fmt.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/formatter.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/json_fmt.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/redaction.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/output/yaml_fmt.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/quartermaster/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/quartermaster/client.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/quartermaster/tenants.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/release_notes.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/responses.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/frontmatter.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/layout.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/resolver.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/skills/staleness.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/config.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/identity.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/recorder.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/scrubber.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/telemetry/sender.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/workitem_resolve.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/core/workitems.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/models/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/folder.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/manifest.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/mocks.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/preview.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/scaffold.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/ng/serve_lock.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/py.typed +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/datasource_binding.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/engine.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/field_parser.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/folder.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/manifest.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/models.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/preview.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/preview_arjs.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/schema.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/svg_renderer.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/templates/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/validate_arjs.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/validator.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/report/wrapper.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/click_options.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/clipboard.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/config.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/errors.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/filtering.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/http_errors.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/image.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/paths.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/resolvers.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/responses.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/restricted.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/sorting.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/update_check.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/utils/versioning.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/__init__.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/app.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/design.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/proxy_app.py +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/247-8b4d885f516d109f.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/538-b06224e65f71c9a1.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-e3c660196c244183.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-c083dc9f01f32a04.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/design/page-3e8e7d9d780d48c9.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/layout-cec86f90e385371d.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/app/page-6fafc1af296a566d.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/main-app-694cf3568b6c9fa9.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/_next/static/css/e7143a5b42f30598.css +0 -0
- {datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/lyKgXuwqDbkDi4sPVtE8F → datex_studio_cli-0.5.7/src/dxs/web/static/_next/static/hzAtAMXcc3ZhMB9a3KyPp}/_buildManifest.js +0 -0
- {datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/lyKgXuwqDbkDi4sPVtE8F → datex_studio_cli-0.5.7/src/dxs/web/static/_next/static/hzAtAMXcc3ZhMB9a3KyPp}/_ssgManifest.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/render-cli.js +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/src/dxs/web/static/report-preview.html +0 -0
- {datex_studio_cli-0.5.6 → datex_studio_cli-0.5.7}/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.5.
|
|
3
|
+
Version: 0.5.7
|
|
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
|
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
"""Skill management: dxs skills (issue #95).
|
|
2
|
+
|
|
3
|
+
Replaces ``npx skills add/update`` for Datex skills. Bundles come from the
|
|
4
|
+
Quartermaster companion service, which resolves the running CLI version
|
|
5
|
+
against the skills repo's ``compat.yaml`` and serves the matching ref —
|
|
6
|
+
clients need no GitHub account, npm, or Node. Files land exactly where
|
|
7
|
+
``npx skills`` put them (``~/.agents/skills`` + symlinks into each agent's
|
|
8
|
+
skills directory), so Claude Code and Codex need no configuration.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import shlex
|
|
15
|
+
import subprocess
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
from dxs import __version__
|
|
23
|
+
from dxs.cli import handle_errors
|
|
24
|
+
from dxs.context import DxsContext, pass_context
|
|
25
|
+
from dxs.core.auth import require_auth
|
|
26
|
+
from dxs.core.quartermaster.client import QuartermasterClient
|
|
27
|
+
from dxs.core.skills.discovery import scan_projects
|
|
28
|
+
from dxs.core.skills.frontmatter import installed_depends
|
|
29
|
+
from dxs.core.skills.inspection import (
|
|
30
|
+
DIRECTORY_CONFLICT_GUIDANCE,
|
|
31
|
+
forget_migrated_npx_entries,
|
|
32
|
+
inspect_layout,
|
|
33
|
+
npx_lock_path,
|
|
34
|
+
)
|
|
35
|
+
from dxs.core.skills.installer import install_bundle, remove_skill
|
|
36
|
+
from dxs.core.skills.layout import AGENTS, Layout, layout_for
|
|
37
|
+
from dxs.core.skills.lock import Lock, LockEntry, read_lock, write_lock
|
|
38
|
+
from dxs.core.skills.protection import CheckoutProtection, installation_paths
|
|
39
|
+
from dxs.core.skills.resolver import ResolutionError, closure
|
|
40
|
+
from dxs.utils.errors import DxsError, ValidationError
|
|
41
|
+
from dxs.utils.responses import list_response
|
|
42
|
+
from dxs.utils.restricted import check_restricted_mode_for_option, restrict_in_restricted_mode
|
|
43
|
+
|
|
44
|
+
CATALOG_PATH = "/skills/v1/catalog"
|
|
45
|
+
BUNDLE_PATH = "/skills/v1/bundle"
|
|
46
|
+
MANIFEST_NAME = "MANIFEST.json"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _scope_options(f: Any) -> Any:
|
|
50
|
+
f = click.option(
|
|
51
|
+
"--project",
|
|
52
|
+
is_flag=True,
|
|
53
|
+
help="Use the current directory (.agents/skills + skills-lock.json) instead of ~",
|
|
54
|
+
)(f)
|
|
55
|
+
return f
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _agent_option(f: Any) -> Any:
|
|
59
|
+
return click.option(
|
|
60
|
+
"--agent",
|
|
61
|
+
"agents",
|
|
62
|
+
multiple=True,
|
|
63
|
+
type=click.Choice(list(AGENTS)),
|
|
64
|
+
help="Agent(s) to link the skill into; default: all",
|
|
65
|
+
)(f)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _resolve_agents(agents: tuple[str, ...]) -> tuple[str, ...]:
|
|
69
|
+
return tuple(agents) if agents else tuple(AGENTS)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _fetch_catalog(client: QuartermasterClient) -> dict[str, Any]:
|
|
73
|
+
body = client.get_json(CATALOG_PATH, params={"cli_version": __version__})
|
|
74
|
+
if not isinstance(body, dict) or not isinstance(body.get("skills"), list):
|
|
75
|
+
raise DxsError(
|
|
76
|
+
code="DXS-QM-003",
|
|
77
|
+
message="Quartermaster returned an unexpected catalog shape",
|
|
78
|
+
)
|
|
79
|
+
return body
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _read_manifest(bundle: bytes) -> dict[str, Any]:
|
|
83
|
+
"""Read ``MANIFEST.json`` from the bundle root.
|
|
84
|
+
|
|
85
|
+
It used to arrive as an ``X-Skills-Manifest`` response header, but it grows
|
|
86
|
+
with the catalog -- a path and a 64-char sha256 per skill -- and nginx 502s
|
|
87
|
+
when a response header block overflows a proxy buffer, which capped the
|
|
88
|
+
bundle at 27 skills. Missing or unreadable is not fatal: the skills still
|
|
89
|
+
install, the lock just records no hash, so every one reads as outdated.
|
|
90
|
+
"""
|
|
91
|
+
import io
|
|
92
|
+
import json
|
|
93
|
+
import tarfile
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
with tarfile.open(fileobj=io.BytesIO(bundle), mode="r:gz") as tar:
|
|
97
|
+
member = tar.extractfile(MANIFEST_NAME)
|
|
98
|
+
if member is None:
|
|
99
|
+
return {}
|
|
100
|
+
manifest = json.loads(member.read())
|
|
101
|
+
except (tarfile.TarError, KeyError, ValueError, OSError):
|
|
102
|
+
return {}
|
|
103
|
+
return manifest if isinstance(manifest, dict) else {}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _fetch_bundle(client: QuartermasterClient, names: list[str]) -> tuple[bytes, dict[str, Any]]:
|
|
107
|
+
body, headers = client.get_bytes(
|
|
108
|
+
BUNDLE_PATH, params={"cli_version": __version__, "skill": names}
|
|
109
|
+
)
|
|
110
|
+
return body, {
|
|
111
|
+
"ref": headers.get("x-skills-ref", ""),
|
|
112
|
+
"commit": headers.get("x-skills-commit", ""),
|
|
113
|
+
"manifest": _read_manifest(body),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _install(
|
|
118
|
+
ctx: DxsContext,
|
|
119
|
+
client: QuartermasterClient,
|
|
120
|
+
layout: Layout,
|
|
121
|
+
names: list[str],
|
|
122
|
+
agents: tuple[str, ...],
|
|
123
|
+
*,
|
|
124
|
+
force: bool,
|
|
125
|
+
status: str,
|
|
126
|
+
) -> list[dict[str, Any]]:
|
|
127
|
+
"""Download ``names``, install them, record the lock, and report per skill."""
|
|
128
|
+
protection = CheckoutProtection()
|
|
129
|
+
protection.assert_writable(
|
|
130
|
+
[
|
|
131
|
+
npx_lock_path(layout),
|
|
132
|
+
*(path for name in names for path in installation_paths(layout, name)),
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
lock = read_lock(layout.lock_path, strict=True)
|
|
136
|
+
read_lock(npx_lock_path(layout), strict=True)
|
|
137
|
+
owned_copies = {
|
|
138
|
+
name: tuple(a for a in entry.agents if a not in entry.conflicts)
|
|
139
|
+
for name, entry in lock.skills.items()
|
|
140
|
+
if entry.link_mode == "copy"
|
|
141
|
+
}
|
|
142
|
+
bundle, meta = _fetch_bundle(client, names)
|
|
143
|
+
results = install_bundle(bundle, layout, agents, force=force, owned_copies=owned_copies)
|
|
144
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
145
|
+
report: list[dict[str, Any]] = []
|
|
146
|
+
conflicts: dict[str, tuple[str, ...]] = {}
|
|
147
|
+
for result in results:
|
|
148
|
+
info = meta["manifest"].get(result.name, {})
|
|
149
|
+
linked = tuple(a for a in agents if a not in result.conflicts)
|
|
150
|
+
previous = lock.skills.get(result.name)
|
|
151
|
+
unresolved = tuple(a for a in previous.conflicts if a not in agents) if previous else ()
|
|
152
|
+
lock.skills[result.name] = LockEntry(
|
|
153
|
+
name=result.name,
|
|
154
|
+
skill_path=f"{info.get('path', '')}/SKILL.md".lstrip("/"),
|
|
155
|
+
computed_hash=str(info.get("hash", "")),
|
|
156
|
+
ref=str(meta["ref"]),
|
|
157
|
+
commit=str(meta["commit"]),
|
|
158
|
+
cli_version=__version__,
|
|
159
|
+
installed_at=now,
|
|
160
|
+
agents=tuple(dict.fromkeys((*lock.skills[result.name].agents, *agents)))
|
|
161
|
+
if result.name in lock.skills
|
|
162
|
+
else agents,
|
|
163
|
+
conflicts=(*unresolved, *result.conflicts),
|
|
164
|
+
link_mode=result.link_mode,
|
|
165
|
+
)
|
|
166
|
+
if result.conflicts:
|
|
167
|
+
conflicts[result.name] = result.conflicts
|
|
168
|
+
report.append(
|
|
169
|
+
{
|
|
170
|
+
"name": result.name,
|
|
171
|
+
"status": status,
|
|
172
|
+
"ref": meta["ref"],
|
|
173
|
+
"commit": meta["commit"],
|
|
174
|
+
"agents": list(linked),
|
|
175
|
+
"link_mode": result.link_mode,
|
|
176
|
+
"conflicts": list(result.conflicts),
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
write_lock(layout.lock_path, lock)
|
|
180
|
+
ctx.log(f"Installed {len(results)} skill(s) into {layout.canonical}")
|
|
181
|
+
forget_migrated_npx_entries(
|
|
182
|
+
layout, [r.name for r in results if not lock.skills[r.name].conflicts]
|
|
183
|
+
)
|
|
184
|
+
if conflicts:
|
|
185
|
+
lines = [
|
|
186
|
+
f"{name}: {', '.join(layout.agent_dirs[a].as_posix() + '/' + name for a in ags)}"
|
|
187
|
+
for name, ags in conflicts.items()
|
|
188
|
+
]
|
|
189
|
+
raise DxsError(
|
|
190
|
+
code="DXS-SKILLS-003",
|
|
191
|
+
message=(
|
|
192
|
+
"Installed, but could not link into agent directories that already hold a real "
|
|
193
|
+
"directory of the same name: " + "; ".join(lines)
|
|
194
|
+
),
|
|
195
|
+
details={"conflicts": {n: list(a) for n, a in conflicts.items()}},
|
|
196
|
+
suggestions=[
|
|
197
|
+
DIRECTORY_CONFLICT_GUIDANCE,
|
|
198
|
+
],
|
|
199
|
+
)
|
|
200
|
+
return report
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _lock_or_empty_error(layout: Layout) -> Lock:
|
|
204
|
+
lock = read_lock(layout.lock_path)
|
|
205
|
+
if not lock.skills:
|
|
206
|
+
raise DxsError(
|
|
207
|
+
code="DXS-SKILLS-002",
|
|
208
|
+
message=f"No dxs-managed skills are installed ({layout.scope} scope)",
|
|
209
|
+
suggestions=[
|
|
210
|
+
"Run 'dxs skills add --all' to install every Datex skill",
|
|
211
|
+
"Run 'dxs skills list --available' to see what can be installed",
|
|
212
|
+
],
|
|
213
|
+
)
|
|
214
|
+
return lock
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@click.group()
|
|
218
|
+
def skills() -> None:
|
|
219
|
+
"""Manage Datex agent skills (Claude Code, Codex) via Quartermaster."""
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@skills.command("add")
|
|
223
|
+
@click.argument("names", nargs=-1)
|
|
224
|
+
@click.option("--all", "install_all", is_flag=True, help="Install every skill in the catalog")
|
|
225
|
+
@_scope_options
|
|
226
|
+
@_agent_option
|
|
227
|
+
@click.option("--force", is_flag=True, help="Replace real directories that block linking")
|
|
228
|
+
@pass_context
|
|
229
|
+
@handle_errors
|
|
230
|
+
@require_auth
|
|
231
|
+
@restrict_in_restricted_mode("writes skill files into agent directories")
|
|
232
|
+
def add_cmd(
|
|
233
|
+
ctx: DxsContext,
|
|
234
|
+
names: tuple[str, ...],
|
|
235
|
+
install_all: bool,
|
|
236
|
+
project: bool,
|
|
237
|
+
agents: tuple[str, ...],
|
|
238
|
+
force: bool,
|
|
239
|
+
) -> None:
|
|
240
|
+
"""Install skills (and their dependencies) for this CLI version.
|
|
241
|
+
|
|
242
|
+
Examples:
|
|
243
|
+
dxs skills add report-creator
|
|
244
|
+
dxs skills add --all
|
|
245
|
+
dxs skills add report-creator --project --agent claude
|
|
246
|
+
"""
|
|
247
|
+
if not names and not install_all:
|
|
248
|
+
raise ValidationError(
|
|
249
|
+
message="Name at least one skill, or pass --all",
|
|
250
|
+
code="DXS-SKILLS-005",
|
|
251
|
+
suggestions=["dxs skills list --available", "dxs skills add --all"],
|
|
252
|
+
)
|
|
253
|
+
layout = layout_for(project)
|
|
254
|
+
with QuartermasterClient() as client:
|
|
255
|
+
catalog = _fetch_catalog(client)
|
|
256
|
+
requested = [s["name"] for s in catalog["skills"]] if install_all else list(names)
|
|
257
|
+
try:
|
|
258
|
+
ordered = closure(requested, catalog["skills"])
|
|
259
|
+
except ResolutionError as exc:
|
|
260
|
+
raise DxsError(
|
|
261
|
+
code="DXS-SKILLS-001",
|
|
262
|
+
message=str(exc),
|
|
263
|
+
suggestions=["Run 'dxs skills list --available' to see the catalog"],
|
|
264
|
+
) from exc
|
|
265
|
+
report = _install(
|
|
266
|
+
ctx, client, layout, ordered, _resolve_agents(agents), force=force, status="installed"
|
|
267
|
+
)
|
|
268
|
+
ctx.output(
|
|
269
|
+
list_response(
|
|
270
|
+
report, "skills", scope=layout.scope, ref=catalog.get("ref"), line=catalog.get("line")
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@skills.command("update")
|
|
276
|
+
@click.argument("names", nargs=-1)
|
|
277
|
+
@_scope_options
|
|
278
|
+
@click.option("--force", is_flag=True, help="Replace real directories that block linking")
|
|
279
|
+
@pass_context
|
|
280
|
+
@handle_errors
|
|
281
|
+
@require_auth
|
|
282
|
+
@restrict_in_restricted_mode("rewrites skill files in agent directories")
|
|
283
|
+
def update_cmd(ctx: DxsContext, names: tuple[str, ...], project: bool, force: bool) -> None:
|
|
284
|
+
"""Refresh installed skills to what this CLI version expects.
|
|
285
|
+
|
|
286
|
+
Re-downloads a skill when its content changed upstream or when it was
|
|
287
|
+
installed against a different dxs version, or its files/links need repair.
|
|
288
|
+
Legacy Datex npx installations are adopted; retired skills are reported, not removed.
|
|
289
|
+
"""
|
|
290
|
+
layout = layout_for(project)
|
|
291
|
+
before = {row["name"]: row for row in inspect_layout(layout)}
|
|
292
|
+
eligible = {name: row for name, row in before.items() if row["status"] != "unmanaged"}
|
|
293
|
+
if not eligible:
|
|
294
|
+
_lock_or_empty_error(layout)
|
|
295
|
+
targets = list(names) if names else sorted(eligible)
|
|
296
|
+
unknown = [n for n in targets if n not in eligible]
|
|
297
|
+
if unknown:
|
|
298
|
+
raise DxsError(
|
|
299
|
+
code="DXS-SKILLS-001",
|
|
300
|
+
message=f"not installed by dxs or npx Datex skills: {', '.join(unknown)}",
|
|
301
|
+
suggestions=["Run 'dxs skills doctor' to inspect installed skills"],
|
|
302
|
+
)
|
|
303
|
+
if all(before[name]["status"] == "protected" for name in targets):
|
|
304
|
+
ctx.output(list_response([before[name] for name in targets], "skills", scope=layout.scope))
|
|
305
|
+
return
|
|
306
|
+
with QuartermasterClient() as client:
|
|
307
|
+
catalog = _fetch_catalog(client)
|
|
308
|
+
by_name = {s["name"]: s for s in catalog["skills"]}
|
|
309
|
+
lock = read_lock(layout.lock_path)
|
|
310
|
+
changed = [
|
|
311
|
+
name
|
|
312
|
+
for name in targets
|
|
313
|
+
if before[name]["status"] != "protected"
|
|
314
|
+
and (
|
|
315
|
+
name not in by_name
|
|
316
|
+
or before[name]["status"] != "ok"
|
|
317
|
+
or lock.skills[name].computed_hash != by_name[name]["hash"]
|
|
318
|
+
)
|
|
319
|
+
]
|
|
320
|
+
actions, retired, protected = _refresh_layout(
|
|
321
|
+
ctx, client, catalog, layout, before, changed, force=force
|
|
322
|
+
)
|
|
323
|
+
after = {row["name"]: row for row in inspect_layout(layout)}
|
|
324
|
+
report = []
|
|
325
|
+
for name in sorted(set(targets) | set(actions)):
|
|
326
|
+
row = after[name]
|
|
327
|
+
if name in protected:
|
|
328
|
+
row["status"] = "protected"
|
|
329
|
+
row["problems"] = [protected[name]]
|
|
330
|
+
row["fix"] = None
|
|
331
|
+
elif row["status"] == "protected":
|
|
332
|
+
pass
|
|
333
|
+
elif name in retired:
|
|
334
|
+
row["status"] = "retired"
|
|
335
|
+
elif name in actions and row["status"] == "ok":
|
|
336
|
+
row["status"] = (
|
|
337
|
+
"migrated" if before.get(name, {}).get("status") == "legacy" else "updated"
|
|
338
|
+
)
|
|
339
|
+
elif name not in actions:
|
|
340
|
+
row["status"] = "current"
|
|
341
|
+
report.append(row)
|
|
342
|
+
ctx.output(
|
|
343
|
+
list_response(
|
|
344
|
+
report, "skills", scope=layout.scope, ref=catalog.get("ref"), line=catalog.get("line")
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
if any(row["status"] == "broken" for row in report):
|
|
348
|
+
raise SystemExit(1)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@skills.command("list")
|
|
352
|
+
@click.option("--available", is_flag=True, help="Also list the catalog for this CLI version")
|
|
353
|
+
@_scope_options
|
|
354
|
+
@pass_context
|
|
355
|
+
@handle_errors
|
|
356
|
+
def list_cmd(ctx: DxsContext, available: bool, project: bool) -> None:
|
|
357
|
+
"""List dxs-managed skills (offline), or the whole catalog with --available."""
|
|
358
|
+
layout = layout_for(project)
|
|
359
|
+
lock = read_lock(layout.lock_path)
|
|
360
|
+
if not available:
|
|
361
|
+
installed: list[dict[str, Any]] = [
|
|
362
|
+
{
|
|
363
|
+
"name": e.name,
|
|
364
|
+
"scope": layout.scope,
|
|
365
|
+
"cli_version": e.cli_version,
|
|
366
|
+
"ref": e.ref,
|
|
367
|
+
"commit": e.commit,
|
|
368
|
+
"agents": list(e.agents),
|
|
369
|
+
"link_mode": e.link_mode,
|
|
370
|
+
"installed_at": e.installed_at,
|
|
371
|
+
}
|
|
372
|
+
for _, e in sorted(lock.skills.items())
|
|
373
|
+
]
|
|
374
|
+
ctx.output(list_response(installed, "skills", scope=layout.scope))
|
|
375
|
+
return
|
|
376
|
+
with QuartermasterClient() as client:
|
|
377
|
+
catalog = _fetch_catalog(client)
|
|
378
|
+
items: list[dict[str, Any]] = []
|
|
379
|
+
for skill in catalog["skills"]:
|
|
380
|
+
entry = lock.skills.get(skill["name"])
|
|
381
|
+
if entry is None:
|
|
382
|
+
status = "available"
|
|
383
|
+
elif entry.computed_hash != skill["hash"] or entry.cli_version != __version__:
|
|
384
|
+
status = "outdated"
|
|
385
|
+
else:
|
|
386
|
+
status = "current"
|
|
387
|
+
items.append(
|
|
388
|
+
{
|
|
389
|
+
"name": skill["name"],
|
|
390
|
+
"description": skill.get("description", ""),
|
|
391
|
+
"depends": list(skill.get("depends", [])),
|
|
392
|
+
"installed": entry is not None,
|
|
393
|
+
"status": status,
|
|
394
|
+
}
|
|
395
|
+
)
|
|
396
|
+
ctx.output(
|
|
397
|
+
list_response(
|
|
398
|
+
items, "skills", scope=layout.scope, ref=catalog.get("ref"), line=catalog.get("line")
|
|
399
|
+
)
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@skills.command("remove")
|
|
404
|
+
@click.argument("names", nargs=-1, required=True)
|
|
405
|
+
@_scope_options
|
|
406
|
+
@pass_context
|
|
407
|
+
@handle_errors
|
|
408
|
+
@restrict_in_restricted_mode("deletes skill files from agent directories")
|
|
409
|
+
def remove_cmd(ctx: DxsContext, names: tuple[str, ...], project: bool) -> None:
|
|
410
|
+
"""Remove dxs-managed skills. Dependents that remain are reported."""
|
|
411
|
+
layout = layout_for(project)
|
|
412
|
+
lock = read_lock(layout.lock_path)
|
|
413
|
+
unknown = [n for n in names if n not in lock.skills]
|
|
414
|
+
if unknown:
|
|
415
|
+
raise DxsError(
|
|
416
|
+
code="DXS-SKILLS-001",
|
|
417
|
+
message=f"not installed by dxs: {', '.join(unknown)}",
|
|
418
|
+
suggestions=["Run 'dxs skills list' to see what dxs manages"],
|
|
419
|
+
)
|
|
420
|
+
report = []
|
|
421
|
+
for name in names:
|
|
422
|
+
entry = lock.skills[name]
|
|
423
|
+
dependents = sorted(
|
|
424
|
+
other
|
|
425
|
+
for other in lock.skills
|
|
426
|
+
if other not in names and name in installed_depends(layout.canonical / other)
|
|
427
|
+
)
|
|
428
|
+
installed_agents = tuple(a for a in entry.agents if a not in entry.conflicts)
|
|
429
|
+
removed = remove_skill(name, layout, installed_agents, entry.link_mode)
|
|
430
|
+
del lock.skills[name]
|
|
431
|
+
if dependents:
|
|
432
|
+
ctx.log(f"Warning: {name} is still required by {', '.join(dependents)}")
|
|
433
|
+
report.append({"name": name, "removed": removed, "dependents": dependents})
|
|
434
|
+
write_lock(layout.lock_path, lock)
|
|
435
|
+
ctx.output(list_response(report, "skills", scope=layout.scope))
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _refresh_layout(
|
|
439
|
+
ctx: DxsContext,
|
|
440
|
+
client: QuartermasterClient,
|
|
441
|
+
catalog: dict[str, Any],
|
|
442
|
+
layout: Layout,
|
|
443
|
+
before: dict[str, dict[str, Any]],
|
|
444
|
+
targets: list[str],
|
|
445
|
+
*,
|
|
446
|
+
force: bool,
|
|
447
|
+
) -> tuple[set[str], set[str], dict[str, str]]:
|
|
448
|
+
"""Refresh dependency closures without broadening unrelated agent placements."""
|
|
449
|
+
by_name = {s["name"]: s for s in catalog["skills"]}
|
|
450
|
+
lock = read_lock(layout.lock_path)
|
|
451
|
+
retired = {name for name in targets if name not in by_name}
|
|
452
|
+
placements: dict[str, tuple[str, ...]] = {}
|
|
453
|
+
protected: dict[str, str] = {}
|
|
454
|
+
for name in targets:
|
|
455
|
+
if name in retired:
|
|
456
|
+
continue
|
|
457
|
+
dependencies = closure([name], catalog["skills"])
|
|
458
|
+
protected_dependencies = [
|
|
459
|
+
n for n in dependencies if before.get(n, {}).get("status") == "protected"
|
|
460
|
+
]
|
|
461
|
+
if protected_dependencies:
|
|
462
|
+
protected[name] = (
|
|
463
|
+
"Skipped: dependency belongs to a protected contributor checkout: "
|
|
464
|
+
+ ", ".join(protected_dependencies)
|
|
465
|
+
)
|
|
466
|
+
continue
|
|
467
|
+
for dependency in dependencies:
|
|
468
|
+
existing = before.get(dependency)
|
|
469
|
+
if existing and existing["status"] == "unmanaged":
|
|
470
|
+
raise DxsError(
|
|
471
|
+
code="DXS-SKILLS-007",
|
|
472
|
+
message=f"Cannot replace unmanaged dependency {dependency} in {layout.root}",
|
|
473
|
+
)
|
|
474
|
+
agents = tuple(before[name]["agents"])
|
|
475
|
+
previous = placements.get(dependency, tuple(existing["agents"]) if existing else ())
|
|
476
|
+
placements[dependency] = tuple(dict.fromkeys((*previous, *agents)))
|
|
477
|
+
groups: dict[tuple[str, ...], list[str]] = {}
|
|
478
|
+
for name, agents in placements.items():
|
|
479
|
+
entry = lock.skills.get(name)
|
|
480
|
+
if (
|
|
481
|
+
name not in targets
|
|
482
|
+
and entry
|
|
483
|
+
and before[name]["status"] == "ok"
|
|
484
|
+
and entry.computed_hash == by_name[name]["hash"]
|
|
485
|
+
and set(agents) == set(entry.agents)
|
|
486
|
+
):
|
|
487
|
+
continue
|
|
488
|
+
if any(agent not in AGENTS for agent in agents):
|
|
489
|
+
raise DxsError(code="DXS-SKILLS-007", message=f"Unsupported agent placement for {name}")
|
|
490
|
+
groups.setdefault(agents, []).append(name)
|
|
491
|
+
attempted: set[str] = set()
|
|
492
|
+
for agents, names in groups.items():
|
|
493
|
+
try:
|
|
494
|
+
_install(ctx, client, layout, names, agents, force=force, status="updated")
|
|
495
|
+
except DxsError as exc:
|
|
496
|
+
if exc.code != "DXS-SKILLS-003":
|
|
497
|
+
raise
|
|
498
|
+
# Partial installs are recorded; the caller reports their health.
|
|
499
|
+
attempted.update(names)
|
|
500
|
+
return attempted, retired, protected
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@require_auth
|
|
504
|
+
def _repair_layouts(ctx: DxsContext, layouts: list[Layout], *, force: bool) -> list[dict[str, Any]]:
|
|
505
|
+
reports: list[dict[str, Any]] = []
|
|
506
|
+
with QuartermasterClient() as client:
|
|
507
|
+
catalog = _fetch_catalog(client)
|
|
508
|
+
for layout in layouts:
|
|
509
|
+
before = {row["name"]: row for row in inspect_layout(layout)}
|
|
510
|
+
targets = [
|
|
511
|
+
name
|
|
512
|
+
for name, row in before.items()
|
|
513
|
+
if row["status"] in ("broken", "stale", "legacy")
|
|
514
|
+
]
|
|
515
|
+
actions, retired, protected = _refresh_layout(
|
|
516
|
+
ctx, client, catalog, layout, before, targets, force=force
|
|
517
|
+
)
|
|
518
|
+
for row in inspect_layout(layout):
|
|
519
|
+
if row["name"] in protected:
|
|
520
|
+
row["status"] = "protected"
|
|
521
|
+
row["problems"] = [protected[row["name"]]]
|
|
522
|
+
row["fix"] = None
|
|
523
|
+
elif row["name"] in retired:
|
|
524
|
+
row["status"] = "retired"
|
|
525
|
+
row["problems"].append(
|
|
526
|
+
"No longer available in the catalog for this CLI version"
|
|
527
|
+
)
|
|
528
|
+
row["fix"] = None
|
|
529
|
+
elif row["name"] in actions and row["status"] == "ok":
|
|
530
|
+
row["action"] = (
|
|
531
|
+
"migrated"
|
|
532
|
+
if before.get(row["name"], {}).get("status") == "legacy"
|
|
533
|
+
else "repaired"
|
|
534
|
+
)
|
|
535
|
+
reports.append(row)
|
|
536
|
+
return reports
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
@skills.command("doctor")
|
|
540
|
+
@click.option("--project", is_flag=True, help="Inspect only current-project skills")
|
|
541
|
+
@click.option("--user", "user_only", is_flag=True, help="Inspect only user skills")
|
|
542
|
+
@click.option(
|
|
543
|
+
"--scan",
|
|
544
|
+
"scan_roots",
|
|
545
|
+
multiple=True,
|
|
546
|
+
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
|
547
|
+
help="Search this workspace for Datex project installs (repeatable; replaces default scopes)",
|
|
548
|
+
)
|
|
549
|
+
@click.option(
|
|
550
|
+
"--max-depth",
|
|
551
|
+
default=4,
|
|
552
|
+
show_default=True,
|
|
553
|
+
type=click.IntRange(0, 64),
|
|
554
|
+
help="Scan depth below each root; zero inspects only the root",
|
|
555
|
+
)
|
|
556
|
+
@click.option(
|
|
557
|
+
"--max-directories",
|
|
558
|
+
default=10000,
|
|
559
|
+
show_default=True,
|
|
560
|
+
type=click.IntRange(min=1),
|
|
561
|
+
help="Maximum directories visited across scan roots",
|
|
562
|
+
)
|
|
563
|
+
@click.option("--fix", is_flag=True, help="Migrate Datex skills and repair managed installations")
|
|
564
|
+
@click.option("--force", is_flag=True, help="With --fix, replace real directories blocking links")
|
|
565
|
+
@pass_context
|
|
566
|
+
@handle_errors
|
|
567
|
+
def doctor_cmd(
|
|
568
|
+
ctx: DxsContext,
|
|
569
|
+
project: bool,
|
|
570
|
+
user_only: bool,
|
|
571
|
+
scan_roots: tuple[Path, ...],
|
|
572
|
+
max_depth: int,
|
|
573
|
+
max_directories: int,
|
|
574
|
+
fix: bool,
|
|
575
|
+
force: bool,
|
|
576
|
+
) -> None:
|
|
577
|
+
"""Inspect user/current-project skills and scan the parent directory for other projects.
|
|
578
|
+
|
|
579
|
+
Scans are bounded by --max-depth and --max-directories. Use --fix to repair
|
|
580
|
+
all reported Datex installations, or --project/--user to limit the scope.
|
|
581
|
+
"""
|
|
582
|
+
if force and not fix:
|
|
583
|
+
raise click.UsageError("--force requires --fix")
|
|
584
|
+
if fix:
|
|
585
|
+
check_restricted_mode_for_option("--fix", "rewrites skill files and installer records")
|
|
586
|
+
if sum((project, user_only, bool(scan_roots))) > 1:
|
|
587
|
+
raise click.UsageError("Choose only one of --project, --user, or --scan")
|
|
588
|
+
protection = CheckoutProtection()
|
|
589
|
+
default_scan = not (project or user_only or scan_roots)
|
|
590
|
+
roots = (Path.cwd().parent,) if default_scan else scan_roots
|
|
591
|
+
scan = (
|
|
592
|
+
scan_projects(
|
|
593
|
+
roots, max_depth=max_depth, max_directories=max_directories, protection=protection
|
|
594
|
+
)
|
|
595
|
+
if roots
|
|
596
|
+
else None
|
|
597
|
+
)
|
|
598
|
+
if default_scan:
|
|
599
|
+
# Always inspect the user and current project, even when their files are
|
|
600
|
+
# untracked or a scan limit prevents discovery. Prefer user scope if the
|
|
601
|
+
# home directory also appears among discovered project roots.
|
|
602
|
+
layouts = [layout_for(False), layout_for(True), *(scan.layouts if scan else [])]
|
|
603
|
+
by_root: dict[Path, Layout] = {}
|
|
604
|
+
for layout in layouts:
|
|
605
|
+
by_root.setdefault(layout.root.resolve(), layout)
|
|
606
|
+
layouts = list(by_root.values())
|
|
607
|
+
elif scan is not None:
|
|
608
|
+
layouts = scan.layouts
|
|
609
|
+
else:
|
|
610
|
+
layouts = [layout_for(project)]
|
|
611
|
+
report: list[dict[str, Any]] = []
|
|
612
|
+
errors: list[str] = []
|
|
613
|
+
valid_layouts: list[Layout] = []
|
|
614
|
+
for layout in layouts:
|
|
615
|
+
try:
|
|
616
|
+
report.extend(inspect_layout(layout, protection))
|
|
617
|
+
valid_layouts.append(layout)
|
|
618
|
+
except (DxsError, OSError) as exc:
|
|
619
|
+
errors.append(f"{layout.scope} ({layout.root}): {exc}")
|
|
620
|
+
if fix and any(row["status"] in ("broken", "stale", "legacy") for row in report):
|
|
621
|
+
report = _repair_layouts(ctx, valid_layouts, force=force)
|
|
622
|
+
if scan is not None:
|
|
623
|
+
for row in report:
|
|
624
|
+
if row["fix"] and row["scope"] == "project":
|
|
625
|
+
quoted_root = (
|
|
626
|
+
subprocess.list2cmdline([row["root"]])
|
|
627
|
+
if os.name == "nt"
|
|
628
|
+
else shlex.quote(row["root"])
|
|
629
|
+
)
|
|
630
|
+
row["fix"] = f"dxs skills doctor --scan {quoted_root} --max-depth 0 --fix" + (
|
|
631
|
+
" --force" if row["status"] == "broken" else ""
|
|
632
|
+
)
|
|
633
|
+
metadata: dict[str, Any] = {
|
|
634
|
+
"scope": "all" if default_scan else "scan" if scan else "project" if project else "user",
|
|
635
|
+
"cli_version": __version__,
|
|
636
|
+
}
|
|
637
|
+
if scan is not None:
|
|
638
|
+
metadata["scan"] = {
|
|
639
|
+
"roots": [str(p.resolve()) for p in roots],
|
|
640
|
+
"default": default_scan,
|
|
641
|
+
"visited": scan.visited,
|
|
642
|
+
"depth_limited": scan.depth_limited,
|
|
643
|
+
"limit_reached": scan.limit_reached,
|
|
644
|
+
"errors": scan.errors,
|
|
645
|
+
"max_depth": max_depth,
|
|
646
|
+
"max_directories": max_directories,
|
|
647
|
+
}
|
|
648
|
+
metadata["protected_checkouts"] = sorted(str(p) for p in protection.protected)
|
|
649
|
+
metadata["errors"] = errors
|
|
650
|
+
ctx.output(list_response(report, "skills", **metadata))
|
|
651
|
+
if errors:
|
|
652
|
+
raise SystemExit(1)
|
|
653
|
+
if scan is not None and (scan.limit_reached or scan.errors):
|
|
654
|
+
raise SystemExit(1)
|
|
655
|
+
if any(row["status"] not in ("ok", "unmanaged", "protected") for row in report):
|
|
656
|
+
raise SystemExit(1)
|