@metabase/cli 0.1.0 → 0.1.2

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 (187) hide show
  1. package/README.md +972 -57
  2. package/dist/add-collection-DwxQDXzL.mjs +54 -0
  3. package/dist/add-collection-SL08iMub.mjs +11 -0
  4. package/dist/api-key-BktzvPb7.mjs +13 -0
  5. package/dist/{archive-CsWeHXle.mjs → archive-C1mF-9Kj.mjs} +7 -4
  6. package/dist/archive-CLWtbvvH.mjs +44 -0
  7. package/dist/archive-Cq4WKmJt.mjs +44 -0
  8. package/dist/archive-kYoy5LK5.mjs +39 -0
  9. package/dist/auth-DfYkakP3.mjs +19 -0
  10. package/dist/{body-Dv9hQ0Qk.mjs → body-rDrR-C1c.mjs} +3 -2
  11. package/dist/{branches-BujtceGr.mjs → branches-CH2UcCpX.mjs} +8 -6
  12. package/dist/cancel-CgLZcItQ.mjs +56 -0
  13. package/dist/{cancel-task-CT2xUMRg.mjs → cancel-task-DcYrFsM6.mjs} +9 -7
  14. package/dist/{card-CsXk8T6A.mjs → card-CQxvHeyP.mjs} +34 -15
  15. package/dist/card-ZCGU2JEh.mjs +20 -0
  16. package/dist/cards-C4NIaERo.mjs +37 -0
  17. package/dist/cli.mjs +33 -14
  18. package/dist/collection-B3sPXRLs.mjs +163 -0
  19. package/dist/collection-D_uFLIAS.mjs +19 -0
  20. package/dist/create-BUCLNqiN.mjs +48 -0
  21. package/dist/create-CB0Yp__0.mjs +66 -0
  22. package/dist/create-CNvd5T8h.mjs +48 -0
  23. package/dist/create-Cbh1cGj9.mjs +48 -0
  24. package/dist/create-CzfNOhOF.mjs +48 -0
  25. package/dist/create-DU0ZhnZu.mjs +44 -0
  26. package/dist/create-Dh0p-c2Y.mjs +44 -0
  27. package/dist/create-DvrVZ2hS.mjs +125 -0
  28. package/dist/create-QgN369N5.mjs +50 -0
  29. package/dist/{create-B8ektf-R.mjs → create-bqc_rmix.mjs} +8 -6
  30. package/dist/{create-branch-goZBTNnr.mjs → create-branch-BJFH9Hda.mjs} +9 -7
  31. package/dist/credentials-DTP1xuKz.mjs +85 -0
  32. package/dist/{current-task-DBjRNCFq.mjs → current-task-z_TiJ0kt.mjs} +9 -7
  33. package/dist/dashboard-CnMD04PQ.mjs +163 -0
  34. package/dist/dashboard-G1-dGLUR.mjs +20 -0
  35. package/dist/database-DQkUxTLd.mjs +17 -0
  36. package/dist/database-vvig8k4x.mjs +51 -0
  37. package/dist/db-CBaEfumR.mjs +22 -0
  38. package/dist/{delete-8vGU35r3.mjs → delete-CVYII8mq.mjs} +7 -5
  39. package/dist/{delete-B27KLF5X.mjs → delete-DeZQ1r9w.mjs} +7 -5
  40. package/dist/{delete-runtime-Byr60cR3.mjs → delete-runtime-BMzvfj_B.mjs} +4 -4
  41. package/dist/{delete-table-BNaJ_gA4.mjs → delete-table-ZiR9-ndv.mjs} +7 -5
  42. package/dist/deprovision-BhD3J-Am.mjs +61 -0
  43. package/dist/{dirty-aNUuph4I.mjs → dirty-D9agt7Os.mjs} +8 -6
  44. package/dist/docker-CHpV8PRz.mjs +612 -0
  45. package/dist/eid-B5wawMmO.mjs +13 -0
  46. package/dist/{export-QDkuuzSE.mjs → export-Bfk7JAlR.mjs} +30 -23
  47. package/dist/field-B3gvaqpK.mjs +278 -0
  48. package/dist/field-BDJ1pEgr.mjs +18 -0
  49. package/dist/fields-7ByLsxLg.mjs +38 -0
  50. package/dist/flag-pair-DtR1AiBQ.mjs +17 -0
  51. package/dist/get-BE6Izpus.mjs +36 -0
  52. package/dist/get-C3CcAJGg.mjs +49 -0
  53. package/dist/{get-DI_IJvgk.mjs → get-CQGeF-eP.mjs} +6 -4
  54. package/dist/get-D2m4jhwT.mjs +53 -0
  55. package/dist/{get-BGBIzMKY.mjs → get-DKy3DAJX.mjs} +6 -4
  56. package/dist/{get-COXHplHP.mjs → get-DUSR5i99.mjs} +7 -5
  57. package/dist/get-DikegGzi.mjs +36 -0
  58. package/dist/get-StkjKuh0.mjs +40 -0
  59. package/dist/get-bYc7eGYe.mjs +36 -0
  60. package/dist/{get-Cl8-IauC.mjs → get-cuHp9-6U.mjs} +7 -4
  61. package/dist/{get-i6LWOByV.mjs → get-gOT_RarI.mjs} +6 -4
  62. package/dist/get-run-D59Yqaoh.mjs +36 -0
  63. package/dist/get-tISo-cmg.mjs +41 -0
  64. package/dist/git-sync-BiTWfLgY.mjs +28 -0
  65. package/dist/{has-remote-changes-hjKoQuRy.mjs → has-remote-changes-B1TciDVD.mjs} +8 -6
  66. package/dist/{import-HJsSKRYx.mjs → import-DnnmmJbp.mjs} +11 -9
  67. package/dist/{input-Dojr-RTw.mjs → input-ikCiip6x.mjs} +2 -1
  68. package/dist/is-dirty-DClGFOGV.mjs +10 -0
  69. package/dist/{is-dirty-1Qy7hiHB.mjs → is-dirty-DlfX7e39.mjs} +5 -4
  70. package/dist/items-DQFQSpjF.mjs +77 -0
  71. package/dist/{key-DBxPSFwi.mjs → key-NDEARu2L.mjs} +1 -1
  72. package/dist/{license-MoWse3ZI.mjs → license-DBh13sc8.mjs} +3 -3
  73. package/dist/list-4kYCGv01.mjs +32 -0
  74. package/dist/list-9AOWhxqp.mjs +61 -0
  75. package/dist/{list-Bk6RsbJl.mjs → list-BwjqQ6pp.mjs} +5 -3
  76. package/dist/{list-C_PRdL5e.mjs → list-CP5RNjO6.mjs} +7 -5
  77. package/dist/{list-C8tdLOH5.mjs → list-Cy0VhXQs.mjs} +5 -3
  78. package/dist/list-D067ZSE5.mjs +47 -0
  79. package/dist/list-DAZP-IM5.mjs +32 -0
  80. package/dist/list-DJN-OvTZ.mjs +52 -0
  81. package/dist/list-DQj-QJAs.mjs +40 -0
  82. package/dist/list-Di529OJD.mjs +55 -0
  83. package/dist/{list-C4Ajrw8f.mjs → list-DlKzgnqo.mjs} +6 -3
  84. package/dist/list-GFfR9SuT.mjs +32 -0
  85. package/dist/{list-CWt3fqrZ.mjs → list-iFVEdi2J.mjs} +5 -3
  86. package/dist/{login-C9WTwNn6.mjs → login-DxgkosGx.mjs} +30 -9
  87. package/dist/{logout-oLszGCOg.mjs → logout-BlVwqBog.mjs} +7 -6
  88. package/dist/logs-CudNEkT4.mjs +58 -0
  89. package/dist/{manifest-CAdjQYH8.mjs → manifest-Dv5B9Blc.mjs} +3 -7
  90. package/dist/measure-BEQfnLdN.mjs +67 -0
  91. package/dist/measure-C7SbdYQk.mjs +19 -0
  92. package/dist/metadata-B2Td415K.mjs +38 -0
  93. package/dist/metadata-BTJAFVvZ.mjs +37 -0
  94. package/dist/{package-BGfw4ZWJ.mjs → package-DV6Asqim.mjs} +7 -1
  95. package/dist/paginate-CTSfuYiF.mjs +49 -0
  96. package/dist/parse-id-B38zTlYs.mjs +12 -0
  97. package/dist/parse-ref-DGvh4aDn.mjs +17 -0
  98. package/dist/parse-schemas-Ds-cVE-O.mjs +12 -0
  99. package/dist/{poll-ILanYysl.mjs → poll-Bh6oAifO.mjs} +2 -1
  100. package/dist/{poll-task-DbpsiQhl.mjs → poll-task-vPwV31Fs.mjs} +8 -7
  101. package/dist/predicates-DiIiS3k7.mjs +153 -0
  102. package/dist/preflight-DxJb-hUV.mjs +91 -0
  103. package/dist/{prompt-DpT8yAVy.mjs → prompt-Bf3DQ-qE.mjs} +1 -1
  104. package/dist/provision-B-I0zuDe.mjs +77 -0
  105. package/dist/ps-BmYQYC7t.mjs +10 -0
  106. package/dist/ps-CaiOFCv2.mjs +78 -0
  107. package/dist/query-BtF1yWZZ.mjs +90 -0
  108. package/dist/{query-PihYi-UZ.mjs → query-jmfqaXRP.mjs} +38 -13
  109. package/dist/remove-C2iv0g03.mjs +98 -0
  110. package/dist/remove-collection-DhZghaZy.mjs +38 -0
  111. package/dist/{remove-B2hVYn1v.mjs → remove-xskleeru.mjs} +6 -5
  112. package/dist/render-DXv-D6fU.mjs +182 -0
  113. package/dist/rescan-values-DW6u90ep.mjs +43 -0
  114. package/dist/revision-message-flag-CWQbKhdl.mjs +11 -0
  115. package/dist/{run-C2so6Qp6.mjs → run-DxVzhcF3.mjs} +27 -36
  116. package/dist/runs-BOHk1XnM.mjs +54 -0
  117. package/dist/{runtime-C9CEZhcn.mjs → runtime-cwBS8wwK.mjs} +428 -442
  118. package/dist/schema-tables-CcFbY_jN.mjs +45 -0
  119. package/dist/schemas-DZmv_V62.mjs +47 -0
  120. package/dist/{search-CopOytXY.mjs → search-CYMuc7Fg.mjs} +6 -19
  121. package/dist/segment-BMrUBz94.mjs +70 -0
  122. package/dist/segment-Df4pfjco.mjs +19 -0
  123. package/dist/{set-BcF7M1GQ.mjs → set-B_rrVwU4.mjs} +6 -4
  124. package/dist/{set-CbibegpA.mjs → set-CbGfQ7Ye.mjs} +8 -6
  125. package/dist/{setting-U3NtBMFo.mjs → setting-DqZY9NXP.mjs} +3 -3
  126. package/dist/setup-DxmcAorA.mjs +71 -0
  127. package/dist/snippet-CwSHjQyn.mjs +19 -0
  128. package/dist/snippet-Dw0Sjzkr.mjs +64 -0
  129. package/dist/start-Cn0epTks.mjs +380 -0
  130. package/dist/{stash-DOBbYozC.mjs → stash-BFZIl9F4.mjs} +9 -7
  131. package/dist/{status-Buf1ZbNR.mjs → status-BjCeJNLp.mjs} +10 -8
  132. package/dist/{status-CUcs8XBH.mjs → status-FDIDmqvM.mjs} +4 -2
  133. package/dist/{status-D1F5XHae.mjs → status-UALK3OJl.mjs} +4 -2
  134. package/dist/stop-DUwrDWw8.mjs +81 -0
  135. package/dist/summary-CS4UGiFJ.mjs +41 -0
  136. package/dist/sync-schema-IrHdJxmX.mjs +43 -0
  137. package/dist/{table-Cfk7oSvw.mjs → table-B-PYcgGb.mjs} +22 -9
  138. package/dist/table-Cdr5bKp1.mjs +19 -0
  139. package/dist/transform-CeZusR_w.mjs +24 -0
  140. package/dist/{transform-B5uRpg1G.mjs → transform-IEX4Mx3X.mjs} +56 -2
  141. package/dist/transform-job-BOn9-CGa.mjs +19 -0
  142. package/dist/{transform-job-C7QXWTVE.mjs → transform-job-Csr86muI.mjs} +7 -0
  143. package/dist/translate-B__zbDKm.mjs +111 -0
  144. package/dist/tree-Mh0uQ_Wy.mjs +32 -0
  145. package/dist/update-1Di9hbPo.mjs +56 -0
  146. package/dist/update-B5_pp6Jj.mjs +56 -0
  147. package/dist/update-B9DBMo30.mjs +52 -0
  148. package/dist/update-BfBsM_y1.mjs +56 -0
  149. package/dist/update-Bw0WZix_.mjs +73 -0
  150. package/dist/update-Cp1789qq.mjs +52 -0
  151. package/dist/update-D2VI_5cy.mjs +57 -0
  152. package/dist/update-D8GwQTcL.mjs +59 -0
  153. package/dist/{update-CL8tRbxr.mjs → update-Masp5WeT.mjs} +9 -7
  154. package/dist/update-dashcard-CNiQw1MD.mjs +71 -0
  155. package/dist/update-j9vgemKR.mjs +51 -0
  156. package/dist/url-GFM76VIK.mjs +54 -0
  157. package/dist/uuid-Uif0lNk8.mjs +47 -0
  158. package/dist/validate-DCYx6jdL.mjs +1496 -0
  159. package/dist/validate-query-B07oGG4K.mjs +37 -0
  160. package/dist/values-DrwNHUAI.mjs +36 -0
  161. package/dist/{wait-Bugr9eXD.mjs → wait-BoKk8CJy.mjs} +10 -8
  162. package/dist/wait-DO7tS7NI.mjs +19 -0
  163. package/dist/wait-flags-CjX2sEGm.mjs +35 -0
  164. package/dist/workspace-CyEX40D-.mjs +24 -0
  165. package/dist/workspace-DVuqKJGG.mjs +72 -0
  166. package/dist/workspace-credentials-B6BL-X0d.mjs +139 -0
  167. package/package.json +7 -1
  168. package/dist/auth-BF7IjZIH.mjs +0 -18
  169. package/dist/card-_Ta7zdYe.mjs +0 -19
  170. package/dist/create-CI2Cunq5.mjs +0 -38
  171. package/dist/create-DdbU3TLX.mjs +0 -42
  172. package/dist/database-PA9Goi25.mjs +0 -33
  173. package/dist/db-DMghzgb6.mjs +0 -17
  174. package/dist/field-C8IVs6rp.mjs +0 -76
  175. package/dist/field-DaYo_90x.mjs +0 -13
  176. package/dist/get-Cwpj7lDe.mjs +0 -35
  177. package/dist/get-Dh_acl8q.mjs +0 -34
  178. package/dist/is-dirty-DpKn9HJp.mjs +0 -8
  179. package/dist/list-CBSBHtK-.mjs +0 -38
  180. package/dist/parse-id-BhmmfyCP.mjs +0 -14
  181. package/dist/sync-BPyGXfUk.mjs +0 -26
  182. package/dist/table-D7nJt7JO.mjs +0 -16
  183. package/dist/transform-UbyewMxY.mjs +0 -21
  184. package/dist/transform-job-CrYkr-Ma.mjs +0 -19
  185. package/dist/update-DU2oU2j-.mjs +0 -49
  186. /package/dist/{body-flags-BUA9XV1u.mjs → body-flags-BK7J6Daz.mjs} +0 -0
  187. /package/dist/{setting-26ckqHAP.mjs → setting-CTaAeMci.mjs} +0 -0
