mammoth-cli 2.0.37__tar.gz → 2.0.39__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.37 → mammoth_cli-2.0.39}/PKG-INFO +2 -2
  2. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +38 -2
  4. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +22 -11
  5. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +1 -1
  6. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +2 -2
  7. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +20 -0
  8. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +5 -2
  9. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +35 -0
  10. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +4 -2
  11. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/registry.py +2 -0
  12. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/schema.py +16 -4
  13. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/view.py +200 -15
  14. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/view_ops.py +64 -6
  15. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/strict.py +14 -1
  16. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/command_contract.py +4 -1
  17. mammoth_cli-2.0.39/mammoth_cli/services/data_quality.py +136 -0
  18. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/input_fields.py +3 -0
  19. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/pyproject.toml +2 -2
  20. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/view.yaml +82 -0
  21. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/sdk-catalog.source.yaml +14 -0
  22. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/sdk-methods.yaml +24 -0
  23. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/.gitignore +0 -0
  24. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/LICENSE +0 -0
  25. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/README.md +0 -0
  26. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/__main__.py +0 -0
  27. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/app.py +0 -0
  28. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  29. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  30. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  31. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  32. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  33. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  34. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  35. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  36. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  37. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
  38. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  39. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  40. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  41. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  42. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  43. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  44. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  45. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  46. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  47. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  48. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  49. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  50. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  51. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  52. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  53. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  54. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  55. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  56. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  57. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  58. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  59. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  60. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  61. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  62. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  63. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  64. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  65. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  66. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  67. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  68. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  69. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  70. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  71. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  72. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  73. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  74. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  75. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  76. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  77. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  78. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  79. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
  80. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
  81. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  82. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  83. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  84. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  85. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  86. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  87. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  88. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  89. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/__init__.py +0 -0
  90. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/activity.py +0 -0
  91. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/addon.py +0 -0
  92. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/agent.py +0 -0
  93. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/ai.py +0 -0
  94. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/annotation.py +0 -0
  95. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/auth.py +0 -0
  96. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/automation.py +0 -0
  97. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/batch.py +0 -0
  98. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/billing.py +0 -0
  99. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/browse.py +0 -0
  100. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/capability.py +0 -0
  101. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/client_app.py +0 -0
  102. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/completion.py +0 -0
  103. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/config.py +0 -0
  104. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/connector.py +0 -0
  105. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/context.py +0 -0
  106. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/dashboard.py +0 -0
  107. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/data_app.py +0 -0
  108. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/dataset.py +0 -0
  109. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/doctor.py +0 -0
  110. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/external_key.py +0 -0
  111. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/file.py +0 -0
  112. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/folder.py +0 -0
  113. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/job.py +0 -0
  114. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/notification.py +0 -0
  115. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/parameter.py +0 -0
  116. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/project.py +0 -0
  117. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/report.py +0 -0
  118. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/schedule.py +0 -0
  119. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/skill.py +0 -0
  120. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/snippet.py +0 -0
  121. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/support.py +0 -0
  122. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/template.py +0 -0
  123. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/trash.py +0 -0
  124. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/upgrade.py +0 -0
  125. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/user.py +0 -0
  126. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/webhook.py +0 -0
  127. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/workflow.py +0 -0
  128. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/commands/workspace.py +0 -0
  129. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/context/__init__.py +0 -0
  130. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/context/credentials.py +0 -0
  131. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/context/endpoint.py +0 -0
  132. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/context/profiles.py +0 -0
  133. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/context/resolver.py +0 -0
  134. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/contracts/__init__.py +0 -0
  135. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/contracts/auth.py +0 -0
  136. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/errors/__init__.py +0 -0
  137. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/errors/envelope.py +0 -0
  138. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/manifest/__init__.py +0 -0
  139. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/manifest/loader.py +0 -0
  140. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/messages/__init__.py +0 -0
  141. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/output/__init__.py +0 -0
  142. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/output/envelope.py +0 -0
  143. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/output/normalize.py +0 -0
  144. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/output/policy.py +0 -0
  145. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/output/progress.py +0 -0
  146. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/output/render.py +0 -0
  147. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/py.typed +0 -0
  148. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/__init__.py +0 -0
  149. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/confirm.py +0 -0
  150. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/dryrun.py +0 -0
  151. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/executor.py +0 -0
  152. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/input_loader.py +0 -0
  153. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/invocation.py +0 -0
  154. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/options.py +0 -0
  155. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/parents.py +0 -0
  156. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/runlog.py +0 -0
  157. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/session.py +0 -0
  158. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/updates.py +0 -0
  159. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/runtime/validate.py +0 -0
  160. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/__init__.py +0 -0
  161. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/argspec.py +0 -0
  162. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/coerce.py +0 -0
  163. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/conditions.py +0 -0
  164. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/dispatch.py +0 -0
  165. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/factory.py +0 -0
  166. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/mapping.py +0 -0
  167. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/openapi_types.py +0 -0
  168. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/positionals.py +0 -0
  169. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/protocol.py +0 -0
  170. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/sdk_service.py +0 -0
  171. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/testing.py +0 -0
  172. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/services/type_system.py +0 -0
  173. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/skills/__init__.py +0 -0
  174. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/skills/installer.py +0 -0
  175. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/skills/steering.py +0 -0
  176. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/mammoth_cli/testing.py +0 -0
  177. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/activity.yaml +0 -0
  178. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/addon.yaml +0 -0
  179. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/agent.yaml +0 -0
  180. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/ai.yaml +0 -0
  181. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/annotation.yaml +0 -0
  182. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/auth.yaml +0 -0
  183. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/automation.yaml +0 -0
  184. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/batch.yaml +0 -0
  185. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/billing.yaml +0 -0
  186. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/browse.yaml +0 -0
  187. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/capability.yaml +0 -0
  188. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/client-app.yaml +0 -0
  189. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/completion.yaml +0 -0
  190. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/config.yaml +0 -0
  191. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/connector.yaml +0 -0
  192. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/context.yaml +0 -0
  193. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/dashboard.yaml +0 -0
  194. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/data-app.yaml +0 -0
  195. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/dataset.yaml +0 -0
  196. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/doctor.yaml +0 -0
  197. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/external-key.yaml +0 -0
  198. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/file.yaml +0 -0
  199. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/folder.yaml +0 -0
  200. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/job.yaml +0 -0
  201. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/log.yaml +0 -0
  202. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/notification.yaml +0 -0
  203. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/parameter.yaml +0 -0
  204. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/project.yaml +0 -0
  205. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/report.yaml +0 -0
  206. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/schedule.yaml +0 -0
  207. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/schema.yaml +0 -0
  208. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/skill.yaml +0 -0
  209. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/snippet.yaml +0 -0
  210. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/support.yaml +0 -0
  211. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/template.yaml +0 -0
  212. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/trash.yaml +0 -0
  213. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/upgrade.yaml +0 -0
  214. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/user.yaml +0 -0
  215. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/version.yaml +0 -0
  216. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/webhook.yaml +0 -0
  217. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/workflow.yaml +0 -0
  218. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/commands/workspace.yaml +0 -0
  219. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/openapi-operations.yaml +0 -0
  220. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/manifests/schema-v1.json +0 -0
  221. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/spec/openapi/metadata.json +0 -0
  222. {mammoth_cli-2.0.37 → mammoth_cli-2.0.39}/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.37
