mammoth-cli 2.2.29__tar.gz → 2.2.31__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.
Files changed (257) hide show
  1. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/PKG-INFO +1 -1
  2. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +1 -1
  4. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +2 -2
  5. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +11 -1
  6. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/automation.py +46 -1
  7. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/batch.py +159 -6
  8. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/project.py +37 -6
  9. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/view.py +232 -50
  10. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/view_ops.py +53 -1
  11. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/errors/envelope.py +4 -0
  12. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/command_contract.py +9 -0
  13. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/input_fields.py +4 -11
  14. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/pyproject.toml +1 -1
  15. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/automation.yaml +3 -1
  16. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/batch.yaml +4 -5
  17. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/project.yaml +1 -1
  18. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/view.yaml +18 -13
  19. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/.gitignore +0 -0
  20. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/LICENSE +0 -0
  21. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/README.md +0 -0
  22. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/__main__.py +0 -0
  23. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/app.py +0 -0
  24. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +0 -0
  25. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  26. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  27. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  28. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
  29. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  30. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  31. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  32. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  33. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  34. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  35. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  36. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  37. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  38. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
  39. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  40. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  41. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  42. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  43. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  44. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  45. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
  46. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-style.md +0 -0
  47. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-template.md +0 -0
  48. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  49. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  50. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  51. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  52. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  53. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  54. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  55. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  56. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/link.md +0 -0
  57. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  58. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  59. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  60. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  61. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  62. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  63. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  64. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  65. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  66. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  67. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  68. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  69. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  70. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  71. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  72. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
  73. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
  74. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  75. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  76. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  77. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  78. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  79. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  80. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  81. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  82. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  83. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  84. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
  85. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/database-import.md +0 -0
  86. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  87. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  88. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  89. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  90. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
  91. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  92. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  93. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  94. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  95. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  96. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  97. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  98. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/__init__.py +0 -0
  99. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/activity.py +0 -0
  100. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/addon.py +0 -0
  101. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/agent.py +0 -0
  102. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/ai.py +0 -0
  103. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/annotation.py +0 -0
  104. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/auth.py +0 -0
  105. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/billing.py +0 -0
  106. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/browse.py +0 -0
  107. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/calc.py +0 -0
  108. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/capability.py +0 -0
  109. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/client_app.py +0 -0
  110. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/completion.py +0 -0
  111. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/config.py +0 -0
  112. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/connector.py +0 -0
  113. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/context.py +0 -0
  114. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/dashboard.py +0 -0
  115. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/data_app.py +0 -0
  116. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/dataset.py +0 -0
  117. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/doctor.py +0 -0
  118. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/external_key.py +0 -0
  119. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/file.py +0 -0
  120. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/folder.py +0 -0
  121. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/job.py +0 -0
  122. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/link.py +0 -0
  123. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/notification.py +0 -0
  124. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/parameter.py +0 -0
  125. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/registry.py +0 -0
  126. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/report.py +0 -0
  127. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/schedule.py +0 -0
  128. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/schema.py +0 -0
  129. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/skill.py +0 -0
  130. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/snippet.py +0 -0
  131. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/support.py +0 -0
  132. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/template.py +0 -0
  133. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/trash.py +0 -0
  134. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/upgrade.py +0 -0
  135. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/user.py +0 -0
  136. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/view_profile.py +0 -0
  137. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/webhook.py +0 -0
  138. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/workflow.py +0 -0
  139. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/commands/workspace.py +0 -0
  140. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/context/__init__.py +0 -0
  141. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/context/credentials.py +0 -0
  142. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/context/endpoint.py +0 -0
  143. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/context/profiles.py +0 -0
  144. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/context/resolver.py +0 -0
  145. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/contracts/__init__.py +0 -0
  146. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/contracts/auth.py +0 -0
  147. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/embed.py +0 -0
  148. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/errors/__init__.py +0 -0
  149. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/manifest/__init__.py +0 -0
  150. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/manifest/loader.py +0 -0
  151. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/messages/__init__.py +0 -0
  152. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/output/__init__.py +0 -0
  153. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/output/envelope.py +0 -0
  154. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/output/normalize.py +0 -0
  155. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/output/policy.py +0 -0
  156. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/output/progress.py +0 -0
  157. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/output/render.py +0 -0
  158. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/py.typed +0 -0
  159. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/__init__.py +0 -0
  160. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/confirm.py +0 -0
  161. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dataset_health.py +0 -0
  162. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dryrun.py +0 -0
  163. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/dryrun_targets.py +0 -0
  164. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/embedded.py +0 -0
  165. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/executor.py +0 -0
  166. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/input_loader.py +0 -0
  167. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/intent_only.py +0 -0
  168. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/invocation.py +0 -0
  169. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/new_data.py +0 -0
  170. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/options.py +0 -0
  171. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/parents.py +0 -0
  172. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/runlog.py +0 -0
  173. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/session.py +0 -0
  174. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/state.py +0 -0
  175. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/strict.py +0 -0
  176. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/updates.py +0 -0
  177. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/validate.py +0 -0
  178. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/runtime/verify.py +0 -0
  179. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/__init__.py +0 -0
  180. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/argspec.py +0 -0
  181. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/board_values.py +0 -0
  182. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/coerce.py +0 -0
  183. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/conditional_format.py +0 -0
  184. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/conditions.py +0 -0
  185. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_filters.py +0 -0
  186. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_pages.py +0 -0
  187. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/dashboard_review.py +0 -0
  188. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/data_profile.py +0 -0
  189. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/data_quality.py +0 -0
  190. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/dispatch.py +0 -0
  191. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/factory.py +0 -0
  192. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/listing.py +0 -0
  193. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/local_time.py +0 -0
  194. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/mapping.py +0 -0
  195. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/openapi_types.py +0 -0
  196. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/positionals.py +0 -0
  197. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/protocol.py +0 -0
  198. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/read_queries.py +0 -0
  199. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/sdk_service.py +0 -0
  200. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/sql_check.py +0 -0
  201. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/stored_stats.py +0 -0
  202. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/testing.py +0 -0
  203. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/text_dates.py +0 -0
  204. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/type_system.py +0 -0
  205. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/services/write_impact.py +0 -0
  206. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/skills/__init__.py +0 -0
  207. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/skills/installer.py +0 -0
  208. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/skills/steering.py +0 -0
  209. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/mammoth_cli/testing.py +0 -0
  210. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/activity.yaml +0 -0
  211. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/addon.yaml +0 -0
  212. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/agent.yaml +0 -0
  213. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/ai.yaml +0 -0
  214. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/annotation.yaml +0 -0
  215. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/auth.yaml +0 -0
  216. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/billing.yaml +0 -0
  217. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/browse.yaml +0 -0
  218. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/calc.yaml +0 -0
  219. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/capability.yaml +0 -0
  220. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/client-app.yaml +0 -0
  221. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/completion.yaml +0 -0
  222. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/config.yaml +0 -0
  223. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/connector.yaml +0 -0
  224. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/context.yaml +0 -0
  225. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/dashboard.yaml +0 -0
  226. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/data-app.yaml +0 -0
  227. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/dataset.yaml +0 -0
  228. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/doctor.yaml +0 -0
  229. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/external-key.yaml +0 -0
  230. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/file.yaml +0 -0
  231. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/folder.yaml +0 -0
  232. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/job.yaml +0 -0
  233. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/link.yaml +0 -0
  234. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/log.yaml +0 -0
  235. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/notification.yaml +0 -0
  236. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/parameter.yaml +0 -0
  237. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/report.yaml +0 -0
  238. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/schedule.yaml +0 -0
  239. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/schema.yaml +0 -0
  240. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/skill.yaml +0 -0
  241. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/snippet.yaml +0 -0
  242. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/support.yaml +0 -0
  243. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/template.yaml +0 -0
  244. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/trash.yaml +0 -0
  245. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/upgrade.yaml +0 -0
  246. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/user.yaml +0 -0
  247. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/version.yaml +0 -0
  248. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/webhook.yaml +0 -0
  249. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/workflow.yaml +0 -0
  250. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/commands/workspace.yaml +0 -0
  251. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/edits-target.source.yaml +0 -0
  252. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/openapi-operations.yaml +0 -0
  253. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/schema-v1.json +0 -0
  254. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/sdk-catalog.source.yaml +0 -0
  255. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/manifests/sdk-methods.yaml +0 -0
  256. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/spec/openapi/metadata.json +0 -0
  257. {mammoth_cli-2.2.29 → mammoth_cli-2.2.31}/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.29