package/README.md CHANGED
@@ -63,6 +63,19 @@ metabase auth status --profile staging
63
63
  | `--profile <name>` | Profile to inspect (default: `default`). |
64
64
  | `--json` | Emit JSON. Auto-enabled on non-TTY. |
65
65
 
66
+ ### `metabase auth list`
67
+
68
+ List configured authentication profiles. The index is maintained at `<configDir>/profiles.json` and updated on every `auth login` / `auth logout`. Profiles whose URL/API key were stored in the OS keychain before the index existed are picked up by a one-time backfill from `credentials.json`; profiles that exist only in the keyring (no entry in `credentials.json`) appear after the next `auth login` or `auth logout` against them.
69
+
70
+ ```sh
71
+ metabase auth list
72
+ metabase auth list --json
73
+ ```
74
+
75
+ | Flag | Description |
76
+ | -------- | ----------------------------------- |
77
+ | `--json` | Emit JSON. Auto-enabled on non-TTY. |
78
+
66
79
  ### `metabase auth logout`
67
80
 
68
81
  Clear stored credentials for a profile.
@@ -72,10 +85,10 @@ metabase auth logout --yes
72
85
  metabase auth logout --profile staging --yes
73
86
  ```
74
87
 
75
- | Flag | Description |
76
- | ------------------ | --------------------------------------- |
77
- | `--profile <name>` | Profile to clear (default: `default`). |
78
- | `--yes` | Skip confirmation. Required on non-TTY. |
88
+ | Flag | Description |
89
+ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
90
+ | `--profile <name>` | Profile to clear (default: `default`). |
91
+ | `--yes` | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
79
92
 
80
93
  ## License
81
94
 
@@ -113,9 +126,9 @@ Clear the stored license.
113
126
  metabase license remove --yes
114
127
  ```
115
128
 
116
- | Flag | Description |
117
- | ------- | --------------------------------------- |
118
- | `--yes` | Skip confirmation. Required on non-TTY. |
129
+ | Flag | Description |
130
+ | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
131
+ | `--yes` | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
119
132
 
120
133
  Common output flags (`--json`, `--format`, `--detail`, `--fields`, `--max-bytes`) are accepted; the result payload is rendered through the standard output layer.
121
134
 
@@ -162,9 +175,9 @@ Same `--body` / `--file` resolution as `create`. Stdin is auto-detected when not
162
175
  metabase transform delete 1 --yes
163
176
  ```
164
177
 
165
- | Flag | Description |
166
- | ------- | --------------------------------------- |
167
- | `--yes` | Skip confirmation. Required on non-TTY. |
178
+ | Flag | Description |
179
+ | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
180
+ | `--yes` | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
168
181
 
169
182
  ### `metabase transform run <id>`
170
183
 
@@ -181,6 +194,38 @@ metabase transform run 1 --wait --json
181
194
  | `--timeout <ms>` | Polling timeout in ms (default 600000). Used with `--wait`. |
182
195
  | `--interval <ms>` | Polling interval in ms (default 2000). Used with `--wait`. |
183
196
 
197
+ ### `metabase transform cancel <id>`
198
+
199
+ Cancel the currently-running run for a transform. Exits 0 with `{canceled: true, id}` on success; exits 1 with a 404 if the transform has no active run.
200
+
201
+ ```sh
202
+ metabase transform cancel 1
203
+ metabase transform cancel 1 --json
204
+ ```
205
+
206
+ ### `metabase transform get-run <run-id>`
207
+
208
+ Fetch a single run by run id (not transform id). Same compact / `--full` projection convention as `transform get`.
209
+
210
+ ```sh
211
+ metabase transform get-run 1 --json
212
+ ```
213
+
214
+ ### `metabase transform runs`
215
+
216
+ List recent transform runs across all transforms, or filter to one. Drains all pages by default; pass `--limit` to cap.
217
+
218
+ ```sh
219
+ metabase transform runs
220
+ metabase transform runs --transform-id 1 --json
221
+ metabase transform runs --limit 10 --json
222
+ ```
223
+
224
+ | Flag | Description |
225
+ | --------------------- | --------------------------------------------------- |
226
+ | `--transform-id <id>` | Filter to runs of a single transform id. |
227
+ | `--limit <n>` | Cap total runs returned (default: drain all pages). |
228
+
184
229
  ## Transform jobs
185
230
 
186
231
  CRUD on `/api/transform-job`. Bodies for `create` / `update` follow the same `--body` / `--file` / stdin pattern as transforms.
@@ -220,9 +265,183 @@ metabase transform-job update 1 --body '{"schedule":"0 0 6 * * ?"}'
220
265
  metabase transform-job delete 1 --yes
221
266
  ```
