mammoth-cli 2.0.29__tar.gz → 2.0.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 (222) hide show
  1. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/PKG-INFO +1 -1
  2. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/app.py +196 -61
  4. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +3 -1
  5. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +2 -2
  6. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +2 -0
  7. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +8 -0
  8. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +4 -1
  9. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +7 -3
  10. mammoth_cli-2.0.31/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +73 -0
  11. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/view.py +7 -3
  12. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/view_ops.py +61 -1
  13. mammoth_cli-2.0.31/mammoth_cli/manifest/loader.py +164 -0
  14. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/confirm.py +4 -0
  15. mammoth_cli-2.0.31/mammoth_cli/runtime/dryrun.py +133 -0
  16. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/invocation.py +32 -1
  17. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/session.py +7 -0
  18. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/command_contract.py +10 -0
  19. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/input_fields.py +12 -0
  20. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/sdk_service.py +12 -0
  21. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/testing.py +11 -0
  22. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/pyproject.toml +1 -1
  23. mammoth_cli-2.0.29/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -33
  24. mammoth_cli-2.0.29/mammoth_cli/manifest/loader.py +0 -96
  25. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/.gitignore +0 -0
  26. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/LICENSE +0 -0
  27. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/README.md +0 -0
  28. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/__main__.py +0 -0
  29. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  30. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  31. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  32. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
  33. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  34. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  35. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  36. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  37. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  38. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  39. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  40. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
  41. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  42. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  43. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  44. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  45. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  46. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  47. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  48. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  49. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  50. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  51. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  52. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  53. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  54. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  55. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  56. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  57. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  58. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  59. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  60. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  61. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  62. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  63. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  64. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  65. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  66. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  67. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  68. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  69. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  70. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  71. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  72. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  73. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  74. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  75. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  76. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  77. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  78. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  79. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  80. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
  81. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  82. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  83. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  84. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  85. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  86. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  87. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  88. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  89. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  90. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/__init__.py +0 -0
  91. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/activity.py +0 -0
  92. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/addon.py +0 -0
  93. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/agent.py +0 -0
  94. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/ai.py +0 -0
  95. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/annotation.py +0 -0
  96. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/auth.py +0 -0
  97. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/automation.py +0 -0
  98. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/batch.py +0 -0
  99. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/billing.py +0 -0
  100. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/browse.py +0 -0
  101. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/capability.py +0 -0
  102. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/client_app.py +0 -0
  103. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/completion.py +0 -0
  104. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/config.py +0 -0
  105. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/connector.py +0 -0
  106. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/context.py +0 -0
  107. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/dashboard.py +0 -0
  108. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/data_app.py +0 -0
  109. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/dataset.py +0 -0
  110. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/doctor.py +0 -0
  111. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/external_key.py +0 -0
  112. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/file.py +0 -0
  113. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/folder.py +0 -0
  114. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/job.py +0 -0
  115. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/notification.py +0 -0
  116. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/parameter.py +0 -0
  117. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/project.py +0 -0
  118. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/registry.py +0 -0
  119. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/report.py +0 -0
  120. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/schedule.py +0 -0
  121. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/schema.py +0 -0
  122. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/skill.py +0 -0
  123. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/snippet.py +0 -0
  124. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/support.py +0 -0
  125. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/template.py +0 -0
  126. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/trash.py +0 -0
  127. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/upgrade.py +0 -0
  128. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/user.py +0 -0
  129. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/webhook.py +0 -0
  130. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/workflow.py +0 -0
  131. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/commands/workspace.py +0 -0
  132. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/context/__init__.py +0 -0
  133. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/context/credentials.py +0 -0
  134. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/context/endpoint.py +0 -0
  135. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/context/profiles.py +0 -0
  136. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/context/resolver.py +0 -0
  137. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/contracts/__init__.py +0 -0
  138. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/contracts/auth.py +0 -0
  139. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/errors/__init__.py +0 -0
  140. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/errors/envelope.py +0 -0
  141. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/manifest/__init__.py +0 -0
  142. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/messages/__init__.py +0 -0
  143. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/output/__init__.py +0 -0
  144. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/output/envelope.py +0 -0
  145. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/output/normalize.py +0 -0
  146. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/output/policy.py +0 -0
  147. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/output/progress.py +0 -0
  148. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/output/render.py +0 -0
  149. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/py.typed +0 -0
  150. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/__init__.py +0 -0
  151. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/executor.py +0 -0
  152. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/input_loader.py +0 -0
  153. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/options.py +0 -0
  154. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/parents.py +0 -0
  155. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/runlog.py +0 -0
  156. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/strict.py +0 -0
  157. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/updates.py +0 -0
  158. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/runtime/validate.py +0 -0
  159. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/__init__.py +0 -0
  160. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/argspec.py +0 -0
  161. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/coerce.py +0 -0
  162. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/conditions.py +0 -0
  163. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/dispatch.py +0 -0
  164. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/factory.py +0 -0
  165. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/mapping.py +0 -0
  166. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/openapi_types.py +0 -0
  167. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/positionals.py +0 -0
  168. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/protocol.py +0 -0
  169. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/services/type_system.py +0 -0
  170. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/skills/__init__.py +0 -0
  171. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/skills/installer.py +0 -0
  172. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/skills/steering.py +0 -0
  173. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/mammoth_cli/testing.py +0 -0
  174. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/activity.yaml +0 -0
  175. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/addon.yaml +0 -0
  176. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/agent.yaml +0 -0
  177. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/ai.yaml +0 -0
  178. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/annotation.yaml +0 -0
  179. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/auth.yaml +0 -0
  180. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/automation.yaml +0 -0
  181. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/batch.yaml +0 -0
  182. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/billing.yaml +0 -0
  183. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/browse.yaml +0 -0
  184. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/capability.yaml +0 -0
  185. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/client-app.yaml +0 -0
  186. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/completion.yaml +0 -0
  187. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/config.yaml +0 -0
  188. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/connector.yaml +0 -0
  189. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/context.yaml +0 -0
  190. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/dashboard.yaml +0 -0
  191. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/data-app.yaml +0 -0
  192. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/dataset.yaml +0 -0
  193. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/doctor.yaml +0 -0
  194. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/external-key.yaml +0 -0
  195. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/file.yaml +0 -0
  196. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/folder.yaml +0 -0
  197. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/job.yaml +0 -0
  198. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/log.yaml +0 -0
  199. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/notification.yaml +0 -0
  200. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/parameter.yaml +0 -0
  201. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/project.yaml +0 -0
  202. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/report.yaml +0 -0
  203. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/schedule.yaml +0 -0
  204. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/schema.yaml +0 -0
  205. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/skill.yaml +0 -0
  206. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/snippet.yaml +0 -0
  207. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/support.yaml +0 -0
  208. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/template.yaml +0 -0
  209. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/trash.yaml +0 -0
  210. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/upgrade.yaml +0 -0
  211. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/user.yaml +0 -0
  212. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/version.yaml +0 -0
  213. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/view.yaml +0 -0
  214. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/webhook.yaml +0 -0
  215. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/workflow.yaml +0 -0
  216. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/commands/workspace.yaml +0 -0
  217. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/openapi-operations.yaml +0 -0
  218. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/schema-v1.json +0 -0
  219. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/sdk-catalog.source.yaml +0 -0
  220. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/manifests/sdk-methods.yaml +0 -0
  221. {mammoth_cli-2.0.29 → mammoth_cli-2.0.31}/spec/openapi/metadata.json +0 -0
  222. {mammoth_cli-2.0.29 → mammoth_cli-2.0.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.0.29
3
+ Version: 2.0.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.0.29"
9
+ __version__ = "2.0.31"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -29,7 +29,7 @@ from typer.core import TyperGroup
29
29
 
30
30
  from mammoth_cli import __version__
31
31
  from mammoth_cli.commands import BESPOKE
32
- from mammoth_cli.commands.registry import HANDLERS
32
+ from mammoth_cli.commands.registry import HANDLERS, Handler
33
33
  from mammoth_cli.context import profiles
34
34
  from mammoth_cli.errors.envelope import EXIT_USAGE, CliError, not_implemented_error
35
35
  from mammoth_cli.manifest.loader import command_by_id, load_commands
@@ -330,6 +330,13 @@ class _LeafGroup(TyperGroup):
330
330
  class _EnvelopeGroup(TyperGroup):
331
331
  """Root group that renders Click usage errors as the machine error envelope.
332
332
 
333
+ It also materialises top-level command groups lazily (see
334
+ :func:`_load_top_level_group`): ``list_commands`` and ``get_command`` know
335
+ every group by name from the manifest, but a group's Click tree is only
336
+ built when it is invoked, completed or listed in ``--help``. Reading
337
+ ``commands`` directly materialises them all, so tools that walk the tree
338
+ still see the full surface.
339
+
333
340
  Click's standalone error handling prints a human ``Usage: ... Error: ...``
334
341
  message and exits, even under ``--output json``: an agent driving the CLI
335
342
  then receives un-parseable prose (or, for a leaf that also parents
@@ -345,6 +352,28 @@ class _EnvelopeGroup(TyperGroup):
345
352
  on.
346
353
  """
347
354
 
355
+ @property # type: ignore[override]
356
+ def commands(self) -> dict[str, Any]:
357
+ loaded: dict[str, Any] = self.__dict__.setdefault("_eager_commands", {})
358
+ for name in _LAZY_GROUP_NAMES:
359
+ if name not in loaded:
360
+ loaded[name] = _load_top_level_group(name)
361
+ return loaded
362
+
363
+ @commands.setter
364
+ def commands(self, value: Any) -> None:
365
+ self.__dict__["_eager_commands"] = dict(value or {})
366
+
367
+ def list_commands(self, ctx: Any) -> list[str]:
368
+ eager = self.__dict__.get("_eager_commands", {})
369
+ return [*eager, *(name for name in _LAZY_GROUP_NAMES if name not in eager)]
370
+
371
+ def get_command(self, ctx: Any, cmd_name: str) -> Any:
372
+ eager: dict[str, Any] = self.__dict__.setdefault("_eager_commands", {})
373
+ if cmd_name not in eager and cmd_name in _LAZY_GROUP_NAMES:
374
+ eager[cmd_name] = _load_top_level_group(cmd_name)
375
+ return eager.get(cmd_name)
376
+
348
377
  def main(self, *args: Any, **kwargs: Any) -> Any:
349
378
  if not kwargs.get("standalone_mode", True):
350
379
  return super().main(*args, **kwargs)
@@ -629,6 +658,18 @@ def _shared_option_params() -> list[inspect.Parameter]:
629
658
  ),
