@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
@@ -6,51 +6,23 @@ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
6
6
 
7
7
  # git-sync (representations ↔ instance)
8
8
 
9
- Metabase content (cards, dashboards, transforms, snippets, collections, …) can live in a git repo as YAML and round-trip in and out of a Metabase instance via the `git-sync` verbs. The instance is configured with a `remote-sync-*` settings block (URL, branch, token, type read-only/read-write); the CLI drives the sync tasks against `/api/ee/remote-sync/*`.
9
+ Metabase content (cards, dashboards, transforms, snippets, collections, …) can live in a git repo as YAML and round-trip in and out of a Metabase instance via the `git-sync` verbs. The instance is configured with a `remote-sync-*` settings block (URL, branch, token, type read-only/read-write); the CLI drives the sync tasks against `/api/ee/remote-sync/*`. Only collections flagged for sync serialize; everything else is local-only.
10
10
 
11
- This skill covers the import/export workflow. The general flag conventions and auth setup live in the `core` skill (`mb skills get core`). To author content YAML by hand: the per-resource clause and settings shapes mirror the API form — query bodies follow the `mbql` skill, `visualization_settings` follow the `viz` skill — except the portable YAML uses **name-based** references (e.g. `[Sample Database, PUBLIC, ORDERS, TOTAL]`, and entity-ids for cross-entity FKs) where the API form uses numeric ids. For the on-disk folder layout, model new files on what the synced repo already contains.
11
+ This skill covers the import/export workflow. Flag conventions and auth setup live in `core` (`mb skills get core`). To author content YAML by hand: the per-resource clause and settings shapes mirror the API form — query bodies follow the `mbql` skill, `visualization_settings` follow the `visualization` skill — except the portable YAML uses **name-based** references (e.g. `[Sample Database, PUBLIC, ORDERS, TOTAL]`, and entity-ids for cross-entity FKs) where the API form uses numeric ids. For the on-disk folder layout, model new files on what the synced repo already contains.
12
12
 
13
- ## Adding / removing a directory (collection) to sync
14
-
15
- The set of directories under sync is governed by which **collections** carry `is_remote_synced: true`. Every collection so flagged serializes to its own folder under `collections/` in the repo; everything outside that set is local-only. The CLI exposes per-collection toggles that route to the underlying bulk endpoint (`PUT /api/ee/remote-sync/settings`):
16
-
17
- ```bash
18
- mb git-sync add-collection <collection-id> --profile <n> --json
19
- mb git-sync remove-collection <collection-id> --profile <n> --json
20
- ```
21
-
22
- `<collection-id>` is a **positive integer**. The bulk endpoint's schema is `pos-int? → boolean`; nano-id / `root` / `trash` refs (which `collection get` accepts) are not supported here. Get the id from `mb collection list --profile <n> --json` first.
23
-
24
- Both verbs return `{ success: true, task_id?: <id> }`. The optional `task_id` only appears when the toggle triggered a follow-up task (e.g., a finalization import after switching to read-only mode); for a normal add/remove in read-write, expect `{ success: true }` and nothing else.
25
-
26
- **Cascade.** A toggle on a parent cascades to every descendant by `location` prefix — `add-collection 4` flips `4` plus every collection nested under it. `remove-collection 4` is the symmetric inverse. There is no per-leaf-only mode.
27
-
28
- **Mode prerequisite.** The server rejects toggles while `remote-sync-type` is `:read-only` (the install default). If `mb git-sync add-collection 12` returns `Metabase returned 400 … Cannot change synced collections when remote-sync-type is read-only.`, switch first with:
29
-
30
- ```bash
31
- mb setting set remote-sync-type '"read-write"' --profile <n>
32
- ```
33
-
34
- (Mind the inner double quotes — `setting set` parses the value as strict JSON.) The server also rejects switching to `:read-only` while the Remote Sync collection is dirty; export or `--force` import first if you're going the other way.
35
-
36
- **Verifying the result.** The CLI's `Collection` schema doesn't yet expose `is_remote_synced`, so `collection get --json` won't show the flag. The pragmatic confirmation paths are:
37
-
38
- - `mb git-sync is-dirty --profile <n> --json` after editing a card in the now-synced collection — a `true` reading proves it's tracked.
39
- - The Metabase Admin UI's Remote Sync page renders the per-collection toggles.
40
-
41
- ## Read state before mutating
13
+ ## Precondition: read state before mutating
42
14
 