222
267
 
223
- | Flag | Description |
224
- | ------- | --------------------------------------- |
225
- | `--yes` | Skip confirmation. Required on non-TTY. |
268
+ | Flag | Description |
269
+ | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
270
+ | `--yes` | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
271
+
272
+ ## Databases
273
+
274
+ Read warehouse metadata from `/api/database`. The `db` group exposes the full database list, the per-database record, schema and table inspection, the two manual-sync triggers, and (rarely useful) full-warehouse rollup endpoints.
275
+
276
+ `db` is aliased to `database`.
277
+
278
+ > **Agent traversal:** prefer the granular path — `db list` → `db schemas <db-id>` → `db schema-tables <db-id> <schema>` → `table get <table-id> --include fields`. On a real warehouse (dozens of schemas, hundreds of tables, dozens of fields per table) the rollup commands (`db metadata`, `db get --include tables.fields`, `db list --include tables`) return megabytes of JSON and exhaust the agent context. Reach for them only on small/dev warehouses where you know the size up front.
279
+
280
+ ### `metabase db list`
281
+
282
+ ```sh
283
+ metabase db list
284
+ metabase db list --json
285
+ metabase db list --saved --json
286
+ metabase db list --include tables --full --json # rollup: every db with its full table list
287
+ ```
288
+
289
+ | Flag | Description |
290
+ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
291
+ | `--include <which>` | Hydrate related entities. Currently only `tables` is supported (each database is returned with its `tables`). On real warehouses this returns hundreds of table records per db — use the granular traversal instead. |
292
+ | `--saved` | Include the Saved Questions virtual database in the list. The virtual db has id `-1337` and no `engine`. |
293
+
294
+ ### `metabase db get <id>`
295
+
296
+ ```sh
297
+ metabase db get 1
298
+ metabase db get 1 --json
299
+ metabase db get 1 --include tables --full --json # rollup: db + every table (compact)
300
+ metabase db get 1 --include tables.fields --full --json # rollup: db + every table + every field
301
+ ```
302
+
303
+ | Flag | Description |
304
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
305
+ | `--include <which>` | Hydrate related entities. One of `tables` or `tables.fields`. `tables.fields` returns every column of every table in the database in one response — only safe on small/dev warehouses. For a real warehouse use `db schemas` → `db schema-tables` → `table get --include fields`. |
306
+
307
+ ### `metabase db metadata <id>`
308
+
309
+ Equivalent to `GET /api/database/:id/metadata`: a single database with all its tables and fields rolled up in one response. This is the largest read in the `db` group — on a real warehouse the response will exceed the agent context. Use only when you know the database is small (a seeded dev instance, a sample db, a freshly-bootstrapped test fixture). For agent-driven introspection on a real warehouse, walk `db schemas` → `db schema-tables` → `table get --include fields` instead.
310
+
311
+ ```sh
312
+ metabase db metadata 1 --json --full --max-bytes 0
313
+ ```
314
+
315
+ ### `metabase db schemas <id>`
316
+
317
+ List the schemas in a database. Schemas with no tables are excluded. Cheap and bounded — this is the right entry point for an agent walking a warehouse.
318
+
319
+ ```sh
320
+ metabase db schemas 1
321
+ metabase db schemas 1 --json
322
+ ```
323
+
324
+ ### `metabase db schema-tables <id> <schema>`
325
+
326
+ List the tables in one schema, sorted by display name. Returns compact projections without fields — pair with `table get --include fields` (or `table fields <id>`) per table you actually need to introspect.
327
+
328
+ ```sh
329
+ metabase db schema-tables 1 public
330
+ metabase db schema-tables 1 analytics --json
331
+ ```
332
+
333
+ ### `metabase db sync-schema <id>`
334
+
335
+ Trigger a manual schema sync (`POST /api/database/:id/sync_schema`). Returns `{ id, status: "ok" }` once the sync has been queued; the actual work happens asynchronously on the server.
336
+
337
+ ```sh
338
+ metabase db sync-schema 1
339
+ metabase db sync-schema 1 --json
340
+ ```
341
+
342
+ ### `metabase db rescan-values <id>`
343
+
344
+ Trigger a rescan of cached field values (`POST /api/database/:id/rescan_values`). Returns `{ id, status: "ok" }` once the rescan has been queued.
345
+
346
+ ```sh
347
+ metabase db rescan-values 1
348
+ metabase db rescan-values 1 --json
349
+ ```
350
+
351
+ ## Tables
352
+
353
+ Inspect and edit warehouse tables via `/api/table`. For agent-driven field introspection, `table get --include fields` is the default — it returns the table plus its columns in a single bounded response.
354
+
355
+ ### `metabase table list`
356
+
357
+ Returns every table in the chosen database (or across all databases) as a flat compact list — no fields, no per-table hydration. On a real warehouse with hundreds of tables this is still bounded (kilobytes), but `db schema-tables <db-id> <schema>` is the better starting point when you know the schema.
358
+
359
+ ```sh
360
+ metabase table list
361
+ metabase table list --db-id 1 --json
362
+ ```
363
+
364
+ | Flag | Description |
365
+ | -------------- | ----------------------------------- |
366
+ | `--db-id <id>` | Filter tables by their database id. |
367
+
368
+ ### `metabase table get <id>`
369
+
370
+ Returns the basic table record (no fields). Pass `--include fields` to route through `/api/table/:id/query_metadata` so the response carries the table's columns compact-projected as `fields` — this is the default agent path for field introspection. Use `metabase table fields <id>` if you only want the fields as a list envelope, or `metabase table metadata <id>` when you also need FKs and dimensions hydrated.
371
+
372
+ ```sh
373
+ metabase table get 42
374
+ metabase table get 42 --json
375
+ metabase table get 42 --include fields --json
376
+ ```
377
+
378
+ | Flag | Description |
379
+ | ------------------- | --------------------------------------------------------------------------------------------------- |
380
+ | `--include <which>` | Hydrate related entities. Currently only `fields` is supported (bundles compact-projected columns). |
381
+
382
+ ### `metabase table fields <id>`
383
+
384
+ List the fields on a table (a thin projection over `query_metadata.fields`). Use this when you want just the field array without the surrounding table metadata.
385
+
386
+ ```sh
387
+ metabase table fields 42
388
+ metabase table fields 42 --json
389
+ ```
390
+
391
+ ### `metabase table metadata <id>`
392
+
393
+ `GET /api/table/:id/query_metadata`: the table with its fields, FKs, dimensions, segments, and measures all hydrated. Heavier than `table get --include fields` — reach for it only when you actually need the FK / dimension / segment / measure data.
394
+
395
+ ```sh
396
+ metabase table metadata 42 --json --full --max-bytes 0
397
+ ```
398
+
399
+ ### `metabase table update <id>`
400
+
401
+ Patch a table (`PUT /api/table/:id`). Body fields: `display_name`, `description`, `caveats`, `points_of_interest`, `entity_type`, `visibility_type`, `field_order`, `show_in_getting_started`. Pass the body via `--body`, `--file`, or stdin (exactly one).
402
+
403
+ ```sh
404
+ metabase table update 42 --body '{"display_name":"Customers"}'
405
+ metabase table update 42 --file patch.json
406
+ echo '{"description":"Customer dimension"}' | metabase table update 42
407
+ ```
408
+
409
+ ## Fields
410
+
411
+ Inspect and edit individual columns via `/api/field`.
412
+
413
+ ### `metabase field get <id>`
414
+
415
+ ```sh
416
+ metabase field get 100
417
+ metabase field get 100 --json
418
+ ```
419
+
420
+ ### `metabase field values <id>`
421
+
422
+ Fetch the cached distinct values list (`GET /api/field/:id/values`). Returns the FieldValues envelope (`{ values, field_id, has_more_values }`); empty `values` on fields whose `has_field_values` is `none` or `search`.
423
+
424
+ ```sh
425
+ metabase field values 100 --json
426
+ ```
427
+
428
+ ### `metabase field summary <id>`
429
+
430
+ Row count and distinct count for the field (`GET /api/field/:id/summary`). Metabase returns this as an array-of-pairs; the CLI normalizes it to `{ field_id, count, distincts }`.
431
+
432
+ ```sh
433
+ metabase field summary 100
434
+ metabase field summary 100 --json
435
+ ```
436
+
437
+ ### `metabase field update <id>`
438
+
439
+ Patch a field (`PUT /api/field/:id`). Body fields: `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`. Pass the body via `--body`, `--file`, or stdin.
440
+
441
+ ```sh
442
+ metabase field update 100 --body '{"description":"customer email","semantic_type":"type/Email"}'
443
+ metabase field update 100 --file patch.json
444
+ ```
226
445
 
227
446
  ## Cards
228
447
 
@@ -250,21 +469,24 @@ metabase card get 1 --json --detail full
250
469
 
251
470
  ### `metabase card query <id>`
252
471
 
253
- Run the card's query. Without `--export-format`, returns the Metabase JSON envelope (`status`, `row_count`, `data: { rows, cols }`, …). With `--export-format csv` or `--export-format xlsx`, the export bytes stream straight to stdout.
472
+ Run the card's query. Without `--export-format`, returns the Metabase JSON envelope (`status`, `row_count`, `data: { rows, cols }`, …). With `--export-format csv`, `--export-format json`, or `--export-format xlsx`, the export bytes stream straight to stdout.
254
473
 
255
474
  ```sh
256
475
  metabase card query 1 --json
257
476
  metabase card query 1 --json --limit 20
258
477
  metabase card query 1 --export-format csv > export.csv
478
+ metabase card query 1 --export-format json > export.json
259
479
  metabase card query 1 --export-format xlsx > export.xlsx
260
480
  metabase card query 1 --parameters '[{"type":"category","value":"A","target":["variable",["template-tag","c"]]}]'
261
481
  ```
