mammoth-cli 2.0.29__tar.gz → 2.0.30__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.
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/PKG-INFO +1 -1
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/__init__.py +1 -1
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/app.py +34 -1
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +3 -1
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +2 -2
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +2 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +8 -0
- mammoth_cli-2.0.30/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +66 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/view.py +7 -3
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/view_ops.py +61 -1
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/confirm.py +4 -0
- mammoth_cli-2.0.30/mammoth_cli/runtime/dryrun.py +133 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/invocation.py +32 -1
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/session.py +7 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/command_contract.py +10 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/input_fields.py +12 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/sdk_service.py +12 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/testing.py +11 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/pyproject.toml +1 -1
- mammoth_cli-2.0.29/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -33
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/.gitignore +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/LICENSE +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/README.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/__main__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/activity.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/addon.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/agent.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/ai.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/annotation.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/auth.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/automation.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/batch.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/billing.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/browse.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/capability.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/client_app.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/completion.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/config.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/connector.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/context.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/dashboard.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/data_app.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/dataset.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/doctor.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/external_key.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/file.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/folder.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/job.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/notification.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/parameter.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/project.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/registry.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/report.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/schedule.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/schema.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/skill.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/snippet.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/support.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/template.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/trash.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/upgrade.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/user.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/webhook.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/workflow.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/commands/workspace.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/context/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/context/credentials.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/context/endpoint.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/context/profiles.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/context/resolver.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/contracts/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/contracts/auth.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/errors/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/errors/envelope.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/manifest/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/manifest/loader.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/messages/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/output/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/output/envelope.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/output/normalize.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/output/policy.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/output/progress.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/output/render.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/py.typed +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/executor.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/input_loader.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/options.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/parents.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/runlog.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/strict.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/updates.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/runtime/validate.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/argspec.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/coerce.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/conditions.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/dispatch.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/factory.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/mapping.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/openapi_types.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/positionals.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/protocol.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/services/type_system.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/skills/__init__.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/skills/installer.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/skills/steering.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/testing.py +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/activity.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/addon.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/agent.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/ai.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/annotation.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/auth.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/automation.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/batch.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/billing.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/browse.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/capability.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/client-app.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/completion.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/config.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/connector.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/context.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/dashboard.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/data-app.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/dataset.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/doctor.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/external-key.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/file.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/folder.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/job.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/log.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/notification.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/parameter.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/project.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/report.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/schedule.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/schema.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/skill.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/snippet.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/support.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/template.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/trash.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/upgrade.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/user.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/version.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/view.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/webhook.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/workflow.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/commands/workspace.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/openapi-operations.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/schema-v1.json +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/sdk-catalog.source.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/manifests/sdk-methods.yaml +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/openapi/metadata.json +0 -0
- {mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/spec/openapi/openapi.json +0 -0
|
@@ -29,7 +29,7 @@ from typer.core import TyperGroup
|
|
|
29
29
|
|
|
30
30
|
from mammoth_cli import __version__
|
|
31
31
|
from mammoth_cli.commands import BESPOKE
|
|
32
|
-
from mammoth_cli.commands.registry import HANDLERS
|
|
32
|
+
from mammoth_cli.commands.registry import HANDLERS, Handler
|
|
33
33
|
from mammoth_cli.context import profiles
|
|
34
34
|
from mammoth_cli.errors.envelope import EXIT_USAGE, CliError, not_implemented_error
|
|
35
35
|
from mammoth_cli.manifest.loader import command_by_id, load_commands
|
|
@@ -629,6 +629,18 @@ def _shared_option_params() -> list[inspect.Parameter]:
|
|
|
629
629
|
),
|
|
630
630
|
],
|
|
631
631
|
),
|
|
632
|
+
opt(
|
|
633
|
+
"dry_run",
|
|
634
|
+
False,
|
|
635
|
+
Annotated[
|
|
636
|
+
bool,
|
|
637
|
+
typer.Option(
|
|
638
|
+
"--dry-run",
|
|
639
|
+
help="Resolve and validate, then report the request instead of sending it.",
|
|
640
|
+
rich_help_panel="Safety",
|
|
641
|
+
),
|
|
642
|
+
],
|
|
643
|
+
),
|
|
632
644
|
opt(
|
|
633
645
|
"input_file",
|
|
634
646
|
None,
|
|
@@ -777,6 +789,8 @@ def _execute(invocation: Invocation) -> None:
|
|
|
777
789
|
record = command_by_id(invocation.command_id)
|
|
778
790
|
sdk_symbol = record.get("sdk_symbol", "") if record else ""
|
|
779
791
|
raise not_implemented_error(invocation.command_id, sdk_symbol)
|
|
792
|
+
if invocation.dry_run:
|
|
793
|
+
return _dry_run(handler, invocation)
|
|
780
794
|
return handler(invocation)
|
|
781
795
|
|
|
782
796
|
executor.run(
|
|
@@ -789,6 +803,25 @@ def _execute(invocation: Invocation) -> None:
|
|
|
789
803
|
)
|
|
790
804
|
|
|
791
805
|
|
|
806
|
+
def _dry_run(handler: Handler, invocation: Invocation) -> tuple[Any, dict[str, Any]]:
|
|
807
|
+
"""Run ``handler`` until its request would leave; report that request.
|
|
808
|
+
|
|
809
|
+
Local commands never open a service, so a dry run of one is the real run.
|
|
810
|
+
An API-backed handler that returns normally under ``--dry-run`` made no
|
|
811
|
+
gated SDK call at all (a pure read path); its result is returned as is.
|
|
812
|
+
"""
|
|
813
|
+
from mammoth_cli.runtime.dryrun import DryRunStop
|
|
814
|
+
from mammoth_cli.runtime.session import resolved_project
|
|
815
|
+
|
|
816
|
+
try:
|
|
817
|
+
return handler(invocation)
|
|
818
|
+
except DryRunStop as stop:
|
|
819
|
+
return stop.record, {
|
|
820
|
+
"profile": invocation.profile,
|
|
821
|
+
"project_id": resolved_project(invocation),
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
|
|
792
825
|
def _command_help(command_id: str, record: dict[str, Any] | None) -> str | None:
|
|
793
826
|
"""Build a command's user-facing ``--help`` summary.
|
|
794
827
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mammoth-cli
|
|
3
|
-
version: 2.0.
|
|
3
|
+
version: 2.0.30
|
|
4
4
|
description: "Use Mammoth Analytics from a terminal: install or authenticate the CLI, discover its live command contract, and safely manage projects, data, views, pipelines, dashboards, exports, and handoffs."
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -60,6 +60,8 @@ before the next step.
|
|
|
60
60
|
'{"full": true}'` returns the dependency and display trees too.
|
|
61
61
|
- Column inputs and expressions use the exact **display names** the view
|
|
62
62
|
returns, never backend aliases.
|
|
63
|
+
- `--dry-run` on any API-backed command resolves everything and reports the
|
|
64
|
+
request instead of sending it; use it before a write you are not sure of.
|
|
63
65
|
- Destructive commands need `--yes --confirm ID`. Preserve requested
|
|
64
66
|
deliverables; cleanup is exact-ID authorized and
|
|
65
67
|
never means delete-all-owned-resources: delete only ids this run created,
|
|
@@ -932,7 +932,7 @@ Example: `mammoth view transform extract-date 123 --input '{"column": "Status",
|
|
|
932
932
|
|
|
933
933
|
Result: `ViewTransformExtractDateResult`; mutation `reversible_pipeline`, confirmation `none`, wait policy `always_wait`.
|
|
934
934
|
|
|
935
|
-
Status on release:
|
|
935
|
+
Status on release: ran once on CLI 2.0.18 through `view.task.add` and was read back with `view data get`; other inputs for this transform are untried.
|
|
936
936
|
|
|
937
937
|
### `view.transform.fill-missing`
|
|
938
938
|
|
|
@@ -1112,7 +1112,7 @@ Example: `mammoth view transform window 123 --input '{"function": "ROW_NUMBER",
|
|
|
1112
1112
|
|
|
1113
1113
|
Result: `ViewTransformWindowResult`; mutation `reversible_pipeline`, confirmation `none`, wait policy `always_wait`.
|
|
1114
1114
|
|
|
1115
|
-
Status on release:
|
|
1115
|
+
Status on release: ran once on CLI 2.0.18 through `view.task.add` and was read back with `view data get`; other inputs for this transform are untried.
|
|
1116
1116
|
|
|
1117
1117
|
### `view.trash`
|
|
1118
1118
|
|
{mammoth_cli-2.0.29 → mammoth_cli-2.0.30}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md
RENAMED
|
@@ -13,6 +13,8 @@ mammoth view transform filter 1039 --project 180 \
|
|
|
13
13
|
- `--input FILE` (or `--input @FILE`) reads a JSON or YAML file; the format
|
|
14
14
|
is inferred from the extension.
|
|
15
15
|
- `--input -` reads stdin; then `--input-format json|yaml` is required.
|
|
16
|
+
- `--dry-run` admits and resolves the document, then reports the SDK call
|
|
17
|
+
instead of making it ([safety](safety.md)).
|
|
16
18
|
- The top level must be a mapping. A bad path, format, or shape fails with exit
|
|
17
19
|
code 2 and a stable error code.
|
|
18
20
|
- Column references use display names returned by the exact view schema. Do not
|
|
@@ -90,6 +90,14 @@ task/list or pipeline/items result back after a successful write. Without that
|
|
|
90
90
|
independent specification, report the route as unsupported/ambiguous and stop
|
|
91
91
|
safely.
|
|
92
92
|
|
|
93
|
+
Typed transforms and `view task add` append at the end of the pipeline and
|
|
94
|
+
never reorder or delete an existing step. On a pipeline other people or
|
|
95
|
+
agents also edit, make that checkable: read `view task list VIEW_ID`, plan,
|
|
96
|
+
then write with `"expected_task_count": N` in the input; the CLI re-reads
|
|
97
|
+
the list just before the write and refuses with `pipeline_changed` when the
|
|
98
|
+
count moved ([safety](safety.md)). `--dry-run` on the same command shows the
|
|
99
|
+
resolved call first.
|
|
100
|
+
|
|
93
101
|
For a new workflow (a container for automation), discover and then read it
|
|
94
102
|
back; workflow creation is not the same as adding a view pipeline task:
|
|
95
103
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Safe mutation
|
|
2
|
+
|
|
3
|
+
Each command carries a reviewed confirmation policy:
|
|
4
|
+
|
|
5
|
+
| policy | how to satisfy it noninteractively |
|
|
6
|
+
|---|---|
|
|
7
|
+
| none | nothing required |
|
|
8
|
+
| prompt_or_yes | pass `--yes` |
|
|
9
|
+
| yes_always | pass `--yes` (always, even at a terminal) |
|
|
10
|
+
| confirm_target | pass `--yes` and `--confirm TARGET` (exact match) |
|
|
11
|
+
|
|
12
|
+
Under `--no-input` (and in `json`/`ndjson` modes) there is no prompt: a missing
|
|
13
|
+
`--yes`/`--confirm` fails with exit code 2 and error code
|
|
14
|
+
`confirmation_required` or `confirmation_target_mismatch`.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# normal delete
|
|
18
|
+
mammoth folder delete 7 --project 180 --yes
|
|
19
|
+
|
|
20
|
+
# high-impact: target must equal the resource
|
|
21
|
+
mammoth project user remove --project 180 \
|
|
22
|
+
--yes --confirm 180 --input '{"user_ids": ["u_123"]}'
|
|
23
|
+
|
|
24
|
+
# project delete cascades to every dataset, view, export and dashboard in it
|
|
25
|
+
mammoth project delete 180 --yes --confirm 180
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Discover a command's policy with `mammoth schema get <command.id>`.
|
|
29
|
+
|
|
30
|
+
## Rehearse before you write
|
|
31
|
+
|
|
32
|
+
`--dry-run` on any API-backed command does everything but send the request:
|
|
33
|
+
input admission, parent resolution, display-name and condition checks, then
|
|
34
|
+
it stops at the SDK call and reports it as `data.would_call` (`sdk_symbol`,
|
|
35
|
+
resolved `arguments`, `view_id`/`dataset_id`) with `data.dry_run: true`. No
|
|
36
|
+
confirmation flag is needed for a dry run; the real run still needs its
|
|
37
|
+
`--yes`/`--confirm`. Use it before a mutation on a shared or production
|
|
38
|
+
resource, and whenever you are unsure which view or column an input resolves
|
|
39
|
+
to.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
mammoth view transform filter 144 --dry-run \
|
|
43
|
+
--input '{"filter_type": "REMOVE", "condition": {"column": "Location", "operator": "EQ", "value": "Total"}}'
|
|
44
|
+
# data.would_call.sdk_symbol = ...FilterOpsMixin.filter_rows, view_id 144, dataset_id 122
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Add-only edits to a pipeline someone else may touch
|
|
48
|
+
|
|
49
|
+
Every `view transform *` and `view task add` accepts `expected_task_count`:
|
|
50
|
+
the number of tasks your last `view task list VIEW_ID` returned. The CLI
|
|
51
|
+
re-reads the task list right before the write and refuses with
|
|
52
|
+
`pipeline_changed` (exit 2, `details.actual_task_count`, `task_ids`) when the
|
|
53
|
+
count differs, so a plan made against a stale read cannot land on a pipeline
|
|
54
|
+
that changed under you. Read → decide → write with the count, one edit at a
|
|
55
|
+
time; on `pipeline_changed`, re-read and re-decide rather than retrying with
|
|
56
|
+
the new number blindly.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
mammoth view task list 144 # data.tasks has 3 entries
|
|
60
|
+
mammoth view transform filter 144 --input '{"expected_task_count": 3, "filter_type": "REMOVE", "condition": {...}}'
|
|
61
|
+
```
|
|
62
|
+
Use exact observed resource scope and display names before mutation. A known job
|
|
63
|
+
handle should be inspected or waited, not submitted again. `outcome_unknown`
|
|
64
|
+
means a mutation may have committed without a terminal response: reconcile the
|
|
65
|
+
target before any replay. A retryable read can honor `Retry-After`; exit 7 is
|
|
66
|
+
not blanket permission to retry a mutation.
|
|
@@ -1445,11 +1445,15 @@ def view_task_add(invocation: Invocation) -> HandlerResult:
|
|
|
1445
1445
|
kwargs: dict[str, Any] = {"dataview_id": dataview_id, "task_spec": task_spec}
|
|
1446
1446
|
assert document is not None
|
|
1447
1447
|
_forward_optional(document, kwargs, ("dataset_id",))
|
|
1448
|
+
# Imported here: view_ops imports this module for the brief record helpers.
|
|
1449
|
+
from mammoth_cli.commands.view_ops import (
|
|
1450
|
+
reject_pipeline_reference_errors,
|
|
1451
|
+
require_expected_task_count,
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1448
1454
|
with open_service(invocation) as (service, auth):
|
|
1455
|
+
require_expected_task_count(service, dataview_id, kwargs.get("dataset_id"), document)
|
|
1449
1456
|
data = service.call(_symbol(invocation), **kwargs)
|
|
1450
|
-
# Imported here: view_ops imports this module for the brief record helpers.
|
|
1451
|
-
from mammoth_cli.commands.view_ops import reject_pipeline_reference_errors
|
|
1452
|
-
|
|
1453
1457
|
reject_pipeline_reference_errors(service, dataview_id, kwargs.get("dataset_id"), data)
|
|
1454
1458
|
return data, _meta(invocation, auth.workspace_id, None)
|
|
1455
1459
|
|
|
@@ -43,6 +43,7 @@ from mammoth_cli.runtime.invocation import Invocation
|
|
|
43
43
|
from mammoth_cli.runtime.session import open_service, resolved_project
|
|
44
44
|
from mammoth_cli.services.command_contract import bind_command_inputs
|
|
45
45
|
from mammoth_cli.services.conditions import CONDITION_KWARG
|
|
46
|
+
from mammoth_cli.services.input_fields import TASK_COUNT_FIELD
|
|
46
47
|
|
|
47
48
|
HandlerResult = tuple[Any, dict[str, Any]]
|
|
48
49
|
|
|
@@ -251,6 +252,7 @@ def _dispatch_view(
|
|
|
251
252
|
with open_service(invocation) as (service, auth):
|
|
252
253
|
if dataset_id is None:
|
|
253
254
|
dataset_id = parents.lookup(_profile_name(invocation), auth.workspace_id, view_id)
|
|
255
|
+
require_expected_task_count(service, view_id, dataset_id, document)
|
|
254
256
|
try:
|
|
255
257
|
if dataset_id is None:
|
|
256
258
|
_require_discovery_allowed(invocation, view_id)
|
|
@@ -266,6 +268,60 @@ def _dispatch_view(
|
|
|
266
268
|
return data, _meta(invocation, auth.workspace_id)
|
|
267
269
|
|
|
268
270
|
|
|
271
|
+
CODE_PIPELINE_CHANGED = "pipeline_changed"
|
|
272
|
+
_TASK_LIST_SYMBOL = "mammoth.api.pipeline.PipelineAPI.list_tasks"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def require_expected_task_count(
|
|
276
|
+
service: Any, view_id: int, dataset_id: Any, document: dict[str, Any] | None
|
|
277
|
+
) -> None:
|
|
278
|
+
"""Refuse a pipeline write when the view's task count is not the one read.
|
|
279
|
+
|
|
280
|
+
``expected_task_count`` is the count from the caller's last ``view task
|
|
281
|
+
list``. A different live count means the pipeline changed since that read
|
|
282
|
+
(another agent, a person in the web app), and an append planned against
|
|
283
|
+
the old shape must not land. Without the field nothing is checked.
|
|
284
|
+
"""
|
|
285
|
+
if not document or TASK_COUNT_FIELD not in document:
|
|
286
|
+
return
|
|
287
|
+
expected = int(document[TASK_COUNT_FIELD])
|
|
288
|
+
kwargs: dict[str, Any] = {"dataview_id": view_id}
|
|
289
|
+
if dataset_id is not None:
|
|
290
|
+
kwargs["dataset_id"] = int(dataset_id)
|
|
291
|
+
listing = service.call(_TASK_LIST_SYMBOL, **kwargs)
|
|
292
|
+
tasks = listing.get("tasks") if isinstance(listing, dict) else None
|
|
293
|
+
if not isinstance(tasks, list):
|
|
294
|
+
raise CliError(
|
|
295
|
+
code=CODE_PIPELINE_CHANGED,
|
|
296
|
+
message=f"Could not read the task list of view {view_id} to check the precondition.",
|
|
297
|
+
exit_status=EXIT_USAGE,
|
|
298
|
+
details={"view_id": view_id, "expected_task_count": expected, "response": listing},
|
|
299
|
+
)
|
|
300
|
+
actual = len(tasks)
|
|
301
|
+
if actual == expected:
|
|
302
|
+
return
|
|
303
|
+
raise CliError(
|
|
304
|
+
code=CODE_PIPELINE_CHANGED,
|
|
305
|
+
message=(
|
|
306
|
+
f"View {view_id} has {actual} pipeline task(s), not the {expected} expected; "
|
|
307
|
+
"the pipeline changed since it was read, so nothing was written."
|
|
308
|
+
),
|
|
309
|
+
exit_status=EXIT_USAGE,
|
|
310
|
+
hint=(
|
|
311
|
+
"Re-read the pipeline, decide whether the plan still holds, and retry "
|
|
312
|
+
f"with expected_task_count {actual}."
|
|
313
|
+
),
|
|
314
|
+
details={
|
|
315
|
+
"view_id": view_id,
|
|
316
|
+
"dataset_id": int(dataset_id) if dataset_id is not None else None,
|
|
317
|
+
"expected_task_count": expected,
|
|
318
|
+
"actual_task_count": actual,
|
|
319
|
+
"task_ids": [t.get("id") for t in tasks if isinstance(t, dict)],
|
|
320
|
+
},
|
|
321
|
+
recovery_commands=[f"mammoth view task list {view_id}"],
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
269
325
|
def _compact_reference_error(entry: Any) -> dict[str, Any]:
|
|
270
326
|
if not isinstance(entry, dict):
|
|
271
327
|
return {"reason": str(entry)}
|
|
@@ -362,7 +418,11 @@ def _without_resource_context(document: dict[str, Any]) -> dict[str, Any]:
|
|
|
362
418
|
# mapping. These identifiers select the View receiver and are not method
|
|
363
419
|
# kwargs; strip both parent and receiver identities before the transform
|
|
364
420
|
# adapter forwards the document.
|
|
365
|
-
return {
|
|
421
|
+
return {
|
|
422
|
+
key: value
|
|
423
|
+
for key, value in document.items()
|
|
424
|
+
if key not in {"dataset_id", "view_id", TASK_COUNT_FIELD}
|
|
425
|
+
}
|
|
366
426
|
|
|
367
427
|
|
|
368
428
|
def _bind_transform_inputs(invocation: Invocation, document: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -84,6 +84,10 @@ def enforce_confirmation(
|
|
|
84
84
|
"""
|
|
85
85
|
if policy == POLICY_NONE:
|
|
86
86
|
return
|
|
87
|
+
if invocation.dry_run:
|
|
88
|
+
# Nothing is sent under --dry-run, so there is nothing to confirm; the
|
|
89
|
+
# report names the confirmation the real run will need.
|
|
90
|
+
return
|
|
87
91
|
|
|
88
92
|
tty = sys.stdin.isatty() if is_tty is None else is_tty
|
|
89
93
|
machine = invocation.output in MACHINE_OUTPUTS
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""``--dry-run``: resolve a command completely, then stop before its request.
|
|
2
|
+
|
|
3
|
+
A dry run does everything a real run does up to the network call that the
|
|
4
|
+
command exists for — input admission, parent resolution, display-name checks,
|
|
5
|
+
condition compilation, argument coercion — and then raises :class:`DryRunStop`
|
|
6
|
+
instead of invoking the SDK method. The executor turns that into a success
|
|
7
|
+
envelope describing the call that would have been made.
|
|
8
|
+
|
|
9
|
+
The gate is a small callable installed on the production service by
|
|
10
|
+
:func:`mammoth_cli.runtime.session.open_service`. It classifies each SDK call
|
|
11
|
+
the handler makes:
|
|
12
|
+
|
|
13
|
+
- the command's own manifest ``sdk_symbol`` (or, for View-backed commands,
|
|
14
|
+
the View method of that symbol) → stop and report;
|
|
15
|
+
- a symbol some ``read`` command is backed by → let it through, so the
|
|
16
|
+
resolution the report depends on really happened;
|
|
17
|
+
- anything else → stop and report it as an undeclared write, so a dry run
|
|
18
|
+
can never mutate through a side path.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from collections.abc import Callable, Mapping
|
|
24
|
+
from enum import Enum
|
|
25
|
+
from functools import lru_cache
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from mammoth_cli.manifest.loader import command_by_id, load_commands
|
|
29
|
+
|
|
30
|
+
Gate = Callable[..., None]
|
|
31
|
+
|
|
32
|
+
NOTE_OWN = "No request was sent for this operation; the reads needed to resolve it did run."
|
|
33
|
+
NOTE_UNDECLARED = (
|
|
34
|
+
"Stopped before an SDK call the command's manifest does not declare; "
|
|
35
|
+
"nothing was sent for it."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DryRunStop(Exception):
|
|
40
|
+
"""Raised by the gate at the point a real run would send the request."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, record: dict[str, Any]) -> None:
|
|
43
|
+
super().__init__("dry run")
|
|
44
|
+
self.record = record
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@lru_cache(maxsize=1)
|
|
48
|
+
def _read_symbols() -> frozenset[str]:
|
|
49
|
+
return frozenset(
|
|
50
|
+
str(record["sdk_symbol"])
|
|
51
|
+
for record in load_commands()
|
|
52
|
+
if record.get("mutation_class") == "read" and record.get("sdk_symbol")
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _read_view_methods() -> frozenset[str]:
|
|
57
|
+
return frozenset(
|
|
58
|
+
symbol.rsplit(".", 1)[1]
|
|
59
|
+
for symbol in _read_symbols()
|
|
60
|
+
if symbol.startswith("mammoth.view.") or symbol.startswith("mammoth._mixins.")
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def jsonable(value: Any) -> Any:
|
|
65
|
+
"""Render resolved SDK arguments for the report without leaking objects."""
|
|
66
|
+
if isinstance(value, Enum):
|
|
67
|
+
return value.value
|
|
68
|
+
if isinstance(value, Mapping):
|
|
69
|
+
return {str(key): jsonable(item) for key, item in value.items()}
|
|
70
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
71
|
+
return [jsonable(item) for item in value]
|
|
72
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
73
|
+
return value
|
|
74
|
+
to_dict = getattr(value, "to_dict", None)
|
|
75
|
+
if callable(to_dict):
|
|
76
|
+
try:
|
|
77
|
+
return jsonable(to_dict())
|
|
78
|
+
except Exception: # pragma: no cover - defensive; fall through to repr
|
|
79
|
+
return repr(value)
|
|
80
|
+
view_id = getattr(value, "id", None)
|
|
81
|
+
if type(value).__name__ == "View" and isinstance(view_id, int):
|
|
82
|
+
return {"view_id": view_id, "dataset_id": getattr(value, "dataset_id", None)}
|
|
83
|
+
return repr(value)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def make_gate(command_id: str) -> Gate:
|
|
87
|
+
"""Return the gate for ``command_id`` (see the module docstring)."""
|
|
88
|
+
record = command_by_id(command_id) or {}
|
|
89
|
+
own_symbol = str(record.get("sdk_symbol") or "")
|
|
90
|
+
own_method = own_symbol.rsplit(".", 1)[-1] if own_symbol else ""
|
|
91
|
+
mutation_class = record.get("mutation_class")
|
|
92
|
+
# A CLI-composed command (project ensure, ...) is backed by a CLI function
|
|
93
|
+
# and reaches the SDK through whichever writes it needs; those are its own.
|
|
94
|
+
composed = own_symbol.startswith("mammoth_cli.")
|
|
95
|
+
reads = _read_symbols()
|
|
96
|
+
view_reads = _read_view_methods()
|
|
97
|
+
|
|
98
|
+
def stop(symbol: str, arguments: dict[str, Any], *, own: bool, **scope: Any) -> None:
|
|
99
|
+
raise DryRunStop(
|
|
100
|
+
{
|
|
101
|
+
"dry_run": True,
|
|
102
|
+
"command": command_id.replace(".", " "),
|
|
103
|
+
"mutation_class": mutation_class,
|
|
104
|
+
"would_call": {
|
|
105
|
+
"sdk_symbol": symbol,
|
|
106
|
+
**{key: value for key, value in scope.items() if value is not None},
|
|
107
|
+
"arguments": jsonable(arguments),
|
|
108
|
+
},
|
|
109
|
+
"note": NOTE_OWN if own else NOTE_UNDECLARED,
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def gate(
|
|
114
|
+
symbol: str,
|
|
115
|
+
arguments: dict[str, Any],
|
|
116
|
+
*,
|
|
117
|
+
view_id: int | None = None,
|
|
118
|
+
dataset_id: int | None = None,
|
|
119
|
+
) -> None:
|
|
120
|
+
if view_id is not None:
|
|
121
|
+
# A View-backed call: ``symbol`` is the View method name.
|
|
122
|
+
if symbol == own_method:
|
|
123
|
+
stop(own_symbol, arguments, own=True, view_id=view_id, dataset_id=dataset_id)
|
|
124
|
+
if symbol in view_reads:
|
|
125
|
+
return
|
|
126
|
+
stop(f"View.{symbol}", arguments, own=False, view_id=view_id, dataset_id=dataset_id)
|
|
127
|
+
if symbol == own_symbol:
|
|
128
|
+
stop(symbol, arguments, own=True)
|
|
129
|
+
if symbol in reads:
|
|
130
|
+
return
|
|
131
|
+
stop(symbol, arguments, own=composed)
|
|
132
|
+
|
|
133
|
+
return gate
|
|
@@ -16,7 +16,11 @@ from mammoth_cli.errors.envelope import EXIT_USAGE, CliError
|
|
|
16
16
|
from mammoth_cli.output.policy import OUTPUT_AUTO, SELECTABLE_OUTPUTS, resolve_output
|
|
17
17
|
from mammoth_cli.runtime.input_loader import load_input_document
|
|
18
18
|
from mammoth_cli.runtime.strict import validate_input_fields
|
|
19
|
-
from mammoth_cli.services.input_fields import
|
|
19
|
+
from mammoth_cli.services.input_fields import (
|
|
20
|
+
TASK_COUNT_FIELD,
|
|
21
|
+
accepts_resource_dataset,
|
|
22
|
+
accepts_task_count_precondition,
|
|
23
|
+
)
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
class _UninitializedInput:
|
|
@@ -55,6 +59,7 @@ class Invocation:
|
|
|
55
59
|
debug: bool = False
|
|
56
60
|
yes: bool = False
|
|
57
61
|
confirm: str | None = None
|
|
62
|
+
dry_run: bool = False
|
|
58
63
|
input_file: str | None = None
|
|
59
64
|
input_format: str | None = None
|
|
60
65
|
positionals: dict[str, Any] = field(default_factory=dict)
|
|
@@ -231,10 +236,36 @@ class Invocation:
|
|
|
231
236
|
hint="Pass the exact parent dataset id for the target view.",
|
|
232
237
|
details={"field": "dataset_id", "expected_type": "positive integer"},
|
|
233
238
|
) from None
|
|
239
|
+
expected_tasks: Any = None
|
|
240
|
+
has_expected_tasks = (
|
|
241
|
+
document is not None
|
|
242
|
+
and accepts_task_count_precondition(self.command_id)
|
|
243
|
+
and TASK_COUNT_FIELD in document
|
|
244
|
+
)
|
|
245
|
+
if has_expected_tasks:
|
|
246
|
+
assert document is not None
|
|
247
|
+
expected_tasks = document.pop(TASK_COUNT_FIELD)
|
|
248
|
+
try:
|
|
249
|
+
if isinstance(expected_tasks, bool):
|
|
250
|
+
raise ValueError
|
|
251
|
+
expected_tasks = int(expected_tasks)
|
|
252
|
+
if expected_tasks < 0:
|
|
253
|
+
raise ValueError
|
|
254
|
+
except (TypeError, ValueError):
|
|
255
|
+
raise CliError(
|
|
256
|
+
code="invalid_resource_context",
|
|
257
|
+
message=f"Input field '{TASK_COUNT_FIELD}' must be a non-negative integer.",
|
|
258
|
+
exit_status=EXIT_USAGE,
|
|
259
|
+
hint="Pass the task count from your last 'view task list' read.",
|
|
260
|
+
details={"field": TASK_COUNT_FIELD, "expected_type": "integer >= 0"},
|
|
261
|
+
) from None
|
|
234
262
|
validate_input_fields(self.command_id, document)
|
|
235
263
|
if has_resource_dataset:
|
|
236
264
|
assert document is not None
|
|
237
265
|
document["dataset_id"] = resource_dataset
|
|
266
|
+
if has_expected_tasks:
|
|
267
|
+
assert document is not None
|
|
268
|
+
document[TASK_COUNT_FIELD] = expected_tasks
|
|
238
269
|
object.__setattr__(self, "_prepared_input", document)
|
|
239
270
|
return self._prepared_input # type: ignore[return-value]
|
|
240
271
|
|
|
@@ -79,6 +79,13 @@ def open_service(invocation: Invocation) -> Iterator[tuple[MammothService, Resol
|
|
|
79
79
|
profile=invocation.profile or profiles.get_selected(),
|
|
80
80
|
progress=not policy.progress_disabled,
|
|
81
81
|
)
|
|
82
|
+
if invocation.dry_run:
|
|
83
|
+
# The production service exposes the gate; a test double may not, and
|
|
84
|
+
# then the dry run is simply the double's own behaviour.
|
|
85
|
+
if hasattr(service, "gate"):
|
|
86
|
+
from mammoth_cli.runtime.dryrun import make_gate
|
|
87
|
+
|
|
88
|
+
service.gate = make_gate(invocation.command_id)
|
|
82
89
|
try:
|
|
83
90
|
yield service, auth
|
|
84
91
|
finally:
|
|
@@ -26,7 +26,9 @@ from mammoth.models.batches import (
|
|
|
26
26
|
from mammoth_cli.manifest.loader import command_by_id, load_commands
|
|
27
27
|
from mammoth_cli.services.argspec import ArgSpec, FieldSpec, arg_spec
|
|
28
28
|
from mammoth_cli.services.input_fields import (
|
|
29
|
+
TASK_COUNT_FIELD,
|
|
29
30
|
accepts_resource_dataset,
|
|
31
|
+
accepts_task_count_precondition,
|
|
30
32
|
excluded_input_fields,
|
|
31
33
|
handler_owned_fields,
|
|
32
34
|
is_closed_zero_input,
|
|
@@ -672,6 +674,12 @@ def resolve_command_contract(command_id: str) -> ResolvedCommandContract | None:
|
|
|
672
674
|
field.name for field in fields
|
|
673
675
|
}:
|
|
674
676
|
fields += (FieldSpec("dataset_id", required=False, annotation=int | None, default=None),)
|
|
677
|
+
if accepts_task_count_precondition(command_id) and TASK_COUNT_FIELD not in {
|
|
678
|
+
field.name for field in fields
|
|
679
|
+
}:
|
|
680
|
+
fields += (
|
|
681
|
+
FieldSpec(TASK_COUNT_FIELD, required=False, annotation=int | None, default=None),
|
|
682
|
+
)
|
|
675
683
|
additional_fields = _S7_ADDITIONAL_INPUT_FIELDS.get(command_id, ())
|
|
676
684
|
declared_names = {field.name for field in fields}
|
|
677
685
|
fields += tuple(field for field in additional_fields if field.name not in declared_names)
|
|
@@ -686,6 +694,8 @@ def resolve_command_contract(command_id: str) -> ResolvedCommandContract | None:
|
|
|
686
694
|
# the subsequent shared bind idempotent after that protected preflight.
|
|
687
695
|
if accepts_resource_dataset(command_id):
|
|
688
696
|
context_values["dataset_id"] = "dataset_id"
|
|
697
|
+
if accepts_task_count_precondition(command_id):
|
|
698
|
+
context_values[TASK_COUNT_FIELD] = TASK_COUNT_FIELD
|
|
689
699
|
context = MappingProxyType(context_values)
|
|
690
700
|
extensibility = (
|
|
691
701
|
"closed"
|
|
@@ -351,6 +351,18 @@ def accepts_resource_dataset(command_id: str) -> bool:
|
|
|
351
351
|
return command_id.startswith(_RESOURCE_DATASET_PREFIXES)
|
|
352
352
|
|
|
353
353
|
|
|
354
|
+
#: Pipeline writes that admit ``expected_task_count``: the number of tasks the
|
|
355
|
+
#: caller last read on the view. The write is refused when the pipeline has a
|
|
356
|
+
#: different count, so an edit planned against a stale read cannot land on a
|
|
357
|
+
#: pipeline someone else changed (the "add-only to a shared pipeline" rule).
|
|
358
|
+
TASK_COUNT_FIELD = "expected_task_count"
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def accepts_task_count_precondition(command_id: str) -> bool:
|
|
362
|
+
"""Return True when ``expected_task_count`` is admitted for ``command_id``."""
|
|
363
|
+
return command_id.startswith("view.transform.") or command_id == "view.task.add"
|
|
364
|
+
|
|
365
|
+
|
|
354
366
|
def example_input_hints(command_id: str) -> dict[str, Any]:
|
|
355
367
|
"""Return extra ``--input`` fields to include in the generated example."""
|
|
356
368
|
hints = dict(_EXAMPLE_INPUT_HINTS.get(command_id, {}))
|
|
@@ -9,6 +9,7 @@ a private (``_``-prefixed) SDK member.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import re
|
|
12
|
+
from collections.abc import Callable
|
|
12
13
|
from types import TracebackType
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
@@ -72,6 +73,9 @@ class SdkMammothService:
|
|
|
72
73
|
"""
|
|
73
74
|
self._progress = progress
|
|
74
75
|
self._profile = profile
|
|
76
|
+
#: ``--dry-run`` hook (:mod:`mammoth_cli.runtime.dryrun`): called with
|
|
77
|
+
#: the resolved symbol and arguments right before each SDK call.
|
|
78
|
+
self.gate: Callable[..., None] | None = None
|
|
75
79
|
self._project_id = project_id
|
|
76
80
|
self._workspace_id = auth.workspace_id
|
|
77
81
|
kwargs: dict[str, Any] = {}
|
|
@@ -124,6 +128,8 @@ class SdkMammothService:
|
|
|
124
128
|
):
|
|
125
129
|
raise missing_project_error()
|
|
126
130
|
kwargs = self._coerce_call_arguments(method, kwargs)
|
|
131
|
+
if self.gate is not None:
|
|
132
|
+
self.gate(sdk_symbol, kwargs)
|
|
127
133
|
try:
|
|
128
134
|
with spinner(self._progress):
|
|
129
135
|
return method(**kwargs)
|
|
@@ -356,6 +362,8 @@ class SdkMammothService:
|
|
|
356
362
|
) from exc
|
|
357
363
|
if kwargs.get(CONDITION_KWARG) is not None:
|
|
358
364
|
kwargs[CONDITION_KWARG] = compile_condition(kwargs[CONDITION_KWARG])
|
|
365
|
+
if self.gate is not None:
|
|
366
|
+
self.gate(method, kwargs, view_id=view_id, dataset_id=getattr(view, "dataset_id", None))
|
|
359
367
|
try:
|
|
360
368
|
with spinner(self._progress):
|
|
361
369
|
return attribute(**kwargs)
|
|
@@ -717,6 +725,8 @@ class SdkMammothService:
|
|
|
717
725
|
Raises:
|
|
718
726
|
CliError: Mapped from any SDK exception.
|
|
719
727
|
"""
|
|
728
|
+
if self.gate is not None:
|
|
729
|
+
self.gate("mammoth.api.projects.ProjectsAPI.create", {"name": name, **kwargs})
|
|
720
730
|
try:
|
|
721
731
|
return self._client.projects.create(name, **kwargs)
|
|
722
732
|
except Exception as exc:
|
|
@@ -734,6 +744,8 @@ class SdkMammothService:
|
|
|
734
744
|
Raises:
|
|
735
745
|
CliError: Mapped from any SDK exception.
|
|
736
746
|
"""
|
|
747
|
+
if self.gate is not None:
|
|
748
|
+
self.gate("mammoth.api.projects.ProjectsAPI.delete", {"project_id": project_id})
|
|
737
749
|
try:
|
|
738
750
|
return self._client.projects.delete(project_id)
|
|
739
751
|
except Exception as exc:
|