43
15
  Always run `status` (or `is-dirty` + `has-remote-changes`) before `import` or `export`. Importing on a dirty instance silently rejects unless you pass `--force`; exporting when the instance is behind the remote pushes a stale state.
44
16
 
45
17
  ```bash
46
18
  mb git-sync status --profile <n> --json # → branch, dirty, current task
47
- mb git-sync is-dirty --profile <n> --json # → {dirty: bool}; instance has unexported changes
48
- 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
49
21
  mb git-sync dirty --profile <n> --json # → list the dirty objects
50
22
  mb git-sync current-task --profile <n> --json # → in-flight task (or idle)
51
23
  ```
52
24
 
53
- **Clean up before exporting.** If you've created entities you intend to delete (a failed transform you're going to retry, a card you authored to test a body shape, a draft dashboard) — do the deletes _before_ the first `git-sync export`. Once committed, the cleanup needs a second commit, and the failed entity stays visible in `git log` forever. For the transform case specifically, prefer `transform update <id>` over `delete + create` so iteration never produces "broken-then-fixed" pairs in git history; see the `transform` skill, "Iterating on a failing transform".
25
+ **Clean up before exporting.** If you've created entities you intend to delete (a failed transform you're going to retry, a card you authored to test a body shape, a draft dashboard) — do the deletes _before_ the first `git-sync export`. Once committed, the cleanup needs a second commit, and the failed entity stays visible in `git log` forever. For transforms, prefer `transform update <id>` over delete + create (see the `transform` skill).
54
26
 
55
27
  ## Import (remote → instance)
56
28
 
@@ -71,8 +43,8 @@ Pulls the configured branch and applies it to the instance. Polls until the task
71
43
 
72
44
  Workflow:
73
45
 
74
- 1. `git-sync status` — confirm `dirty: false` (or `--force` is intended).
75
- 2. `git-sync has-remote-changes` — confirm 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.
76
48
  3. `git-sync import --branch <branch>` — runs to terminal status by default.
77
49
 
78
50
  ## Export (instance → remote)
@@ -93,9 +65,9 @@ Pushes Metabase-side changes back to the configured remote. `-m` is the commit m
93
65
  Workflow:
94
66
 
95
67
  1. **Branch guard** (below) — confirm the instance isn't tracking `main`/`master`, or that the user has explicitly accepted exporting to it.
96
- 2. `git-sync is-dirty` — confirm there's something to export.
68
+ 2. Read state (above) — confirm `is-dirty` reports there's something to export.
97
69
  3. `git-sync export -m "..."` — pushes and polls.
98
- 4. (Optional) `git-sync status` — verify `dirty: false` after.
70
+ 4. (Optional) `git-sync status` — verify `is_dirty: false` after.
99
71
 
100
72
  ### Branch guard: don't export to main/master without confirmation
101
73
 
@@ -131,6 +103,34 @@ mb git-sync cancel-task --profile <n> # cancel the in-flight task
131
103
 
132
104
  Use `wait` after `import --no-wait` / `export --no-wait`. Use `cancel-task` if a git-sync task hangs and you want to abandon it.
133
105
 