262
482
 
263
- | Flag | Description |
264
- | ----------------------- | ------------------------------------------------------------------------------------------ |
265
- | `--export-format <fmt>` | Stream the export instead of the JSON envelope. One of `csv`, `xlsx`. |
266
- | `--parameters <json>` | JSON array of Metabase parameter objects (the same shape Metabase POSTs from a dashboard). |
267
- | `--limit <n>` | Cap rows kept in the JSON envelope. No effect on `csv` / `xlsx` exports. |
483
+ | Flag | Description |
484
+ | ----------------------- | ------------------------------------------------------------------------------------------------------ |
485
+ | `--export-format <fmt>` | Stream the export instead of the JSON envelope. One of `csv`, `json`, `xlsx`. |
486
+ | `--parameters <json>` | JSON array of Metabase parameter objects (the same shape Metabase POSTs from a dashboard). |
487
+ | `--limit <n>` | Cap rows kept in the JSON envelope. No effect on streamed exports. |
488
+ | `--format-rows` | Streamed exports only: apply the card's visualization-settings formatting to values (default `false`). |
489
+ | `--pivot-results` | Streamed exports only: emit the pivoted output for pivot questions (default `false`). |
268
490
 
269
491
  ### `metabase card create`
270
492
 
@@ -279,15 +501,380 @@ metabase card create --body '{"name":"x","display":"table","dataset_query":{...}
279
501
  | `--body <json>` | Inline JSON body. |
280
502
  | `--file <path>` | Path to JSON body file. |
281
503
 
504
+ ### `metabase card update <id>`
505
+
506
+ Patch a card. Body is a partial subset of the create shape (`name`, `display`, `dataset_query`, `visualization_settings`, `description`, `archived`, `collection_id`, `dashboard_id`, `cache_ttl`, `parameters`, `parameter_mappings`, etc.). Only the keys you send are touched. If `dataset_query` is MBQL 5 (`lib/type: "mbql/query"`) it goes through the same pre-flight validation as `card create` and `metabase query`; pass `--skip-validate` to bypass.
507
+
508
+ ```sh
509
+ cat patch.json | metabase card update 1
510
+ metabase card update 1 --file patch.json
511
+ metabase card update 1 --body '{"name":"renamed"}'
512
+ metabase card update 1 --body '{"display":"bar"}'
513
+ metabase card update 1 --body '{"archived":true}'
514
+ metabase card update 1 --file patch.json --skip-validate
515
+ ```
516
+
517
+ | Flag | Description |
518
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
519
+ | `--body <json>` | Inline JSON body. |
520
+ | `--file <path>` | Path to JSON body file. |
521
+ | `--skip-validate` | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
522
+
282
523
  ### `metabase card archive <id>`
283
524
 
284
- Soft-delete a card by setting `archived: true`. The archived card stays available via `card list --filter archived` and `card get <id>` until permanently deleted server-side.
525
+ Soft-delete a card by setting `archived: true`. The archived card stays available via `card list --filter archived` and `card get <id>` until permanently deleted server-side. To unarchive (or otherwise toggle the flag) use `metabase card update <id> --body '{"archived":false}'`.
285
526
 
286
527
  ```sh
287
528
  metabase card archive 1
288
529
  metabase card archive 1 --json
289
530
  ```
290
531
 
