@metabase/cli 0.1.17 → 0.2.0

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 (224) hide show
  1. package/README.md +61 -35
  2. package/dist/{add-collection-C-t9SQBk.mjs → add-collection-BTSGp4f9.mjs} +4 -5
  3. package/dist/add-collection-DlAwt2Z3.mjs +9 -0
  4. package/dist/append-DY1-O789.mjs +42 -0
  5. package/dist/{archive-wNXwIiF0.mjs → archive-BOEEMrYL.mjs} +6 -8
  6. package/dist/{archive-CaoUUTIb.mjs → archive-CJ9rElKn.mjs} +6 -8
  7. package/dist/{archive-DS-KEB4a.mjs → archive-CWtUzlHO.mjs} +5 -7
  8. package/dist/{archive-LG8u7ec5.mjs → archive-DCPw3df6.mjs} +5 -7
  9. package/dist/{archive-D1FX-sbU.mjs → archive-DOa1u1aJ.mjs} +5 -7
  10. package/dist/{archive-C7dnyzVY.mjs → archive-pHsa4dL0.mjs} +8 -9
  11. package/dist/{archive-qSOcACQo.mjs → archive-w6TX-zEk.mjs} +7 -8
  12. package/dist/auth-BrQtY0pO.mjs +22 -0
  13. package/dist/{body-IcJ5kFtk.mjs → body-C1_AaTS4.mjs} +3 -3
  14. package/dist/{branches-BlNCTmFB.mjs → branches-BsBzaGIf.mjs} +5 -7
  15. package/dist/{cancel-BnveTPNw.mjs → cancel-BNM0yyx-.mjs} +4 -6
  16. package/dist/{cancel-task-CSVI8Zgl.mjs → cancel-task-Bggzn4JL.mjs} +5 -7
  17. package/dist/capabilities-DrDJo_ng.mjs +684 -0
  18. package/dist/{card-DEmcRlNO.mjs → card-B8G5jQQT.mjs} +24 -18
  19. package/dist/card-DHCcmpgr.mjs +26 -0
  20. package/dist/{cards-CMgGA8OZ.mjs → cards-D0hvA13L.mjs} +7 -8
  21. package/dist/cli.mjs +175 -29
  22. package/dist/{collection-DrLpA1SO.mjs → collection-CFoLPrY5.mjs} +1 -1
  23. package/dist/collection-D0O9gp3S.mjs +23 -0
  24. package/dist/{collection-namespace-CsaxEqOb.mjs → collection-namespace-DqPNrCdQ.mjs} +2 -2
  25. package/dist/{create-I5Cv-MBa.mjs → create-05rX4oL-.mjs} +11 -12
  26. package/dist/{create-B6LQutd0.mjs → create-BR7x66KW.mjs} +12 -13
  27. package/dist/{create-B79M8YpX.mjs → create-C17Fh1_X.mjs} +9 -10
  28. package/dist/{create-FZOrCw5k.mjs → create-C8ndg3g4.mjs} +28 -21
  29. package/dist/{create-C7u3umLj.mjs → create-CZ9KFnbi.mjs} +8 -9
  30. package/dist/{create-B1xfaZJB.mjs → create-Cii_utzV.mjs} +5 -7
  31. package/dist/{create-SHKlj0z3.mjs → create-Cyv7vHV_.mjs} +8 -9
  32. package/dist/{create-Bg_uMu0p.mjs → create-DKuixGJ2.mjs} +8 -9
  33. package/dist/{create-AeWNv0v-.mjs → create-E6L3WMqV.mjs} +8 -9
  34. package/dist/{create-LHqwcVbS.mjs → create-a3s1S2Dw.mjs} +11 -12
  35. package/dist/{create-branch-CGyT99Ny.mjs → create-branch-Dxn6UnV_.mjs} +5 -7
  36. package/dist/{create-BpkWlgoR.mjs → create-oLb0E-ZP.mjs} +17 -14
  37. package/dist/csv-T6_vlLJS.mjs +58 -0
  38. package/dist/{current-task-C-0hw2Ae.mjs → current-task-DxWbw-bU.mjs} +5 -7
  39. package/dist/{dashboard-DOplbKyQ.mjs → dashboard-B-MTpfeS.mjs} +35 -15
  40. package/dist/dashboard-BpSPoeAn.mjs +35 -0
  41. package/dist/{database-D9fftP-i.mjs → database-CIbVSfua.mjs} +2 -2
  42. package/dist/db-DBRijnh4.mjs +27 -0
  43. package/dist/{delete-tCVrYjKD.mjs → delete--P1xFX37.mjs} +6 -8
  44. package/dist/{delete-Do3nn9sl.mjs → delete-BBOIUl0D.mjs} +6 -8
  45. package/dist/{delete--NYYN6wv.mjs → delete-Ce2EBQYA.mjs} +6 -8
  46. package/dist/{delete-runtime-B0ha5QR4.mjs → delete-runtime-B-8I9V1g.mjs} +2 -3
  47. package/dist/{delete-table-CWSGPw0i.mjs → delete-table-Bj32NsXn.mjs} +6 -8
  48. package/dist/{dependencies-CyFocD8I.mjs → dependencies-ixzjVjUz.mjs} +5 -7
  49. package/dist/{dirty-BXM0YJ_a.mjs → dirty-CH70h5m5.mjs} +5 -7
  50. package/dist/{document-1W7NRaO_.mjs → document-CL4ML5FN.mjs} +20 -4
  51. package/dist/document-dZpuFu3I.mjs +22 -0
  52. package/dist/{eid-p-zn_1RJ.mjs → eid-XjkBbyb_.mjs} +10 -11
  53. package/dist/embedding-CKdw8q7p.mjs +11 -0
  54. package/dist/{export-B_8wghyo.mjs → export-CChMg-mL.mjs} +7 -9
  55. package/dist/field-B1heFeCz.mjs +21 -0
  56. package/dist/{field-CMY_LWUe.mjs → field-DsXTeqBy.mjs} +6 -4
  57. package/dist/{fields-BsufL9kv.mjs → fields-C7h2QwUp.mjs} +6 -8
  58. package/dist/{get-Cl5Ak73C.mjs → get-AvGH_oph.mjs} +8 -9
  59. package/dist/{get-DurjkUKJ.mjs → get-B6ZJCJ8H.mjs} +5 -7
  60. package/dist/{get-68P-L4ja.mjs → get-BWHJDa0B.mjs} +5 -7
  61. package/dist/{get-B67pe00Z.mjs → get-BsyccuCH.mjs} +5 -7
  62. package/dist/{get-C7mq17-2.mjs → get-C98IFgFi.mjs} +5 -7
  63. package/dist/{get-BTKeO4vE.mjs → get-Cj90oyxG.mjs} +5 -7
  64. package/dist/{get-BJoZkATD.mjs → get-D9r2znrn.mjs} +5 -7
  65. package/dist/{get-D3k2JUsa.mjs → get-DRZENkCj.mjs} +5 -7
  66. package/dist/{get-4lufgahL.mjs → get-DWWmpQ87.mjs} +5 -7
  67. package/dist/{get-CPw0ROO4.mjs → get-Dg1J0wxb.mjs} +5 -7
  68. package/dist/{get-B45uYMAs.mjs → get-DrWYvefc.mjs} +7 -8
  69. package/dist/{get-DTqBuIf3.mjs → get-KVN9ANVh.mjs} +4 -6
  70. package/dist/get-RlP_xiUd.mjs +67 -0
  71. package/dist/{get-Df_3LCVC.mjs → get-cyR67Lmq.mjs} +5 -7
  72. package/dist/{get-DeLnNVQE.mjs → get-kqY2lwhO.mjs} +16 -9
  73. package/dist/{get-run-Dq4qfGfD.mjs → get-run-C4C_QQqO.mjs} +5 -7
  74. package/dist/git-sync-JHa7bIyY.mjs +31 -0
  75. package/dist/{group-BNE_RiH5.mjs → group-CILtQqCL.mjs} +2 -1
  76. package/dist/{has-remote-changes-E-4N_O_t.mjs → has-remote-changes-dLUGr0MD.mjs} +5 -7
  77. package/dist/{import-Dg0j3HCP.mjs → import-Byull6jF.mjs} +7 -9
  78. package/dist/{input-7Sj85_K7.mjs → input-m9ctfLxZ.mjs} +6 -3
  79. package/dist/is-dirty-Bb2dfEYs.mjs +8 -0
  80. package/dist/{is-dirty-DWw1yBeR.mjs → is-dirty-CU3ovgdG.mjs} +3 -4
  81. package/dist/{items-CQtt9X4M.mjs → items-CEGDYWuF.mjs} +7 -9
  82. package/dist/{key-bltP32Pm.mjs → key-Ge4VwWvS.mjs} +1 -1
  83. package/dist/{library-B5AvpACG.mjs → library-DHWWa4Tt.mjs} +6 -6
  84. package/dist/{list-BFVuPodI.mjs → list-3z1ZChTG.mjs} +4 -6
  85. package/dist/{list-D3TSAqwl.mjs → list-B9TyELob.mjs} +4 -6
  86. package/dist/{list-BAOTQHst.mjs → list-BFm4iEBt.mjs} +4 -6
  87. package/dist/{list-BKUvhAs4.mjs → list-BliCJAjt.mjs} +4 -6
  88. package/dist/{list-BgUWqZa2.mjs → list-Bpv06V24.mjs} +6 -8
  89. package/dist/{list-DG0FIhTK.mjs → list-Br48kL09.mjs} +4 -6
  90. package/dist/{list-BHAWYZ1z.mjs → list-BtcpNl4-.mjs} +4 -6
  91. package/dist/{list-B1uVWy6A.mjs → list-BuyRG3GB.mjs} +7 -8
  92. package/dist/{list-CvhVYOVl.mjs → list-CMb03cv3.mjs} +5 -7
  93. package/dist/{list-CfqpDTna.mjs → list-CmCJwaGT.mjs} +4 -6
  94. package/dist/{list-C4bALfCs.mjs → list-Cur_72xZ.mjs} +5 -7
  95. package/dist/{list-BF-W4jOZ.mjs → list-D9FY30xH.mjs} +8 -9
  96. package/dist/{list-D5Gdz8hi.mjs → list-DEkPxVZu.mjs} +4 -6
  97. package/dist/{list-C9O2RY5u.mjs → list-O2w5xd1q.mjs} +4 -6
  98. package/dist/{list-KxQNqp4T.mjs → list-th_Ym6Cv.mjs} +6 -8
  99. package/dist/{login-Bt6j6yBM.mjs → login-hKlYsAa1.mjs} +8 -10
  100. package/dist/{logout-ANkL02p0.mjs → logout-DO8GVC6b.mjs} +4 -6
  101. package/dist/measure-R3T20wnB.mjs +25 -0
  102. package/dist/{measure-DoJvtCaA.mjs → measure-S9WszHre.mjs} +5 -4
  103. package/dist/{parameter-values-D4J8Ctu0.mjs → parameter-values-BJb2GuLz.mjs} +7 -9
  104. package/dist/{parse-enum-BL9i_brN.mjs → parse-enum-Cfne2suT.mjs} +1 -1
  105. package/dist/{parse-id-D4LeTUsP.mjs → parse-id-DfY0Lugw.mjs} +1 -1
  106. package/dist/{parse-ref-CB_KvF9h.mjs → parse-ref-C_MsXYI6.mjs} +1 -1
  107. package/dist/{path-5nQgdvrs.mjs → path-DzhQVCH8.mjs} +4 -6
  108. package/dist/{poll-BpAJpvb-.mjs → poll-D1Yg78c3.mjs} +2 -2
  109. package/dist/{poll-task-TilgciQn.mjs → poll-task-DLUZaglY.mjs} +2 -2
  110. package/dist/{preflight-OfHU3Toi.mjs → preflight-Cy2jUSca.mjs} +4 -5
  111. package/dist/{process-j8UHMHc2.mjs → process-D5HN1wpT.mjs} +1 -1
  112. package/dist/{prompt-C85xd9HR.mjs → prompt-ZG9Drin_.mjs} +1 -1
  113. package/dist/{publish-DKiqyDwo.mjs → publish-3hmHoaX-.mjs} +7 -9
  114. package/dist/{query-CQ3xXa9P.mjs → query-DTASj9Zu.mjs} +9 -10
  115. package/dist/{query-BWJ5h1g3.mjs → query-DkZViD_3.mjs} +15 -15
  116. package/dist/{query-result-D6mfoVfQ.mjs → query-result-CbTFdlSf.mjs} +1 -1
  117. package/dist/{remove-collection-CO-aMzTO.mjs → remove-collection-D3wH9H6R.mjs} +7 -9
  118. package/dist/replace-CFzqkZXs.mjs +42 -0
  119. package/dist/requests-KBiY9UYy.mjs +96 -0
  120. package/dist/{rescan-values-DpL6LGAK.mjs → rescan-values-B1WycObS.mjs} +7 -9
  121. package/dist/{resolve-Dj2MTBkn.mjs → resolve-Ct084wx3.mjs} +1 -1
  122. package/dist/{run-DUcdaZs3.mjs → run-B8qEEdC8.mjs} +4 -6
  123. package/dist/{run-QYJ-mAaG.mjs → run-cw0qTEQ7.mjs} +7 -9
  124. package/dist/{runs-DgasTnGd.mjs → runs-BOA4kK_X.mjs} +6 -8
  125. package/dist/{runtime-BJtuxWM8.mjs → runtime-B-c-Fc2i.mjs} +3 -4
  126. package/dist/{schema-tables-ooYjimrV.mjs → schema-tables-DRYzSVcW.mjs} +6 -8
  127. package/dist/{schemas-HjFPzsd-.mjs → schemas-BiMjD25L.mjs} +4 -6
  128. package/dist/{search-vaT5EwhG.mjs → search-DjKxRXO5.mjs} +4 -6
  129. package/dist/{segment-TXktTCfU.mjs → segment-BPb1H6DW.mjs} +6 -5
  130. package/dist/segment-CZkk0g79.mjs +25 -0
  131. package/dist/{selectors-DlmZpo2L.mjs → selectors-B7MJUn3I.mjs} +3 -3
  132. package/dist/{set-active-oZOUe9V7.mjs → set-active-CF868qdW.mjs} +4 -6
  133. package/dist/{set-CLHJauzG.mjs → set-kyoORFG3.mjs} +10 -10
  134. package/dist/setting-BZgjURQ_.mjs +20 -0
  135. package/dist/{setup-D1de5Sbc.mjs → setup-D9joJYPb.mjs} +11 -12
  136. package/dist/{skills-B6gfH0iR.mjs → skills-BqI05u72.mjs} +1 -1
  137. package/dist/{skills-NhgVypJ7.mjs → skills-D8NrJxjp.mjs} +3 -3
  138. package/dist/{snippet-CtA2Pkoa.mjs → snippet-DWiXNy4T.mjs} +7 -4
  139. package/dist/snippet-y_FzKxaQ.mjs +22 -0
  140. package/dist/{stash-DoRwelwY.mjs → stash-BVMerTaF.mjs} +7 -9
  141. package/dist/{status-CmRgHTrV.mjs → status-C-0jFjFn.mjs} +4 -6
  142. package/dist/{status-BpMOlfsA.mjs → status-x8bDOa1i.mjs} +6 -8
  143. package/dist/{summary-BEu7pmpq.mjs → summary-B-R9SdMF.mjs} +5 -7
  144. package/dist/{sync-schema-DTUXubMg.mjs → sync-schema-wne2KXIW.mjs} +9 -11
  145. package/dist/{table-DE3i82T_.mjs → table-C3RIKiYM.mjs} +16 -4
  146. package/dist/table-CS2-2INi.mjs +21 -0
  147. package/dist/transform-6hqM1DXA.mjs +31 -0
  148. package/dist/{transform-DEF38FWe.mjs → transform-JQel7e5H.mjs} +31 -5
  149. package/dist/transform-job-DuERhLaj.mjs +25 -0
  150. package/dist/transform-tag-DaNKreDj.mjs +21 -0
  151. package/dist/{transforms-CWvMpLc9.mjs → transforms-DB2WNP2R.mjs} +5 -7
  152. package/dist/{tree-D18vYSe4.mjs → tree-BIOphGVs.mjs} +4 -6
  153. package/dist/{unpublish-CJbL4ZsC.mjs → unpublish-BWM36ZTg.mjs} +5 -7
  154. package/dist/{update-mr9todHq.mjs → update-6umfSz5t.mjs} +9 -10
  155. package/dist/{update-BjjZIe2W.mjs → update-7Ts7KrB8.mjs} +9 -10
  156. package/dist/{update-Doia9MP_.mjs → update-BCbv880u.mjs} +12 -13
  157. package/dist/{update-CSxwZ2us.mjs → update-BchtHlbH.mjs} +10 -11
  158. package/dist/{update-BvsvyBw9.mjs → update-BiUeEGU-.mjs} +12 -13
  159. package/dist/{update-DhOfrW1j.mjs → update-BlzcZwRQ.mjs} +18 -15
  160. package/dist/{update-Bos8nnv0.mjs → update-CTZg2Lia.mjs} +13 -14
  161. package/dist/{update-DV7IY7IQ.mjs → update-DJDuIhYG.mjs} +9 -10
  162. package/dist/{update-Dxa_6H2A.mjs → update-HxYcvpSq.mjs} +15 -15
  163. package/dist/{update-Ck0Kxv8u.mjs → update-Sz0QRGWB.mjs} +9 -10
  164. package/dist/{update-dashcard-CZUWZhal.mjs → update-dashcard-CfDpy7Lz.mjs} +11 -11
  165. package/dist/{update-7DacwIvi.mjs → update-zfLbmC-H.mjs} +9 -10
  166. package/dist/{upgrade-CBW8V9qZ.mjs → upgrade-BWl0VjMK.mjs} +5 -7
  167. package/dist/upload-CPzOhGeX.mjs +16 -0
  168. package/dist/{uuid-D6JVJ-R1.mjs → uuid-D_YeqDui.mjs} +3 -5
  169. package/dist/{validate-BqNW4Sk1.mjs → validate-clAWO3X-.mjs} +1 -2
  170. package/dist/{validate-query-CcZVKYPV.mjs → validate-query-CsK2_2_u.mjs} +2 -3
  171. package/dist/{values-DBvuGnnA.mjs → values-DSk8ZJ3B.mjs} +5 -7
  172. package/dist/{verify-LIShMNZ2.mjs → verify-DkQdLjf3.mjs} +2 -2
  173. package/dist/{wait-DUHze3_B.mjs → wait-DvSsMjea.mjs} +6 -8
  174. package/dist/{wait-flags-Ybpt98PK.mjs → wait-flags-dXGThljd.mjs} +2 -2
  175. package/package.json +1 -1
  176. package/skill-data/core/SKILL.md +31 -22
  177. package/skill-data/dashboard/SKILL.md +130 -0
  178. package/skill-data/data-workflow/SKILL.md +17 -15
  179. package/skill-data/data-workflow/references/building-clean-tables.md +1 -1
  180. package/skill-data/document/SKILL.md +3 -3
  181. package/skill-data/git-sync/SKILL.md +5 -5
  182. package/skill-data/mbql/SKILL.md +17 -17
  183. package/skill-data/mbql/references/operators.md +4 -3
  184. package/skill-data/metadata/SKILL.md +78 -0
  185. package/skill-data/metadata/references/semantic-types.md +83 -0
  186. package/skill-data/native-sql/SKILL.md +118 -0
  187. package/skill-data/native-sql/references/template-tags.md +178 -0
  188. package/skill-data/transform/SKILL.md +6 -6
  189. package/skill-data/visualization/SKILL.md +3 -3
  190. package/skill-data/visualization/references/settings.md +2 -0
  191. package/dist/add-collection-Dek6kiRI.mjs +0 -11
  192. package/dist/auth-Kv2MRkRk.mjs +0 -22
  193. package/dist/capabilities-N0jo5U7S.mjs +0 -239
  194. package/dist/card-Bp58WEUF.mjs +0 -26
  195. package/dist/collection-D8TI20Ir.mjs +0 -23
  196. package/dist/dashboard-C9pQCnX6.mjs +0 -28
  197. package/dist/db-DWykmBOh.mjs +0 -28
  198. package/dist/document-DNDm8_Py.mjs +0 -22
  199. package/dist/error-5H_tcfL8.mjs +0 -227
  200. package/dist/field-CFt1-KDR.mjs +0 -21
  201. package/dist/get-CDLa6LiC.mjs +0 -50
  202. package/dist/git-sync-ByvjqSiy.mjs +0 -31
  203. package/dist/is-dirty-D7UMN0mt.mjs +0 -10
  204. package/dist/manifest-BVf8P4bl.mjs +0 -126
  205. package/dist/measure-CgFwtL1r.mjs +0 -25
  206. package/dist/metadata-ClehtgZj.mjs +0 -39
  207. package/dist/metadata-DplshwI3.mjs +0 -38
  208. package/dist/notice-DyVl5aYB.mjs +0 -206
  209. package/dist/segment-BPC725mo.mjs +0 -25
  210. package/dist/setting-DHYO-W5g.mjs +0 -20
  211. package/dist/snippet-DtLmHr2J.mjs +0 -22
  212. package/dist/table-B1iBqmNu.mjs +0 -22
  213. package/dist/transform-BuIooRQh.mjs +0 -31
  214. package/dist/transform-job-C3yz0krA.mjs +0 -25
  215. package/dist/transform-tag-CJD6mJUe.mjs +0 -21
  216. /package/dist/{body-flags-DWTTxJpP.mjs → body-flags-CgHgJBRC.mjs} +0 -0
  217. /package/dist/{command-augment-DdZIfx1V.mjs → command-augment-D9pI9Vbh.mjs} +0 -0
  218. /package/dist/{paginate-FVZUxL4J.mjs → paginate-CMqbqhMC.mjs} +0 -0
  219. /package/dist/{parameter-CiJ4CwWE.mjs → parameter-BZ6y-Gfs.mjs} +0 -0
  220. /package/dist/{render-BTKnWL0d.mjs → render-CkuFkWlQ.mjs} +0 -0
  221. /package/dist/{revision-message-flag-CHrJgFFx.mjs → revision-message-flag-CHzX0cqV.mjs} +0 -0
  222. /package/dist/{setting-m46MUtW5.mjs → setting-Cko6bE_w.mjs} +0 -0
  223. /package/dist/{transform-job-CtixL4An.mjs → transform-job-CtVziW85.mjs} +0 -0
  224. /package/dist/{transform-tag-rsIrckCM.mjs → transform-tag-wFiWmiyO.mjs} +0 -0
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: dashboard
3
+ description: Build Metabase dashboards via the `mb` CLI — lay out dashcards on the 24-column grid (`{col,row,size_x,size_y}` math, per-chart default sizes) and turn cards into a filterable, cross-linked app. Covers wiring filters to cards (parameters + parameter_mappings), the field-filter vs. raw-variable target grammar, linked/cascading filters and their foreign-key requirement, cross-filtering, click-through, multi-series overlays, and tabs. The parameter type enum and whole-array replace semantics live in `core`. Triggers — "build a dashboard from these cards", "my dashboard is squished into half the width", "wire a filter to these cards", "make a filter cascade", "click a bar to filter the other charts", "add a dashboard tab", "add a second series", "why isn't my filter showing".
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
+ ---
6
+
7
+ # Dashboard
8
+
9
+ A dashboard starts as cards on a grid; it becomes an **app** when filters drive the cards, charts cross-filter each other, and clicks navigate. This skill owns both: the grid layout and the interactive layer.
10
+
11
+ **`core` owns the transport mechanics** — the whole-array replace semantics (editing `dashcards` or `parameters` replaces the entire set; omitted entries are deleted; new cards use negative ids), `update-dashcard` for a single safe patch vs. `update --body` for a full replace, the closed `parameter.type` enum, and the `parameter-values` verb. Read it first (`mb skills get core`). **`visualization`** owns each card's chart and the full `click_behavior` key catalog.
12
+
13
+ Inspect before you wire: `mb dashboard get <id> --json` hydrates `parameters`, `dashcards`, and `tabs`; `mb dashboard cards <id>` lists just the dashcards.
14
+
15
+ ## Layout: the grid is 24 columns — not 12
16
+
17
+ Every dashcard carries `{col, row, size_x, size_y}` in grid units: `col` is 0-indexed from the left edge, `row` grows downward, and `col + size_x ≤ 24`. **Full-width is `size_x: 24` — Metabase's per-chart _default_ width of 12 is half a row.** A layout authored on the usual 12-column web-grid assumption crams the whole dashboard into the left half of the viewport. The server stores whatever geometry you send — overlaps and gaps included, no auto-fix.
18
+
19
+ Default sizes (w×h): `scalar`/`smartscalar` 6×3, `pie` 12×8, `table`/`pivot`/`object` 12×9, `waterfall` 14×6, `sankey` 16×10, `heading` 24×1, `text` 12×3, every other chart 12×6.
20
+
21
+ The standard shape — a KPI row of scalars across the full 24, charts in halves or thirds below, wide tables full-width:
22
+
23
+ ```jsonc
24
+ "dashcards": [
25
+ { "id": -1, "card_id": 101, "col": 0, "row": 0, "size_x": 6, "size_y": 3 }, // 4 KPIs × 6 = 24
26
+ { "id": -2, "card_id": 102, "col": 6, "row": 0, "size_x": 6, "size_y": 3 },
27
+ { "id": -3, "card_id": 103, "col": 12, "row": 0, "size_x": 6, "size_y": 3 },
28
+ { "id": -4, "card_id": 104, "col": 18, "row": 0, "size_x": 6, "size_y": 3 },
29
+ { "id": -5, "card_id": 105, "col": 0, "row": 3, "size_x": 12, "size_y": 6 }, // two halves
30
+ { "id": -6, "card_id": 106, "col": 12, "row": 3, "size_x": 12, "size_y": 6 },
31
+ { "id": -7, "card_id": 107, "col": 0, "row": 9, "size_x": 24, "size_y": 9 } // full-width table
32
+ ]
33
+ ```
34
+
35
+ **Sanity-check before sending:** rows should fill to 24 and at least one card must end at `col + size_x = 24`. If nothing in the array crosses column 12, you've authored a 12-column layout — double every width.
36
+
37
+ ## The wiring loop: a filter is a parameter + a mapping per card
38
+
39
+ A dashboard filter is one entry in the dashboard's `parameters` array **plus** a `parameter_mappings` entry on every dashcard it should control. A parameter with no mapping is an inert widget — the most common "my filter does nothing" cause.
40
+
41
+ ```jsonc
42
+ // dashboard.parameters — the widget
43
+ { "id": "status", "name": "Status", "slug": "status", "type": "string/=" }
44
+
45
+ // on each target dashcard — bind the widget to a column of that card
46
+ { "parameter_id": "status", "card_id": 42,
47
+ "target": ["dimension", ["field", 1779, null]] }
48
+ ```
49
+
50
+ The `target` grammar depends on what the card is:
51
+
52
+ | Card's query | `target` |
53
+ | --------------------------- | -------------------------------------------- |
54
+ | MBQL column | `["dimension", ["field", <field-id>, null]]` |
55
+ | Native **field filter** tag | `["dimension", ["template-tag", "<tag>"]]` |
56
+ | Native **raw variable** tag | `["variable", ["template-tag", "<tag>"]]` |
57
+
58
+ (Field ids from `table get <id> --include fields`; native tags from `native-sql`.) Because editing replaces the whole set, adding a filter is read-modify-write: `dashboard get <id> --json`, append to `parameters` and to each dashcard's `parameter_mappings`, send the full arrays back. To wire one card without touching the rest, `update-dashcard <dash-id> <dashcard-id> --body '{"parameter_mappings":[…]}'`.
59
+
60
+ ## Choose the interaction
61
+
62
+ Four distinct mechanisms — pick by what the user wants clicking or filtering to _do_:
63
+
64
+ | Want | Mechanism |
65
+ | -------------------------------------------------- | ----------------------------------------------------- |
66
+ | One widget filters several cards | a **dashboard parameter** mapped to each card (above) |
67
+ | One filter's choices narrow another's | a **linked filter** (`filteringParameters`) |
68
+ | Clicking a chart filters the other charts | **cross-filter** click behavior |
69
+ | Clicking navigates to a question / dashboard / URL | **link** click behavior |
70
+
71
+ ### Linked (cascading) filters
72
+
73
+ Make a child filter (City) show only values consistent with a parent (State) by listing the parent's id in the child parameter's `filteringParameters`:
74
+
75
+ ```json
76
+ { "id": "city", "type": "category", "filteringParameters": ["state"] }
77
+ ```
78
+
79
+ Two hard constraints, both from the same root: **linked filters read table-metadata foreign keys only.** They ignore joins defined inside a saved question or model. So the parent and child columns must be connected by a FK set in metadata — if the cascade shows values it shouldn't, the FK is missing (fix it via `metadata`, then retry). And `filteringParameters` is **incompatible with a `values_source_type` of `static-list` or `card`** — a custom value source overrides the cascade, so Metabase clears the link. Leave the child's value source live (omit it) for linked filtering to work.
80
+
81
+ ### Cross-filtering (click a chart to filter the rest)
82
+
83
+ Set the **driver** chart's whole-card click behavior to `crossfilter`, mapping the clicked value into a dashboard parameter; map that same parameter onto the **follower** cards normally. The driver stays unmapped to it (it emits the value; it doesn't consume it). `click_behavior` lives in the dashcard's `visualization_settings` (whole card) or `column_settings[<col>].click_behavior` (per column on tables), with **camelCase** keys:
84
+
85
+ ```json
86
+ {
87
+ "click_behavior": {
88
+ "type": "crossfilter",
89
+ "parameterMapping": {
90
+ "status": {
91
+ "id": "status",
92
+ "source": { "type": "column", "id": "STATUS", "name": "Status" },
93
+ "target": { "type": "parameter", "id": "status" }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### Click-through (navigate)
101
+
102
+ ```json
103
+ { "click_behavior": { "type": "link", "linkType": "dashboard",
104
+ "targetId": 7, "parameterMapping": { … } } }
105
+ ```
106
+
107
+ `linkType` is `question` / `dashboard` (carry a `parameterMapping` to pass the clicked context) or `url` (a `linkTemplate` like `"https://app/orders/{{ORDER_ID}}"`, `{{Column}}` interpolated). The full `click_behavior`/`parameterMapping` key catalog is in `visualization`'s settings reference — don't hand-author a complex one; build it once in the UI and copy it (`mb dashboard get <id> --full --json`). Note: a **native-SQL card can't drill through** — only cross-filter and link click behaviors work on it.
108
+
109
+ ## Series, tabs, value sources
110
+
111
+ **Multi-series overlay** — put several cards on one chart (**line / area / bar only**): the dashcard's `series` is an array of card ids in draw order (`"series": [43, 51]`). Sending it replaces the set; an empty array clears it. Patch with `update-dashcard`.
112
+
113
+ **Tabs** — `tabs` is `[{ "name": "Overview", "position": 0 }, …]`; a dashcard joins a tab via `dashboard_tab_id`. Creating tabs and cards together uses **negative ids** (per `core`): give a new tab `id: -1`, point new dashcards at `dashboard_tab_id: -1`, and the create/update response returns the real ids. A filter widget only appears on a tab if it's mapped to at least one card on **that** tab.
114
+
115
+ **Filter value source** (the dropdown behind a parameter) — omit `values_source_type` to pull live distinct values from the mapped column; `"static-list"` + `values_source_config.values` for a fixed list; `"card"` + `{card_id, value_field, label_field}` to source from a query. `mb dashboard parameter-values <id> <param-id> [--query <substr>]` previews what a widget will offer.
116
+
117
+ ## Gotchas
118
+
119
+ - **Auto-connect / the missing map:** a filter that "does nothing" or "won't show" is almost always unmapped, or mapped only to cards on another tab.
120
+ - **Time-grouping parameters** (`temporal-unit`) bind only to a datetime column in the query's **last** stage — add one after a time-bucketed summary and it can't attach.
121
+ - **Required + default:** a `required: true` parameter with no `default` blocks its cards until a value is chosen; give it a `default` for expensive queries you don't want running unfiltered.
122
+ - **Whole-array replace:** never send a partial `parameters`/`dashcards` array to `update` — you'll delete what you omit. Use `update-dashcard` for a single-card change.
123
+
124
+ ## Don't
125
+
126
+ - Don't lay out on a 12-column assumption — full-width is `size_x: 24`; a layout where no card crosses column 12 renders in the left half of the viewport.
127
+ - Don't declare a parameter and forget the per-card `parameter_mappings` — the widget won't filter anything.
128
+ - Don't expect a linked filter to work off a model/question join, a custom column, or with a static/card value source — it needs a metadata FK and a live source.
129
+ - Don't hand-author a complex `click_behavior` — copy a UI-built one.
130
+ - Don't add a second `series` to a pie/scalar/table — it's line/area/bar only.
@@ -10,14 +10,14 @@ The front door for turning a raw database into clean tables, reusable definition
10
10
 
11
11
  A data project moves through stages. A user can start at any of them — detect where their data already is, don't assume.
12
12
 
13
- | Stage | What it does | Where the method lives |
14
- | --------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
15
- | **Build clean tables** | Raw, normalized source DB → a small set of wide, clean, analysis-ready tables (built as transforms) | `references/building-clean-tables.md` |
16
- | **Define reusable metrics** | Clean tables → shared segments (saved filters), measures (saved calculations), metrics (official numbers) | `references/reusable-definitions.md` |
17
- | **Answer questions** | Clean tables → a trustworthy plain-language written answer | `references/answering-questions.md` |
18
- | **Build dashboards** | Clean tables / definitions → charts and dashboards people look at | the `visualization` skill (`mb skills get visualization`) |
13
+ | Stage | What it does | Where the method lives |
14
+ | --------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
15
+ | **Build clean tables** | Raw, normalized source DB → a small set of wide, clean, analysis-ready tables (built as transforms) | `references/building-clean-tables.md` |
16
+ | **Define reusable metrics** | Clean tables → shared segments (saved filters), measures (saved calculations), metrics (official numbers) | `references/reusable-definitions.md` |
17
+ | **Answer questions** | Clean tables → a trustworthy plain-language written answer | `references/answering-questions.md` |
18
+ | **Build dashboards** | Clean tables / definitions → charts and dashboards people look at | the `visualization` skill (charts) + `dashboard` skill (grid layout, filters, interactivity) |
19
19
 
20
- The first three methods are references in this skill — read **only the one the current stage needs**: run `mb skills path data-workflow` and Read `references/<file>.md`. (`mb skills get data-workflow --full` appends all three at once — heavier; prefer the single Read.) "Build dashboards" is the standalone `visualization` skill because authoring a chart is a CLI capability in its own right.
20
+ The first three methods are references in this skill — read **only the one the current stage needs**: run `mb skills path data-workflow` and Read `references/<file>.md`. (`mb skills get data-workflow --full` appends all three at once — heavier; prefer the single Read.) "Build dashboards" lives in two standalone skills — `visualization` for authoring each chart, `dashboard` for laying cards out on the grid and wiring filters and interactivity — both CLI capabilities in their own right. Load `dashboard` before composing any dashboard, even a plain no-filter layout: the grid geometry lives there.
21
21
 
22
22
  CLI mechanics come from the reference skills, not from here: `mb skills get core` (auth, inspection, the `field`/`table`/`library`/`segment`/`measure` verbs), `mbql` (query and definition bodies), `transform` (creating/running transforms). This skill owns the _judgment_ — which tables, which definitions, what to keep — and the conventions below.
23
23
 
@@ -88,19 +88,21 @@ The rules every stage follows. The reference files assume this contract rather t
88
88
 
89
89
  Don't make the user name a _stage_ — but do find out _where their data lives_ before going looking.
90
90
 
91
+ **Data not in a database yet?** A local CSV gets in via `mb upload csv --file <path>` (creates a table + model); treat the result as a starting table and clean it with a transform if needed. Uploads must be admin-enabled — check `mb setting get uploads-settings --json` (`db_id: null` ⇒ not configured). See `core`'s **upload** quirk for the rest.
92
+
91
93
  **Ask before you crawl.** If you don't already know which database/schema/table the user means, ask — one plain question short-circuits a dozen tool calls. The asymmetry: if they name a **database**, ask which **schema**; if they name a **table**, ask which **database**. "If you don't know, no problem — I'll look" is the fallback, not the opener.
92
94
 
93
- **When you do crawl,** use `core`'s cheap, narrowest-first ladder (never whole-warehouse rollups): `mb db list` → `db schemas <id>` → `db schema-tables <id> <schema>` → `table list [--db-id]` → `table fields <id>` (or `table metadata <id>` for FK targets and dimensions — heavier). Have a _name_ rather than a tree to walk? `mb search <query> [--models] [--db-id]`. Need to know what's in a column? `mb field summary <id>` (counts) and `field values <id>` (sample values). If a database looks freshly connected or an expected table is missing, offer `mb db sync-schema <id> --wait` before concluding it doesn't exist.
95
+ **When you do crawl,** use `core`'s cheap, narrowest-first ladder (never whole-warehouse rollups): `mb db list` → `db schemas <id>` → `db schema-tables <id> <schema>` → `table list [--db-id]` → `table fields <id>` (or `table get <id> --include fields --full` for FK targets and dimensions). Have a _name_ rather than a tree to walk? `mb search <query> [--models] [--db-id]`. Need to know what's in a column? `mb field summary <id>` (counts) and `field values <id>` (sample values). If a database looks freshly connected or an expected table is missing, offer `mb db sync-schema <id> --wait` before concluding it doesn't exist.
94
96
 
95
97
  **Read the shape to pick a stage.** Raw, normalized, SaaS-synced tables (many tables, coded columns, `*_field`/`*_choice` lookups)? → **build clean tables** first. Already wide, clean, human-readable ones? Then it depends on the goal:
96
98
 
97
- | What the user wants / what's there | Stage |
98
- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
99
- | "Clean up / flatten / make sense of" raw, normalized data; no clean tables yet | Build clean tables → `references/building-clean-tables.md` |
100
- | Clean tables exist; "make this reusable", "define active customers / revenue / MRR officially" | Define reusable metrics → `references/reusable-definitions.md` |
101
- | Clean tables exist; "answer this question", "who registered", "analyze / report on X" (wants a written answer) | Answer questions → `references/answering-questions.md` |
102
- | Clean tables (and maybe definitions) exist; "chart this", "build a dashboard", "show me X over time" | Build dashboards → `mb skills get visualization` |
103
- | "Do the whole thing" / "set up analytics for X" from raw data | Start at build-clean-tables, then continue down the stages |
99
+ | What the user wants / what's there | Stage |
100
+ | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
101
+ | "Clean up / flatten / make sense of" raw, normalized data; no clean tables yet | Build clean tables → `references/building-clean-tables.md` |
102
+ | Clean tables exist; "make this reusable", "define active customers / revenue / MRR officially" | Define reusable metrics → `references/reusable-definitions.md` |
103
+ | Clean tables exist; "answer this question", "who registered", "analyze / report on X" (wants a written answer) | Answer questions → `references/answering-questions.md` |
104
+ | Clean tables (and maybe definitions) exist; "chart this", "build a dashboard", "show me X over time" | Build dashboards → `visualization` (charts) + `dashboard` (layout, interactivity) |
105
+ | "Do the whole thing" / "set up analytics for X" from raw data | Start at build-clean-tables, then continue down the stages |
104
106
 
105
107
  **Answer and dashboard are alternative endpoints, not a sequence.** Once tables are clean (and maybe defined), answering in prose and building a dashboard are two different things you can do with the data — route to whichever the goal calls for; neither has to precede the other.
106
108
 
@@ -131,7 +131,7 @@ Then make the links real, not just implied:
131
131
  - **Wire foreign keys between your tables.** Mark each linking id as a foreign key pointing at the id it references — set the column's type to foreign-key and its target so Metabase itself knows the tables connect and can traverse them.
132
132
  - **Graft onto existing clean data** the user approved (step 3 / Phase 1): point the linking id at the existing table's id the same way. Link, don't duplicate.
133
133
 
134
- **Set the metadata — a transform's output starts blank, and these tables are Library-bound.** A fresh transform table has no descriptions, raw column names, and untyped columns. You worked it out while investigating; don't leave that knowledge stranded in this chat. Set it on the table so the data explains itself inside Metabase (search, the Question editor, Metabot) and is fit to publish. The mechanics — `mb field update` for semantic types / FK targets / display names, `mb table update` for table descriptions — and their footguns are in `core`; the calls about _what_ to set:
134
+ **Set the metadata — a transform's output starts blank, and these tables are Library-bound.** A fresh transform table has no descriptions, raw column names, and untyped columns. You worked it out while investigating; don't leave that knowledge stranded in this chat. Set it on the table so the data explains itself inside Metabase (search, the Question editor, Metabot) and is fit to publish. The mechanics — `mb field update` for semantic types / FK targets / display names, `mb table update` for table descriptions, and the feature each edit unlocks — are in the `metadata` skill; the calls about _what_ to set:
135
135
 
136
136
  - **Semantic types — the highest-value piece.** A column's semantic type is what makes Metabase treat it right: `type/Email`, `type/Currency`/`type/Price`, `type/Category` (turns into a filter dropdown), `type/City`/`type/State`/`type/Country`, `type/CreationTimestamp`, `type/Description`. Set it on every column whose meaning you decoded. A typed column shows money as money, offers a filter dropdown, and lands on the right chart axis for everyone downstream; an untyped one is a guess.
137
137
  - **Descriptions.** A one-line description on each table and every non-obvious column.
@@ -22,11 +22,11 @@ mb document archive <id> --profile <name> --json # soft-delete (PUT arch
22
22
 
23
23
  - `list` returns the standard envelope (`{data, returned, total}`). The compact item is `{id, name, collection_id, archived, creator_id, can_write}` and omits the (potentially huge) `document` body — pull the body with `get --full`.
24
24
  - `archive` is the only delete, mirroring `card` / `dashboard`. **Unarchive** with `mb document update <id> --body '{"archived":false}'`.
25
- - `update` is PATCH — send only the keys you want to change, and only these are accepted: `name`, `document`, `collection_id`, `collection_position`, `archived`. Replacing `document` replaces the **whole** body; there is no partial-node patch.
25
+ - `update` is PATCH — send only the keys you want to change: `name`, `document`, `collection_id`, `collection_position`, `archived`, and `cards` (inline card creation works on update too, not just create — see below). Replacing `document` replaces the **whole** body; there is no partial-node patch.
26
26
 
27
27
  ## Node ids (`_id`)
28
28
 
29
- The editor anchors only these node types with an `_id` (a UUID): `paragraph`, `heading`, `codeBlock`, `orderedList`, `bulletList`, `blockquote`, `cardEmbed`, `supportingText`. **`create`/`update` require a non-empty `_id` on every node of those types** and reject a body missing any ("did not match expected schema"). Other node types (`doc`, `text`, `listItem`, `resizeNode`, `flexContainer`, …) take no `_id` and are left alone. Without them the editor backfills ids when the document opens, which makes a freshly-saved document show a spurious "unsaved changes" prompt.
29
+ The editor anchors only these node types with an `_id` (a UUID): `paragraph`, `heading`, `codeBlock`, `orderedList`, `bulletList`, `blockquote`, `cardEmbed`, `supportingText`. **`create`/`update` require a non-empty `_id` on every node of those types** — the CLI validates before sending and rejects a body missing any (`every … node needs a non-empty string _id (mint with mb uuid)`). Other node types (`doc`, `text`, `listItem`, `resizeNode`, `flexContainer`, …) take no `_id` and are left alone. Without them the editor backfills ids when the document opens, which makes a freshly-saved document show a spurious "unsaved changes" prompt.
30
30
 
31
31
  Mint the ids with `mb uuid --count <n> --json` (→ `["…", …]`), one per id-bearing node, and set each as that node's `attrs._id`.
32
32
 
@@ -81,7 +81,7 @@ Every node is `{ "type": string, "attrs"?: object, "content"?: [nodes], "text"?:
81
81
  - **`resizeNode`** — wraps a single `cardEmbed` or `flexContainer` to make it resizable (no `_id`). `attrs: { "height": <px>, "minHeight": <px> }`, `content` is exactly one `cardEmbed`/`flexContainer`.
82
82
  - **`flexContainer`** — a horizontal row of 1–3 `cardEmbed` / `supportingText` cells side by side (no `_id`). `attrs.columnWidths` is an array of width percentages.
83
83
  - **`supportingText`** — a text column that sits next to a card inside a `flexContainer` (id-bearing); `content` is the usual block nodes (`paragraph`, `heading`, lists, …).
84
- - **`smartLink`** — an inline reference to a Metabase entity (renders as a live chip). Inline, atomic, no `_id`. `attrs: { "entityId": <id>, "model": <model>, "label": <string|null>, "href": <relative-path> }`. `model` ∈ `card`, `dataset`, `metric`, `dashboard`, `collection`, `table`, `database`, `document`, `transform`, `segment`, `measure`, `user`, `action`, `indexed-entity`.
84
+ - **`smartLink`** — an inline reference to a Metabase entity (renders as a live chip). Inline, atomic, no `_id`. `attrs: { "entityId": <id>, "model": <model>, "label": <string|null>, "href": <relative-path> }`. `model` ∈ `card`, `dataset`, `metric`, `dashboard`, `collection`, `table`, `database`, `document`, `transform`, `segment`, `user`, `action`, `indexed-entity` (plus `measure` on v60+).
85
85
  - **`metabot`** — an inline Metabot prompt block.
86
86
 
87
87
  ## Embedding an existing card
@@ -16,8 +16,8 @@ Always run `status` (or `is-dirty` + `has-remote-changes`) before `import` or `e
16
16
 
17
17
  ```bash
18
18
  mb git-sync status --profile <n> --json # → branch, dirty, current task
19
- mb git-sync is-dirty --profile <n> --json # → {dirty: bool}; instance has unexported changes
20
- mb git-sync has-remote-changes --profile <n> --json # → {behind: bool}; remote has unimported commits
19
+ mb git-sync is-dirty --profile <n> --json # → {is_dirty: bool}; instance has unexported changes
20
+ mb git-sync has-remote-changes --profile <n> --json # → {has_changes: bool, remote_version, local_version, cached}; remote has unimported commits
21
21
  mb git-sync dirty --profile <n> --json # → list the dirty objects
22
22
  mb git-sync current-task --profile <n> --json # → in-flight task (or idle)
23
23
  ```
@@ -43,8 +43,8 @@ Pulls the configured branch and applies it to the instance. Polls until the task
43
43
 
44
44
  Workflow:
45
45
 
46
- 1. Read state (above) — confirm `dirty: false` (or `--force` is intended).
47
- 2. Confirm `has-remote-changes` reports there's actually something to import.
46
+ 1. Read state (above) — confirm `is_dirty: false` (or `--force` is intended).
47
+ 2. Confirm `has-remote-changes` reports `has_changes: true` — there's actually something to import.
48
48
  3. `git-sync import --branch <branch>` — runs to terminal status by default.
49
49
 
50
50
  ## Export (instance → remote)
@@ -67,7 +67,7 @@ Workflow:
67
67
  1. **Branch guard** (below) — confirm the instance isn't tracking `main`/`master`, or that the user has explicitly accepted exporting to it.
68
68
  2. Read state (above) — confirm `is-dirty` reports there's something to export.
69
69
  3. `git-sync export -m "..."` — pushes and polls.
70
- 4. (Optional) `git-sync status` — verify `dirty: false` after.
70
+ 4. (Optional) `git-sync status` — verify `is_dirty: false` after.
71
71
 
72
72
  ### Branch guard: don't export to main/master without confirmation
73
73
 
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: mbql
3
- description: Author and debug MBQL 5 query bodies for the `mb` CLI — the only hand-authorable query format. Covers the JSON shape (flat numeric-id stages, options-object-second clauses, optional lib/uuid), joins and FK traversal, multi-stage pipelines, aggregation naming, the flat-vs-legacy-envelope footgun, and the print-schema → dry-run → run validation loop. Use when writing or fixing any query body — `mb query`, a card's `dataset_query`, a transform's `source.query`, or a segment/measure `definition` — or when `--dry-run`/run reports validation errors. Triggers — "write an MBQL query", "the dataset_query is wrong", "aggregate and group by", "join two tables", "month-over-month".
3
+ description: Author and debug MBQL query bodies for the `mb` CLI — the only hand-authorable query format. Covers the JSON shape (flat numeric-id stages, options-object-second clauses, optional lib/uuid), joins and FK traversal, multi-stage pipelines, aggregation naming, the flat-vs-legacy-envelope footgun, and the print-schema → dry-run → run validation loop. Use when writing or fixing any query body — `mb query`, a card's `dataset_query`, a transform's `source.query`, or a segment/measure `definition` — or when `--dry-run`/run reports validation errors. Triggers — "write an MBQL query", "the dataset_query is wrong", "aggregate and group by", "join two tables", "month-over-month".
4
4
  allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
5
  ---
6
6
 
7
- # MBQL 5
7
+ # MBQL
8
8
 
9
- MBQL 5 is the **only query format you can author by hand** with confidence — it has a bundled JSON Schema, so the CLI pre-flight-validates it before sending. Legacy MBQL 4 and native SQL are accepted but **not** schema-validated (see "Other formats" below).
9
+ MBQL is the query format you author by hand — it has a bundled JSON Schema, so the CLI pre-flight-validates it before sending. A native SQL query is **also** MBQL: its single stage is `mbql.stage/native` (raw SQL) instead of `mbql.stage/mbql` (structured), so it's pre-flight-validated the same way (its SQL string aside) — see `native-sql`. Only the legacy flat forms below skip validation.
10
10
 
11
- Prefer MBQL over native SQL: portable across warehouse engines and pre-flight-validated. Try it first; fall back to native SQL when MBQL can't express what you need, or when an MBQL body keeps failing server-side and you can't resolve it.
11
+ Prefer a **structured** stage over a native SQL stage: portable across warehouse engines. Try it first; fall back to a native stage when structured MBQL can't express what you need, or when a structured body keeps failing server-side and you can't resolve it. For native SQL with parameters (template tags, field filters, snippets), load `native-sql`.
12
12
 
13
13
  General flag conventions, body-input precedence, output flags, `./.scratch`, and `mb uuid` mechanics live in `core` (`mb skills get core`).
14
14
 
@@ -47,7 +47,7 @@ Every clause is `[op, {options}, ...args]`. The options object is element **1**,
47
47
  ["asc", {}, ["field", {}, 42]]
48
48
  ```
49
49
 
50
- The legacy MBQL 4 field shape `["field", id, opts]` (id second) is **rejected** here. A slot-1 violation surfaces from `--dry-run` as `must be the field options object` / `must be the clause options object` at `/stages/0/<verb>/<n>/1`.
50
+ The legacy field shape `["field", id, opts]` (id second) is **rejected** here. A slot-1 violation surfaces from `--dry-run` as `must be the field options object` / `must be the clause options object` at `/stages/0/<verb>/<n>/1`.
51
51
 
52
52
  The same `[op, {options}, …]` rule holds for `aggregation`, `breakout` (a list of field refs), `filters` (implicitly ANDed; nest an explicit `["or", {}, …]` for OR), `order-by`, `expressions`, and join `conditions`.
53
53
 
@@ -89,15 +89,15 @@ mb query --file q.json --profile <n> --json # 3. validate +
89
89
  - `Invalid :expression reference: no expression named "X"` (or an invalid `:aggregation` reference) → a **ref** points to an expression name / aggregation `lib/uuid` that isn't defined in the query; fix the target string.
90
90
  - `Duplicate :lib/uuid` → you reused a `lib/uuid`. Omit them (the server mints unique ones) or give each clause a distinct value.
91
91
 
92
- A successful run emits the compact envelope by default: `data.rows` + slim `data.cols` (`name`, `display_name`, `base_type`, `semantic_type`). Pass `--full` for the raw `/api/dataset` envelope (`results_metadata`, `native_form`, per-column fingerprints/`field_ref`) only when you need that metadata; `--fields data.rows` narrows to rows alone. `mb query` also runs a **native** body — `{database, type:"native", native:{query:"SELECT …"}}` — which skips pre-flight; the quickest way to eyeball warehouse data.
92
+ A successful run emits the compact envelope by default: `data.rows` + slim `data.cols` (`name`, `display_name`, `base_type`, `semantic_type`). Pass `--full` for the raw `/api/dataset` envelope (`results_metadata`, `native_form`, per-column fingerprints/`field_ref`) only when you need that metadata; `--fields data.rows` narrows to rows alone. `mb query` also runs a native query — author it as an `mbql.stage/native` stage (pre-flight-validated like any MBQL body; see `native-sql`).
93
93
 
94
94
  `--skip-validate` bypasses pre-flight and sends as-is — use only when the bundled schema disagrees with what the server actually accepts (drift / false negative). Mutually exclusive with `--dry-run`. Same flag exists on `card create/update` and `transform create/update`.
95
95
 
96
- ## Where MBQL 5 is consumed
96
+ ## Where the query is consumed
97
97
 
98
- The same body and pre-flight apply everywhere a query is embedded. Each pre-flights only when the value is MBQL 5 (`lib/type: "mbql/query"`); legacy shapes skip it; `--skip-validate` bypasses.
98
+ The same body and pre-flight apply everywhere a query is embedded. Each pre-flights only when the value is the `mbql/query` shape (`lib/type: "mbql/query"`); legacy shapes skip it; `--skip-validate` bypasses.
99
99
 
100
- | Command | MBQL 5 lives at | Notes |
100
+ | Command | The query lives at | Notes |
101
101
  | --------------------------------------- | ---------------------------------------------- | ------------------------------------------- |
102
102
  | `mb query` | the whole body | ad-hoc run against `/api/dataset` |
103
103
  | `card create` / `card update` | `dataset_query` | a **flat** `mbql/query` — see footgun below |
@@ -107,7 +107,7 @@ The same body and pre-flight apply everywhere a query is embedded. Each pre-flig
107
107
 
108
108
  ## Footgun: `dataset_query` is the flat mbql/query, not a legacy envelope
109
109
 
110
- The most common mistake. The legacy MBQL 4 shape `{ "type": "query", "database": N, "query": {…} }` looks similar but is wrong for MBQL 5. `dataset_query` (and `source.query`, and `definition`) **is the `mbql/query` value itself**:
110
+ The most common mistake. The legacy shape `{ "type": "query", "database": N, "query": {…} }` looks similar but is wrong. `dataset_query` (and `source.query`, and `definition`) **is the `mbql/query` value itself**:
111
111
 
112
112
  ```json
113
113
  "dataset_query": {
@@ -118,16 +118,16 @@ The most common mistake. The legacy MBQL 4 shape `{ "type": "query", "database":
118
118
  }
119
119
  ```
120
120
 
121
- No `type:"query"` wrapper, no `query:` nesting. If you wrap MBQL 5 inside a legacy envelope the CLI rejects it pre-send with a `ConfigError` (no `--skip-validate` gets it past). If it reached the server it would store silently and fail at run time with `Initial MBQL stage must have either :source-table or :source-card`.
121
+ No `type:"query"` wrapper, no `query:` nesting. If you wrap the query inside a legacy envelope the CLI rejects it pre-send with a `ConfigError` (no `--skip-validate` gets it past). If it reached the server it would store silently and fail at run time with `Initial MBQL stage must have either :source-table or :source-card`.
122
122
 
123
- ## Other formats skip pre-flight
123
+ ## Legacy formats you may encounter
124
124
 
125
- Anything not `lib/type: "mbql/query"` is sent as-is and normalized server-side:
125
+ Older Metabase servers used a different query envelope (sometimes called MBQL 4 / "legacy MBQL"); the `mbql/query` shape above is what recent servers store and return. You won't author the legacy shapes, but you may see them in queries created long ago. Anything not `lib/type: "mbql/query"` is sent as-is and normalized server-side — you lose validation, so don't author these:
126
126
 
127
- - **Legacy MBQL 4** — `{ "type": "query", "database": N, "query": { "source-table": T, … } }`
128
- - **Native SQL** — `{ "type": "native", "database": N, "native": { "query": "SELECT …" } }`
127
+ - **Legacy structured** — `{ "type": "query", "database": N, "query": { "source-table": T, … } }`
128
+ - **Flat native** — `{ "type": "native", "database": N, "native": { "query": "SELECT …" } }` — the server accepts it, but author the native stage instead (`native-sql`).
129
129
 
130
- `mb query --file probe.json` runs these directly; `--dry-run` on them returns `{ ok: true, errors: [] }`. Don't author MBQL 4 by hand — build a legacy or complex query in the Metabase UI and pull the body with `mb card get <id> --full --json` / `mb transform get <id> --full --json`.
130
+ `mb query --file probe.json` runs these directly; `--dry-run` on them returns `{ ok: true, errors: [] }`. Don't author them by hand — build a legacy or complex query in the Metabase UI and pull the body with `mb card get <id> --full --json` / `mb transform get <id> --full --json` (which returns the `mbql/query` shape).
131
131
 
132
132
  ## Joins and FK traversal
133
133
 
@@ -193,7 +193,7 @@ Later stages address the first stage's aggregation by the `name` you gave it (`"
193
193
 
194
194
  ## Naming aggregation output columns
195
195
 
196
- Default MBQL 5 aggregations materialize as `count`, `count_where`, `avg`, `avg_2`, `sum`, … — fine for an ad-hoc run, ugly for a transform target table or card column. Set `name` (the warehouse column name) and `display-name` (the UI header) in the aggregation's options:
196
+ Default aggregations materialize as `count`, `count_where`, `avg`, `avg_2`, `sum`, … — fine for an ad-hoc run, ugly for a transform target table or card column. Set `name` (the warehouse column name) and `display-name` (the UI header) in the aggregation's options:
197
197
 
198
198
  ```json
199
199
  ["count", { "name": "shipments_shipped", "display-name": "Shipments shipped" }]
@@ -1,4 +1,4 @@
1
- # MBQL 5 operator reference
1
+ # MBQL operator reference
2
2
 
3
3
  The complete clause vocabulary the bundled schema accepts, in the CLI's API/numeric
4
4
  form. The clause _structure_ and the slot-1-options rule are in the SKILL.md body —
@@ -240,8 +240,9 @@ positional arg is the default.)
240
240
  - Truncation: `default`, `millisecond`, `second`, `minute`, `hour`, `day`, `week`,
241
241
  `month`, `quarter`, `year`.
242
242
  - Extraction (returns an integer): `minute-of-hour`, `hour-of-day`, `day-of-week`,
243
- `day-of-week-iso`, `day-of-month`, `day-of-year`, `week-of-year`, `week-of-year-iso`,
244
- `month-of-year`, `quarter-of-year`, `year-of-era`, `second-of-minute`.
243
+ `day-of-month`, `day-of-year`, `week-of-year`, `month-of-year`, `quarter-of-year`,
244
+ `year-of-era`, `second-of-minute`. (The `*-iso`/`*-us` variants are `temporal-extract`
245
+ operator modes, not field-option bucketing units.)
245
246
 
246
247
  ```json
247
248
  ["field", { "temporal-unit": "month" }, 22]
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: metadata
3
+ description: Set Metabase field and table metadata via the `mb` CLI — semantic types, foreign-key targets, dropdown/scan behavior, column visibility, and display names. The point is the causal chain — one metadata edit unlocks a downstream feature (a FK target enables joins and linked filters; `has_field_values` picks the filter widget; `visibility_type` can block queries). Covers `field update` / `table update`, the writable-vs-read-only split, the semantic-type catalog, why semantic types are labels not casts (and how to actually cast), and sync-vs-scan-vs-fingerprint. Triggers — "set this column as currency / email / a category", "mark this as a foreign key", "make this column a dropdown", "why doesn't the query builder suggest a join", "hide this column", "set the entity key".
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
+ ---
6
+
7
+ # Metadata
8
+
9
+ Metabase reads the raw column types from your warehouse; **metadata** is the layer you edit on top to make columns behave well — the right filter widget, joins, formatting, maps. You set it per-column with `mb field update <id>` and per-table with `mb table update <id>`. Both are **PATCH** — send only the keys you're changing.
10
+
11
+ Metadata is a small set of fields with large, indirect effects. Get the field ids from `mb table get <id> --include fields` (or `mb table fields <id>`); inspect a column's shape with `mb field get <id>`, its live cardinality with `mb field summary <id>`, its cached distinct set with `mb field values <id>`. General flag/output/body mechanics live in `core`.
12
+
13
+ ## The causal chain — set X, unlock Y
14
+
15
+ This is the whole point of the skill. Each edit below is a key in the `field update` (or `table update`) body; the value change is what turns a feature on.
16
+
17
+ | Set (via `field update`) | Unlocks / does |
18
+ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | `semantic_type: "type/PK"` | marks the row's identity key — enables record/detail view, and lets other tables' FKs point here |
20
+ | `semantic_type: "type/FK"` **+** `fk_target_field_id: <pk field id>` | the join relationship — implicit FK joins in queries (`mbql` `source-field`), query-builder join suggestions, and dashboard **linked filters** |
21
+ | `semantic_type: "type/Currency"` / `type/Email` / `type/City` / … | correct display formatting and the matching filter widget (and region/pin maps for location types) |
22
+ | `has_field_values: "list"` (or `"auto-list"`) | a **dropdown** filter widget, backed by a scanned distinct-value set |
23
+ | `has_field_values: "search"` | a **search box** (no value set stored) — for high-cardinality columns |
24
+ | `has_field_values: "none"` | a plain input box, no dropdown |
25
+ | `visibility_type: "sensitive"` or `"retired"` | **blocks queries** that touch the field — not a UI hint, an error |
26
+ | `visibility_type: "hidden"` | removes the column from the query builder and data reference (SQL can still read it — **not access control**) |
27
+ | `visibility_type: "details-only"` | hidden in table views, shown in the single-record detail view (for long blobs) |
28
+ | `coercion_strategy: <strategy>` | **actually casts** the column — the only entry here that changes the value's type (below) |
29
+ | `display_name` / `description` | the human label and help text shown everywhere |
30
+
31
+ `table update` carries the table-level equivalents: `display_name`, `description`, `visibility_type` (`hidden` / `technical` / `cruft` — hides the whole table from the builder), `field_order`, and `entity_type`.
32
+
33
+ ## Foreign keys are the highest-leverage edit
34
+
35
+ A FK relationship is what makes a warehouse browsable. Set it in **two keys on the FK column**, in one PATCH:
36
+
37
+ ```bash
38
+ mb field update 1711 --body '{"semantic_type":"type/FK","fk_target_field_id":1684}' --profile <n> --json
39
+ ```
40
+
41
+ `1711` is `orders.customer_id`; `1684` is `customers.id` (which should itself be `type/PK`). Once set:
42
+
43
+ - Queries can pull columns from the related table with no explicit join — `["field", {"source-field": 1711}, 1682]` in MBQL (see `mbql`).
44
+ - Dashboard **linked filters** become possible (a State filter narrowing a City filter). **Linked filters read only these table-metadata FKs** — never a join you wrote inside a saved question — which is why a linked filter that "shows values it shouldn't" almost always means the FK isn't set in metadata. (See `dashboard`.)
45
+
46
+ Removing the `type/FK` semantic type auto-clears `fk_target_field_id`. Point a FK only at a field in the **same database** — v60+ rejects a cross-database target; v58–v59 accept it silently and leave a broken relationship.
47
+
48
+ ## Semantic types are labels, not casts
49
+
50
+ The commonest misconception. `semantic_type: "type/Quantity"` on a text column does **not** make it a number — it changes formatting and widget choice, nothing about the stored value. Sorting still sorts as text.
51
+
52
+ To genuinely change the type, use **`coercion_strategy`**, which casts `base_type` → an `effective_type` (e.g. a Unix-epoch integer read as a timestamp, or a numeric string read as a number):
53
+
54
+ ```bash
55
+ mb field update 42 --body '{"coercion_strategy":"Coercion/UNIXSeconds->DateTime"}' --profile <n> --json
56
+ ```
57
+
58
+ `base_type`, `effective_type`, and the physical `name` are **read-only** — set by warehouse sync, never editable here. For a durable transformation (splitting, combining, recomputing columns), build a `transform` rather than leaning on coercion.
59
+
60
+ The full semantic-type catalog — every value grouped by the base type it attaches to, plus the `has_field_values` and `visibility_type` value tables and the exact writable-key lists — is in `references/semantic-types.md` (`mb skills get metadata --full`).
61
+
62
+ ## Sync, scan, fingerprint — three different refreshes
63
+
64
+ When a column looks stale or missing, know which one you need (`db` verbs, mechanics in `core`):
65
+
66
+ - **Sync** (`mb db sync-schema <id> --wait`) — re-reads table/column **structure** (new tables, new columns, types). Run after a schema change.
67
+ - **Scan / rescan** (`mb db rescan-values <id>`) — refreshes the **distinct-value sets** behind dropdown filters. Run when a `list` column's values changed but its dropdown is stale.
68
+ - **Fingerprint** — value-distribution stats (min/max, null count) computed on a sample; drives smart defaults. Refreshed by sync; not a separate CLI verb.
69
+
70
+ A newly connected database or a missing expected column usually just needs a `sync-schema --wait` before you conclude anything.
71
+
72
+ ## Don't
73
+
74
+ - Don't expect a `semantic_type` to cast — it's a label. Use `coercion_strategy`, or a `transform`.
75
+ - Don't edit `name`, `base_type`, or `effective_type` — they're read-only from sync.
76
+ - Don't treat `visibility_type: hidden` as security — it only hides from the builder; native SQL still reads the column. Real restriction is a permissions concern, outside the CLI.
77
+ - Don't set a `type/City` / `type/State` filter and expect a map or clean dropdown if the values are inconsistent (abbreviations mixed with full names) — fix the values first (a `transform`), then the metadata.
78
+ - Don't blame the data when a dashboard linked filter misbehaves — check the FK is set here first.
@@ -0,0 +1,83 @@
1
+ # Metadata — full reference
2
+
3
+ The semantic-type catalog, the `has_field_values` / `visibility_type` value tables, and the exact writable-key lists for `field update` and `table update`. All values are strings in JSON (`"type/Currency"`). Unknown values are rejected — a new server type surfaces as a parse error, a deliberate signal.
4
+
5
+ ## Semantic types by base type
6
+
7
+ Assign the one that matches the column's meaning. Grouped by the base type it belongs on; assigning a numeric semantic type to a text column is legal but only affects formatting, not behavior.
8
+
9
+ **Relations (any type)**
10
+ `type/PK` (entity key) · `type/FK` (needs `fk_target_field_id`)
11
+
12
+ **Text — categorical & descriptive**
13
+ `type/Category` · `type/Enum` · `type/Name` · `type/Title` · `type/Description` · `type/Comment` · `type/Source`
14
+
15
+ **Text — communication & links**
16
+ `type/Email` · `type/URL` · `type/ImageURL` · `type/AvatarURL` · `type/IPAddress`
17
+
18
+ **Text — business entities**
19
+ `type/User` · `type/Author` · `type/Owner` · `type/Product` · `type/Company` · `type/Subscription`
20
+
21
+ **Location** (text unless noted)
22
+ `type/City` · `type/State` · `type/Country` · `type/ZipCode` · `type/Latitude` (numeric) · `type/Longitude` (numeric) · `type/Coordinate` (numeric) · `type/Address`
23
+
24
+ **Numeric — quantities & ratios**
25
+ `type/Quantity` · `type/Score` · `type/Percentage` · `type/Share` · `type/Duration`
26
+
27
+ **Numeric — money**
28
+ `type/Currency` · `type/Price` · `type/Income` · `type/Cost` · `type/Discount` · `type/GrossMargin`
29
+
30
+ **Temporal** (each has `…Timestamp` / `…Time` / `…Date` variants)
31
+ `type/CreationTimestamp` · `type/UpdatedTimestamp` · `type/JoinTimestamp` · `type/CancelationTimestamp` · `type/DeletionTimestamp` · `type/Birthdate`
32
+
33
+ **Structured**
34
+ `type/Structured` · `type/SerializedJSON` · `type/XML`
35
+
36
+ Location maps need clean inputs: lat/long must be numeric; `City`/`State`/`Country` must hold consistent, correctly-spelled values (and usually a scanned value set) to render region maps.
37
+
38
+ ## `has_field_values`
39
+
40
+ Controls the filter widget and whether Metabase stores a distinct-value set (scanned from the column).
41
+
42
+ | Value | Widget | Value set stored? | Use for |
43
+ | ----------- | ----------- | ------------------------------------------------- | ------------------------------------------------ |
44
+ | `list` | dropdown | yes (kept even if cardinality grows) | low-cardinality columns you want as a picker |
45
+ | `auto-list` | dropdown | yes (sync-assigned; reverts if too many distinct) | the default Metabase picks automatically |
46
+ | `search` | search box | no | high-cardinality text (names, emails) |
47
+ | `none` | plain input | no | free-form values |
48
+ | `null` | inferred | — | let sync decide (you rarely set this explicitly) |
49
+
50
+ A `list`/`auto-list` column's dropdown is refreshed by `mb db rescan-values <db-id>`.
51
+
52
+ ## `visibility_type` (field)
53
+
54
+ | Value | Effect |
55
+ | -------------- | --------------------------------------------------------------------------------------------------------------- |
56
+ | `normal` | default — visible everywhere |
57
+ | `details-only` | hidden in table views; shown in the single-record detail view (long text / JSON blobs) |
58
+ | `hidden` | removed from the query builder and data reference — **UI only, not access control** (native SQL still reads it) |
59
+ | `sensitive` | **queries touching the field error** — stronger than hidden |
60
+ | `retired` | auto-set for dropped columns; **queries error** |
61
+
62
+ ## `visibility_type` (table)
63
+
64
+ `hidden` · `technical` · `cruft` — all hide the table from the query builder and data reference (degrees of "don't show this"). `null` is normal.
65
+
66
+ ## Writable keys
67
+
68
+ Everything else on a field/table (physical `name`, `base_type`, `effective_type`, `active`, ids, timestamps) is read-only, set by sync.
69
+
70
+ **`PUT /api/field/:id` (`mb field update`)**
71
+ `display_name` · `description` · `caveats` · `points_of_interest` · `semantic_type` · `coercion_strategy` · `fk_target_field_id` · `visibility_type` · `has_field_values` · `settings` · `nfc_path` · `json_unfolding`
72
+
73
+ **`PUT /api/table/:id` (`mb table update`)**
74
+ `display_name` · `description` · `visibility_type` · `field_order` (`database` / `alphabetical` / `custom` / `smart`) · `entity_type` · `caveats` · `points_of_interest` · `show_in_getting_started` · `owner_user_id` / `owner_email` (ownership) · `data_layer` / `data_authority` / `data_source` (data-governance tiers) · `collection_id` (v62+)
75
+
76
+ ## Coercion strategies (common)
77
+
78
+ Cast a `base_type` to a more useful `effective_type`. The value must be compatible with the column's base type and is driver-dependent (an unsupported one 400s at update).
79
+
80
+ - Epoch numbers → datetime: `Coercion/UNIXSeconds->DateTime`, `Coercion/UNIXMilliSeconds->DateTime`, `Coercion/UNIXMicroSeconds->DateTime`, `Coercion/UNIXNanoSeconds->DateTime`
81
+ - ISO-8601 strings → temporal: `Coercion/ISO8601->DateTime`, `Coercion/ISO8601->Date`, `Coercion/ISO8601->Time`
82
+ - Numeric strings → number: `Coercion/String->Integer`, `Coercion/String->Float`
83
+ - Narrowing: `Coercion/Float->Integer`, `Coercion/DateTime->Date`