106
+ ## Adding / removing a directory (collection) to sync
107
+
108
+ The set of directories under sync is governed by which **collections** carry `is_remote_synced: true`. Every collection so flagged serializes to its own folder under `collections/` in the repo; everything outside that set is local-only. The CLI exposes per-collection toggles that route to the underlying bulk endpoint (`PUT /api/ee/remote-sync/settings`):
109
+
110
+ ```bash
111
+ mb git-sync add-collection <collection-id> --profile <n> --json
112
+ mb git-sync remove-collection <collection-id> --profile <n> --json
113
+ ```
114
+
115
+ `<collection-id>` is a **positive integer** (the bulk endpoint's schema is `pos-int? → boolean`; nano-id / `root` / `trash` refs are not supported). Get the id from `collection list` (see `core`).
116
+
117
+ Both verbs return `{ success: true, task_id?: <id> }`. The optional `task_id` only appears when the toggle triggered a follow-up task (e.g., a finalization import after switching to read-only mode); for a normal add/remove in read-write, expect `{ success: true }` and nothing else.
118
+
119
+ **Cascade.** A toggle on a parent cascades to every descendant by `location` prefix — `add-collection 4` flips `4` plus every collection nested under it. `remove-collection 4` is the symmetric inverse. There is no per-leaf-only mode.
120
+
121
+ **Mode prerequisite.** The server rejects toggles while `remote-sync-type` is `:read-only` (the install default). If `mb git-sync add-collection 12` returns `Metabase returned 400 … Cannot change synced collections when remote-sync-type is read-only.`, switch first with:
122
+
123
+ ```bash
124
+ mb setting set remote-sync-type '"read-write"' --profile <n>
125
+ ```
126
+
127
+ (`setting set` parses the value as strict JSON — mind the inner double quotes; see `core`.) The server also rejects switching to `:read-only` while the Remote Sync collection is dirty; export or `--force` import first if you're going the other way.
128
+
129
+ **Verifying the result.** The CLI's `Collection` schema doesn't yet expose `is_remote_synced`, so `collection get --json` won't show the flag. The pragmatic confirmation paths are:
130
+
131
+ - `mb git-sync is-dirty --profile <n> --json` after editing a card in the now-synced collection — a `true` reading proves it's tracked.
132
+ - The Metabase Admin UI's Remote Sync page renders the per-collection toggles.
133
+
134
134
  ## Don't (git-sync-specific)
135
135
 
136
136
  - Don't run `git-sync import --force` or `git-sync export --force` without explicit user confirmation. Both are lossy — `--force` import discards instance-side work, `--force` export overwrites the remote branch.
@@ -138,4 +138,4 @@ Use `wait` after `import --no-wait` / `export --no-wait`. Use `cancel-task` if a
138
138
  - Don't author content directly via `card create` / `transform create` and then assume `git-sync export` will commit it cleanly — the instance and repo can drift if you mix direct API writes with sync-tracked changes. If you do, follow direct writes immediately with `git-sync export -m "..."` to keep them in step.
139
139
  - Don't omit `-m` on `export` if the user wants a meaningful commit message — the default server-generated message is generic.
140
140
  - Don't `git-sync export` to `main`/`master` without explicit user confirmation — sync work is conventionally on a feature branch. See "Branch guard" above.
141
- - Don't reach for `mb setting set` to mark a collection as remote-synced — that endpoint writes single-key settings, not the bulk `collections` map. Use `mb git-sync add-collection <id>` / `mb git-sync remove-collection <id>` (see "Adding / removing a directory (collection) to sync" above), and remember the toggle cascades to descendants.
141
+ - Don't reach for `mb setting set` to mark a collection as remote-synced — that endpoint writes single-key settings, not the bulk `collections` map. Use `mb git-sync add-collection <id>` / `mb git-sync remove-collection <id>` (above), and remember the toggle cascades to descendants.
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: mbql
3
- description: Author Metabase MBQL 5 query bodies for the `mb` CLI - the only hand-authorable query format. Covers the JSON shape (lib/type mbql/query, flat numeric-id stages), the options-object-always-second clause rule, when lib/uuid is needed (optional - only to reference a clause), the print-schema/dry-run/run loop, where MBQL 5 is consumed (mb query, card dataset_query, transform source.query, measure/segment definition), the flat-vs-legacy-envelope footgun, joins and FK traversal, multi-stage pipelines, naming aggregation columns. Load when building or fixing an MBQL query by hand - "write an MBQL query", "create a card from MBQL", "the dataset_query is wrong", "fix the validation errors", "aggregate and group by", "join two tables", "month-over-month", or any `--dry-run` / `mb query` work.
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
- General flag conventions, body-input precedence, and output flags live in the `core` skill (`mb skills get core`).
13
+ General flag conventions, body-input precedence, output flags, `./.scratch`, and `mb uuid` mechanics live in `core` (`mb skills get core`).
14
14
 
15
15
  ## The shape
16
16
 
@@ -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
 
@@ -64,11 +64,7 @@ Set an explicit `lib/uuid` only when you must **reference a clause from elsewher
64
64
 
65
65
  (`AGG_UUID` is both the aggregation's own `lib/uuid` and the string the ref points at — one value, by string equality. Every other clause omits its UUID. Expression refs work the same way but key off the expression's `lib/expression-name` string, so expressions rarely need an explicit `lib/uuid`.)
66
66
 
67
- When you do need one, **always mint it with `mb uuid` — never write, guess, or copy a UUID yourself.** A hand-authored value is rejected pre-flight as not-a-v4 (`"a1"`, `"uuid-1"`, `"agg-uuid-001"` → `must be a UUID v4 (RFC 4122) — run \`mb uuid\``), or if it looks valid risks colliding with another clause. Only `mb uuid`gives genuine, unique v4s — mint just the few you reference (also covers native template-tag ids and any other`format: "uuid"` slot):
68
-
69
- ```bash
70
- mb uuid --count 2 --json # mint only the clauses you actually reference
71
- ```
67
+ When you do need one, **always mint it with `mb uuid` — never write, guess, or copy a UUID yourself.** A hand-authored value is rejected pre-flight as not-a-v4 (`"a1"`, `"uuid-1"`, `"agg-uuid-001"` → ``must be a UUID v4 (RFC 4122) — run `mb uuid` ``), or if it looks valid risks colliding with another clause. Mint just the few you reference (`mb uuid --count 2 --json`; this also covers native template-tag ids and any other `format: "uuid"` slot).
72
68
 
73
69
  ## Authoring loop: print-schema → dry-run → run
74
70
 
@@ -93,15 +89,15 @@ mb query --file q.json --profile <n> --json # 3. validate +
93
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.
94
90
  - `Duplicate :lib/uuid` → you reused a `lib/uuid`. Omit them (the server mints unique ones) or give each clause a distinct value.
95
91
 
96
- 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`).
97
93
 
98
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`.
99
95
 
100
- ## Where MBQL 5 is consumed
96
+ ## Where the query is consumed
101
97
 
102
- 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.
103
99
 
104
- | Command | MBQL 5 lives at | Notes |
100
+ | Command | The query lives at | Notes |
105
101
  | --------------------------------------- | ---------------------------------------------- | ------------------------------------------- |
106
102
  | `mb query` | the whole body | ad-hoc run against `/api/dataset` |
107
103
  | `card create` / `card update` | `dataset_query` | a **flat** `mbql/query` — see footgun below |
@@ -111,7 +107,7 @@ The same body and pre-flight apply everywhere a query is embedded. Each pre-flig
111
107
 
112
108
  ## Footgun: `dataset_query` is the flat mbql/query, not a legacy envelope
113
109
 
114
- 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**:
115
111
 
116
112
  ```json
117
113
  "dataset_query": {
@@ -122,16 +118,16 @@ The most common mistake. The legacy MBQL 4 shape `{ "type": "query", "database":
122
118
  }
123
119
  ```
124
120
 
125
- 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`.
126
122
 
127
- ## Other formats skip pre-flight
123
+ ## Legacy formats you may encounter
128
124
 
129
- 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:
130
126
 
131
- - **Legacy MBQL 4** — `{ "type": "query", "database": N, "query": { "source-table": T, … } }`
132
- - **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`).
133
129
 
134
- `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).
135
131
 