532
+ ## Dashboards
533
+
534
+ Read and write dashboards on `/api/dashboard`. A dashboard groups cards (questions, models, metrics) into a single layout. Each card on a dashboard is a "dashcard" — a placement record with its own id, position (`row`/`col`), and size (`size_x`/`size_y`). Dashcards live nested inside the parent dashboard's `dashcards` array; the API has no per-dashcard endpoint, so single-dashcard edits round-trip through `PUT /api/dashboard/:id`.
535
+
536
+ ### `metabase dashboard list`
537
+
538
+ ```sh
539
+ metabase dashboard list
540
+ metabase dashboard list --json
541
+ metabase dashboard list --filter archived --json
542
+ ```
543
+
544
+ | Flag | Description |
545
+ | ------------------- | ------------------------------------------- |
546
+ | `--filter <preset>` | One of `all` (default), `mine`, `archived`. |
547
+
548
+ ### `metabase dashboard get <id>`
549
+
550
+ ```sh
551
+ metabase dashboard get 1
552
+ metabase dashboard get 1 --json
553
+ metabase dashboard get 1 --json --full
554
+ ```
555
+
556
+ `--full` returns the full hydrated dashboard including the `dashcards` and `tabs` arrays. The default compact view returns only `id`, `name`, `description`, `archived`, and `collection_id`.
557
+
558
+ ### `metabase dashboard cards <id>`
559
+
560
+ List the dashcards on a dashboard.
561
+
562
+ ```sh
563
+ metabase dashboard cards 1
564
+ metabase dashboard cards 1 --json
565
+ ```
566
+
567
+ ### `metabase dashboard create`
568
+
569
+ The body accepts the same dashboard-level fields as the underlying `POST /api/dashboard` (`name`, `description`, `parameters`, `cache_ttl`, `collection_id`, `collection_position`). It also accepts optional `dashcards` and `tabs`: when either is present, the CLI chains a `PUT /api/dashboard/:id` after the create and returns the updated dashboard with its dashcards/tabs applied. Use a negative `id` on a dashcard to indicate one the server should newly create.
570
+
571
+ ```sh
572
+ cat dashboard.json | metabase dashboard create
573
+ metabase dashboard create --file dashboard.json
574
+ metabase dashboard create --body '{"name":"My Dashboard","collection_id":4}'
575
+ metabase dashboard create --body '{"name":"D","dashcards":[{"id":-1,"card_id":42,"row":0,"col":0,"size_x":12,"size_y":6}]}'
576
+ ```
577
+
578
+ | Flag | Description |
579
+ | --------------- | --------------------------------------------------- |
580
+ | `--body <json>` | Inline JSON body. |
581
+ | `--file <path>` | Path to JSON body file. Use `-` to read from stdin. |
582
+
583
+ ### `metabase dashboard update <id>`
584
+
585
+ Patch a dashboard. To edit the dashcard set, send the entire `dashcards` array — IDs not in the array get deleted, and a negative `id` indicates a new dashcard the server should create.
586
+
587
+ ```sh
588
+ cat patch.json | metabase dashboard update 1
589
+ metabase dashboard update 1 --file patch.json
590
+ metabase dashboard update 1 --body '{"name":"renamed"}'
591
+ metabase dashboard update 1 --body '{"dashcards":[{"id":-1,"card_id":42,"row":0,"col":0,"size_x":12,"size_y":6}]}'
592
+ ```
593
+
594
+ ### `metabase dashboard update-dashcard <dashboard-id> <dashcard-id>`
595
+
596
+ Patch a single dashcard's layout or settings. The command does the round-trip for you: `GET /api/dashboard/:id`, merges the patch into the targeted dashcard while preserving every other dashcard verbatim, then `PUT`s the whole array back.
597
+
598
+ ```sh
599
+ metabase dashboard update-dashcard 1 5 --body '{"row":2,"col":0}'
600
+ metabase dashboard update-dashcard 1 5 --body '{"size_x":12,"size_y":4}'
601
+ cat patch.json | metabase dashboard update-dashcard 1 5
602
+ ```
603
+
604
+ | Patch field | Type |
605
+ | ------------------------ | ---------------------------------- |
606
+ | `row`, `col` | non-negative integer |
607
+ | `size_x`, `size_y` | positive integer |
608
+ | `dashboard_tab_id` | integer or `null` |
609
+ | `parameter_mappings` | array of parameter-mapping objects |
610
+ | `inline_parameters` | array of strings |
611
+ | `visualization_settings` | object |
612
+
613
+ The patch must contain at least one field; an empty object is rejected before the network round-trip.
614
+
615
+ ## Snippets
616
+
617
+ CRUD on `/api/native-query-snippet`. A snippet is a named, reusable piece of native (SQL) query text — referenced from cards via `{{snippet: Name}}`. The list endpoint returns either active or archived rows (mutually exclusive — pass `--archived` to swap).
618
+
619
+ ### `metabase snippet list`
620
+
621
+ ```sh
622
+ metabase snippet list
623
+ metabase snippet list --json
624
+ metabase snippet list --archived --json
625
+ ```
626
+
627
+ | Flag | Description |
628
+ | ------------ | ---------------------------------------------- |
629
+ | `--archived` | Show archived snippets instead of active ones. |
630
+
631
+ ### `metabase snippet get <id>`
632
+
633
+ ```sh
634
+ metabase snippet get 1
635
+ metabase snippet get 1 --json --full
636
+ ```
637
+
638
+ ### `metabase snippet create`
639
+
640
+ ```sh
641
+ cat snippet.json | metabase snippet create
642
+ metabase snippet create --file snippet.json
643
+ metabase snippet create --body '{"name":"active","content":"WHERE active = true"}'
644
+ ```
645
+
646
+ | Flag | Description |
647
+ | --------------- | ----------------------- |
648
+ | `--body <json>` | Inline JSON body. |
649
+ | `--file <path>` | Path to JSON body file. |
650
+
651
+ Body fields: `name` (required), `content` (required), `description` (optional), `collection_id` (optional positive integer).
652
+
653
+ ### `metabase snippet update <id>`
654
+
655
+ Patch a snippet. Body is a partial subset of the create shape plus `archived`. Only the keys you send are touched.
656
+
657
+ ```sh
658
+ cat patch.json | metabase snippet update 1
659
+ metabase snippet update 1 --file patch.json
660
+ metabase snippet update 1 --body '{"name":"renamed"}'
661
+ metabase snippet update 1 --body '{"archived":true}'
662
+ ```
663
+
664
+ | Flag | Description |
665
+ | --------------- | ----------------------- |
666
+ | `--body <json>` | Inline JSON body. |
667
+ | `--file <path>` | Path to JSON body file. |
668
+
669
+ ### `metabase snippet archive <id>`
670
+
671
+ Soft-delete a snippet by setting `archived: true`. To unarchive use `metabase snippet update <id> --body '{"archived":false}'`.
672
+
673
+ ```sh
674
+ metabase snippet archive 1
675
+ metabase snippet archive 1 --json
676
+ ```
677
+
678
+ ## Segments
679
+
680
+ CRUD on `/api/segment`. A segment is a saved MBQL filter macro tied to a table — used in card filters to share a reusable predicate. Mutating endpoints require a `revision_message` for the audit log.
681
+
682
+ ### `metabase segment list`
683
+
684
+ ```sh
685
+ metabase segment list
686
+ metabase segment list --json
687
+ ```
688
+
689
+ ### `metabase segment get <id>`
690
+
691
+ ```sh
692
+ metabase segment get 1
693
+ metabase segment get 1 --json --full
694
+ ```
695
+
696
+ ### `metabase segment create`
697
+
698
+ ```sh
699
+ cat segment.json | metabase segment create
700
+ metabase segment create --file segment.json
701
+ metabase segment create --file segment.json --skip-validate
702
+ ```
703
+
704
+ | Flag | Description |
705
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
706
+ | `--body <json>` | Inline JSON body. |
707
+ | `--file <path>` | Path to JSON body file. |
708
+ | `--skip-validate` | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
709
+
710
+ Body fields: `name` (required), `table_id` (required positive integer), `definition` (required MBQL filter object), `description` (optional). If `definition` is MBQL 5 (`lib/type: "mbql/query"`) it goes through the same pre-flight validation as `card create` and `metabase query`; pass `--skip-validate` to bypass.
711
+
712
+ ### `metabase segment update <id>`
713
+
714
+ Patch a segment. The body MUST include `revision_message`. Other keys are partial: `name`, `definition`, `archived`, `description`, `caveats`, `points_of_interest`, `show_in_getting_started`. If `definition` is MBQL 5 (`lib/type: "mbql/query"`) it goes through the same pre-flight validation as `segment create`; pass `--skip-validate` to bypass.
715
+
716
+ ```sh
717
+ cat patch.json | metabase segment update 1
718
+ metabase segment update 1 --file patch.json
719
+ metabase segment update 1 --body '{"name":"renamed","revision_message":"rename"}'
720
+ metabase segment update 1 --file patch.json --skip-validate
721
+ ```
722
+
723
+ | Flag | Description |
724
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
725
+ | `--body <json>` | Inline JSON body. |
726
+ | `--file <path>` | Path to JSON body file. |
727
+ | `--skip-validate` | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
728
+
729
+ ### `metabase segment archive <id>`
730
+
731
+ Soft-delete a segment by setting `archived: true`. The default revision message is `"Archived via metabase CLI"`; override with `--revision-message`.
732
+
733
+ ```sh
734
+ metabase segment archive 1
735
+ metabase segment archive 1 --revision-message "deprecated"
736
+ ```
737
+
738
+ | Flag | Description |
739
+ | --------------------------- | ------------------------------------------- |
740
+ | `--revision-message <text>` | Audit-log message recorded with the change. |
741
+
742
+ ## Measures
743
+
744
+ CRUD on `/api/measure`. A measure is a saved MBQL aggregation (a single `:aggregation` clause) tied to a table — referenced from cards and metrics to share a reusable computation. Mutating endpoints require a `revision_message` for the audit log.
745
+
746
+ ### `metabase measure list`
747
+
748
+ ```sh
749
+ metabase measure list
750
+ metabase measure list --json
751
+ ```
752
+
753
+ ### `metabase measure get <id>`
754
+
755
+ ```sh
756
+ metabase measure get 1
757
+ metabase measure get 1 --json --full
758
+ ```
759
+
760
+ ### `metabase measure create`
761
+
762
+ ```sh
763
+ cat measure.json | metabase measure create
764
+ metabase measure create --file measure.json
765
+ metabase measure create --file measure.json --skip-validate
766
+ ```
767
+
768
+ | Flag | Description |
769
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
770
+ | `--body <json>` | Inline JSON body. |
771
+ | `--file <path>` | Path to JSON body file. |
772
+ | `--skip-validate` | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
773
+
774
+ Body fields: `name` (required), `table_id` (required positive integer), `definition` (required MBQL aggregation object), `description` (optional). If `definition` is MBQL 5 (`lib/type: "mbql/query"`) it goes through the same pre-flight validation as `card create` and `metabase query`; pass `--skip-validate` to bypass.
775
+
776
+ ### `metabase measure update <id>`
777
+
778
+ Patch a measure. The body MUST include `revision_message`. Other keys are partial: `name`, `definition`, `archived`, `description`. If `definition` is MBQL 5 (`lib/type: "mbql/query"`) it goes through the same pre-flight validation as `measure create`; pass `--skip-validate` to bypass.
779
+
780
+ ```sh
781
+ cat patch.json | metabase measure update 1
782
+ metabase measure update 1 --file patch.json
783
+ metabase measure update 1 --body '{"name":"renamed","revision_message":"rename"}'
784
+ metabase measure update 1 --file patch.json --skip-validate
785
+ ```
786
+
787
+ | Flag | Description |
788
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
789
+ | `--body <json>` | Inline JSON body. |
790
+ | `--file <path>` | Path to JSON body file. |
791
+ | `--skip-validate` | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
792
+
793
+ ### `metabase measure archive <id>`
794
+
795
+ Soft-delete a measure by setting `archived: true`. The default revision message is `"Archived via metabase CLI"`; override with `--revision-message`.
796
+
797
+ ```sh
798
+ metabase measure archive 1
799
+ metabase measure archive 1 --revision-message "deprecated"
800
+ ```
801
+
802
+ | Flag | Description |
803
+ | --------------------------- | ------------------------------------------- |
804
+ | `--revision-message <text>` | Audit-log message recorded with the change. |
805
+
806
+ ## Collections
807
+
808
+ Read collections on `/api/collection`. Collections are the folders that contain cards, dashboards, and other collections. The list endpoint surfaces a virtual root collection (id `"root"`) alongside regular numeric ids; the get endpoint accepts only the numeric id.
809
+
810
+ ### `metabase collection list`
811
+
812
+ ```sh
813
+ metabase collection list
814
+ metabase collection list --json
815
+ metabase collection list --filter archived --json
816
+ ```
817
+
818
+ | Flag | Description |
819
+ | ------------------- | --------------------------------------------------------------------------------------------------------------- |
820
+ | `--filter <preset>` | One of `all` (default), `archived` (returns the trash collection only), `personal` (only personal collections). |
821
+
822
+ ### `metabase collection get <id>`
823
+
824
+ `<id>` accepts any of: a positive integer collection id, the literal `root` (the virtual "Our analytics" root), the literal `trash` (the trash collection), or a 21-character entity id (NanoID). Anything else is rejected with a `ConfigError` before any HTTP call.
825
+
826
+ ```sh
827
+ metabase collection get 4
828
+ metabase collection get root --json
829
+ metabase collection get trash --json
830
+ metabase collection get voo1If9y8Sld0lXej6xl0 --json
831
+ metabase collection get 4 --json --full
832
+ ```
833
+
834
+ `--full` returns the full hydrated collection including `slug`, `entity_id`, `can_write`, `namespace`, and `personal_owner_id`. The default compact view returns `id`, `name`, `description`, `archived`, `location`, `parent_id`, `type`, `authority_level`, and `is_personal`. The root collection has a stripped-down shape — `archived`, `description`, `location`, `type`, etc. are absent rather than `null`.
835
+
836
+ ### `metabase collection items <id>`
837
+
838
+ List the cards, dashboards, sub-collections, and other content stored inside a collection. The CLI drains all pages of `/api/collection/:id/items`; pass `--limit` to cap the result. `<id>` accepts the same forms as `collection get` — including `root` for top-level content (items there have `collection_id: null`).
839
+
840
+ ```sh
841
+ metabase collection items 4
842
+ metabase collection items root --json
843
+ metabase collection items 4 --models card,dashboard --json
844
+ metabase collection items 4 --pinned-state is_pinned --json
845
+ ```
846
+
847
+ | Flag | Description |
848
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------ |
849
+ | `--models <csv>` | Restrict to one or more models (`card`, `dataset`, `metric`, `dashboard`, `snippet`, `collection`, `document`, …). |
850
+ | `--archived` | Return archived items instead of unarchived. |
851
+ | `--pinned-state <state>` | One of `all`, `is_pinned`, `is_not_pinned`. |
852
+ | `--limit <n>` | Cap total items returned. Default: drain all pages. |
853
+
854
+ ### `metabase collection tree`
855
+
856
+ Fetch the full collection hierarchy as a nested tree. Output is always JSON — the recursive structure does not render meaningfully as a key/value table.
857
+
858
+ ```sh
859
+ metabase collection tree
860
+ metabase collection tree --json
861
+ ```
862
+
863
+ ### `metabase collection create`
864
+
865
+ Create a collection from a JSON spec. The body accepts the same fields as `POST /api/collection`: `name` (required), `description`, `parent_id` (omit or `null` for the root), `namespace`, and `authority_level`.
866
+
867
+ ```sh
868
+ cat collection.json | metabase collection create
869
+ metabase collection create --file collection.json
870
+ metabase collection create --body '{"name":"My Collection","parent_id":4}'
871
+ ```
872
+
873
+ | Flag | Description |
874
+ | --------------- | --------------------------------------------------- |
875
+ | `--body <json>` | Inline JSON body. |
876
+ | `--file <path>` | Path to JSON body file. Use `-` to read from stdin. |
877
+
291
878
  ## Settings
292
879
 
293
880
  Read and write Metabase instance settings via `/api/setting`. Listing all settings requires admin privileges; per-key reads/writes additionally enforce per-setting access. Setting values are always JSON — `"main"` is the string `main`, `42` is a number, `null` deletes the override and resets the value to its default.
@@ -348,73 +935,73 @@ metabase search products --archived
348
935
  | `--table-db-id` | Restrict to items on a given database id. |
349
936
  | `--verified` | Only verified content. |
350
937
 
351
- ## Sync
938
+ ## Git Sync
352
939
 