3
+ Version: 2.0.39
4
4
  Summary: Command-line interface for the Mammoth Analytics platform
5
5
  Project-URL: homepage, https://mammoth.io
6
6
  Project-URL: repository, https://github.com/EdgeMetric/mammothsdk
@@ -17,7 +17,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
17
  Requires-Python: <3.15,>=3.12
18
18
  Requires-Dist: jsonschema>=4.20
19
19
  Requires-Dist: keyring<26,>=25.7
20
- Requires-Dist: mammoth-io<0.8,>=0.7.16
20
+ Requires-Dist: mammoth-io<0.8,>=0.7.17
21
21
  Requires-Dist: platformdirs<5,>=4.10
22
22
  Requires-Dist: pydantic<3,>=2.13
23
23
  Requires-Dist: pyyaml<7,>=6
@@ -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.37"
9
+ __version__ = "2.0.39"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mammoth-cli
3
- version: 2.0.37
3
+ version: 2.0.39
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 (join, merge, pivot, filter, clean), dashboards, exports, and handoffs."
5
5
  ---
6
6
 
@@ -76,17 +76,49 @@ mammoth view transform --help # every data transformation
76
76
  | Blanks to a constant, or to the previous row's value | `view transform set-values` (`IS_EMPTY`), `view transform fill-missing` |
77
77
  | Clean text | `view transform text`, `replace`, `bulk-replace` |
78
78
  | Change a column's type | `view transform convert-type` |
79
+ | Rename a column, sort the rows (view settings, not tasks) | `view transform rename-columns`, `view transform sort` |
79
80
  | Rank, running total, previous row | `view transform window` |
80
81
  | A dashboard | `dashboard create-blank` ([dashboards](references/recipes/dashboards.md)) |
81
82
  | Deliver the rows | `view export csv`, `view export postgres` (and other destinations), `view export dataset` |
82
83
 
83
84
  A search with no full match returns `suggestions` and a `hint`. Before you
84
85
  conclude the CLI cannot do something the web app does, check
85
- `view transform --help` and "Not a pipeline step" in
86
+ `view transform --help` and "View settings, and what has no command" in
86
87
  [about Mammoth](references/about-mammoth.md). Do not switch to the web app
87
88
  in a browser, or compute the result locally, for work the CLI covers;
88
89
  report a real gap with the command you tried.
89
90
 
