mammoth-cli 2.2.28__tar.gz → 2.2.30__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/PKG-INFO +1 -1
  2. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +6 -1
  4. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +2 -2
  5. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/automation.py +46 -1
  6. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/batch.py +159 -6
  7. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/project.py +37 -6
  8. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/view.py +84 -14
  9. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/errors/envelope.py +1 -0
  10. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/input_fields.py +4 -11
  11. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/pyproject.toml +1 -1
  12. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/automation.yaml +3 -1
  13. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/batch.yaml +4 -5
  14. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/project.yaml +1 -1
  15. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/view.yaml +1 -1
  16. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/.gitignore +0 -0
  17. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/LICENSE +0 -0
  18. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/README.md +0 -0
  19. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/__main__.py +0 -0
  20. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/app.py +0 -0
  21. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +0 -0
  22. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  23. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  24. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  25. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
  26. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  27. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  28. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  29. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  30. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  31. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  32. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  33. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  34. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  35. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
  36. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  37. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  38. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  39. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  40. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  41. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  42. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
  43. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-style.md +0 -0
  44. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-template.md +0 -0
  45. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  46. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  47. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  48. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  49. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  50. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  51. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  52. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  53. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/link.md +0 -0
  54. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  55. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  56. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  57. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  58. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  59. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  60. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  61. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  62. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  63. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  64. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  65. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  66. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  67. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  68. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  69. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
  70. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
  71. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  72. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  73. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  74. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  75. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  76. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  77. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  78. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  79. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  80. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  81. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
  82. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/database-import.md +0 -0
  83. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  84. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
  85. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  86. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  87. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  88. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
  89. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  90. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  91. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  92. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  93. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  94. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  95. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  96. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/__init__.py +0 -0
  97. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/activity.py +0 -0
  98. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/addon.py +0 -0
  99. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/agent.py +0 -0
  100. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/ai.py +0 -0
  101. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/annotation.py +0 -0
  102. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/auth.py +0 -0
  103. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/billing.py +0 -0
  104. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/browse.py +0 -0
  105. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/calc.py +0 -0
  106. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/capability.py +0 -0
  107. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/client_app.py +0 -0
  108. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/completion.py +0 -0
  109. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/config.py +0 -0
  110. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/connector.py +0 -0
  111. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/context.py +0 -0
  112. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/dashboard.py +0 -0
  113. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/data_app.py +0 -0
  114. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/dataset.py +0 -0
  115. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/doctor.py +0 -0
  116. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/external_key.py +0 -0
  117. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/file.py +0 -0
  118. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/folder.py +0 -0
  119. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/job.py +0 -0
  120. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/link.py +0 -0
  121. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/notification.py +0 -0
  122. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/parameter.py +0 -0
  123. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/registry.py +0 -0
  124. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/report.py +0 -0
  125. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/schedule.py +0 -0
  126. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/schema.py +0 -0
  127. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/skill.py +0 -0
  128. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/snippet.py +0 -0
  129. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/support.py +0 -0
  130. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/template.py +0 -0
  131. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/trash.py +0 -0
  132. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/upgrade.py +0 -0
  133. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/user.py +0 -0
  134. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/view_ops.py +0 -0
  135. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/view_profile.py +0 -0
  136. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/webhook.py +0 -0
  137. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/workflow.py +0 -0
  138. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/commands/workspace.py +0 -0
  139. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/context/__init__.py +0 -0
  140. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/context/credentials.py +0 -0
  141. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/context/endpoint.py +0 -0
  142. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/context/profiles.py +0 -0
  143. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/context/resolver.py +0 -0
  144. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/contracts/__init__.py +0 -0
  145. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/contracts/auth.py +0 -0
  146. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/embed.py +0 -0
  147. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/errors/__init__.py +0 -0
  148. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/manifest/__init__.py +0 -0
  149. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/manifest/loader.py +0 -0
  150. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/messages/__init__.py +0 -0
  151. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/output/__init__.py +0 -0
  152. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/output/envelope.py +0 -0
  153. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/output/normalize.py +0 -0
  154. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/output/policy.py +0 -0
  155. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/output/progress.py +0 -0
  156. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/output/render.py +0 -0
  157. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/py.typed +0 -0
  158. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/__init__.py +0 -0
  159. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/confirm.py +0 -0
  160. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/dataset_health.py +0 -0
  161. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/dryrun.py +0 -0
  162. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/dryrun_targets.py +0 -0
  163. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/embedded.py +0 -0
  164. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/executor.py +0 -0
  165. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/input_loader.py +0 -0
  166. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/intent_only.py +0 -0
  167. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/invocation.py +0 -0
  168. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/new_data.py +0 -0
  169. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/options.py +0 -0
  170. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/parents.py +0 -0
  171. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/runlog.py +0 -0
  172. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/session.py +0 -0
  173. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/state.py +0 -0
  174. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/strict.py +0 -0
  175. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/updates.py +0 -0
  176. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/validate.py +0 -0
  177. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/runtime/verify.py +0 -0
  178. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/__init__.py +0 -0
  179. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/argspec.py +0 -0
  180. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/board_values.py +0 -0
  181. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/coerce.py +0 -0
  182. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/command_contract.py +0 -0
  183. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/conditional_format.py +0 -0
  184. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/conditions.py +0 -0
  185. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/dashboard_filters.py +0 -0
  186. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/dashboard_pages.py +0 -0
  187. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/dashboard_review.py +0 -0
  188. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/data_profile.py +0 -0
  189. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/data_quality.py +0 -0
  190. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/dispatch.py +0 -0
  191. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/factory.py +0 -0
  192. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/listing.py +0 -0
  193. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/local_time.py +0 -0
  194. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/mapping.py +0 -0
  195. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/openapi_types.py +0 -0
  196. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/positionals.py +0 -0
  197. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/protocol.py +0 -0
  198. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/read_queries.py +0 -0
  199. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/sdk_service.py +0 -0
  200. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/sql_check.py +0 -0
  201. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/stored_stats.py +0 -0
  202. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/testing.py +0 -0
  203. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/text_dates.py +0 -0
  204. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/type_system.py +0 -0
  205. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/services/write_impact.py +0 -0
  206. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/skills/__init__.py +0 -0
  207. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/skills/installer.py +0 -0
  208. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/skills/steering.py +0 -0
  209. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/mammoth_cli/testing.py +0 -0
  210. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/activity.yaml +0 -0
  211. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/addon.yaml +0 -0
  212. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/agent.yaml +0 -0
  213. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/ai.yaml +0 -0
  214. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/annotation.yaml +0 -0
  215. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/auth.yaml +0 -0
  216. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/billing.yaml +0 -0
  217. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/browse.yaml +0 -0
  218. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/calc.yaml +0 -0
  219. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/capability.yaml +0 -0
  220. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/client-app.yaml +0 -0
  221. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/completion.yaml +0 -0
  222. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/config.yaml +0 -0
  223. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/connector.yaml +0 -0
  224. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/context.yaml +0 -0
  225. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/dashboard.yaml +0 -0
  226. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/data-app.yaml +0 -0
  227. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/dataset.yaml +0 -0
  228. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/doctor.yaml +0 -0
  229. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/external-key.yaml +0 -0
  230. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/file.yaml +0 -0
  231. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/folder.yaml +0 -0
  232. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/job.yaml +0 -0
  233. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/link.yaml +0 -0
  234. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/log.yaml +0 -0
  235. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/notification.yaml +0 -0
  236. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/parameter.yaml +0 -0
  237. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/report.yaml +0 -0
  238. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/schedule.yaml +0 -0
  239. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/schema.yaml +0 -0
  240. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/skill.yaml +0 -0
  241. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/snippet.yaml +0 -0
  242. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/support.yaml +0 -0
  243. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/template.yaml +0 -0
  244. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/trash.yaml +0 -0
  245. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/upgrade.yaml +0 -0
  246. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/user.yaml +0 -0
  247. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/version.yaml +0 -0
  248. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/webhook.yaml +0 -0
  249. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/workflow.yaml +0 -0
  250. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/commands/workspace.yaml +0 -0
  251. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/edits-target.source.yaml +0 -0
  252. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/openapi-operations.yaml +0 -0
  253. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/schema-v1.json +0 -0
  254. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/sdk-catalog.source.yaml +0 -0
  255. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/manifests/sdk-methods.yaml +0 -0
  256. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/spec/openapi/metadata.json +0 -0
  257. {mammoth_cli-2.2.28 → mammoth_cli-2.2.30}/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.28