353
- Drive Metabase Enterprise Remote Sync (`/api/ee/remote-sync`) — import / export Metabase content against a configured git remote, inspect dirty state, and manage branches. All sync commands require an active EE token and superuser credentials.
940
+ Drive Metabase Enterprise Remote Sync (`/api/ee/remote-sync`) — import / export Metabase content against a configured git remote, inspect dirty state, and manage branches. All git-sync commands require an active EE token and superuser credentials.
354
941
 
355
- ### `metabase sync status`
942
+ ### `metabase git-sync status`
356
943
 
357
944
  Roll up the current sync state in one call: configured branch, dirty flag, and the most recent sync task (or `null` if none has ever run).
358
945
 
359
946
  ```sh
360
- metabase sync status
361
- metabase sync status --json
947
+ metabase git-sync status
948
+ metabase git-sync status --json
362
949
  ```
363
950
 
364
- ### `metabase sync is-dirty`
951
+ ### `metabase git-sync is-dirty`
365
952
 
366
953
  Boolean check for whether any synced collection has unsynced local changes.
367
954
 
368
955
  ```sh
369
- metabase sync is-dirty --json
956
+ metabase git-sync is-dirty --json
370
957
  ```
371
958
 
372
- ### `metabase sync has-remote-changes`
959
+ ### `metabase git-sync has-remote-changes`
373
960
 
374
961
  Compare the latest version on the remote branch against the version Metabase last imported. Cached for a short TTL server-side; pass `--force-refresh` to bypass.
375
962
 
376
963
  ```sh
377
- metabase sync has-remote-changes
378
- metabase sync has-remote-changes --force-refresh --json
964
+ metabase git-sync has-remote-changes
965
+ metabase git-sync has-remote-changes --force-refresh --json
379
966
  ```
380
967
 
381
968
  | Flag | Description |
382
969
  | ----------------- | --------------------------------------------------- |
383
970
  | `--force-refresh` | Bypass the in-memory cache and re-check the remote. |
384
971
 
385
- ### `metabase sync dirty`
972
+ ### `metabase git-sync dirty`
386
973
 
387
974
  List every object that has unsynced local changes (compact list envelope; `--full` for the per-row payload).
388
975
 
389
976
  ```sh
390
- metabase sync dirty
391
- metabase sync dirty --json
977
+ metabase git-sync dirty
978
+ metabase git-sync dirty --json
392
979
  ```
393
980
 
394
- ### `metabase sync current-task`
981
+ ### `metabase git-sync current-task`
395
982
 
396
983
  Fetch the most recent sync task. Renders `{ status: "idle" }` when no task has ever run, otherwise the full task with its hydrated `status`.
397
984
 
398
985
  ```sh
399
- metabase sync current-task
400
- metabase sync current-task --json
986
+ metabase git-sync current-task
987
+ metabase git-sync current-task --json
401
988
  ```
402
989
 
403
- ### `metabase sync cancel-task`
990
+ ### `metabase git-sync cancel-task`
404
991
 
405
992
  Cancel the currently running sync task. Fails with HTTP 400 if no task is running.
406
993
 
407
994
  ```sh
408
- metabase sync cancel-task --json
995
+ metabase git-sync cancel-task --json
409
996
  ```
410
997
 
411
- ### `metabase sync wait`
998
+ ### `metabase git-sync wait`
412
999
 
413
1000
  Poll `/current-task` until it reaches a terminal status (`successful`, `errored`, `cancelled`, `timed-out`, `conflict`). Exits 0 on `successful` or `cancelled`; exits 1 on `errored` / `timed-out` / `conflict`. Returns immediately with `{ status: "idle" }` if no task is running.
414
1001
 
415
1002
  ```sh
416
- metabase sync wait
417
- metabase sync wait --timeout 300000 --json
1003
+ metabase git-sync wait
1004
+ metabase git-sync wait --timeout 300000 --json
418
1005
  ```
419
1006
 
420
1007
  | Flag | Description |
@@ -422,14 +1009,14 @@ metabase sync wait --timeout 300000 --json
422
1009
  | `--timeout <ms>` | Polling timeout in ms (default 600000). |
423
1010
  | `--interval <ms>` | Polling interval in ms (default 2000). |
424
1011
 
425
- ### `metabase sync import`
1012
+ ### `metabase git-sync import`
426
1013
 
427
1014
  Import content from the configured git remote into Metabase (repo → Metabase). Auto-polls until the resulting task reaches a terminal status; pass `--no-wait` to return immediately after kickoff.
428
1015
 
429
1016
  ```sh
430
- metabase sync import
431
- metabase sync import --branch main --json
432
- metabase sync import --force --no-wait
1017
+ metabase git-sync import
1018
+ metabase git-sync import --branch main --json
1019
+ metabase git-sync import --force --no-wait
433
1020
  ```
434
1021
 
435
1022
  | Flag | Description |
@@ -440,14 +1027,14 @@ metabase sync import --force --no-wait
440
1027
  | `--timeout <ms>` | Polling timeout in ms (default 600000). Used with `--wait`. |
441
1028
  | `--interval <ms>` | Polling interval in ms (default 2000). Used with `--wait`. |
442
1029
 
443
- ### `metabase sync export`
1030
+ ### `metabase git-sync export`
444
1031
 
445
1032
  Export Metabase changes back to the configured git remote (Metabase → repo). Auto-polls by default.
446
1033
 
447
1034
  ```sh
448
- metabase sync export -m "update dashboards"
449
- metabase sync export --branch main --json
450
- metabase sync export --no-wait
1035
+ metabase git-sync export -m "update dashboards"
1036
+ metabase git-sync export --branch main --json
1037
+ metabase git-sync export --no-wait
451
1038
  ```
452
1039
 
453
1040
  | Flag | Description |
@@ -459,13 +1046,13 @@ metabase sync export --no-wait
459
1046
  | `--timeout <ms>` | Polling timeout in ms (default 600000). Used with `--wait`. |
460
1047
  | `--interval <ms>` | Polling interval in ms (default 2000). Used with `--wait`. |
461
1048
 
462
- ### `metabase sync stash`
1049
+ ### `metabase git-sync stash`
463
1050
 
464
1051
  Export the current Metabase state to a NEW branch on the remote and switch sync to it. Requires `remote-sync-type` to be `read-write`.
465
1052
 
466
1053
  ```sh
467
- metabase sync stash --new-branch wip
468
- metabase sync stash --new-branch wip -m "work in progress" --json
1054
+ metabase git-sync stash --new-branch wip
1055
+ metabase git-sync stash --new-branch wip -m "work in progress" --json
469
1056
  ```
470
1057
 
471
1058
  | Flag | Description |
@@ -476,23 +1063,351 @@ metabase sync stash --new-branch wip -m "work in progress" --json
476
1063
  | `--timeout <ms>` | Polling timeout in ms. Used with `--wait`. |
477
1064
  | `--interval <ms>` | Polling interval in ms. Used with `--wait`. |
478
1065
 
479
- ### `metabase sync branches`
1066
+ ### `metabase git-sync branches`
480
1067
 
481
1068
  List branches available on the configured git remote.
482
1069
 
483
1070
  ```sh
484
- metabase sync branches --json
1071
+ metabase git-sync branches --json
485
1072
  ```
486
1073
 
487
- ### `metabase sync create-branch <name>`
1074
+ ### `metabase git-sync create-branch <name>`
488
1075
 
489
1076
  Create a new branch on the git remote (from the last imported version) and switch sync to it.
490
1077
 