3
+ Version: 2.2.31
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
@@ -6,7 +6,7 @@ not implement a second HTTP client and does not call private SDK members.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "2.2.29"
9
+ __version__ = "2.2.31"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -106,7 +106,7 @@ Checkpoint Alert are the `view data-check` and `view checkpoint` families.
106
106
  | Add columns from another view by matching keys | Join | `join` (`INNER`, `LEFT`, `RIGHT`, `OUTER`) |
107
107
  | Bring one value per key from another view | Lookup | `lookup` |
108
108
  | Totals, counts, averages per group | Group & Pivot | `pivot` (replaces the view's columns) |
109
- | A rows-by-columns summary table | Group & Pivot (crosstab) | `crosstab` (writes a new dataset) |
109
+ | A rows-by-columns summary table | Group & Pivot (crosstab) | `crosstab` (writes a new dataset, and keeps rewriting it on every pipeline run of the view) |
110
110
  | Turn wide columns into label/value rows | Unpivot | `unnest` |
111
111
  | Read fields out of a JSON column | Extract JSON | `json-extract` |
112
112
  | Classify, tag or summarise rows with AI | Generative AI | `ai` |
@@ -6,7 +6,7 @@ Every command returns the standard JSON envelope. On nonzero exit, read the erro
6
6
 
7
7
  Run: `mammoth batch bulk-delete`. Exact input fields: `mammoth schema get batch.bulk-delete`.
8
8
 
9
- Example: `mammoth batch bulk-delete 123`. Illustrative only: append `--yes` after observing an owned target.
9
+ Example: `mammoth batch bulk-delete 123 --input '{"ids": [11, 12]}'`. Illustrative only: append `--yes` after observing an owned target.
10
10
 
11
11
  Result: `BatchBulkDeleteResult`; mutation `destructive`, confirmation `prompt_or_yes`, wait policy `not_async`.
12
12
 
@@ -16,7 +16,7 @@ Status on release: ran once on CLI 2.0.14 — write sweep 2026-09-18: exit 0 on
16
16
 
17
17
  Run: `mammoth batch create`. Exact input fields: `mammoth schema get batch.create`.
18
18
 
19
- Example: `mammoth batch create 123 123 --input '{"mapping": [{"source_c_name": "column_1", "destination_c_name": "column_1", "expected_destination_c_type": "TEXT"}]}'`. Placeholders are illustrative; resolve IDs and input from observed reads.
19
+ Example: `mammoth batch create 123 123 --input '{"mapping": {"column_1": "column_1"}}'`. Placeholders are illustrative; resolve IDs and input from observed reads.
20
20
 
21
21
  Result: `BatchCreateResult`; mutation `benign_mutation`, confirmation `none`, wait policy `returns_job`.
22
22
 
@@ -84,7 +84,7 @@ the run log. The connector command is `external_effect` with
84
84
  mammoth schema get view.export.postgres # read secret_fields, required fields
85
85
  # operator writes /private/path/request.json (mode 0600):
86
86
  # {"host":"db.example","port":5432,"database":"analytics","table":"exports",
87
- # "username":"agent","password":"…","dataset_id":DATASET_ID}
87
+ # "username":"agent","password":"…","replace_table":true,"dataset_id":DATASET_ID}
88
88
  mammoth view export postgres VIEW_ID DATASET_ID --project PROJECT_ID \
89
89
  --input /private/path/request.json --yes
90
90
  mammoth view export list VIEW_ID DATASET_ID --project PROJECT_ID
@@ -93,6 +93,16 @@ mammoth view export list VIEW_ID DATASET_ID --project PROJECT_ID
93
93
  If the operator has not supplied such a file, stop and ask for it; do not
94
94
  compose the body yourself from values seen in chat.
95
95
 
96
+ A database export (`postgres`, `mysql`, `mssql`, `redshift`, `bigquery` with
97
+ `export_type` REPLACE, and `publish-db`) drops and recreates its destination
98
+ table now and on every later run of the view's pipeline, so any existing table
99
+ of that name is lost. It is refused (`replace_table_not_acknowledged`) unless
100
+ the input says `"replace_table": true`; only set that for a table the user said
101
+ may be overwritten. `postgres` and `mysql` also cut a table name to 60
102
+ characters and write a different table, so a longer name is refused
103
+ (`table_name_too_long`). The result carries `refreshes_on_pipeline_run` and a
104
+ `note` saying so.
105
+
96
106
  ## Generic export routes
97
107
 
98
108
  `view export csv` and the typed destination commands are the proven path. The
@@ -24,6 +24,7 @@ from mammoth_cli.errors.envelope import (
24
24
  )
25
25
  from mammoth_cli.manifest.loader import command_by_id
26
26
  from mammoth_cli.runtime.confirm import (
27
+ POLICY_CONFIRM_TARGET,
27
28
  POLICY_PROMPT_OR_YES,
28
29
  POLICY_YES_ALWAYS,
29
30
  enforce_confirmation,
@@ -215,6 +216,50 @@ def automation_delete(invocation: Invocation) -> HandlerResult:
215
216
  return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
216
217
 
217
218
 
219
+ _RETENTION_TASK = "apply_retention_policy"
220
+ _UNATTENDED_DELETE_TARGET = "unattended-delete"
221
+
222
+
223
+ def _guard_retention_tasks(invocation: Invocation, tasks: Any) -> Any:
224
+ """Default retention tasks to ``require_approval`` and gate an unattended delete.
225
+
226
+ The route defaults ``require_approval`` to false, so a retention task with
227
+ the default ``delete`` action would hard-delete batches on every run with no
228
+ one approving. Here an omitted ``require_approval`` becomes true; an explicit
229
+ false with a delete action needs ``--yes --confirm unattended-delete``.
230
+ """
231
+ if not isinstance(tasks, list):
232
+ return tasks
233
+ guarded: list[Any] = []
234
+ unattended = False
235
+ for task in tasks:
236
+ details = task.get("details") if isinstance(task, dict) else None
237
+ if not (
238
+ isinstance(task, dict)
239
+ and task.get("task_type") == _RETENTION_TASK
240
+ and isinstance(details, dict)
241
+ ):
242
+ guarded.append(task)
243
+ elif "require_approval" not in details:
244
+ guarded.append({**task, "details": {**details, "require_approval": True}})
245
+ else:
246
+ unattended = unattended or (
247
+ details["require_approval"] is False and details.get("action", "delete") == "delete"
248
+ )
249
+ guarded.append(task)
250
+ if unattended:
251
+ enforce_confirmation(
252
+ invocation,
253
+ policy=POLICY_CONFIRM_TARGET,
254
+ action=(
255
+ "create a retention policy that DELETES matching batches on every run without "
256
+ "approval (require_approval is false)"
257
+ ),
258
+ target=_UNATTENDED_DELETE_TARGET,
259
+ )
260
+ return guarded
261
+
262
+
218
263
  def automation_create(invocation: Invocation) -> HandlerResult:
219
264
  """Create an automation. Name comes from a positional or the ``name`` field.
220
265
 
@@ -232,7 +277,7 @@ def automation_create(invocation: Invocation) -> HandlerResult:
232
277
  hint="Pass the name as a positional argument or a 'name' input field.",
233
278
  )
234
279
  description = document.get("description", "")
235
- tasks = _require_field(document, "tasks")
280
+ tasks = _guard_retention_tasks(invocation, _require_field(document, "tasks"))
236
281
  kwargs: dict[str, Any] = {"name": name, "description": description, "tasks": tasks}
237
282
  _forward_optional(document, kwargs, ("conditions", "condition_mode"))
238
283
  enforce_confirmation(invocation, policy=POLICY_YES_ALWAYS, action="create an automation")
@@ -123,6 +123,144 @@ def batch_get(invocation: Invocation) -> HandlerResult:
123
123
  return data, _meta(invocation, auth.workspace_id, project_id)
124
124
 
125
125
 
126
+ _DATASET_GET_SYMBOL = "mammoth.api.datasets.DatasetsAPI.get"
127
+
128
+
129
+ def _dataset_columns(service: Any, dataset_id: int, side: str) -> list[dict[str, Any]]:
130
+ """The dataset's own schema rows (``c_name``, ``c_id``, ``c_type``), or raise.
131
+
132
+ An unreadable schema is never treated as "no columns": the mapping check
133
+ could not run, so the batch is not created.
134
+ """
135
+ response = service.call(_DATASET_GET_SYMBOL, dataset_id=dataset_id)
136
+ dataset = response.get("dataset") if isinstance(response, dict) else None
137
+ schema = dataset.get("data_schema") if isinstance(dataset, dict) else None
138
+ if not isinstance(schema, list) or not all(
139
+ isinstance(col, dict) and isinstance(col.get("c_name"), str) for col in schema
140
+ ):
141
+ raise CliError(
142
+ code="append_schema_unreadable",
143
+ message=f"Could not read the {side} dataset {dataset_id}'s schema before the batch.",
144
+ hint=f"Read dataset {dataset_id} before creating the batch.",
145
+ details={"side": side, "dataset_id": dataset_id},
146
+ )
147
+ return schema
148
+
149
+
150
+ def _mapping_items(mapping: Any) -> list[Any]:
151
+ """Expand a ``{src: dst}`` mapping into ``ColumnNameMapping`` items, untyped."""
152
+ if isinstance(mapping, dict):
153
+ return [{"source_c_name": src, "destination_c_name": dst} for src, dst in mapping.items()]
154
+ return list(mapping) if isinstance(mapping, list) else []
155
+
156
+
157
+ def _resolve_item(
158
+ item: dict[str, Any], source: list[dict[str, Any]], dest: list[dict[str, Any]]
159
+ ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
160
+ """The (source, destination) schema rows an item names by name or id, else None."""
161
+ by_id = "source_c_id" in item
162
+ field, src_key, dest_key = (
163
+ ("c_id", "source_c_id", "destination_c_id")
164
+ if by_id
165
+ else ("c_name", "source_c_name", "destination_c_name")
166
+ )
167
+ src_col = next((col for col in source if col.get(field) == item.get(src_key)), None)
168
+ dest_col = next((col for col in dest if col.get(field) == item.get(dest_key)), None)
169
+ return src_col, dest_col
170
+
171
+
172
+ def _reject_incomplete_batch_mapping(
173
+ ids: tuple[int, int],
174
+ source_names: set[str],
175
+ mapped_sources: set[str],
176
+ unknown: list[str],
177
+ ) -> None:
178
+ """Refuse a mapping that leaves source columns out or names a missing destination."""
179
+ unmapped = sorted(source_names - mapped_sources)
180
+ if not unmapped and not unknown:
181
+ return
182
+ dataset_id, source_id = ids
183
+ problems = []
184
+ if unknown:
185
+ problems.append(f"destination column(s) not in dataset {dataset_id}: {', '.join(unknown)}")
186
+ if unmapped:
187
+ problems.append(f"source column(s) left unmapped and not appended: {', '.join(unmapped)}")
188
+ raise CliError(
189
+ code="append_mapping_incomplete",
190
+ message=(
191
+ f"mapping for appending dataset {source_id} into dataset {dataset_id} would corrupt "
192
+ f"the append: {'; '.join(problems)}. Unmapped source columns are not appended."
193
+ ),
194
+ exit_status=EXIT_USAGE,
195
+ hint=(
196
+ f"Map every source column of dataset {source_id} to an existing column of dataset "
197
+ f"{dataset_id}."
198
+ ),
199
+ details={"unknown_destinations": unknown, "unmapped_source_columns": unmapped},
200
+ )
201
+
202
+
203
+ def _reject_type_change(item: dict[str, Any], dest_col: dict[str, Any], dataset_id: int) -> None:
204
+ """Refuse an explicit expected type that differs from the destination's real one."""
205
+ expected, actual = item.get("expected_destination_c_type"), dest_col.get("c_type")
206
+ if expected is None or not actual or str(expected).upper() == str(actual).upper():
207
+ return
208
+ raise CliError(
209
+ code="append_type_mismatch",
210
+ message=(
211
+ f"expected_destination_c_type {expected} for destination column "
212
+ f"{dest_col['c_name']!r} of dataset {dataset_id} would change its type from "
213
+ f"{actual}; the append would convert the whole destination column."
214
+ ),
215
+ exit_status=EXIT_USAGE,
216
+ hint=(
217
+ "Omit expected_destination_c_type (the CLI sends the destination's current "
218
+ f"type, {actual})."
219
+ ),
220
+ details={"column": dest_col["c_name"], "expected": expected, "actual": actual},
221
+ )
222
+
223
+
224
+ def _checked_batch_mapping(
225
+ service: Any, dataset_id: int, source_id: int, mapping: Any
226
+ ) -> tuple[list[dict[str, Any]], list[str]]:
227
+ """Validate a batch mapping against both schemas; return typed items and warnings.
228
+
229
+ Refuses unmapped source columns and unknown destinations (the backend appends
230
+ only the mapped columns) and stamps every item with the destination's actual
231
+ current type, so no destination column is silently re-typed. A mapped pair
232
+ whose source and destination types differ is returned as a warning.
233
+ """
234
+ source = _dataset_columns(service, source_id, "source")
235
+ dest = _dataset_columns(service, dataset_id, "target")
236
+ items = [dict(item) for item in _mapping_items(mapping) if isinstance(item, dict)]
237
+ typed: list[dict[str, Any]] = []
238
+ mapped_sources: set[str] = set()
239
+ unknown: list[str] = []
240
+ warnings: list[str] = []
241
+ for item in items:
242
+ src_col, dest_col = _resolve_item(item, source, dest)
243
+ if src_col is not None:
244
+ mapped_sources.add(src_col["c_name"])
245
+ if dest_col is None:
246
+ if item.get("action") != "add_column":
247
+ unknown.append(str(item.get("destination_c_name", item.get("destination_c_id"))))
248
+ typed.append(item)
249
+ continue
250
+ _reject_type_change(item, dest_col, dataset_id)
251
+ item.setdefault("expected_destination_c_type", dest_col.get("c_type"))
252
+ typed.append(item)
253
+ if src_col and src_col.get("c_type") and src_col["c_type"] != dest_col.get("c_type"):
254
+ warnings.append(
255
+ f"mapped column {src_col['c_name']!r} is {str(src_col['c_type']).lower()} but "
256
+ f"destination column {dest_col['c_name']!r} is {str(dest_col['c_type']).lower()}"
257
+ )
258
+ _reject_incomplete_batch_mapping(
259
+ (dataset_id, source_id), {col["c_name"] for col in source}, mapped_sources, unknown
260
+ )
261
+ return typed, warnings
262
+
263
+
126
264
  def batch_create(invocation: Invocation) -> HandlerResult:
127
265
  """Create a batch for a dataset. ``source_id`` and ``mapping`` are required."""
128
266
  project_id = require_project(invocation)
@@ -158,7 +296,12 @@ def batch_create(invocation: Invocation) -> HandlerResult:
158
296
  action=f"create a batch on dataset {dataset_id} and DELETE the source dataset",
159
297
  )
160
298
  with open_service(invocation) as (service, auth):
299
+ kwargs["mapping"], warnings = _checked_batch_mapping(
300
+ service, dataset_id, source_id, mapping
301
+ )
161
302
  data = service.call(_symbol(invocation), **kwargs)
303
+ if warnings and isinstance(data, dict):
304
+ data["mapping_check"] = {"warnings": warnings}
162
305
  return data, _meta(invocation, auth.workspace_id, project_id)
163
306
 
164
307
 
@@ -249,20 +392,30 @@ def batch_delete(invocation: Invocation) -> HandlerResult:
249
392
 
250
393
 
251
394
  def batch_bulk_delete(invocation: Invocation) -> HandlerResult:
252
- """Bulk-delete batches for a dataset. Prompt or ``--yes`` required.
395
+ """Bulk-delete the named batches of a dataset. Prompt or ``--yes`` required.
253
396
 
254
- When no ``ids`` are given, every batch for the dataset is deleted.
397
+ ``ids`` is required: the backend rejects a delete with no batch ids, it
398
+ never deletes "all batches".
255
399
  """
256
400
  project_id = require_project(invocation)
257
401
  dataset_id = _require_int_positional_at(invocation, 0, "dataset id")
258
402
  document = invocation.load_input() or {}
403
+ ids = _require_field(document, "ids")
404
+ if not ids:
405
+ raise CliError(
406
+ code=CODE_INVALID_ARGUMENT,
407
+ message="The 'ids' input field must name at least one batch id.",
408
+ exit_status=EXIT_USAGE,
409
+ hint=f"List batch ids with `mammoth batch list {dataset_id}`.",
410
+ )
411
+ count = len(ids) if isinstance(ids, list) else len(str(ids).split(","))
259
412
  enforce_confirmation(
260
413
  invocation,
261
414
  policy=POLICY_PROMPT_OR_YES,
262
- action=f"delete batches of dataset {dataset_id}",
415
+ action=f"delete {count} batch(es) of dataset {dataset_id}",
263
416
  )
264
- kwargs: dict[str, Any] = {"dataset_id": dataset_id, "project_id": project_id}
265
- _forward_optional(document, kwargs, ("ids",))
266
417
  with open_service(invocation) as (service, auth):
267
- data = service.call(_symbol(invocation), **kwargs)
418
+ data = service.call(
419
+ _symbol(invocation), dataset_id=dataset_id, project_id=project_id, ids=ids
420
+ )
268
421
  return data, _meta(invocation, auth.workspace_id, project_id)
@@ -505,23 +505,54 @@ def project_data_check_list(invocation: Invocation) -> HandlerResult:
505
505
  return _list_with_filters(invocation)
506
506
 
507
507
 
508
+ _PROJECT_MEMBERS_SYMBOL = "mammoth.api.workspace.WorkspaceAPI.list_users"
509
+
510
+
511
+ def _reject_existing_members(service: Any, project_id: int, user_ids: Any) -> None:
512
+ """Refuse an add naming a user who is already a member of the project.
513
+
514
+ The backend treats adding an existing member as a role replacement (it
515
+ revokes the current role, then assigns the given one), so an add can
516
+ silently demote a project admin. Role changes belong to ``project user
517
+ update``.
518
+ """
519
+ members = service.call(_PROJECT_MEMBERS_SYMBOL, project_id=project_id)
520
+ member_ids = {str(m.get("id")) for m in members if isinstance(m, dict)}
521
+ existing = [uid for uid in user_ids if str(uid) in member_ids]
522
+ if existing:
523
+ raise CliError(
524
+ code=CODE_INVALID_ARGUMENT,
525
+ message=(
526
+ f"User(s) {', '.join(str(uid) for uid in existing)} already belong to project "
527
+ f"{project_id}; adding them again would replace their current role."
528
+ ),
529
+ exit_status=EXIT_USAGE,
530
+ hint="Use `mammoth project user update` to change a member's role.",
531
+ details={"existing_user_ids": existing, "project_id": project_id},
532
+ )
533
+
534
+
508
535
  def project_user_add(invocation: Invocation) -> HandlerResult:
509
- """Add users to a project. High-impact: ``--yes --confirm PROJECT_ID``."""
536
+ """Add users to a project. High-impact: ``--yes --confirm PROJECT_ID``.
537
+
538
+ ``role`` is required and no listed user may already be a member (the
539
+ backend would replace that member's role).
540
+ """
510
541
  project_id = _project_id(invocation)
511
542
  document = invocation.load_input()
512
543
  user_ids = _require_input_field(document, "user_ids")
544
+ role = _require_input_field(document, "role")
513
545
  enforce_confirmation(
514
546
  invocation,
515
547
  policy=POLICY_CONFIRM_TARGET,
516
548
  action=f"add users to project {project_id}",
517
549
  target=str(project_id),
518
550
  )
519
- kwargs: dict[str, Any] = {"project_id": project_id, "user_ids": user_ids}
520
- assert document is not None
521
- if "role" in document:
522
- kwargs["role"] = document["role"]
523
551
  with open_service(invocation) as (service, auth):
524
- data = service.call(_symbol(invocation), **kwargs)
552
+ _reject_existing_members(service, project_id, user_ids)
553
+ data = service.call(
554
+ _symbol(invocation), project_id=project_id, user_ids=user_ids, role=role
555
+ )
525
556
  return data, _meta(invocation, auth.workspace_id, project_id)
526
557
 
527
558