3
+ Version: 2.2.30
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.28"
9
+ __version__ = "2.2.30"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -141,7 +141,12 @@ No command:
141
141
  - **Union or append two views.** There is no union task. Upload a file into
142
142
  an existing dataset with `append_to_ds_id`, or send a view's rows into an
143
143
  existing dataset with `view export dataset` (`target_ds_id` and
144
- `save_as_mode` `APPEND_TO_DS`). An append is a standing link: it is stored
144
+ `save_as_mode` `APPEND_TO_DS`). Omit `column_mapping` and every column is
145
+ matched by name; a non-empty `column_mapping` is used as-is, so it must map
146
+ every source column to an existing target column. A partial mapping, or a
147
+ destination the target lacks, is refused with `append_mapping_incomplete`
148
+ before anything is written (the backend would append only the mapped columns
149
+ and add a new column for the unknown name). An append is a standing link: it is stored
145
150
  as a pipeline step of the source view, so every later re-run of that view
146
151
  appends again into the target dataset, not just the first run. A column
147
152
  with the same name but a different type in the source view and the target
@@ -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
 
@@ -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
 
@@ -3545,6 +3545,7 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
3545
3545
  _reject_export_into_draft(service, dataview_id, dataset_id)
3546
3546
  target_view_before = None
3547
3547
  target_only_columns: list[str] = []