136
132
  ## Joins and FK traversal
137
133
 
@@ -197,7 +193,7 @@ Later stages address the first stage's aggregation by the `name` you gave it (`"
197
193
 
198
194
  ## Naming aggregation output columns
199
195
 
200
- 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:
201
197
 
202
198
  ```json
203
199
  ["count", { "name": "shipments_shipped", "display-name": "Shipments shipped" }]
@@ -213,11 +209,3 @@ mb skills path mbql # → the skill dir; then Read references/operator
213
209
  ```
214
210
 
215
211
  `mb query --print-schema` is the exhaustive-but-heavy fallback (the full JSON Schema, ~1600 lines). The cheat-sheet covers the vocabulary; the `--dry-run` loop settles any disagreement.
216
-
217
- ## Don't
218
-
219
- - Don't mint a `lib/uuid` for every clause — they're optional; omit them and the server fills them in. Mint (with `mb uuid`) only the clause you need to reference; never invent, hard-code, or copy a UUID (duplicates are rejected server-side).
220
- - Keep the options object in slot 1 of every clause — `[op, {options}, ...args]`, id last (`["field", {}, 1779]`). The legacy `["field", id, opts]` order (id second) is rejected pre-flight.
221
- - Don't wrap an MBQL 5 body in `{type:"query", query:…}` — `dataset_query` / `source.query` / `definition` is the flat `mbql/query`.
222
- - Don't author MBQL 4 by hand — build it in the UI and pull it with `… get <id> --full --json`.
223
- - Don't skip the `--dry-run` loop on a non-trivial query — it's free and exact.
@@ -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 —
@@ -11,6 +11,15 @@ when noted: an operator-specific option named in the row, or an explicit `lib/uu
11
11
  you mint to reference the clause. Field refs are numeric: `["field", {…}, <field-id>]`.
12
12
  Everything here passes `mb query --dry-run`; when in doubt, that loop is the authority.
13
13
 
14
+ **Contents**
15
+
16
+ - [Filter operators](#filter-operators) — Logical, Comparison, Null/empty, String match, Temporal, Segment
17
+ - [Aggregation functions](#aggregation-functions) — including naming and the `offset` window function
18
+ - [Expression operators](#expression-operators) — Arithmetic, Math, String, Temporal, Type conversion, Conditional
19
+ - [References (within clauses)](#references-within-clauses) — field, expression, aggregation
20
+ - [Field option: temporal bucketing](#field-option-temporal-bucketing)
21
+ - [Field option: binning](#field-option-binning)
22
+
14
23
  > For relative date filters, prefer **`time-interval`** / **`relative-time-interval`**
15
24
  > (below). `relative-datetime` / `absolute-datetime` literals (e.g. from a UI-built
16
25
  > query) also work.
@@ -231,8 +240,9 @@ positional arg is the default.)
231
240
  - Truncation: `default`, `millisecond`, `second`, `minute`, `hour`, `day`, `week`,
232
241
  `month`, `quarter`, `year`.
233
242
  - Extraction (returns an integer): `minute-of-hour`, `hour-of-day`, `day-of-week`,
234
- `day-of-week-iso`, `day-of-month`, `day-of-year`, `week-of-year`, `week-of-year-iso`,
235
- `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.)
236
246
 