630
659
  ],
631
660
  ),
661
+ opt(
662
+ "dry_run",
663
+ False,
664
+ Annotated[
665
+ bool,
666
+ typer.Option(
667
+ "--dry-run",
668
+ help="Resolve and validate, then report the request instead of sending it.",
669
+ rich_help_panel="Safety",
670
+ ),
671
+ ],
672
+ ),
632
673
  opt(
633
674
  "input_file",
634
675
  None,
@@ -777,6 +818,8 @@ def _execute(invocation: Invocation) -> None:
777
818
  record = command_by_id(invocation.command_id)
778
819
  sdk_symbol = record.get("sdk_symbol", "") if record else ""
779
820
  raise not_implemented_error(invocation.command_id, sdk_symbol)
821
+ if invocation.dry_run:
822
+ return _dry_run(handler, invocation)
780
823
  return handler(invocation)
781
824
 
782
825
  executor.run(
@@ -789,6 +832,25 @@ def _execute(invocation: Invocation) -> None:
789
832
  )
790
833
 
791
834
 
835
+ def _dry_run(handler: Handler, invocation: Invocation) -> tuple[Any, dict[str, Any]]:
836
+ """Run ``handler`` until its request would leave; report that request.
837
+
838
+ Local commands never open a service, so a dry run of one is the real run.
839
+ An API-backed handler that returns normally under ``--dry-run`` made no
840
+ gated SDK call at all (a pure read path); its result is returned as is.
841
+ """
842
+ from mammoth_cli.runtime.dryrun import DryRunStop
843
+ from mammoth_cli.runtime.session import resolved_project
844
+
845
+ try:
846
+ return handler(invocation)
847
+ except DryRunStop as stop:
848
+ return stop.record, {
849
+ "profile": invocation.profile,
850
+ "project_id": resolved_project(invocation),
851
+ }
852
+
853
+
792
854
  def _command_help(command_id: str, record: dict[str, Any] | None) -> str | None:
793
855
  """Build a command's user-facing ``--help`` summary.
794
856
 
@@ -855,68 +917,55 @@ def _short_type(type_name: str) -> str:
855
917
  return " | ".join(part.strip().rsplit(".", 1)[-1] for part in text.split("|"))[:40]
856
918
 
857
919
 
858
- def build_app() -> typer.Typer:
859
- """Construct the full Typer command tree from the command manifests."""
860
- root = typer.Typer(
861
- name="mammoth",
862
- help=(
863
- "Command-line interface for the Mammoth Analytics platform.\n\n"
864
- "Start with 'mammoth doctor' to check access, 'mammoth schema find QUERY' "
865
- "to locate a command, and 'mammoth schema get COMMAND_ID' for its full input schema."
866
- ),
920
+ def _command_records() -> list[dict[str, Any]]:
921
+ return [r for r in load_commands() if r.get("disposition") != "alias"]
922
+
923
+
924
+ @cache
925
+ def _command_tree() -> tuple[dict[tuple[str, ...], str], frozenset[tuple[str, ...]]]:
926
+ """Map every command path to its id, and name the container nodes.
927
+
928
+ A "container command" sits at a node that also has deeper subcommands
929
+ (e.g. ``dataset file-settings``, which also has ``... undo`` / ``... update``).
930
+ """
931
+ path_to_command = {
932
+ tuple(r["command_path"].split()): r["command_id"] for r in _command_records()
933
+ }
934
+ prefixes: set[tuple[str, ...]] = set()
935
+ for tokens in path_to_command:
936
+ for depth in range(1, len(tokens)):
937
+ prefixes.add(tokens[:depth])
938
+ return path_to_command, frozenset(prefixes & path_to_command.keys())
939
+
940
+
941
+ def _group_typer(tokens: tuple[str, ...], command_id: str | None) -> typer.Typer:
942
+ if command_id is not None:
943
+ # This node is both a group and an invocable command.
944
+ sub = typer.Typer(
945
+ cls=_LeafGroup,
946
+ no_args_is_help=False,
947
+ invoke_without_command=True,
948
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
949
+ )
950
+ sub.callback()(_build_leaf(command_id, is_group_callback=True))
951
+ return sub
952
+ return typer.Typer(
953
+ help=_GROUP_DESCRIPTIONS.get(tokens[0], f"Commands for {' '.join(tokens)}."),
867
954
  no_args_is_help=True,
868
- add_completion=True,
869
- cls=_EnvelopeGroup,
870
955
  context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
871
956
  )
872
957
 
873
- @root.callback()
874
- def _root(
875
- version: bool = typer.Option(
876
- False, "--version", callback=_version_callback, is_eager=True, help="Show version."
877
- ),
878
- ) -> None:
879
- """Root callback holding eager global options."""
880
958
 
881
- records = [r for r in load_commands() if r.get("disposition") != "alias"]
882
- path_to_command: dict[tuple[str, ...], str] = {
883
- tuple(r["command_path"].split()): r["command_id"] for r in records
884
- }
885
- all_paths = set(path_to_command)
886
- # A "container command" sits at a node that also has deeper subcommands
887
- # (e.g. `dataset file-settings`, which also has `... undo` / `... update`).
888
- containers = {
889
- tokens
890
- for tokens in all_paths
891
- if any(other != tokens and other[: len(tokens)] == tokens for other in all_paths)
892
- }
893
-
894
- groups: dict[tuple[str, ...], typer.Typer] = {(): root}
895
-
896
- def _make_group(tokens: tuple[str, ...]) -> typer.Typer:
897
- command_id = path_to_command.get(tokens)
898
- if command_id is not None:
899
- # This node is both a group and an invocable command.
900
- sub = typer.Typer(
901
- cls=_LeafGroup,
902
- no_args_is_help=False,
903
- invoke_without_command=True,
904
- context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
905
- )
906
- sub.callback()(_build_leaf(command_id, is_group_callback=True))
907
- else:
908
- sub = typer.Typer(
909
- help=_GROUP_DESCRIPTIONS.get(tokens[0], f"Commands for {' '.join(tokens)}."),
910
- no_args_is_help=True,
911
- context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
912
- )
913
- return sub
959
+ def _populate(base: tuple[str, ...], group: typer.Typer) -> None:
960
+ """Register every command below ``base`` (exclusive) on ``group``."""
961
+ path_to_command, containers = _command_tree()
962
+ groups: dict[tuple[str, ...], typer.Typer] = {base: group}
914
963
 
915
964
  def _group_for(tokens: tuple[str, ...]) -> typer.Typer:
916
965
  if tokens in groups:
917
966
  return groups[tokens]
918
967
  parent = _group_for(tokens[:-1])
919
- sub = _make_group(tokens)
968
+ sub = _group_typer(tokens, path_to_command.get(tokens))
920
969
  top_level = tokens[0]
921
970
  parent.add_typer(
922
971
  sub,
@@ -927,15 +976,16 @@ def build_app() -> typer.Typer:
927
976
  groups[tokens] = sub
928
977
  return sub
929
978
 
979
+ below = {t: c for t, c in path_to_command.items() if t[: len(base)] == base and t != base}
930
980
  # Ensure every container group exists (parents before children).
931
- for tokens in sorted(containers, key=len):
981
+ for tokens in sorted((t for t in below if t in containers), key=len):
932
982
  _group_for(tokens)
933
983
 
934
984
  # Register every non-container command as a leaf under its parent group.
935
985
  # A command_id with a bespoke, fully-typed callback overrides the generic
936
986
  # leaf at the same registered name and path; the manifest-driven surface
937
987
  # is otherwise unchanged.
938
- for tokens, command_id in sorted(path_to_command.items()):
988
+ for tokens, command_id in sorted(below.items()):
939
989
  if tokens in containers:
940
990
  continue
941
991
  record = command_by_id(command_id)
@@ -947,11 +997,88 @@ def build_app() -> typer.Typer:
947
997
  context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
948
998
  )(callback)
949
999
 
1000
+
1001
+ @cache
1002
+ def _top_level_typer(name: str) -> typer.Typer:
1003
+ """Build (once) the Typer sub-app for one top-level command group."""
1004
+ path_to_command, _ = _command_tree()
1005
+ sub = _group_typer((name,), path_to_command.get((name,)))
1006
+ _populate((name,), sub)
1007
+ return sub
1008
+
1009
+
1010
+ # Top-level groups are converted to Click on first use. Building the leaf
1011
+ # callbacks and ``--help`` text for all ~550 commands, then letting Typer
1012
+ # convert every one of them, used to cost most of the CLI's start-up time on
1013
+ # every invocation; a run touches one group.
1014
+ _LAZY_GROUP_NAMES: list[str] = []
1015
+ _LAZY_SETTINGS: dict[str, Any] = {}
1016
+
1017
+
1018
+ def _load_top_level_group(name: str) -> Any:
1019
+ from typer.models import TyperInfo
1020
+
1021
+ info = TyperInfo(
1022
+ _top_level_typer(name),
1023
+ name=name,
1024
+ help=_GROUP_DESCRIPTIONS.get(name, f"Commands for {name}."),
1025
+ rich_help_panel=_ROOT_HELP_PANELS.get(name),
1026
+ )
1027
+ return typer.main.get_group_from_info(info, **_LAZY_SETTINGS)
1028
+
1029
+
1030
+ def build_app() -> typer.Typer:
1031
+ """Construct the Typer command tree from the command manifests.
1032
+
1033
+ Top-level leaves (``doctor``, ``version``, ...) are registered here; every
1034
+ top-level group is materialised by :class:`_EnvelopeGroup` on demand.
1035
+ """
1036
+ root = typer.Typer(
1037
+ name="mammoth",
1038
+ help=(
1039
+ "Command-line interface for the Mammoth Analytics platform.\n\n"
1040
+ "Start with 'mammoth doctor' to check access, 'mammoth schema find QUERY' "
1041
+ "to locate a command, and 'mammoth schema get COMMAND_ID' for its full input schema."
1042
+ ),
1043
+ no_args_is_help=True,
1044
+ add_completion=True,
1045
+ cls=_EnvelopeGroup,
1046
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
1047
+ )
1048
+
1049
+ @root.callback()
1050
+ def _root(
1051
+ version: bool = typer.Option(
1052
+ False, "--version", callback=_version_callback, is_eager=True, help="Show version."
1053
+ ),
1054
+ ) -> None:
1055
+ """Root callback holding eager global options."""
1056
+
1057
+ path_to_command, containers = _command_tree()
1058
+ top_names = sorted({tokens[0] for tokens in path_to_command})
1059
+ lazy: list[str] = []
1060
+ for name in top_names:
1061
+ if (name,) in containers or (name,) not in path_to_command:
1062
+ lazy.append(name)
1063
+ continue
1064
+ command_id = path_to_command[(name,)]
1065
+ root.command(
1066
+ name=name,
1067
+ help=_command_help(command_id, command_by_id(command_id)),
1068
+ rich_help_panel=_ROOT_HELP_PANELS.get(name),
1069
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
1070
+ )(BESPOKE.get(command_id) or _build_leaf(command_id))
1071
+ _LAZY_GROUP_NAMES[:] = lazy
1072
+ _LAZY_SETTINGS.update(
1073
+ pretty_exceptions_short=root.pretty_exceptions_short,
1074
+ rich_markup_mode=root.rich_markup_mode,
1075
+ suggest_commands=root.suggest_commands,
1076
+ )
950
1077
  return root
951
1078
 
952
1079
 
953
1080
  def registered_command_paths() -> set[str]:
954
- """Return every registered command path by walking the built Typer tree.
1081
+ """Return every registered command path by walking the built Typer trees.
955
1082
 
956
1083
  Container commands (a node that is both a group and invocable) are counted
957
1084
  by their own path as well as their subcommands.
@@ -965,14 +1092,20 @@ def registered_command_paths() -> set[str]:
965
1092
  paths.add(" ".join((*prefix, name)))
966
1093
  for group in instance.registered_groups:
967
1094
  sub = group.typer_instance
968
- if sub is None or not group.name:
1095
+ name = group.name
1096
+ if sub is None or not name:
969
1097
  continue
970
- node = (*prefix, group.name)
971
- if getattr(sub.info, "invoke_without_command", False) and sub.registered_callback:
1098
+ node = (*prefix, name)
1099
+ if sub.registered_callback is not None:
972
1100
  paths.add(" ".join(node))
973
1101
  walk(sub, node)
974
1102
 
975
- walk(build_app(), ())
1103
+ walk(app, ())
1104
+ for name in _LAZY_GROUP_NAMES:
1105
+ sub = _top_level_typer(name)
1106
+ if sub.registered_callback is not None:
1107
+ paths.add(name)
1108
+ walk(sub, (name,))
976
1109
  return paths
977
1110
 
978
1111
 
@@ -987,7 +1120,9 @@ def _root_click_command() -> Any:
987
1120
  the tree is fixed for the process lifetime (:data:`app` is built once at
988
1121
  import time).
989
1122
  """
990
- return typer.main.get_command(app)
1123
+ root: Any = typer.main.get_command(app)
1124
+ root.commands # noqa: B018 -- materialise every lazy top-level group
1125
+ return root
991
1126
 
992
1127
 
993
1128
  def command_option_names(command_path: str) -> set[str]:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mammoth-cli
3
- version: 2.0.29
3
+ version: 2.0.31
4
4
  description: "Use Mammoth Analytics from a terminal: install or authenticate the CLI, discover its live command contract, and safely manage projects, data, views, pipelines, dashboards, exports, and handoffs."
5
5
  ---
6
6
 
@@ -60,6 +60,8 @@ before the next step.
60
60
  '{"full": true}'` returns the dependency and display trees too.
61
61
  - Column inputs and expressions use the exact **display names** the view
62
62
  returns, never backend aliases.
63
+ - `--dry-run` on any API-backed command resolves everything and reports the
64
+ request instead of sending it; use it before a write you are not sure of.
63
65
  - Destructive commands need `--yes --confirm ID`. Preserve requested
64
66
  deliverables; cleanup is exact-ID authorized and
65
67
  never means delete-all-owned-resources: delete only ids this run created,
@@ -932,7 +932,7 @@ Example: `mammoth view transform extract-date 123 --input '{"column": "Status",
932
932
 
933
933
  Result: `ViewTransformExtractDateResult`; mutation `reversible_pipeline`, confirmation `none`, wait policy `always_wait`.
934
934
 
935
- Status on release: untried; it submits through `view.task.add`, but this transform was not among those run.
935
+ Status on release: ran once on CLI 2.0.18 through `view.task.add` and was read back with `view data get`; other inputs for this transform are untried.
936
936
 
937
937
  ### `view.transform.fill-missing`
938
938
 
@@ -1112,7 +1112,7 @@ Example: `mammoth view transform window 123 --input '{"function": "ROW_NUMBER",
1112
1112
 
1113
1113
  Result: `ViewTransformWindowResult`; mutation `reversible_pipeline`, confirmation `none`, wait policy `always_wait`.
1114
1114
 
1115
- Status on release: untried; it submits through `view.task.add`, but this transform was not among those run.
1115
+ Status on release: ran once on CLI 2.0.18 through `view.task.add` and was read back with `view data get`; other inputs for this transform are untried.
1116
1116
 
1117
1117
  ### `view.trash`
1118
1118
 
@@ -13,6 +13,8 @@ mammoth view transform filter 1039 --project 180 \
13
13
  - `--input FILE` (or `--input @FILE`) reads a JSON or YAML file; the format
14
14
  is inferred from the extension.
15
15
  - `--input -` reads stdin; then `--input-format json|yaml` is required.
16
+ - `--dry-run` admits and resolves the document, then reports the SDK call
17
+ instead of making it ([safety](safety.md)).
16
18
  - The top level must be a mapping. A bad path, format, or shape fails with exit
17
19
  code 2 and a stable error code.
18
20
  - Column references use display names returned by the exact view schema. Do not
@@ -90,6 +90,14 @@ task/list or pipeline/items result back after a successful write. Without that
90
90
  independent specification, report the route as unsupported/ambiguous and stop
91
91
  safely.
92
92
 
93
+ Typed transforms and `view task add` append at the end of the pipeline and
94
+ never reorder or delete an existing step. On a pipeline other people or
95
+ agents also edit, make that checkable: read `view task list VIEW_ID`, plan,
96
+ then write with `"expected_task_count": N` in the input; the CLI re-reads
97
+ the list just before the write and refuses with `pipeline_changed` when the
98
+ count moved ([safety](safety.md)). `--dry-run` on the same command shows the
99
+ resolved call first.
100
+
93
101
  For a new workflow (a container for automation), discover and then read it
94
102
  back; workflow creation is not the same as adding a view pipeline task:
95
103
 
@@ -3,10 +3,13 @@
3
3
  ```bash
4
4
  mammoth schema find "view export"
5
5
  mammoth schema get view.export.csv
6
- mammoth view export csv VIEW_ID --project PROJECT_ID
6
+ mammoth view export csv VIEW_ID --project PROJECT_ID --input '{"dataset_id": DATASET_ID, "output_path": "out.csv"}'
7
7
  mammoth view export list VIEW_ID DATASET_ID --project PROJECT_ID
8
8
  ```
9
9
 
10
+ `view export csv` takes one positional (the view); `dataset_id` and
11
+ `output_path` go in `--input`. It is a download, not a pipeline step.
12
+
10
13
  If the export or its source dataset/view is requested as a deliverable, mark
11
14
  both as retained before starting. Wait/reconcile returned jobs. Verify local
12
15
  artifact headers, rows and hash;
@@ -30,9 +30,13 @@ mammoth dataset file-settings update DATASET_ID --project PROJECT_ID \
30
30
  ```
31
31
 
32
32
  The response is a job (`operation: "understand_csv"`). Poll `dataset get`
33
- until `status` leaves `need_action`; it normally reaches `ready` within a few
34
- seconds, after which `view list` returns the generated view and typed
35
- transforms can proceed. If the status becomes `error`, read `status_info`
33
+ (the record is under `data.dataset`, so read `data.dataset.status`) until
34
+ the status leaves `need_action`. It usually reaches `ready` within seconds;
35
+ on prague an ISO `YYYY-MM-DD` column was flagged ambiguous and took about
36
+ three minutes, so poll for up to five minutes before treating it as stuck.
37
+ Then `view list` returns the generated view and typed transforms can
38
+ proceed. Which environments ask differs: release processed the same file
39
+ straight to `ready`. If the status becomes `error`, read `status_info`
36
40
  and stop; do not retry with guessed settings.
37
41
 
38
42
  ## All-text CSV: `ready` without a view
@@ -0,0 +1,73 @@
1
+ # Safe mutation
2
+
3
+ Each command carries a reviewed confirmation policy:
4
+
5
+ | policy | how to satisfy it noninteractively |
6
+ |---|---|
7
+ | none | nothing required |
8
+ | prompt_or_yes | pass `--yes` |
9
+ | yes_always | pass `--yes` (always, even at a terminal) |
10
+ | confirm_target | pass `--yes` and `--confirm TARGET` (exact match) |
11
+
12
+ Under `--no-input` (and in `json`/`ndjson` modes) there is no prompt: a missing
13
+ `--yes`/`--confirm` fails with exit code 2 and error code
14
+ `confirmation_required` or `confirmation_target_mismatch`.
15
+
16
+ ```bash
17
+ # normal delete
18
+ mammoth folder delete 7 --project 180 --yes
19
+
20
+ # high-impact: target must equal the resource
21
+ mammoth project user remove --project 180 \
22
+ --yes --confirm 180 --input '{"user_ids": ["u_123"]}'
23
+
24
+ # project delete cascades to every dataset, view, export and dashboard in it
25
+ mammoth project delete 180 --yes --confirm 180
26
+ ```
27
+
28
+ Discover a command's policy with `mammoth schema get <command.id>`.
29
+
30
+ ## Rehearse before you write
31
+
32
+ `--dry-run` on any API-backed command does everything but send the request:
33
+ input admission, parent resolution, display-name and condition checks, then
34
+ it stops at the SDK call and reports it as `data.would_call` (`sdk_symbol`,
35
+ resolved `arguments`, `view_id`/`dataset_id`) with `data.dry_run: true`. No
36
+ confirmation flag is needed for a dry run; the real run still needs its
37
+ `--yes`/`--confirm`. Use it before a mutation on a shared or production
38
+ resource, and whenever you are unsure which view or column an input resolves
39
+ to.
40
+
41
+ ```bash
42
+ mammoth view transform filter 144 --dry-run \
43
+ --input '{"filter_type": "REMOVE", "condition": {"column": "Location", "operator": "EQ", "value": "Total"}}'
44
+ # data.would_call.sdk_symbol = ...FilterOpsMixin.filter_rows, view_id 144, dataset_id 122
45
+ ```
46
+
47
+ A dry run is a full process with the same reads as the real run. On a
48
+ hosted environment budget roughly 2 s per request (about 0.7 s of it TLS
49
+ setup from a cold process, the rest server time), so `view task list` →
50
+ `--dry-run` → write costs about 30–40 s per pipeline step. That is the
51
+ price of the procedure, not a hang; do not shorten `--timeout` below the
52
+ default to speed it up.
53
+
54
+ ## Add-only edits to a pipeline someone else may touch
55
+
56
+ Every `view transform *` and `view task add` accepts `expected_task_count`:
57
+ the number of tasks your last `view task list VIEW_ID` returned. The CLI
58
+ re-reads the task list right before the write and refuses with
59
+ `pipeline_changed` (exit 2, `details.actual_task_count`, `task_ids`) when the
60
+ count differs, so a plan made against a stale read cannot land on a pipeline
61
+ that changed under you. Read → decide → write with the count, one edit at a
62
+ time; on `pipeline_changed`, re-read and re-decide rather than retrying with
63
+ the new number blindly.
64
+
65
+ ```bash
66
+ mammoth view task list 144 # data.tasks has 3 entries
67
+ mammoth view transform filter 144 --input '{"expected_task_count": 3, "filter_type": "REMOVE", "condition": {...}}'
68
+ ```
69
+ Use exact observed resource scope and display names before mutation. A known job
70
+ handle should be inspected or waited, not submitted again. `outcome_unknown`
71
+ means a mutation may have committed without a terminal response: reconcile the
72
+ target before any replay. A retryable read can honor `Retry-After`; exit 7 is
73
+ not blanket permission to retry a mutation.
@@ -1445,11 +1445,15 @@ def view_task_add(invocation: Invocation) -> HandlerResult:
1445
1445
  kwargs: dict[str, Any] = {"dataview_id": dataview_id, "task_spec": task_spec}
1446
1446
  assert document is not None
1447
1447
  _forward_optional(document, kwargs, ("dataset_id",))
1448
+ # Imported here: view_ops imports this module for the brief record helpers.
1449
+ from mammoth_cli.commands.view_ops import (
1450
+ reject_pipeline_reference_errors,
1451
+ require_expected_task_count,
1452
+ )
1453
+
1448
1454
  with open_service(invocation) as (service, auth):
1455
+ require_expected_task_count(service, dataview_id, kwargs.get("dataset_id"), document)
1449
1456
  data = service.call(_symbol(invocation), **kwargs)
1450
- # Imported here: view_ops imports this module for the brief record helpers.
1451
- from mammoth_cli.commands.view_ops import reject_pipeline_reference_errors
1452
-
1453
1457
  reject_pipeline_reference_errors(service, dataview_id, kwargs.get("dataset_id"), data)
1454
1458
  return data, _meta(invocation, auth.workspace_id, None)
1455
1459
 
@@ -43,6 +43,7 @@ from mammoth_cli.runtime.invocation import Invocation
43
43
  from mammoth_cli.runtime.session import open_service, resolved_project
44
44
  from mammoth_cli.services.command_contract import bind_command_inputs
45
45
  from mammoth_cli.services.conditions import CONDITION_KWARG
46
+ from mammoth_cli.services.input_fields import TASK_COUNT_FIELD
46
47
 
47
48
  HandlerResult = tuple[Any, dict[str, Any]]
48
49
 
@@ -251,6 +252,7 @@ def _dispatch_view(
251
252
  with open_service(invocation) as (service, auth):
252
253
  if dataset_id is None:
253
254
  dataset_id = parents.lookup(_profile_name(invocation), auth.workspace_id, view_id)
255
+ require_expected_task_count(service, view_id, dataset_id, document)
254
256
  try:
255
257
  if dataset_id is None:
256
258
  _require_discovery_allowed(invocation, view_id)
@@ -266,6 +268,60 @@ def _dispatch_view(
266
268
  return data, _meta(invocation, auth.workspace_id)
267
269
 
268
270
 
271
+ CODE_PIPELINE_CHANGED = "pipeline_changed"
272
+ _TASK_LIST_SYMBOL = "mammoth.api.pipeline.PipelineAPI.list_tasks"
273
+
274
+
275
+ def require_expected_task_count(
276
+ service: Any, view_id: int, dataset_id: Any, document: dict[str, Any] | None
277
+ ) -> None:
278
+ """Refuse a pipeline write when the view's task count is not the one read.
279
+
280
+ ``expected_task_count`` is the count from the caller's last ``view task
281
+ list``. A different live count means the pipeline changed since that read
282
+ (another agent, a person in the web app), and an append planned against
283
+ the old shape must not land. Without the field nothing is checked.
284
+ """
285
+ if not document or TASK_COUNT_FIELD not in document:
286
+ return
287
+ expected = int(document[TASK_COUNT_FIELD])
288
+ kwargs: dict[str, Any] = {"dataview_id": view_id}
289
+ if dataset_id is not None:
290
+ kwargs["dataset_id"] = int(dataset_id)
291
+ listing = service.call(_TASK_LIST_SYMBOL, **kwargs)
292
+ tasks = listing.get("tasks") if isinstance(listing, dict) else None
293
+ if not isinstance(tasks, list):
294
+ raise CliError(
295
+ code=CODE_PIPELINE_CHANGED,
296
+ message=f"Could not read the task list of view {view_id} to check the precondition.",
297
+ exit_status=EXIT_USAGE,
298
+ details={"view_id": view_id, "expected_task_count": expected, "response": listing},
299
+ )
300
+ actual = len(tasks)
301
+ if actual == expected:
302
+ return
303
+ raise CliError(
304
+ code=CODE_PIPELINE_CHANGED,
305
+ message=(
306
+ f"View {view_id} has {actual} pipeline task(s), not the {expected} expected; "
307
+ "the pipeline changed since it was read, so nothing was written."
308
+ ),
309
+ exit_status=EXIT_USAGE,
310
+ hint=(
311
+ "Re-read the pipeline, decide whether the plan still holds, and retry "
312
+ f"with expected_task_count {actual}."
313
+ ),
314
+ details={
315
+ "view_id": view_id,
316
+ "dataset_id": int(dataset_id) if dataset_id is not None else None,
317
+ "expected_task_count": expected,
318
+ "actual_task_count": actual,
319
+ "task_ids": [t.get("id") for t in tasks if isinstance(t, dict)],
320
+ },
321
+ recovery_commands=[f"mammoth view task list {view_id}"],
322
+ )
323
+
324
+
269
325
  def _compact_reference_error(entry: Any) -> dict[str, Any]:
270
326
  if not isinstance(entry, dict):
271
327
  return {"reason": str(entry)}
@@ -362,7 +418,11 @@ def _without_resource_context(document: dict[str, Any]) -> dict[str, Any]:
362
418
  # mapping. These identifiers select the View receiver and are not method
363
419
  # kwargs; strip both parent and receiver identities before the transform
364
420
  # adapter forwards the document.
365
- return {key: value for key, value in document.items() if key not in {"dataset_id", "view_id"}}
421
+ return {
422
+ key: value
423
+ for key, value in document.items()
424
+ if key not in {"dataset_id", "view_id", TASK_COUNT_FIELD}
425
+ }
366
426
 
367
427
 
368
428
  def _bind_transform_inputs(invocation: Invocation, document: dict[str, Any]) -> dict[str, Any]: