@metabase/cli 0.1.16 → 0.1.18

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 (221) hide show
  1. package/README.md +21 -0
  2. package/dist/{add-collection-H4LcP-9B.mjs → add-collection-CRZAFCZy.mjs} +5 -5
  3. package/dist/add-collection-DbalcC3_.mjs +11 -0
  4. package/dist/{archive-D1mfiftv.mjs → archive-3mEhK9_H.mjs} +8 -7
  5. package/dist/{archive-C6MDtV1F.mjs → archive-C3RWdM-2.mjs} +8 -6
  6. package/dist/{archive-CQQWkC5S.mjs → archive-CFHYRF65.mjs} +7 -6
  7. package/dist/{archive-hN8PfvhX.mjs → archive-De4yVEV8.mjs} +7 -6
  8. package/dist/{archive-BCXoM1nX.mjs → archive-Z8ZM-ilB.mjs} +9 -7
  9. package/dist/{archive-CdUS-OG9.mjs → archive-d74nDcp4.mjs} +7 -6
  10. package/dist/{archive-B3cjzOIK.mjs → archive-errg-EKw.mjs} +8 -7
  11. package/dist/auth--PWX0oj5.mjs +22 -0
  12. package/dist/{body-DB2upz6a.mjs → body-BS2s2zDl.mjs} +3 -3
  13. package/dist/{branches-B6R60Vr1.mjs → branches-mcyT7wI-.mjs} +7 -6
  14. package/dist/{cancel-oPsWomYc.mjs → cancel-B2l-i2NS.mjs} +6 -5
  15. package/dist/{cancel-task-CleJVDNI.mjs → cancel-task-CugcIeIi.mjs} +7 -6
  16. package/dist/{capabilities-BX1rnVuH.mjs → capabilities-N0jo5U7S.mjs} +1 -1
  17. package/dist/card-D07WYCQY.mjs +26 -0
  18. package/dist/{card-wCPcuKSi.mjs → card-DEmcRlNO.mjs} +6 -5
  19. package/dist/{cards-Bw37jizL.mjs → cards-BGkz5BeZ.mjs} +8 -6
  20. package/dist/cli.mjs +54 -27
  21. package/dist/collection-Dss2qSEF.mjs +23 -0
  22. package/dist/{collection-namespace-CUDPh2rF.mjs → collection-namespace-BP_LJrAD.mjs} +2 -2
  23. package/dist/command-augment-DdZIfx1V.mjs +11 -0
  24. package/dist/{create-Bj6PuaAW.mjs → create-B4HAEEE0.mjs} +9 -8
  25. package/dist/{create-BEkuqChu.mjs → create-BOmmMSjF.mjs} +16 -11
  26. package/dist/{create-DSWjS09p.mjs → create-CD9Rp8qs.mjs} +21 -12
  27. package/dist/{create-DwoqFYb9.mjs → create-CkRFKtFG.mjs} +9 -8
  28. package/dist/{create-CZ0_s5ap.mjs → create-Cztp5kES.mjs} +7 -6
  29. package/dist/{create-CF2Zn4pT.mjs → create-D8KA4zJA.mjs} +10 -9
  30. package/dist/{create-B-mvVFIl.mjs → create-DhrftYre.mjs} +28 -12
  31. package/dist/{create-D-qE3Oq8.mjs → create-DzyMvAVy.mjs} +9 -8
  32. package/dist/{create-Cy2TNnJB.mjs → create-YfGzANNB.mjs} +9 -8
  33. package/dist/{create-branch-BaZ00MIc.mjs → create-branch-DsoMfx_2.mjs} +7 -6
  34. package/dist/{create-CfLaBO0V.mjs → create-kWzqvTGR.mjs} +16 -11
  35. package/dist/{create-B87ZQjNM.mjs → create-wpvDXSSY.mjs} +17 -12
  36. package/dist/{current-task-Dr5dOD4V.mjs → current-task-WuKPZpZ6.mjs} +7 -6
  37. package/dist/dashboard-Crh4v6Fq.mjs +35 -0
  38. package/dist/{dashboard-B4bn3z6t.mjs → dashboard-DOplbKyQ.mjs} +7 -5
  39. package/dist/{database-BJxGUXhA.mjs → database-D9fftP-i.mjs} +1 -1
  40. package/dist/db-BCAURQei.mjs +28 -0
  41. package/dist/{delete-DHrFA1SZ.mjs → delete-BCitmugk.mjs} +8 -7
  42. package/dist/{delete-DJti0TOA.mjs → delete-Buai0_e_.mjs} +8 -7
  43. package/dist/{delete-evwMw6Hk.mjs → delete-CmGh0jo1.mjs} +8 -7
  44. package/dist/{delete-runtime-CZMw_AGX.mjs → delete-runtime-B0ha5QR4.mjs} +3 -3
  45. package/dist/{delete-table-DvuuwVqu.mjs → delete-table-BOkYxdjA.mjs} +8 -7
  46. package/dist/{dependencies-DrV31Rj5.mjs → dependencies-XZrEvHUA.mjs} +7 -6
  47. package/dist/{dirty-C-rkrnVM.mjs → dirty-B-5NDbG9.mjs} +7 -6
  48. package/dist/document-upxL2nKH.mjs +22 -0
  49. package/dist/{eid-BeXI-eII.mjs → eid-BCuJRv7a.mjs} +13 -7
  50. package/dist/{error-CIObEXLY.mjs → error-BaBm-UrT.mjs} +2 -2
  51. package/dist/{export-CGa-SgEV.mjs → export-Dod2gdyE.mjs} +9 -8
  52. package/dist/field-CloFa1oe.mjs +21 -0
  53. package/dist/{fields-Recymu7n.mjs → fields-B0yttrzR.mjs} +8 -7
  54. package/dist/{get-C6g2C4dM.mjs → get-9p0Gt1c7.mjs} +7 -6
  55. package/dist/{get-CYh2DBsq.mjs → get-B0OsW6jk.mjs} +7 -6
  56. package/dist/{get-CHi8tU_1.mjs → get-B8tugTku.mjs} +9 -8
  57. package/dist/{get-BD_P_Ejc.mjs → get-BkGh9BPd.mjs} +7 -6
  58. package/dist/{get-BM-d3-zk.mjs → get-BpZ81hX8.mjs} +7 -6
  59. package/dist/{get-CARdLkmp.mjs → get-CbpHt4Hs.mjs} +7 -6
  60. package/dist/{get-b4xbfFbA.mjs → get-CfeYa0v5.mjs} +7 -6
  61. package/dist/{get-DaixPDU9.mjs → get-D-pNrQGA.mjs} +7 -6
  62. package/dist/{get-rFcAVIch.mjs → get-D7QMqPOK.mjs} +7 -6
  63. package/dist/{get-BAH_M5vj.mjs → get-DBY5esTW.mjs} +7 -6
  64. package/dist/{get-Q2WZ79q_.mjs → get-DISgP66L.mjs} +8 -7
  65. package/dist/{get-Nc5GOs6-.mjs → get-DJ8huA8y.mjs} +8 -6
  66. package/dist/{get-CXMv-r1p.mjs → get-Dz7fcqoG.mjs} +9 -7
  67. package/dist/{get-BQxLtFE-.mjs → get-k6M5nGMC.mjs} +7 -6
  68. package/dist/{get-run-CfQR6ZNa.mjs → get-run-DQJVpDw1.mjs} +7 -6
  69. package/dist/{get-7fWSU6ow.mjs → get-u5Uq9vms.mjs} +6 -5
  70. package/dist/git-sync-C9m-OHac.mjs +31 -0
  71. package/dist/group-BNE_RiH5.mjs +28 -0
  72. package/dist/{has-remote-changes-Bvyv4FLP.mjs → has-remote-changes-DvQBXudi.mjs} +7 -6
  73. package/dist/{import-c3o3OAx0.mjs → import-PGS8-DwE.mjs} +9 -8
  74. package/dist/{input-BXWgdKiS.mjs → input-7Sj85_K7.mjs} +1 -1
  75. package/dist/is-dirty-DpyKeGAJ.mjs +10 -0
  76. package/dist/{is-dirty-BE53XwOC.mjs → is-dirty-DyEVFQVJ.mjs} +4 -4
  77. package/dist/{items-1KkBMiO4.mjs → items-CkFEy2Du.mjs} +9 -8
  78. package/dist/{key-DwiMOWRQ.mjs → key-bltP32Pm.mjs} +1 -1
  79. package/dist/library-1AAVbk-K.mjs +24 -0
  80. package/dist/{list-C4KnM3Rq.mjs → list-82NkvRpI.mjs} +8 -6
  81. package/dist/{list-Drr4JiWg.mjs → list-B9wXg3qi.mjs} +6 -5
  82. package/dist/{list-BwdO1_gX.mjs → list-BBxRjuMn.mjs} +6 -5
  83. package/dist/{list-CSjFJDls.mjs → list-BCYTTCFD.mjs} +6 -5
  84. package/dist/{list-BqgbrpQQ.mjs → list-BVzu2RIZ.mjs} +6 -5
  85. package/dist/{list-DUEYX3bX.mjs → list-BWAYDSbQ.mjs} +6 -5
  86. package/dist/{list-BC2B02IR.mjs → list-BY4S32Lg.mjs} +6 -5
  87. package/dist/{list-7rwzxX6t.mjs → list-Bb8YRON_.mjs} +6 -5
  88. package/dist/{list-DIvOPW1g.mjs → list-CJx5q-Yn.mjs} +8 -7
  89. package/dist/{list-F0vkE22V.mjs → list-CK0p7vvK.mjs} +7 -6
  90. package/dist/{list-CO5J3SZU.mjs → list-CKzpoTgP.mjs} +8 -7
  91. package/dist/{list-FR8Q1SzV.mjs → list-CjF12k1G.mjs} +7 -6
  92. package/dist/{list-D52_BozQ.mjs → list-D-rgDFa5.mjs} +9 -7
  93. package/dist/{list-DPcPTqFU.mjs → list-Sgo3RfDY.mjs} +6 -5
  94. package/dist/{list-DHb4vQUM.mjs → list-pQ22nXhQ.mjs} +6 -5
  95. package/dist/{login-C0Rf2hg0.mjs → login-C6ZAnGHz.mjs} +10 -9
  96. package/dist/{logout-C7_UON-s.mjs → logout-CWjyY3Y8.mjs} +6 -5
  97. package/dist/{manifest-B2F8iL7X.mjs → manifest-BVf8P4bl.mjs} +9 -2
  98. package/dist/measure-Cbly1r0E.mjs +25 -0
  99. package/dist/{metadata-Db3Kpo-z.mjs → metadata-CW5Lfw5d.mjs} +9 -8
  100. package/dist/{metadata-FltZq5Ek.mjs → metadata-aQAqseCm.mjs} +8 -7
  101. package/dist/{command-augment-CAur0XOQ.mjs → notice-DyVl5aYB.mjs} +1 -11
  102. package/dist/parameter-CiJ4CwWE.mjs +118 -0
  103. package/dist/parameter-values-DmDOuE-j.mjs +56 -0
  104. package/dist/{parse-enum-BatHQ-Gs.mjs → parse-enum-BL9i_brN.mjs} +1 -1
  105. package/dist/{parse-id-B5adfBlS.mjs → parse-id-DlXnOcmP.mjs} +1 -1
  106. package/dist/{parse-ref-CZr1bYIl.mjs → parse-ref-CB_KvF9h.mjs} +1 -1
  107. package/dist/{path-BojuJkE4.mjs → path-D8IJ4YrW.mjs} +6 -5
  108. package/dist/{poll-AduuU55-.mjs → poll-hgnrHBoh.mjs} +2 -2
  109. package/dist/{poll-task-B00Qwd87.mjs → poll-task-NQNLT_aA.mjs} +2 -2
  110. package/dist/{preflight-QVPvG_Xg.mjs → preflight-DvaPQHHf.mjs} +4 -4
  111. package/dist/{process-DsGf7Mg5.mjs → process-j8UHMHc2.mjs} +1 -1
  112. package/dist/{prompt-Bc_bHSD0.mjs → prompt-C85xd9HR.mjs} +1 -1
  113. package/dist/{publish-h5RJh6im.mjs → publish-BntmFR6g.mjs} +9 -8
  114. package/dist/{query-BUkuB4bZ.mjs → query-BQfgKV68.mjs} +10 -8
  115. package/dist/{query-Dvi-Rksy.mjs → query-DSKQZu91.mjs} +19 -13
  116. package/dist/{query-result-L5_NrwQR.mjs → query-result-D6mfoVfQ.mjs} +1 -1
  117. package/dist/{remove-collection-D8ZfB2RN.mjs → remove-collection-BguaI3-6.mjs} +9 -8
  118. package/dist/{rescan-values-DOsDLrRG.mjs → rescan-values-DjD6IW3J.mjs} +9 -8
  119. package/dist/{resolve-BQ9vjlNJ.mjs → resolve-Dj2MTBkn.mjs} +1 -1
  120. package/dist/{run-CeG0KH5W.mjs → run-Bp1yxkBN.mjs} +6 -5
  121. package/dist/{run-CjhD-Zbr.mjs → run-RQfQj7Rk.mjs} +9 -8
  122. package/dist/{runs-6k8C6kXF.mjs → runs-CSsatfWb.mjs} +8 -7
  123. package/dist/{runtime-CmAIahm5.mjs → runtime-oxjmrYoP.mjs} +5 -3
  124. package/dist/{schema-tables-C45QegaY.mjs → schema-tables-5I5pCxHl.mjs} +8 -7
  125. package/dist/{schemas-EVwEFuTj.mjs → schemas-CfzFCfBt.mjs} +6 -5
  126. package/dist/{search-C_uw_D1U.mjs → search-Vo-BqliA.mjs} +11 -5
  127. package/dist/segment-BuN_IoM8.mjs +25 -0
  128. package/dist/{selectors-AktxTEMK.mjs → selectors-DBnJsKlW.mjs} +3 -3
  129. package/dist/{set-C3EAuyb8.mjs → set-1Vh0AF-T.mjs} +9 -8
  130. package/dist/{set-active-BW6LN6y0.mjs → set-active-C0mUZlNN.mjs} +6 -5
  131. package/dist/setting-BbdKR-lO.mjs +20 -0
  132. package/dist/{setup-CYrbNrlG.mjs → setup-CzYFvFK5.mjs} +8 -7
  133. package/dist/{skills-DJsuBguh.mjs → skills-B6gfH0iR.mjs} +1 -1
  134. package/dist/{skills-D6xQkmhu.mjs → skills-CR2xyV33.mjs} +3 -3
  135. package/dist/snippet-Bzo2U9Fv.mjs +22 -0
  136. package/dist/{stash-C1V2FvJR.mjs → stash-CQHXwBu_.mjs} +9 -8
  137. package/dist/{status-DY92F9mn.mjs → status-C_7aqTvB.mjs} +6 -5
  138. package/dist/{status-CxYw6zQM.mjs → status-DXQkM18v.mjs} +8 -7
  139. package/dist/{summary-DOxgqJoA.mjs → summary-1aNpQc8j.mjs} +7 -6
  140. package/dist/{sync-schema-CQPfffjU.mjs → sync-schema-BqLp5uTS.mjs} +11 -10
  141. package/dist/table-BwGOz97O.mjs +22 -0
  142. package/dist/{table-CDMG0Zi5.mjs → table-DE3i82T_.mjs} +1 -1
  143. package/dist/transform-B65ZD9-e.mjs +31 -0
  144. package/dist/transform-job-BWVKXSV6.mjs +25 -0
  145. package/dist/transform-tag-C4qvmicL.mjs +21 -0
  146. package/dist/{transforms-DzBJDydn.mjs → transforms-BelyllUL.mjs} +7 -6
  147. package/dist/{tree-B3f5F_dP.mjs → tree-YvmwGql7.mjs} +6 -5
  148. package/dist/{unpublish-B5RDeN-V.mjs → unpublish-CHjGLMu9.mjs} +7 -6
  149. package/dist/{update-CcvDVqNd.mjs → update-BACl_bJS.mjs} +10 -9
  150. package/dist/{update-PZPNx0Xd.mjs → update-BGMJTpA6.mjs} +17 -12
  151. package/dist/{update-DOfL_KPx.mjs → update-BKhekuqr.mjs} +22 -13
  152. package/dist/{update-DSueNZRw.mjs → update-BWlN3QDo.mjs} +10 -9
  153. package/dist/{update-C0pFSc1B.mjs → update-BnHYSu96.mjs} +18 -13
  154. package/dist/{update-D698CaeV.mjs → update-C9fndCqb.mjs} +10 -9
  155. package/dist/{update-I3TA2Tem.mjs → update-CrRz47aj.mjs} +22 -13
  156. package/dist/{update-BIZ9XhjS.mjs → update-D7vc8GBF.mjs} +17 -12
  157. package/dist/{update-DudyZ-FP.mjs → update-K-jjQ0Aw.mjs} +11 -10
  158. package/dist/{update-CiWPEqQ-.mjs → update-Zz01Woxj.mjs} +10 -9
  159. package/dist/{update-dashcard-BXZ4vS15.mjs → update-dashcard-Dvth-yLC.mjs} +11 -9
  160. package/dist/{update-D5gioyBa.mjs → update-txUfAJxy.mjs} +10 -9
  161. package/dist/{upgrade-4cWfLu90.mjs → upgrade-DsXlfPef.mjs} +7 -6
  162. package/dist/{uuid---pAboNQ.mjs → uuid-ByJmWV7b.mjs} +10 -5
  163. package/dist/{validate-VawhJ5Sc.mjs → validate-BqNW4Sk1.mjs} +2 -2
  164. package/dist/{validate-query-CSV-TTnd.mjs → validate-query-CcZVKYPV.mjs} +3 -3
  165. package/dist/{values-I503dI7K.mjs → values-CIjJI4sz.mjs} +7 -6
  166. package/dist/{verify-BMhTWW9s.mjs → verify-Jsuc2dat.mjs} +2 -2
  167. package/dist/{wait-oMSs_IdS.mjs → wait-5ltTFvSa.mjs} +8 -7
  168. package/dist/{wait-flags-HtCL2l1r.mjs → wait-flags-D6kd_G7c.mjs} +2 -2
  169. package/package.json +1 -1
  170. package/skill-data/core/SKILL.md +47 -56
  171. package/skill-data/dashboard/SKILL.md +107 -0
  172. package/skill-data/data-workflow/SKILL.md +116 -0
  173. package/skill-data/{data-analysis/SKILL.md → data-workflow/references/answering-questions.md} +7 -13
  174. package/skill-data/{data-transformation/SKILL.md → data-workflow/references/building-clean-tables.md} +23 -32
  175. package/skill-data/{semantic-layer/SKILL.md → data-workflow/references/reusable-definitions.md} +26 -56
  176. package/skill-data/document/SKILL.md +11 -21
  177. package/skill-data/git-sync/SKILL.md +39 -39
  178. package/skill-data/mbql/SKILL.md +19 -31
  179. package/skill-data/mbql/references/operators.md +13 -3
  180. package/skill-data/metadata/SKILL.md +78 -0
  181. package/skill-data/metadata/references/semantic-types.md +83 -0
  182. package/skill-data/native-sql/SKILL.md +118 -0
  183. package/skill-data/native-sql/references/template-tags.md +178 -0
  184. package/skill-data/transform/SKILL.md +30 -46
  185. package/skill-data/visualization/SKILL.md +8 -8
  186. package/skill-data/visualization/references/settings.md +14 -0
  187. package/skills/metabase-cli/SKILL.md +2 -2
  188. package/dist/add-collection-BqfYL4FU.mjs +0 -10
  189. package/dist/auth-BaCMFLTA.mjs +0 -19
  190. package/dist/card-DzH3aK0a.mjs +0 -20
  191. package/dist/collection-Wagz-ira.mjs +0 -20
  192. package/dist/dashboard-OUgS1Gi-.mjs +0 -21
  193. package/dist/db-Btfl5JMZ.mjs +0 -22
  194. package/dist/document-CU28GfFw.mjs +0 -19
  195. package/dist/field-BTbzlcyC.mjs +0 -18
  196. package/dist/git-sync-CBxS2urR.mjs +0 -28
  197. package/dist/is-dirty-Z-pqyVyB.mjs +0 -9
  198. package/dist/library-BlbH0xyK.mjs +0 -18
  199. package/dist/measure-BUedPu4K.mjs +0 -19
  200. package/dist/segment-CkZUZcWz.mjs +0 -19
  201. package/dist/setting-C50HEiGG.mjs +0 -17
  202. package/dist/snippet-BjaWAxCu.mjs +0 -19
  203. package/dist/table-B35ovbcd.mjs +0 -19
  204. package/dist/transform-DF79sJ0_.mjs +0 -25
  205. package/dist/transform-job-PmA_D8gz.mjs +0 -22
  206. package/dist/transform-tag-CE3cuO1K.mjs +0 -18
  207. package/skill-data/robot-data-engineer/SKILL.md +0 -142
  208. /package/dist/{body-flags-D7q87Btw.mjs → body-flags-DWTTxJpP.mjs} +0 -0
  209. /package/dist/{collection-Deiziuu2.mjs → collection-DrLpA1SO.mjs} +0 -0
  210. /package/dist/{document-qfwR0r63.mjs → document-1W7NRaO_.mjs} +0 -0
  211. /package/dist/{field-E0IBy4Uw.mjs → field-CMY_LWUe.mjs} +0 -0
  212. /package/dist/{measure-BCv5wDDN.mjs → measure-DoJvtCaA.mjs} +0 -0
  213. /package/dist/{paginate-BexjkjbY.mjs → paginate-FVZUxL4J.mjs} +0 -0
  214. /package/dist/{render-CkuFkWlQ.mjs → render-BTKnWL0d.mjs} +0 -0
  215. /package/dist/{revision-message-flag-CP5NFrWQ.mjs → revision-message-flag-CHrJgFFx.mjs} +0 -0
  216. /package/dist/{segment-BAUuELKs.mjs → segment-TXktTCfU.mjs} +0 -0
  217. /package/dist/{setting-DhMk0TNo.mjs → setting-m46MUtW5.mjs} +0 -0
  218. /package/dist/{snippet-D4SyVLKB.mjs → snippet-CtA2Pkoa.mjs} +0 -0
  219. /package/dist/{transform-MmqHKGU-.mjs → transform-DEF38FWe.mjs} +0 -0
  220. /package/dist/{transform-job-CtVziW85.mjs → transform-job-CtixL4An.mjs} +0 -0
  221. /package/dist/{transform-tag-wFiWmiyO.mjs → transform-tag-rsIrckCM.mjs} +0 -0
@@ -0,0 +1,178 @@
1
+ # Template tags — full reference
2
+
3
+ Every template-tag body, the widget-type vocabulary, and the parameter-object shapes. The main skill covers the two you author most (field filter, raw variable); this is the rest plus the exhaustive field lists.
4
+
5
+ ## Template-tag bodies by `type`
6
+
7
+ The `template-tags` value is a map keyed by tag name; each entry's `name` must equal its key and the `{{name}}` in the SQL. `id` is a UUID — mint with `mb uuid`.
8
+
9
+ ### Raw variable — `text` / `number` / `date` / `boolean`
10
+
11
+ ```json
12
+ "min_total": {
13
+ "id": "<uuid>",
14
+ "name": "min_total",
15
+ "display-name": "Minimum total",
16
+ "type": "number",
17
+ "required": false,
18
+ "default": "50"
19
+ }
20
+ ```
21
+
22
+ | Field | Req | Notes |
23
+ | -------------- | --- | -------------------------------------------- |
24
+ | `name` | ✓ | equals map key and `{{name}}` |
25
+ | `display-name` | ✓ | label shown in the widget |
26
+ | `type` | ✓ | `text` \| `number` \| `date` \| `boolean` |
27
+ | `id` | — | UUID; supply one |
28
+ | `required` | — | `true` blocks the run until a value is given |
29
+ | `default` | — | value used when none passed (string form) |
30
+
31
+ SQL: `{{min_total}}`, spliced literally — you write the operator (`total > {{min_total}}`).
32
+
33
+ ### Field filter — `dimension`
34
+
35
+ ```json
36
+ "status": {
37
+ "id": "<uuid>",
38
+ "name": "status",
39
+ "display-name": "Status",
40
+ "type": "dimension",
41
+ "dimension": ["field", {}, 141],
42
+ "widget-type": "string/=",
43
+ "default": null,
44
+ "options": null,
45
+ "alias": null
46
+ }
47
+ ```
48
+
49
+ | Field | Req | Notes |
50
+ | ------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
51
+ | `type` | ✓ | `"dimension"` |
52
+ | `dimension` | ✓ | field ref `["field", {}, <id>]` — options object second, id third (the `mbql` rule); the legacy `["field", <id>, null]` form is rejected by pre-flight |
53
+ | `widget-type` | ✓ | the widget/operator; must suit the column type (table below) |
54
+ | `default` | — | e.g. a value, or a `["2024-01-01","2024-12-31"]` range |
55
+ | `options` | — | filter options map (e.g. case sensitivity), usually `null` |
56
+ | `alias` | — | set when the column comes from an aliased table in the SQL |
57
+
58
+ SQL: bare — `WHERE {{status}}`. Never `WHERE status = {{status}}`. On write, send `{}` for the ref's options; the server fills a `lib/uuid` and the card reads back `["field", {"lib/uuid": "…"}, <id>]`.
59
+
60
+ ### Snippet — `snippet`
61
+
62
+ ```json
63
+ "snippet: Active Rows": {
64
+ "id": "<uuid>",
65
+ "name": "snippet: Active Rows",
66
+ "display-name": "Snippet: Active Rows",
67
+ "type": "snippet",
68
+ "snippet-name": "Active Rows",
69
+ "snippet-id": 5
70
+ }
71
+ ```
72
+
73
+ SQL: `{{snippet: Active Rows}}`. Create/manage the fragment with `mb snippet` (`content` is bare SQL). No user value.
74
+
75
+ ### Card reference — `card`
76
+
77
+ ```json
78
+ "#42": {
79
+ "id": "<uuid>",
80
+ "name": "#42",
81
+ "display-name": "#42",
82
+ "type": "card",
83
+ "card-id": 42
84
+ }
85
+ ```
86
+
87
+ SQL: `{{#42}}` or `{{#42-slug}}`, used where a table/subquery goes (`FROM {{#42}}`, `WITH x AS {{#42}}`). Runs with the referenced card's own defaults; no user value.
88
+
89
+ ### Source table — `table` (v59+)
90
+
91
+ A niche v59+ type that references a warehouse table by id (`{type: "table", table-id: <id>}`, optional `source-filters`) where a table/subquery goes — analogous to a card reference but pointing at a raw table. Absent on v0.58. Reach for a card reference (`card`) unless you specifically need a bare-table source tag.
92
+
93
+ ### Temporal unit — `temporal-unit`
94
+
95
+ A widget that lets the viewer pick the time bucket (day/week/month/…) for a datetime column. Body mirrors a field filter (`dimension` legacy ref, optional `alias`) with `type: "temporal-unit"`.
96
+
97
+ ## `widget-type` by column type
98
+
99
+ Closed enum — same vocabulary as a dashboard parameter `type`. Pick one whose family matches the bound column.
100
+
101
+ | Column type | Common widget-types |
102
+ | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
103
+ | Text / string | `string/=` `string/!=` `string/contains` `string/does-not-contain` `string/starts-with` `string/ends-with` `category` |
104
+ | Number | `number/=` `number/!=` `number/between` `number/>=` `number/<=` |
105
+ | Date / datetime | `date/all-options` `date/single` `date/range` `date/relative` `date/month-year` `date/quarter-year` |
106
+ | Boolean | `boolean/=` |
107
+ | ID / FK | `id` |
108
+ | Location (with matching semantic type) | `location/city` `location/state` `location/zip_code` `location/country` |
109
+
110
+ `date/all-options` gives the fullest date picker (single, range, relative). `category` yields a value-list dropdown for a low-cardinality text column.
111
+
112
+ ## Parameter object — declared vs. runtime
113
+
114
+ Same object, two contexts. `target` links the parameter to a template tag: `["dimension", ["template-tag", "<name>"]]` for a field filter, `["variable", ["template-tag", "<name>"]]` for a raw variable.
115
+
116
+ **Declared** — in the card's `parameters` array, to set a default or a dropdown source:
117
+
118
+ ```json
119
+ {
120
+ "id": "<uuid>",
121
+ "name": "status",
122
+ "slug": "status",
123
+ "type": "string/=",
124
+ "target": ["dimension", ["template-tag", "status"]],
125
+ "default": "active",
126
+ "values_source_type": "static-list",
127
+ "values_source_config": { "values": ["active", "churned", "trial"] }
128
+ }
129
+ ```
130
+
131
+ `values_source_type`: omit to pull live distinct values from the bound field; `"static-list"` + `values_source_config.values` for a fixed list; `"card"` + `{card_id, value_field, label_field}` to source from a query.
132
+
133
+ **Runtime** — passed to `card query --parameters`; carries a `value`, no source config:
134
+
135
+ ```json
136
+ { "type": "string/=", "target": ["dimension", ["template-tag", "status"]], "value": "active" }
137
+ ```
138
+
139
+ The runtime `type` is the value's type, not the tag's. Date ranges pass as `"value": ["2024-01-01", "2024-12-31"]`. Omit a parameter entirely to leave an optional (`[[ ]]`) clause out.
140
+
141
+ ## Full native card body
142
+
143
+ What `mb card create --file` consumes:
144
+
145
+ ```json
146
+ {
147
+ "name": "Active orders by status",
148
+ "display": "table",
149
+ "visualization_settings": {},
150
+ "dataset_query": {
151
+ "lib/type": "mbql/query",
152
+ "database": 1,
153
+ "stages": [
154
+ {
155
+ "lib/type": "mbql.stage/native",
156
+ "native": "SELECT status, count(*) FROM orders WHERE total > {{min_total}} [[AND {{status}}]] GROUP BY status",
157
+ "template-tags": {
158
+ "min_total": {
159
+ "id": "<uuid>",
160
+ "name": "min_total",
161
+ "display-name": "Minimum total",
162
+ "type": "number",
163
+ "default": "0"
164
+ },
165
+ "status": {
166
+ "id": "<uuid>",
167
+ "name": "status",
168
+ "display-name": "Status",
169
+ "type": "dimension",
170
+ "dimension": ["field", {}, 141],
171
+ "widget-type": "string/="
172
+ }
173
+ }
174
+ }
175
+ ]
176
+ }
177
+ }
178
+ ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: transform
3
- description: Author and run Metabase transforms via `mb` — body shape (native SQL + MBQL 5), create + run-with-wait, run inspection, dependencies, cancel, the `update`-vs-recreate iteration rule, the writable-keys-only PATCH contract, plus transform tags and tag-driven transform-job schedules. Load when the user touches transforms — "create a transform", "run a transform", "fix a failing transform", "list transform runs", "cancel a running transform", "manage transform tags", "run a transform job", or anything `mb transform …` / `mb transform-job …` / `mb transform-tag …`.
3
+ description: Author and run Metabase transforms via `mb` — body shape (native SQL or structured MBQL), create + run-with-wait, run inspection, dependencies, cancel, the `update`-vs-recreate iteration rule, the writable-keys-only PATCH contract, plus transform tags and tag-driven transform-job schedules. Load when the user touches transforms — "create a transform", "run a transform", "fix a failing transform", "list transform runs", "cancel a running transform", "manage transform tags", "run a transform job", or anything `mb transform …` / `mb transform-job …` / `mb transform-tag …`.
4
4
  allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
5
  ---
6
6
 
@@ -8,7 +8,7 @@ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
8
8
 
9
9
  A **transform** persists the result of a query (native SQL or MBQL) to a warehouse table the user can read from cards, dashboards, and other transforms. It runs on a schedule (via `transform-job`) or on-demand (`transform run`).
10
10
 
11
- Flag conventions, body-input precedence, and output flags live in the `core` skill (`mb skills get core`). Deciding _which_ transforms to build — modeling a whole raw database into a set of clean, analysis-ready tables — is the `data-transformation` skill (`mb skills get data-transformation`).
11
+ Flag conventions, body-input precedence, and the `./.scratch` convention live in `core` (`mb skills get core`). Deciding _which_ transforms to build — modeling a whole raw database into clean, analysis-ready tables — is the `data-workflow` skill's build-clean-tables stage (`mb skills get data-workflow`).
12
12
 
13
13
  ## Body shape
14
14
 
@@ -17,14 +17,13 @@ A transform has two halves:
17
17
  - `source` — the query to run (`type: "query"`, with `query.type` of `native` or `mbql`).
18
18
  - `target` — the warehouse destination (`type: "table"`, with `database`, `schema`, `name`).
19
19
 
20
- Native SQL is the simplest source and the easiest to author by hand. MBQL is what the Metabase UI emits and is more verbose; pull a sample with `mb transform get <id> --full --json` if you need its shape.
21
-
22
- For an **MBQL 5** `source.query` (`lib/type: "mbql/query"`), the body shape, the "options object is always second" clause rule, UUID minting, aggregation/order-by refs, naming aggregation output columns, and the `--print-schema` → `--dry-run` validation loop are all in the `mbql` skill — **`mb skills get mbql`**. The MBQL-5 pre-flight on `transform create`/`update` is documented there too (legacy MBQL 4 and native sources skip it). For a transform target, naming your aggregation output columns matters more than usual — a bare `count` / `avg_2` becomes the warehouse column name; see the `mbql` skill's "Naming aggregation output columns".
20
+ Native SQL is the simplest source — author it as an `mbql.stage/native` stage (the SQL string sits at `source.query.stages[0].native`), the form below. For a **structured** `source.query` (an `mbql.stage/mbql` stage) — the options-object-is-always-second clause rule, UUID minting, aggregation/order-by refs, naming aggregation output columns, and the `--print-schema` → `--dry-run` validation loop — see `mbql` (**`mb skills get mbql`**). Both stage types are the `mbql/query` shape, so `transform create`/`update` pre-flight them (only the legacy flat forms skip it). Pull a sample body with `mb transform get <id> --full --json`. For a transform target, naming aggregation output columns matters more than usual: a bare `count` / `avg_2` becomes the warehouse column name.
23
21
 
24
22
  ## Create + run (native SQL)
25
23
 
24
+ **Keep the SQL formatted.** Author it multi-line in `./.scratch/<name>.sql` and embed with `jq --rawfile` (jq ≥1.6, which JSON-encodes the file so newlines become `\n`). The stored SQL (`source.query.stages[0].native`) is what `mb transform get` and the Metabase editor render — a single-line blob is valid JSON but unreadable when anyone opens the transform. Single-quote the heredoc delimiter (`<<'SQL'`) so the shell leaves `$vars` in the query alone (e.g. Postgres `$1`, `$$`).
25
+
26
26
  ```bash
27
- # Author the SQL formatted — it's what `mb transform get` and the Metabase editor show.
28
27
  cat > ./.scratch/user_counts_by_signup_year.sql <<'SQL'
29
28
  SELECT
30
29
  date_trunc('year', created_at)::date AS signup_year,
@@ -34,11 +33,10 @@ GROUP BY 1
34
33
  ORDER BY 1
35
34
  SQL
36
35
 
37
- # Embed it with jq --rawfile so the newlines survive as \n in valid JSON (don't hand-write the SQL as one line).
38
36
  jq -n --rawfile q ./.scratch/user_counts_by_signup_year.sql \
39
37
  '{ name: "user_counts_by_signup_year",
40
38
  description: "Sample transform: counts users by year of signup",
41
- source: { type: "query", query: { type: "native", database: <db-id>, native: { query: $q } } },
39
+ source: { type: "query", query: { "lib/type": "mbql/query", database: <db-id>, stages: [{ "lib/type": "mbql.stage/native", native: $q }] } },
42
40
  target: { type: "table", database: <db-id>, schema: "public", name: "user_counts_by_signup_year" } }' \
43
41
  > ./.scratch/transform.json
44
42
 
@@ -46,17 +44,13 @@ TRANSFORM_ID=$(mb transform create --file ./.scratch/transform.json --profile <n
46
44
  mb transform run "$TRANSFORM_ID" --wait --profile <name> --json
47
45
  ```
48
46
 
49
- Notes:
50
-
51
- - `<db-id>` comes from `mb database list --profile <name> --json`. Database ids are per-instance.
52
- - Target `schema` is the schema the result table is written into (e.g. `public`).
53
- - `--wait` on `transform run` polls until status is `succeeded` or `failed`. Without it you only get `{message: "Transform run started", run_id, final: null}` and have to poll yourself.
54
- - `--sync` implies `--wait`, then waits until the run's output table is registered — the run registers it itself, no `db sync-schema` needed — adding `target_table_id` to the envelope. Use it when you'll build MBQL on the output (see "Inspect").
55
- - The `--json` envelope is shape-stable: `{message, run_id, final}` (plus `target_table_id` under `--sync` — a number, or `null` if the table didn't register before the timeout). `final` is `null` when `--wait` is omitted or the run never started, otherwise a full `TransformRun` object with `status` and `message`. On a failed run (`final.status` ∈ {`failed`, `timeout`, `canceled`}) the CLI exits 1 and writes a one-line summary `transform run <id> failed` to stderr; the failure detail lives only in `final.message` on stdout, so `jq -r '.final.message'` is where to look.
56
- - **Keep the SQL formatted.** Author it multi-line in `./.scratch/<name>.sql` and embed with `jq --rawfile` (jq ≥1.6, which JSON-encodes the file so newlines become `\n`). The stored `native.query` is what `mb transform get` and the Metabase editor render — a single-line blob is valid JSON but unreadable when anyone opens the transform. Single-quote the heredoc delimiter (`<<'SQL'`) so the shell leaves `$vars` in the query alone (e.g. Postgres `$1`, `$$`).
57
- - `transform create --json` returns the agent-facing compact projection: `{id, name, description, source_type, target: {type, database, schema, name}, target_db_id}`. Read `target.schema`/`target.name` directly off the create output — no follow-up `transform get` needed to verify where the transform will write.
58
- - If a transform with the same `name` already has a YAML representation on disk under the configured remote-sync repo, `create` mints a `_2` suffix on the exported filename (the new transform gets a fresh `entity_id`; the prior one isn't touched). For "iterate on the same concept" workflows, prefer `transform update <id>` — see "Iterating on a failing transform" below.
59
- - **`collection_id` only accepts a collection in the `:transforms` namespace.** Transforms aren't filed next to cards and dashboards — passing a normal analytics collection id (the kind a dashboard lives in) fails create/update with `collection_id: A Transform can only go in Collections in the :transforms namespace.` Omit `collection_id` to leave the transform uncollected (the common case), or create one with `mb collection create --body '{"name":"…"}' --namespace transforms --json` and pass the returned `id`. Cards and dashboards you build **on top of** the transform's output table go in ordinary collections as usual — so "put the transform and its dashboard in collection X" generally means _X holds the dashboard + cards; the transform stays in the transforms namespace._
47
+ - `<db-id>` comes from `mb database list --profile <name> --json`; ids are per-instance. Target `schema` is the schema the result table is written into (e.g. `public`).
48
+ - `--wait` polls until status is `succeeded` or `failed`. Without it you get only `{message: "Transform run started", run_id, final: null}` and must poll yourself — don't put bare `transform run` in a tight loop; let `--wait` do the polling.
49
+ - `--sync` implies `--wait`, then waits until the run registers its output table (the run registers it itself — no `db sync-schema` needed), adding `target_table_id` to the envelope. Use it when you'll build MBQL on the output (see "Inspect").
50
+ - The `--json` envelope is shape-stable: `{message, run_id, final}` (plus `target_table_id` under `--sync` — a number, or `null` if the table didn't register before the timeout). `final` is `null` when `--wait` is omitted or the run never started, otherwise a full `TransformRun` with `status` and `message`. On a failed run (`final.status` ∈ {`failed`, `timeout`, `canceled`}) the CLI exits 1 and writes a one-line `transform run <id> failed` to stderr; the failure detail lives only in `final.message` on stdout, so `jq -r '.final.message'` is where to look.
51
+ - `transform create --json` returns the agent-facing compact projection: `{id, name, description, source_type, target: {type, database, schema, name}, target_db_id}`. Read `target.schema`/`target.name` directly off it — no follow-up `transform get`.
52
+ - If a transform with the same `name` already has a YAML representation on disk under the configured remote-sync repo, `create` mints a `_2` suffix on the exported filename (the new transform gets a fresh `entity_id`; the prior one isn't touched). For "iterate on the same concept", prefer `transform update <id>` — see "Iterating on a failing transform".
53
+ - **`collection_id` only accepts a collection in the `:transforms` namespace.** Transforms aren't filed next to cards and dashboards — a normal analytics collection id fails create/update with `collection_id: A Transform can only go in Collections in the :transforms namespace.` Omit `collection_id` to leave the transform uncollected (the common case), or provision one with `mb collection create --body '{"name":"…"}' --namespace transforms --json` (see `core`) and pass the returned `id`. Cards and dashboards you build **on top of** the output table go in ordinary collections — so "put the transform and its dashboard in collection X" means _X holds the dashboard + cards; the transform stays in the transforms namespace._
60
54
 
61
55
  ## Inspect
62
56
 
@@ -66,7 +60,7 @@ mb transform get <id> --profile <name> --full --json # full transform i
66
60
  mb transform dependencies <id> --profile <name> --json # upstream transforms this one must run after
67
61
  ```
68
62
 
69
- After a run the table physically exists in the warehouse, but Metabase addresses tables/columns by numeric id, so **MBQL and the UI can't reference a brand-new table until the instance syncs** (native SQL — a native `card` or `mb query` against `<schema>.<name>` — reads it immediately). Run and register in one step with `--sync`.
63
+ After a run the table physically exists in the warehouse, but Metabase addresses tables/columns by numeric id, so **MBQL and the UI can't reference a brand-new table until the instance syncs** (native SQL — a native `card` or `mb query` against `<schema>.<name>` — reads it immediately). Run and register in one step with `--sync`:
70
64
 
71
65
  ```bash
72
66
  TABLE_ID=$(mb transform run <id> --sync --profile <name> --json | jq -r '.target_table_id')
@@ -91,12 +85,10 @@ mb transform get-run <run-id> --profile <name> --json
91
85
  mb transform cancel <id> --profile <name> --json
92
86
  ```
93
87
 
94
- Notes:
95
-
96
88
  - `transform runs` and `transform get-run` parse against the same `TransformRun` schema, so `get-run` returns the same per-run shape as one entry of `runs`. The compact projection is `{id, transform_id, status, run_method, start_time, end_time, message}`. Pass `--full` on `get-run` for the hydrated row including `is_active`, `user_id`, `transform_name`, `transform_entity_id`, `checkpoint_*` fields, and a nested `transform: {id, name, …}` block.
97
- - `transform cancel` takes the **transform** id and 404s with `Endpoint not found — is this a Metabase instance?` if there is no active run. The response shape is `{canceled: true, id: <transform-id>}`.
98
- - For native-SQL transforms, cancel marks the run as `canceling` but does **not** kill the warehouse query mid-flight — the query runs to completion, then the run lands as `canceled` (or stays `succeeded` if the cancel arrived after the writer committed). For Python transforms the worker is interrupted directly. Don't expect cancel to free warehouse resources instantly on long native queries; expect it to flip state and prevent downstream consumers from treating the result as good.
99
- - The `--transform-id` filter on `runs` accepts a single integer; the CLI translates to the server's `transform-ids` query vector. To cross-filter multiple transforms, run `transform runs --json` and `jq` post-hoc.
89
+ - `transform cancel` takes the **transform** id and returns `{canceled: true, id: <transform-id>}`. It 404s with `Endpoint not found — is this a Metabase instance?` if there is no active run.
90
+ - **Cancel semantics differ by source.** For native SQL, cancel marks the run `canceling` but does **not** kill the warehouse query mid-flight — the query runs to completion, then the run lands as `canceled` (or stays `succeeded` if the cancel arrived after the writer committed). For Python transforms the worker is interrupted directly. Don't expect cancel to free warehouse resources instantly on long native queries; expect it to flip state and prevent downstream consumers from treating the result as good.
91
+ - The `--transform-id` filter on `runs` accepts a single integer (translated to the server's `transform-ids` vector). To cross-filter multiple transforms, run `transform runs --json` and `jq` post-hoc.
100
92
 
101
93
  ## Update body: send only writable keys, never round-trip the GET body
102
94
 
@@ -107,7 +99,7 @@ name, description, source, target, run_trigger,
107
99
  tag_ids, collection_id, owner_user_id, owner_email
108
100
  ```
109
101
 
110
- **Don't paste the output of `transform get` into a `transform update` body.** The GET response carries server-side fields (`id`, `entity_id`, `created_at`, `updated_at`, `creator_id`, `last_run`, `target_db_id`, `target_table_id`, `source_type`, `source_database_id`, `source_readable`, `creator`, `owner`, `table`, …) that the PUT endpoint isn't built to handle. Unknown top-level keys flow into `t2/update!` and produce a leaked H2 SQL error like:
102
+ **Never paste the output of `transform get` into a `transform update` body.** The GET response carries server-side fields (`id`, `entity_id`, `created_at`, `updated_at`, `creator_id`, `last_run`, `target_db_id`, `target_table_id`, `source_type`, `source_database_id`, `source_readable`, `creator`, `owner`, `table`, …) that the PUT endpoint isn't built to handle. Unknown top-level keys flow into `t2/update!` and leak a raw H2 SQL error like:
111
103
 
112
104
  ```
113
105
  Column "TAGS" not found; SQL statement:
@@ -116,11 +108,11 @@ UPDATE "TRANSFORM" SET "TAGS" = (), "UPDATED_AT" = NOW() WHERE "ID" = ? [42122-2
116
108
 
117
109
  Three specific footguns:
118
110
 
119
- - **`tags` is not a key on the REST API.** The serdes/YAML representation uses `tags`; the REST contract uses `tag_ids` (an array of integer ids). If you pulled a YAML representation and want to PUT it, translate `tags: [...]` → `tag_ids: [...]` first (or omit it entirely if you're not changing tag membership).
111
+ - **`tags` is not a REST key.** The serdes/YAML representation uses `tags`; the REST contract uses `tag_ids` (an array of integer ids). If you pulled a YAML representation and want to PUT it, translate `tags: [...]` → `tag_ids: [...]` first (or omit it if you're not changing tag membership).
120
112
  - **`source_type`, `target_db_id`, `target_table_id`, `entity_id`** are derived/computed by the server. They appear in GET responses for the agent's benefit; the server doesn't accept them on update.
121
- - **`collection_id` must be a `:transforms`-namespace collection** — a regular card/dashboard collection id is rejected with `A Transform can only go in Collections in the :transforms namespace.` Omit it unless you have one (see the create notes above). Round-tripping the existing value is safe; setting it to an ordinary collection is what fails.
113
+ - **`collection_id` must be a `:transforms`-namespace collection** — a regular card/dashboard collection id is rejected with `A Transform can only go in Collections in the :transforms namespace.` Round-tripping the existing value is safe; setting it to an ordinary collection is what fails.
122
114
 
123
- Right shape — patch only what changes:
115
+ Patch only what changes:
124
116
 
125
117
  ```bash
126
118
  # Rename only:
@@ -132,7 +124,7 @@ SELECT …
132
124
  FROM public.orders
133
125
  SQL
134
126
  jq -n --rawfile q ./.scratch/orders.sql \
135
- '{ source: { type: "query", query: { type: "native", database: <db-id>, native: { query: $q } } } }' \
127
+ '{ source: { type: "query", query: { "lib/type": "mbql/query", database: <db-id>, stages: [{ "lib/type": "mbql.stage/native", native: $q }] } } }' \
136
128
  > ./.scratch/patch.json
137
129
  mb transform update <id> --file ./.scratch/patch.json --profile <name> --json
138
130
 
@@ -140,7 +132,7 @@ mb transform update <id> --file ./.scratch/patch.json --profile <name> --json
140
132
  mb transform update <id> --body '{"tag_ids":[1,3]}' --profile <name> --json
141
133
  ```
142
134
 
143
- `tag_ids` are the integer ids of **transform tags** — discover and manage them with the `transform-tag` group: `mb transform-tag list --json` (find ids; the four built-ins `hourly`/`daily`/`weekly`/`monthly` are seeded), `mb transform-tag create --body '{"name":"nightly"}' --json`, `mb transform-tag update <id>`, `mb transform-tag delete <id>`. Tags are also how a `transform-job` selects what to run — a job executes every transform carrying one of the job's tags (see "Transform jobs" below).
135
+ `tag_ids` are the integer ids of **transform tags** — manage them with the `transform-tag` group: `mb transform-tag list --json` (find ids; the four built-ins `hourly`/`daily`/`weekly`/`monthly` are seeded), `mb transform-tag create --body '{"name":"nightly"}' --json`, `mb transform-tag update <id>`, `mb transform-tag delete <id>`. Tags are also how a `transform-job` selects what to run — a job executes every transform carrying one of the job's tags (see "Transform jobs").
144
136
 
145
137
  If you really must round-trip, project to the writable subset:
146
138
 
@@ -153,13 +145,11 @@ mb transform get <id> --full --profile <name> --json \
153
145
 
154
146
  ## Iterating on a failing transform
155
147
 
156
- When `transform run` fails and you want to retry with a fixed body, **prefer `transform update <id> --file body.json` over `transform delete <id>` + `transform create`.** Update keeps the same row, the same `entity_id`, the same materialized table, and the same on-disk YAML filename:
148
+ When `transform run` fails and you want to retry with a fixed body, **prefer `transform update <id> --file body.json` over `transform delete <id>` + `transform create`.** Update keeps the same row, `entity_id`, materialized table, and on-disk YAML filename:
157
149
 
158
150
  - `git-sync export` produces **one** clean commit containing only the fix, instead of "broken transform" + "remove broken transform" landing as two commits in `git log`.
159
- - You don't have to chase `_2` suffixes minted when two YAMLs share a `name` on disk (see the `transform create` notes above).
160
- - The materialized output table either updates in place or, if the SELECT shape changed incompatibly, errors loudly on the next run rather than landing in a parallel `..._2` table the agent has to clean up. (`transform delete-table <id>` resets the column shape if you need a clean slate.)
161
-
162
- Recipe:
151
+ - You don't chase `_2` suffixes minted when two YAMLs share a `name` on disk.
152
+ - The materialized output table either updates in place or, if the SELECT shape changed incompatibly, errors loudly on the next run rather than landing in a parallel `..._2` table you have to clean up. (`transform delete-table <id>` resets the column shape for a clean slate.)
163
153
 
164
154
  ```bash
165
155
  # 1. Try once
@@ -172,7 +162,7 @@ cat > ./.scratch/source.sql <<'SQL'
172
162
  <fixed SQL, formatted>
173
163
  SQL
174
164
  jq -n --rawfile q ./.scratch/source.sql \
175
- '{ source: { type: "query", query: { type: "native", database: <db-id>, native: { query: $q } } } }' \
165
+ '{ source: { type: "query", query: { "lib/type": "mbql/query", database: <db-id>, stages: [{ "lib/type": "mbql.stage/native", native: $q }] } } }' \
176
166
  > ./.scratch/source-patch.json
177
167
  mb transform update "$ID" --file ./.scratch/source-patch.json --profile <n> --json
178
168
 
@@ -180,7 +170,7 @@ mb transform update "$ID" --file ./.scratch/source-patch.json --profile <n> --js
180
170
  mb transform run "$ID" --wait --profile <n> --json # → succeeded
181
171
  ```
182
172
 
183
- If you really must `create + delete` instead, do the `delete` **before** the first `git-sync export` so the failed entity never lands in git history. Order matters: agents reflex to "export to checkpoint progress," but for transforms an export of a soft-failed state is mostly noise that needs a follow-up cleanup commit. See the `git-sync` skill, "Read state before mutating" for the ordering rule.
173
+ If you really must `create + delete` instead, do the `delete` **before** the first `git-sync export` so the failed entity never lands in git history — an export of a soft-failed state is noise that needs a follow-up cleanup commit. See `git-sync`, "Read state before mutating", for the ordering rule.
184
174
 
185
175
  ## Drop the materialized table (keep the transform)
186
176
 
@@ -188,7 +178,7 @@ If you really must `create + delete` instead, do the `delete` **before** the fir
188
178
  mb transform delete-table <id> --yes --profile <name>
189
179
  ```
190
180
 
191
- Useful when you've changed the SELECT and want a fresh `CREATE TABLE` on the next run. **`--yes` is required** in non-interactive contexts; without it the command exits with `--yes required to delete non-interactively`.
181
+ Useful when you've changed the SELECT and want a fresh `CREATE TABLE` on the next run. **`--yes` is required** non-interactively; without it the command exits with `refusing to delete <id> without confirmation — pass --yes to proceed non-interactively`.
192
182
 
193
183
  ## Delete the transform
194
184
 
@@ -196,7 +186,7 @@ Useful when you've changed the SELECT and want a fresh `CREATE TABLE` on the nex
196
186
  mb transform delete <id> --yes --profile <name>
197
187
  ```
198
188
 
199
- Removes the definition. Whether the materialized table is dropped depends on the server — check with `mb table list --db-id <db-id> --profile <name> --json` if it matters. Same `--yes` rule as `delete-table`.
189
+ Removes the definition. Whether the materialized table is dropped depends on the server — check with `mb table list --db-id <db-id> --profile <name> --json` if it matters. Same `--yes` rule and message as `delete-table`.
200
190
 
201
191
  ## Transform jobs (schedules)
202
192
 
@@ -212,9 +202,3 @@ mb transform-job set-active false --profile <name> --json # disable every job a
212
202
  ```
213
203
 
214
204
  `transform-job run` is fire-and-forget — the server returns `{message, job_run_id}` immediately, with no per-job-run polling (no `--wait`). Most ad-hoc agent work is one-off `transform run`, not job authoring.
215
-
216
- ## Don't (transform-specific)
217
-
218
- - Don't put `transform run` calls in tight polling loops — pass `--wait` and let the CLI handle the polling. Manual loops without `--wait` will hammer the server.
219
- - Don't author MBQL 4 (the legacy nested `{ type: "query", query: {...} }` shape) by hand — pull a sample with `mb transform get <id> --full --json`. MBQL 5 (`lib/type: "mbql/query"`) **is** authorable by hand thanks to the `mb query --print-schema` + `--dry-run` feedback loop; for non-trivial pipelines you may still prefer building in the UI and exporting.
220
- - Don't paste a `transform get` body into `transform update` — the PUT endpoint only accepts writable keys, and unknown keys (notably `tags`, `source_type`, `entity_id`, `created_at`, `last_run`) leak as raw SQL errors. See "Update body: send only writable keys" above. Use `tag_ids` (not `tags`) on the REST contract.
@@ -1,21 +1,21 @@
1
1
  ---
2
2
  name: visualization
3
- description: Pick the right `display` (chart type) for a card's data and author its `visualization_settings` via the `mb` CLI. Covers which chart fits which data shape, the required and optional settings per chart type, the rule that settings reference OUTPUT columns by name, minimum-viable settings per chart family, and the `column_settings` JSON-string-key footgun. The full per-chart key catalog lives in references. Load whenever choosing or shaping how a card renders — "what chart should I use for this", "create a bar chart", "make this a line chart", "turn this into a pie", "map this by state", "format this column as currency", "set the pie dimension and metric", "the card renders as a table instead of a chart", "add conditional formatting", or any `display` / `visualization_settings` work.
3
+ description: Choose a card's `display` (chart type) and author its `visualization_settings` for the `mb` CLI — which chart fits which data shape, the required keys per chart, the rule that settings name OUTPUT columns, and the `column_settings` JSON-string-key footgun; the full per-chart key catalog is in references. Use when deciding or fixing how a card renders — "what chart should I use", "make this a bar/line/pie chart", "map this by state", "format this column as currency", "add conditional formatting", "the card renders as a table instead of a chart", or any `display` / `visualization_settings` work.
4
4
  allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
5
  ---
6
6
 
7
7
  # Visualization: pick the chart, then set it
8
8
 
9
- > **Shared contract (read first).** This skill is part of the `robot-data-engineer` family and follows its shared rules: audience is a non-technical user, so no database jargon (skip "normalize"/"grain"; ERD/foreign key are fine; explain "wide"/"long" the first time you use them). Ask before showing PII row-by-row (names, emails, phones) — default to aggregates. When asked for something the CLI can't do (alerts, dashboard filters), name the limit instead of erroring into raw SQL. Honor the autonomy mode the user picked. Full text and the autonomy slider live in the router — run `mb skills get robot-data-engineer` and read its **Shared Contract** if you haven't.
9
+ > **Building charts as part of a guided data project?** Follow the `data-workflow` **Shared Contract** — answer-first with detail on demand, ask before showing PII, honor the autonomy mode, name what the CLI can't do instead of erroring into raw SQL: `mb skills get data-workflow`.
10
10
 
11
11
  A card has two presentation fields alongside its `dataset_query`:
12
12
 
13
- - **`display`** — the chart type (`bar`, `line`, `pie`, `scalar`, `map`, `table`, …). One closed set; pick from the enum below.
13
+ - **`display`** — the chart type (`bar`, `line`, `pie`, `scalar`, `map`, `table`, …); pick from the valid values below.
14
14
  - **`visualization_settings`** — a map whose keys are **namespaced by `display`** (`graph.*` for bar/line/area/combo, `pie.*` for pie, `table.*` for table, …). The server stores almost anything and **silently ignores keys that don't apply** to the chosen `display`.
15
15
 
16
16
  Nothing validates `visualization_settings` — there is no pre-flight to fail past. A `display` typo or a misnamed key is accepted by the API; the card just renders as a default table or drops the setting. So **the feedback loop is read-back, not pre-flight**: after `card create`/`update`, confirm with `mb card get <id> --full --json` (or open the card) that it rendered as intended.
17
17
 
18
- Flag conventions and body-input precedence live in the `core` skill (`mb skills get core`); the `dataset_query` itself is the `mbql` skill's job (`mb skills get mbql`). This skill is only about how the result is displayed.
18
+ Flag conventions and body-input precedence live in `core` (`mb skills get core`); the `dataset_query` itself is the `mbql` skill's job (`mb skills get mbql`). This skill is only about how the result is displayed.
19
19
 
20
20
  Two steps: **(1) pick the `display` that fits the data**, then **(2) bind the data columns and set options**.
21
21
 
@@ -27,7 +27,7 @@ Decide which relationship in the data matters most, then pick the chart. The sha
27
27
  - **Compare a measure across categories** → `bar` (vertical). Use `row` (horizontal bar) when labels are long or there are many categories. Sort by value unless the dimension has a natural order.
28
28
  - **Change over time / trend** → `line` for a continuous series; `bar`/`area` for a few discrete periods. Two measures on unlike scales → `combo` (line + bar, dual-axis) — only when the metrics are genuinely related.
29
29
  - **Part-to-whole, one snapshot** → `pie`, but only for a meaningful whole with **≤5 slices**; beyond that use a sorted `bar`/`row`. Composition over time → stacked `area`/`bar`.
30
- - **Distribution / spread / outliers** → `boxplot` (especially comparing several groups).
30
+ - **Distribution / spread / outliers** → a `bar` histogram (bin the measure — see `mbql` binning); on **v59+** servers `boxplot` compares several groups' spread directly.
31
31
  - **Correlation between two measures** → `scatter` (a third measure → bubble size).
32
32
  - **Sequential additive contributions** (start → +/− steps → total) → `waterfall`.
33
33
  - **Stage drop-off in an ordered, cumulative funnel** → `funnel`.
@@ -35,7 +35,7 @@ Decide which relationship in the data matters most, then pick the chart. The sha
35
35
  - **Geographic** → `map`: region/choropleth (a region dimension + a measure), pin (lat + long), or grid/heat (coordinates + measure).
36
36
  - **Precise values, many columns, mixed types, or no chart fits** → `table`; `pivot` for a cross-tab of two dimensions; `object` for a single record's detail.
37
37
 
38
- Closed `display` enum (card-level, non-hidden): `table`, `bar`, `line`, `area`, `row`, `pie`, `scalar`, `smartscalar`, `combo`, `pivot`, `funnel`, `map`, `scatter`, `waterfall`, `progress`, `gauge`, `object`, `sankey`, `boxplot`. (`scalar` **is** the "Number" viz — `display: number` is a legacy serialization alias, not a registered visualization; use `scalar`. `list` exists but is hidden — don't pick it. `heading`/`text`/`link`/`iframe`/`action` are dashcard virtuals, not standalone cards — see references.) An unknown `display` is accepted by the API but renders nothing — typos like `bargraph`/`linechart` are the most common "why is my chart blank" cause.
38
+ Valid `display` values — the registered visualizations: `table`, `bar`, `line`, `area`, `row`, `pie`, `scalar`, `smartscalar`, `combo`, `pivot`, `funnel`, `map`, `scatter`, `waterfall`, `progress`, `gauge`, `object`, `sankey`. The API types `display` as a plain string and accepts any value — it renders an unknown one as nothing. (`boxplot` is registered only on **v59+** servers — older ones render it blank; use a `bar` histogram for distributions instead. `scalar` **is** the "Number" viz — `display: number` is a legacy serialization alias, not a registered visualization; use `scalar`. `list` exists but is hidden — don't pick it. `heading`/`text`/`link`/`iframe`/`action` are dashcard virtuals, not standalone cards — see references.) A typo like `bargraph`/`linechart` is accepted and renders blank — the most common "why is my chart blank" cause.
39
39
 
40
40
  ## Step 2 — bind data columns and set options
41
41
 
@@ -53,7 +53,7 @@ Closed `display` enum (card-level, non-hidden): `table`, `bar`, `line`, `area`,
53
53
  | `row` | as bar; prefer for long/many category labels | `graph.dimensions`, `graph.metrics` |
54
54
  | `scatter` | two numeric measures (correlation) | `graph.dimensions`, `graph.metrics` (`scatter.bubble` opt) |
55
55
  | `waterfall` | exactly 1 dimension + ≥1 measure; sequential | `graph.dimensions` (1), `graph.metrics` (1) |
56
- | `boxplot` | ≥3 cols, ≥2 dimensions, ≥1 measure | `graph.dimensions`, `graph.metrics` |
56
+ | `boxplot` _(v59+)_ | ≥3 cols, ≥2 dimensions, ≥1 measure | `graph.dimensions`, `graph.metrics` |
57
57
  | `pie` | ≥2 rows, ≥2 cols, ≥1 dimension + ≥1 measure; ≤~5 slices | `pie.dimension`, `pie.metric` |
58
58
  | `funnel` | 2 columns (stage + value); ordered stages | `funnel.dimension`, `funnel.metric` |
59
59
  | `map` (region) | a string/region dimension + a measure | `map.region`, `map.dimension`, `map.metric` |
@@ -151,7 +151,7 @@ mb skills path visualization # → the skill dir; then Read references
151
151
 
152
152
  ## Don't
153
153
 
154
- - Don't invent `display` values (`bargraph`, `linechart`, `histogram`) or use `number`/`list` — use the closed non-hidden enum; the API accepts a typo and renders nothing.
154
+ - Don't invent `display` values (`bargraph`, `linechart`, `histogram`) or use `number`/`list` — use a registered value; the API accepts a typo and renders nothing.
155
155
  - Don't put numeric field ids in `graph.dimensions`/`pie.metric`/`scalar.field`/`map.latitude_column` etc. — they take **output column-name strings**.
156
156
  - Don't reach for a `pie` with >5 slices, a `combo` of unrelated metrics, or a `pie`/`scalar` to show a trend — see Step 1.
157
157
  - Don't write a `column_settings` key as an object — it's a JSON **string** (`"[\"name\",\"COL\"]"`), inner quotes escaped.
@@ -1,5 +1,17 @@
1
1
  # visualization_settings — per-chart key reference
2
2
 
3
+ ## Contents
4
+
5
+ - [Cartesian — `bar`, `line`, `area`, `combo`, `scatter`, `waterfall`, `row`, `boxplot`](#cartesian--bar-line-area-combo-scatter-waterfall-row-boxplot) — shared keys (binding, stacking, goal/trend, data labels, axes, tooltip) plus `scatter`, `waterfall`, `row`, `boxplot` extras
6
+ - [Part-to-whole & single value — `pie`, `funnel`, `gauge`, `progress`, `scalar`, `smartscalar`](#part-to-whole--single-value--pie-funnel-gauge-progress-scalar-smartscalar)
7
+ - [Tabular, geographic & flow — `table`, `pivot`, `object`, `map`, `sankey`](#tabular-geographic--flow--table-pivot-object-map-sankey) — includes `table.column_formatting` conditional formatting
8
+ - [`column_settings` — per-column formatting](#column_settings--per-column-formatting) — number/date/currency, `view_as`, alignment, mini bars
9
+ - [`series_settings` — per-series styling (cartesian)](#series_settings--per-series-styling-cartesian)
10
+ - [Virtual cards (dashcards only, `card_id: null`)](#virtual-cards-dashcards-only-card_id-null) — heading/text/link/iframe
11
+ - [Click behavior (dashcards only)](#click-behavior-dashcards-only)
12
+
13
+ ---
14
+
3
15
  Authorable keys per `display`, plus the data shape each chart suits and the minimum needed to render. Set keys only to override defaults — an empty `{}` works for a simple aggregate.
4
16
 
5
17
  All column-naming keys (`graph.dimensions`, `pie.dimension`, `table.columns[].name`, `map.latitude_column`, …) take **output column-name strings** — the names the query produces. Every key and value below is identical in the API form (`mb card create`) and the portable git-sync form, with two exceptions: `column_settings` `["ref", …]` keys and click-behavior dimension targets carry a numeric field id in the API form and a name-path in the portable form. In a JSON body, `column_settings` keys are escaped strings: `"[\"name\",\"TOTAL\"]"`.
@@ -92,6 +104,8 @@ Horizontal bars — use when category labels are long or numerous. Here `graph.d
92
104
 
93
105
  ## boxplot
94
106
 
107
+ _Registered only on **v59+** servers — older ones render `display: boxplot` blank; use a `bar` histogram there._
108
+
95
109
  Use for distribution/spread/outliers, especially across several groups. Needs **unaggregated** rows. **Use for:** ≥3 columns, ≥2 dimensions, ≥1 measure. **Required:** `graph.dimensions`, `graph.metrics`. X-scale is `"ordinal"`.
96
110
 
97
111
  | Key | Type | Values | Default |
@@ -20,8 +20,8 @@ mb skills get core # auth, flag conventions, every command group
20
20
  mb skills list # everything available on the installed version
21
21
  ```
22
22
 
23
- **Doing a whole job, not one command?** If the user wants an outcome — "make sense of my data", "build a data model", "go from raw data to a dashboard", "answer questions about my data", "be my data analyst", "set up analytics for X" — load the front-door router instead and let it drive:
23
+ **Doing a whole job, not one command?** If the user wants an outcome — "make sense of my data", "build a data model", "go from raw data to a dashboard", "answer questions about my data", "be my data analyst", "set up analytics for X" — load the guided end-to-end skill instead and let it drive:
24
24
 
25
25
  ```bash
26
- mb skills get robot-data-engineer
26
+ mb skills get data-workflow
27
27
  ```
@@ -1,10 +0,0 @@
1
- import "./command-augment-CAur0XOQ.mjs";
2
- import "./error-CIObEXLY.mjs";
3
- import "./runtime-CmAIahm5.mjs";
4
- import "./capabilities-BX1rnVuH.mjs";
5
- import "./parse-id-B5adfBlS.mjs";
6
- import "./poll-AduuU55-.mjs";
7
- import "./poll-task-B00Qwd87.mjs";
8
- import { SyncSettingsUpdateResult, add_collection_default, setCollectionRemoteSynced, syncSettingsUpdateView } from "./add-collection-H4LcP-9B.mjs";
9
-
10
- export { add_collection_default as default };
@@ -1,19 +0,0 @@
1
- import { defineCommand } from "citty";
2
-
3
- //#region src/commands/auth/index.ts
4
- var auth_default = defineCommand({
5
- meta: {
6
- name: "auth",
7
- description: "Authenticate against a Metabase instance"
8
- },
9
- default: "login",
10
- subCommands: {
11
- login: () => import("./login-C0Rf2hg0.mjs").then((m) => m.default),
12
- status: () => import("./status-DY92F9mn.mjs").then((m) => m.default),
13
- list: () => import("./list-FR8Q1SzV.mjs").then((m) => m.default),
14
- logout: () => import("./logout-C7_UON-s.mjs").then((m) => m.default)
15
- }
16
- });
17
-
18
- //#endregion
19
- export { auth_default as default };
@@ -1,20 +0,0 @@
1
- import { defineCommand } from "citty";
2
-
3
- //#region src/commands/card/index.ts
4
- var card_default = defineCommand({
5
- meta: {
6
- name: "card",
7
- description: "Manage Metabase cards (questions, models, metrics)"
8
- },
9
- subCommands: {
10
- list: () => import("./list-D52_BozQ.mjs").then((mod) => mod.default),
11
- get: () => import("./get-CXMv-r1p.mjs").then((mod) => mod.default),
12
- query: () => import("./query-BUkuB4bZ.mjs").then((mod) => mod.default),
13
- create: () => import("./create-B-mvVFIl.mjs").then((mod) => mod.default),
14
- update: () => import("./update-I3TA2Tem.mjs").then((mod) => mod.default),
15
- archive: () => import("./archive-BCXoM1nX.mjs").then((mod) => mod.default)
16
- }
17
- });
18
-
19
- //#endregion
20
- export { card_default as default };
@@ -1,20 +0,0 @@
1
- import { defineCommand } from "citty";
2
-
3
- //#region src/commands/collection/index.ts
4
- var collection_default = defineCommand({
5
- meta: {
6
- name: "collection",
7
- description: "Manage Metabase collections"
8
- },
9
- subCommands: {
10
- list: () => import("./list-F0vkE22V.mjs").then((mod) => mod.default),
11
- get: () => import("./get-rFcAVIch.mjs").then((mod) => mod.default),
12
- items: () => import("./items-1KkBMiO4.mjs").then((mod) => mod.default),
13
- tree: () => import("./tree-B3f5F_dP.mjs").then((mod) => mod.default),
14
- create: () => import("./create-CF2Zn4pT.mjs").then((mod) => mod.default),
15
- archive: () => import("./archive-hN8PfvhX.mjs").then((mod) => mod.default)
16
- }
17
- });
18
-
19
- //#endregion
20
- export { collection_default as default };
@@ -1,21 +0,0 @@
1
- import { defineCommand } from "citty";
2
-
3
- //#region src/commands/dashboard/index.ts
4
- var dashboard_default = defineCommand({
5
- meta: {
6
- name: "dashboard",
7
- description: "Manage Metabase dashboards"
8
- },
9
- subCommands: {
10
- list: () => import("./list-C4KnM3Rq.mjs").then((mod) => mod.default),
11
- get: () => import("./get-Nc5GOs6-.mjs").then((mod) => mod.default),
12
- cards: () => import("./cards-Bw37jizL.mjs").then((mod) => mod.default),
13
- create: () => import("./create-DSWjS09p.mjs").then((mod) => mod.default),
14
- update: () => import("./update-DOfL_KPx.mjs").then((mod) => mod.default),
15
- "update-dashcard": () => import("./update-dashcard-BXZ4vS15.mjs").then((mod) => mod.default),
16
- archive: () => import("./archive-C6MDtV1F.mjs").then((mod) => mod.default)
17
- }
18
- });
19
-
20
- //#endregion
21
- export { dashboard_default as default };