91
+ ## Several files, one deliverable
92
+
93
+ Users often upload related files without saying how they relate ("make a
94
+ dashboard from t_a and t_b"). Work it out from the data before you build:
95
+
96
+ 1. Upload every file, then read every view in Mammoth (not the local
97
+ files): `view get VIEW_ID` (columns, types, row count) and `view data get
98
+ VIEW_ID`. The data read lists `column_warnings`: numbers or dates stored
99
+ as text, and blanks, each with the fix.
100
+ 2. Find the keys. A column in one view whose values appear in a column of
101
+ the other (`customer_id` and `id`, `order_ref` and `order_no`) is a
102
+ foreign key; the view with many rows per key is the main one.
103
+ 3. Make the keys match before the join: same type (`convert-type`), same
104
+ case and padding (`text`), no blanks (`set-values`, `filter`).
105
+ 4. Act on every `column_warnings` entry for a column the deliverable uses:
106
+ run its `fix` (`convert-type` to `NUMERIC` or `DATE`; the values that are
107
+ not numbers become empty), then decide on blanks (fill, filter, or keep and
108
+ say so). A text `price` cannot be summed on a dashboard.
109
+ 5. `view transform join` the lookup view into the main view (`LEFT`). The
110
+ result's `join_check` gives `match_rate`, `unmatched_rows` and
111
+ `unmatched_keys`; put them in your report, and stop to compare the keys
112
+ if more than a few rows found no match.
113
+ 6. Build the dashboard from the joined view
114
+ ([dashboards](references/recipes/dashboards.md)).
115
+
116
+ For the defaults that most pipelines use (new column or overwrite, `LEFT`
117
+ joins, date steps), see
118
+ [transforms](references/recipes/transforms.md#what-most-pipelines-look-like).
119
+ Say what you inferred (which columns you joined on, what you converted) in
120
+ your report. If no column links the files, ask before you combine them.
121
+
90
122
  ## Calling a command
91
123
 
92
124
  - Ids are positionals; request fields are one `--input '{...}'` document;
@@ -125,6 +157,10 @@ report a real gap with the command you tried.
125
157
  target column *before* the change so the after-read has a comparison.
126
158
  - A uniform result (every amount 0, every region "Unknown", most join rows
127
159
  unmatched) means the previous step went wrong; stop and re-inspect it.
160
+ - `column_warnings` on a data read and `join_check` on a join are findings,
161
+ not decoration: fix each one the deliverable depends on, or say in the
162
+ report why you kept it. `view data get` pages with `{"offset": 51,
163
+ "limit": 50}`.
128
164
  - A timeout, exit 7 or interruption does not prove failure; reconcile an
129
165
  `outcome_unknown` (`job get`, `view task list`) before replaying.
130
166
  - Run the [report checklist](references/report-checklist.md) before stating
@@ -100,6 +100,8 @@ Checkpoint Alert are the `view data-check` and `view checkpoint` families.
100
100
  | Shift a date by an interval | Add / Subtract Date Values | `increment-date` |
101
101
  | Add an empty column | Add Column | `add-column` |
102
102
  | Copy a column under a new name | Copy Columns | `copy-columns` |
103
+ | Rename a column | Rename (column header) | `rename-columns` (a view setting, not a task) |
104
+ | Sort the rows | Sort (grid) | `sort` (a view setting, not a task) |
103
105
  | Remove columns | Remove Column | `delete-columns` |
104
106
  | Add columns from another view by matching keys | Join | `join` (`INNER`, `LEFT`, `RIGHT`, `OUTER`) |
105
107
  | Bring one value per key from another view | Lookup | `lookup` |
@@ -117,15 +119,24 @@ value per key into one new column. Before either, confirm the key columns
117
119
  in both views have the same type and values; recipes/transforms.md has the
118
120
  read-back.
119
121
 
120
- ## Not a pipeline step, and the nearest route
122
+ ## View settings, and what has no command
123
+
124
+ Two commands change how the view shows its rows, not the rows. They add no
125
+ pipeline task, like their web counterparts (a column-header rename, a grid
126
+ sort):
127
+
128
+ - **Rename a column:** `view transform rename-columns` with `{"renames":
129
+ {"cust_id": "Customer ID"}}`. The column keeps its internal name, so
130
+ earlier tasks keep working; later commands, data reads, exports and
131
+ dashboards use the new name. To name a column you create, use its
132
+ `new_column` or `as_name` field instead.
133
+ - **Sort the rows:** `view transform sort` with `{"order_by": [["Revenue",
134
+ "DESC"]]}` (up to three columns; `[]` clears it). Data reads and exports
135
+ return rows in this order. To keep only the top N rows, use `limit-rows`
136
+ with `order_by`; for a rank column, `window` (`RANK`, `ROW_NUMBER`).
137
+
138
+ No command:
121
139
 
122
- - **Rename a column.** The web app's rename is a display change, not a
123
- task; the CLI has no rename command. Name new columns when you create them
124
- (`new_column`, `as_name`); for an existing column, `copy-columns` under
125
- the new name.
126
- - **Sort.** There is no sort task, and the CLI has no sort command. For
127
- "top N by X" use `limit-rows` with `order_by`; for a rank column use
128
- `window` (`RANK`, `ROW_NUMBER`).
129
140
  - **Union or append two views.** There is no union task. Upload a file into
130
141
  an existing dataset with `append_to_ds_id`, or send a view's rows into an
131
142
  existing dataset with `view export dataset` (`target_ds_id` and
@@ -139,6 +150,6 @@ Both call the same API. Do not switch to the web app in a browser for a
139
150
  task the CLI covers. Before you conclude that the CLI cannot do something,
140
151
  run `mammoth view transform --help`, search in plain words (`mammoth schema
141
152
  find "merge two datasets"` finds `join`; a search with no full match lists
142
- `suggestions`), and read the section above. If it is still missing, report
143
- the gap with the command you tried; do not work around it in the browser or
144
- by computing the result locally.
153
+ `suggestions`), and read "View settings, and what has no command" above.
154
+ If it is still missing, report the gap with the command you tried; do not
155
+ work around it in the browser or by computing the result locally.
@@ -1,7 +1,7 @@
1
1
  # What is proven on release
2
2
 
3
3
  Generated from `docs/release-capability-matrix.json`; do not edit by hand.
4
- The CLI publishes 554 commands. 455 of them bind one of the 528 API operations in the matrix; the remainder are local commands (`schema`, `auth`, `doctor`, `log`, ...) or typed variants that share an operation (every `view transform *` command submits through `view.task.add`). 262 bound commands ran once successfully on release, 2 are not supported there, and the rest are untried. Untried is not broken: discover the contract with `mammoth schema get COMMAND_ID`, run it, and treat the structured error envelope as the answer.
4
+ The CLI publishes 556 commands. 455 of them bind one of the 528 API operations in the matrix; the remainder are local commands (`schema`, `auth`, `doctor`, `log`, ...) or typed variants that share an operation (`view transform *` commands submit through `view.task.add`, except `rename-columns` and `sort`, which set view display properties through `view.update`). 262 bound commands ran once successfully on release, 2 are not supported there, and the rest are untried. Untried is not broken: discover the contract with `mammoth schema get COMMAND_ID`, run it, and treat the structured error envelope as the answer.
5
5
 
6
6
  Status meanings:
7
7
 
@@ -1,6 +1,6 @@
1
1
  # Published CLI command catalog
2
2
 
3
- This generated catalog is a lookup table for every command in the published CLI manifest, currently 554; each entry carries its release status line. It is a command-contract index, not an assertion that every backend/OpenAPI operation has a CLI binding. For an exact local CLI contract, run `mammoth schema get COMMAND_ID`; `mammoth capability list` is an API-binding inventory and can omit typed/local CLI routes. For focused workflows, read the [recipes index](recipes/index.md). Sensitive structured input must come from a private file or pipe; never put secrets in literal argv.
3
+ This generated catalog is a lookup table for every command in the published CLI manifest, currently 556; each entry carries its release status line. It is a command-contract index, not an assertion that every backend/OpenAPI operation has a CLI binding. For an exact local CLI contract, run `mammoth schema get COMMAND_ID`; `mammoth capability list` is an API-binding inventory and can omit typed/local CLI routes. For focused workflows, read the [recipes index](recipes/index.md). Sensitive structured input must come from a private file or pipe; never put secrets in literal argv.
4
4
 
5
5
  Load only the applicable domain file:
6
6
 
@@ -43,7 +43,7 @@ Load only the applicable domain file:
43
43
  - [upgrade](commands/upgrade.md) — 1 published commands
44
44
  - [user](commands/user.md) — 8 published commands
45
45
  - [version](commands/version.md) — 1 published commands
46
- - [view](commands/view.md) — 116 published commands
46
+ - [view](commands/view.md) — 118 published commands
47
47
  - [webhook](commands/webhook.md) — 7 published commands
48
48
  - [workflow](commands/workflow.md) — 16 published commands
49
49
  - [workspace](commands/workspace.md) — 20 published commands
@@ -1034,6 +1034,16 @@ Result: `ViewTransformPivotResult`; mutation `reversible_pipeline`, confirmation
1034
1034
 
1035
1035
  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.
1036
1036
 
1037
+ ### `view.transform.rename-columns`
1038
+
1039
+ Run: `mammoth view transform rename-columns`. Exact input fields: `mammoth schema get view.transform.rename-columns`.
1040
+
1041
+ Example: `mammoth view transform rename-columns 123 --input '{"renames": {"cust_id": "Customer ID"}, "dataset_id": 456}'`. Placeholders are illustrative; resolve IDs and input from observed reads.
1042
+
1043
+ Result: `ViewTransformRenameColumnsResult`; mutation `benign_mutation`, confirmation `none`, wait policy `not_async`.
1044
+
1045
+ Status on release: untried; it submits through `view.task.add`, but this transform was not among those run.
1046
+
1037
1047
  ### `view.transform.replace`
1038
1048
 
1039
1049
  Run: `mammoth view transform replace`. Exact input fields: `mammoth schema get view.transform.replace`.
@@ -1064,6 +1074,16 @@ Result: `ViewTransformSmallLargeResult`; mutation `reversible_pipeline`, confirm
1064
1074
 
1065
1075
  Status on release: untried; it submits through `view.task.add`, but this transform was not among those run.
1066
1076
 
1077
+ ### `view.transform.sort`
1078
+
1079
+ Run: `mammoth view transform sort`. Exact input fields: `mammoth schema get view.transform.sort`.
1080
+
1081
+ Example: `mammoth view transform sort 123 --input '{"order_by": [["Revenue", "DESC"]], "dataset_id": 456}'`. Placeholders are illustrative; resolve IDs and input from observed reads.
1082
+
1083
+ Result: `ViewTransformSortResult`; mutation `benign_mutation`, confirmation `none`, wait policy `not_async`.
1084
+
1085
+ Status on release: untried; it submits through `view.task.add`, but this transform was not among those run.
1086
+
1067
1087
  ### `view.transform.split`
1068
1088
 
1069
1089
  Run: `mammoth view transform split`. Exact input fields: `mammoth schema get view.transform.split`.
@@ -22,6 +22,8 @@ mammoth view list CUSTOMERS_DS --project PROJECT_ID # -> CUSTOMERS_VIEW
22
22
  # 2. look before you change: column names, types, row_count, a sample
23
23
  mammoth view get ORDERS_VIEW ORDERS_DS --project PROJECT_ID
24
24
  mammoth view data get ORDERS_VIEW ORDERS_DS --project PROJECT_ID
25
+ # data.column_warnings: text columns of numbers/dates (with the convert-type fix) and blanks;
26
+ # plan step 3 from them
25
27
 
26
28
  # 3. clean orders (each call waits for its job; read back after each value change)
27
29
  mammoth view transform discard-duplicates ORDERS_VIEW --project PROJECT_ID \
@@ -43,9 +45,10 @@ mammoth view get ORDERS_VIEW ORDERS_DS --project PROJECT_ID # row_count before
43
45
  # 5. join, then check the match rate before building on it
44
46
  mammoth view transform join ORDERS_VIEW --project PROJECT_ID \
45
47
  --input '{"dataset_id": ORDERS_DS, "foreign_view": CUSTOMERS_VIEW, "foreign_dataset_id": CUSTOMERS_DS, "join_type": "LEFT", "on": [{"left": "customer_id", "right": "customer_id"}], "select": ["region"]}'
46
- mammoth view data get ORDERS_VIEW ORDERS_DS --project PROJECT_ID
47
- # if region is empty/"Unknown" on most rows, the key does not match: compare the two key columns' values
48
+ # data.join_check: rows_before/rows_after, match_rate, unmatched_rows, unmatched_keys; report them
49
+ # if many rows are unmatched, the key does not match: compare the two key columns' values
48
50
  # (type, padding, case) with view data get on both views; do not summarise unmatched data
51
+ # rows_after > rows_before: a key repeats in the other view (use lookup for one value per key)
49
52
 
50
53
  # 6. deliverable first: export the cleaned, joined rows before any step that replaces them
51
54
  mammoth view export csv ORDERS_VIEW --project PROJECT_ID \
@@ -73,6 +73,41 @@ mammoth schema get view.transform.join
73
73
  mammoth view transform join VIEW_ID --project PROJECT_ID --input INPUT_JSON
74
74
  ```
75
75
 
76
+ ## What most pipelines look like
77
+
78
+ These patterns come from how Mammoth pipelines are usually built. Use them
79
+ as defaults, and let the user's request override them:
80
+
81
+ - Keep it short. Most pipelines have one to three steps and touch a few
82
+ columns. Add only the steps the deliverable needs.
83
+ - New column or overwrite? `set-values`, `math` and `lookup` take
84
+ `new_column` (add a column) or `existing_column` (replace the values in
85
+ place), never both. Overwriting is as common as adding. If the user said
86
+ "fix", "clean" or "change" a column, use `existing_column`. If they said
87
+ "add", "calculate" or "flag", use `new_column`. If it is not clear, ask.
88
+ - Joins are nearly always `LEFT` on one key column. Use `INNER`, `RIGHT`,
89
+ `OUTER` or a key of two or more columns only when the user asks for it or
90
+ the data needs it, and say why in your report.
91
+ - Check the key before a join or lookup. Most keys go in without a prep
92
+ step, so a type, case or padding difference shows up as unmatched rows. Read
93
+ both key columns first (`view data get`) and act on `join_check`.
94
+ - Date steps (`increment-date`, `extract-date`, and `text` on date columns)
95
+ fail more often than other steps. Before a date step, confirm that the
96
+ column type is `DATE` (`view get`); if it is `TEXT`, run `convert-type`
97
+ first. Check the result on a few rows.
98
+ - Convert several columns to the same type in one `convert-type` call (one
99
+ `conversions` entry for each column), not one call for each column.
100
+ - Remove duplicates near the end, after the cleanup and joins, so the check
101
+ uses the final columns.
102
+ - `filter` also takes a plain-language `prompt` instead of a `condition`.
103
+ Use a `condition` when the rule is exact (a column, an operator, a value).
104
+ Use `prompt` only when the user described the rule in words and no exact
105
+ condition expresses it, then check the kept rows.
106
+ - "Top N" means the largest values first. Use the smallest values first only
107
+ when the user says "bottom", "lowest" or "smallest".
108
+ - The last step of a pipeline is not a signal that it is finished. Say that
109
+ the work is done only after you verify the deliverable.
110
+
76
111
  ## Aggregate or summarise
77
112
 
78
113
  For a grouped summary (totals per region, counts per status) use the typed
@@ -9,11 +9,13 @@ or secrets in an argument, prompt, transcript, or checkpoint.
9
9
  from PyPI into a fresh environment and verify it:
10
10
 
11
11
  ```bash
12
- python3 -m venv .mammoth && .mammoth/bin/python -m pip install --upgrade mammoth-cli
12
+ python3 -m venv .mammoth && .mammoth/bin/python -m pip install --no-cache-dir --upgrade mammoth-cli
13
13
  .mammoth/bin/mammoth --version # or: uv tool install mammoth-cli
14
14
  ```
15
15
 
16
- Pin `mammoth-cli==X.Y.Z` when the task names an approved release. Use the
16
+ Pin `mammoth-cli==X.Y.Z` when the task names an approved release, and
17
+ check that `mammoth --version` prints it (a pip cache can serve an older
18
+ release for a few minutes after a new one is published). Use the
17
19
  host installer script (`installers/mammoth-install.sh` in the repository)
18
20
  only when the operator names it; do not pipe a remote script to a shell on
19
21
  your own initiative. The SDK (`mammoth-io`) comes with the CLI. If a later
@@ -684,9 +684,11 @@ HANDLERS: dict[str, Handler] = {
684
684
  "view.transform.lookup": view_ops_cmd.view_transform_lookup,
685
685
  "view.transform.math": view_ops_cmd.view_transform_math,
686
686
  "view.transform.pivot": view_ops_cmd.view_transform_pivot,
687
+ "view.transform.rename-columns": view_ops_cmd.view_transform_rename_columns,
687
688
  "view.transform.replace": view_ops_cmd.view_transform_replace,
688
689
  "view.transform.set-values": view_ops_cmd.view_transform_set_values,
689
690
  "view.transform.small-large": view_ops_cmd.view_transform_small_large,
691
+ "view.transform.sort": view_ops_cmd.view_transform_sort,
690
692
  "view.transform.split": view_ops_cmd.view_transform_split,
691
693
  "view.transform.substring": view_ops_cmd.view_transform_substring,
692
694
  "view.transform.text": view_ops_cmd.view_transform_text,
@@ -89,11 +89,11 @@ _COMMAND_DISCOVERY_PURPOSES = {
89
89
  "view.transform.convert-type": (
90
90
  "convert type cast numeric number text date column parse to number to date"
91
91
  ),
92
- "view.transform.copy-columns": "copy duplicate rename column into a new column name",
92
+ "view.transform.copy-columns": "copy duplicate column into a new column",
93
93
  "view.transform.crosstab": (
94
94
  "crosstab cross tab pivot table matrix rows by columns summary into a new dataset"
95
95
  ),
96
- "view.transform.date-diff": "date difference days between two date columns age duration",
96
+ "view.transform.date-diff": "date dates difference days between two date columns age duration",
97
97
  "view.transform.delete-columns": "delete drop remove columns",
98
98
  "view.transform.discard-duplicates": (
99
99
  "duplicate duplicates dedup dedupe deduplicate remove repeated rows unique distinct"
@@ -117,7 +117,7 @@ _COMMAND_DISCOVERY_PURPOSES = {
117
117
  "second view views dataset datasets table tables vlookup"
118
118
  ),
119
119
  "view.transform.json-extract": "json extract parse nested fields keys into columns",
120
- "view.transform.limit-rows": "limit top bottom first last n rows order by sorted head",
120
+ "view.transform.limit-rows": "limit top bottom first last n rows head",
121
121
  "view.transform.lookup": (
122
122
  "lookup look up vlookup reference table map code to name enrich one value "
123
123
  "from another view dataset"
@@ -130,12 +130,19 @@ _COMMAND_DISCOVERY_PURPOSES = {
130
130
  "pivot group by aggregate aggregation sum count average summary summarize "
131
131
  "summarise per region total"
132
132
  ),
133
+ "view.transform.rename-columns": (
134
+ "rename column columns header headers relabel change column name names title"
135
+ ),
133
136
  "view.transform.replace": "find replace substitute text value in columns",
134
137
  "view.transform.set-values": (
135
138
  "set values assign overwrite blank empty default where condition label "
136
139
  "category bucket flag if then conditional value"
137
140
  ),
138
141
  "view.transform.small-large": "nth smallest largest value across columns",
142
+ "view.transform.sort": (
143
+ "sort sorting order rows by column ascending descending asc desc arrange "
144
+ "highest lowest newest oldest latest earliest date dates alphabetical"
145
+ ),
139
146
  "view.transform.split": "split column by delimiter separator into columns",
140
147
  "view.transform.substring": "substring left right characters regex pattern extract part text",
141
148
  "view.transform.text": (
@@ -404,7 +411,12 @@ def _tokens(value: str) -> frozenset[str]:
404
411
 
405
412
 
406
413
  def _query_tokens(query: str) -> tuple[str, ...]:
407
- return tuple(token for token in _tokens(query) if token not in _DISCOVERY_STOPWORDS)
414
+ # Bare numbers ("top 10") are values, not intent words.
415
+ return tuple(
416
+ token
417
+ for token in _tokens(query)
418
+ if token not in _DISCOVERY_STOPWORDS and not token.isdigit()
419
+ )
408
420
 
409
421
 
410
422
  def _token_aliases(token: str) -> frozenset[str]:
@@ -46,6 +46,7 @@ from mammoth_cli.runtime.confirm import (
46
46
  from mammoth_cli.runtime.invocation import Invocation
47
47
  from mammoth_cli.runtime.session import open_service, require_project
48
48
  from mammoth_cli.services.conditions import CONDITION_KWARG, compile_condition
49
+ from mammoth_cli.services.data_quality import column_warnings
49
50
 
50
51
  HandlerResult = tuple[Any, dict[str, Any]]
51
52
 
@@ -252,10 +253,33 @@ BRIEF_VIEW_FIELDS: tuple[str, ...] = (
252
253
  )
253
254
 
254
255
 
256
+ def apply_column_renames(record: Any) -> Any:
257
+ """Show renamed columns under their new names in a dataview record.
258
+
259
+ A rename (``view transform rename-columns`` or the web grid) is stored in
260
+ ``display_properties.COLUMN_NAMES`` as ``{internal_name: name}``; the
261
+ server's ``metadata`` keeps the name the pipeline produced. Every CLI
262
+ output uses the name the user sees, so the rename wins.
263
+ """
264
+ if not isinstance(record, dict):
265
+ return record
266
+ display = record.get("display_properties")
267
+ renames = display.get("COLUMN_NAMES") if isinstance(display, dict) else None
268
+ metadata = record.get(_METADATA_KEY)
269
+ if not isinstance(renames, dict) or not renames or not isinstance(metadata, list):
270
+ return record
271
+ columns = []
272
+ for column in metadata:
273
+ new_name = renames.get(column.get(_INTERNAL_NAME_KEY)) if isinstance(column, dict) else None
274
+ columns.append({**column, _DISPLAY_NAME_KEY: new_name} if new_name else column)
275
+ return {**record, _METADATA_KEY: columns}
276
+
277
+
255
278
  def brief_view_record(record: Any) -> Any:
256
- """Keep only :data:`BRIEF_VIEW_FIELDS` of a dataview record."""
279
+ """Keep only :data:`BRIEF_VIEW_FIELDS` of a dataview record, renames applied."""
257
280
  if not isinstance(record, dict):
258
281
  return record
282
+ record = apply_column_renames(record)
259
283
  return {key: record[key] for key in BRIEF_VIEW_FIELDS if key in record}
260
284
 
261
285
 
@@ -369,6 +393,7 @@ def _dataview_metadata(
369
393
  )
370
394
  except Exception: # noqa: BLE001 -- labels are a presentation nicety, never fatal
371
395
  return []
396
+ info = apply_column_renames(info)
372
397
  metadata = info.get(_METADATA_KEY) if isinstance(info, dict) else None
373
398
  if not isinstance(metadata, list):
374
399
  return []
@@ -400,7 +425,7 @@ def _compile_query_filters(
400
425
  project_id: int | None,
401
426
  document: dict[str, Any],
402
427
  kwargs: dict[str, Any],
403
- ) -> dict[str, str] | None:
428
+ ) -> tuple[dict[str, str] | None, dict[str, str] | None]:
404
429
  """Translate a data query's ``condition`` and ``columns`` to the wire format.
405
430
 
406
431
  The data route takes the backend condition shape (``{internal: {OP: ...}}``,
@@ -409,11 +434,11 @@ def _compile_query_filters(
409
434
  only have one key". The spec is compiled through the shared condition
410
435
  service and built against the view's display -> internal name and type
411
436
  maps, and ``columns`` given as display names are mapped the same way.
412
- Returns the internal -> display map when metadata was fetched, so the row
413
- relabelling can reuse it.
437
+ Returns the internal -> display map and the display -> type map when
438
+ metadata was fetched, so the row relabelling and checks can reuse them.
414
439
  """
415
440
  if CONDITION_KWARG not in document and "columns" not in document:
416
- return None
441
+ return None, None
417
442
  metadata = _dataview_metadata(service, dataset_id, dataview_id, project_id)
418
443
  column_map: dict[str, str] = {}
419
444
  column_types: dict[str, str] = {}
@@ -430,7 +455,9 @@ def _compile_query_filters(
430
455
  columns = document.get("columns")
431
456
  if isinstance(columns, list):
432
457
  kwargs["columns"] = [column_map.get(c, c) if isinstance(c, str) else c for c in columns]
433
- return {internal: display for display, internal in column_map.items()} if metadata else None
458
+ if not metadata:
459
+ return None, None
460
+ return {internal: display for display, internal in column_map.items()}, column_types
434
461
 
435
462
 
436
463
  def _relabel_columns(
@@ -586,11 +613,14 @@ def view_data_get(invocation: Invocation) -> HandlerResult:
586
613
  """Fetch a dataview's data, waiting for the backing job to complete.
587
614
 
588
615
  The dataset is resolved from the view unless given as a trailing positional
589
- or a ``dataset_id`` --input field.
616
+ or a ``dataset_id`` --input field. ``offset`` (1-based) reads a later page
617
+ through the query route. The result carries ``column_warnings`` when the
618
+ rows show a text column of numbers or dates, or blanks.
590
619
  """
591
620
  project_id = require_project(invocation)
592
621
  view_id = _require_int_positional_at(invocation, 0, "view id")
593
622
  document = invocation.load_input() or {}
623
+ limit = document.get("limit", _DATA_GET_DEFAULT_LIMIT)
594
624
  with open_service(invocation) as (service, auth):
595
625
  dataset_id = _resolve_dataset_id(service, invocation, view_id, document)
596
626
  kwargs: dict[str, Any] = {
@@ -598,12 +628,165 @@ def view_data_get(invocation: Invocation) -> HandlerResult:
598
628
  "dataview_id": view_id,
599
629
  "project_id": project_id,
600
630
  }
601
- _forward_optional(document, kwargs, ("timeout", "poll_interval", "sequence"))
602
- data = service.call(_symbol(invocation), **kwargs)
603
- data = _relabel_columns(service, dataset_id, view_id, project_id, data)
604
- return _trim_rows(data, document.get("limit", _DATA_GET_DEFAULT_LIMIT)), _meta(
605
- invocation, auth.workspace_id, project_id
606
- )
631
+ if document.get("offset") is not None:
632
+ kwargs["offset"] = int(document["offset"])
633
+ kwargs["limit"] = int(limit) if int(limit) > 0 else 400
634
+ _forward_optional(document, kwargs, ("sequence",))
635
+ data = service.call(_QUERY_DATA_SYMBOL, **kwargs)
636
+ else:
637
+ _forward_optional(document, kwargs, ("timeout", "poll_interval", "sequence"))
638
+ data = service.call(_symbol(invocation), **kwargs)
639
+ data = _relabel_and_check(service, dataset_id, view_id, project_id, data)
640
+ return _trim_rows(data, limit), _meta(invocation, auth.workspace_id, project_id)
641
+
642
+
643
+ _QUERY_DATA_SYMBOL = "mammoth.api.dataviews.DataviewsAPI.query_data"
644
+
645
+ _DATA_PAGE_SYMBOL = "mammoth.api.dataviews.DataviewsAPI.get_data"
646
+ _MAX_UNMATCHED_KEYS = 5
647
+
648
+
649
+ def join_snapshot(
650
+ service: Any, dataset_id: int, dataview_id: int, project_id: int | None
651
+ ) -> dict[str, Any]:
652
+ """Row count, columns and the first data page of a view (best effort)."""
653
+ snapshot: dict[str, Any] = {"row_count": None, "columns": {}, "rows": []}
654
+ try:
655
+ info = apply_column_renames(
656
+ service.call(
657
+ _DATAVIEW_GET_SYMBOL,
658
+ dataset_id=dataset_id,
659
+ dataview_id=dataview_id,
660
+ project_id=project_id,
661
+ )
662
+ )
663
+ if isinstance(info, dict):
664
+ snapshot["row_count"] = info.get("row_count")
665
+ snapshot["columns"] = {
666
+ c.get(_INTERNAL_NAME_KEY): c.get(_DISPLAY_NAME_KEY)
667
+ for c in info.get(_METADATA_KEY) or []
668
+ if isinstance(c, dict)
669
+ }
670
+ page = service.call(
671
+ _DATA_PAGE_SYMBOL,
672
+ dataset_id=dataset_id,
673
+ dataview_id=dataview_id,
674
+ project_id=project_id,
675
+ )
676
+ page = _relabel_columns(
677
+ service, dataset_id, dataview_id, project_id, page, snapshot["columns"]
678
+ )
679
+ rows = page.get(_ROWS_KEY) if isinstance(page, dict) else None
680
+ snapshot["rows"] = [r for r in rows or [] if isinstance(r, dict)]
681
+ except Exception: # noqa: BLE001 -- the check is advice; the join already ran
682
+ return snapshot
683
+ return snapshot
684
+
685
+
686
+ def with_join_check(data: Any, before: Any, after: dict[str, Any], document: dict[str, Any]) -> Any:
687
+ """Add ``join_check`` (row counts, columns added, match rate) to a join result.
688
+
689
+ ``unmatched_rows`` counts rows where every added column is blank: for a
690
+ LEFT join these are rows whose key found no match in the other view.
691
+ """
692
+ if not isinstance(data, dict) or not isinstance(before, dict):
693
+ return data
694
+ added = [
695
+ name
696
+ for internal, name in after.get("columns", {}).items()
697
+ if internal not in before.get("columns", {}) and isinstance(name, str)
698
+ ]
699
+ rows = after.get("rows") or []
700
+ check: dict[str, Any] = {
701
+ "rows_before": before.get("row_count"),
702
+ "rows_after": after.get("row_count"),
703
+ "columns_added": added,
704
+ "rows_checked": len(rows),
705
+ }
706
+ notes: list[str] = []
707
+ on = document.get("on")
708
+ left_key = None
709
+ if isinstance(on, list) and on and isinstance(on[0], dict):
710
+ left_key = on[0].get("left")
711
+ if added and rows:
712
+ unmatched = [r for r in rows if all(r.get(c) in (None, "") for c in added)]
713
+ check["unmatched_rows"] = len(unmatched)
714
+ check["match_rate"] = round((len(rows) - len(unmatched)) / len(rows), 3)
715
+ if unmatched:
716
+ keys = sorted({str(r.get(left_key)) for r in unmatched if left_key in r})
717
+ check["unmatched_keys"] = keys[:_MAX_UNMATCHED_KEYS]
718
+ notes.append(
719
+ f"{len(unmatched)} of {len(rows)} rows found no match. If that is more "
720
+ "than a few, compare the key columns in both views (type, case, "
721
+ "padding) before you build on this; otherwise say so in your report."
722
+ )
723
+ before_n, after_n = check["rows_before"], check["rows_after"]
724
+ if isinstance(before_n, int) and isinstance(after_n, int):
725
+ if after_n > before_n:
726
+ notes.append(
727
+ f"The join added {after_n - before_n} rows: a key repeats in the other "
728
+ "view, so matching rows repeat. Totals over this view count them twice; "
729
+ "use view transform lookup for one value per key."
730
+ )
731
+ elif after_n < before_n:
732
+ notes.append(
733
+ f"{before_n - after_n} rows had no match and were dropped "
734
+ "(an INNER join keeps matched rows only)."
735
+ )
736
+ if isinstance(after_n, int) and len(rows) < after_n:
737
+ notes.append(f"Match rate is from the first {len(rows)} rows of {after_n}.")
738
+ if notes:
739
+ check["notes"] = notes
740
+ return {**data, "join_check": check}
741
+
742
+
743
+ def _column_profile(
744
+ service: Any, dataset_id: int, dataview_id: int, project_id: int | None
745
+ ) -> tuple[dict[str, str], dict[str, str]]:
746
+ """One metadata read: internal-to-display names, and display name to type."""
747
+ mapping: dict[str, str] = {}
748
+ types: dict[str, str] = {}
749
+ for column in _dataview_metadata(service, dataset_id, dataview_id, project_id):
750
+ internal = column.get(_INTERNAL_NAME_KEY)
751
+ display = column.get(_DISPLAY_NAME_KEY)
752
+ if isinstance(internal, str) and isinstance(display, str):
753
+ mapping[internal] = display
754
+ types[display] = str(column.get("type") or "")
755
+ return mapping, types
756
+
757
+
758
+ def _relabel_and_check(
759
+ service: Any,
760
+ dataset_id: int,
761
+ view_id: int,
762
+ project_id: int | None,
763
+ data: Any,
764
+ mapping: dict[str, str] | None = None,
765
+ types: dict[str, str] | None = None,
766
+ ) -> Any:
767
+ """Relabel a data page to display names and add ``column_warnings``.
768
+
769
+ The metadata read happens only when the page has rows and the caller has
770
+ not read it already (one read serves both the names and the types).
771
+ """
772
+ rows = data.get(_ROWS_KEY) if isinstance(data, dict) else None
773
+ if not isinstance(rows, list) or not rows:
774
+ return _relabel_columns(service, dataset_id, view_id, project_id, data, mapping)
775
+ if mapping is None or types is None:
776
+ mapping, types = _column_profile(service, dataset_id, view_id, project_id)
777
+ data = _relabel_columns(service, dataset_id, view_id, project_id, data, mapping)
778
+ return _with_column_warnings(data, types, view_id)
779
+
780
+
781
+ def _with_column_warnings(data: Any, types: dict[str, str], view_id: int) -> Any:
782
+ """Add ``column_warnings`` for the rows of a data page (never fatal)."""
783
+ if not isinstance(data, dict) or not isinstance(data.get(_ROWS_KEY), list) or not types:
784
+ return data
785
+ try:
786
+ warnings = column_warnings(data[_ROWS_KEY], types, view_id)
787
+ except Exception: # noqa: BLE001 -- a presentation aid must not fail the read
788
+ return data
789
+ return {**data, "column_warnings": warnings} if warnings else data
607
790
 
608
791
 
609
792
  #: Rows a plain ``view data get`` returns unless ``limit`` says otherwise.
@@ -653,9 +836,11 @@ def view_data_query(invocation: Invocation) -> HandlerResult:
653
836
  "project_id": project_id,
654
837
  }
655
838
  _forward_optional(document, kwargs, ("sequence", "offset", "limit", "sort"))
656
- mapping = _compile_query_filters(service, dataset_id, view_id, project_id, document, kwargs)
839
+ mapping, types = _compile_query_filters(
840
+ service, dataset_id, view_id, project_id, document, kwargs
841
+ )
657
842
  data = service.call(_symbol(invocation), **kwargs)
658
- data = _relabel_columns(service, dataset_id, view_id, project_id, data, mapping)
843
+ data = _relabel_and_check(service, dataset_id, view_id, project_id, data, mapping, types)
659
844
  return data, _meta(invocation, auth.workspace_id, project_id)
660
845
 
661
846