3548
+ mapped_type_warnings: list[str] = []
3548
3549
  if target_ds_id is not None:
3549
3550
  # Each `view export dataset` call creates its own PERSISTENT
3550
3551
  # export trigger on this view, which re-runs on every pipeline
@@ -3578,7 +3579,7 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
3578
3579
  # before the call so a later re-read can never be confused with it.
3579
3580
  target_view_before = _dataset_view_info(service, int(target_ds_id), result_project_id)
3580
3581
  if save_as_mode == "APPEND_TO_DS":
3581
- target_only_columns = _reject_append_schema_mismatch(
3582
+ target_only_columns, mapped_type_warnings = _reject_append_schema_mismatch(
3582
3583
  service,
3583
3584
  dataview_id,
3584
3585
  dataset_id,
@@ -3640,13 +3641,17 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
3640
3641
  # An append is expected to grow the target; verify.py treats
3641
3642
  # a non-increase here as unverified, not just an omitted count.
3642
3643
  row_check["expected_row_increase"] = True
3644
+ warnings = list(mapped_type_warnings)
3643
3645
  if target_only_columns:
3644
3646
  # Allowed -- an append never has to cover every target column
3645
3647
  # -- but worth surfacing rather than leaving silent.
3646
- row_check["warnings"] = [
3648
+ warnings.insert(
3649
+ 0,
3647
3650
  "target dataset has column(s) the source view does not (kept "
3648
- "as-is): " + ", ".join(target_only_columns)
3649
- ]
3651
+ "as-is): " + ", ".join(target_only_columns),
3652
+ )
3653
+ if warnings:
3654
+ row_check["warnings"] = warnings
3650
3655
  data["row_check"] = row_check
3651
3656
  if pipeline_error is not None:
3652
3657
  data["pipeline_error"] = pipeline_error
@@ -3799,24 +3804,92 @@ def _reject_append_type_mismatch(
3799
3804
  )
3800
3805
 
3801
3806
 
3807
+ def _reject_partial_append_mapping(
3808
+ source_names: set[str],
3809
+ target_names: set[str],
3810
+ mapping: dict[str, str],
3811
+ ids: tuple[int, int],
3812
+ ) -> None:
3813
+ """Refuse a non-empty append ``column_mapping`` that drops or invents columns.
3814
+
3815
+ The backend maps every column by name when the mapping is empty, but uses
3816
+ a non-empty one as-is: unmapped source columns are not appended (their
3817
+ target cells stay NULL) and a destination the target lacks becomes a new
3818
+ column named after the source.
3819
+ """
3820
+ if not mapping:
3821
+ return
3822
+ unknown = {src: dest for src, dest in sorted(mapping.items()) if dest not in target_names}
3823
+ unmapped = sorted(source_names - set(mapping))
3824
+ if not unknown and not unmapped:
3825
+ return
3826
+ view_id, target_ds_id = ids
3827
+ problems = []
3828
+ if unknown:
3829
+ listed = ", ".join(f"{dest!r} (for {src!r})" for src, dest in unknown.items())
3830
+ problems.append(f"destination column(s) not in target dataset {target_ds_id}: {listed}")
3831
+ if unmapped:
3832
+ problems.append(f"source column(s) left unmapped and not appended: {', '.join(unmapped)}")
3833
+ raise CliError(
3834
+ code="append_mapping_incomplete",
3835
+ message=(
3836
+ f"column_mapping for appending view {view_id} into dataset {target_ds_id} would "
3837
+ f"corrupt the append: {'; '.join(problems)}. A non-empty column_mapping is used "
3838
+ "as-is: unmapped columns are not appended and an unknown destination creates a "
3839
+ "new column."
3840
+ ),
3841
+ exit_status=EXIT_USAGE,
3842
+ hint=(
3843
+ "Omit column_mapping to map all columns by name, or map every source column to "
3844
+ f"an existing column of dataset {target_ds_id}."
3845
+ ),
3846
+ details={
3847
+ "unknown_destinations": unknown,
3848
+ "unmapped_source_columns": unmapped,
3849
+ "target_columns": sorted(target_names),
3850
+ },
3851
+ )
3852
+
3853
+
3854
+ def _mapped_type_warnings(
3855
+ source_types: Any, target_schema: dict[str, str], mapping: dict[str, str]
3856
+ ) -> list[str]:
3857
+ """Warn where a mapped source column's type differs from its target column's."""
3858
+ if not isinstance(source_types, dict):
3859
+ return []
3860
+ return [
3861
+ f"mapped column {src!r} is {str(source_types[src]).lower()} but target column "
3862
+ f"{dest!r} is {target_schema[dest].lower()}"
3863
+ for src, dest in sorted(mapping.items())
3864
+ if source_types.get(src)
3865
+ and target_schema.get(dest)
3866
+ and str(source_types[src]).lower() != target_schema[dest].lower()
3867
+ ]
3868
+
3869
+
3802
3870
  def _reject_append_schema_mismatch(
3803
3871
  service: Any,
3804
3872
  dataview_id: int,
3805
3873
  dataset_id: int | None,
3806
3874
  target_ds_id: int,
3807
3875
  column_mapping: Any,
3808
- ) -> list[str]:
3876
+ ) -> tuple[list[str], list[str]]:
3809
3877
  """Refuse an ``APPEND_TO_DS`` export whose source has a column the target
3810
3878
  dataset's schema does not, and ``column_mapping`` does not cover.
3811
3879
 
3880
+ A non-empty ``column_mapping`` is stricter: the backend then copies only
3881
+ the mapped columns and turns an unknown destination into a new column, so
3882
+ every source column must be mapped to a column the target already has
3883
+ (``_reject_partial_append_mapping``).
3884
+
3812
3885
  An append writes into rows the target dataset already has; a source
3813
3886
  column with no home in the target schema and no explicit mapping would
3814
3887
  otherwise reach the backend as a malformed write. Checked here, before
3815
3888
  the call, so the refusal costs nothing and names exactly what to fix.
3816
3889
 
3817
3890
  Returns the target-only column names (present in the target schema, not
3818
- in the source, not a ``column_mapping`` destination) -- allowed, but
3819
- worth a warning rather than silence.
3891
+ in the source, not a ``column_mapping`` destination) and the type
3892
+ warnings for mapped pairs -- both allowed, but worth a warning.
3820
3893
  """
3821
3894
  source_columns = service.call_view(dataview_id, "columns", dataset_id=dataset_id)
3822
3895
  if not isinstance(source_columns, dict):
@@ -3833,14 +3906,11 @@ def _reject_append_schema_mismatch(
3833
3906
  # dict keys are display names, matching what the backend's own schema
3834
3907
  # match compares by -- never the internal ids in the dict's values.
3835
3908
  source_names = set(source_columns)
3909
+ _reject_partial_append_mapping(source_names, target_names, mapping, (dataview_id, target_ds_id))
3836
3910
  source_only = sorted(source_names - target_names - set(mapping))
3837
3911
  target_only = sorted(target_names - source_names - set(mapping.values()))
3838
- _reject_append_type_mismatch(
3839
- service.call_view(dataview_id, "column_types", dataset_id=dataset_id),
3840
- target_schema,
3841
- mapping,
3842
- (dataview_id, target_ds_id),
3843
- )
3912
+ source_types = service.call_view(dataview_id, "column_types", dataset_id=dataset_id)
3913
+ _reject_append_type_mismatch(source_types, target_schema, mapping, (dataview_id, target_ds_id))
3844
3914
  if source_only:
3845
3915
  raise CliError(
3846
3916
  code="append_schema_mismatch",
@@ -3855,7 +3925,7 @@ def _reject_append_schema_mismatch(
3855
3925
  ),
3856
3926
  details={"source_only": source_only, "target_only": target_only},
3857
3927
  )
3858
- return target_only
3928
+ return target_only, _mapped_type_warnings(source_types, target_schema, mapping)
3859
3929
 
3860
3930
 
3861
3931
  def _dataset_view_info(
@@ -77,6 +77,7 @@ ERROR_SUMMARIES: dict[str, str] = {
77
77
  "More than one item matched, so the assistant needs to be more specific."
78
78
  ),
79
79
  "api_error": "Mammoth couldn't complete this step.",
80
+ "append_mapping_incomplete": "The column mapping would leave columns out or add new ones.",
80
81
  "append_schema_mismatch": "The new data doesn't line up with the existing columns.",
81
82
  "append_type_mismatch": "A column has a different type than in the existing data.",
82
83
  "append_schema_unreadable": "The columns of the existing data couldn't be read.",
@@ -236,6 +236,7 @@ _EXAMPLE_INPUT_HINTS: dict[str, dict[str, Any]] = {
236
236
  },
237
237
  "view.conditional-format.delete-all": {"rule_id": "bca0ff33bd6f8ed1"},
238
238
  "dataset.bulk-delete": {"dataset_ids": [456, 457]},
239
+ "batch.bulk-delete": {"ids": [11, 12]},
239
240
  "folder.move": {"resource_ids": [8024], "target_folder_resource_id": "root"},
240
241
  "project.update": {"name": "Renamed project"},
241
242
  # ProjectsPatch: role changes across explicitly named projects.
@@ -267,17 +268,9 @@ _EXAMPLE_INPUT_HINTS: dict[str, dict[str, Any]] = {
267
268
  "view.export.publish-db-update": {
268
269
  "patch": [{"op": "replace", "path": "credentials", "value": {"odbc_type": "postgres"}}],
269
270
  },
270
- # BatchesPostRequest: every ColumnNameMapping item carries the expected
271
- # destination type; a bare {src: dst} map cannot satisfy the route.
272
- "batch.create": {
273
- "mapping": [
274
- {
275
- "source_c_name": "column_1",
276
- "destination_c_name": "column_1",
277
- "expected_destination_c_type": "TEXT",
278
- }
279
- ]
280
- },
271
+ # BatchesPostRequest items carry the expected destination type; the CLI
272
+ # fills it from the destination's real schema, so the example omits it.
273
+ "batch.create": {"mapping": {"column_1": "column_1"}},
281
274
  # The SQL task reads the view as the quoted table "view:<id>" (or its
282
275
  # quoted display name); placeholder names such as ``data`` are rejected.
283
276
  "view.transform.add-sql": {
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mammoth-cli"
3
- version = "2.2.28"
3
+ version = "2.2.30"
4
4
  description = "Command-line interface for the Mammoth Analytics platform"
5
5
  authors = [{name = "Mammoth Analytics", email = "support@mammoth.io"}]
6
6
  readme = "README.md"
@@ -82,7 +82,9 @@ commands:
82
82
  task, trigger, option and file format in and out (PDF included). Task models can pull cloud files and send email;
83
83
  requires connector/email allowlists and dedicated fixtures. Delete old rows on a schedule with
84
84
  task apply_retention_policy (datasource_id, rule_type time_based, count_based or
85
- condition_based). An emailed file is task send_an_alert with attachments.dataview_ids: each
85
+ condition_based). The CLI sets require_approval=true on a retention task that omits it (the route
86
+ defaults to false, an unattended hard delete); require_approval=false with the delete action needs
87
+ --yes --confirm unattended-delete. An emailed file is task send_an_alert with attachments.dataview_ids: each
86
88
  selected view is emailed as a CSV file. The combined row count of all attached views must stay
87
89
  within 100,000 -- over that, the automation is refused (and a run that grows past it fails).'
88
90
  reviewed_by: primary
@@ -30,7 +30,7 @@ commands:
30
30
  required_fixture_guard: null
31
31
  secret_fields: []
32
32
  human_example: mammoth batch bulk-delete DATASET_ID --help
33
- agent_example: mammoth batch bulk-delete 123
33
+ agent_example: 'mammoth batch bulk-delete 123 --input ''{"ids": [11, 12]}'''
34
34
  unit_tests:
35
35
  - UT-BATCH-BULK-DELETE
36
36
  contract_tests:
@@ -39,7 +39,7 @@ commands:
39
39
  - CT-BATCH-BULK-DELETE-ERROR
40
40
  draft_test: null
41
41
  undo_test: null
42
- known_restrictions: null
42
+ known_restrictions: ids is required (the backend rejects a delete with no batch ids; it never deletes all batches). List ids with batch list.
43
43
  reviewed_by: primary
44
44
  readback:
45
45
  command: batch.list
@@ -82,8 +82,7 @@ commands:
82
82
  required_fixture_guard: null
83
83
  secret_fields: []
84
84
  human_example: mammoth batch create DATASET_ID SOURCE_ID --help
85
- agent_example: 'mammoth batch create 123 123 --input ''{"mapping": [{"source_c_name": "column_1", "destination_c_name":
86
- "column_1", "expected_destination_c_type": "TEXT"}]}'''
85
+ agent_example: 'mammoth batch create 123 123 --input ''{"mapping": {"column_1": "column_1"}}'''
87
86
  unit_tests:
88
87
  - UT-BATCH-CREATE
89
88
  contract_tests:
@@ -92,7 +91,7 @@ commands:
92
91
  - CT-BATCH-CREATE-ERROR
93
92
  draft_test: null
94
93
  undo_test: null
95
- known_restrictions: SOURCE_ID is a standalone dataset in the same project (4BATC010 INVALID_SOURCE_DATASET for a file id or an append child); to append a file use file upload with append_to_ds_id. delete_source_ds=true changes the mutation class to destructive and requires confirmation.
94
+ known_restrictions: Every source column must be mapped to an existing destination column; the CLI reads both schemas and refuses (append_mapping_incomplete) an unmapped source column or a destination the dataset lacks, because the backend appends only mapped columns. The CLI sends each item's expected_destination_c_type as the destination's current type (never re-types it; an explicit different value is refused with append_type_mismatch) and warns in mapping_check.warnings when source and destination types differ. SOURCE_ID is a standalone dataset in the same project (4BATC010 INVALID_SOURCE_DATASET for a file id or an append child); to append a file use file upload with append_to_ds_id. delete_source_ds=true changes the mutation class to destructive and requires confirmation.
96
95
  reviewed_by: primary
97
96
  readback:
98
97
  command: batch.list
@@ -933,7 +933,7 @@ commands:
933
933
  - CT-PROJECT-USER-ADD-ERROR
934
934
  draft_test: null
935
935
  undo_test: null
936
- known_restrictions: 'B03 PROJECT_USER_ID_ROLE_UNTYPED. Takes existing workspace users'' ids. To add
936
+ known_restrictions: 'B03 PROJECT_USER_ID_ROLE_UNTYPED. role is required, and a user who is already a project member is refused (the backend would replace that member''s role, demoting an admin); change roles with project user update. Takes existing workspace users'' ids. To add
937
937
  someone by email who is not in the workspace yet, use workspace user add with
938
938
  {"email_ids": [EMAIL], "projects": [{"project_id": PROJECT_ID, "role": ROLE}]}: it invites them as a workspace member
939
939
  with that project role, which is how a project gets a new person.'