mammoth-cli 2.2.47__tar.gz → 2.2.48__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.2.47 → mammoth_cli-2.2.48}/PKG-INFO +2 -2
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/__init__.py +1 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +1 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +5 -2
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +1 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +3 -2
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +3 -2
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/auth.py +51 -23
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/job.py +3 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/project.py +2 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/schema.py +1 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/view.py +43 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/context/oauth.py +122 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/errors/envelope.py +3 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/dataset_health.py +2 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/job_outcome.py +23 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/mapping.py +22 -1
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/positionals.py +3 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/pyproject.toml +2 -2
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/view.yaml +3 -3
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/.gitignore +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/LICENSE +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/README.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/__main__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/app.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-style.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-template.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/link.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/resolve.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/token.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/database-import.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/activity.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/addon.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/agent.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/ai.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/annotation.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/automation.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/batch.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/billing.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/browse.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/calc.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/capability.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/client_app.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/completion.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/config.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/connector.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/context.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/dashboard.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/data_app.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/dataset.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/doctor.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/external_key.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/file.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/folder.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/link.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/notification.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/parameter.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/registry.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/report.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/resolve.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/schedule.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/skill.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/snippet.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/support.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/template.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/trash.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/upgrade.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/user.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/view_ops.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/view_profile.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/webhook.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/workflow.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/commands/workspace.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/context/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/context/credentials.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/context/endpoint.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/context/profiles.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/context/resolver.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/contracts/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/contracts/auth.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/embed.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/errors/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/manifest/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/manifest/loader.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/messages/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/output/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/output/envelope.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/output/normalize.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/output/policy.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/output/progress.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/output/render.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/py.typed +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/confirm.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/dryrun.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/dryrun_targets.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/embedded.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/executor.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/input_loader.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/intent_only.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/invocation.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/locked_files.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/new_data.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/options.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/parents.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/runlog.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/session.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/state.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/strict.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/updates.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/validate.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/runtime/verify.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/append_blank_columns.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/argspec.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/board_values.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/coerce.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/command_contract.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/conditional_format.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/conditions.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/dashboard_filters.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/dashboard_pages.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/dashboard_review.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/data_profile.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/data_quality.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/dispatch.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/factory.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/input_fields.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/listing.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/local_time.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/openapi_types.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/protocol.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/read_queries.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/sdk_service.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/sql_check.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/stored_stats.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/testing.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/text_dates.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/type_system.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/view_analysis.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/services/write_impact.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/skills/__init__.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/skills/installer.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/skills/steering.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/testing.py +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/activity.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/addon.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/agent.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/ai.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/annotation.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/auth.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/automation.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/batch.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/billing.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/browse.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/calc.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/capability.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/client-app.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/completion.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/config.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/connector.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/context.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/dashboard.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/data-app.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/dataset.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/doctor.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/external-key.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/file.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/folder.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/job.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/link.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/log.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/notification.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/parameter.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/project.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/report.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/resolve.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/schedule.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/schema.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/skill.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/snippet.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/support.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/template.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/token.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/trash.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/upgrade.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/user.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/version.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/webhook.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/workflow.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/commands/workspace.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/edits-target.source.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/openapi-operations.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/schema-v1.json +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/sdk-catalog.source.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/manifests/sdk-methods.yaml +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/openapi/metadata.json +0 -0
- {mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/spec/openapi/openapi.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mammoth-cli
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.48
|
|
4
4
|
Summary: Command-line interface for the Mammoth Analytics platform
|
|
5
5
|
Project-URL: homepage, https://mammoth.io
|
|
6
6
|
Project-URL: repository, https://github.com/EdgeMetric/mammothsdk
|
|
@@ -17,7 +17,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
17
17
|
Requires-Python: <3.15,>=3.12
|
|
18
18
|
Requires-Dist: jsonschema>=4.20
|
|
19
19
|
Requires-Dist: keyring<26,>=25.7
|
|
20
|
-
Requires-Dist: mammoth-io<0.9,>=0.8.
|
|
20
|
+
Requires-Dist: mammoth-io<0.9,>=0.8.26
|
|
21
21
|
Requires-Dist: platformdirs<5,>=4.10
|
|
22
22
|
Requires-Dist: pydantic<3,>=2.12.5
|
|
23
23
|
Requires-Dist: pyyaml<7,>=6
|
|
@@ -49,7 +49,7 @@ MAMMOTH_OUTPUT=json MAMMOTH_NO_INPUT=1` once.
|
|
|
49
49
|
Use the project you are given; never `project ensure`. No `doctor`, auth,
|
|
50
50
|
profiles, `config set`, `upgrade`, `skill install`, env vars, local files,
|
|
51
51
|
stdin, `--input FILE` or `output_path` (they fail `not_available_embedded`);
|
|
52
|
-
downloads return `download_url`.
|
|
52
|
+
downloads return `download_url`. Ask for a reload on `authentication_failed` only; on a 403 check ids and scope.
|
|
53
53
|
|
|
54
54
|
## Defaults you do not repeat
|
|
55
55
|
|
{mammoth_cli-2.2.47 → mammoth_cli-2.2.48}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md
RENAMED
|
@@ -17,8 +17,11 @@ created without a project.
|
|
|
17
17
|
`mammoth auth login --method oauth` signs the operator in through the browser (PKCE, loopback
|
|
18
18
|
`127.0.0.1`). The CLI keeps a 1 h `mm_` token and refreshes it under a file lock. `auth status`
|
|
19
19
|
reports `credential: oauth` and `expires_at`. A refresh that fails exits 4 with `login_expired`:
|
|
20
|
-
tell the operator to run `mammoth auth login` again. `--no-browser` prints the URL.
|
|
21
|
-
|
|
20
|
+
tell the operator to run `mammoth auth login` again. `--no-browser` prints the URL. `--device` is the sign-in for a
|
|
21
|
+
machine with no browser: it prints a short code and a URL for the operator to approve on another
|
|
22
|
+
device, then polls. Browser and device sign-in are for the operator's own terminal; an agent never
|
|
23
|
+
runs them. A key made with `token create` expires after 30 days: `cli_key_expired` means run
|
|
24
|
+
`mammoth auth login` again. `auth logout` also revokes the
|
|
22
25
|
connection on the server, and warns (still clearing local state) if it cannot.
|
|
23
26
|
|
|
24
27
|
`mammoth token create NAME`, `token list`, `token revoke KEY` are aliases of `client-app create`,
|
|
@@ -738,7 +738,7 @@ Status on release: ran once on CLI 2.0.21 — ergonomics sweep 2026-09-19: exit
|
|
|
738
738
|
|
|
739
739
|
Run: `mammoth view optimize`. Exact input fields: `mammoth schema get view.optimize`.
|
|
740
740
|
|
|
741
|
-
Example: `mammoth view optimize 123
|
|
741
|
+
Example: `mammoth view optimize 123`. Illustrative only: append `--yes` after observing an owned target.
|
|
742
742
|
|
|
743
743
|
Result: `ViewOptimizeResult`; mutation `reversible_pipeline`, confirmation `prompt_or_yes`, wait policy `start_or_wait`.
|
|
744
744
|
|
|
@@ -19,5 +19,6 @@ condition, not permission to retry mutations. The credential rule lives in
|
|
|
19
19
|
Exit 4 is auth/authorization; preserve the structured error and stop safely.
|
|
20
20
|
|
|
21
21
|
In the Mammoth app none of the above applies: there is no `auth status`,
|
|
22
|
-
`doctor` or profile, and the app passes the login and project. On
|
|
23
|
-
the user to reload the page
|
|
22
|
+
`doctor` or profile, and the app passes the login and project. On `authentication_failed` or
|
|
23
|
+
`not_authenticated` ask the user to reload the page; on `authorization_required` (403)
|
|
24
|
+
check the ids and scope first, since a reload does not fix them.
|
|
@@ -7,7 +7,8 @@ these commands are not blanket permission to replay a mutation.
|
|
|
7
7
|
|
|
8
8
|
| exit | error.code (examples) | next step |
|
|
9
9
|
|---|---|---|
|
|
10
|
-
| 4 | not_authenticated, authentication_failed | `mammoth auth login`; in the Mammoth app, ask the user to reload the page |
|
|
10
|
+
| 4 | not_authenticated, authentication_failed | `mammoth auth login`; in the Mammoth app, ask the user to reload the page (an expired or invalid sign-in only) |
|
|
11
|
+
| 4 | authorization_required (403) | check the view, dataset and project ids and the scope first; a reload does not fix a wrong id |
|
|
11
12
|
| 2 | keyring_unavailable, keyring_unresponsive | operator runs `mammoth auth login --storage file` (or unlocks the OS keychain); not seen in the app |
|
|
12
13
|
| 5 | resource_not_found | re-list to find the correct id |
|
|
13
14
|
| 2 | project_required | `mammoth context project use ID` or `--project` (in the app: `--project` only) |
|
|
@@ -48,7 +49,7 @@ the `run_id` when you report a backend fault.
|
|
|
48
49
|
|
|
49
50
|
Do not run the next mutation on any of these; report the state (with
|
|
50
51
|
`log_ref.run_id`) and either recover per the tables above or hand off:
|
|
51
|
-
- `auth status` is missing or its `endpoint` does not match the intended environment (in the app there is no `auth status` or `doctor`:
|
|
52
|
+
- `auth status` is missing or its `endpoint` does not match the intended environment (in the app there is no `auth status` or `doctor`: a sign-in error (`authentication_failed`, `not_authenticated`) means ask the user to reload the page; `authorization_required` means check the ids and scope)
|
|
52
53
|
- `mammoth doctor` fails (CLI only)
|
|
53
54
|
- a dataset is still `need_action` after the settings update
|
|
54
55
|
- a read-back after a value-changing step shows a uniform column (all 0 / all one value / all null) or a changed row the condition should not have touched
|
|
@@ -270,13 +270,13 @@ def _verify_login(invocation: Invocation, *, base_url: str, api_token: str) -> i
|
|
|
270
270
|
return workspace_id
|
|
271
271
|
|
|
272
272
|
|
|
273
|
-
_LOGIN_METHODS = ("oauth", "token")
|
|
273
|
+
_LOGIN_METHODS = ("oauth", "device", "token")
|
|
274
274
|
|
|
275
275
|
|
|
276
276
|
def _choose_method(
|
|
277
277
|
method: str | None, invocation: Invocation, *, server_prefix: str | None, blockers: list[str]
|
|
278
278
|
) -> str:
|
|
279
|
-
"""Pick ``oauth`` or ``token``: explicit, implied by ``--input``, or asked.
|
|
279
|
+
"""Pick ``oauth``, ``device`` or ``token``: explicit, implied by ``--input``, or asked.
|
|
280
280
|
|
|
281
281
|
A server with no CLI OAuth client, a non-interactive run, and ``--input``
|
|
282
282
|
all keep the token login that existed before browser sign-in.
|
|
@@ -289,10 +289,10 @@ def _choose_method(
|
|
|
289
289
|
exit_status=EXIT_USAGE,
|
|
290
290
|
hint=f"Use one of: {', '.join(_LOGIN_METHODS)}.",
|
|
291
291
|
)
|
|
292
|
-
if method
|
|
292
|
+
if method != "token" and invocation.input_file is not None:
|
|
293
293
|
raise CliError(
|
|
294
294
|
code="invalid_argument_combination",
|
|
295
|
-
message="--input is for token logins; it cannot be used with --method
|
|
295
|
+
message=f"--input is for token logins; it cannot be used with --method {method}.",
|
|
296
296
|
exit_status=EXIT_USAGE,
|
|
297
297
|
)
|
|
298
298
|
return method
|
|
@@ -302,16 +302,18 @@ def _choose_method(
|
|
|
302
302
|
return "token"
|
|
303
303
|
typer.echo("How do you want to sign in?", err=True)
|
|
304
304
|
typer.echo(" 1) Browser (OAuth) recommended", err=True)
|
|
305
|
+
typer.echo(" 2) Device code no browser on this machine", err=True)
|
|
305
306
|
typer.echo(" 3) Paste an API token for CI and scripts", err=True)
|
|
306
307
|
choice = typer.prompt("Choose", default="1", show_default=False).strip()
|
|
307
|
-
|
|
308
|
+
methods = {"1": "oauth", "2": "device", "3": "token"}
|
|
309
|
+
if choice not in methods:
|
|
308
310
|
raise CliError(
|
|
309
311
|
code="invalid_login_method",
|
|
310
312
|
message=f"'{choice}' is not one of the choices.",
|
|
311
313
|
exit_status=EXIT_USAGE,
|
|
312
|
-
hint="Enter 1 for the browser or 3 to paste an API token.",
|
|
314
|
+
hint="Enter 1 for the browser, 2 for a device code or 3 to paste an API token.",
|
|
313
315
|
)
|
|
314
|
-
return
|
|
316
|
+
return methods[choice]
|
|
315
317
|
|
|
316
318
|
|
|
317
319
|
def _iso(epoch: int) -> str:
|
|
@@ -325,16 +327,18 @@ def _run_oauth_login(
|
|
|
325
327
|
storage: str,
|
|
326
328
|
open_browser: bool,
|
|
327
329
|
interactive: bool,
|
|
330
|
+
device: bool = False,
|
|
328
331
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
329
|
-
"""Sign in through the browser and persist the refreshable session."""
|
|
332
|
+
"""Sign in through the browser (or a device code) and persist the refreshable session."""
|
|
330
333
|
base_url = resolve_base_url(server_prefix)
|
|
331
334
|
client_id = oauth.client_id_for(server_prefix)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
client_id=client_id,
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
say = lambda line: typer.echo(line, err=True) # noqa: E731
|
|
336
|
+
if device:
|
|
337
|
+
session = oauth.device_login(base_url, client_id=client_id, say=say)
|
|
338
|
+
else:
|
|
339
|
+
session = oauth.browser_login(
|
|
340
|
+
base_url, client_id=client_id, open_browser=open_browser, say=say
|
|
341
|
+
)
|
|
338
342
|
workspace_id = _verify_login(invocation, base_url=base_url, api_token=session.access)
|
|
339
343
|
|
|
340
344
|
profile_name = invocation.profile or profiles.DEFAULT_PROFILE_NAME
|
|
@@ -378,8 +382,18 @@ def _run_login(
|
|
|
378
382
|
storage: str,
|
|
379
383
|
method: str | None = None,
|
|
380
384
|
no_browser: bool = False,
|
|
385
|
+
device: bool = False,
|
|
381
386
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
382
387
|
"""Validate, connection-check, and persist one `auth login` invocation."""
|
|
388
|
+
if device:
|
|
389
|
+
if method not in (None, "device"):
|
|
390
|
+
raise CliError(
|
|
391
|
+
code="invalid_argument_combination",
|
|
392
|
+
message=f"--device cannot be used with --method {method}.",
|
|
393
|
+
exit_status=EXIT_USAGE,
|
|
394
|
+
hint="Use --device alone, or --method device.",
|
|
395
|
+
)
|
|
396
|
+
method = "device"
|
|
383
397
|
if storage not in _STORAGE_MODES:
|
|
384
398
|
raise CliError(
|
|
385
399
|
code="invalid_storage_mode",
|
|
@@ -390,16 +404,15 @@ def _run_login(
|
|
|
390
404
|
|
|
391
405
|
blockers = _prompt_blockers(invocation)
|
|
392
406
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
== "oauth"
|
|
396
|
-
):
|
|
407
|
+
chosen = _choose_method(method, invocation, server_prefix=server_prefix, blockers=blockers)
|
|
408
|
+
if chosen in ("oauth", "device"):
|
|
397
409
|
return _run_oauth_login(
|
|
398
410
|
invocation,
|
|
399
411
|
server_prefix=server_prefix,
|
|
400
412
|
storage=storage,
|
|
401
413
|
open_browser=not no_browser,
|
|
402
414
|
interactive=not blockers,
|
|
415
|
+
device=chosen == "device",
|
|
403
416
|
)
|
|
404
417
|
|
|
405
418
|
if invocation.input_file is not None:
|
|
@@ -488,19 +501,33 @@ def auth_login(
|
|
|
488
501
|
method: str | None = typer.Option(
|
|
489
502
|
None,
|
|
490
503
|
"--method",
|
|
491
|
-
help=
|
|
492
|
-
|
|
504
|
+
help=(
|
|
505
|
+
"How to sign in: a browser (oauth), a code typed on another device (device) "
|
|
506
|
+
"or a pasted API token (token)."
|
|
507
|
+
),
|
|
508
|
+
metavar="oauth|device|token",
|
|
493
509
|
),
|
|
494
510
|
no_browser: bool = typer.Option(
|
|
495
511
|
False, "--no-browser", help="Print the sign-in URL instead of opening a browser."
|
|
496
512
|
),
|
|
513
|
+
device: bool = typer.Option(
|
|
514
|
+
False,
|
|
515
|
+
"--device",
|
|
516
|
+
help=(
|
|
517
|
+
"Sign in with a device code, for a machine with no browser: the CLI prints "
|
|
518
|
+
"a short code and a URL, you approve on any other device, and the CLI "
|
|
519
|
+
"polls until you do. Same as --method device."
|
|
520
|
+
),
|
|
521
|
+
),
|
|
497
522
|
) -> None:
|
|
498
523
|
"""Log in and store one profile's credentials.
|
|
499
524
|
|
|
500
525
|
In a terminal, choose a browser sign-in (OAuth; the CLI keeps a token that
|
|
501
|
-
refreshes itself)
|
|
502
|
-
|
|
503
|
-
(
|
|
526
|
+
refreshes itself), a device code (``--device``: for a machine with no
|
|
527
|
+
browser, you type a short code on any other device) or paste an API token
|
|
528
|
+
(mm_..., from Workspace settings -> API Tokens). The token names its
|
|
529
|
+
workspace. For non-interactive use (agents, CI), pass ``--input FILE`` with
|
|
530
|
+
{"api_token": ...} instead.
|
|
504
531
|
Performs a lightweight connection check before saving anything; a failed
|
|
505
532
|
check leaves existing profile state unchanged.
|
|
506
533
|
"""
|
|
@@ -531,6 +558,7 @@ def auth_login(
|
|
|
531
558
|
storage=storage,
|
|
532
559
|
method=method,
|
|
533
560
|
no_browser=no_browser,
|
|
561
|
+
device=device,
|
|
534
562
|
)
|
|
535
563
|
|
|
536
564
|
executor.run(
|
|
@@ -199,7 +199,9 @@ def _with_outcome(service: Any, data: dict[str, Any]) -> dict[str, Any]:
|
|
|
199
199
|
gets its copied counts and per-view runs (no read). The job succeeded either way, so
|
|
200
200
|
a view that cannot be read is an ``outcome_error``, never a failed wait.
|
|
201
201
|
"""
|
|
202
|
-
|
|
202
|
+
result = job_outcome.with_derived_datasets(data.get("result"))
|
|
203
|
+
data = {**data, "result": result} if result is not data.get("result") else data
|
|
204
|
+
copied = job_outcome.copy_outcome(result)
|
|
203
205
|
if copied is not None and data.get("operation") == job_outcome.COPY_PROJECT_OPERATION:
|
|
204
206
|
return {**data, "outcome": copied}
|
|
205
207
|
target = job_outcome.view_target(data)
|
|
@@ -36,6 +36,7 @@ from mammoth_cli.runtime.confirm import (
|
|
|
36
36
|
)
|
|
37
37
|
from mammoth_cli.runtime.invocation import Invocation
|
|
38
38
|
from mammoth_cli.runtime.session import open_service, resolved_project
|
|
39
|
+
from mammoth_cli.services import job_outcome
|
|
39
40
|
|
|
40
41
|
HandlerResult = tuple[Any, dict[str, Any]]
|
|
41
42
|
|
|
@@ -299,7 +300,7 @@ def project_copy(invocation: Invocation) -> HandlerResult:
|
|
|
299
300
|
_forward_optional(document, kwargs, _COPY_OPTIONAL)
|
|
300
301
|
with open_service(invocation) as (service, auth):
|
|
301
302
|
queued = service.call(_symbol(invocation), **kwargs)
|
|
302
|
-
data = service.wait_if_job(queued)
|
|
303
|
+
data = job_outcome.with_derived_datasets(service.wait_if_job(queued))
|
|
303
304
|
return data, _meta(invocation, auth.workspace_id, source_id)
|
|
304
305
|
|
|
305
306
|
|
|
@@ -45,6 +45,7 @@ _PROTECTED_INPUT_PATH = "/private/path/request.json"
|
|
|
45
45
|
# Commands whose useful example the SDK signature cannot express: a distinct
|
|
46
46
|
# second id, an optional-but-central input field, or two views to compare.
|
|
47
47
|
_FIXED_EXAMPLES: dict[str, tuple[tuple[str, ...], dict[str, Any]]] = {
|
|
48
|
+
"view.optimize": (("123",), {}),
|
|
48
49
|
"project.check": (("123", "456"), {}),
|
|
49
50
|
"view.data.profile": (("123",), {"target": "Churn"}),
|
|
50
51
|
"workflow.canvas": (
|
|
@@ -213,6 +213,48 @@ def _resolve_dataset_id(
|
|
|
213
213
|
return dataset_id
|
|
214
214
|
|
|
215
215
|
|
|
216
|
+
def _check_parent_matches(view_id: int, given: int, real: int) -> None:
|
|
217
|
+
"""Refuse a DATASET_ID that is not the view's parent, before any write."""
|
|
218
|
+
if given == real:
|
|
219
|
+
return
|
|
220
|
+
raise CliError(
|
|
221
|
+
code=CODE_INVALID_ARGUMENT,
|
|
222
|
+
message=f"view {view_id} belongs to dataset {real}, not {given}",
|
|
223
|
+
exit_status=EXIT_USAGE,
|
|
224
|
+
hint=f"Run it again with dataset {real}, or leave DATASET_ID out: mammoth looks it up.",
|
|
225
|
+
details={"view_id": view_id, "given_dataset_id": given, "dataset_id": real},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _view_parent_dataset_id(service: Any, invocation: Invocation, view_id: int) -> int:
|
|
230
|
+
"""The view's own dataset: the remembered parent, else one read of the view's row."""
|
|
231
|
+
profile_name = _profile_name(invocation)
|
|
232
|
+
workspace_id = getattr(service, "_workspace_id", None)
|
|
233
|
+
remembered = parents.lookup(profile_name, workspace_id, view_id)
|
|
234
|
+
if remembered is not None:
|
|
235
|
+
return remembered
|
|
236
|
+
dataset_id = int(service.call(_FIND_DATASET_SYMBOL, dataview_id=view_id))
|
|
237
|
+
parents.remember(profile_name, workspace_id, {view_id: dataset_id})
|
|
238
|
+
return dataset_id
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _verified_dataset_id(
|
|
242
|
+
service: Any, invocation: Invocation, view_id: int, document: dict[str, Any]
|
|
243
|
+
) -> int:
|
|
244
|
+
"""The view's dataset for a write, so the caller never has to supply it.
|
|
245
|
+
|
|
246
|
+
A DATASET_ID (positional or ``dataset_id`` input field) is checked against the
|
|
247
|
+
view's real parent and refused when it differs; with none, the parent is looked up.
|
|
248
|
+
"""
|
|
249
|
+
explicit = _int_positional_at(invocation, 1, "dataset id")
|
|
250
|
+
if explicit is None and document.get(_DATASET_ID_FIELD) is not None:
|
|
251
|
+
explicit = _int_field(document[_DATASET_ID_FIELD], _DATASET_ID_FIELD)
|
|
252
|
+
real = _view_parent_dataset_id(service, invocation, view_id)
|
|
253
|
+
if explicit is not None:
|
|
254
|
+
_check_parent_matches(view_id, explicit, real)
|
|
255
|
+
return real
|
|
256
|
+
|
|
257
|
+
|
|
216
258
|
def _resolve_dataset_id_for_settle(
|
|
217
259
|
service: Any,
|
|
218
260
|
invocation: Invocation,
|
|
@@ -1030,7 +1072,7 @@ def view_optimize(invocation: Invocation) -> HandlerResult:
|
|
|
1030
1072
|
kwargs: dict[str, Any] = {"dataview_id": dataview_id, "project_id": project_id}
|
|
1031
1073
|
_forward_optional(document, kwargs, ("apply_rules",))
|
|
1032
1074
|
with open_service(invocation) as (service, auth):
|
|
1033
|
-
kwargs["dataset_id"] =
|
|
1075
|
+
kwargs["dataset_id"] = _verified_dataset_id(service, invocation, dataview_id, document)
|
|
1034
1076
|
data = service.call(_symbol(invocation), **kwargs)
|
|
1035
1077
|
if isinstance(data, dict) and data.get("job_id") is not None:
|
|
1036
1078
|
settled = service.wait_if_job({"job_id": data["job_id"]})
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
"""Browser sign-in (OAuth 2.0 authorization code + PKCE)
|
|
1
|
+
"""Browser sign-in (OAuth 2.0 authorization code + PKCE), device-code sign-in
|
|
2
|
+
(RFC 8628) and token refresh.
|
|
2
3
|
|
|
3
4
|
``mammoth auth login`` opens the Mammoth consent page, receives the code on a
|
|
4
5
|
loopback listener (RFC 8252), and exchanges it for a 1 h ``mm_`` access token
|
|
5
6
|
plus a rotating refresh token. The CLI is a public client: it holds no secret.
|
|
6
7
|
|
|
8
|
+
``mammoth auth login --device`` is for a machine with no browser: it prints a
|
|
9
|
+
short code and a URL to open on any other device, then polls the token endpoint
|
|
10
|
+
until the code is approved, denied or expired.
|
|
11
|
+
|
|
7
12
|
The refresh token rotates on every use, and replaying an already-used one
|
|
8
13
|
revokes the whole connection on the server. Two CLI processes must therefore
|
|
9
14
|
never refresh at once: :func:`refresh_session` takes a file lock, re-reads the
|
|
@@ -52,6 +57,14 @@ _LOCK_WAIT_SECONDS = 60.0
|
|
|
52
57
|
CODE_LOGIN_EXPIRED = "login_expired"
|
|
53
58
|
CODE_OAUTH_UNAVAILABLE = "oauth_unavailable"
|
|
54
59
|
CODE_OAUTH_LOGIN_FAILED = "oauth_login_failed"
|
|
60
|
+
CODE_DEVICE_LOGIN_EXPIRED = "device_login_expired"
|
|
61
|
+
|
|
62
|
+
#: RFC 8628 §3.4: the grant type the device sign-in polls with.
|
|
63
|
+
DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
|
|
64
|
+
#: RFC 8628 §3.5: a slow_down answer adds this many seconds to the interval.
|
|
65
|
+
SLOW_DOWN_STEP_SECONDS = 5
|
|
66
|
+
#: Added to every wait so a poll never lands a hair before the server's interval.
|
|
67
|
+
POLL_MARGIN_SECONDS = 1.0
|
|
55
68
|
|
|
56
69
|
|
|
57
70
|
def login_expired_error() -> CliError:
|
|
@@ -324,6 +337,114 @@ def browser_login(
|
|
|
324
337
|
return session_from_grant(grant, client_id)
|
|
325
338
|
|
|
326
339
|
|
|
340
|
+
# --- device code (RFC 8628) -----------------------------------------------------
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@dataclass(frozen=True)
|
|
344
|
+
class DeviceChallenge:
|
|
345
|
+
"""What the server issues to start a device sign-in."""
|
|
346
|
+
|
|
347
|
+
device_code: str
|
|
348
|
+
user_code: str
|
|
349
|
+
verification_uri: str
|
|
350
|
+
verification_uri_complete: str
|
|
351
|
+
expires_in: int
|
|
352
|
+
interval: int
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def parse_device_challenge(body: dict[str, object]) -> DeviceChallenge:
|
|
356
|
+
"""Read the device-authorization answer; an unreadable one is a failed login."""
|
|
357
|
+
try:
|
|
358
|
+
return DeviceChallenge(
|
|
359
|
+
device_code=str(body["device_code"]),
|
|
360
|
+
user_code=str(body["user_code"]),
|
|
361
|
+
verification_uri=str(body["verification_uri"]),
|
|
362
|
+
verification_uri_complete=str(body.get("verification_uri_complete") or ""),
|
|
363
|
+
expires_in=int(str(body["expires_in"])),
|
|
364
|
+
interval=int(str(body["interval"])),
|
|
365
|
+
)
|
|
366
|
+
except (ValueError, KeyError, TypeError) as exc:
|
|
367
|
+
raise _login_failed("Mammoth's sign-in service returned an unreadable answer.") from exc
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def start_device_login(base_url: str, *, client_id: str) -> DeviceChallenge:
|
|
371
|
+
"""Ask Mammoth for a device code and the code the person will type."""
|
|
372
|
+
try:
|
|
373
|
+
status, body = sdk_oauth.device_authorization_request(base_url, {"client_id": client_id})
|
|
374
|
+
except sdk_oauth.OAuthTransportError as exc:
|
|
375
|
+
raise CliError(
|
|
376
|
+
code="network_error",
|
|
377
|
+
message="Could not reach Mammoth's sign-in service.",
|
|
378
|
+
exit_status=EXIT_AUTH,
|
|
379
|
+
hint="Check your connection and try again.",
|
|
380
|
+
) from exc
|
|
381
|
+
if status != 200 or not isinstance(body, dict):
|
|
382
|
+
raise _login_failed(f"Mammoth refused to start a device sign-in (HTTP {status}).")
|
|
383
|
+
return parse_device_challenge(body)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def read_poll(status: int, body: dict[str, object], interval: int) -> TokenGrant | int:
|
|
387
|
+
"""Interpret one poll answer.
|
|
388
|
+
|
|
389
|
+
Returns the tokens once approved, or the seconds to wait before the next
|
|
390
|
+
poll (longer after ``slow_down``).
|
|
391
|
+
|
|
392
|
+
Raises:
|
|
393
|
+
CliError: ``oauth_login_failed`` when the person denied the sign-in or
|
|
394
|
+
the answer is not one RFC 8628 lets the client retry on;
|
|
395
|
+
``device_login_expired`` when the code ran out.
|
|
396
|
+
"""
|
|
397
|
+
if status == 200:
|
|
398
|
+
return _parse_grant(body)
|
|
399
|
+
error = str(body.get("error", ""))
|
|
400
|
+
if error == "authorization_pending":
|
|
401
|
+
return interval
|
|
402
|
+
if error == "slow_down":
|
|
403
|
+
return interval + SLOW_DOWN_STEP_SECONDS
|
|
404
|
+
if error == "access_denied":
|
|
405
|
+
raise _login_failed("The sign-in was denied in the browser.")
|
|
406
|
+
if error == "expired_token":
|
|
407
|
+
raise device_login_expired_error()
|
|
408
|
+
raise _login_failed(f"Mammoth refused the sign-in ({error or f'HTTP {status}'}).")
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def device_login_expired_error() -> CliError:
|
|
412
|
+
return CliError(
|
|
413
|
+
code=CODE_DEVICE_LOGIN_EXPIRED,
|
|
414
|
+
message="The sign-in code expired before it was approved.",
|
|
415
|
+
exit_status=EXIT_AUTH,
|
|
416
|
+
hint="Run `mammoth auth login --device` again and approve the new code.",
|
|
417
|
+
recovery_commands=["mammoth auth login --device"],
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def device_login(base_url: str, *, client_id: str, say: Callable[[str], None]) -> OAuthSession:
|
|
422
|
+
"""Run one device sign-in: print the code, poll until it is approved."""
|
|
423
|
+
challenge = start_device_login(base_url, client_id=client_id)
|
|
424
|
+
say(f"To sign in, open {challenge.verification_uri} on any device and enter:")
|
|
425
|
+
say(f" {challenge.user_code}")
|
|
426
|
+
if challenge.verification_uri_complete:
|
|
427
|
+
say(f"Or open this link directly:\n {challenge.verification_uri_complete}")
|
|
428
|
+
say("Waiting for approval ...")
|
|
429
|
+
deadline = time.monotonic() + challenge.expires_in
|
|
430
|
+
interval = challenge.interval
|
|
431
|
+
while time.monotonic() < deadline:
|
|
432
|
+
time.sleep(interval + POLL_MARGIN_SECONDS)
|
|
433
|
+
status, body = _post_token(
|
|
434
|
+
base_url,
|
|
435
|
+
{
|
|
436
|
+
"grant_type": DEVICE_GRANT_TYPE,
|
|
437
|
+
"device_code": challenge.device_code,
|
|
438
|
+
"client_id": client_id,
|
|
439
|
+
},
|
|
440
|
+
)
|
|
441
|
+
step = read_poll(status, body, interval)
|
|
442
|
+
if isinstance(step, TokenGrant):
|
|
443
|
+
return session_from_grant(step, client_id)
|
|
444
|
+
interval = step
|
|
445
|
+
raise device_login_expired_error()
|
|
446
|
+
|
|
447
|
+
|
|
327
448
|
# --- refresh under a file lock ----------------------------------------------------
|
|
328
449
|
|
|
329
450
|
|
|
@@ -89,6 +89,7 @@ ERROR_SUMMARIES: dict[str, str] = {
|
|
|
89
89
|
"authentication_failed": "Mammoth didn't accept the sign-in for this step.",
|
|
90
90
|
"authorization_required": "You don't have permission to do this.",
|
|
91
91
|
"capability_not_found": "The assistant tried something Mammoth doesn't support.",
|
|
92
|
+
"cli_key_expired": "The saved API key has expired, so the assistant needs to sign in again.",
|
|
92
93
|
"cli_keys_need_browser_sign_in": "Managing API keys needs a browser sign-in, not an API token.",
|
|
93
94
|
"cli_keys_not_allowed": "This server doesn't let a browser sign-in create API keys.",
|
|
94
95
|
"confirmation_declined": "This step was not confirmed, so nothing was changed.",
|
|
@@ -97,6 +98,7 @@ ERROR_SUMMARIES: dict[str, str] = {
|
|
|
97
98
|
"The confirmation was for a different item, so nothing was changed."
|
|
98
99
|
),
|
|
99
100
|
"conflict": "This conflicts with the current state of your data, so nothing was changed.",
|
|
101
|
+
"device_login_expired": "The sign-in code expired before it was approved.",
|
|
100
102
|
"dry_run_targets_unresolvable": (
|
|
101
103
|
"The assistant couldn't tell exactly which items this would change, so nothing was changed."
|
|
102
104
|
),
|
|
@@ -284,7 +286,7 @@ def no_saved_project_error() -> CliError:
|
|
|
284
286
|
)
|
|
285
287
|
|
|
286
288
|
|
|
287
|
-
EMBEDDED_AUTH_HINT = "Your session expired or is
|
|
289
|
+
EMBEDDED_AUTH_HINT = "Your session expired or is invalid; ask the user to reload the page."
|
|
288
290
|
EMBEDDED_NO_PROFILE_HINT = "This environment keeps no profiles; the login comes from the host."
|
|
289
291
|
|
|
290
292
|
|
|
@@ -10,6 +10,7 @@ makes a request.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
+
import shlex
|
|
13
14
|
from collections.abc import Iterator
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
@@ -113,7 +114,7 @@ def _entry(record: dict[str, Any]) -> dict[str, Any]:
|
|
|
113
114
|
entry["stored_suggestions"] = suggestions
|
|
114
115
|
entry["fix"] = (
|
|
115
116
|
f"mammoth dataset interpretation preview {dataset_id} --input "
|
|
116
|
-
+ json.dumps({"user_instruction": suggestions[0]})
|
|
117
|
+
+ shlex.quote(json.dumps({"user_instruction": suggestions[0]}))
|
|
117
118
|
+ "; then mammoth dataset interpretation confirm "
|
|
118
119
|
+ f"{dataset_id} with the same input"
|
|
119
120
|
)
|
|
@@ -91,6 +91,26 @@ def view_outcome(record: Any, target: ViewTarget) -> dict[str, Any]:
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
def with_derived_datasets(result: Any) -> Any:
|
|
95
|
+
"""Add ``derived_datasets`` beside a copy's ``derived``, whose direction misleads.
|
|
96
|
+
|
|
97
|
+
The backend records ``derived`` as ``{"5214": [5224]}``: dataset 5214 is derived,
|
|
98
|
+
and view 5224 exports into it. Read as "view 5224 of dataset 5214", it sent an agent
|
|
99
|
+
to the wrong parent. ``derived_datasets`` says the same thing in words:
|
|
100
|
+
``[{"dataset_id": 5214, "from_view_ids": [5224]}]``. ``derived`` stays as it was.
|
|
101
|
+
"""
|
|
102
|
+
derived = result.get("derived") if isinstance(result, dict) else None
|
|
103
|
+
if not isinstance(derived, dict) or not derived:
|
|
104
|
+
return result
|
|
105
|
+
return {
|
|
106
|
+
**result,
|
|
107
|
+
"derived_datasets": [
|
|
108
|
+
{"dataset_id": _int_or_none(dataset) or dataset, "from_view_ids": list(views or [])}
|
|
109
|
+
for dataset, views in sorted(derived.items(), key=lambda item: str(item[0]))
|
|
110
|
+
],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
94
114
|
def copy_outcome(result: Any) -> dict[str, Any] | None:
|
|
95
115
|
"""A project copy's outcome: what was copied, what was skipped, and how each run ended.
|
|
96
116
|
|
|
@@ -107,6 +127,9 @@ def copy_outcome(result: Any) -> dict[str, Any] | None:
|
|
|
107
127
|
"views": len(result.get("view_map") or {}),
|
|
108
128
|
"skipped": len(result.get("skipped") or []),
|
|
109
129
|
}
|
|
130
|
+
derived = with_derived_datasets(result).get("derived_datasets")
|
|
131
|
+
if derived:
|
|
132
|
+
outcome["derived_datasets"] = derived
|
|
110
133
|
runs = result.get("runs")
|
|
111
134
|
if isinstance(runs, dict):
|
|
112
135
|
outcome["runs"] = _run_summary(runs, result.get("run_views"))
|
|
@@ -258,6 +258,8 @@ def _not_allowed_reason(body: Any, status: int | None) -> str | None:
|
|
|
258
258
|
|
|
259
259
|
CODE_CLI_KEYS_NOT_ALLOWED = "cli_keys_not_allowed"
|
|
260
260
|
CODE_CLI_KEYS_NEED_BROWSER_SIGN_IN = "cli_keys_need_browser_sign_in"
|
|
261
|
+
CODE_CLI_KEY_EXPIRED = "cli_key_expired"
|
|
262
|
+
_TOKEN_EXPIRED_NAME = "API_TOKEN_EXPIRED" # noqa: S105 - an error name
|
|
261
263
|
_TOKEN_REFUSED_NAME = "INVALID_TOKEN_FOR_CLIENT_APPS" # noqa: S105 - an error name
|
|
262
264
|
|
|
263
265
|
|
|
@@ -267,6 +269,12 @@ def _is_cli_keys_refused(exc: BaseException) -> bool:
|
|
|
267
269
|
return isinstance(body, dict) and body.get("name") == _TOKEN_REFUSED_NAME
|
|
268
270
|
|
|
269
271
|
|
|
272
|
+
def _is_token_expired(exc: BaseException) -> bool:
|
|
273
|
+
"""True for the server's 401 naming an API key that is past its expiry."""
|
|
274
|
+
body = getattr(exc, "response_body", None)
|
|
275
|
+
return isinstance(body, dict) and body.get("name") == _TOKEN_EXPIRED_NAME
|
|
276
|
+
|
|
277
|
+
|
|
270
278
|
def _stored_credential_kind(profile: str | None) -> str | None:
|
|
271
279
|
"""The stored credential kind of ``profile`` (the selected one when None)."""
|
|
272
280
|
credential = credentials.load_credential(profile or profiles.get_selected())
|
|
@@ -303,6 +311,17 @@ def map_sdk_exception(
|
|
|
303
311
|
details=getattr(exc, "details", None),
|
|
304
312
|
)
|
|
305
313
|
|
|
314
|
+
if isinstance(exc, MammothAuthError) and _is_token_expired(exc):
|
|
315
|
+
return CliError(
|
|
316
|
+
code=CODE_CLI_KEY_EXPIRED,
|
|
317
|
+
message="This API key has expired; run `mammoth auth login` to sign in again.",
|
|
318
|
+
exit_status=EXIT_AUTH,
|
|
319
|
+
hint="Sign in again to get a new key.",
|
|
320
|
+
details=_metadata(exc),
|
|
321
|
+
request_id=exc.request_id,
|
|
322
|
+
recovery_commands=["mammoth auth login"],
|
|
323
|
+
)
|
|
324
|
+
|
|
306
325
|
if isinstance(exc, MammothAuthError):
|
|
307
326
|
mismatch = _workspace_mismatch_error(exc, workspace_id)
|
|
308
327
|
if mismatch is not None:
|
|
@@ -535,7 +554,9 @@ def map_sdk_exception(
|
|
|
535
554
|
message="Mammoth denied access to the requested resource or operation.",
|
|
536
555
|
exit_status=EXIT_AUTH,
|
|
537
556
|
hint=(
|
|
538
|
-
"Check the
|
|
557
|
+
"Check the ids and scope first: a view id given with another dataset's id, "
|
|
558
|
+
"or an id from a different project, is refused the same way. Then ask an "
|
|
559
|
+
"administrator for permission. "
|
|
539
560
|
"An API token limited to one project gets this in every other "
|
|
540
561
|
"project, including one it created: pass that project's --project ID, "
|
|
541
562
|
"or log in with a token created without a project."
|
|
@@ -1231,6 +1231,9 @@ EXACT_PARENT_HELP = (
|
|
|
1231
1231
|
# instead and hit a not-found on the (unrelated) source view's own parent
|
|
1232
1232
|
# (WPP evidence c38/c39).
|
|
1233
1233
|
_EXACT_PARENT_HELP_OVERRIDES: dict[str, str] = {
|
|
1234
|
+
"view.optimize": (
|
|
1235
|
+
"Optional parent dataset ID. Leave it out and the CLI looks up the view's dataset. A value that is not the view's dataset is refused before the write."
|
|
1236
|
+
),
|
|
1234
1237
|
"view.export.dataset": (
|
|
1235
1238
|
"Exact parent dataset ID of the SOURCE view being exported -- not the "
|
|
1236
1239
|
"destination. Required for this command: pass it here or as the "
|