237
247
  ```json
238
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`
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: native-sql
3
+ description: Author native SQL queries with parameters (filter widgets) for the `mb` CLI. Native SQL is a query whose single stage is raw SQL (`mbql.stage/native`) instead of structured MBQL — the same query envelope, so it is pre-flight-validated and round-trips. Covers the shape, the four template-tag kinds (raw variable, field filter, snippet, card reference), the variable / optional-block / snippet / card-reference syntax, the field-filter-vs-variable decision, the field ref in a field-filter dimension, wiring a tag to a dashboard filter, and running with values. Triggers — "write a SQL question", "add a filter widget to my SQL", "parameterize this query", "use a field filter", "reference a saved question in SQL", "why does my variable return no rows".
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
5
+ ---
6
+
7
+ # Native SQL
8
+
9
+ **Prefer a structured query.** Native SQL is a Metabase query whose single stage is raw SQL (`mbql.stage/native`) instead of structured MBQL (`mbql.stage/mbql`) — both are the same query envelope (`mbql`). Reach for a native stage only when a structured query genuinely can't express it — engine-specific functions, CTEs, window logic beyond `offset`, hairy hand-tuned SQL — or when the user asks for SQL. If you can write it as a structured stage, do.
10
+
11
+ General flag conventions, body-input precedence, `./.scratch`, and `mb uuid` mechanics live in `core` (`mb skills get core`).
12
+
13
+ ## The shape
14
+
15
+ A native `dataset_query` is a query with one **native stage** — the `lib/type: "mbql/query"` envelope, a numeric `database`, and a single `mbql.stage/native` stage carrying the SQL string (`native`) plus a `template-tags` map:
16
+
17
+ ```json
18
+ {
19
+ "lib/type": "mbql/query",
20
+ "database": 1,
21
+ "stages": [
22
+ {
23
+ "lib/type": "mbql.stage/native",
24
+ "native": "SELECT count(*) FROM orders WHERE {{status}} AND total > {{min_total}}",
25
+ "template-tags": { "status": { … }, "min_total": { … } }
26
+ }
27
+ ]
28
+ }
29
+ ```
30
+
31
+ This is the form a card stores and returns — author it. The CLI **pre-flight-validates** it — the envelope, the template-tag shapes, the field refs — through the usual `--print-schema → --dry-run → run` loop (`mbql`), and a saved card **round-trips** in exactly this shape: `mb card get <id> --full --json`, edit the `stages[0].native` string, send it straight back. Only the SQL string is opaque to pre-flight — a **SQL** syntax error surfaces just when you run it, not at `--dry-run`. A parameterless query needs no `template-tags` — just the `native` string.
32
+
33
+ You may see an older flat form in cards created long ago — `{database, type:"native", native:{query}}`. The server still accepts it (it normalizes to the above) but it skips pre-flight and doesn't round-trip — **don't author it**.
34
+
35
+ ## Parameters are template tags
36
+
37
+ Every `{{name}}` in the SQL must have a matching entry in the stage's `template-tags`, keyed by that name. **The three must agree exactly:** the `{{name}}` in SQL = the map key = the entry's `"name"` field. Names are case-sensitive (`{{Cat}}` ≠ `{{cat}}`). A `{{name}}` with no entry fails at run time; an unused entry is ignored.
38
+
39
+ Four kinds of tag, by `type`:
40
+
41
+ | Kind | `type` | SQL syntax | What it is |
42
+ | ------------------ | -------------------------------- | ----------------------------- | -------------------------------------------- |
43
+ | **Raw variable** | `text` `number` `date` `boolean` | `WHERE total > {{min_total}}` | a literal substituted into the SQL |
44
+ | **Field filter** | `dimension` | `WHERE {{status}}` (bare!) | a smart filter widget bound to a real column |
45
+ | **Snippet** | `snippet` | `{{snippet: Active Rows}}` | a reusable SQL fragment (`mb snippet`) |
46
+ | **Card reference** | `card` | `{{#42}}` or `{{#42-slug}}` | another saved query, as a subquery |
47
+
48
+ Give each tag an `id` — mint one per tag with `mb uuid` (never hand-write one). Wrap any clause that should be droppable when its value is empty in **`[[ … ]]`**, keyword and all: `[[AND {{status}}]]`, not `AND [[{{status}}]]`. Only one level of nesting; a query using several optional `[[AND …]]` blocks needs a real `WHERE` first (e.g. `WHERE true [[AND {{a}}]] [[AND {{b}}]]`).
49
+
50
+ ## The decision that matters: field filter vs. raw variable
51
+
52
+ This is the call agents get wrong. Default to a **field filter** whenever the tag filters a real table column.
53
+
54
+ - A **raw variable** (`{{x}}`) is a dumb literal splice. You write the operator yourself: `WHERE status = {{x}}`. It gives a plain text/number/date box, no dropdown, no date picker, and it's what powers computed bits that aren't a column (`LIMIT {{n}}`, a threshold, an interpolated identifier).
55
+ - A **field filter** (`type: dimension`) is a smart widget bound to a column via `dimension`. You write it **bare** — `WHERE {{status}}` — and Metabase expands it to the right SQL (`status IN (...)`, a `BETWEEN` for dates, etc.), driving a dropdown/date-picker sourced from the column's values. Writing `WHERE status = {{status}}` around a field filter **breaks the expansion** — the single most common native-SQL bug.
56
+
57
+ Field filters only bind to a **real, connected database column** — not an expression, not an aggregate, not a subquery/CTE column. If the thing you're filtering isn't a physical column, it has to be a raw variable.
58
+
59
+ ## Template-tag bodies (the two you author most)
60
+
61
+ **Field filter** — `dimension` binds a column, `widget-type` picks the widget:
62
+
63
+ ```json
64
+ "status": {
65
+ "id": "9ddca4ca-3906-83fd-bc6b-8480ae9ab05e",
66
+ "name": "status",
67
+ "display-name": "Status",
68
+ "type": "dimension",
69
+ "dimension": ["field", {}, 141],
70
+ "widget-type": "string/="
71
+ }
72
+ ```
73
+
74
+ **`dimension` is a field ref: `["field", {options}, <field-id>]`** — options object **second**, id **third**, exactly the `mbql` rule. The legacy `["field", <id>, null]` shape (id first) that the UI and older docs show is **rejected by pre-flight** here (`must be the field options object`). Send `{}` for the options; the server fills in a `lib/uuid`. The field id comes from `table get <id> --include fields`.
75
+
76
+ `widget-type` must suit the column's type and is a closed enum (same vocabulary as dashboard filter `type`): string ops (`string/=`, `string/!=`, `string/contains`, `string/starts-with`, …), number ops (`number/=`, `number/between`, `number/>=`, …), dates (`date/all-options`, `date/range`, `date/relative`, `date/month-year`, …), plus `category`, `id`, `boolean/=`, and the `location/*` set. Text column → a `string/*` or `category`; datetime → a `date/*`; numeric → a `number/*`. `date/all-options` is the most flexible date widget.
77
+
78
+ **Raw variable** — no `dimension`, no `widget-type`:
79
+
80
+ ```json
81
+ "min_total": {
82
+ "id": "35f1ecd4-d622-6d14-54be-750c498043cb",
83
+ "name": "min_total",
84
+ "display-name": "Minimum total",
85
+ "type": "number",
86
+ "required": true,
87
+ "default": "50"
88
+ }
89
+ ```
90
+
91
+ Snippet and card-reference bodies (and the full field list for every kind) are in `references/template-tags.md` — load it when you need them (`mb skills get native-sql --full`).
92
+
93
+ ## Snippets and card references
94
+
95
+ - **Snippet** (`{{snippet: Name}}`): a shared SQL fragment stored via `mb snippet create --body '{"name":"Active Rows","content":"status = '\''active'\''"}'` — `content` is bare SQL, no wrapping. Reuse it across queries; edit it once. The tag body carries `snippet-id` + `snippet-name`.
96
+ - **Card reference** (`{{#42}}`): inlines another saved query as a subquery — `SELECT * FROM {{#42}}` or `WITH x AS {{#42}} …`. The tag body carries `card-id`.
97
+ - **Neither takes a parameter value.** A referenced card runs with **its own saved defaults** — you can't override its parameters from the parent query. Snippets are static text. Only raw variables and field filters are user-fillable.
98
+
99
+ ## Wiring, defaults, and running
100
+
101
+ **Give a tag a default or a dropdown source** by declaring it in the card's `parameters` array (alongside `dataset_query`) — this is where `default`, and a `values_source_type` (`static-list` / `card`) live. Its `target` links back to the tag: `["dimension", ["template-tag", "status"]]` for a field filter, `["variable", ["template-tag", "min_total"]]` for a raw variable. (Metabase auto-derives basic `parameters` from the template tags, so you only declare them to add defaults or a value source.)
102
+
103
+ **Run a saved card with values** via `card query`, whose `--parameters` is a JSON array of `{type, target, value}` — same `target` grammar:
104
+
105
+ ```bash
106
+ mb card query 12 --parameters '[{"type":"string/=","target":["dimension",["template-tag","status"]],"value":"active"}]' --json
107
+ ```
108
+
109
+ **Expose it as a dashboard filter** by mapping a dashboard parameter to the tag on the dashcard — the mapping `target` is the same `["dimension",["template-tag","status"]]` (field filter) or `["variable",["template-tag","status"]]` (raw variable). The dashboard-side mechanics (the `parameters` array and `parameter_mappings`) live in `core`.
110
+
111
+ ## Don't
112
+
113
+ - Don't wrap a field filter in an operator (`WHERE col = {{ff}}`) — write it bare (`WHERE {{ff}}`).
114
+ - Don't write the field-filter `dimension` in the legacy `["field", id, null]` shape — use `["field", {}, id]` (options second).
115
+ - Don't author the flat `{type:"native", …}` form — send the native stage above.
116
+ - Don't use native SQL for DDL or multiple statements — the editor is read-only, single-statement; `CREATE`/`UPDATE`/`;`-chained SQL is unsupported. To materialize a table, use a `transform`.
117
+ - Don't expect `[[ ]]` to save you from a case/type mismatch — `WHERE plan = {{p}}` returns zero rows on a case-sensitive engine if the value's case is off; that's a value problem, not syntax.
118
+ - Don't reach for native when a structured query fits — you lose the engine-independence and readability of an `mbql.stage/mbql` stage.