491
1078
  ```sh
492
- metabase sync create-branch feat/dashboards
493
- metabase sync create-branch feat/x --json
1079
+ metabase git-sync create-branch feat/dashboards
1080
+ metabase git-sync create-branch feat/x --json
1081
+ ```
1082
+
1083
+ ### `metabase git-sync add-collection <id>`
1084
+
1085
+ Mark a collection as git-synced. The toggle cascades to every descendant by `location` prefix, so flagging a parent flags the whole subtree. Returns `{ success, task_id? }`; `task_id` only appears when the toggle triggers a follow-up task (e.g. a finalization import after switching to read-only mode).
1086
+
1087
+ ```sh
1088
+ metabase git-sync add-collection 12
1089
+ metabase git-sync add-collection 12 --json --profile prod
1090
+ ```
1091
+
1092
+ The server rejects toggles while `remote-sync-type` is `read-only` (the install default). Switch first with `metabase setting set remote-sync-type '"read-write"'`.
1093
+
1094
+ ### `metabase git-sync remove-collection <id>`
1095
+
1096
+ Unmark a collection as git-synced. Same cascade and same `read-only` precondition as `add-collection`.
1097
+
1098
+ ```sh
1099
+ metabase git-sync remove-collection 12
1100
+ metabase git-sync remove-collection 12 --json --profile prod
1101
+ ```
1102
+
1103
+ ## Workspaces
1104
+
1105
+ CRUD on `/api/ee/workspace-manager`. Run against the workspace-manager parent instance.
1106
+
1107
+ ### `metabase workspace list`
1108
+
1109
+ ```sh
1110
+ metabase workspace list
1111
+ metabase workspace list --json
1112
+ ```
1113
+
1114
+ ### `metabase workspace create`
1115
+
1116
+ ```sh
1117
+ metabase workspace create --name analytics
1118
+ echo '{"name":"analytics"}' | metabase workspace create
1119
+ metabase workspace create --file workspace.json
1120
+ ```
1121
+
1122
+ | Flag | Description |
1123
+ | --------------- | ------------------------------------------------------- |
1124
+ | `--name <name>` | Workspace name. Shortcut for `--body '{"name":"<n>"}'`. |
1125
+ | `--body <json>` | Inline JSON body. |
1126
+ | `--file <path>` | Path to JSON body file. |
1127
+
1128
+ ### `metabase workspace database provision <workspace-id>`
1129
+
1130
+ Provision a database into a workspace. The backend kicks off the work asynchronously and returns the workspace with the new entry in `status: "provisioning"`. Pass `--wait` to poll until the entry reaches `status: "provisioned"` and surface the polled state instead of the initial response.
1131
+
1132
+ ```sh
1133
+ metabase workspace database provision 1 --database-id 5 --schemas analytics,github
1134
+ metabase workspace database provision 1 --database-id 5 --schemas analytics --wait
1135
+ metabase workspace database provision 1 --file provision.json
1136
+ ```
1137
+
1138
+ | Flag | Description |
1139
+ | -------------------- | -------------------------------------------------------------- |
1140
+ | `--database-id <id>` | Database id (used with `--schemas`). |
1141
+ | `--schemas <csv>` | Comma-separated input schemas (used with `--database-id`). |
1142
+ | `--body <json>` | Inline JSON body. |
1143
+ | `--file <path>` | Path to JSON body file. |
1144
+ | `--wait` | Poll until the database entry reaches `status: "provisioned"`. |
1145
+ | `--timeout <ms>` | Polling timeout in ms (default 600000). Used with `--wait`. |
1146
+ | `--interval <ms>` | Polling interval in ms (default 2000). Used with `--wait`. |
1147
+
1148
+ ### `metabase workspace database update <workspace-id> <db-id>`
1149
+
1150
+ Update a workspace's provisioned database (server-side this is deprovision + provision). Body accepts only `input` — the database id comes from the URL.
1151
+
1152
+ ```sh
1153
+ metabase workspace database update 1 5 --schemas analytics,github
1154
+ metabase workspace database update 1 5 --schemas analytics --wait
1155
+ metabase workspace database update 1 5 --file update.json
1156
+ ```
1157
+
1158
+ | Flag | Description |
1159
+ | ----------------- | ----------------------------------------------------------------- |
1160
+ | `--schemas <csv>` | Comma-separated input schemas. Shortcut for body. |
1161
+ | `--body <json>` | Inline JSON body (`{"input":[{"schema":"..."}]}`). |
1162
+ | `--file <path>` | Path to JSON body file. |
1163
+ | `--wait` | Poll until the database entry returns to `status: "provisioned"`. |
1164
+ | `--timeout <ms>` | Polling timeout in ms (default 600000). Used with `--wait`. |
1165
+ | `--interval <ms>` | Polling interval in ms (default 2000). Used with `--wait`. |
1166
+
1167
+ ### `metabase workspace database deprovision <workspace-id> <db-id>`
1168
+
1169
+ ```sh
1170
+ metabase workspace database deprovision 1 5 --yes
1171
+ metabase workspace database deprovision 1 5 --yes --wait
1172
+ ```
1173
+
1174
+ | Flag | Description |
1175
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------- |
1176
+ | `--yes` | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
1177
+ | `--wait` | Poll until the database entry is removed from the workspace. |
1178
+ | `--timeout <ms>` | Polling timeout in ms (default 600000). Used with `--wait`. |
1179
+ | `--interval <ms>` | Polling interval in ms (default 2000). Used with `--wait`. |
1180
+
1181
+ ### Local runtime
1182
+
1183
+ These commands manage a Docker container that serves as the workspace's child Metabase instance. State lives in Docker labels and a named volume — there is no per-workspace local state directory. The container is named `metabase-workspace-<id>`; the app-db volume is `metabase-workspace-<id>-appdb`.
1184
+
1185
+ ### `metabase workspace start <id>`
1186
+
1187
+ ```sh
1188
+ metabase workspace start 1
1189
+ metabase workspace start 1 --wait
1190
+ metabase workspace start 1 --port 3100
1191
+ metabase workspace start 1 --image metabase/metabase-dev:feature-workspaces-v2 --no-pull
1192
+ metabase workspace start 1 --force
1193
+ metabase workspace start 1 --repo /path/to/sync-repo --wait
1194
+ metabase workspace start 1 --repo /path/to/sync-repo --repo-branch dev --repo-mode read-only
1195
+ ```
1196
+
1197
+ Resolves the parent via the active profile (or `--profile`/`--url`/`--api-key`) and the EE license via `resolveLicenseToken` (the same path `metabase license set` writes to). Refuses to start if the workspace has any database that isn't `status: "provisioned"`.
1198
+
1199
+ The boot bundle (`config.yml`, `credentials.json`, optional `metadata.json`) is built in process memory and tar-streamed into the container's `/mw-config/` directory through `docker cp -`; no host-disk artifact is created. The CLI generates a per-workspace admin user + API key, injects them into the YAML before shipping, and stores the same values in `credentials.json` for later retrieval via `metabase workspace credentials`. Once the child logs that it has read `config.yml`, the CLI scrubs the in-container copy (`docker exec rm /mw-config/config.yml`) so the warehouse credentials in `details.password` no longer linger; `credentials.json` stays.
1200
+
1201
+ By default `start` returns once the bundle has been consumed by the child (`state: "starting"`); pass `--wait` to also block until `/api/health` reports ready and the response reports `state: "running"`.
1202
+
1203
+ When `--repo <host-path>` is passed, the CLI bind-mounts the host directory at `/mnt/repo` inside the container and injects three settings into the workspace's `config.yml` so the child boots already wired to the repo: `remote-sync-url=file:///mnt/repo`, `remote-sync-branch=<branch>` (defaults to the current branch of the host repo, read via `git -C <path> symbolic-ref --short HEAD`; override with `--repo-branch`), and `remote-sync-type=<mode>` (defaults to `read-write`; override with `--repo-mode read-only`, which also makes the bind mount read-only). The bind mount is set at container-create time only — to add or change it after the fact, run `start --force` again with the new flags. The host path must be an existing directory; the CLI does not create or `git init` it for you.
1204
+
1205
+ | Flag | Description |
1206
+ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1207
+ | `--port <n>` | Host port (default: 3000; auto-shifts up to 100 ports if taken). |
1208
+ | `--image <ref>` | Docker image (default: `metabase/metabase-dev:feature-workspaces-v2`). |
1209
+ | `--wait` | Block until `/api/health` is ready. Default: return as soon as consumed. |
1210
+ | `--timeout <ms>` | Per-phase readiness deadline (default: 240000). Covers post-create config consumption, (with `--wait`) the `/api/health` probe, and (with `--metadata`) the metadata-import status poll. |
1211
+ | `--no-pull` | Skip `docker pull` (useful if the image is already present). |
1212
+ | `--no-metadata` | Skip the warehouse metadata export. |
1213
+ | `--force` | If a container for this workspace already exists, remove it before starting. |
1214
+ | `--repo <host-path>` | Bind-mount a host directory at `/mnt/repo` and set `remote-sync-url=file:///mnt/repo` in `config.yml`. |
1215
+ | `--repo-branch <name>` | `remote-sync-branch` value (default: current branch of the host repo). |
1216
+ | `--repo-mode <mode>` | `remote-sync-type`: `read-write` (default) or `read-only`. Read-only also makes the bind mount read-only. |
1217
+
1218
+ ### `metabase workspace stop <id>`
1219
+
1220
+ ```sh
1221
+ metabase workspace stop 1
1222
+ metabase workspace stop 1 --json
1223
+ ```
1224
+
1225
+ Stops the running container; no-ops if it's already exited or missing. Reports the prior state.
1226
+
1227
+ ### `metabase workspace remove <id>`
1228
+
1229
+ ```sh
1230
+ metabase workspace remove 1 --yes
1231
+ metabase workspace remove 1 --keep-volume --yes
1232
+ ```
1233
+
1234
+ Stops and removes the container. By default, also removes the app-db volume — pass `--keep-volume` to preserve it across rebuilds. **Does not affect the remote workspace** on the parent.
1235
+
1236
+ | Flag | Description |
1237
+ | --------------- | --------------------------------------------------------------------------------------------------------------------------------- |
1238
+ | `--yes` | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
1239
+ | `--keep-volume` | Preserve the app-db volume (`metabase-workspace-<id>-appdb`). |
1240
+
1241
+ ### `metabase workspace logs <id>`
1242
+
1243
+ ```sh
1244
+ metabase workspace logs 1
1245
+ metabase workspace logs 1 --follow
1246
+ metabase workspace logs 1 --tail 500
1247
+ ```
1248
+
1249
+ Passthrough to `docker logs`. Output streams directly to your terminal; Ctrl-C terminates a follow.
1250
+
1251
+ | Flag | Description |
1252
+ | -------------- | --------------------------------------------- |
1253
+ | `--follow, -f` | Stream indefinitely. |
1254
+ | `--tail <n>` | Lines from the end of the logs (default 200). |
1255
+
1256
+ ### `metabase workspace url <id>`
1257
+
1258
+ ```sh
1259
+ metabase workspace url 1
1260
+ metabase workspace url 1 --json
1261
+ ```
1262
+
1263
+ Prints `http://localhost:<port>` for the workspace's container. Reads the host port from the container's `com.metabase.workspace.host-port` label.
1264
+
1265
+ ### `metabase workspace credentials <id>`
1266
+
1267
+ ```sh
1268
+ metabase workspace credentials 1
1269
+ metabase workspace credentials 1 --json
1270
+ ```
1271
+
1272
+ Reads the workspace child's admin credentials (email, password, admin API key) from `/mw-config/credentials.json` inside the container. The file is written by `workspace start` from CLI-generated, per-workspace values; the same values are injected into `config.yml`'s `:users` and `:api-keys` sections so they take effect on the child's first boot. Works against running and stopped containers (uses `docker cp`); errors clearly if no container exists for the given workspace id. Removing the container destroys the file — recover by `workspace start <id> --force`.
1273
+
1274
+ ### `metabase workspace ps`
1275
+
1276
+ ```sh
1277
+ metabase workspace ps
1278
+ metabase workspace ps --json
1279
+ ```
1280
+
1281
+ Lists every container that carries the `com.metabase.workspace.id` label, running or stopped. The `--json` envelope is the canonical agent-facing shape and contains only `workspace_id`, `workspace_name`, `state`, and `url`; `--full --json` emits the wider record (image, profile, parent URL, container name, status string, host port).
1282
+
1283
+ ## Instance setup
1284
+
1285
+ Operations against a workspace-instance Metabase. The setup wizard and API key creation are distinct endpoints — there is no shared body schema.
1286
+
1287
+ ### `metabase setup`
1288
+
1289
+ Complete the initial setup wizard (`POST /api/setup`). The body must include the setup token, the default user, and the `prefs` block (with `site_name`).
1290
+
1291
+ ```sh
1292
+ cat setup.json | metabase setup
1293
+ metabase setup --file setup.json
1294
+ metabase setup --body '{"token":"<setup-token>","user":{"email":"a@b.c","password":"..."},"prefs":{"site_name":"Acme"}}'
1295
+ ```
1296
+
1297
+ | Flag | Description |
1298
+ | --------------- | ----------------------- |
1299
+ | `--body <json>` | Inline JSON body. |
1300
+ | `--file <path>` | Path to JSON body file. |
1301
+
1302
+ ### `metabase api-key create`
1303
+
1304
+ Create a new API key (`POST /api/api-key`). The unmasked key is returned on creation only; capture it from the output.
1305
+
1306
+ ```sh
1307
+ metabase api-key create --name deploy-bot --group-id 2
1308
+ echo '{"name":"k","group_id":2}' | metabase api-key create
1309
+ metabase api-key create --file key.json
1310
+ ```
1311
+
1312
+ | Flag | Description |
1313
+ | ----------------- | ----------------------------------------- |
1314
+ | `--name <name>` | API key name (used with `--group-id`). |
1315
+ | `--group-id <id>` | Permission group id (used with `--name`). |
1316
+ | `--body <json>` | Inline JSON body. |
1317
+ | `--file <path>` | Path to JSON body file. |
1318
+
1319
+ ## Agent helpers
1320
+
1321
+ Endpoints commonly used by agents driving the instance. `card query` and `transform run` are documented in their own sections; the helper below covers entity-id translation.
1322
+
1323
+ ### `metabase eid translate`
1324
+
1325
+ Translate string entity ids (EIDs) to numeric ids (`POST /api/eid-translation/translate`).
1326
+
1327
+ ```sh
1328
+ metabase eid translate --model card --eids abc123XYZ,def456ABC
1329
+ metabase eid translate --file translate.json
1330
+ metabase eid translate --body '{"entity_ids":{"card":["abc123XYZ"]}}'
1331
+ ```
1332
+
1333
+ | Flag | Description |
1334
+ | ---------------- | ------------------------------------------------------------------------------------------------ |
1335
+ | `--model <name>` | Entity model for the shortcut form (e.g. `card`, `dashboard`, `collection`). Used with `--eids`. |
1336
+ | `--eids <csv>` | Comma-separated EIDs. Used with `--model`. |
1337
+ | `--body <json>` | Inline JSON body. |
1338
+ | `--file <path>` | Path to JSON body file. |
1339
+
1340
+ ## Query
1341
+
1342
+ ### `metabase query`
1343
+
1344
+ Run an MBQL 5 query with built-in schema validation. Three modes — discover the schema (`--print-schema`), validate without sending (`--dry-run`), run.
1345
+
1346
+ MBQL 5 bodies use numeric IDs (`database: 1`, `source-table: 7`) and POST to `/api/dataset`. The bundled query schema is synced from `@metabase/representations`; `id.yaml` is overridden to require positive integers for every ID `$def`.
1347
+
1348
+ ```sh
1349
+ metabase query --print-schema # JSON Schema bundle
1350
+ cat q.json | metabase query --dry-run # validate, no network
1351
+ metabase query --file q.json
1352
+ metabase query --file q.json --skip-validate # bypass pre-flight; let server reject
1353
+ ```
1354
+
1355
+ Body sources: `--file`, `--body`, or stdin (exactly one). Body is JSON.
1356
+
1357
+ Any non-MBQL 5 body skips pre-flight automatically — legacy MBQL 4 (`{ "type": "query", "database": N, "query": { "source-table": T, ... } }`), legacy native (`{ "type": "native", "database": N, "native": { "query": "..." } }`), or any other shape that doesn't carry `"lib/type": "mbql/query"`. The bundled schema only models MBQL 5; `/api/dataset` normalizes the rest server-side via `lib-be/normalize-query` (the same normalizer that backs `card create` / `transform create`), so behavior is symmetric across endpoints. `--dry-run` on a non-MBQL 5 body emits `{ ok: true, errors: [] }` (no schema applies). The double-wrap footgun — an MBQL 5 query nested inside a `{type:"query", query:…}` envelope — is still rejected with a `ConfigError` before send.
1358
+
1359
+ `--skip-validate` is an escape hatch when the bundled schema disagrees with what the server actually accepts (drift, false negative, edge case) for MBQL 5 bodies. Validation is skipped entirely and the body is sent as-is. Mutually exclusive with `--dry-run` (which is itself the validation mode).
1360
+
1361
+ Exit codes:
1362
+
1363
+ - `0` — valid (and the query ran successfully when not in dry-run).
1364
+ - `2` — validation failed, malformed body, or `ConfigError`.
1365
+ - `1` — server-side error after a valid pre-flight (network, HTTP 4xx/5xx).
1366
+
1367
+ Output by mode:
1368
+
1369
+ - `--print-schema` — `{ schema, defs: { "id.yaml", "parameter.yaml", "ref.yaml", "temporal_bucketing.yaml" } }`. The query schema's `$ref`s point into the `defs` namespace by file path; an agent can either feed the bundle directly into Ajv (`addSchema(defs["id.yaml"], "id.yaml")` etc., then `compile(schema)`) or read it as documentation.
1370
+ - `--dry-run` — `{ ok: boolean, errors: { path: string, message: string }[] }`. `path` is a JSON Pointer into the body, `message` is the Ajv error string.
1371
+ - Run failure (no `--dry-run`) — same `{ ok, errors }` envelope on stdout, exit 2, no request made.
1372
+ - Run success — the streamed `CardQueryResult`.
1373
+
1374
+ ### MBQL 5 pre-flight in `card create`/`update`, `transform create`/`update`, `measure create`/`update`, and `segment create`/`update`
1375
+
1376
+ When the embedded query (`card.dataset_query`, `transform.source.query` for `source.type: "query"`, or `measure.definition` / `segment.definition`) is MBQL 5 (`lib/type: "mbql/query"`), it is pre-flight-validated against the same schema as `metabase query`. Validation failure: `{ ok, errors }` envelope on stdout, exit 2, request not made. MBQL 4 (legacy) bodies and Python transform sources skip validation — they're still accepted by the server and we don't ship a schema for them.
1377
+
1378
+ Pass `--skip-validate` to bypass the pre-flight on any of `card create`, `card update`, `transform create`, `transform update`, `measure create`, `measure update`, `segment create`, or `segment update` — the body is sent as-is and the server is the authority. Same escape hatch as on `metabase query`; use only when the bundled schema disagrees with what the server actually accepts.
1379
+
1380
+ Agent discovery path: `metabase __manifest` lists every command's args and description; the description for `card create`/`update`, `transform create`/`update`, `measure create`/`update`, and `segment create`/`update` references `metabase query --print-schema` so an agent can fetch the validating schema directly.
1381
+
1382
+ The bundled query schema is synced from a pinned `@metabase/representations` release via `bun run sync:representations`; CI guards against drift.
1383
+
1384
+ ### Card-reference pre-flight in `dashboard create` / `dashboard update`
1385
+
1386
+ Before either command sends anything, every positive `card_id` referenced from the body's `dashcards` array is checked against `GET /api/card/:id` in parallel (de-duplicated per id). Cards that don't exist, are archived, or aren't readable fail pre-flight: the CLI writes a `{ ok: false, errors: [{ path, message }] }` envelope to stdout (one entry per offending dashcard, `path` is a JSON pointer like `/dashcards/3/card_id`) and exits **2** with `dashboard card-reference pre-flight failed: N error(s) — fix the dashcard card_id values listed above` on stderr. No dashboard is created or modified on a pre-flight miss — this is the contract that prevents orphan dashboards when a stale spec references an archived or missing card.
1387
+
1388
+ There is no `--skip-validate` escape hatch here. The pre-flight queries live server state (no bundled schema to drift from), so the only legitimate path on a pre-flight miss is to fix the input.
1389
+
1390
+ If the chained `PUT /api/dashboard/:id` fails _after_ the create has already inserted the row (rare with pre-flight in place, but possible on a permission / 5xx / network failure mid-flight), the user-facing error is rewritten to `dashboard <id> created but follow-up PUT /api/dashboard/<id> failed: <reason>; dashcards not applied`, so the caller knows the orphan exists. Recovery: `dashboard update <id> --body '{"dashcards":[...]}'` to retry the dashcards, or `dashboard update <id> --body '{"archived":true}'` to archive the orphan.
1391
+
1392
+ ## UUIDs
1393
+
1394
+ ### `metabase uuid`
1395
+
1396
+ Mint UUID v4 strings (Node `crypto.randomUUID`) for MBQL clause `lib/uuid` slots, native template-tag ids, and any other Metabase-side identifier whose schema enforces RFC 4122 format. Agents must call this command to obtain UUIDs rather than authoring them by hand: the bundled MBQL 5 schema rejects placeholder strings (`a1`, `uuid-1`, etc.) at `format: "uuid"` validation.
1397
+
1398
+ ```sh
1399
+ metabase uuid # one UUID
1400
+ metabase uuid --count 5 # five UUIDs, one per line (text mode in a TTY, JSON when piped)
1401
+ metabase uuid --count 5 --json # explicit JSON: ["…", "…", "…", "…", "…"]
1402
+ metabase uuid --count 5 --format text # explicit text: one UUID per line
494
1403
  ```
495
1404
 
1405
+ Output: text mode prints one UUID per line; JSON mode prints a `string[]`. Default behavior follows the standard `--format auto` rule — JSON when stdout is a pipe, text when it's a TTY.
1406
+
1407
+ `--count` accepts integers `1` through `10000`; outside that range exits 2 with a `ConfigError`.
1408
+
1409
+ Exit codes: `0` success, `2` invalid `--count`.
1410
+
496
1411
  ## Environment variables
497
1412
 
498
1413
  | Variable | Effect |